[PATCH v3 03/10] irqchip/sun6i-r: Use a stacked irqchip driver

Samuel Holland samuel at sholland.org
Sun Jan 3 07:08:43 EST 2021


On 1/3/21 5:27 AM, Marc Zyngier wrote:
> On Sun, 03 Jan 2021 10:30:54 +0000,
> Samuel Holland <samuel at sholland.org> wrote:
>>
>> The R_INTC in the A31 and newer sun8i/sun50i SoCs is more similar to the
>> original sun4i interrupt controller than the sun7i/sun9i NMI controller.
>> It is used for two distinct purposes:
>>  - To control the trigger, latch, and mask for the NMI input pin
>>  - To provide the interrupt input for the ARISC coprocessor
>>
>> As this interrupt controller is not documented, information about it
>> comes from vendor-provided firmware blobs and from experimentation.
>>
>> Like the original sun4i interrupt controller, it has:
>>  - A VECTOR_REG at 0x00 (configurable via the BASE_ADDR_REG at 0x04)
>>  - A NMI_CTRL_REG, PENDING_REG, and ENABLE_REG as used by both the
>>    sun4i and sunxi-nmi drivers
>>  - A MASK_REG at 0x50
>>  - A RESP_REG at 0x60
>>
>> Differences from the sun4i interrupt controller appear to be:
>>  - It only has one or two registers of each kind (max 32 or 64 IRQs)
>>  - Multiplexing logic is added to support additional inputs
>>  - There is no FIQ-related logic
>>  - There is no interrupt priority logic
>>
>> In order to fulfill its two purposes, this hardware block combines four
>> types of IRQs. First, the NMI pin is routed to the "IRQ 0" input on this
>> chip, with a trigger type controlled by the NMI_CTRL_REG. The "IRQ 0
>> pending" output from this chip, if enabled, is then routed to a SPI IRQ
>> input on the GIC. In other words, bit 0 of IRQ_ENABLE_REG *does* affect
>> the NMI IRQ seen at the GIC.
>>
>> The NMI is followed by a contiguous block of 15 "direct" (my name for
>> them) IRQ inputs that are connected in parallel to both R_INTC and the
>> GIC. Or in other words, these bits of IRQ_ENABLE_REG *do not* affect the
>> IRQs seen at the GIC.
>>
>> Following the direct IRQs are the ARISC's copy of banked IRQs for shared
>> peripherals. These are not relevant to Linux. The remaining IRQs are
>> connected to a multiplexer and provide access to the first (up to) 128
>> SPIs from the ARISC. This range of SPIs overlaps with the direct IRQs.
>>
>> Finally, the global "IRQ pending" output from R_INTC, after being masked
>> by MASK_REG and RESP_REG, is connected to the "external interrupt" input
>> of the ARISC CPU. This path is also irrelevant to Linux.
> 
> An ASCII-art version of this would help a lot, and would look good in
> the driver code...

There is this diagram which I forgot to include in the cover letter:

  https://linux-sunxi.org/images/5/5c/R_INTC.png

I can try to come up with some ASCII art.

>> Because of the 1:1 correspondence between R_INTC and GIC inputs, this is
>> a perfect scenario for using a stacked irqchip driver. We want to hook
>> into enabling/disabling IRQs to add more features to the GIC
>> (specifically to allow masking the NMI and setting its trigger type),
>> but we don't need to actually handle the IRQ in this driver.
>>
>> And since R_INTC is in the always-on power domain, and its output is
>> connected directly in to the power management coprocessor, a stacked
>> irqchip driver provides a simple way to add wakeup support to this set
>> of IRQs. That is the next patch; for now, just the NMI is moved over.
>>
>> To allow access to all multiplexed IRQs, this driver requires a new
>> binding where the interrupt number matches the GIC interrupt number.
>> (This moves the NMI number from 0 to 32 or 96, depending on the SoC.)
>> For simplicity, copy the three-cell GIC binding; this disambiguates
>> interrupt 0 in the old binding (the NMI) from interrupt 0 in the new
>> binding (SPI 0) by the number of cells.
>>
>> This commit mostly reverts commit 173bda53b340 ("irqchip/sunxi-nmi:
>> Support sun6i-a31-r-intc compatible").
>>
>> Signed-off-by: Samuel Holland <samuel at sholland.org>
>> ---
>>  arch/arm/mach-sunxi/Kconfig     |   1 +
>>  arch/arm64/Kconfig.platforms    |   1 +
>>  drivers/irqchip/Makefile        |   1 +
>>  drivers/irqchip/irq-sun6i-r.c   | 267 ++++++++++++++++++++++++++++++++
>>  drivers/irqchip/irq-sunxi-nmi.c |  26 +---
>>  5 files changed, 273 insertions(+), 23 deletions(-)
>>  create mode 100644 drivers/irqchip/irq-sun6i-r.c
>>
> 
> [...]
> 
>> diff --git a/drivers/irqchip/irq-sun6i-r.c b/drivers/irqchip/irq-sun6i-r.c
>> new file mode 100644
>> index 000000000000..7490ade7b254
>> --- /dev/null
>> +++ b/drivers/irqchip/irq-sun6i-r.c
>> @@ -0,0 +1,267 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +//
>> +// R_INTC driver for Allwinner A31 and newer SoCs
>> +//
>> +
>> +#include <linux/irq.h>
>> +#include <linux/irqchip.h>
>> +#include <linux/irqdomain.h>
>> +#include <linux/of.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_irq.h>
>> +
>> +#include <dt-bindings/interrupt-controller/arm-gic.h>
>> +
>> +/*
>> + * The R_INTC manages between 32 and 64 IRQs, divided into four groups. Example
>> + * bit numbers are for the original variant in the A31:
>> + *
>> + *   Bit      0: The "External NMI" input, connected in series to a GIC SPI.
>> + *   Bits  1-15: "Direct" IRQs for ARISC peripherals, connected in parallel to
>> + *               the GIC and mapped 1:1 to SPIs numerically following the NMI.
>> + *   Bits 16-18: "Banked" IRQs for peripherals that have separate interfaces
>> + *               for the ARM CPUs and ARISC. These do not map to any GIC SPI.
>> + *   Bits 19-31: "Muxed" IRQs, each corresponding to a group of up to 8 SPIs.
>> + *               Later variants added a second PENDING and ENABLE register to
>> + *               make use of all 128 mux inputs (16 IRQ lines).
>> + *
>> + * Since the direct IRQs are inside the muxed IRQ range, they do not increase
>> + * the number of HWIRQs needed.
>> + */
>> +#define SUN6I_NR_IRQS			64
>> +#define SUN6I_NR_DIRECT_IRQS		16
>> +#define SUN6I_NR_MUX_INPUTS		128
>> +#define SUN6I_NR_HWIRQS			SUN6I_NR_MUX_INPUTS
>> +
>> +#define SUN6I_NMI_CTRL			(0x0c)
>> +#define SUN6I_IRQ_PENDING(n)		(0x10 + 4 * (n))
>> +#define SUN6I_IRQ_ENABLE(n)		(0x40 + 4 * (n))
>> +#define SUN6I_MUX_ENABLE(n)		(0xc0 + 4 * (n))
>> +
>> +#define SUN6I_NMI_IRQ_BIT		BIT(0)
>> +
>> +static void __iomem *base;
>> +static irq_hw_number_t nmi_hwirq;
>> +static u32 nmi_type;
>> +
>> +static struct irq_chip sun6i_r_intc_edge_chip;
>> +static struct irq_chip sun6i_r_intc_level_chip;
>> +
>> +static void sun6i_r_intc_nmi_ack(void)
>> +{
>> +	/*
>> +	 * The NMI channel has a latch separate from its trigger type.
>> +	 * This latch must be cleared to clear the signal to the GIC.
>> +	 */
>> +	writel_relaxed(SUN6I_NMI_IRQ_BIT, base + SUN6I_IRQ_PENDING(0));
>> +}
>> +
>> +static void sun6i_r_intc_irq_mask(struct irq_data *data)
>> +{
>> +	if (data->hwirq == nmi_hwirq)
>> +		sun6i_r_intc_nmi_ack();
> 
> I'm a bit worried by this. I can see it working with level interrupts
> (you can clear the input, and if the interrupt is asserted, it will
> fire again), but I'm worried that it will simply result in lost
> interrupts for edge signalling.

For edge interrupts, don't you want to ack as early as possible, before the
handler clears the source of the interrupt? That way if a second interrupt comes
in while you're handling the first one, you don't ack the second one without
handling it?

> It also begs the question: why would you want to clear the signal to
> the GIC on mask (or unmask)? The expectations are that a pending
> interrupt is preserved across a mask/unmask sequence.

I hadn't thought about anything masking the IRQ outside of the handler; but
you're right, this breaks that case. I'm trying to work within the constraints
of stacking the GIC driver, which assumes handle_fasteoi_irq, so it sounds like
I should switch back to handle_fasteoi_ack_irq and use .irq_ack. Or based on
your previous paragraph, maybe I'm missing some other consideration?

>> +
>> +	irq_chip_mask_parent(data);
>> +}
>> +
>> +static void sun6i_r_intc_irq_unmask(struct irq_data *data)
>> +{
>> +	if (data->hwirq == nmi_hwirq)
>> +		sun6i_r_intc_nmi_ack();
>> +
>> +	irq_chip_unmask_parent(data);
>> +}
>> +
>> +static int sun6i_r_intc_irq_set_type(struct irq_data *data, unsigned int type)
>> +{
>> +	/*
>> +	 * The GIC input labeled "External NMI" connects to bit 0 of the R_INTC
>> +	 * PENDING register, not to the pin directly. So the trigger type of the
>> +	 * GIC input does not depend on the trigger type of the NMI pin itself.
>> +	 *
>> +	 * Only the NMI channel is routed through this interrupt controller on
>> +	 * its way to the GIC. Other IRQs are routed to the GIC and R_INTC in
>> +	 * parallel; they must have a trigger type appropriate for the GIC.
>> +	 */
>> +	if (data->hwirq == nmi_hwirq) {
>> +		struct irq_chip *chip;
>> +		u32 nmi_src_type;
>> +
>> +		switch (type) {
>> +		case IRQ_TYPE_LEVEL_LOW:
>> +			chip = &sun6i_r_intc_level_chip;
>> +			nmi_src_type = 0;
> 
> Please add symbolic names for these types.

I removed them based on your previous comment:

https://lkml.org/lkml/2020/1/20/278
> It is unusual to use an enum for values that get directly programmed into the HW.

Do you want them to be specifically #defines?

>> +			break;
>> +		case IRQ_TYPE_EDGE_FALLING:
>> +			chip = &sun6i_r_intc_edge_chip;
>> +			nmi_src_type = 1;
>> +			break;
>> +		case IRQ_TYPE_LEVEL_HIGH:
>> +			chip = &sun6i_r_intc_level_chip;
>> +			nmi_src_type = 2;
>> +			break;
>> +		case IRQ_TYPE_EDGE_RISING:
>> +			chip = &sun6i_r_intc_edge_chip;
>> +			nmi_src_type = 3;
>> +			break;
>> +		default:
>> +			pr_err("%pOF: invalid trigger type %d for IRQ %d\n",
>> +			       irq_domain_get_of_node(data->domain), type,
>> +			       data->irq);
> 
> A failing set_type already triggers a kernel message.

I'll remove this.

>> +			return -EBADR;
> 
> That's a pretty odd error. I see it used in 3 drivers (including the
> one this driver replaces), but the canonical error code is -EINVAL.

I'll change it to -EINVAL.

>> +		}
>> +
>> +		irq_set_chip_handler_name_locked(data, chip,
>> +						 handle_fasteoi_irq, NULL);
>> +
>> +		writel_relaxed(nmi_src_type, base + SUN6I_NMI_CTRL);
>> +
>> +		/*
>> +		 * Use the trigger type from the OF node for the NMI's
>> +		 * R_INTC to GIC connection.
>> +		 */
>> +		type = nmi_type;
> 
> This looks wrong. The GIC only supports level-high and edge-rising, so
> half of the possible settings will result in an error. I assume the
> R_INTC has an inverter controlled by nmi_src_type, and only outputs
> something the GIC can grok.

nmi_type isn't the setting from the incoming IRQ. nmi_type is from the
interrupts property in the irqchip OF node itself. So this is a fwspec for
`interrupt-parent = <&gic>;`, i.e. already assumed to be GIC-compatible. I'm not
sure what you mean by "half of the possible settings".

Maybe I should remove the `interrupts` property and hardcode the number and type
connecting to the GIC? If I did that, I'm not quite sure whether it would be
high or rising. The output to the GIC is literally the bit in the pending
register: 1 for pending, 0 for not. Since that is after a latch (regardless of
the input trigger type) it sounds like it should be level-high, because a pulse
would have already been converted to a steady signal. Or does it depend on when
I clear the latch?

>> +	}
>> +
>> +	return irq_chip_set_type_parent(data, type);
>> +}
>> +
>> +static struct irq_chip sun6i_r_intc_edge_chip = {
>> +	.name			= "sun6i-r-intc",
>> +	.irq_mask		= sun6i_r_intc_irq_mask,
>> +	.irq_unmask		= irq_chip_unmask_parent,
>> +	.irq_eoi		= irq_chip_eoi_parent,
>> +	.irq_set_affinity	= irq_chip_set_affinity_parent,
>> +	.irq_set_type		= sun6i_r_intc_irq_set_type,
>> +	.irq_get_irqchip_state	= irq_chip_get_parent_state,
>> +	.irq_set_irqchip_state	= irq_chip_set_parent_state,
>> +	.irq_set_vcpu_affinity	= irq_chip_set_vcpu_affinity_parent,
>> +	.flags			= IRQCHIP_SET_TYPE_MASKED,
>> +};
>> +
>> +static struct irq_chip sun6i_r_intc_level_chip = {
>> +	.name			= "sun6i-r-intc",
>> +	.irq_mask		= irq_chip_mask_parent,
>> +	.irq_unmask		= sun6i_r_intc_irq_unmask,
>> +	.irq_eoi		= irq_chip_eoi_parent,
>> +	.irq_set_affinity	= irq_chip_set_affinity_parent,
>> +	.irq_set_type		= sun6i_r_intc_irq_set_type,
>> +	.irq_get_irqchip_state	= irq_chip_get_parent_state,
>> +	.irq_set_irqchip_state	= irq_chip_set_parent_state,
>> +	.irq_set_vcpu_affinity	= irq_chip_set_vcpu_affinity_parent,
>> +	.flags			= IRQCHIP_SET_TYPE_MASKED,
>> +};
>> +
>> +static int sun6i_r_intc_domain_translate(struct irq_domain *domain,
>> +					 struct irq_fwspec *fwspec,
>> +					 unsigned long *hwirq,
>> +					 unsigned int *type)
>> +{
>> +	/* Accept the old two-cell binding for the NMI only. */
>> +	if (fwspec->param_count == 2 && fwspec->param[0] == 0) {
>> +		*hwirq = nmi_hwirq;
>> +		*type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
>> +		return 0;
>> +	}
>> +
>> +	/* Otherwise this binding should match the GIC SPI binding. */
>> +	if (fwspec->param_count < 3)
>> +		return -EINVAL;
>> +	if (fwspec->param[0] != GIC_SPI)
>> +		return -EINVAL;
>> +
>> +	*hwirq = fwspec->param[1];
>> +	*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
>> +
>> +	return 0;
>> +}
>> +
>> +static int sun6i_r_intc_domain_alloc(struct irq_domain *domain,
>> +				     unsigned int virq,
>> +				     unsigned int nr_irqs, void *arg)
>> +{
>> +	struct irq_fwspec *fwspec = arg;
>> +	struct irq_fwspec gic_fwspec;
>> +	unsigned long hwirq;
>> +	unsigned int type;
>> +	int i, ret;
>> +
>> +	ret = sun6i_r_intc_domain_translate(domain, fwspec, &hwirq, &type);
>> +	if (ret)
>> +		return ret;
>> +	if (hwirq + nr_irqs > SUN6I_NR_HWIRQS)
>> +		return -EINVAL;
>> +
>> +	/* Construct a GIC-compatible fwspec from this fwspec. */
>> +	gic_fwspec = (struct irq_fwspec) {
>> +		.fwnode      = domain->parent->fwnode,
>> +		.param_count = 3,
>> +		.param       = { GIC_SPI, hwirq, type },
>> +	};
>> +
>> +	for (i = 0; i < nr_irqs; ++i)
>> +		irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
>> +					      &sun6i_r_intc_level_chip, NULL);
> 
> Unconditionally level, without looking at the requested type?

__setup_irq calls __irq_set_trigger if any trigger is provided, which calls
chip->irq_set_type unconditionally. So I don't think the chip here matters,
because .irq_set_type will be called before the IRQ is enabled anyway. Again,
maybe I'm missing something.

For non-NMI IRQs, the choice of chip doesn't matter, because this driver handles
nothing but .irq_set_wake for them (should I provide a third chip for this that
doesn't provide its own ack/mask/unmask?).

>> +
>> +	return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_fwspec);
>> +}
>> +
>> +static const struct irq_domain_ops sun6i_r_intc_domain_ops = {
>> +	.translate	= sun6i_r_intc_domain_translate,
>> +	.alloc		= sun6i_r_intc_domain_alloc,
>> +	.free		= irq_domain_free_irqs_common,
>> +};
>> +
>> +static void sun6i_r_intc_resume(void)
>> +{
>> +	int i;
>> +
>> +	/* Only the NMI is relevant during normal operation. */
>> +	writel_relaxed(SUN6I_NMI_IRQ_BIT, base + SUN6I_IRQ_ENABLE(0));
>> +	for (i = 1; i < BITS_TO_U32(SUN6I_NR_IRQS); ++i)
>> +		writel_relaxed(0, base + SUN6I_IRQ_ENABLE(i));
> 
> If only the NMI is relevant, why are the other interrupts enabled?
> Shouldn't this be moved to the following patch (I presume this is
> wake-up related...).

The other IRQs aren't enabled? I'm writing all zeroes to the enable register.

>> +}
>> +
>> +static int __init sun6i_r_intc_init(struct device_node *node,
>> +				    struct device_node *parent)
>> +{
>> +	struct irq_domain *domain, *parent_domain;
>> +	struct of_phandle_args parent_irq;
>> +	int ret;
>> +
>> +	/* Extract the NMI's R_INTC to GIC mapping from the OF node. */
>> +	ret = of_irq_parse_one(node, 0, &parent_irq);
>> +	if (ret)
>> +		return ret;
>> +	if (parent_irq.args_count < 3 || parent_irq.args[0] != GIC_SPI)
>> +		return -EINVAL;
>> +	nmi_hwirq = parent_irq.args[1];
>> +	nmi_type = parent_irq.args[2];
> 
> This looks a lot like the translate callback.

Yes, I could use that here.

>> +
>> +	parent_domain = irq_find_host(parent);
>> +	if (!parent_domain) {
>> +		pr_err("%pOF: Failed to obtain parent domain\n", node);
>> +		return -ENXIO;
>> +	}
>> +
>> +	base = of_io_request_and_map(node, 0, NULL);
>> +	if (IS_ERR(base)) {
>> +		pr_err("%pOF: Failed to map MMIO region\n", node);
>> +		return PTR_ERR(base);
>> +	}
>> +
>> +	sun6i_r_intc_nmi_ack();
>> +	sun6i_r_intc_resume();
>> +
>> +	domain = irq_domain_add_hierarchy(parent_domain, 0,
>> +					  SUN6I_NR_HWIRQS, node,
>> +					  &sun6i_r_intc_domain_ops, NULL);
>> +	if (!domain) {
>> +		pr_err("%pOF: Failed to allocate domain\n", node);
>> +		iounmap(base);
>> +		return -ENOMEM;
>> +	}
>> +
>> +	return 0;
>> +}
>> +IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc", sun6i_r_intc_init);
> 
> Thanks,
> 
> 	M.
> 

Thank you for your (extremely quick, I must say) review!
Samuel



More information about the linux-arm-kernel mailing list