diff mbox

[v5,2/9] drivers: irqchip: Add STM32 external interrupts support

Message ID 1473432124-6784-3-git-send-email-alexandre.torgue@st.com
State New
Headers show

Commit Message

Alexandre TORGUE Sept. 9, 2016, 2:41 p.m. UTC
The STM32 external interrupt controller consists of edge detectors that
generate interrupts requests or wake-up events.

Each line can be independently configured as interrupt or wake-up source,
and triggers either on rising, falling or both edges. Each line can also
be masked independently.

Signed-off-by: Maxime Coquelin <mcoquelin.stm32@gmail.com>
Signed-off-by: Alexandre TORGUE <alexandre.torgue@st.com>

Comments

Marc Zyngier Sept. 13, 2016, 8:19 a.m. UTC | #1
On 09/09/16 15:41, Alexandre TORGUE wrote:
> The STM32 external interrupt controller consists of edge detectors that
> generate interrupts requests or wake-up events.
> 
> Each line can be independently configured as interrupt or wake-up source,
> and triggers either on rising, falling or both edges. Each line can also
> be masked independently.
> 
> Signed-off-by: Maxime Coquelin <mcoquelin.stm32@gmail.com>
> Signed-off-by: Alexandre TORGUE <alexandre.torgue@st.com>

Acked-by: Marc Zyngier <marc.zyngier@arm.com>

	M.
Thomas Gleixner Sept. 13, 2016, 3:21 p.m. UTC | #2
On Fri, 9 Sep 2016, Alexandre TORGUE wrote:
> +static void stm32_exti_free(struct irq_domain *d, unsigned int virq,
> +			    unsigned int nr_irqs)
> +{
> +	struct irq_data *data = irq_get_irq_data(virq);
> +
> +	irq_gc_mask_clr_bit(data->parent_data);

I have a hard time to understand this. The irq domain is not hierarchical.

> +	irq_domain_reset_irq_data(data);

> +	domain = irq_domain_add_linear(node, nr_exti,
> +				       &irq_exti_domain_ops, NULL);

It's a simple linear domain. So how can data->parent_data be a valid
irq_data pointer? Answer: It can't!

But it doesn't blow up in your face simply because the alloc/free callbacks
are never invoked for simple non hierarchical domains. So you should have
removed that stuff after copying some other irqchip driver.

Thanks,

	tglx
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexandre TORGUE Sept. 13, 2016, 4:29 p.m. UTC | #3
Hi Thomas,

On 09/13/2016 05:21 PM, Thomas Gleixner wrote:
> On Fri, 9 Sep 2016, Alexandre TORGUE wrote:
>> +static void stm32_exti_free(struct irq_domain *d, unsigned int virq,
>> +			    unsigned int nr_irqs)
>> +{
>> +	struct irq_data *data = irq_get_irq_data(virq);
>> +
>> +	irq_gc_mask_clr_bit(data->parent_data);
>
> I have a hard time to understand this. The irq domain is not hierarchical.

Actually, I wanted to test ".free" callback function of gpio_irq_domain 
in STM32 pinctrl driver. To do that I modified gpio driver: just after 
getting virq through gpio_to_irq, I called "irq_dispose_mapping(virq)".
I know it is dirty but I thought it was the only way to test.

Doing that, I see that ".free" callback of gpio domain is called but as 
it is hirerchical ".free" callback for parent domain (exti one) is also 
called. I observed that virq was well unmapped, but not masked at exti 
level. It is for this reason than I added 
"irq_gc_mask_clr_bit(data->parent_data);" which mask interrupt at exti 
level.

Maybe this use case can never happen ? (and in this case all this stuff 
is not needed)

>
>> +	irq_domain_reset_irq_data(data);
>
>> +	domain = irq_domain_add_linear(node, nr_exti,
>> +				       &irq_exti_domain_ops, NULL);
>
> It's a simple linear domain. So how can data->parent_data be a valid
> irq_data pointer? Answer: It can't!

This Exti domain is parent of stm32 gpio domain. When ".free" callback 
of stm32 gpio domain is called then ".free" callback of Exti domain will 
be automatically called. Those both ".free" callbacks are called with 
"virq". This virq is created through stm32 gpio domain (in stm32 pinctrl 
driver), data and parent->data are then associated to it.

Sorry if it is not clear.

Regards

Alex

>
> But it doesn't blow up in your face simply because the alloc/free callbacks
> are never invoked for simple non hierarchical domains. So you should have
> removed that stuff after copying some other irqchip driver.



>
> Thanks,
>
> 	tglx
>
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thomas Gleixner Sept. 14, 2016, 9:19 a.m. UTC | #4
On Tue, 13 Sep 2016, Alexandre Torgue wrote:
> On 09/13/2016 05:21 PM, Thomas Gleixner wrote:
> > On Fri, 9 Sep 2016, Alexandre TORGUE wrote:
> > > +static void stm32_exti_free(struct irq_domain *d, unsigned int virq,
> > > +			    unsigned int nr_irqs)
> > > +{
> > > +	struct irq_data *data = irq_get_irq_data(virq);
> > > +
> > > +	irq_gc_mask_clr_bit(data->parent_data);
> > 
> > I have a hard time to understand this. The irq domain is not hierarchical.
> 
> Actually, I wanted to test ".free" callback function of gpio_irq_domain in
> STM32 pinctrl driver. To do that I modified gpio driver: just after getting
> virq through gpio_to_irq, I called "irq_dispose_mapping(virq)".
> I know it is dirty but I thought it was the only way to test.
> 
> Doing that, I see that ".free" callback of gpio domain is called but as it is
> hirerchical ".free" callback for parent domain (exti one) is also called. I
> observed that virq was well unmapped, but not masked at exti level. It is for
> this reason than I added "irq_gc_mask_clr_bit(data->parent_data);" which mask
> interrupt at exti level.

Aargh. I really misread the patch, but this is entirely non obvious and you
should do:

       struct irq_data *data = irq_domain_get_irq_data(d, virq);

       irq_gc_mask_clr_bit(d);

Then it is entirely clear that you mask the interrupt of _this_ (the exti)
domain.

Now what really bugs me is that you do that at all. An interrupt which is
freed must be masked already. Why is it unmasked in the first place?

Thanks,

	tglx

--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexandre TORGUE Sept. 14, 2016, 1:05 p.m. UTC | #5
Hi Thomas,

On 09/14/2016 11:19 AM, Thomas Gleixner wrote:
> On Tue, 13 Sep 2016, Alexandre Torgue wrote:
>> On 09/13/2016 05:21 PM, Thomas Gleixner wrote:
>>> On Fri, 9 Sep 2016, Alexandre TORGUE wrote:
>>>> +static void stm32_exti_free(struct irq_domain *d, unsigned int virq,
>>>> +			    unsigned int nr_irqs)
>>>> +{
>>>> +	struct irq_data *data = irq_get_irq_data(virq);
>>>> +
>>>> +	irq_gc_mask_clr_bit(data->parent_data);
>>>
>>> I have a hard time to understand this. The irq domain is not hierarchical.
>>
>> Actually, I wanted to test ".free" callback function of gpio_irq_domain in
>> STM32 pinctrl driver. To do that I modified gpio driver: just after getting
>> virq through gpio_to_irq, I called "irq_dispose_mapping(virq)".
>> I know it is dirty but I thought it was the only way to test.
>>
>> Doing that, I see that ".free" callback of gpio domain is called but as it is
>> hirerchical ".free" callback for parent domain (exti one) is also called. I
>> observed that virq was well unmapped, but not masked at exti level. It is for
>> this reason than I added "irq_gc_mask_clr_bit(data->parent_data);" which mask
>> interrupt at exti level.
>
> Aargh. I really misread the patch, but this is entirely non obvious and you
> should do:
>
>        struct irq_data *data = irq_domain_get_irq_data(d, virq);
>
>        irq_gc_mask_clr_bit(d);
>
> Then it is entirely clear that you mask the interrupt of _this_ (the exti)
> domain.

Ok, it's easier to understand like that.

>
> Now what really bugs me is that you do that at all. An interrupt which is
> freed must be masked already. Why is it unmasked in the first place?

Honestly I don't know. When "devm_free_irq" is called to release virq, 
there is no issue and interrupt is well masked. But, when I tried to use
"irq_dispose_mapping(virq)" I observed that .free is called (child and 
parent domain) but interrupt is not masked.

Regards

Alex

>
> Thanks,
>
> 	tglx
>



--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thomas Gleixner Sept. 14, 2016, 1:34 p.m. UTC | #6
On Wed, 14 Sep 2016, Alexandre Torgue wrote:
> On 09/14/2016 11:19 AM, Thomas Gleixner wrote:
> > 
> > Now what really bugs me is that you do that at all. An interrupt which is
> > freed must be masked already. Why is it unmasked in the first place?
> 
> Honestly I don't know. When "devm_free_irq" is called to release virq, there
> is no issue and interrupt is well masked. But, when I tried to use
> "irq_dispose_mapping(virq)" I observed that .free is called (child and parent
> domain) but interrupt is not masked.

Well, you just used some function in some context which is not relevant to
the normal operation. So adding that mask() is just paranoia for no value.

Thanks,

	tglx
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexandre TORGUE Sept. 14, 2016, 1:44 p.m. UTC | #7
On 09/14/2016 03:34 PM, Thomas Gleixner wrote:
> On Wed, 14 Sep 2016, Alexandre Torgue wrote:
>> On 09/14/2016 11:19 AM, Thomas Gleixner wrote:
>>>
>>> Now what really bugs me is that you do that at all. An interrupt which is
>>> freed must be masked already. Why is it unmasked in the first place?
>>
>> Honestly I don't know. When "devm_free_irq" is called to release virq, there
>> is no issue and interrupt is well masked. But, when I tried to use
>> "irq_dispose_mapping(virq)" I observed that .free is called (child and parent
>> domain) but interrupt is not masked.
>
> Well, you just used some function in some context which is not relevant to
> the normal operation. So adding that mask() is just paranoia for no value.

I agree. I just wanted to "force" a test for .free callback. If it not 
relevant I'll remove ".free" callback of exti domain.
As a part of this series has already been taken by Linus (pinctrl part), 
I will send a new series only for irqchip part (patches [1] and [2]). Do 
you agree ?

Thanks
Alex


>
> Thanks,
>
> 	tglx
>



--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexandre TORGUE Sept. 20, 2016, 9:48 a.m. UTC | #8
Hi Thomas,

On 09/14/2016 03:44 PM, Alexandre Torgue wrote:
>
>
> On 09/14/2016 03:34 PM, Thomas Gleixner wrote:
>> On Wed, 14 Sep 2016, Alexandre Torgue wrote:
>>> On 09/14/2016 11:19 AM, Thomas Gleixner wrote:
>>>>
>>>> Now what really bugs me is that you do that at all. An interrupt
>>>> which is
>>>> freed must be masked already. Why is it unmasked in the first place?
>>>
>>> Honestly I don't know. When "devm_free_irq" is called to release
>>> virq, there
>>> is no issue and interrupt is well masked. But, when I tried to use
>>> "irq_dispose_mapping(virq)" I observed that .free is called (child
>>> and parent
>>> domain) but interrupt is not masked.
>>
>> Well, you just used some function in some context which is not
>> relevant to
>> the normal operation. So adding that mask() is just paranoia for no
>> value.
>
A gentle reminder ping...
If ".free" callback is not relevant then I 'll remove it from exti domain.

> I agree. I just wanted to "force" a test for .free callback. If it not
> relevant I'll remove ".free" callback of exti domain.
> As a part of this series has already been taken by Linus (pinctrl part),
> I will send a new series only for irqchip part (patches [1] and [2]). Do
> you agree ?
>

Thanks in advance
Alex

> Thanks
> Alex
>
>
>>
>> Thanks,
>>
>>     tglx
>>
>
>
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel



--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thomas Gleixner Sept. 20, 2016, 9:51 a.m. UTC | #9
On Tue, 20 Sep 2016, Alexandre Torgue wrote:
> > On 09/14/2016 03:34 PM, Thomas Gleixner wrote:
> > > Well, you just used some function in some context which is not
> > > relevant to
> > > the normal operation. So adding that mask() is just paranoia for no
> > > value.
> > 
> A gentle reminder ping...
> If ".free" callback is not relevant then I 'll remove it from exti domain.

I was not talking about the .free callback in general. I was talking about
the masking. But yes, if the thing is otherwise a NOOP, then you can spare
it completely.

Thanks,

	tglx
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexandre TORGUE Sept. 20, 2016, 12:40 p.m. UTC | #10
Thomas,

On 09/20/2016 11:51 AM, Thomas Gleixner wrote:
> On Tue, 20 Sep 2016, Alexandre Torgue wrote:
>>> On 09/14/2016 03:34 PM, Thomas Gleixner wrote:
>>>> Well, you just used some function in some context which is not
>>>> relevant to
>>>> the normal operation. So adding that mask() is just paranoia for no
>>>> value.
>>>
>> A gentle reminder ping...
>> If ".free" callback is not relevant then I 'll remove it from exti domain.

Sorry for discussing about the same thing again (and again) but I just 
want to be sure before sending a new version. As you know I have 2 
domains: EXTI domain (parent) and stm32-pinctrl-bank domain (child one).

There does it make sens to have ".free" callbacks defined in both domain 
(actually if I define one for the child domain I have to define also 
".free" callback for parent domain (EXTI) as it is hierarchical) ?
If ".free" have no chance to be called then I will send a new version by 
removing .free callbacks (in both domain).

Regards
Alex

>
> I was not talking about the .free callback in general. I was talking about
> the masking. But yes, if the thing is otherwise a NOOP, then you can spare
> it completely.
>
> Thanks,
>
> 	tglx
>

--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thomas Gleixner Sept. 20, 2016, 12:44 p.m. UTC | #11
On Tue, 20 Sep 2016, Alexandre Torgue wrote:

> Thomas,
> 
> On 09/20/2016 11:51 AM, Thomas Gleixner wrote:
> > On Tue, 20 Sep 2016, Alexandre Torgue wrote:
> > > > On 09/14/2016 03:34 PM, Thomas Gleixner wrote:
> > > > > Well, you just used some function in some context which is not
> > > > > relevant to
> > > > > the normal operation. So adding that mask() is just paranoia for no
> > > > > value.
> > > > 
> > > A gentle reminder ping...
> > > If ".free" callback is not relevant then I 'll remove it from exti domain.
> 
> Sorry for discussing about the same thing again (and again) but I just want to
> be sure before sending a new version. As you know I have 2 domains: EXTI
> domain (parent) and stm32-pinctrl-bank domain (child one).
> 
> There does it make sens to have ".free" callbacks defined in both domain
> (actually if I define one for the child domain I have to define also ".free"
> callback for parent domain (EXTI) as it is hierarchical) ?
> If ".free" have no chance to be called then I will send a new version by
> removing .free callbacks (in both domain).

Free will be called when a interrupt in the child domain is torn down,
i.e. when irq_domain_free_irqs() is called. And it will be called for both
domains like the alloc callback is invoked on both domains via
irq_domain_alloc_irqs().

Thanks,

	tglx




--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexandre TORGUE Sept. 20, 2016, 1:33 p.m. UTC | #12
Thomas,

On 09/20/2016 02:44 PM, Thomas Gleixner wrote:
> On Tue, 20 Sep 2016, Alexandre Torgue wrote:
>
>> Thomas,
>>
>> On 09/20/2016 11:51 AM, Thomas Gleixner wrote:
>>> On Tue, 20 Sep 2016, Alexandre Torgue wrote:
>>>>> On 09/14/2016 03:34 PM, Thomas Gleixner wrote:
>>>>>> Well, you just used some function in some context which is not
>>>>>> relevant to
>>>>>> the normal operation. So adding that mask() is just paranoia for no
>>>>>> value.
>>>>>
>>>> A gentle reminder ping...
>>>> If ".free" callback is not relevant then I 'll remove it from exti domain.
>>
>> Sorry for discussing about the same thing again (and again) but I just want to
>> be sure before sending a new version. As you know I have 2 domains: EXTI
>> domain (parent) and stm32-pinctrl-bank domain (child one).
>>
>> There does it make sens to have ".free" callbacks defined in both domain
>> (actually if I define one for the child domain I have to define also ".free"
>> callback for parent domain (EXTI) as it is hierarchical) ?
>> If ".free" have no chance to be called then I will send a new version by
>> removing .free callbacks (in both domain).
>
> Free will be called when a interrupt in the child domain is torn down,
> i.e. when irq_domain_free_irqs() is called. And it will be called for both
> domains like the alloc callback is invoked on both domains via
> irq_domain_alloc_irqs().

Thanks Thomas for this clarification (I'm sure now that we need .free 
callbacks).
irq_domain_free_irqs() is called in 2 scenario:
1- when issue occurs in irq_create_fwspec_mapping()
2- when irq_dispose_mapping() is called

Case 2 is the one I tested some times ago. In this case, I need to mask 
interrupts in .free callback of EXTI (parent) domain to avoid spurious 
interrupts.

Regards

Alex


>
> Thanks,
>
> 	tglx
>
>
>
>

--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thomas Gleixner Sept. 20, 2016, 2:02 p.m. UTC | #13
On Tue, 20 Sep 2016, Alexandre Torgue wrote:
> On 09/20/2016 02:44 PM, Thomas Gleixner wrote:
> > Free will be called when a interrupt in the child domain is torn down,
> > i.e. when irq_domain_free_irqs() is called. And it will be called for both
> > domains like the alloc callback is invoked on both domains via
> > irq_domain_alloc_irqs().
> 
> Thanks Thomas for this clarification (I'm sure now that we need .free
> callbacks).
> irq_domain_free_irqs() is called in 2 scenario:
> 1- when issue occurs in irq_create_fwspec_mapping()
> 2- when irq_dispose_mapping() is called
> 
> Case 2 is the one I tested some times ago. In this case, I need to mask
> interrupts in .free callback of EXTI (parent) domain to avoid spurious
> interrupts.

And why would irq_dispose_mapping() be called on an unmasked, i.e. active,
interrupt? The masking is just papering over that.

Thanks,

	tglx
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexandre TORGUE Sept. 20, 2016, 3:28 p.m. UTC | #14
Thomas,

On 09/20/2016 04:02 PM, Thomas Gleixner wrote:
> On Tue, 20 Sep 2016, Alexandre Torgue wrote:
>> On 09/20/2016 02:44 PM, Thomas Gleixner wrote:
>>> Free will be called when a interrupt in the child domain is torn down,
>>> i.e. when irq_domain_free_irqs() is called. And it will be called for both
>>> domains like the alloc callback is invoked on both domains via
>>> irq_domain_alloc_irqs().
>>
>> Thanks Thomas for this clarification (I'm sure now that we need .free
>> callbacks).
>> irq_domain_free_irqs() is called in 2 scenario:
>> 1- when issue occurs in irq_create_fwspec_mapping()
>> 2- when irq_dispose_mapping() is called
>>
>> Case 2 is the one I tested some times ago. In this case, I need to mask
>> interrupts in .free callback of EXTI (parent) domain to avoid spurious
>> interrupts.
>
> And why would irq_dispose_mapping() be called on an unmasked, i.e. active,
> interrupt? The masking is just papering over that.

Ok. So my test was wrong and irq_dispose_mapping() has to be called when 
irq is masked (for example just  after free_irq()). For sure in this 
case the mask inside exti free callback has no sens (catch :))

I will change .free callback by:

static void stm32_exti_free(struct irq_domain *d, unsigned int virq,
			    unsigned int nr_irqs)
{
	struct irq_data *data = irq_domain_get_irq_data(d, virq);
	irq_domain_reset_irq_data(data);
}


so if you agree I will resend only patches concerning stm32 exti driver 
[1],[2],[3],[4]

Thanks for your time.

alex


>
> Thanks,
>
> 	tglx
>

--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thomas Gleixner Sept. 20, 2016, 3:36 p.m. UTC | #15
On Tue, 20 Sep 2016, Alexandre Torgue wrote:
> so if you agree I will resend only patches concerning stm32 exti driver
> [1],[2],[3],[4]

Ok.
 
> Thanks for your time.

Welcome,

	tglx
--
To unsubscribe from this list: send the line "unsubscribe linux-gpio" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 7f87289..bc62d1f 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -264,3 +264,7 @@  config EZNPS_GIC
 	select IRQ_DOMAIN
 	help
 	  Support the EZchip NPS400 global interrupt controller
+
+config STM32_EXTI
+	bool
+	select IRQ_DOMAIN
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 4c203b6..96383b2 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -71,3 +71,4 @@  obj-$(CONFIG_MVEBU_ODMI)		+= irq-mvebu-odmi.o
 obj-$(CONFIG_LS_SCFG_MSI)		+= irq-ls-scfg-msi.o
 obj-$(CONFIG_EZNPS_GIC)			+= irq-eznps.o
 obj-$(CONFIG_ARCH_ASPEED)		+= irq-aspeed-vic.o
+obj-$(CONFIG_STM32_EXTI) 		+= irq-stm32-exti.o
diff --git a/drivers/irqchip/irq-stm32-exti.c b/drivers/irqchip/irq-stm32-exti.c
new file mode 100644
index 0000000..95e46ba
--- /dev/null
+++ b/drivers/irqchip/irq-stm32-exti.c
@@ -0,0 +1,202 @@ 
+/*
+ * Copyright (C) Maxime Coquelin 2015
+ * Author:  Maxime Coquelin <mcoquelin.stm32@gmail.com>
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#define EXTI_IMR	0x0
+#define EXTI_EMR	0x4
+#define EXTI_RTSR	0x8
+#define EXTI_FTSR	0xc
+#define EXTI_SWIER	0x10
+#define EXTI_PR		0x14
+
+static void stm32_irq_handler(struct irq_desc *desc)
+{
+	struct irq_domain *domain = irq_desc_get_handler_data(desc);
+	struct irq_chip_generic *gc = domain->gc->gc[0];
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	unsigned long pending;
+	int n;
+
+	chained_irq_enter(chip, desc);
+
+	while ((pending = irq_reg_readl(gc, EXTI_PR))) {
+		for_each_set_bit(n, &pending, BITS_PER_LONG) {
+			generic_handle_irq(irq_find_mapping(domain, n));
+			irq_reg_writel(gc, BIT(n), EXTI_PR);
+		}
+	}
+
+	chained_irq_exit(chip, desc);
+}
+
+static int stm32_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
+	int pin = data->hwirq;
+	u32 rtsr, ftsr;
+
+	irq_gc_lock(gc);
+
+	rtsr = irq_reg_readl(gc, EXTI_RTSR);
+	ftsr = irq_reg_readl(gc, EXTI_FTSR);
+
+	switch (type) {
+	case IRQ_TYPE_EDGE_RISING:
+		rtsr |= BIT(pin);
+		ftsr &= ~BIT(pin);
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		rtsr &= ~BIT(pin);
+		ftsr |= BIT(pin);
+		break;
+	case IRQ_TYPE_EDGE_BOTH:
+		rtsr |= BIT(pin);
+		ftsr |= BIT(pin);
+		break;
+	default:
+		irq_gc_unlock(gc);
+		return -EINVAL;
+	}
+
+	irq_reg_writel(gc, rtsr, EXTI_RTSR);
+	irq_reg_writel(gc, ftsr, EXTI_FTSR);
+
+	irq_gc_unlock(gc);
+
+	return 0;
+}
+
+static int stm32_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
+	int pin = data->hwirq;
+	u32 emr;
+
+	irq_gc_lock(gc);
+
+	emr = irq_reg_readl(gc, EXTI_EMR);
+	if (on)
+		emr |= BIT(pin);
+	else
+		emr &= ~BIT(pin);
+	irq_reg_writel(gc, emr, EXTI_EMR);
+
+	irq_gc_unlock(gc);
+
+	return 0;
+}
+
+static int stm32_exti_alloc(struct irq_domain *d, unsigned int virq,
+			    unsigned int nr_irqs, void *data)
+{
+	struct irq_chip_generic *gc = d->gc->gc[0];
+	struct irq_fwspec *fwspec = data;
+	irq_hw_number_t hwirq;
+
+	hwirq = fwspec->param[0];
+
+	irq_map_generic_chip(d, virq, hwirq);
+	irq_domain_set_info(d, virq, hwirq, &gc->chip_types->chip, gc,
+			    handle_simple_irq, NULL, NULL);
+
+	return 0;
+}
+
+static void stm32_exti_free(struct irq_domain *d, unsigned int virq,
+			    unsigned int nr_irqs)
+{
+	struct irq_data *data = irq_get_irq_data(virq);
+
+	irq_gc_mask_clr_bit(data->parent_data);
+	irq_domain_reset_irq_data(data);
+}
+
+struct irq_domain_ops irq_exti_domain_ops = {
+	.map	= irq_map_generic_chip,
+	.xlate	= irq_domain_xlate_onetwocell,
+	.alloc  = stm32_exti_alloc,
+	.free	= stm32_exti_free,
+};
+
+static int __init stm32_exti_init(struct device_node *node,
+				  struct device_node *parent)
+{
+	unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
+	int nr_irqs, nr_exti, ret, i;
+	struct irq_chip_generic *gc;
+	struct irq_domain *domain;
+	void *base;
+
+	base = of_iomap(node, 0);
+	if (!base) {
+		pr_err("%s: Unable to map registers\n", node->full_name);
+		return -ENOMEM;
+	}
+
+	/* Determine number of irqs supported */
+	writel_relaxed(~0UL, base + EXTI_RTSR);
+	nr_exti = fls(readl_relaxed(base + EXTI_RTSR));
+	writel_relaxed(0, base + EXTI_RTSR);
+
+	pr_info("%s: %d External IRQs detected\n", node->full_name, nr_exti);
+
+	domain = irq_domain_add_linear(node, nr_exti,
+				       &irq_exti_domain_ops, NULL);
+	if (!domain) {
+		pr_err("%s: Could not register interrupt domain.\n",
+				node->name);
+		ret = -ENOMEM;
+		goto out_unmap;
+	}
+
+	ret = irq_alloc_domain_generic_chips(domain, nr_exti, 1, "exti",
+					     handle_edge_irq, clr, 0, 0);
+	if (ret) {
+		pr_err("%s: Could not allocate generic interrupt chip.\n",
+			node->full_name);
+		goto out_free_domain;
+	}
+
+	gc = domain->gc->gc[0];
+	gc->reg_base                         = base;
+	gc->chip_types->type               = IRQ_TYPE_EDGE_BOTH;
+	gc->chip_types->chip.name          = gc->chip_types[0].chip.name;
+	gc->chip_types->chip.irq_ack       = irq_gc_ack_set_bit;
+	gc->chip_types->chip.irq_mask      = irq_gc_mask_clr_bit;
+	gc->chip_types->chip.irq_unmask    = irq_gc_mask_set_bit;
+	gc->chip_types->chip.irq_set_type  = stm32_irq_set_type;
+	gc->chip_types->chip.irq_set_wake  = stm32_irq_set_wake;
+	gc->chip_types->regs.ack           = EXTI_PR;
+	gc->chip_types->regs.mask          = EXTI_IMR;
+	gc->chip_types->handler            = handle_edge_irq;
+
+	nr_irqs = of_irq_count(node);
+	for (i = 0; i < nr_irqs; i++) {
+		unsigned int irq = irq_of_parse_and_map(node, i);
+
+		irq_set_handler_data(irq, domain);
+		irq_set_chained_handler(irq, stm32_irq_handler);
+	}
+
+	return 0;
+
+out_free_domain:
+	irq_domain_remove(domain);
+out_unmap:
+	iounmap(base);
+	return ret;
+}
+
+IRQCHIP_DECLARE(stm32_exti, "st,stm32-exti", stm32_exti_init);