Skip to content

Commit

Permalink
gpio: Implement tighter IRQ chip integration
Browse files Browse the repository at this point in the history
Currently GPIO drivers are required to add the GPIO chip and its
corresponding IRQ chip separately, which can result in a lot of
boilerplate. Use the newly introduced struct gpio_irq_chip, embedded in
struct gpio_chip, that drivers can fill in if they want the GPIO core
to automatically register the IRQ chip associated with a GPIO chip.

Signed-off-by: Thierry Reding <treding@nvidia.com>
Acked-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
  • Loading branch information
thierryreding authored and linusw committed Nov 8, 2017
1 parent ca9df05 commit e0d8972
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 1 deletion.
108 changes: 107 additions & 1 deletion drivers/gpio/gpiolib.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ static LIST_HEAD(gpio_lookup_list);
LIST_HEAD(gpio_devices);

static void gpiochip_free_hogs(struct gpio_chip *chip);
static int gpiochip_add_irqchip(struct gpio_chip *gpiochip);
static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip);
static int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gpiochip);
static void gpiochip_irqchip_free_valid_mask(struct gpio_chip *gpiochip);
Expand Down Expand Up @@ -1266,6 +1267,10 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data)
if (status)
goto err_remove_from_list;

status = gpiochip_add_irqchip(chip);
if (status)
goto err_remove_chip;

status = of_gpiochip_add(chip);
if (status)
goto err_remove_chip;
Expand Down Expand Up @@ -1637,6 +1642,7 @@ static int gpiochip_irq_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
struct gpio_chip *chip = d->host_data;
int err = 0;

if (!gpiochip_irqchip_irq_valid(chip, hwirq))
return -ENXIO;
Expand All @@ -1653,6 +1659,14 @@ static int gpiochip_irq_map(struct irq_domain *d, unsigned int irq,
irq_set_nested_thread(irq, 1);
irq_set_noprobe(irq);

if (chip->irq.num_parents == 1)
err = irq_set_parent(irq, chip->irq.parents[0]);
else if (chip->irq.map)
err = irq_set_parent(irq, chip->irq.map[hwirq]);

if (err < 0)
return err;

/*
* No set-up of the hardware will happen if IRQ_TYPE_NONE
* is passed as default type.
Expand Down Expand Up @@ -1709,9 +1723,96 @@ static int gpiochip_to_irq(struct gpio_chip *chip, unsigned offset)
{
if (!gpiochip_irqchip_irq_valid(chip, offset))
return -ENXIO;

return irq_create_mapping(chip->irq.domain, offset);
}

/**
* gpiochip_add_irqchip() - adds an IRQ chip to a GPIO chip
* @gpiochip: the GPIO chip to add the IRQ chip to
*/
static int gpiochip_add_irqchip(struct gpio_chip *gpiochip)
{
struct irq_chip *irqchip = gpiochip->irq.chip;
const struct irq_domain_ops *ops;
struct device_node *np;
unsigned int type;
unsigned int i;

if (!irqchip)
return 0;

if (gpiochip->irq.parent_handler && gpiochip->can_sleep) {
chip_err(gpiochip, "you cannot have chained interrupts on a "
"chip that may sleep\n");
return -EINVAL;
}

np = gpiochip->gpiodev->dev.of_node;
type = gpiochip->irq.default_type;

/*
* Specifying a default trigger is a terrible idea if DT or ACPI is
* used to configure the interrupts, as you may end up with
* conflicting triggers. Tell the user, and reset to NONE.
*/
if (WARN(np && type != IRQ_TYPE_NONE,
"%s: Ignoring %u default trigger\n", np->full_name, type))
type = IRQ_TYPE_NONE;

if (has_acpi_companion(gpiochip->parent) && type != IRQ_TYPE_NONE) {
acpi_handle_warn(ACPI_HANDLE(gpiochip->parent),
"Ignoring %u default trigger\n", type);
type = IRQ_TYPE_NONE;
}

gpiochip->to_irq = gpiochip_to_irq;
gpiochip->irq.default_type = type;

if (gpiochip->irq.domain_ops)
ops = gpiochip->irq.domain_ops;
else
ops = &gpiochip_domain_ops;

gpiochip->irq.domain = irq_domain_add_simple(np, gpiochip->ngpio,
0, ops, gpiochip);
if (!gpiochip->irq.domain)
return -EINVAL;

/*
* It is possible for a driver to override this, but only if the
* alternative functions are both implemented.
*/
if (!irqchip->irq_request_resources &&
!irqchip->irq_release_resources) {
irqchip->irq_request_resources = gpiochip_irq_reqres;
irqchip->irq_release_resources = gpiochip_irq_relres;
}

if (gpiochip->irq.parent_handler) {
void *data = gpiochip->irq.parent_handler_data ?: gpiochip;

for (i = 0; i < gpiochip->irq.num_parents; i++) {
/*
* The parent IRQ chip is already using the chip_data
* for this IRQ chip, so our callbacks simply use the
* handler_data.
*/
irq_set_chained_handler_and_data(gpiochip->irq.parents[i],
gpiochip->irq.parent_handler,
data);
}

gpiochip->irq.nested = false;
} else {
gpiochip->irq.nested = true;
}

acpi_gpiochip_request_interrupts(gpiochip);

return 0;
}

/**
* gpiochip_irqchip_remove() - removes an irqchip added to a gpiochip
* @gpiochip: the gpiochip to remove the irqchip from
Expand All @@ -1724,7 +1825,7 @@ static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip)

acpi_gpiochip_free_interrupts(gpiochip);

if (gpiochip->irq.num_parents > 0) {
if (gpiochip->irq.chip && gpiochip->irq.parent_handler) {
struct gpio_irq_chip *irq = &gpiochip->irq;
unsigned int i;

Expand Down Expand Up @@ -1857,6 +1958,11 @@ EXPORT_SYMBOL_GPL(gpiochip_irqchip_add_key);

#else /* CONFIG_GPIOLIB_IRQCHIP */

static inline int gpiochip_add_irqchip(struct gpio_chip *gpiochip)
{
return 0;
}

static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip) {}
static inline int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gpiochip)
{
Expand Down
7 changes: 7 additions & 0 deletions include/linux/gpio/driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ struct gpio_irq_chip {
*/
unsigned int *parents;

/**
* @map:
*
* A list of interrupt parents for each line of a GPIO chip.
*/
unsigned int *map;

/**
* @nested:
*
Expand Down

0 comments on commit e0d8972

Please sign in to comment.