Message ID | 20210112055950.21209-1-samuel@sholland.org |
---|---|
Headers | show |
Series | sunxi: Support IRQ wakeup from deep sleep | expand |
On Mon, Jan 11, 2021 at 11:59:40PM -0600, Samuel Holland wrote: > Allwinner sun6i/sun8i/sun50i SoCs (A31 and newer) have two interrupt > controllers: GIC and R_INTC. GIC does not support wakeup. R_INTC handles > the external NMI pin, and provides 32+ IRQs to the ARISC. The first 16 > of these correspond 1:1 to a block of GIC IRQs starting with the NMI. > The last 13-16 multiplex the first (up to) 128 GIC SPIs. > > This series replaces the existing chained irqchip driver that could only > control the NMI, with a stacked irqchip driver that also provides wakeup > capability for those multiplexed SPI IRQs. The idea is to preconfigure > the ARISC's IRQ controller, and then the ARISC firmware knows to wake up > as soon as it receives an IRQ. It can also decide how deep it can > suspend based on the enabled wakeup IRQs. > > As future work, it may be useful to do the chained->stacked conversion > on the sunxi-nmi driver as well. > > Patches 1-2 add the new bindings. > Patch 3 adds the new driver. > Patch 4 adds wakeup capability. > Remaining patches update the device trees to use R_INTC where beneficial. > > With appropriate firmware and configuration, this series allows waking > from (and it has been tested with) the RTC, NMI/PMIC (power button, A/C > plug, etc.), all GPIO ports (button, lid switch, modem, etc.), LRADC, > and UARTs. I have tested this patch set on the H3, A64, H5, and H6 SoCs. Acked-by: Maxime Ripard <mripard@kernel.org> Thanks! Maxime
Hi Samuel, On 2021-01-12 05:59, Samuel Holland wrote: [...] > +static void sun6i_r_intc_ack_nmi(void) > +{ > + writel(SUN6I_NMI_BIT, base + SUN6I_IRQ_PENDING(0)); writel_relaxed() > +} > + > +static void sun6i_r_intc_nmi_ack(struct irq_data *data) > +{ > + if (irqd_get_trigger_type(data) & IRQ_TYPE_EDGE_BOTH) > + sun6i_r_intc_ack_nmi(); > + else > + data->chip_data = SUN6I_NMI_NEEDS_ACK; > +} > + > +static void sun6i_r_intc_nmi_eoi(struct irq_data *data) > +{ > + /* For oneshot IRQs, delay the ack until the IRQ is unmasked. */ > + if (data->chip_data == SUN6I_NMI_NEEDS_ACK && !irqd_irq_masked(data)) > { > + sun6i_r_intc_ack_nmi(); > + data->chip_data = 0; nit: NULL rather than 0? [...] > +static struct irq_chip sun6i_r_intc_nmi_chip = { > + .name = "sun6i-r-intc", > + .irq_ack = sun6i_r_intc_nmi_ack, > + .irq_mask = irq_chip_mask_parent, > + .irq_unmask = sun6i_r_intc_nmi_unmask, > + .irq_eoi = sun6i_r_intc_nmi_eoi, > + .irq_set_affinity = irq_chip_set_affinity_parent, > + .irq_set_type = sun6i_r_intc_nmi_set_type, > + .irq_set_irqchip_state = sun6i_r_intc_nmi_set_irqchip_state, You probably also want to wire irq_get_irqchip_state(), while you're at it. Otherwise, looks pretty good now. Thanks, M.
On Tue, 12 Jan 2021 05:59:44 +0000, Samuel Holland <samuel@sholland.org> wrote: > > Maintain bitmaps of wake-enabled IRQs and mux inputs, and program them > to the hardware during the syscore phase of suspend and shutdown. Then > restore the original set of enabled IRQs (only the NMI) during resume. > > This serves two purposes. First, it lets power management firmware > running on the ARISC coprocessor know which wakeup sources Linux wants > to have enabled. That way, it can avoid turning them off when it shuts > down the remainder of the clock tree. Second, it preconfigures the > coprocessor's interrupt controller, so the firmware's wakeup logic > is as simple as waiting for an interrupt to arrive. > > The suspend/resume logic is not conditional on PM_SLEEP because it is > identical to the init/shutdown logic. Wake IRQs may be enabled during > shutdown to allow powering the board back on. As an example, see > commit a5c5e50cce9d ("Input: gpio-keys - add shutdown callback"). > > Signed-off-by: Samuel Holland <samuel@sholland.org> > --- > drivers/irqchip/irq-sun6i-r.c | 107 ++++++++++++++++++++++++++++++++-- > 1 file changed, 101 insertions(+), 6 deletions(-) > > diff --git a/drivers/irqchip/irq-sun6i-r.c b/drivers/irqchip/irq-sun6i-r.c > index d04d067423f4..a1b58c98d6ca 100644 > --- a/drivers/irqchip/irq-sun6i-r.c > +++ b/drivers/irqchip/irq-sun6i-r.c > @@ -39,6 +39,7 @@ > * set of 128 mux bits. This requires a second set of top-level registers. > */ > > +#include <linux/bitmap.h> > #include <linux/interrupt.h> > #include <linux/irq.h> > #include <linux/irqchip.h> > @@ -46,6 +47,7 @@ > #include <linux/of.h> > #include <linux/of_address.h> > #include <linux/of_irq.h> > +#include <linux/syscore_ops.h> > > #include <dt-bindings/interrupt-controller/arm-gic.h> > > @@ -67,8 +69,17 @@ > #define SUN6I_NR_DIRECT_IRQS 16 > #define SUN6I_NR_MUX_BITS 128 > > +struct sun6i_r_intc_variant { > + u32 first_mux_irq; > + u32 nr_mux_irqs; > + u32 mux_valid[BITS_TO_U32(SUN6I_NR_MUX_BITS)]; > +}; > + > static void __iomem *base; > static irq_hw_number_t nmi_hwirq; > +static DECLARE_BITMAP(wake_irq_enabled, SUN6I_NR_TOP_LEVEL_IRQS); > +static DECLARE_BITMAP(wake_mux_enabled, SUN6I_NR_MUX_BITS); > +static DECLARE_BITMAP(wake_mux_valid, SUN6I_NR_MUX_BITS); > > static void sun6i_r_intc_ack_nmi(void) > { > @@ -145,6 +156,21 @@ static int sun6i_r_intc_nmi_set_irqchip_state(struct irq_data *data, > return irq_chip_set_parent_state(data, which, state); > } > > +static int sun6i_r_intc_irq_set_wake(struct irq_data *data, unsigned int on) > +{ > + unsigned long offset_from_nmi = data->hwirq - nmi_hwirq; > + > + if (offset_from_nmi < SUN6I_NR_DIRECT_IRQS) > + assign_bit(offset_from_nmi, wake_irq_enabled, on); > + else if (test_bit(data->hwirq, wake_mux_valid)) > + assign_bit(data->hwirq, wake_mux_enabled, on); > + else > + /* Not wakeup capable. */ > + return -EPERM; > + > + return 0; > +} > + > static struct irq_chip sun6i_r_intc_nmi_chip = { > .name = "sun6i-r-intc", > .irq_ack = sun6i_r_intc_nmi_ack, > @@ -154,8 +180,19 @@ static struct irq_chip sun6i_r_intc_nmi_chip = { > .irq_set_affinity = irq_chip_set_affinity_parent, > .irq_set_type = sun6i_r_intc_nmi_set_type, > .irq_set_irqchip_state = sun6i_r_intc_nmi_set_irqchip_state, > - .flags = IRQCHIP_SET_TYPE_MASKED | > - IRQCHIP_SKIP_SET_WAKE, > + .irq_set_wake = sun6i_r_intc_irq_set_wake, > + .flags = IRQCHIP_SET_TYPE_MASKED, > +}; > + > +static struct irq_chip sun6i_r_intc_wakeup_chip = { > + .name = "sun6i-r-intc", > + .irq_mask = irq_chip_mask_parent, > + .irq_unmask = irq_chip_unmask_parent, > + .irq_eoi = irq_chip_eoi_parent, > + .irq_set_affinity = irq_chip_set_affinity_parent, > + .irq_set_type = irq_chip_set_type_parent, > + .irq_set_wake = sun6i_r_intc_irq_set_wake, > + .flags = IRQCHIP_SET_TYPE_MASKED, Worth implementing irq_get/set_irqchip_state() using the _parent helper, I guess. Thanks, M.
Hello, On 1/14/21 3:06 PM, Marc Zyngier wrote: > Hi Samuel, > > On 2021-01-12 05:59, Samuel Holland wrote: > > [...] > >> +static void sun6i_r_intc_ack_nmi(void) >> +{ >> + writel(SUN6I_NMI_BIT, base + SUN6I_IRQ_PENDING(0)); > > writel_relaxed() irq_chip_unmask_parent(), which calls gic_unmask_irq(), is called immediately after this in .irq_unmask. Since gic_unmask_irq() also uses writel_relaxed(), the GIC write could be ordered before the write here. I was getting occasional spurious interrupts (1 out of each 20-25) when using a level trigger, which were resolved by switching to writel() here. I mentioned this in the changelog, but it probably deserves a comment in the code as well. Or maybe I should use an explicit barrier somewhere? >> +} >> + >> +static void sun6i_r_intc_nmi_ack(struct irq_data *data) >> +{ >> + if (irqd_get_trigger_type(data) & IRQ_TYPE_EDGE_BOTH) >> + sun6i_r_intc_ack_nmi(); >> + else >> + data->chip_data = SUN6I_NMI_NEEDS_ACK; >> +} >> + >> +static void sun6i_r_intc_nmi_eoi(struct irq_data *data) >> +{ >> + /* For oneshot IRQs, delay the ack until the IRQ is unmasked. */ >> + if (data->chip_data == SUN6I_NMI_NEEDS_ACK && !irqd_irq_masked(data)) >> { >> + sun6i_r_intc_ack_nmi(); >> + data->chip_data = 0; > > nit: NULL rather than 0? NULL seemed less appropriate since I'm not using the field as a pointer, but I don't have a strong opinion about it. > [...] > >> +static struct irq_chip sun6i_r_intc_nmi_chip = { >> + .name = "sun6i-r-intc", >> + .irq_ack = sun6i_r_intc_nmi_ack, >> + .irq_mask = irq_chip_mask_parent, >> + .irq_unmask = sun6i_r_intc_nmi_unmask, >> + .irq_eoi = sun6i_r_intc_nmi_eoi, >> + .irq_set_affinity = irq_chip_set_affinity_parent, >> + .irq_set_type = sun6i_r_intc_nmi_set_type, >> + .irq_set_irqchip_state = sun6i_r_intc_nmi_set_irqchip_state, > > You probably also want to wire irq_get_irqchip_state(), while > you're at it. I thought if the interrupt was pending here, it would necessarily also be pending at the GIC, so adding a separate layer would be redundant. irq_set_vcpu_affinity(), __irq_get_irqchip_state(), and irq_set_irqchip_state() [the functions, not the callbacks] have the interesting property that they search up the irqdomain hierarchy for the first irqdomain with the callback. So if all the callback would do is defer to its parent, it doesn't need to be provided at all*. *except in case this irqdomain has a child which calls irq_chip_get_parent_state(), which does not look past its immediate parent. But I did not think that case was worth worrying about. Cheers, Samuel > Otherwise, looks pretty good now. > > Thanks, > > M. >
On 1/14/21 3:44 PM, Marc Zyngier wrote: > On Tue, 12 Jan 2021 05:59:44 +0000, > Samuel Holland <samuel@sholland.org> wrote: >> >> Maintain bitmaps of wake-enabled IRQs and mux inputs, and program them >> to the hardware during the syscore phase of suspend and shutdown. Then >> restore the original set of enabled IRQs (only the NMI) during resume. >> >> This serves two purposes. First, it lets power management firmware >> running on the ARISC coprocessor know which wakeup sources Linux wants >> to have enabled. That way, it can avoid turning them off when it shuts >> down the remainder of the clock tree. Second, it preconfigures the >> coprocessor's interrupt controller, so the firmware's wakeup logic >> is as simple as waiting for an interrupt to arrive. >> >> The suspend/resume logic is not conditional on PM_SLEEP because it is >> identical to the init/shutdown logic. Wake IRQs may be enabled during >> shutdown to allow powering the board back on. As an example, see >> commit a5c5e50cce9d ("Input: gpio-keys - add shutdown callback"). >> >> Signed-off-by: Samuel Holland <samuel@sholland.org> >> --- >> drivers/irqchip/irq-sun6i-r.c | 107 ++++++++++++++++++++++++++++++++-- >> 1 file changed, 101 insertions(+), 6 deletions(-) >> >> diff --git a/drivers/irqchip/irq-sun6i-r.c b/drivers/irqchip/irq-sun6i-r.c >> index d04d067423f4..a1b58c98d6ca 100644 >> --- a/drivers/irqchip/irq-sun6i-r.c >> +++ b/drivers/irqchip/irq-sun6i-r.c >> @@ -39,6 +39,7 @@ >> * set of 128 mux bits. This requires a second set of top-level registers. >> */ >> >> +#include <linux/bitmap.h> >> #include <linux/interrupt.h> >> #include <linux/irq.h> >> #include <linux/irqchip.h> >> @@ -46,6 +47,7 @@ >> #include <linux/of.h> >> #include <linux/of_address.h> >> #include <linux/of_irq.h> >> +#include <linux/syscore_ops.h> >> >> #include <dt-bindings/interrupt-controller/arm-gic.h> >> >> @@ -67,8 +69,17 @@ >> #define SUN6I_NR_DIRECT_IRQS 16 >> #define SUN6I_NR_MUX_BITS 128 >> >> +struct sun6i_r_intc_variant { >> + u32 first_mux_irq; >> + u32 nr_mux_irqs; >> + u32 mux_valid[BITS_TO_U32(SUN6I_NR_MUX_BITS)]; >> +}; >> + >> static void __iomem *base; >> static irq_hw_number_t nmi_hwirq; >> +static DECLARE_BITMAP(wake_irq_enabled, SUN6I_NR_TOP_LEVEL_IRQS); >> +static DECLARE_BITMAP(wake_mux_enabled, SUN6I_NR_MUX_BITS); >> +static DECLARE_BITMAP(wake_mux_valid, SUN6I_NR_MUX_BITS); >> >> static void sun6i_r_intc_ack_nmi(void) >> { >> @@ -145,6 +156,21 @@ static int sun6i_r_intc_nmi_set_irqchip_state(struct irq_data *data, >> return irq_chip_set_parent_state(data, which, state); >> } >> >> +static int sun6i_r_intc_irq_set_wake(struct irq_data *data, unsigned int on) >> +{ >> + unsigned long offset_from_nmi = data->hwirq - nmi_hwirq; >> + >> + if (offset_from_nmi < SUN6I_NR_DIRECT_IRQS) >> + assign_bit(offset_from_nmi, wake_irq_enabled, on); >> + else if (test_bit(data->hwirq, wake_mux_valid)) >> + assign_bit(data->hwirq, wake_mux_enabled, on); >> + else >> + /* Not wakeup capable. */ >> + return -EPERM; >> + >> + return 0; >> +} >> + >> static struct irq_chip sun6i_r_intc_nmi_chip = { >> .name = "sun6i-r-intc", >> .irq_ack = sun6i_r_intc_nmi_ack, >> @@ -154,8 +180,19 @@ static struct irq_chip sun6i_r_intc_nmi_chip = { >> .irq_set_affinity = irq_chip_set_affinity_parent, >> .irq_set_type = sun6i_r_intc_nmi_set_type, >> .irq_set_irqchip_state = sun6i_r_intc_nmi_set_irqchip_state, >> - .flags = IRQCHIP_SET_TYPE_MASKED | >> - IRQCHIP_SKIP_SET_WAKE, >> + .irq_set_wake = sun6i_r_intc_irq_set_wake, >> + .flags = IRQCHIP_SET_TYPE_MASKED, >> +}; >> + >> +static struct irq_chip sun6i_r_intc_wakeup_chip = { >> + .name = "sun6i-r-intc", >> + .irq_mask = irq_chip_mask_parent, >> + .irq_unmask = irq_chip_unmask_parent, >> + .irq_eoi = irq_chip_eoi_parent, >> + .irq_set_affinity = irq_chip_set_affinity_parent, >> + .irq_set_type = irq_chip_set_type_parent, >> + .irq_set_wake = sun6i_r_intc_irq_set_wake, >> + .flags = IRQCHIP_SET_TYPE_MASKED, > > Worth implementing irq_get/set_irqchip_state() using the _parent > helper, I guess. This is the same situation as the previous patch. Assuming it is safe to rely on the behavior of the top-level functions, adding the callbacks here would be redundant. Cheers, Samuel > Thanks, > > M. >
On 2021-01-15 04:01, Samuel Holland wrote: > Hello, > > On 1/14/21 3:06 PM, Marc Zyngier wrote: >> Hi Samuel, >> >> On 2021-01-12 05:59, Samuel Holland wrote: >> >> [...] >> >>> +static void sun6i_r_intc_ack_nmi(void) >>> +{ >>> + writel(SUN6I_NMI_BIT, base + SUN6I_IRQ_PENDING(0)); >> >> writel_relaxed() > > irq_chip_unmask_parent(), which calls gic_unmask_irq(), is called > immediately after this in .irq_unmask. Since gic_unmask_irq() also uses > writel_relaxed(), the GIC write could be ordered before the write here. That's odd. writel() places a barrier *before* the actual write, ensuring that this write is ordered w.r.t. previous accesses. If you are trying to ensure ordering with what follows, you need an explicit barrier after this access. I guess that in the end, you may need both, as what you have orders the access to GICC_AIR to take place before the write to this pending register, and you also need to provide the ordering you just described. > > I was getting occasional spurious interrupts (1 out of each 20-25) when > using a level trigger, which were resolved by switching to writel() > here. > > I mentioned this in the changelog, but it probably deserves a comment > in > the code as well. Or maybe I should use an explicit barrier somewhere? Please document it in the code. This is subtle enough to warrant a good description. >>> +} >>> + >>> +static void sun6i_r_intc_nmi_ack(struct irq_data *data) >>> +{ >>> + if (irqd_get_trigger_type(data) & IRQ_TYPE_EDGE_BOTH) >>> + sun6i_r_intc_ack_nmi(); >>> + else >>> + data->chip_data = SUN6I_NMI_NEEDS_ACK; >>> +} >>> + >>> +static void sun6i_r_intc_nmi_eoi(struct irq_data *data) >>> +{ >>> + /* For oneshot IRQs, delay the ack until the IRQ is unmasked. */ >>> + if (data->chip_data == SUN6I_NMI_NEEDS_ACK && >>> !irqd_irq_masked(data)) >>> { >>> + sun6i_r_intc_ack_nmi(); >>> + data->chip_data = 0; >> >> nit: NULL rather than 0? > > NULL seemed less appropriate since I'm not using the field as a > pointer, > but I don't have a strong opinion about it. chip_data *is* a pointer, which is why we conventionally use NULL rather than an integer value. Up to you. > >> [...] >> >>> +static struct irq_chip sun6i_r_intc_nmi_chip = { >>> + .name = "sun6i-r-intc", >>> + .irq_ack = sun6i_r_intc_nmi_ack, >>> + .irq_mask = irq_chip_mask_parent, >>> + .irq_unmask = sun6i_r_intc_nmi_unmask, >>> + .irq_eoi = sun6i_r_intc_nmi_eoi, >>> + .irq_set_affinity = irq_chip_set_affinity_parent, >>> + .irq_set_type = sun6i_r_intc_nmi_set_type, >>> + .irq_set_irqchip_state = sun6i_r_intc_nmi_set_irqchip_state, >> >> You probably also want to wire irq_get_irqchip_state(), while >> you're at it. > > I thought if the interrupt was pending here, it would necessarily also > be pending at the GIC, so adding a separate layer would be redundant. > > irq_set_vcpu_affinity(), __irq_get_irqchip_state(), and > irq_set_irqchip_state() [the functions, not the callbacks] have the > interesting property that they search up the irqdomain hierarchy for > the > first irqdomain with the callback. So if all the callback would do is > defer to its parent, it doesn't need to be provided at all*. Ah, of course... I even wrote that code! Thanks, M.