diff mbox series

[v2] irqchip/irq-mst: Support polarity configuration

Message ID 20210308060538.11164-1-mark-pk.tsai@mediatek.com (mailing list archive)
State New, archived
Headers show
Series [v2] irqchip/irq-mst: Support polarity configuration | expand

Commit Message

Mark-PK Tsai (蔡沛剛) March 8, 2021, 6:05 a.m. UTC
Support irq polarity configuration and save and restore the config
when system suspend and resume.

Signed-off-by: Mark-PK Tsai <mark-pk.tsai@mediatek.com>
---
 drivers/irqchip/irq-mst-intc.c | 90 ++++++++++++++++++++++++++++++++--
 1 file changed, 87 insertions(+), 3 deletions(-)

Comments

Daniel Palmer March 8, 2021, 1:40 p.m. UTC | #1
Hi Mark-PK,

On Mon, 8 Mar 2021 at 15:05, Mark-PK Tsai <mark-pk.tsai@mediatek.com> wrote:
> +static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
> +{
> +       if (type != IRQ_TYPE_LEVEL_LOW && type != IRQ_TYPE_LEVEL_HIGH)
> +               return -EINVAL;
> +

Does this mean we can't do rising or falling edge interrupts?

Thanks,

Daniel
Mark-PK Tsai (蔡沛剛) March 8, 2021, 2:30 p.m. UTC | #2
From: Daniel Palmer <daniel@0x0f.com>


>On Mon, 8 Mar 2021 at 15:05, Mark-PK Tsai <mark-pk.tsai@mediatek.com> wrote:
>> +static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
>	> +{
>> +       if (type != IRQ_TYPE_LEVEL_LOW && type != IRQ_TYPE_LEVEL_HIGH)
>> +               return -EINVAL;
>> +
>
>Does this mean we can't do rising or falling edge interrupts?

Yes, the interrupt of mst-intc is either level high or level low.
Actually the input signal can be pulse, but it will be converted to level
by the latch in mst-intc.
Daniel Palmer March 10, 2021, 10:20 a.m. UTC | #3
Hi Mark-PK,

On Mon, 8 Mar 2021 at 23:30, Mark-PK Tsai <mark-pk.tsai@mediatek.com> wrote:
> From: Daniel Palmer <daniel@0x0f.com>
> >On Mon, 8 Mar 2021 at 15:05, Mark-PK Tsai <mark-pk.tsai@mediatek.com> wrote:
> >> +static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
> >       > +{
> >> +       if (type != IRQ_TYPE_LEVEL_LOW && type != IRQ_TYPE_LEVEL_HIGH)
> >> +               return -EINVAL;
> >> +
> >
> >Does this mean we can't do rising or falling edge interrupts?
>
> Yes, the interrupt of mst-intc is either level high or level low.
> Actually the input signal can be pulse, but it will be converted to level
> by the latch in mst-intc.

Are the GPIO connected interrupts meant to be configured as level
interrupts then?
For the MStar MSC313(e) there are 4 (that I know of) GPIO lines that
are wired into the mst-intc that requires EOI.
Until this patch with those lines configured as a rising edge a single
interrupt came each time the GPIO was pulled up as far as I remember.

I'm probably misunderstanding but a level interrupt doesn't seem to
make sense for a GPIO as it can't be serviced to clear the interrupt.

Thanks,

Daniel
Mark-PK Tsai (蔡沛剛) March 11, 2021, 3:12 a.m. UTC | #4
From: Daniel Palmer <daniel@0x0f.com>

>On Mon, 8 Mar 2021 at 23:30, Mark-PK Tsai <mark-pk.tsai@mediatek.com> wrote:
>> From: Daniel Palmer <daniel@0x0f.com>
>> >On Mon, 8 Mar 2021 at 15:05, Mark-PK Tsai <mark-pk.tsai@mediatek.com> wrote:
>> >> +static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
>> >       > +{
>> >> +       if (type != IRQ_TYPE_LEVEL_LOW && type != IRQ_TYPE_LEVEL_HIGH)
>> >> +               return -EINVAL;
>> >> +
>> >
>> >Does this mean we can't do rising or falling edge interrupts?
>>
>> Yes, the interrupt of mst-intc is either level high or level low.
>> Actually the input signal can be pulse, but it will be converted to level
>> by the latch in mst-intc.
>
>Are the GPIO connected interrupts meant to be configured as level
>interrupts then?
>For the MStar MSC313(e) there are 4 (that I know of) GPIO lines that
>are wired into the mst-intc that requires EOI.
>Until this patch with those lines configured as a rising edge a single
>interrupt came each time the GPIO was pulled up as far as I remember.
>
>I'm probably misunderstanding but a level interrupt doesn't seem to
>make sense for a GPIO as it can't be serviced to clear the interrupt.


As I understand it, there are 2 types of mst-intc, one we need do EOI
operation(fiq) and the other we don't(irq).
For a fiq controller, the input edge signal will be convert to level and
keep the interrupt status until we do EOI operation.
That means if a rising edge input if trigger the ouput line will keep high
until we clear the interrupt status.

And I think you're right, make these kind of interrupts edge may make more sense.
So I will modify the patch as following:

 static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
 {
-       if (type != IRQ_TYPE_LEVEL_LOW && type != IRQ_TYPE_LEVEL_HIGH)
-               return -EINVAL;
-
-       if (type == IRQ_TYPE_LEVEL_LOW) {
+       if (type == IRQ_TYPE_EDGE_FALLING) {
+               mst_set_irq(data, INTC_REV_POLARITY);
+               type = IRQ_TYPE_EDGE_RISING;
+       } else if (type == IRQ_TYPE_LEVEL_LOW) {
                mst_set_irq(data, INTC_REV_POLARITY);
                type = IRQ_TYPE_LEVEL_HIGH;
        }
Daniel Palmer March 11, 2021, 12:33 p.m. UTC | #5
Hi Mark-PK.

On Thu, 11 Mar 2021 at 12:12, Mark-PK Tsai <mark-pk.tsai@mediatek.com> wrote:
> For a fiq controller, the input edge signal will be convert to level and
> keep the interrupt status until we do EOI operation.
> That means if a rising edge input if trigger the ouput line will keep high
> until we clear the interrupt status.

I think maybe the fiq is always edge triggered?
It seems like it latches on an edge and holds it's output to the GIC
high until it is reset by eoi and then only triggers again on another
edge.
I can experiment to confirm that's what it actually does for the chips I have.

Then it seems like the irq version is almost just a configurable
inverter that passes either the input signal or the inverted input
signal to the GIC.

So maybe fiq should only accept edge type interrupts and irq could
accept either?

>  static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
>  {
> -       if (type != IRQ_TYPE_LEVEL_LOW && type != IRQ_TYPE_LEVEL_HIGH)
> -               return -EINVAL;
> -
> -       if (type == IRQ_TYPE_LEVEL_LOW) {
> +       if (type == IRQ_TYPE_EDGE_FALLING) {
> +               mst_set_irq(data, INTC_REV_POLARITY);
> +               type = IRQ_TYPE_EDGE_RISING;
> +       } else if (type == IRQ_TYPE_LEVEL_LOW) {
>                 mst_set_irq(data, INTC_REV_POLARITY);
>                 type = IRQ_TYPE_LEVEL_HIGH;
>         }

I think this still needs the logic to check that type is something we
can handle (not IRQ_TYPE_EDGE_BOTH) and maybe if the fiq controller
can only do edge interrupts level types should return -EINVAL?

Thanks,

Daniel
Mark-PK Tsai (蔡沛剛) March 11, 2021, 4:11 p.m. UTC | #6
From: Daniel Palmer <daniel@0x0f.com>

> On Thu, 11 Mar 2021 at 12:12, Mark-PK Tsai <mark-pk.tsai@mediatek.com> wrote:
> > For a fiq controller, the input edge signal will be convert to level and
> > keep the interrupt status until we do EOI operation.
> > That means if a rising edge input if trigger the ouput line will keep high
> > until we clear the interrupt status.
> 
> I think maybe the fiq is always edge triggered?
> It seems like it latches on an edge and holds it's output to the GIC
> high until it is reset by eoi and then only triggers again on another
> edge.
> I can experiment to confirm that's what it actually does for the chips I have.
> 
> Then it seems like the irq version is almost just a configurable
> inverter that passes either the input signal or the inverted input
> signal to the GIC.
> 
> So maybe fiq should only accept edge type interrupts and irq could
> accept either?

Why irq could accept either?
And if it's a fiq controller, we can just remove 'mstar,intc-no-eoi' from the
device node.
Whether the controller is fiq or irq, the interrupt request from mst-intc to
parent GIC is level sensitive. Because if the source is edge triggered, mst-intc
always latch it.

> 
> >  static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
> >  {
> > -       if (type != IRQ_TYPE_LEVEL_LOW && type != IRQ_TYPE_LEVEL_HIGH)
> > -               return -EINVAL;
> > -
> > -       if (type == IRQ_TYPE_LEVEL_LOW) {
> > +       if (type == IRQ_TYPE_EDGE_FALLING) {
> > +               mst_set_irq(data, INTC_REV_POLARITY);
> > +               type = IRQ_TYPE_EDGE_RISING;

The interrupt triggered type of the parent GIC should be active high,
I will modify it.

> > +       } else if (type == IRQ_TYPE_LEVEL_LOW) {
> >                 mst_set_irq(data, INTC_REV_POLARITY);
> >                 type = IRQ_TYPE_LEVEL_HIGH;
> >         }
> 
> I think this still needs the logic to check that type is something we
> can handle (not IRQ_TYPE_EDGE_BOTH) and maybe if the fiq controller

Agree.

> can only do edge interrupts level types should return -EINVAL?

No matter what triggered type of an irq we write in dts is edge or level triggered,
the init and handling flow is the same except we need to do eoi if the
controller doesn't have the property 'mstar,intc-no-eoi'.
So maybe we don't need to do extra work to check the type for an fiq or irq controller?

And I will update the patch as following:

diff --git a/drivers/irqchip/irq-mst-intc.c b/drivers/irqchip/irq-mst-intc.c
index 841b9b1c2699..f46ade7b1775 100644
--- a/drivers/irqchip/irq-mst-intc.c
+++ b/drivers/irqchip/irq-mst-intc.c
@@ -90,13 +90,14 @@ static void mst_intc_eoi_irq(struct irq_data *d)
 
 static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
 {
-	if (type != IRQ_TYPE_LEVEL_LOW && type != IRQ_TYPE_LEVEL_HIGH)
+	if (type != IRQ_TYPE_EDGE_RISING && type != IRQ_TYPE_EDGE_FALLING &&
+	    type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_LEVEL_LOW)
 		return -EINVAL;
 
-	if (type == IRQ_TYPE_LEVEL_LOW) {
+	if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_EDGE_FALLING)
 		mst_set_irq(data, INTC_REV_POLARITY);
-		type = IRQ_TYPE_LEVEL_HIGH;
-	}
+
+	type = IRQ_TYPE_LEVEL_HIGH;
 
 	return irq_chip_set_type_parent(data, type);
 }
@@ -219,11 +220,12 @@ static int mst_intc_domain_alloc(struct irq_domain *domain, unsigned int virq,
 	parent_fwspec.param[1] = cd->irq_start + hwirq;
 
 	/*
-	 * If the irq signal is active low, configure it to active high
-	 * to meet GIC SPI spec in mst_irq_chip_set_type via REV_POLARITY bit
+	 * mst-intc latch the interrupt request if it's edge triggered,
+	 * so the output signal to parent GIC is always level sensitive.
+	 * And if the irq signal is active low, configure it to active high
+	 * to meet GIC SPI spec in mst_irq_chip_set_type via REV_POLARITY bit.
 	 */
-	if (fwspec->param[2] == IRQ_TYPE_LEVEL_LOW)
-		parent_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
+	parent_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
 
 	return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec);
 }
Daniel Palmer March 12, 2021, 10:35 a.m. UTC | #7
On Fri, 12 Mar 2021 at 01:11, Mark-PK Tsai <mark-pk.tsai@mediatek.com> wrote:
> Why irq could accept either?

As the irq intc has no way to clear it's triggered state (no eoi) it
must just pass the signal through instead of latching it?
Otherwise it would latch once and never again right? That's what I
really didn't understand.
If it just passes the signal through and maybe inverts it then the GIC
can use edge or level I think.

> So maybe we don't need to do extra work to check the type for an fiq or irq controller?

I think without the eoi callback for the fiq it would only ever fire
once. I don't think doing the same eoi callback for the irq intc hurts
anything but it wouldn't do anything either from what I can tell.

> And I will update the patch as following:

I think maybe Marc or someone else that knows better than I do should
comment on what needs to happen.
My input is just that the fiq controller seems to trigger on an edge,
holds it's signal to the GIC high until eoi happens and then only
triggers again on an edge.
I guess it doesn't matter if it's an edge or level if that's how it
works but you'd only get one interrupt out of it per edge even if
configured as a level interrupt.

The main thing I didn't want was filtering out edge interrupts
entirely as that breaks using edge interrupts with gpios i.e. using
gpiomon.
With the changes to set the polarity it can now detect rising or
falling edge gpio events. :)

Thanks,

Daniel
Mark-PK Tsai (蔡沛剛) March 15, 2021, 8:58 a.m. UTC | #8
From: Daniel Palmer <daniel@0x0f.com>

> On Fri, 12 Mar 2021 at 01:11, Mark-PK Tsai <mark-pk.tsai@mediatek.com> wrote:
> > Why irq could accept either?
> 
> As the irq intc has no way to clear it's triggered state (no eoi) it
> must just pass the signal through instead of latching it?
> Otherwise it would latch once and never again right? That's what I
> really didn't understand.
> If it just passes the signal through and maybe inverts it then the GIC
> can use edge or level I think.

Yes, but if we accidentally loss a irq and the interrupt is edge triggered which is
latch to level by mst-intc, we will miss all the follow irqs because the driver
didn't reset the interrupt status.
Actually, I'm not sure if it's possible.
But even if it's not, I think use level for parent GIC can better match
the hardware signal processing.

> 
> > So maybe we don't need to do extra work to check the type for an fiq or irq controller?
> 
> I think without the eoi callback for the fiq it would only ever fire
> once. I don't think doing the same eoi callback for the irq intc hurts
> anything but it wouldn't do anything either from what I can tell.

The reason why I don't do the same eoi callback for irq intc is that
it's not ont spec.
And some of MTK TV SoC use it for certain debug function which
may cause unexpected result.

> 
> > And I will update the patch as following:
> 
> I think maybe Marc or someone else that knows better than I do should
> comment on what needs to happen.
> My input is just that the fiq controller seems to trigger on an edge,
> holds it's signal to the GIC high until eoi happens and then only
> triggers again on an edge.
> I guess it doesn't matter if it's an edge or level if that's how it
> works but you'd only get one interrupt out of it per edge even if
> configured as a level interrupt.
> 
> The main thing I didn't want was filtering out edge interrupts
> entirely as that breaks using edge interrupts with gpios i.e. using
> gpiomon.
> With the changes to set the polarity it can now detect rising or
> falling edge gpio events. :)

Thanks for your feedback and I will send patch v4 which includes the
change I proposed in this thread.
diff mbox series

Patch

diff --git a/drivers/irqchip/irq-mst-intc.c b/drivers/irqchip/irq-mst-intc.c
index 143657b0cf28..841b9b1c2699 100644
--- a/drivers/irqchip/irq-mst-intc.c
+++ b/drivers/irqchip/irq-mst-intc.c
@@ -13,15 +13,25 @@ 
 #include <linux/of_irq.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
+#include <linux/syscore_ops.h>
 
-#define INTC_MASK	0x0
-#define INTC_EOI	0x20
+#define INTC_MASK		0x0
+#define INTC_REV_POLARITY	0x10
+#define INTC_EOI		0x20
+
+#ifdef CONFIG_PM_SLEEP
+static LIST_HEAD(mst_intc_list);
+#endif
 
 struct mst_intc_chip_data {
 	raw_spinlock_t	lock;
 	unsigned int	irq_start, nr_irqs;
 	void __iomem	*base;
 	bool		no_eoi;
+#ifdef CONFIG_PM_SLEEP
+	struct list_head entry;
+	u16 saved_polarity_conf[DIV_ROUND_UP(64, 16)];
+#endif
 };
 
 static void mst_set_irq(struct irq_data *d, u32 offset)
@@ -78,6 +88,19 @@  static void mst_intc_eoi_irq(struct irq_data *d)
 	irq_chip_eoi_parent(d);
 }
 
+static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type)
+{
+	if (type != IRQ_TYPE_LEVEL_LOW && type != IRQ_TYPE_LEVEL_HIGH)
+		return -EINVAL;
+
+	if (type == IRQ_TYPE_LEVEL_LOW) {
+		mst_set_irq(data, INTC_REV_POLARITY);
+		type = IRQ_TYPE_LEVEL_HIGH;
+	}
+
+	return irq_chip_set_type_parent(data, type);
+}
+
 static struct irq_chip mst_intc_chip = {
 	.name			= "mst-intc",
 	.irq_mask		= mst_intc_mask_irq,
@@ -87,13 +110,62 @@  static struct irq_chip mst_intc_chip = {
 	.irq_set_irqchip_state	= irq_chip_set_parent_state,
 	.irq_set_affinity	= irq_chip_set_affinity_parent,
 	.irq_set_vcpu_affinity	= irq_chip_set_vcpu_affinity_parent,
-	.irq_set_type		= irq_chip_set_type_parent,
+	.irq_set_type		= mst_irq_chip_set_type,
 	.irq_retrigger		= irq_chip_retrigger_hierarchy,
 	.flags			= IRQCHIP_SET_TYPE_MASKED |
 				  IRQCHIP_SKIP_SET_WAKE |
 				  IRQCHIP_MASK_ON_SUSPEND,
 };
 
+#ifdef CONFIG_PM_SLEEP
+static void mst_intc_polarity_save(struct mst_intc_chip_data *cd)
+{
+	int i;
+	void __iomem *addr = cd->base + INTC_REV_POLARITY;
+
+	for (i = 0; i < DIV_ROUND_UP(cd->nr_irqs, 16); i++)
+		cd->saved_polarity_conf[i] = readw_relaxed(addr + i * 4);
+}
+
+static void mst_intc_polarity_restore(struct mst_intc_chip_data *cd)
+{
+	int i;
+	void __iomem *addr = cd->base + INTC_REV_POLARITY;
+
+	for (i = 0; i < DIV_ROUND_UP(cd->nr_irqs, 16); i++)
+		writew_relaxed(cd->saved_polarity_conf[i], addr + i * 4);
+}
+
+static void mst_irq_resume(void)
+{
+	struct mst_intc_chip_data *cd;
+
+	list_for_each_entry(cd, &mst_intc_list, entry)
+		mst_intc_polarity_restore(cd);
+}
+
+static int mst_irq_suspend(void)
+{
+	struct mst_intc_chip_data *cd;
+
+	list_for_each_entry(cd, &mst_intc_list, entry)
+		mst_intc_polarity_save(cd);
+	return 0;
+}
+
+static struct syscore_ops mst_irq_syscore_ops = {
+	.suspend	= mst_irq_suspend,
+	.resume		= mst_irq_resume,
+};
+
+static int __init mst_irq_pm_init(void)
+{
+	register_syscore_ops(&mst_irq_syscore_ops);
+	return 0;
+}
+late_initcall(mst_irq_pm_init);
+#endif
+
 static int mst_intc_domain_translate(struct irq_domain *d,
 				     struct irq_fwspec *fwspec,
 				     unsigned long *hwirq,
@@ -145,6 +217,14 @@  static int mst_intc_domain_alloc(struct irq_domain *domain, unsigned int virq,
 	parent_fwspec = *fwspec;
 	parent_fwspec.fwnode = domain->parent->fwnode;
 	parent_fwspec.param[1] = cd->irq_start + hwirq;
+
+	/*
+	 * If the irq signal is active low, configure it to active high
+	 * to meet GIC SPI spec in mst_irq_chip_set_type via REV_POLARITY bit
+	 */
+	if (fwspec->param[2] == IRQ_TYPE_LEVEL_LOW)
+		parent_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
+
 	return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec);
 }
 
@@ -193,6 +273,10 @@  static int __init mst_intc_of_init(struct device_node *dn,
 		return -ENOMEM;
 	}
 
+#ifdef CONFIG_PM_SLEEP
+	INIT_LIST_HEAD(&cd->entry);
+	list_add_tail(&cd->entry, &mst_intc_list);
+#endif
 	return 0;
 }