diff mbox

[6/7] gpio: brcmstb: consolidate interrupt domains

Message ID 20170930034057.15166-7-opendmb@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Doug Berger Sept. 30, 2017, 3:40 a.m. UTC
The GPIOLIB IRQ chip helpers were very appealing, but badly broke
the 1:1 mapping between a GPIO controller's device_node and its
interrupt domain.

This commit consolidates the per bank irq domains to a version
where we have one larger interrupt domain per GPIO controller
instance spanning multiple GPIO banks.

Fixes: 19a7b6940b78 ("gpio: brcmstb: Add interrupt and wakeup source support")
Signed-off-by: Doug Berger <opendmb@gmail.com>
---
 drivers/gpio/Kconfig        |   2 +-
 drivers/gpio/gpio-brcmstb.c | 188 +++++++++++++++++++++++++++++++++-----------
 2 files changed, 145 insertions(+), 45 deletions(-)

Comments

Gregory Fong Oct. 4, 2017, 3:03 a.m. UTC | #1
Hi Doug,

On Fri, Sep 29, 2017 at 8:40 PM, Doug Berger <opendmb@gmail.com> wrote:
> The GPIOLIB IRQ chip helpers were very appealing, but badly broke
> the 1:1 mapping between a GPIO controller's device_node and its
> interrupt domain.

Out of curiosity, what sort of problems have you seen from this?

>
> This commit consolidates the per bank irq domains to a version
> where we have one larger interrupt domain per GPIO controller
> instance spanning multiple GPIO banks.

This works (and is reminiscent to my initially submitted
implementation at [1]), but I think it might make sense to keep as-is
(using the gpiolib irqchip helpers), and instead allocate an irqchip
fwnode per bank and use to_of_node() to set it as the of_node for the
gpiochip before calling gpiochip_irqchip_add().  OTOH, that capability
might go away...

Linus, can you comment on the FIXME in gpiochip_irqchip_add_key() that
says "get rid of this and use gpiochip->parent->of_node everywhere"?
It seems like it would still be beneficial to be able to override the
associated node for a gpiochip, since that's what's used for the
irqdomain, but if that's going away, obviously we don't want to start
using that now.

Thanks,
Gregory

[1] https://patchwork.kernel.org/patch/6347811/
Doug Berger Oct. 4, 2017, 9:24 p.m. UTC | #2
On 10/03/2017 08:03 PM, Gregory Fong wrote:
> Hi Doug,
> 
> On Fri, Sep 29, 2017 at 8:40 PM, Doug Berger <opendmb@gmail.com> wrote:
>> The GPIOLIB IRQ chip helpers were very appealing, but badly broke
>> the 1:1 mapping between a GPIO controller's device_node and its
>> interrupt domain.
> 
> Out of curiosity, what sort of problems have you seen from this?
> 

As you know, the BRCMSTB devices conceptually distinguish between an
always-on GPIO device and a regular GPIO device that each can have many
more than 32 General Purpose I/Os. The driver supports these by dividing
the GPIO across a number of banks each of which is implemented as a
separate gpiochip as an implementation convenience. The main issue is
that each gpiochip that uses the GPIOLIB IRQ chip helpers creates its
own irq domain even though they are associated with the same device and
device-tree node.

When another device-tree node references a GPIO device as its interrupt
parent, the irq_create_of_mapping() function looks for the irq domain of
the GPIO device and since all bank irq domains reference the same GPIO
device node it always resolves to the irq domain of the first bank
regardless of which bank the number of the GPIO should resolve. This
domain can only map hwirq numbers 0-31 so interrupts on GPIO above that
can't be mapped by the device-tree.

>>
>> This commit consolidates the per bank irq domains to a version
>> where we have one larger interrupt domain per GPIO controller
>> instance spanning multiple GPIO banks.
> 
> This works (and is reminiscent to my initially submitted
> implementation at [1]), but I think it might make sense to keep as-is
> (using the gpiolib irqchip helpers), and instead allocate an irqchip
> fwnode per bank and use to_of_node() to set it as the of_node for the
> gpiochip before calling gpiochip_irqchip_add().  OTOH, that capability
> might go away...
> 
> Linus, can you comment on the FIXME in gpiochip_irqchip_add_key() that
> says "get rid of this and use gpiochip->parent->of_node everywhere"?
> It seems like it would still be beneficial to be able to override the
> associated node for a gpiochip, since that's what's used for the
> irqdomain, but if that's going away, obviously we don't want to start
> using that now.
> 

Yes, this is effectively a reversion to an earlier implementation. I
produced an implementation based on the generic irqchip libraries, but
that was stripped from this submission when I discovered that no support
exists within the generic irqchip libraries for removal of domain
generic chips and we wanted to preserve the module support of this driver.

It is conceivable that the current GPIO device-tree nodes could be
broken down into separate devices per bank, but it is believed that this
would only confuse things for users of the device as the concept
diverges from the concept expressed in device documentation.

> Thanks,
> Gregory
> 
> [1] https://patchwork.kernel.org/patch/6347811/
> 

Thanks for the review,
    Doug
Doug Berger Oct. 16, 2017, 11:04 p.m. UTC | #3
On 10/04/2017 02:24 PM, Doug Berger wrote:
> On 10/03/2017 08:03 PM, Gregory Fong wrote:
>> Hi Doug,
>>
>> On Fri, Sep 29, 2017 at 8:40 PM, Doug Berger <opendmb@gmail.com> wrote:
>>> The GPIOLIB IRQ chip helpers were very appealing, but badly broke
>>> the 1:1 mapping between a GPIO controller's device_node and its
>>> interrupt domain.
>>
>> Out of curiosity, what sort of problems have you seen from this?
>>
> 
> As you know, the BRCMSTB devices conceptually distinguish between an
> always-on GPIO device and a regular GPIO device that each can have many
> more than 32 General Purpose I/Os. The driver supports these by dividing
> the GPIO across a number of banks each of which is implemented as a
> separate gpiochip as an implementation convenience. The main issue is
> that each gpiochip that uses the GPIOLIB IRQ chip helpers creates its
> own irq domain even though they are associated with the same device and
> device-tree node.
> 
> When another device-tree node references a GPIO device as its interrupt
> parent, the irq_create_of_mapping() function looks for the irq domain of
> the GPIO device and since all bank irq domains reference the same GPIO
> device node it always resolves to the irq domain of the first bank
> regardless of which bank the number of the GPIO should resolve. This
> domain can only map hwirq numbers 0-31 so interrupts on GPIO above that
> can't be mapped by the device-tree.
> 
>>>
>>> This commit consolidates the per bank irq domains to a version
>>> where we have one larger interrupt domain per GPIO controller
>>> instance spanning multiple GPIO banks.
>>
>> This works (and is reminiscent to my initially submitted
>> implementation at [1]), but I think it might make sense to keep as-is
>> (using the gpiolib irqchip helpers), and instead allocate an irqchip
>> fwnode per bank and use to_of_node() to set it as the of_node for the
>> gpiochip before calling gpiochip_irqchip_add().  OTOH, that capability
>> might go away...
>>
>> Linus, can you comment on the FIXME in gpiochip_irqchip_add_key() that
>> says "get rid of this and use gpiochip->parent->of_node everywhere"?
>> It seems like it would still be beneficial to be able to override the
>> associated node for a gpiochip, since that's what's used for the
>> irqdomain, but if that's going away, obviously we don't want to start
>> using that now.
>>
> 
> Yes, this is effectively a reversion to an earlier implementation. I
> produced an implementation based on the generic irqchip libraries, but
> that was stripped from this submission when I discovered that no support
> exists within the generic irqchip libraries for removal of domain
> generic chips and we wanted to preserve the module support of this driver.
> 
> It is conceivable that the current GPIO device-tree nodes could be
> broken down into separate devices per bank, but it is believed that this
> would only confuse things for users of the device as the concept
> diverges from the concept expressed in device documentation.
> 
>> Thanks,
>> Gregory
>>
>> [1] https://patchwork.kernel.org/patch/6347811/
>>
> 
> Thanks for the review,
>     Doug
> 

Gregory,

Do you have any additional feedback on patches 6 and 7 before I submit a
version 2?

Thanks,
    Doug
Gregory Fong Oct. 19, 2017, 7:57 a.m. UTC | #4
Hi Doug,

On Wed, Oct 04, 2017 at 02:24:37PM -0700, Doug Berger wrote:
> On 10/03/2017 08:03 PM, Gregory Fong wrote:
> > On Fri, Sep 29, 2017 at 8:40 PM, Doug Berger <opendmb@gmail.com> wrote:
> >> The GPIOLIB IRQ chip helpers were very appealing, but badly broke
> >> the 1:1 mapping between a GPIO controller's device_node and its
> >> interrupt domain.
> > 
> > Out of curiosity, what sort of problems have you seen from this?
> > 
> 
> [snip]
> 
> When another device-tree node references a GPIO device as its interrupt
> parent, the irq_create_of_mapping() function looks for the irq domain of
> the GPIO device and since all bank irq domains reference the same GPIO
> device node it always resolves to the irq domain of the first bank
> regardless of which bank the number of the GPIO should resolve. This
> domain can only map hwirq numbers 0-31 so interrupts on GPIO above that
> can't be mapped by the device-tree.

Thanks for clarifying.  This would be great information to include in
the commit message.

> 
> >>
> >> This commit consolidates the per bank irq domains to a version
> >> where we have one larger interrupt domain per GPIO controller
> >> instance spanning multiple GPIO banks.
> > 
> > This works (and is reminiscent to my initially submitted
> > implementation at [1]), but I think it might make sense to keep as-is
> > (using the gpiolib irqchip helpers), and instead allocate an irqchip
> > fwnode per bank and use to_of_node() to set it as the of_node for the
> > gpiochip before calling gpiochip_irqchip_add().  OTOH, that capability
> > might go away...
> > 
> > Linus, can you comment on the FIXME in gpiochip_irqchip_add_key() that
> > says "get rid of this and use gpiochip->parent->of_node everywhere"?
> > It seems like it would still be beneficial to be able to override the
> > associated node for a gpiochip, since that's what's used for the
> > irqdomain, but if that's going away, obviously we don't want to start
> > using that now.
> > 
> 
> Yes, this is effectively a reversion to an earlier implementation. I
> produced an implementation based on the generic irqchip libraries, but
> that was stripped from this submission when I discovered that no support
> exists within the generic irqchip libraries for removal of domain
> generic chips and we wanted to preserve the module support of this driver.

Considering this is heavily based on my initial implementation (several
functions are exactly the same), it'd be nice to have some small
attribution in the commit message. :-)

> 
> It is conceivable that the current GPIO device-tree nodes could be
> broken down into separate devices per bank, but it is believed that this
> would only confuse things for users of the device as the concept
> diverges from the concept expressed in device documentation.

OK, that sounds like a worse alternative.  And since these are all
actually using the same parent IRQ, it does make sense to keep them all
in the same IRQ domain.


On Fri, Sep 29, 2017 at 08:40:56PM -0700, Doug Berger wrote:
> [snip]
> diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c
> index e2fff559c1ca..752a46ce3589 100644
> --- a/drivers/gpio/gpio-brcmstb.c
> +++ b/drivers/gpio/gpio-brcmstb.c
> [snip]
> @@ -77,12 +79,18 @@ brcmstb_gpio_get_active_irqs(struct brcmstb_gpio_bank *bank)
>  	return status;
>  }
>  
> +static int brcmstb_gpio_hwirq_to_offset(irq_hw_number_t hwirq,
> +					struct brcmstb_gpio_bank *bank)
> +{
> +	return hwirq - (bank->gc.base - bank->parent_priv->gpio_base);
> +}
> +
>  static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
>  		unsigned int offset, bool enable)
>  {
>  	struct gpio_chip *gc = &bank->gc;
>  	struct brcmstb_gpio_priv *priv = bank->parent_priv;
> -	u32 mask = gc->pin2mask(gc, offset);
> +	u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(offset, bank));

Consider renaming "offset" to "hwirq".

>  	u32 imask;
>  	unsigned long flags;
>  
> @@ -96,6 +104,17 @@ static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
>  	spin_unlock_irqrestore(&gc->bgpio_lock, flags);
>  }
>  
> +static int brcmstb_gpio_to_irq(struct gpio_chip *gc, unsigned gc_offset)
> +{
> +	struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc);
> +	/* gc_offset is relative to this gpio_chip; want real offset */
> +	int offset = gc_offset + (gc->base - priv->gpio_base);

Consider renaming "gc_offset" to "offset" and "offset" to "hwirq" to
keep things consistent.

> +
> +	if (offset >= priv->num_gpios)
> +		return -ENXIO;
> +	return irq_create_mapping(priv->irq_domain, offset);
> +}
> +
>  
> [snip]
>
> @@ -226,18 +245,19 @@ static irqreturn_t brcmstb_gpio_wake_irq_handler(int irq, void *data)
>  static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank)
>  {
>  	struct brcmstb_gpio_priv *priv = bank->parent_priv;
> -	struct irq_domain *irq_domain = bank->gc.irqdomain;
> +	struct irq_domain *domain = priv->irq_domain;
> +	int hwbase = bank->gc.base - priv->gpio_base;
>  	unsigned long status;
> +	unsigned int irq;
>  
>  	while ((status = brcmstb_gpio_get_active_irqs(bank))) {
> -		int bit;
> -
> -		for_each_set_bit(bit, &status, 32) {
> -			if (bit >= bank->width)
> +		for_each_set_bit(irq, &status, 32) {
> +			if (irq >= bank->width)
>  				dev_warn(&priv->pdev->dev,
>  					 "IRQ for invalid GPIO (bank=%d, offset=%d)\n",
> -					 bank->id, bit);
> -			generic_handle_irq(irq_find_mapping(irq_domain, bit));
> +					 bank->id, irq);
> +			irq = irq_linear_revmap(domain, irq + hwbase);
> +			generic_handle_irq(irq);

I'm not a fan of the change to use "irq" to mean several different
things in this function.  How about this instead?

static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank)
{
	struct brcmstb_gpio_priv *priv = bank->parent_priv;
	struct irq_domain *domain = priv->irq_domain;
	int hwbase = bank->gc.base - priv->gpio_base;
	unsigned long status;

	while ((status = brcmstb_gpio_get_active_irqs(bank))) {
		int offset;

		for_each_set_bit(offset, &status, 32) {
			if (offset >= bank->width)
 				dev_warn(&priv->pdev->dev,
 					 "IRQ for invalid GPIO (bank=%d, offset=%d)\n",
					 bank->id, offset);
			generic_handle_irq(irq_linear_revmap(domain, offset + hwbase));


>  		}
>  	}
>  }
> [snip] 
> 

The rest looks good.

Thanks,
Gregory
Doug Berger Oct. 19, 2017, 6:25 p.m. UTC | #5
On 10/19/2017 12:57 AM, Gregory Fong wrote:
> Hi Doug,
> 
> On Wed, Oct 04, 2017 at 02:24:37PM -0700, Doug Berger wrote:
>> On 10/03/2017 08:03 PM, Gregory Fong wrote:
>>> On Fri, Sep 29, 2017 at 8:40 PM, Doug Berger <opendmb@gmail.com> wrote:
>>>> The GPIOLIB IRQ chip helpers were very appealing, but badly broke
>>>> the 1:1 mapping between a GPIO controller's device_node and its
>>>> interrupt domain.
>>>
>>> Out of curiosity, what sort of problems have you seen from this?
>>>
>>
>> [snip]
>>
>> When another device-tree node references a GPIO device as its interrupt
>> parent, the irq_create_of_mapping() function looks for the irq domain of
>> the GPIO device and since all bank irq domains reference the same GPIO
>> device node it always resolves to the irq domain of the first bank
>> regardless of which bank the number of the GPIO should resolve. This
>> domain can only map hwirq numbers 0-31 so interrupts on GPIO above that
>> can't be mapped by the device-tree.
> 
> Thanks for clarifying.  This would be great information to include in
> the commit message.
>

Will do.

>>
>>>>
>>>> This commit consolidates the per bank irq domains to a version
>>>> where we have one larger interrupt domain per GPIO controller
>>>> instance spanning multiple GPIO banks.
>>>
>>> This works (and is reminiscent to my initially submitted
>>> implementation at [1]), but I think it might make sense to keep as-is
>>> (using the gpiolib irqchip helpers), and instead allocate an irqchip
>>> fwnode per bank and use to_of_node() to set it as the of_node for the
>>> gpiochip before calling gpiochip_irqchip_add().  OTOH, that capability
>>> might go away...
>>>
>>> Linus, can you comment on the FIXME in gpiochip_irqchip_add_key() that
>>> says "get rid of this and use gpiochip->parent->of_node everywhere"?
>>> It seems like it would still be beneficial to be able to override the
>>> associated node for a gpiochip, since that's what's used for the
>>> irqdomain, but if that's going away, obviously we don't want to start
>>> using that now.
>>>
>>
>> Yes, this is effectively a reversion to an earlier implementation. I
>> produced an implementation based on the generic irqchip libraries, but
>> that was stripped from this submission when I discovered that no support
>> exists within the generic irqchip libraries for removal of domain
>> generic chips and we wanted to preserve the module support of this driver.
> 
> Considering this is heavily based on my initial implementation (several
> functions are exactly the same), it'd be nice to have some small
> attribution in the commit message. :-)
> 

Yes, I suppose I should have said "a reversion to your earlier
implementation" above :).  I'd be happy to to add your Signed-of-by if
you would like, but you'll have to let me know which email address to
use (the one in the original downstream submission or this one).

>>
>> It is conceivable that the current GPIO device-tree nodes could be
>> broken down into separate devices per bank, but it is believed that this
>> would only confuse things for users of the device as the concept
>> diverges from the concept expressed in device documentation.
> 
> OK, that sounds like a worse alternative.  And since these are all
> actually using the same parent IRQ, it does make sense to keep them all
> in the same IRQ domain.
> 
> 
> On Fri, Sep 29, 2017 at 08:40:56PM -0700, Doug Berger wrote:
>> [snip]
>> diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c
>> index e2fff559c1ca..752a46ce3589 100644
>> --- a/drivers/gpio/gpio-brcmstb.c
>> +++ b/drivers/gpio/gpio-brcmstb.c
>> [snip]
>> @@ -77,12 +79,18 @@ brcmstb_gpio_get_active_irqs(struct brcmstb_gpio_bank *bank)
>>  	return status;
>>  }
>>  
>> +static int brcmstb_gpio_hwirq_to_offset(irq_hw_number_t hwirq,
>> +					struct brcmstb_gpio_bank *bank)
>> +{
>> +	return hwirq - (bank->gc.base - bank->parent_priv->gpio_base);
>> +}
>> +
>>  static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
>>  		unsigned int offset, bool enable)
>>  {
>>  	struct gpio_chip *gc = &bank->gc;
>>  	struct brcmstb_gpio_priv *priv = bank->parent_priv;
>> -	u32 mask = gc->pin2mask(gc, offset);
>> +	u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(offset, bank));
> 
> Consider renaming "offset" to "hwirq".
> 

I could do that, but I was just using the existing argument so now you
are editing yourself ;).  I'll think about it.

>>  	u32 imask;
>>  	unsigned long flags;
>>  
>> @@ -96,6 +104,17 @@ static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
>>  	spin_unlock_irqrestore(&gc->bgpio_lock, flags);
>>  }
>>  
>> +static int brcmstb_gpio_to_irq(struct gpio_chip *gc, unsigned gc_offset)
>> +{
>> +	struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc);
>> +	/* gc_offset is relative to this gpio_chip; want real offset */
>> +	int offset = gc_offset + (gc->base - priv->gpio_base);
> 
> Consider renaming "gc_offset" to "offset" and "offset" to "hwirq" to
> keep things consistent.
> 

Sounds better.  I'll consider that too.

>> +
>> +	if (offset >= priv->num_gpios)
>> +		return -ENXIO;
>> +	return irq_create_mapping(priv->irq_domain, offset);
>> +}
>> +
>>  
>> [snip]
>>
>> @@ -226,18 +245,19 @@ static irqreturn_t brcmstb_gpio_wake_irq_handler(int irq, void *data)
>>  static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank)
>>  {
>>  	struct brcmstb_gpio_priv *priv = bank->parent_priv;
>> -	struct irq_domain *irq_domain = bank->gc.irqdomain;
>> +	struct irq_domain *domain = priv->irq_domain;
>> +	int hwbase = bank->gc.base - priv->gpio_base;
>>  	unsigned long status;
>> +	unsigned int irq;
>>  
>>  	while ((status = brcmstb_gpio_get_active_irqs(bank))) {
>> -		int bit;
>> -
>> -		for_each_set_bit(bit, &status, 32) {
>> -			if (bit >= bank->width)
>> +		for_each_set_bit(irq, &status, 32) {
>> +			if (irq >= bank->width)
>>  				dev_warn(&priv->pdev->dev,
>>  					 "IRQ for invalid GPIO (bank=%d, offset=%d)\n",
>> -					 bank->id, bit);
>> -			generic_handle_irq(irq_find_mapping(irq_domain, bit));
>> +					 bank->id, irq);
>> +			irq = irq_linear_revmap(domain, irq + hwbase);
>> +			generic_handle_irq(irq);
> 
> I'm not a fan of the change to use "irq" to mean several different
> things in this function.  How about this instead?
> 
> static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank)
> {
> 	struct brcmstb_gpio_priv *priv = bank->parent_priv;
> 	struct irq_domain *domain = priv->irq_domain;
> 	int hwbase = bank->gc.base - priv->gpio_base;
> 	unsigned long status;
> 
> 	while ((status = brcmstb_gpio_get_active_irqs(bank))) {
> 		int offset;
> 
> 		for_each_set_bit(offset, &status, 32) {
> 			if (offset >= bank->width)
>  				dev_warn(&priv->pdev->dev,
>  					 "IRQ for invalid GPIO (bank=%d, offset=%d)\n",
> 					 bank->id, offset);
> 			generic_handle_irq(irq_linear_revmap(domain, offset + hwbase));
> 
> 

That is more or less where I started, but the "ugliness" began creeping
in with the need to wrap that last line at 80 characters. It seemed
silly to declare two separate ints for transitory purposes just to make
the naming clearer, but I'll revisit it too.

>>  		}
>>  	}
>>  }
>> [snip] 
>>
> 
> The rest looks good.
> 
> Thanks,
> Gregory
> 

Thanks for taking the time and the thoughtful feedback,
    Doug
diff mbox

Patch

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 796b11c489ae..28dd05aaa408 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -139,7 +139,7 @@  config GPIO_BRCMSTB
 	default y if (ARCH_BRCMSTB || BMIPS_GENERIC)
 	depends on OF_GPIO && (ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST)
 	select GPIO_GENERIC
-	select GPIOLIB_IRQCHIP
+	select IRQ_DOMAIN
 	help
 	  Say yes here to enable GPIO support for Broadcom STB (BCM7XXX) SoCs.
 
diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c
index e2fff559c1ca..752a46ce3589 100644
--- a/drivers/gpio/gpio-brcmstb.c
+++ b/drivers/gpio/gpio-brcmstb.c
@@ -37,20 +37,22 @@  struct brcmstb_gpio_bank {
 	struct gpio_chip gc;
 	struct brcmstb_gpio_priv *parent_priv;
 	u32 width;
-	struct irq_chip irq_chip;
 };
 
 struct brcmstb_gpio_priv {
 	struct list_head bank_list;
 	void __iomem *reg_base;
 	struct platform_device *pdev;
+	struct irq_domain *irq_domain;
+	struct irq_chip irq_chip;
 	int parent_irq;
 	int gpio_base;
+	int num_gpios;
 	int parent_wake_irq;
 	struct notifier_block reboot_notifier;
 };
 
-#define MAX_GPIO_PER_BANK           32
+#define MAX_GPIO_PER_BANK       32
 #define GPIO_BANK(gpio)         ((gpio) >> 5)
 /* assumes MAX_GPIO_PER_BANK is a multiple of 2 */
 #define GPIO_BIT(gpio)          ((gpio) & (MAX_GPIO_PER_BANK - 1))
@@ -77,12 +79,18 @@  brcmstb_gpio_get_active_irqs(struct brcmstb_gpio_bank *bank)
 	return status;
 }
 
+static int brcmstb_gpio_hwirq_to_offset(irq_hw_number_t hwirq,
+					struct brcmstb_gpio_bank *bank)
+{
+	return hwirq - (bank->gc.base - bank->parent_priv->gpio_base);
+}
+
 static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
 		unsigned int offset, bool enable)
 {
 	struct gpio_chip *gc = &bank->gc;
 	struct brcmstb_gpio_priv *priv = bank->parent_priv;
-	u32 mask = gc->pin2mask(gc, offset);
+	u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(offset, bank));
 	u32 imask;
 	unsigned long flags;
 
@@ -96,6 +104,17 @@  static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank,
 	spin_unlock_irqrestore(&gc->bgpio_lock, flags);
 }
 
+static int brcmstb_gpio_to_irq(struct gpio_chip *gc, unsigned gc_offset)
+{
+	struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc);
+	/* gc_offset is relative to this gpio_chip; want real offset */
+	int offset = gc_offset + (gc->base - priv->gpio_base);
+
+	if (offset >= priv->num_gpios)
+		return -ENXIO;
+	return irq_create_mapping(priv->irq_domain, offset);
+}
+
 /* -------------------- IRQ chip functions -------------------- */
 
 static void brcmstb_gpio_irq_mask(struct irq_data *d)
@@ -119,7 +138,7 @@  static void brcmstb_gpio_irq_ack(struct irq_data *d)
 	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
 	struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc);
 	struct brcmstb_gpio_priv *priv = bank->parent_priv;
-	u32 mask = BIT(d->hwirq);
+	u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank));
 
 	gc->write_reg(priv->reg_base + GIO_STAT(bank->id), mask);
 }
@@ -129,7 +148,7 @@  static int brcmstb_gpio_irq_set_type(struct irq_data *d, unsigned int type)
 	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
 	struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc);
 	struct brcmstb_gpio_priv *priv = bank->parent_priv;
-	u32 mask = BIT(d->hwirq);
+	u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank));
 	u32 edge_insensitive, iedge_insensitive;
 	u32 edge_config, iedge_config;
 	u32 level, ilevel;
@@ -226,18 +245,19 @@  static irqreturn_t brcmstb_gpio_wake_irq_handler(int irq, void *data)
 static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank)
 {
 	struct brcmstb_gpio_priv *priv = bank->parent_priv;
-	struct irq_domain *irq_domain = bank->gc.irqdomain;
+	struct irq_domain *domain = priv->irq_domain;
+	int hwbase = bank->gc.base - priv->gpio_base;
 	unsigned long status;
+	unsigned int irq;
 
 	while ((status = brcmstb_gpio_get_active_irqs(bank))) {
-		int bit;
-
-		for_each_set_bit(bit, &status, 32) {
-			if (bit >= bank->width)
+		for_each_set_bit(irq, &status, 32) {
+			if (irq >= bank->width)
 				dev_warn(&priv->pdev->dev,
 					 "IRQ for invalid GPIO (bank=%d, offset=%d)\n",
-					 bank->id, bit);
-			generic_handle_irq(irq_find_mapping(irq_domain, bit));
+					 bank->id, irq);
+			irq = irq_linear_revmap(domain, irq + hwbase);
+			generic_handle_irq(irq);
 		}
 	}
 }
@@ -245,8 +265,7 @@  static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank)
 /* Each UPG GIO block has one IRQ for all banks */
 static void brcmstb_gpio_irq_handler(struct irq_desc *desc)
 {
-	struct gpio_chip *gc = irq_desc_get_handler_data(desc);
-	struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc);
+	struct brcmstb_gpio_priv *priv = irq_desc_get_handler_data(desc);
 	struct irq_chip *chip = irq_desc_get_chip(desc);
 	struct brcmstb_gpio_bank *bank;
 
@@ -272,6 +291,63 @@  static int brcmstb_gpio_reboot(struct notifier_block *nb,
 	return NOTIFY_DONE;
 }
 
+static struct brcmstb_gpio_bank *brcmstb_gpio_hwirq_to_bank(
+		struct brcmstb_gpio_priv *priv, irq_hw_number_t hwirq)
+{
+	struct brcmstb_gpio_bank *bank;
+	int i = 0;
+
+	/* banks are in descending order */
+	list_for_each_entry_reverse(bank, &priv->bank_list, node) {
+		i += bank->gc.ngpio;
+		if (hwirq < i)
+			return bank;
+	}
+	return NULL;
+}
+
+/*
+ * This lock class tells lockdep that GPIO irqs are in a different
+ * category than their parents, so it won't report false recursion.
+ */
+static struct lock_class_key brcmstb_gpio_irq_lock_class;
+
+
+static int brcmstb_gpio_irq_map(struct irq_domain *d, unsigned int irq,
+		irq_hw_number_t hwirq)
+{
+	struct brcmstb_gpio_priv *priv = d->host_data;
+	struct brcmstb_gpio_bank *bank =
+		brcmstb_gpio_hwirq_to_bank(priv, hwirq);
+	struct platform_device *pdev = priv->pdev;
+	int ret;
+
+	if (!bank)
+		return -EINVAL;
+
+	dev_dbg(&pdev->dev, "Mapping irq %d for gpio line %d (bank %d)\n",
+		irq, (int)hwirq, bank->id);
+	ret = irq_set_chip_data(irq, &bank->gc);
+	if (ret < 0)
+		return ret;
+	irq_set_lockdep_class(irq, &brcmstb_gpio_irq_lock_class);
+	irq_set_chip_and_handler(irq, &priv->irq_chip, handle_level_irq);
+	irq_set_noprobe(irq);
+	return 0;
+}
+
+static void brcmstb_gpio_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+	irq_set_chip_and_handler(irq, NULL, NULL);
+	irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops brcmstb_gpio_irq_domain_ops = {
+	.map = brcmstb_gpio_irq_map,
+	.unmap = brcmstb_gpio_irq_unmap,
+	.xlate = irq_domain_xlate_twocell,
+};
+
 /* Make sure that the number of banks matches up between properties */
 static int brcmstb_gpio_sanity_check_banks(struct device *dev,
 		struct device_node *np, struct resource *res)
@@ -293,13 +369,25 @@  static int brcmstb_gpio_remove(struct platform_device *pdev)
 {
 	struct brcmstb_gpio_priv *priv = platform_get_drvdata(pdev);
 	struct brcmstb_gpio_bank *bank;
-	int ret = 0;
+	int offset, ret = 0, virq;
 
 	if (!priv) {
 		dev_err(&pdev->dev, "called %s without drvdata!\n", __func__);
 		return -EFAULT;
 	}
 
+	if (priv->parent_irq > 0)
+		irq_set_chained_handler_and_data(priv->parent_irq, NULL, NULL);
+
+	/* Remove all IRQ mappings and delete the domain */
+	if (priv->irq_domain) {
+		for (offset = 0; offset < priv->num_gpios; offset++) {
+			virq = irq_find_mapping(priv->irq_domain, offset);
+			irq_dispose_mapping(virq);
+		}
+		irq_domain_remove(priv->irq_domain);
+	}
+
 	/*
 	 * You can lose return values below, but we report all errors, and it's
 	 * more important to actually perform all of the steps.
@@ -347,28 +435,24 @@  static int brcmstb_gpio_of_xlate(struct gpio_chip *gc,
 	return offset;
 }
 
-/* Before calling, must have bank->parent_irq set and gpiochip registered */
+/* priv->parent_irq and priv->num_gpios must be set before calling */
 static int brcmstb_gpio_irq_setup(struct platform_device *pdev,
-		struct brcmstb_gpio_bank *bank)
+		struct brcmstb_gpio_priv *priv)
 {
-	struct brcmstb_gpio_priv *priv = bank->parent_priv;
 	struct device *dev = &pdev->dev;
 	struct device_node *np = dev->of_node;
 	int err;
 
-	bank->irq_chip.name = dev_name(dev);
-	bank->irq_chip.irq_mask = brcmstb_gpio_irq_mask;
-	bank->irq_chip.irq_unmask = brcmstb_gpio_irq_unmask;
-	bank->irq_chip.irq_ack = brcmstb_gpio_irq_ack;
-	bank->irq_chip.irq_set_type = brcmstb_gpio_irq_set_type;
-
-	/* Ensures that all non-wakeup IRQs are disabled at suspend */
-	/* and that interrupts are masked when changing their type  */
-	bank->irq_chip.flags = IRQCHIP_MASK_ON_SUSPEND |
-			       IRQCHIP_SET_TYPE_MASKED;
+	priv->irq_domain =
+		irq_domain_add_linear(np, priv->num_gpios,
+				      &brcmstb_gpio_irq_domain_ops,
+				      priv);
+	if (!priv->irq_domain) {
+		dev_err(dev, "Couldn't allocate IRQ domain\n");
+		return -ENXIO;
+	}
 
-	if (IS_ENABLED(CONFIG_PM_SLEEP) && !priv->parent_wake_irq &&
-			of_property_read_bool(np, "wakeup-source")) {
+	if (of_property_read_bool(np, "wakeup-source")) {
 		priv->parent_wake_irq = platform_get_irq(pdev, 1);
 		if (priv->parent_wake_irq < 0) {
 			priv->parent_wake_irq = 0;
@@ -389,7 +473,7 @@  static int brcmstb_gpio_irq_setup(struct platform_device *pdev,
 
 			if (err < 0) {
 				dev_err(dev, "Couldn't request wake IRQ");
-				return err;
+				goto out_free_domain;
 			}
 
 			priv->reboot_notifier.notifier_call =
@@ -398,17 +482,30 @@  static int brcmstb_gpio_irq_setup(struct platform_device *pdev,
 		}
 	}
 
+	priv->irq_chip.name = dev_name(dev);
+	priv->irq_chip.irq_disable = brcmstb_gpio_irq_mask;
+	priv->irq_chip.irq_mask = brcmstb_gpio_irq_mask;
+	priv->irq_chip.irq_unmask = brcmstb_gpio_irq_unmask;
+	priv->irq_chip.irq_ack = brcmstb_gpio_irq_ack;
+	priv->irq_chip.irq_set_type = brcmstb_gpio_irq_set_type;
+
+	/* Ensures that all non-wakeup IRQs are disabled at suspend */
+	/* and that interrupts are masked when changing their type  */
+	priv->irq_chip.flags = IRQCHIP_MASK_ON_SUSPEND |
+			       IRQCHIP_SET_TYPE_MASKED;
+
 	if (priv->parent_wake_irq)
-		bank->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake;
+		priv->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake;
 
-	err = gpiochip_irqchip_add(&bank->gc, &bank->irq_chip, 0,
-				   handle_level_irq, IRQ_TYPE_NONE);
-	if (err)
-		return err;
-	gpiochip_set_chained_irqchip(&bank->gc, &bank->irq_chip,
-			priv->parent_irq, brcmstb_gpio_irq_handler);
+	irq_set_chained_handler_and_data(priv->parent_irq,
+					 brcmstb_gpio_irq_handler, priv);
 
 	return 0;
+
+out_free_domain:
+	irq_domain_remove(priv->irq_domain);
+
+	return err;
 }
 
 static int brcmstb_gpio_probe(struct platform_device *pdev)
@@ -513,6 +610,8 @@  static int brcmstb_gpio_probe(struct platform_device *pdev)
 		gc->of_xlate = brcmstb_gpio_of_xlate;
 		/* not all ngpio lines are valid, will use bank width later */
 		gc->ngpio = MAX_GPIO_PER_BANK;
+		if (priv->parent_irq > 0)
+			gc->to_irq = brcmstb_gpio_to_irq;
 
 		/*
 		 * Mask all interrupts by default, since wakeup interrupts may
@@ -528,12 +627,6 @@  static int brcmstb_gpio_probe(struct platform_device *pdev)
 		}
 		gpio_base += gc->ngpio;
 
-		if (priv->parent_irq > 0) {
-			err = brcmstb_gpio_irq_setup(pdev, bank);
-			if (err)
-				goto fail;
-		}
-
 		dev_dbg(dev, "bank=%d, base=%d, ngpio=%d, width=%d\n", bank->id,
 			gc->base, gc->ngpio, bank->width);
 
@@ -543,6 +636,13 @@  static int brcmstb_gpio_probe(struct platform_device *pdev)
 		num_banks++;
 	}
 
+	priv->num_gpios = gpio_base - priv->gpio_base;
+	if (priv->parent_irq > 0) {
+		err = brcmstb_gpio_irq_setup(pdev, priv);
+		if (err)
+			goto fail;
+	}
+
 	dev_info(dev, "Registered %d banks (GPIO(s): %d-%d)\n",
 			num_banks, priv->gpio_base, gpio_base - 1);