Message ID | 20190203214205.13594-5-linus.walleij@linaro.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | ARM: ixp4xx: Modernize and DT support | expand |
Hi Linus, On 03/02/2019 21:41, Linus Walleij wrote: > The IXP4xx (arch/arm/mach-ixp4xx) is an old Intel XScale > platform that has very wide deployment and use. > > As part of modernizing the platform, we need to implement a > proper irqchip in the irqchip subsystem. > > The IXP4xx irqchip is tightly jotted together with the GPIO > controller, and wheras in the past we would deal with this nit: whereas > complex logic by adding necessarily different code, we can > nowadays modernize it using a hierarchical irqchip. > > The actual IXP4 irqchip is a simple active low level IRQ > controller, whereas the GPIO functionality resides in a > different memory area and adds edge trigger support for > the interrupts. > > The interrupts from GPIO lines 0..12 are 1:1 mapped to > a fixed set of hardware IRQs on this IRQchip, so we > expect the child GPIO interrupt controller to go in and > allocate descriptors for these interrupts. > > For the other interrupts, as we do not yet have DT > support for this platform, we create a linear irqdomain > and then go in and allocate the IRQs that the legacy > boards use. This code will be removed on the DT probe > path when we add DT support to the platform. > > We add some translation code for supporting DT > translations for the fwnodes, but we leave most of that > for later. > > Cc: Marc Zyngier <marc.zyngier@arm.com> > Cc: Jason Cooper <jason@lakedaemon.net> > Cc: Thomas Gleixner <tglx@linutronix.de> > Signed-off-by: Linus Walleij <linus.walleij@linaro.org> > --- > irqchip maintainers: I am requesting an ACK for this once > you're happy with the driver, as I intend to merge all of > this IXP4xx rework through ARM SoC. > --- > MAINTAINERS | 2 + > drivers/irqchip/Kconfig | 6 + > drivers/irqchip/Makefile | 1 + > drivers/irqchip/irq-ixp4xx.c | 360 +++++++++++++++++++++++++++++ > include/linux/irqchip/irq-ixp4xx.h | 12 + > 5 files changed, 381 insertions(+) > create mode 100644 drivers/irqchip/irq-ixp4xx.c > create mode 100644 include/linux/irqchip/irq-ixp4xx.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 32d444476a90..0d48faa3e635 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -1651,6 +1651,8 @@ M: Krzysztof Halasa <khalasa@piap.pl> > L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) > S: Maintained > F: arch/arm/mach-ixp4xx/ > +F: drivers/irqchip/irq-ixp4xx.c > +F: include/linux/irqchip/irq-ixp4xx.h > > ARM/INTEL RESEARCH IMOTE/STARGATE 2 MACHINE SUPPORT > M: Jonathan Cameron <jic23@cam.ac.uk> > diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig > index 3d1e60779078..c7e09913826b 100644 > --- a/drivers/irqchip/Kconfig > +++ b/drivers/irqchip/Kconfig > @@ -150,6 +150,12 @@ config IMGPDC_IRQ > select GENERIC_IRQ_CHIP > select IRQ_DOMAIN > > +config IXP4XX_IRQ > + bool > + select IRQ_DOMAIN > + select GENERIC_IRQ_MULTI_HANDLER > + select SPARSE_IRQ > + > config MADERA_IRQ > tristate > > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile > index c93713d24b86..06139d612108 100644 > --- a/drivers/irqchip/Makefile > +++ b/drivers/irqchip/Makefile > @@ -41,6 +41,7 @@ obj-$(CONFIG_ATMEL_AIC5_IRQ) += irq-atmel-aic-common.o irq-atmel-aic5.o > obj-$(CONFIG_I8259) += irq-i8259.o > obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o > obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o > +obj-$(CONFIG_IXP4XX_IRQ) += irq-ixp4xx.o > obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o > obj-$(CONFIG_JCORE_AIC) += irq-jcore-aic.o > obj-$(CONFIG_RDA_INTC) += irq-rda-intc.o > diff --git a/drivers/irqchip/irq-ixp4xx.c b/drivers/irqchip/irq-ixp4xx.c > new file mode 100644 > index 000000000000..7deaf0f82a53 > --- /dev/null > +++ b/drivers/irqchip/irq-ixp4xx.c > @@ -0,0 +1,360 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * irqchip for the IXP4xx interrupt controller > + * Copyright (C) 2019 Linus Walleij <linus.walleij@linaro.org> > + * > + * Based on arch/arm/mach-ixp4xx/common.c > + * Copyright 2002 (C) Intel Corporation > + * Copyright 2003-2004 (C) MontaVista, Software, Inc. > + * Copyright (C) Deepak Saxena <dsaxena@plexity.net> > + */ > +#include <linux/bitops.h> > +#include <linux/gpio/driver.h> > +#include <linux/irq.h> > +#include <linux/io.h> > +#include <linux/irqchip.h> > +#include <linux/irqchip/irq-ixp4xx.h> > +#include <linux/irqdomain.h> > +#include <linux/platform_device.h> > +#include <linux/cpu.h> > + > +#include <asm/exception.h> > +#include <asm/mach/irq.h> > + > +#define IXP4XX_ICPR 0x00 /* Interrupt Status */ > +#define IXP4XX_ICMR 0x04 /* Interrupt Enable */ > +#define IXP4XX_ICLR 0x08 /* Interrupt IRQ/FIQ Select */ > +#define IXP4XX_ICIP 0x0C /* IRQ Status */ > +#define IXP4XX_ICFP 0x10 /* FIQ Status */ > +#define IXP4XX_ICHR 0x14 /* Interrupt Priority */ > +#define IXP4XX_ICIH 0x18 /* IRQ Highest Pri Int */ > +#define IXP4XX_ICFH 0x1C /* FIQ Highest Pri Int */ > + > +/* IXP43x and IXP46x-only */ > +#define IXP4XX_ICPR2 0x20 /* Interrupt Status 2 */ > +#define IXP4XX_ICMR2 0x24 /* Interrupt Enable 2 */ > +#define IXP4XX_ICLR2 0x28 /* Interrupt IRQ/FIQ Select 2 */ > +#define IXP4XX_ICIP2 0x2C /* IRQ Status */ > +#define IXP4XX_ICFP2 0x30 /* FIQ Status */ > +#define IXP4XX_ICEEN 0x34 /* Error High Pri Enable */ > + > +/** > + * struct ixp4xx_irq - state container for the Faraday IRQ controller > + * @irqbase: IRQ controller memory base in virtual memory > + * @is_356: if this is an IXP43x, IXP45x or IX46x SoC (with 64 IRQs) > + * @irqchip: irqchip for this instance > + * @domain: IRQ domain for this instance > + */ > +struct ixp4xx_irq { > + void __iomem *irqbase; > + bool is_356; > + struct irq_chip irqchip; > + struct irq_domain *domain; > +}; > + > +/* Local static state container */ > +static struct ixp4xx_irq ixirq; > + > +/* GPIO Clocks */ > +#define IXP4XX_GPIO_CLK_0 14 > +#define IXP4XX_GPIO_CLK_1 15 > + > +static int ixp4xx_set_irq_type(struct irq_data *d, unsigned int type) > +{ > + /* All are level active high (asserted) here */ It'd be good to return an error if type isn't LEVEL_HIGH. > + return 0; > +} > + > +static void ixp4xx_irq_mask(struct irq_data *d) > +{ > + struct ixp4xx_irq *ixi = irq_data_get_irq_chip_data(d); > + u32 val; > + > + if (ixi->is_356 && d->hwirq >= 32) { > + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR2); > + val &= ~BIT(d->hwirq - 32); > + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR2); > + } else { > + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR); > + val &= ~BIT(d->hwirq); > + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR); > + } > +} This probably comes from the original code, but I'd like to be able to use a LE kernel on this HW (full disclosure: I have some of this crap stashed somewhere in the basement... ;-). How about using something that enforces the endianness of the accesses, as I suspect the bus is hardcoded to BE? ioread32be/iowrite32be springs to mind, and I can see the current IXP4xx code provides such an implementation in its own io.h (which you may have to make private). > + > +/* > + * Level triggered interrupts on GPIO lines can only be cleared when the > + * interrupt condition disappears. > + */ > +static void ixp4xx_irq_unmask(struct irq_data *d) > +{ > + struct ixp4xx_irq *ixi = irq_data_get_irq_chip_data(d); > + u32 val; > + > + if (ixi->is_356 && d->hwirq >= 32) { > + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR2); > + val |= BIT(d->hwirq - 32); > + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR2); > + } else { > + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR); > + val |= BIT(d->hwirq); > + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR); > + } > +} Same here, as well as all the other places sporting a __raw_ accessor. > + > +asmlinkage void __exception_irq_entry ixp4xx_handle_irq(struct pt_regs *regs) > +{ > + struct ixp4xx_irq *ixi = &ixirq; > + unsigned long status; > + int i; > + > + status = __raw_readl(ixi->irqbase + IXP4XX_ICIP); > + for_each_set_bit(i, &status, 32) > + handle_domain_irq(ixi->domain, i, regs); > + > + /* > + * IXP465/IXP435 has an upper IRQ status register > + */ > + if (ixi->is_356) { > + status = __raw_readl(ixi->irqbase + IXP4XX_ICIP2); > + for_each_set_bit(i, &status, 32) > + handle_domain_irq(ixi->domain, i + 32, regs); > + } > +} > + > +static int ixp4xx_irq_domain_translate(struct irq_domain *domain, > + struct irq_fwspec *fwspec, > + unsigned long *hwirq, > + unsigned int *type) > +{ > + /* We support standard DT translation */ > + if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) { > + *hwirq = fwspec->param[0]; > + *type = fwspec->param[1]; > + return 0; > + } > + > + if (is_fwnode_irqchip(fwspec->fwnode)) { > + if (fwspec->param_count != 2) > + return -EINVAL; > + *hwirq = fwspec->param[0]; > + *type = fwspec->param[1]; > + WARN_ON(*type == IRQ_TYPE_NONE); > + return 0; > + } > + > + return -EINVAL; > +} > + > +static int ixp4xx_irq_domain_alloc(struct irq_domain *d, > + unsigned int irq, unsigned int nr_irqs, > + void *data) > +{ > + struct ixp4xx_irq *ixi = d->host_data; > + irq_hw_number_t hwirq; > + unsigned int type = IRQ_TYPE_NONE; > + struct irq_fwspec *fwspec = data; > + int ret; > + int i; > + > + ret = ixp4xx_irq_domain_translate(d, fwspec, &hwirq, &type); > + if (ret) > + return ret; > + > + for (i = 0; i < nr_irqs; i++) { > + /* > + * TODO: after converting IXP4xx to only device tree, set > + * handle_bad_irq as default handler and assume all consumers > + * call .set_type() as this is provided in the second cell in > + * the device tree phandle. > + */ > + irq_domain_set_info(d, > + irq + i, > + hwirq + i, > + &ixi->irqchip, > + ixi, > + handle_level_irq, > + NULL, NULL); > + irq_set_probe(irq + i); > + } > + > + return 0; > +} > + > +/* > + * This needs to be a hierarchical irqdomain to work well with the > + * GPIO irqchip (which is lower in the hierarchy) > + */ > +static const struct irq_domain_ops ixp4xx_irqdomain_ops = { > + .translate = ixp4xx_irq_domain_translate, > + .alloc = ixp4xx_irq_domain_alloc, > + .free = irq_domain_free_irqs_common, > +}; > + > +/** > + * ixp4xx_get_irq_domain() - retrieve the ixp4xx irq domain > + * > + * This function will go away when we transition to DT probing. > + */ > +struct irq_domain *ixp4xx_get_irq_domain(void) > +{ > + struct ixp4xx_irq *ixi = &ixirq; > + > + return ixi->domain; > +} > +EXPORT_SYMBOL_GPL(ixp4xx_get_irq_domain); > + > +/* > + * This is the Linux IRQ to hwirq mapping table. This goes away when > + * we have DT support as all IRQ resources are defined in the device > + * tree. It will register all the IRQs that are not used by the hierarchical > + * GPIO IRQ chip. The "holes" inbetween these IRQs will be requested by > + * the GPIO driver using . This is a step-gap solution. > + */ > +struct ixp4xx_irq_chunk { > + int irq; > + int hwirq; > + int nr_irqs; > +}; > + > +static const struct ixp4xx_irq_chunk ixp4xx_irq_chunks[] = { > + { > + .irq = 16, > + .hwirq = 0, > + .nr_irqs = 6, > + }, > + { > + .irq = 24, > + .hwirq = 8, > + .nr_irqs = 11, > + }, > + { > + .irq = 46, > + .hwirq = 30, > + .nr_irqs = 2, > + }, > + /* Only on the 436 variants */ > + { > + .irq = 48, > + .hwirq = 32, > + .nr_irqs = 10, > + }, > +}; > + > +/** > + * ixp4x_irq_setup() - Common setup code for the IXP4xx interrupt controller > + * @ixi: State container > + * @irqbase: Virtual memory base for the interrupt controller > + * @fwnode: Corresponding fwnode abstraction for this controller > + * @is_356: if this is an IXP43x, IXP45x or IXP46x SoC variant > + */ > +static int ixp4xx_irq_setup(struct ixp4xx_irq *ixi, > + void __iomem *irqbase, > + struct fwnode_handle *fwnode, > + bool is_356) > +{ > + int nr_irqs; > + > + ixi->irqbase = irqbase; > + ixi->is_356 = is_356; > + > + /* Route all sources to IRQ instead of FIQ */ > + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICLR); > + > + /* Disable all interrupts */ > + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICMR); > + > + if (is_356) { > + /* Route upper 32 sources to IRQ instead of FIQ */ > + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICLR2); > + > + /* Disable upper 32 interrupts */ > + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICMR2); > + > + nr_irqs = 64; > + } else { > + nr_irqs = 32; > + } > + > + ixi->irqchip.name = "IXP4xx"; > + ixi->irqchip.irq_mask = ixp4xx_irq_mask; > + ixi->irqchip.irq_unmask = ixp4xx_irq_unmask; > + ixi->irqchip.irq_set_type = ixp4xx_set_irq_type; Aren't you guaranteed to only have one such irqchip? If so, this could become a static const object, instead of allocating it dynamically. Not a big deal though. > + > + ixi->domain = irq_domain_create_linear(fwnode, nr_irqs, > + &ixp4xx_irqdomain_ops, > + ixi); > + if (!ixi->domain) { > + pr_crit("IXP4XX: can not add primary irqdomain\n"); > + return -ENODEV; > + } > + > + set_handle_irq(ixp4xx_handle_irq); > + > + return 0; > +} > + > +/** > + * ixp4xx_irq_init() - Function to initialize the irqchip from boardfiles > + * @irqbase: physical base for the irq controller > + * @is_356: if this is an IXP43x, IXP45x or IXP46x SoC variant > + */ > +void __init ixp4xx_irq_init(resource_size_t irqbase, > + bool is_356) > +{ > + struct ixp4xx_irq *ixi = &ixirq; > + void __iomem *base; > + struct fwnode_handle *fwnode; > + struct irq_fwspec fwspec; > + int nr_chunks; > + int ret; > + int i; > + > + base = ioremap(irqbase, 0x100); > + if (!base) { > + pr_crit("IXP4XX: could not ioremap interrupt controller\n"); > + return; > + } > + fwnode = irq_domain_alloc_fwnode(base); I assume this is a temporary solution until the SoC gains a DT port (and the irqchip a DT node)? > + if (!fwnode) { > + pr_crit("IXP4XX: no domain handle\n"); > + return; > + } > + ret = ixp4xx_irq_setup(ixi, base, fwnode, is_356); > + if (ret) { > + pr_crit("IXP4XX: failed to set up irqchip\n"); > + irq_domain_free_fwnode(fwnode); > + } > + > + nr_chunks = ARRAY_SIZE(ixp4xx_irq_chunks); > + if (!is_356) > + nr_chunks--; > + > + /* > + * After adding OF support, this is no longer needed: irqs > + * will be allocated for the respective fwnodes. > + */ > + for (i = 0; i < nr_chunks; i++) { > + const struct ixp4xx_irq_chunk *chunk = &ixp4xx_irq_chunks[i]; > + > + pr_info("Allocate Linux IRQs %d..%d HW IRQs %d..%d\n", > + chunk->irq, chunk->irq + chunk->nr_irqs - 1, > + chunk->hwirq, chunk->hwirq + chunk->nr_irqs - 1); > + fwspec.fwnode = fwnode; > + fwspec.param[0] = chunk->hwirq; > + fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH; > + fwspec.param_count = 2; > + ret = __irq_domain_alloc_irqs(ixi->domain, > + chunk->irq, > + chunk->nr_irqs, > + NUMA_NO_NODE, > + &fwspec, > + false, > + NULL); > + if (ret < 0) { > + pr_crit("IXP4XX: can not allocate irqs in hierarchy %d\n", > + ret); > + return; > + } > + } > +} > +EXPORT_SYMBOL_GPL(ixp4xx_irq_init); > diff --git a/include/linux/irqchip/irq-ixp4xx.h b/include/linux/irqchip/irq-ixp4xx.h > new file mode 100644 > index 000000000000..9395917d6936 > --- /dev/null > +++ b/include/linux/irqchip/irq-ixp4xx.h > @@ -0,0 +1,12 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +#ifndef __IRQ_IXP4XX_H > +#define __IRQ_IXP4XX_H > + > +#include <linux/ioport.h> > +struct irq_domain; > + > +void ixp4xx_irq_init(resource_size_t irqbase, > + bool is_356); > +struct irq_domain *ixp4xx_get_irq_domain(void); > + > +#endif /* __IRQ_IXP4XX_H */ > Thanks, M.
On Mon, Feb 11, 2019 at 4:30 PM Marc Zyngier <marc.zyngier@arm.com> wrote: > > +static int ixp4xx_set_irq_type(struct irq_data *d, unsigned int type) > > +{ > > + /* All are level active high (asserted) here */ > > It'd be good to return an error if type isn't LEVEL_HIGH. OK > > + if (ixi->is_356 && d->hwirq >= 32) { > > + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR2); > > + val &= ~BIT(d->hwirq - 32); > > + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR2); > > + } else { > > + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR); > > + val &= ~BIT(d->hwirq); > > + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR); > > + } > > +} > > This probably comes from the original code, but I'd like to be able to > use a LE kernel on this HW (full disclosure: I have some of this crap > stashed somewhere in the basement... ;-). It will work. One reason I am refactoring and preserving IXP4xx is this stuff from the datasheet: Ambient Air Temperature (Extended) -40° C to 85° C Ambient Air Temperature (Commercial) 0° C to 70° C MTBF: 60 Years at 55°C And I don't think there is any big difference between the "extended" and "commercial" version of this SoC, they all come in the same sturdy packaging too. Diamonds and IXP4xx are forever. > How about using something that enforces the endianness of the accesses, > as I suspect the bus is hardcoded to BE? So, IIUC (Krzysztof can confirm) IXP4xx works like this that when the CPU is brought up in LE mode, all bytes on the address bus are swizzled so that the access can always use the CPU native endianness. Isn't it crazy :D This means __raw_writel() will always do the right thing, because that uses native endianness, and that is why it is used quite a lot in IXP4xx drivers. If you want full control you need to define local helpers for everything that change behaviour at compile-time such as: static void foo_writel(u32 val, __iomem void *addr) { #if defined(CONFIG_CPU_BIG_ENDIAN) iowrite32be(val, addr); #else writel(val, addr); #endif } Which will have the same effect as __raw_writel(). So that is why we use the (ugly) macro __raw_writel() a lot in IXP4xx drivers. > ioread32be/iowrite32be springs > to mind, and I can see the current IXP4xx code provides such an > implementation in its own io.h (which you may have to make private). The stuf in <mach/io.h> is there for indirect PCI, which is another unrelated oddity. PCI devices are always LE (as is proper). It is there because the PCI host on the IXP4xx can only access > 64MB PCI memory space with certain quirks that no other PCI host controller needs. I will get to this when I try to move that driver to drivers/pci ... It's unrelated to this business however. > > + ixi->irqchip.name = "IXP4xx"; > > + ixi->irqchip.irq_mask = ixp4xx_irq_mask; > > + ixi->irqchip.irq_unmask = ixp4xx_irq_unmask; > > + ixi->irqchip.irq_set_type = ixp4xx_set_irq_type; > > Aren't you guaranteed to only have one such irqchip? If so, this could > become a static const object, instead of allocating it dynamically. Not > a big deal though. Force of habit. Comes from the GPIO side of things where we never know how many instances there are of stuff, if it's OK I'd like to keep it. > > + fwnode = irq_domain_alloc_fwnode(base); > > I assume this is a temporary solution until the SoC gains a DT port (and > the irqchip a DT node)? Yes, see the later DT patch that passes the parent as fwnode, once all boards are converted we can drop this. BTW here is how I then use the hierarchical parent in the GPIO chip: https://marc.info/?l=linux-arm-kernel&m=154923023907623&w=2 https://marc.info/?l=linux-arm-kernel&m=154923038707686&w=2 Yours, Linus Walleij
On Mon, 11 Feb 2019 21:58:41 +0100 Linus Walleij <linus.walleij@linaro.org> wrote: > On Mon, Feb 11, 2019 at 4:30 PM Marc Zyngier <marc.zyngier@arm.com> wrote: [...] > > How about using something that enforces the endianness of the accesses, > > as I suspect the bus is hardcoded to BE? > > So, IIUC (Krzysztof can confirm) IXP4xx works like this that when the > CPU is brought up in LE mode, all bytes on the address bus are > swizzled so that the access can always use the CPU native > endianness. > > Isn't it crazy :D It's spelled BE32! ;-) I vaguely remember doing some of that on vulcan, but that's a decade ago, and I've paged it out... ;-) > This means __raw_writel() will always do the right thing, because > that uses native endianness, and that is why it is used quite a lot > in IXP4xx drivers. OK, it now makes some sense. Maybe a comment somewhere will help if someone tries to reverse engineer it again in the next decade! > > Aren't you guaranteed to only have one such irqchip? If so, this could > > become a static const object, instead of allocating it dynamically. Not > > a big deal though. > > Force of habit. Comes from the GPIO side of things where we > never know how many instances there are of stuff, if it's OK > I'd like to keep it. No problem. > > > > + fwnode = irq_domain_alloc_fwnode(base); > > > > I assume this is a temporary solution until the SoC gains a DT port (and > > the irqchip a DT node)? > > Yes, see the later DT patch that passes the parent as > fwnode, once all boards are converted we can drop this. > > BTW here is how I then use the hierarchical parent in the > GPIO chip: > https://marc.info/?l=linux-arm-kernel&m=154923023907623&w=2 > https://marc.info/?l=linux-arm-kernel&m=154923038707686&w=2 Looks pretty cool, and surely even better once entirely converted to DT (I'm sure I can revive this FSG-3 that has been collecting dust on the shelf for a number of years). Please CC me on the whole series next time you respin it! Thanks, M.
Marc Zyngier <marc.zyngier@arm.com> writes: > It's spelled BE32! ;-) I vaguely remember doing some of that on vulcan, > but that's a decade ago, and I've paged it out... ;-) Right. It's also called value-preserving (where the "value" must 32-bit and aligned on 32-bit boundary) or (double) word invariant. Since most of these accesses are natural 32-bit registers and (almost) none are ASCII/byte-style, it makes sense. IIRC the PCI has its own rules, though.
On Mon, Feb 18, 2019 at 8:06 AM Krzysztof Hałasa <khalasa@piap.pl> wrote: > Marc Zyngier <marc.zyngier@arm.com> writes: > > > It's spelled BE32! ;-) I vaguely remember doing some of that on vulcan, > > but that's a decade ago, and I've paged it out... ;-) > > Right. It's also called value-preserving (where the "value" must 32-bit > and aligned on 32-bit boundary) or (double) word invariant. > > Since most of these accesses are natural 32-bit registers and (almost) > none are ASCII/byte-style, it makes sense. IIRC the PCI has its own > rules, though. IIUC PCI should always be LE and readl() writel() always read/write in LE. But I see PCI on the IXP4xx is a special history so I am going to experience it in all its glory now that I start looking at it. Thanks, Linus Walleij
Linus Walleij <linus.walleij@linaro.org> writes: > IIUC PCI should always be LE and readl() writel() always read/write > in LE. But I see PCI on the IXP4xx is a special history so I am going to > experience it in all its glory now that I start looking at it. Oh yeah. BTW obviously readl() and writel() do the right thing on IXP4xx (otherwise the drivers wouldn't work) and the byte operations work as well. That's why the internal stuff uses __raw_{readl,writel}() and the PCI uses readl/writel() and so on (which corrects for endianness on BE).
On Mon, Feb 11, 2019 at 11:12 PM Marc Zyngier <marc.zyngier@arm.com> wrote: > > On Mon, 11 Feb 2019 21:58:41 +0100 > Linus Walleij <linus.walleij@linaro.org> wrote: > > > On Mon, Feb 11, 2019 at 4:30 PM Marc Zyngier <marc.zyngier@arm.com> wrote: > > [...] > > > > How about using something that enforces the endianness of the accesses, > > > as I suspect the bus is hardcoded to BE? > > > > So, IIUC (Krzysztof can confirm) IXP4xx works like this that when the > > CPU is brought up in LE mode, all bytes on the address bus are > > swizzled so that the access can always use the CPU native > > endianness. > > > > Isn't it crazy :D > > It's spelled BE32! ;-) I vaguely remember doing some of that on vulcan, > but that's a decade ago, and I've paged it out... ;-) > > > This means __raw_writel() will always do the right thing, because > > that uses native endianness, and that is why it is used quite a lot > > in IXP4xx drivers. > > OK, it now makes some sense. Maybe a comment somewhere will help if > someone tries to reverse engineer it again in the next decade! I think the best way forward would be to change all the __raw_readl()/__raw_writel() in ixp4xx specific drivers to a new accessor that is defined as a trivial wrapper around __raw_readl()/ __raw_writel() but that is explicitly defined as - always accessing a 32-bit word in native endianess - only valid on ixp4xx on-chip registers (not PCI) - containing the required barriers (if any) In my mental model of the I/O accessors, readl/writel are used for (little-endian) MMIO register accesses, while __raw_readl()/__raw_writel() can only be used in portable code on byte streams in memory behind a bus, not on registers. The way ixp4xx works fits into neither of those two, so we should not use them here. I also think it's a bug to use 8-bit or 16-bit accessors for internal registers on ixp4xx, so having a pair of special helpers makes it impossible to do so. Arnd
On Mon, Feb 18, 2019 at 8:17 AM Linus Walleij <linus.walleij@linaro.org> wrote: > > On Mon, Feb 18, 2019 at 8:06 AM Krzysztof Hałasa <khalasa@piap.pl> wrote: > > Marc Zyngier <marc.zyngier@arm.com> writes: > > > > > It's spelled BE32! ;-) I vaguely remember doing some of that on vulcan, > > > but that's a decade ago, and I've paged it out... ;-) > > > > Right. It's also called value-preserving (where the "value" must 32-bit > > and aligned on 32-bit boundary) or (double) word invariant. > > > > Since most of these accesses are natural 32-bit registers and (almost) > > none are ASCII/byte-style, it makes sense. IIRC the PCI has its own > > rules, though. > > IIUC PCI should always be LE and readl() writel() always read/write > in LE. But I see PCI on the IXP4xx is a special history so I am going to > experience it in all its glory now that I start looking at it. I found a document that explains some of the more obscure details of this chip (among a lot of the general concepts), in particular how you can configure memory regions as either BE, LE-data-coherent or LE-address-coherent, and the byte swap on PCI depending on that: https://www.intel.com/content/www/us/en/intelligent-systems/previous-generation/ixp4xx-ixc1100-big-endian-little-endian-modes-app-note.html Arnd
Arnd Bergmann <arnd@arndb.de> writes: > I found a document that explains some of the more obscure details > of this chip (among a lot of the general concepts), in particular how > you can configure memory regions as either BE, LE-data-coherent or > LE-address-coherent, and the byte swap on PCI depending on that: There is also a problem with certain devices (certain old US Robotics access servers?) using IXP425 rev. A0. Those first chips don't have the LE data coherent mode. We (= the official Linux kernel) don't use LE data coherent mode at all, though I have had a patch (not very clean) which could do that.
On Mon, Feb 18, 2019 at 1:03 PM Krzysztof Hałasa <khalasa@piap.pl> wrote: > > Arnd Bergmann <arnd@arndb.de> writes: > > > I found a document that explains some of the more obscure details > > of this chip (among a lot of the general concepts), in particular how > > you can configure memory regions as either BE, LE-data-coherent or > > LE-address-coherent, and the byte swap on PCI depending on that: > > There is also a problem with certain devices (certain old US Robotics > access servers?) using IXP425 rev. A0. Those first chips don't have the > LE data coherent mode. > We (= the official Linux kernel) don't use LE data coherent mode at all, > though I have had a patch (not very clean) which could do that. It does sound appealing to move to data coherent mode, since that is closer to what we have elsewhere. Maybe we can finally work that out and then allow a little-endian multiplatform configuration to include ixp4xx. I have two questions about this: - I have a vague memory that only specific revisions actually require the PCI indirect mode. Is it the same chip revision A0 of IXP425 that requires both pci-indirect and address-coherent mode, or are there other combinations? - How much would it hurt actual users to drop support for both features? I.e. do we know of anyone using the affected chips (and still doing updates), or are the remaining ixp4xx systems generally using the later chip revision? Arnd
Arnd Bergmann <arnd@arndb.de> writes: > - I have a vague memory that only specific revisions actually require > the PCI indirect mode. Is it the same chip revision A0 of IXP425 > that requires both pci-indirect and address-coherent mode, or are > there other combinations? IIRC, all chips support indirect PCI. Perhaps there are some changes about LE mode on the PCI, but that's it. Most boards don't need indirect PCI. It's only needed when there is more than 64 MB of PCI address space to be used - IIRC IXP4xx have 4 direct PCI memory access windows, and each is 16 MB in size. I remember I needed indirect PCI for use with a (SiS?) VGA card, it had (I guess) 128 MB of RAM. I think indirect PCI means the memory can't be really mapped, it can only be accessed using readl() and friends (which are then converted into register accesses resulting in the actual MMIO). Even with indirect PCI, 64 MB can be accessed directly (though I think we don't support it). This is different from the bus mastering access to the main RAM - I think only the first 64 MB can be used for PCI BM DMA. This limitation, IIRC, doesn't apply to internal devices (Ethernet etc). > - How much would it hurt actual users to drop support for both > features? I.e. do we know of anyone using the affected chips > (and still doing updates), or are the remaining ixp4xx systems > generally using the later chip revision? Most chips are B0+, though there are (were) certain A0 platforms. I don't have any idea about their current use.
On Tue, Feb 19, 2019 at 7:51 AM Krzysztof Hałasa <khalasa@piap.pl> wrote: > Arnd Bergmann <arnd@arndb.de> writes: > > - I have a vague memory that only specific revisions actually require > > the PCI indirect mode. Is it the same chip revision A0 of IXP425 > > that requires both pci-indirect and address-coherent mode, or are > > there other combinations? > > IIRC, all chips support indirect PCI. Perhaps there are some changes > about LE mode on the PCI, but that's it. > > Most boards don't need indirect PCI. It's only needed when there is more > than 64 MB of PCI address space to be used - IIRC IXP4xx have 4 direct > PCI memory access windows, and each is 16 MB in size. > I remember I needed indirect PCI for use with a (SiS?) VGA card, it had > (I guess) 128 MB of RAM. I think indirect PCI means the memory can't be > really mapped, it can only be accessed using readl() and friends (which > are then converted into register accesses resulting in the actual MMIO). > Even with indirect PCI, 64 MB can be accessed directly (though I think > we don't support it). > > This is different from the bus mastering access to the main RAM - I > think only the first 64 MB can be used for PCI BM DMA. This limitation, > IIRC, doesn't apply to internal devices (Ethernet etc). Thanks a lot for this background, that helps a lot. We actually have a more general implementation of this (indirect mmio and pio) in arch/powerpc/kernel/io-workarounds.c, and I was considering whether we can make that architecture independent code and reuse it here as well. Looking at the powerpc implementation however, I see two other problems: - the way it is implemented requires using some spare address bits on a 64-bit pointer, so that would have to be change a lot to work on a 32-bit architecture - the MMIO workarounds don't really work for a VGA card in general since you cannot map the frame buffer into user space, so the application of that is still a bit limited. I suspect you would have had the same problem with the existing ixp4xx code as well, depending on how the driver implements user space graphics. We could still consider using it on Allwinner H6, which is a 64-bit chip that is even more limited (only 64 /KB/ can be mapped at a time). > > - How much would it hurt actual users to drop support for both > > features? I.e. do we know of anyone using the affected chips > > (and still doing updates), or are the remaining ixp4xx systems > > generally using the later chip revision? > > Most chips are B0+, though there are (were) certain A0 platforms. > I don't have any idea about their current use. Ok, fair enough. Arnd
Arnd Bergmann <arnd@arndb.de> writes: > - the MMIO workarounds don't really work for a VGA card > in general since you cannot map the frame buffer into user > space, so the application of that is still a bit limited. I suspect > you would have had the same problem with the existing > ixp4xx code as well, depending on how the driver implements > user space graphics. Right. Now I remember I put the VGA FB in the first 64 MB of PCI address space (this means it was a 64 MB VGA, and that I could actually do direct and indirect accesses at the same time). The rest of PCI address space was used indirectly for (I guess) VGA registers and other hardware (SATA controller and similar stuff - nothing which would require mapped PCI memory). The PCI bus mastering limitation was far worse - there are boards with 128 and 256 MB (maximum possible on IXP42x) of RAM and double SKB buffering with e.g. dual gigabit Ethernet wasn't nice. I ended up with a hack which allocated network buffers from the first 64 MB of RAM only.
diff --git a/MAINTAINERS b/MAINTAINERS index 32d444476a90..0d48faa3e635 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1651,6 +1651,8 @@ M: Krzysztof Halasa <khalasa@piap.pl> L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: arch/arm/mach-ixp4xx/ +F: drivers/irqchip/irq-ixp4xx.c +F: include/linux/irqchip/irq-ixp4xx.h ARM/INTEL RESEARCH IMOTE/STARGATE 2 MACHINE SUPPORT M: Jonathan Cameron <jic23@cam.ac.uk> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 3d1e60779078..c7e09913826b 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -150,6 +150,12 @@ config IMGPDC_IRQ select GENERIC_IRQ_CHIP select IRQ_DOMAIN +config IXP4XX_IRQ + bool + select IRQ_DOMAIN + select GENERIC_IRQ_MULTI_HANDLER + select SPARSE_IRQ + config MADERA_IRQ tristate diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index c93713d24b86..06139d612108 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_ATMEL_AIC5_IRQ) += irq-atmel-aic-common.o irq-atmel-aic5.o obj-$(CONFIG_I8259) += irq-i8259.o obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o +obj-$(CONFIG_IXP4XX_IRQ) += irq-ixp4xx.o obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o obj-$(CONFIG_JCORE_AIC) += irq-jcore-aic.o obj-$(CONFIG_RDA_INTC) += irq-rda-intc.o diff --git a/drivers/irqchip/irq-ixp4xx.c b/drivers/irqchip/irq-ixp4xx.c new file mode 100644 index 000000000000..7deaf0f82a53 --- /dev/null +++ b/drivers/irqchip/irq-ixp4xx.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * irqchip for the IXP4xx interrupt controller + * Copyright (C) 2019 Linus Walleij <linus.walleij@linaro.org> + * + * Based on arch/arm/mach-ixp4xx/common.c + * Copyright 2002 (C) Intel Corporation + * Copyright 2003-2004 (C) MontaVista, Software, Inc. + * Copyright (C) Deepak Saxena <dsaxena@plexity.net> + */ +#include <linux/bitops.h> +#include <linux/gpio/driver.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/irqchip.h> +#include <linux/irqchip/irq-ixp4xx.h> +#include <linux/irqdomain.h> +#include <linux/platform_device.h> +#include <linux/cpu.h> + +#include <asm/exception.h> +#include <asm/mach/irq.h> + +#define IXP4XX_ICPR 0x00 /* Interrupt Status */ +#define IXP4XX_ICMR 0x04 /* Interrupt Enable */ +#define IXP4XX_ICLR 0x08 /* Interrupt IRQ/FIQ Select */ +#define IXP4XX_ICIP 0x0C /* IRQ Status */ +#define IXP4XX_ICFP 0x10 /* FIQ Status */ +#define IXP4XX_ICHR 0x14 /* Interrupt Priority */ +#define IXP4XX_ICIH 0x18 /* IRQ Highest Pri Int */ +#define IXP4XX_ICFH 0x1C /* FIQ Highest Pri Int */ + +/* IXP43x and IXP46x-only */ +#define IXP4XX_ICPR2 0x20 /* Interrupt Status 2 */ +#define IXP4XX_ICMR2 0x24 /* Interrupt Enable 2 */ +#define IXP4XX_ICLR2 0x28 /* Interrupt IRQ/FIQ Select 2 */ +#define IXP4XX_ICIP2 0x2C /* IRQ Status */ +#define IXP4XX_ICFP2 0x30 /* FIQ Status */ +#define IXP4XX_ICEEN 0x34 /* Error High Pri Enable */ + +/** + * struct ixp4xx_irq - state container for the Faraday IRQ controller + * @irqbase: IRQ controller memory base in virtual memory + * @is_356: if this is an IXP43x, IXP45x or IX46x SoC (with 64 IRQs) + * @irqchip: irqchip for this instance + * @domain: IRQ domain for this instance + */ +struct ixp4xx_irq { + void __iomem *irqbase; + bool is_356; + struct irq_chip irqchip; + struct irq_domain *domain; +}; + +/* Local static state container */ +static struct ixp4xx_irq ixirq; + +/* GPIO Clocks */ +#define IXP4XX_GPIO_CLK_0 14 +#define IXP4XX_GPIO_CLK_1 15 + +static int ixp4xx_set_irq_type(struct irq_data *d, unsigned int type) +{ + /* All are level active high (asserted) here */ + return 0; +} + +static void ixp4xx_irq_mask(struct irq_data *d) +{ + struct ixp4xx_irq *ixi = irq_data_get_irq_chip_data(d); + u32 val; + + if (ixi->is_356 && d->hwirq >= 32) { + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR2); + val &= ~BIT(d->hwirq - 32); + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR2); + } else { + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR); + val &= ~BIT(d->hwirq); + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR); + } +} + +/* + * Level triggered interrupts on GPIO lines can only be cleared when the + * interrupt condition disappears. + */ +static void ixp4xx_irq_unmask(struct irq_data *d) +{ + struct ixp4xx_irq *ixi = irq_data_get_irq_chip_data(d); + u32 val; + + if (ixi->is_356 && d->hwirq >= 32) { + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR2); + val |= BIT(d->hwirq - 32); + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR2); + } else { + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR); + val |= BIT(d->hwirq); + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR); + } +} + +asmlinkage void __exception_irq_entry ixp4xx_handle_irq(struct pt_regs *regs) +{ + struct ixp4xx_irq *ixi = &ixirq; + unsigned long status; + int i; + + status = __raw_readl(ixi->irqbase + IXP4XX_ICIP); + for_each_set_bit(i, &status, 32) + handle_domain_irq(ixi->domain, i, regs); + + /* + * IXP465/IXP435 has an upper IRQ status register + */ + if (ixi->is_356) { + status = __raw_readl(ixi->irqbase + IXP4XX_ICIP2); + for_each_set_bit(i, &status, 32) + handle_domain_irq(ixi->domain, i + 32, regs); + } +} + +static int ixp4xx_irq_domain_translate(struct irq_domain *domain, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + /* We support standard DT translation */ + if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) { + *hwirq = fwspec->param[0]; + *type = fwspec->param[1]; + return 0; + } + + if (is_fwnode_irqchip(fwspec->fwnode)) { + if (fwspec->param_count != 2) + return -EINVAL; + *hwirq = fwspec->param[0]; + *type = fwspec->param[1]; + WARN_ON(*type == IRQ_TYPE_NONE); + return 0; + } + + return -EINVAL; +} + +static int ixp4xx_irq_domain_alloc(struct irq_domain *d, + unsigned int irq, unsigned int nr_irqs, + void *data) +{ + struct ixp4xx_irq *ixi = d->host_data; + irq_hw_number_t hwirq; + unsigned int type = IRQ_TYPE_NONE; + struct irq_fwspec *fwspec = data; + int ret; + int i; + + ret = ixp4xx_irq_domain_translate(d, fwspec, &hwirq, &type); + if (ret) + return ret; + + for (i = 0; i < nr_irqs; i++) { + /* + * TODO: after converting IXP4xx to only device tree, set + * handle_bad_irq as default handler and assume all consumers + * call .set_type() as this is provided in the second cell in + * the device tree phandle. + */ + irq_domain_set_info(d, + irq + i, + hwirq + i, + &ixi->irqchip, + ixi, + handle_level_irq, + NULL, NULL); + irq_set_probe(irq + i); + } + + return 0; +} + +/* + * This needs to be a hierarchical irqdomain to work well with the + * GPIO irqchip (which is lower in the hierarchy) + */ +static const struct irq_domain_ops ixp4xx_irqdomain_ops = { + .translate = ixp4xx_irq_domain_translate, + .alloc = ixp4xx_irq_domain_alloc, + .free = irq_domain_free_irqs_common, +}; + +/** + * ixp4xx_get_irq_domain() - retrieve the ixp4xx irq domain + * + * This function will go away when we transition to DT probing. + */ +struct irq_domain *ixp4xx_get_irq_domain(void) +{ + struct ixp4xx_irq *ixi = &ixirq; + + return ixi->domain; +} +EXPORT_SYMBOL_GPL(ixp4xx_get_irq_domain); + +/* + * This is the Linux IRQ to hwirq mapping table. This goes away when + * we have DT support as all IRQ resources are defined in the device + * tree. It will register all the IRQs that are not used by the hierarchical + * GPIO IRQ chip. The "holes" inbetween these IRQs will be requested by + * the GPIO driver using . This is a step-gap solution. + */ +struct ixp4xx_irq_chunk { + int irq; + int hwirq; + int nr_irqs; +}; + +static const struct ixp4xx_irq_chunk ixp4xx_irq_chunks[] = { + { + .irq = 16, + .hwirq = 0, + .nr_irqs = 6, + }, + { + .irq = 24, + .hwirq = 8, + .nr_irqs = 11, + }, + { + .irq = 46, + .hwirq = 30, + .nr_irqs = 2, + }, + /* Only on the 436 variants */ + { + .irq = 48, + .hwirq = 32, + .nr_irqs = 10, + }, +}; + +/** + * ixp4x_irq_setup() - Common setup code for the IXP4xx interrupt controller + * @ixi: State container + * @irqbase: Virtual memory base for the interrupt controller + * @fwnode: Corresponding fwnode abstraction for this controller + * @is_356: if this is an IXP43x, IXP45x or IXP46x SoC variant + */ +static int ixp4xx_irq_setup(struct ixp4xx_irq *ixi, + void __iomem *irqbase, + struct fwnode_handle *fwnode, + bool is_356) +{ + int nr_irqs; + + ixi->irqbase = irqbase; + ixi->is_356 = is_356; + + /* Route all sources to IRQ instead of FIQ */ + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICLR); + + /* Disable all interrupts */ + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICMR); + + if (is_356) { + /* Route upper 32 sources to IRQ instead of FIQ */ + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICLR2); + + /* Disable upper 32 interrupts */ + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICMR2); + + nr_irqs = 64; + } else { + nr_irqs = 32; + } + + ixi->irqchip.name = "IXP4xx"; + ixi->irqchip.irq_mask = ixp4xx_irq_mask; + ixi->irqchip.irq_unmask = ixp4xx_irq_unmask; + ixi->irqchip.irq_set_type = ixp4xx_set_irq_type; + + ixi->domain = irq_domain_create_linear(fwnode, nr_irqs, + &ixp4xx_irqdomain_ops, + ixi); + if (!ixi->domain) { + pr_crit("IXP4XX: can not add primary irqdomain\n"); + return -ENODEV; + } + + set_handle_irq(ixp4xx_handle_irq); + + return 0; +} + +/** + * ixp4xx_irq_init() - Function to initialize the irqchip from boardfiles + * @irqbase: physical base for the irq controller + * @is_356: if this is an IXP43x, IXP45x or IXP46x SoC variant + */ +void __init ixp4xx_irq_init(resource_size_t irqbase, + bool is_356) +{ + struct ixp4xx_irq *ixi = &ixirq; + void __iomem *base; + struct fwnode_handle *fwnode; + struct irq_fwspec fwspec; + int nr_chunks; + int ret; + int i; + + base = ioremap(irqbase, 0x100); + if (!base) { + pr_crit("IXP4XX: could not ioremap interrupt controller\n"); + return; + } + fwnode = irq_domain_alloc_fwnode(base); + if (!fwnode) { + pr_crit("IXP4XX: no domain handle\n"); + return; + } + ret = ixp4xx_irq_setup(ixi, base, fwnode, is_356); + if (ret) { + pr_crit("IXP4XX: failed to set up irqchip\n"); + irq_domain_free_fwnode(fwnode); + } + + nr_chunks = ARRAY_SIZE(ixp4xx_irq_chunks); + if (!is_356) + nr_chunks--; + + /* + * After adding OF support, this is no longer needed: irqs + * will be allocated for the respective fwnodes. + */ + for (i = 0; i < nr_chunks; i++) { + const struct ixp4xx_irq_chunk *chunk = &ixp4xx_irq_chunks[i]; + + pr_info("Allocate Linux IRQs %d..%d HW IRQs %d..%d\n", + chunk->irq, chunk->irq + chunk->nr_irqs - 1, + chunk->hwirq, chunk->hwirq + chunk->nr_irqs - 1); + fwspec.fwnode = fwnode; + fwspec.param[0] = chunk->hwirq; + fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH; + fwspec.param_count = 2; + ret = __irq_domain_alloc_irqs(ixi->domain, + chunk->irq, + chunk->nr_irqs, + NUMA_NO_NODE, + &fwspec, + false, + NULL); + if (ret < 0) { + pr_crit("IXP4XX: can not allocate irqs in hierarchy %d\n", + ret); + return; + } + } +} +EXPORT_SYMBOL_GPL(ixp4xx_irq_init); diff --git a/include/linux/irqchip/irq-ixp4xx.h b/include/linux/irqchip/irq-ixp4xx.h new file mode 100644 index 000000000000..9395917d6936 --- /dev/null +++ b/include/linux/irqchip/irq-ixp4xx.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __IRQ_IXP4XX_H +#define __IRQ_IXP4XX_H + +#include <linux/ioport.h> +struct irq_domain; + +void ixp4xx_irq_init(resource_size_t irqbase, + bool is_356); +struct irq_domain *ixp4xx_get_irq_domain(void); + +#endif /* __IRQ_IXP4XX_H */
The IXP4xx (arch/arm/mach-ixp4xx) is an old Intel XScale platform that has very wide deployment and use. As part of modernizing the platform, we need to implement a proper irqchip in the irqchip subsystem. The IXP4xx irqchip is tightly jotted together with the GPIO controller, and wheras in the past we would deal with this complex logic by adding necessarily different code, we can nowadays modernize it using a hierarchical irqchip. The actual IXP4 irqchip is a simple active low level IRQ controller, whereas the GPIO functionality resides in a different memory area and adds edge trigger support for the interrupts. The interrupts from GPIO lines 0..12 are 1:1 mapped to a fixed set of hardware IRQs on this IRQchip, so we expect the child GPIO interrupt controller to go in and allocate descriptors for these interrupts. For the other interrupts, as we do not yet have DT support for this platform, we create a linear irqdomain and then go in and allocate the IRQs that the legacy boards use. This code will be removed on the DT probe path when we add DT support to the platform. We add some translation code for supporting DT translations for the fwnodes, but we leave most of that for later. Cc: Marc Zyngier <marc.zyngier@arm.com> Cc: Jason Cooper <jason@lakedaemon.net> Cc: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Linus Walleij <linus.walleij@linaro.org> --- irqchip maintainers: I am requesting an ACK for this once you're happy with the driver, as I intend to merge all of this IXP4xx rework through ARM SoC. --- MAINTAINERS | 2 + drivers/irqchip/Kconfig | 6 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-ixp4xx.c | 360 +++++++++++++++++++++++++++++ include/linux/irqchip/irq-ixp4xx.h | 12 + 5 files changed, 381 insertions(+) create mode 100644 drivers/irqchip/irq-ixp4xx.c create mode 100644 include/linux/irqchip/irq-ixp4xx.h