diff mbox series

[04/17,v1] irqchip: Add driver for IXP4xx

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

Commit Message

Linus Walleij Feb. 3, 2019, 9:41 p.m. UTC
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

Comments

Marc Zyngier Feb. 11, 2019, 3:30 p.m. UTC | #1
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.
Linus Walleij Feb. 11, 2019, 8:58 p.m. UTC | #2
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
Marc Zyngier Feb. 11, 2019, 10:11 p.m. UTC | #3
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.
Krzysztof Hałasa Feb. 18, 2019, 7:06 a.m. UTC | #4
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.
Linus Walleij Feb. 18, 2019, 7:16 a.m. UTC | #5
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
Krzysztof Hałasa Feb. 18, 2019, 7:35 a.m. UTC | #6
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).
Arnd Bergmann Feb. 18, 2019, 9:18 a.m. UTC | #7
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
Arnd Bergmann Feb. 18, 2019, 9:40 a.m. UTC | #8
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
Krzysztof Hałasa Feb. 18, 2019, 12:03 p.m. UTC | #9
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.
Arnd Bergmann Feb. 18, 2019, 12:44 p.m. UTC | #10
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
Krzysztof Hałasa Feb. 19, 2019, 6:51 a.m. UTC | #11
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.
Arnd Bergmann Feb. 19, 2019, 9:46 a.m. UTC | #12
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
Krzysztof Hałasa Feb. 20, 2019, 7:35 a.m. UTC | #13
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 mbox series

Patch

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 */