irqchip: add TS7800v1 fpga based controller driver

Message ID 20221018133034.10442-1-firas.ashkar@savoirfairelinux.com
State New
Headers
Series irqchip: add TS7800v1 fpga based controller driver |

Commit Message

firas ashkar Oct. 18, 2022, 1:30 p.m. UTC
  1. add TS-7800v1 fpga based irq controller driver, and
2. add related memory and irq resources

By default only mapped FPGA interrupts will be chained/multiplexed, these
chained interrupts will then be available to other device drivers to
request via the corresponding Linux IRQs.

$ cat /proc/cpuinfo
processor	: 0
model name	: Feroceon rev 0 (v5l)
BogoMIPS	: 333.33
Features	: swp half thumb fastmult edsp
CPU implementer	: 0x41
CPU architecture: 5TEJ
CPU variant	: 0x0
CPU part	: 0x926
CPU revision	: 0

Hardware	: Technologic Systems TS-78xx SBC
Revision	: 0000
Serial		: 0000000000000000
$

$ cat /proc/interrupts
           CPU0
  1:        902  orion_irq     Level     orion_tick
  4:        795  orion_irq     Level     ttyS0
 13:          0  orion_irq     Level     ehci_hcd:usb2
 18:          0  orion_irq     Level     ehci_hcd:usb1
 22:         69  orion_irq     Level     eth0
 23:        171  orion_irq     Level     orion-mdio
 29:          0  orion_irq     Level     mv_crypto
 31:          2  orion_irq     Level     mv_xor.0
 65:          1  ts7800-irqc   0 Edge      ts-dmac-cpupci
Err:          0
$

$ cat /proc/iomem
00000000-07ffffff : System RAM
  00008000-00978fff : Kernel code
  00d20000-00e54f97 : Kernel data
e8000044-e8000047 : timeriomem_rng
  e8000044-e8000047 : timeriomem_rng timeriomem_rng
e8000200-e80002ff : ts_irqc
  e8000200-e80002ff : ts7800-irqc ts_irqc
e8000400-e80004ff : ts_dmac
  e8000400-e80004ff : ts7800-dmac ts_dmac
e8000804-e8000807 : gen_nand
  e8000804-e8000807 : gen_nand gen_nand
e8000808-e8000808 : rtc-m48t86
  e8000808-e8000808 : rtc-m48t86 rtc-m48t86
e800080c-e800080c : rtc-m48t86
  e800080c-e800080c : rtc-m48t86 rtc-m48t86
f1012000-f10120ff : serial8250.0
  f1012000-f101201f : serial
f1012100-f10121ff : serial8250.1
  f1012100-f101211f : serial
f1020108-f102010b : orion_wdt
f1020300-f1020303 : orion_wdt
f1020400-f102040f : ts_bridge
  f1020400-f102040f : ts7800-irqc ts_bridge
f1050000-f1050fff : orion-ehci.0
  f1050000-f1050fff : orion-ehci.0 orion-ehci.0
f1060900-f10609ff : xor 0 low
f1060b00-f1060bff : xor 0 high
f1072000-f1075fff : ge00 base
  f1072004-f1072087 : ge00 mvmdio base
f1080000-f1084fff : sata base
f1090000-f109ffff : regs
  f1090000-f109ffff : mv_crypto regs
f10a0000-f10a0fff : orion-ehci.1
  f10a0000-f10a0fff : orion-ehci.1 orion-ehci.1
f2200000-f2201fff : sram
  f2200000-f2201fff : mv_crypto sram
$

$ uname -a
Linux ts-7800 6.1.0-rc1 #2 PREEMPT Mon Oct 17 11:19:12 EDT 2022 armv5tel
 GNU/Linux
$

Signed-off-by: Firas Ashkar <firas.ashkar@savoirfairelinux.com>
---
:100644 100644 2f4fe3ca5c1a ed8378893208 M	arch/arm/mach-orion5x/ts78xx-fpga.h
:100644 100644 af810e7ccd79 d319a68b7160 M	arch/arm/mach-orion5x/ts78xx-setup.c
:100644 100644 7ef9f5e696d3 d184fb435c5d M	drivers/irqchip/Kconfig
:100644 100644 87b49a10962c b022eece2042 M	drivers/irqchip/Makefile
:000000 100644 000000000000 178f43548b2a A	drivers/irqchip/irq-ts7800.c
 arch/arm/mach-orion5x/ts78xx-fpga.h  |   1 +
 arch/arm/mach-orion5x/ts78xx-setup.c |  55 +++++
 drivers/irqchip/Kconfig              |  12 +
 drivers/irqchip/Makefile             |   1 +
 drivers/irqchip/irq-ts7800.c         | 319 +++++++++++++++++++++++++++
 5 files changed, 388 insertions(+)
  

Comments

Marc Zyngier Oct. 18, 2022, 4:36 p.m. UTC | #1
+Arnd

On Tue, 18 Oct 2022 14:30:34 +0100,
Firas Ashkar <firas.ashkar@savoirfairelinux.com> wrote:
> 
> 1. add TS-7800v1 fpga based irq controller driver, and
> 2. add related memory and irq resources
> 
> By default only mapped FPGA interrupts will be chained/multiplexed, these
> chained interrupts will then be available to other device drivers to
> request via the corresponding Linux IRQs.
> 
> $ cat /proc/cpuinfo
> processor	: 0
> model name	: Feroceon rev 0 (v5l)
> BogoMIPS	: 333.33
> Features	: swp half thumb fastmult edsp
> CPU implementer	: 0x41
> CPU architecture: 5TEJ
> CPU variant	: 0x0
> CPU part	: 0x926
> CPU revision	: 0
> 
> Hardware	: Technologic Systems TS-78xx SBC
> Revision	: 0000
> Serial		: 0000000000000000
> $
> 
> $ cat /proc/interrupts
>            CPU0
>   1:        902  orion_irq     Level     orion_tick
>   4:        795  orion_irq     Level     ttyS0
>  13:          0  orion_irq     Level     ehci_hcd:usb2
>  18:          0  orion_irq     Level     ehci_hcd:usb1
>  22:         69  orion_irq     Level     eth0
>  23:        171  orion_irq     Level     orion-mdio
>  29:          0  orion_irq     Level     mv_crypto
>  31:          2  orion_irq     Level     mv_xor.0
>  65:          1  ts7800-irqc   0 Edge      ts-dmac-cpupci
> Err:          0
> $
> 
> $ cat /proc/iomem
> 00000000-07ffffff : System RAM
>   00008000-00978fff : Kernel code
>   00d20000-00e54f97 : Kernel data
> e8000044-e8000047 : timeriomem_rng
>   e8000044-e8000047 : timeriomem_rng timeriomem_rng
> e8000200-e80002ff : ts_irqc
>   e8000200-e80002ff : ts7800-irqc ts_irqc
> e8000400-e80004ff : ts_dmac
>   e8000400-e80004ff : ts7800-dmac ts_dmac
> e8000804-e8000807 : gen_nand
>   e8000804-e8000807 : gen_nand gen_nand
> e8000808-e8000808 : rtc-m48t86
>   e8000808-e8000808 : rtc-m48t86 rtc-m48t86
> e800080c-e800080c : rtc-m48t86
>   e800080c-e800080c : rtc-m48t86 rtc-m48t86
> f1012000-f10120ff : serial8250.0
>   f1012000-f101201f : serial
> f1012100-f10121ff : serial8250.1
>   f1012100-f101211f : serial
> f1020108-f102010b : orion_wdt
> f1020300-f1020303 : orion_wdt
> f1020400-f102040f : ts_bridge
>   f1020400-f102040f : ts7800-irqc ts_bridge
> f1050000-f1050fff : orion-ehci.0
>   f1050000-f1050fff : orion-ehci.0 orion-ehci.0
> f1060900-f10609ff : xor 0 low
> f1060b00-f1060bff : xor 0 high
> f1072000-f1075fff : ge00 base
>   f1072004-f1072087 : ge00 mvmdio base
> f1080000-f1084fff : sata base
> f1090000-f109ffff : regs
>   f1090000-f109ffff : mv_crypto regs
> f10a0000-f10a0fff : orion-ehci.1
>   f10a0000-f10a0fff : orion-ehci.1 orion-ehci.1
> f2200000-f2201fff : sram
>   f2200000-f2201fff : mv_crypto sram
> $
> 
> $ uname -a
> Linux ts-7800 6.1.0-rc1 #2 PREEMPT Mon Oct 17 11:19:12 EDT 2022 armv5tel
>  GNU/Linux
> $

I don't see how useful this information is. Please limit the commit
message to the provided functionality.

> 
> Signed-off-by: Firas Ashkar <firas.ashkar@savoirfairelinux.com>
> ---
> :100644 100644 2f4fe3ca5c1a ed8378893208 M	arch/arm/mach-orion5x/ts78xx-fpga.h
> :100644 100644 af810e7ccd79 d319a68b7160 M	arch/arm/mach-orion5x/ts78xx-setup.c
> :100644 100644 7ef9f5e696d3 d184fb435c5d M	drivers/irqchip/Kconfig
> :100644 100644 87b49a10962c b022eece2042 M	drivers/irqchip/Makefile
> :000000 100644 000000000000 178f43548b2a A	drivers/irqchip/irq-ts7800.c

How did you generated this patch?

>  arch/arm/mach-orion5x/ts78xx-fpga.h  |   1 +
>  arch/arm/mach-orion5x/ts78xx-setup.c |  55 +++++
>  drivers/irqchip/Kconfig              |  12 +
>  drivers/irqchip/Makefile             |   1 +
>  drivers/irqchip/irq-ts7800.c         | 319 +++++++++++++++++++++++++++
>  5 files changed, 388 insertions(+)
> 
> diff --git a/arch/arm/mach-orion5x/ts78xx-fpga.h b/arch/arm/mach-orion5x/ts78xx-fpga.h
> index 2f4fe3ca5c1a..ed8378893208 100644
> --- a/arch/arm/mach-orion5x/ts78xx-fpga.h
> +++ b/arch/arm/mach-orion5x/ts78xx-fpga.h
> @@ -32,6 +32,7 @@ struct fpga_devices {
>  	struct fpga_device	ts_rtc;
>  	struct fpga_device	ts_nand;
>  	struct fpga_device	ts_rng;
> +	struct fpga_device	ts_irqc;
>  };
>  
>  struct ts78xx_fpga_data {
> diff --git a/arch/arm/mach-orion5x/ts78xx-setup.c b/arch/arm/mach-orion5x/ts78xx-setup.c
> index af810e7ccd79..d319a68b7160 100644
> --- a/arch/arm/mach-orion5x/ts78xx-setup.c
> +++ b/arch/arm/mach-orion5x/ts78xx-setup.c
> @@ -322,6 +322,49 @@ static void ts78xx_ts_rng_unload(void)
>  	platform_device_del(&ts78xx_ts_rng_device);
>  }
>  
> +/*****************************************************************************
> + * fpga irq controller
> + ****************************************************************************/
> +#define TS_IRQC_CTRL (TS78XX_FPGA_REGS_PHYS_BASE + 0x200)
> +#define TS_BRIDGE_CTRL (ORION5X_REGS_PHYS_BASE + 0x20400)
> +#define TS_IRQC_PARENT 0x2
> +static struct resource ts_irqc_resources[] = {
> +	DEFINE_RES_MEM_NAMED(TS_IRQC_CTRL, 0x100, "ts_irqc"),
> +	DEFINE_RES_MEM_NAMED(TS_BRIDGE_CTRL, 0x10, "ts_bridge"),
> +	DEFINE_RES_IRQ_NAMED(TS_IRQC_PARENT, "ts_irqc_parent"),
> +};
> +
> +static struct platform_device ts_irqc_device = {
> +	.name = "ts7800-irqc",
> +	.id = -1,
> +	.resource = ts_irqc_resources,
> +	.num_resources = ARRAY_SIZE(ts_irqc_resources),
> +};
> +
> +static int ts_irqc_load(void)
> +{
> +	int rc;
> +
> +	if (ts78xx_fpga.supports.ts_irqc.init == 0) {
> +		rc = platform_device_register(&ts_irqc_device);
> +		if (!rc)
> +			ts78xx_fpga.supports.ts_irqc.init = 1;
> +	} else {
> +		rc = platform_device_add(&ts_irqc_device);
> +	}
> +
> +	if (rc)
> +		pr_info("FPGA based IRQ controller could not be registered: %d\n",
> +			rc);
> +
> +	return rc;
> +}
> +
> +static void ts_irqc_unload(void)
> +{
> +	platform_device_del(&ts_irqc_device);
> +}
> +
>  /*****************************************************************************
>   * FPGA 'hotplug' support code
>   ****************************************************************************/
> @@ -330,6 +373,7 @@ static void ts78xx_fpga_devices_zero_init(void)
>  	ts78xx_fpga.supports.ts_rtc.init = 0;
>  	ts78xx_fpga.supports.ts_nand.init = 0;
>  	ts78xx_fpga.supports.ts_rng.init = 0;
> +	ts78xx_fpga.supports.ts_irqc.init = 0;
>  }
>  
>  static void ts78xx_fpga_supports(void)
> @@ -348,6 +392,7 @@ static void ts78xx_fpga_supports(void)
>  		ts78xx_fpga.supports.ts_rtc.present = 1;
>  		ts78xx_fpga.supports.ts_nand.present = 1;
>  		ts78xx_fpga.supports.ts_rng.present = 1;
> +		ts78xx_fpga.supports.ts_irqc.present = 1;
>  		break;
>  	default:
>  		/* enable devices if magic matches */
> @@ -358,11 +403,13 @@ static void ts78xx_fpga_supports(void)
>  			ts78xx_fpga.supports.ts_rtc.present = 1;
>  			ts78xx_fpga.supports.ts_nand.present = 1;
>  			ts78xx_fpga.supports.ts_rng.present = 1;
> +			ts78xx_fpga.supports.ts_irqc.present = 1;
>  			break;
>  		default:
>  			ts78xx_fpga.supports.ts_rtc.present = 0;
>  			ts78xx_fpga.supports.ts_nand.present = 0;
>  			ts78xx_fpga.supports.ts_rng.present = 0;
> +			ts78xx_fpga.supports.ts_irqc.present = 0;
>  		}
>  	}
>  }
> @@ -371,6 +418,12 @@ static int ts78xx_fpga_load_devices(void)
>  {
>  	int tmp, ret = 0;
>  
> +	if (ts78xx_fpga.supports.ts_irqc.present == 1) {
> +		tmp = ts_irqc_load();
> +		if (tmp)
> +			ts78xx_fpga.supports.ts_irqc.present = 0;
> +		ret |= tmp;
> +	}
>  	if (ts78xx_fpga.supports.ts_rtc.present == 1) {
>  		tmp = ts78xx_ts_rtc_load();
>  		if (tmp)
> @@ -402,6 +455,8 @@ static int ts78xx_fpga_unload_devices(void)
>  		ts78xx_ts_nand_unload();
>  	if (ts78xx_fpga.supports.ts_rng.present == 1)
>  		ts78xx_ts_rng_unload();
> +	if (ts78xx_fpga.supports.ts_irqc.present == 1)
> +		ts_irqc_unload();
>  
>  	return 0;
>  }

I am absolutely *NOT* keen on adding more non-DT stuff in this day and
age. The DT effort has been going on for over 10 years, and maybe it
is time that this board makes the jump before we add anything new to
it, specially given that there is a DT board for this platform.

Arnd, what's your call on this?

> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 7ef9f5e696d3..d184fb435c5d 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -290,6 +290,18 @@ config TS4800_IRQ
>  	help
>  	  Support for the TS-4800 FPGA IRQ controller
>  
> +config TS7800_IRQ
> +	tristate "TS-7800 IRQ controller"
> +	select IRQ_DOMAIN
> +	depends on HAS_IOMEM
> +	depends on MACH_TS78XX
> +	help
> +	  Support for TS-7800 FPGA based IRQ controller.
> +	  This IRQ controller acts as a multiplexer chaining mapped
> +	  FPGA interrupts to a single system bridge interrupt.
> +
> +	  If you have an FPGA IP corresponding to this driver, say Y or M here.
> +

How can the user tell they have this IP or another one? Can they
freely load it? Can the make *changes* to it?

>  config VERSATILE_FPGA_IRQ
>  	bool
>  	select IRQ_DOMAIN
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 87b49a10962c..b022eece2042 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -58,6 +58,7 @@ obj-$(CONFIG_ARCH_VT8500)		+= irq-vt8500.o
>  obj-$(CONFIG_ST_IRQCHIP)		+= irq-st.o
>  obj-$(CONFIG_TB10X_IRQC)		+= irq-tb10x.o
>  obj-$(CONFIG_TS4800_IRQ)		+= irq-ts4800.o
> +obj-$(CONFIG_TS7800_IRQ)		+= irq-ts7800.o
>  obj-$(CONFIG_XTENSA)			+= irq-xtensa-pic.o
>  obj-$(CONFIG_XTENSA_MX)			+= irq-xtensa-mx.o
>  obj-$(CONFIG_XILINX_INTC)		+= irq-xilinx-intc.o
> diff --git a/drivers/irqchip/irq-ts7800.c b/drivers/irqchip/irq-ts7800.c
> new file mode 100644
> index 000000000000..178f43548b2a
> --- /dev/null
> +++ b/drivers/irqchip/irq-ts7800.c
> @@ -0,0 +1,319 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * FPGA based IRQ contorller driver for TS-7800v1
> + *
> + * Copyright (C) 2022 Savoir-faire Linux Inc.
> + *
> + */
> +
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/irqchip.h>
> +#include <linux/irqchip/chained_irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +
> +#define DRIVER_NAME "ts7800-irqc"
> +
> +#define IRQ_MASK_REG 0x4
> +#define IRQ_STATUS_REG 0x8
> +#define TS7800_IRQ_NUM 0x20
> +
> +/*
> + * FPGA IRQ Controller, list of all mappable/chainable interrupts
> + *
> + * IRQ  Description
> + * ---  -----------
> + * 0x0  dma_cpu_pci_dack_o
> + * 0x1  dma_fpga_dack_o
> + * 0x2  SD Busy#
> + * 0x3  isa_irq3
> + * 0x4  isa_irq4
> + * 0x5  isa_irq5
> + * 0x6  isa_irq6
> + * 0x7  isa_irq7
> + * 0x8  Reserved
> + * 0x9  isa_irq9
> + * 0xa  isa_irq10
> + * 0xb  isa_irq11
> + * 0xc  isa_irq12
> + * 0xd  isa_irq14
> + * 0xe  isa_irq15
> + * 0x10:0x19  tsuart_irqs
> + *
> + * To map or enable a specific FPGA interrupt, add its corresponding number to 'enabled_mappings'.
> + * Example:
> + *  1. For 'dma_cpu_pci_dack_o' (FPGA based DMA controller), add 0x0 to 'enabled_mappings'
> + *  2. For 'tsuart_irqs' (FPGA based UARTs), add their FPGA interrupt range of 0x10-0x19
> + *     to 'enabled_mappings'
> + *
> + * By default only mapped/enabled interrupts will be chained/multiplexed,
> + * these chained interrupts will then be available to other device drivers to request via the
> + * corresponding Linux IRQs.
> + *
> + * Each mapped FPGA interrupt will have a corresponding Linux IRQ, this new IRQ will be appended
> + * to the system's current list of Linux IRQs, on TS78xx, the first mapped FPGA interrupt will have
> + * a corresponding new Linux IRQ starting at 65. The next mapped FPGA interrupt will have a Linux
> + * IRQ of 66 and so forth.
> + *
> + * Example-1:
> + *  In order for a device driver, say the FPGA based DMA controller driver 'TS-DMAC',
> + *  to use either of the corresponding FPGA interrupts 'dma_cpu_pci_dack_o' and 'dma_fpga_dack_o',
> + *  the 'TS-DMAC' driver has to:
> + *  1. add FPGA interrupt numbers 0x0 and 0x1 to 'enabled_mappings', in this file, and
> + *  2. request the corresponding Linux IRQ numbers 65 and 66 respectively in its implementation.
> + *
> + * Eample-2:
> + *  In order for another device driver, say the FPGA based 'TSUART' driver, to use the related FPGA
> + *  interrupts, the 'TSUART' driver has to:
> + *  1. append FPGA interrupt numbers 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19 to
> + *     'enabled_mappings', in this file, and
> + *  2. request the corresponding Linux IRQ numbers, these IRQs could start from 65 till 74, if no
> + *     previous FPGA interrupts where mapped. However, if other FPGA interrupts, say those of
> + *     'TS-DMAC' in Example-1 were also mapped/enabled, then the correct corresponding Linux IRQs
> + *     would start from 67 till 76.
> + */

See why this really should be a DT-based driver? And please keep
comments to less than 80 chars per line.

> +
> +static irq_hw_number_t enabled_mappings[] = { 0x0 };
> +
> +struct ts7800_irq_data {
> +	int mpp7_virq;
> +	void __iomem *base;
> +	void __iomem *bridge;
> +	struct irq_domain *domain;
> +	struct irq_chip irq_chip;
> +};
> +
> +static void ts7800_irq_mask(struct irq_data *d)
> +{
> +	struct ts7800_irq_data *data = irq_data_get_irq_chip_data(d);
> +	u32 fpga_mask_reg = readl(data->base + IRQ_MASK_REG);
> +	u32 mask_bits = 1 << d->hwirq;
> +
> +	writel(fpga_mask_reg & ~mask_bits, data->base + IRQ_MASK_REG);
> +	writel(fpga_mask_reg & ~mask_bits, data->bridge + IRQ_MASK_REG);

I know your HW is UP, but seeing these RMW sequences without a lock
makes me jump. You probably want to either forbid this driver on SMP
systems, or add the required locks.

> +}
> +
> +static void ts7800_irq_unmask(struct irq_data *d)
> +{
> +	struct ts7800_irq_data *data = irq_data_get_irq_chip_data(d);
> +	u32 fpga_mask_reg = readl(data->base + IRQ_MASK_REG);
> +	u32 mask_bits = 1 << d->hwirq;
> +
> +	writel(fpga_mask_reg | mask_bits, data->base + IRQ_MASK_REG);
> +	writel(fpga_mask_reg | mask_bits, data->bridge + IRQ_MASK_REG);
> +}
> +
> +static int ts7800_irqdomain_map(struct irq_domain *d, unsigned int irq,
> +				irq_hw_number_t hwirq)
> +{
> +	struct ts7800_irq_data *data = d->host_data;
> +
> +	irq_set_chip_and_handler(irq, &data->irq_chip, handle_simple_irq);

Are you sure of this? Edge and level interrupts have very different
semantics, and handle_simple_irq seems like a massive cop-out.

> +	irq_set_chip_data(irq, data);
> +	irq_set_noprobe(irq);
> +
> +	return 0;
> +}
> +
> +static const struct irq_domain_ops ts7800_ic_ops = {
> +	.map = ts7800_irqdomain_map,
> +	.xlate = irq_domain_xlate_onecell,

I don't really get the usage model of this thing. Who provides the
interrupt specifier?

> +};
> +
> +static void ts7800_ic_chained_handle_irq(struct irq_desc *desc)
> +{
> +	struct ts7800_irq_data *data = irq_desc_get_handler_data(desc);
> +	struct irq_chip *chip = irq_desc_get_chip(desc);
> +	u32 mask_bits = readl(data->base + IRQ_STATUS_REG);
> +	u32 status = mask_bits;
> +
> +	chained_irq_enter(chip, desc);
> +
> +	if (unlikely(status == 0)) {
> +		handle_bad_irq(desc);
> +		goto out;
> +	}
> +
> +	do {
> +		unsigned int bit = __ffs(status);
> +		int irq = irq_find_mapping(data->domain, bit);
> +
> +		status &= ~(1 << bit);
> +
> +		if (irq && (mask_bits & BIT(bit))) {
> +			u32 x32 = readl(data->bridge);
> +
> +			generic_handle_irq(irq);

Please use generic handle_dopmain_irq().

> +
> +			x32 &= ~(mask_bits & BIT(bit));
> +			writel(x32, data->bridge);

What does this write do? If this is an Ack of the interrupt, you've
just lost an edge.

> +		}
> +	} while (status);
> +
> +out:
> +	chained_irq_exit(chip, desc);
> +}
> +
> +static int ts7800_ic_probe(struct platform_device *pdev)
> +{
> +	struct ts7800_irq_data *data = NULL;
> +	struct irq_chip *irq_chip = NULL;
> +	struct resource *mem_res = NULL, *brdg_res = NULL, *irq_res = NULL;
> +	unsigned int irqdomain;
> +	int i, ret;
> +
> +	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> +	if (IS_ERR_OR_NULL(data)) {
> +		dev_err(&pdev->dev,
> +			"Failed to allocate TS7800 data, error %ld\n",
> +			PTR_ERR(data));
> +		ret = PTR_ERR(data);
> +		goto devm_kzalloc_err;
> +	}
> +
> +	mem_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ts_irqc");
> +	if (IS_ERR_OR_NULL(mem_res)) {
> +		dev_err(&pdev->dev,
> +			"Failed to get platform memory resource, error %ld\n",
> +			PTR_ERR(mem_res));
> +		ret = PTR_ERR(mem_res);
> +		goto pltfrm_get_res_mem_err;
> +	}
> +
> +	data->base = devm_ioremap_resource(&pdev->dev, mem_res);
> +	if (IS_ERR_OR_NULL(data->base)) {
> +		dev_err(&pdev->dev,
> +			"Failed to IO map mem-region %s, error %ld\n",
> +			mem_res->name, PTR_ERR(data->base));
> +		ret = PTR_ERR(data->base);
> +		goto devm_ioremap_res_mem_err;
> +	}
> +
> +	brdg_res =
> +		platform_get_resource_byname(pdev, IORESOURCE_MEM, "ts_bridge");

Don't break assignments.

> +	if (IS_ERR_OR_NULL(brdg_res)) {
> +		dev_err(&pdev->dev,
> +			"Failed to get platform bridge resource, error %ld\n",
> +			PTR_ERR(brdg_res));
> +		ret = PTR_ERR(brdg_res);
> +		goto pltfrm_get_res_brdg_err;
> +	}
> +
> +	data->bridge = devm_ioremap_resource(&pdev->dev, brdg_res);
> +	if (IS_ERR_OR_NULL(data->bridge)) {
> +		dev_err(&pdev->dev,
> +			"Failed to IO map bridge-region %s, error %ld\n",
> +			mem_res->name, PTR_ERR(data->bridge));
> +		ret = PTR_ERR(data->bridge);
> +		goto devm_ioremap_res_brdge_err;
> +	}
> +
> +	irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
> +					       "ts_irqc_parent");
> +	if (IS_ERR_OR_NULL(irq_res)) {
> +		dev_err(&pdev->dev,
> +			"Failed to get platform parent irq resource, error %ld\n",
> +			PTR_ERR(irq_res));
> +		ret = PTR_ERR(irq_res);
> +		goto pltfrm_get_res_irq_err;
> +	}
> +
> +	data->mpp7_virq = irq_res->start;
> +	writel(0x0, data->base + IRQ_MASK_REG);
> +	writel(0x0, data->bridge + IRQ_MASK_REG);
> +	writel(0x0, data->bridge);
> +
> +	irq_chip = &data->irq_chip;
> +	irq_chip->name = dev_name(&pdev->dev);

No. We don't pretty-print these things. And really, the irq_chip
structure should be as static as possible (if possible const).

> +	irq_chip->irq_mask = ts7800_irq_mask;
> +	irq_chip->irq_unmask = ts7800_irq_unmask;
> +
> +	data->domain = irq_domain_add_linear(pdev->dev.of_node, TS7800_IRQ_NUM,
> +					     &ts7800_ic_ops, data);
> +	if (IS_ERR_OR_NULL(data->domain)) {
> +		dev_err(&pdev->dev, "cannot add IRQ domain\n");
> +		ret = PTR_ERR(data->domain);
> +		goto irq_domain_add_linear_err;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(enabled_mappings); ++i) {
> +		irqdomain =
> +			irq_create_mapping(data->domain, enabled_mappings[i]);
> +	}
> +
> +	irq_set_chained_handler_and_data(data->mpp7_virq,
> +					 ts7800_ic_chained_handle_irq, data);
> +
> +	irq_set_status_flags(data->mpp7_virq,
> +			     IRQ_DISABLE_UNLAZY | IRQ_LEVEL | IRQ_NOPROBE);

Why the IRQ_DISABLE_UNLAZY flag?

> +
> +	platform_set_drvdata(pdev, data);
> +
> +	return 0;
> +
> +irq_domain_add_linear_err:
> +pltfrm_get_res_irq_err:
> +	devm_iounmap(&pdev->dev, data->bridge);
> +
> +devm_ioremap_res_brdge_err:
> +pltfrm_get_res_brdg_err:
> +	devm_iounmap(&pdev->dev, data->base);
> +
> +devm_ioremap_res_mem_err:
> +pltfrm_get_res_mem_err:
> +	devm_kfree(&pdev->dev, data);
> +
> +devm_kzalloc_err:
> +	return ret;
> +}
> +
> +static int ts7800_ic_remove(struct platform_device *pdev)
> +{
> +	struct ts7800_irq_data *data = platform_get_drvdata(pdev);
> +	int i;
> +
> +	if (!IS_ERR_OR_NULL(data)) {
> +		irq_set_chained_handler_and_data(data->mpp7_virq, NULL, NULL);
> +
> +		for (i = 0; i < ARRAY_SIZE(enabled_mappings); ++i)
> +			irq_dispose_mapping(irq_find_mapping(
> +				data->domain, enabled_mappings[i]));
> +
> +		irq_domain_remove(data->domain);
> +	}
> +
> +	return 0;
> +}

We don't remove interrupt controllers. What happens if someone still
had a mapping?

> +
> +static const struct platform_device_id ts7800v1_ic_ids[] = {
> +	{
> +		.name = DRIVER_NAME,
> +	},
> +	{
> +		/* sentinel */
> +	}
> +};
> +
> +MODULE_DEVICE_TABLE(platform, ts7800v1_ic_ids);
> +
> +static struct platform_driver ts7800_ic_driver = {
> +	.probe  = ts7800_ic_probe,
> +	.remove = ts7800_ic_remove,
> +	.id_table	= ts7800v1_ic_ids,
> +	.driver = {
> +		.name = DRIVER_NAME,
> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +	},
> +};
> +module_platform_driver(ts7800_ic_driver);

Please use the existing helpers.

> +
> +MODULE_ALIAS("platform:ts7800-irqc");
> +MODULE_DESCRIPTION("TS-7800v1 FPGA based IRQ controller Driver");
> +MODULE_AUTHOR("Firas Ashkar <firas.ashkar@savoirfairelinux.com>");
> +MODULE_LICENSE("GPL");

	M.
  
Arnd Bergmann Oct. 18, 2022, 6:51 p.m. UTC | #2
On Tue, Oct 18, 2022, at 18:36, Marc Zyngier wrote:

>>  }
>> @@ -371,6 +418,12 @@ static int ts78xx_fpga_load_devices(void)
>>  {
>>  	int tmp, ret = 0;
>>  
>> +	if (ts78xx_fpga.supports.ts_irqc.present == 1) {
>> +		tmp = ts_irqc_load();
>> +		if (tmp)
>> +			ts78xx_fpga.supports.ts_irqc.present = 0;
>> +		ret |= tmp;
>> +	}
>>  	if (ts78xx_fpga.supports.ts_rtc.present == 1) {
>>  		tmp = ts78xx_ts_rtc_load();
>>  		if (tmp)
>> @@ -402,6 +455,8 @@ static int ts78xx_fpga_unload_devices(void)
>>  		ts78xx_ts_nand_unload();
>>  	if (ts78xx_fpga.supports.ts_rng.present == 1)
>>  		ts78xx_ts_rng_unload();
>> +	if (ts78xx_fpga.supports.ts_irqc.present == 1)
>> +		ts_irqc_unload();
>>  
>>  	return 0;
>>  }
>
> I am absolutely *NOT* keen on adding more non-DT stuff in this day and
> age. The DT effort has been going on for over 10 years, and maybe it
> is time that this board makes the jump before we add anything new to
> it, specially given that there is a DT board for this platform.
>
> Arnd, what's your call on this?

I'm in the middle of sending out the patch series that removes
most of the legacy board files:

https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git/log/?h=boardfile-remove

TS78xx actually stays for now because this is one of the
machines enabled in the debian armel tree, but my hope is
for the remaining 27 board files, we can either complete
the DT conversion or just remove them in the future.

Aside from the FPGA, this machine should be fairly easy to
convert, so I agree we should try doing this first. Not
sure how the FPGA is best represented, but I'm sure we can come
up with something once the rest of the system is working
with DT.

      Arnd
  
firas ashkar Oct. 20, 2022, 2:03 p.m. UTC | #3
thank you for the comments, pls check below for replies and acks

On Tue, 2022-10-18 at 17:36 +0100, Marc Zyngier wrote:
> +Arnd
> 
> On Tue, 18 Oct 2022 14:30:34 +0100,
> Firas Ashkar <firas.ashkar@savoirfairelinux.com> wrote:
> > 
> > 1. add TS-7800v1 fpga based irq controller driver, and
> > 2. add related memory and irq resources
> > 
> > By default only mapped FPGA interrupts will be chained/multiplexed,
> > these
> > chained interrupts will then be available to other device drivers
> > to
> > request via the corresponding Linux IRQs.
> > 
> > $ cat /proc/cpuinfo
> > processor       : 0
> > model name      : Feroceon rev 0 (v5l)
> > BogoMIPS        : 333.33
> > Features        : swp half thumb fastmult edsp
> > CPU implementer : 0x41
> > CPU architecture: 5TEJ
> > CPU variant     : 0x0
> > CPU part        : 0x926
> > CPU revision    : 0
> > 
> > Hardware        : Technologic Systems TS-78xx SBC
> > Revision        : 0000
> > Serial          : 0000000000000000
> > $
> > 
> > $ cat /proc/interrupts
> >            CPU0
> >   1:        902  orion_irq     Level     orion_tick
> >   4:        795  orion_irq     Level     ttyS0
> >  13:          0  orion_irq     Level     ehci_hcd:usb2
> >  18:          0  orion_irq     Level     ehci_hcd:usb1
> >  22:         69  orion_irq     Level     eth0
> >  23:        171  orion_irq     Level     orion-mdio
> >  29:          0  orion_irq     Level     mv_crypto
> >  31:          2  orion_irq     Level     mv_xor.0
> >  65:          1  ts7800-irqc   0 Edge      ts-dmac-cpupci
> > Err:          0
> > $
> > 
> > $ cat /proc/iomem
> > 00000000-07ffffff : System RAM
> >   00008000-00978fff : Kernel code
> >   00d20000-00e54f97 : Kernel data
> > e8000044-e8000047 : timeriomem_rng
> >   e8000044-e8000047 : timeriomem_rng timeriomem_rng
> > e8000200-e80002ff : ts_irqc
> >   e8000200-e80002ff : ts7800-irqc ts_irqc
> > e8000400-e80004ff : ts_dmac
> >   e8000400-e80004ff : ts7800-dmac ts_dmac
> > e8000804-e8000807 : gen_nand
> >   e8000804-e8000807 : gen_nand gen_nand
> > e8000808-e8000808 : rtc-m48t86
> >   e8000808-e8000808 : rtc-m48t86 rtc-m48t86
> > e800080c-e800080c : rtc-m48t86
> >   e800080c-e800080c : rtc-m48t86 rtc-m48t86
> > f1012000-f10120ff : serial8250.0
> >   f1012000-f101201f : serial
> > f1012100-f10121ff : serial8250.1
> >   f1012100-f101211f : serial
> > f1020108-f102010b : orion_wdt
> > f1020300-f1020303 : orion_wdt
> > f1020400-f102040f : ts_bridge
> >   f1020400-f102040f : ts7800-irqc ts_bridge
> > f1050000-f1050fff : orion-ehci.0
> >   f1050000-f1050fff : orion-ehci.0 orion-ehci.0
> > f1060900-f10609ff : xor 0 low
> > f1060b00-f1060bff : xor 0 high
> > f1072000-f1075fff : ge00 base
> >   f1072004-f1072087 : ge00 mvmdio base
> > f1080000-f1084fff : sata base
> > f1090000-f109ffff : regs
> >   f1090000-f109ffff : mv_crypto regs
> > f10a0000-f10a0fff : orion-ehci.1
> >   f10a0000-f10a0fff : orion-ehci.1 orion-ehci.1
> > f2200000-f2201fff : sram
> >   f2200000-f2201fff : mv_crypto sram
> > $
> > 
> > $ uname -a
> > Linux ts-7800 6.1.0-rc1 #2 PREEMPT Mon Oct 17 11:19:12 EDT 2022
> > armv5tel
> >  GNU/Linux
> > $
> 
> I don't see how useful this information is. Please limit the commit
> message to the provided functionality.
ack
> 
> > 
> > Signed-off-by: Firas Ashkar <firas.ashkar@savoirfairelinux.com>
> > ---
> > :100644 100644 2f4fe3ca5c1a ed8378893208 M      arch/arm/mach-
> > orion5x/ts78xx-fpga.h
> > :100644 100644 af810e7ccd79 d319a68b7160 M      arch/arm/mach-
> > orion5x/ts78xx-setup.c
> > :100644 100644 7ef9f5e696d3 d184fb435c5d
> > M      drivers/irqchip/Kconfig
> > :100644 100644 87b49a10962c b022eece2042
> > M      drivers/irqchip/Makefile
> > :000000 100644 000000000000 178f43548b2a
> > A      drivers/irqchip/irq-ts7800.c
> 
> How did you generated this patch?
$ git format-patch --stat -p --raw --signoff -1

> 
> >  arch/arm/mach-orion5x/ts78xx-fpga.h  |   1 +
> >  arch/arm/mach-orion5x/ts78xx-setup.c |  55 +++++
> >  drivers/irqchip/Kconfig              |  12 +
> >  drivers/irqchip/Makefile             |   1 +
> >  drivers/irqchip/irq-ts7800.c         | 319
> > +++++++++++++++++++++++++++
> >  5 files changed, 388 insertions(+)
> > 
> > diff --git a/arch/arm/mach-orion5x/ts78xx-fpga.h b/arch/arm/mach-
> > orion5x/ts78xx-fpga.h
> > index 2f4fe3ca5c1a..ed8378893208 100644
> > --- a/arch/arm/mach-orion5x/ts78xx-fpga.h
> > +++ b/arch/arm/mach-orion5x/ts78xx-fpga.h
> > @@ -32,6 +32,7 @@ struct fpga_devices {
> >         struct fpga_device      ts_rtc;
> >         struct fpga_device      ts_nand;
> >         struct fpga_device      ts_rng;
> > +       struct fpga_device      ts_irqc;
> >  };
> >  
> >  struct ts78xx_fpga_data {
> > diff --git a/arch/arm/mach-orion5x/ts78xx-setup.c b/arch/arm/mach-
> > orion5x/ts78xx-setup.c
> > index af810e7ccd79..d319a68b7160 100644
> > --- a/arch/arm/mach-orion5x/ts78xx-setup.c
> > +++ b/arch/arm/mach-orion5x/ts78xx-setup.c
> > @@ -322,6 +322,49 @@ static void ts78xx_ts_rng_unload(void)
> >         platform_device_del(&ts78xx_ts_rng_device);
> >  }
> >  
> > +/*****************************************************************
> > ************
> > + * fpga irq controller
> > +
> > *******************************************************************
> > *********/
> > +#define TS_IRQC_CTRL (TS78XX_FPGA_REGS_PHYS_BASE + 0x200)
> > +#define TS_BRIDGE_CTRL (ORION5X_REGS_PHYS_BASE + 0x20400)
> > +#define TS_IRQC_PARENT 0x2
> > +static struct resource ts_irqc_resources[] = {
> > +       DEFINE_RES_MEM_NAMED(TS_IRQC_CTRL, 0x100, "ts_irqc"),
> > +       DEFINE_RES_MEM_NAMED(TS_BRIDGE_CTRL, 0x10, "ts_bridge"),
> > +       DEFINE_RES_IRQ_NAMED(TS_IRQC_PARENT, "ts_irqc_parent"),
> > +};
> > +
> > +static struct platform_device ts_irqc_device = {
> > +       .name = "ts7800-irqc",
> > +       .id = -1,
> > +       .resource = ts_irqc_resources,
> > +       .num_resources = ARRAY_SIZE(ts_irqc_resources),
> > +};
> > +
> > +static int ts_irqc_load(void)
> > +{
> > +       int rc;
> > +
> > +       if (ts78xx_fpga.supports.ts_irqc.init == 0) {
> > +               rc = platform_device_register(&ts_irqc_device);
> > +               if (!rc)
> > +                       ts78xx_fpga.supports.ts_irqc.init = 1;
> > +       } else {
> > +               rc = platform_device_add(&ts_irqc_device);
> > +       }
> > +
> > +       if (rc)
> > +               pr_info("FPGA based IRQ controller could not be
> > registered: %d\n",
> > +                       rc);
> > +
> > +       return rc;
> > +}
> > +
> > +static void ts_irqc_unload(void)
> > +{
> > +       platform_device_del(&ts_irqc_device);
> > +}
> > +
> >  /*****************************************************************
> > ************
> >   * FPGA 'hotplug' support code
> >  
> > *******************************************************************
> > *********/
> > @@ -330,6 +373,7 @@ static void ts78xx_fpga_devices_zero_init(void)
> >         ts78xx_fpga.supports.ts_rtc.init = 0;
> >         ts78xx_fpga.supports.ts_nand.init = 0;
> >         ts78xx_fpga.supports.ts_rng.init = 0;
> > +       ts78xx_fpga.supports.ts_irqc.init = 0;
> >  }
> >  
> >  static void ts78xx_fpga_supports(void)
> > @@ -348,6 +392,7 @@ static void ts78xx_fpga_supports(void)
> >                 ts78xx_fpga.supports.ts_rtc.present = 1;
> >                 ts78xx_fpga.supports.ts_nand.present = 1;
> >                 ts78xx_fpga.supports.ts_rng.present = 1;
> > +               ts78xx_fpga.supports.ts_irqc.present = 1;
> >                 break;
> >         default:
> >                 /* enable devices if magic matches */
> > @@ -358,11 +403,13 @@ static void ts78xx_fpga_supports(void)
> >                         ts78xx_fpga.supports.ts_rtc.present = 1;
> >                         ts78xx_fpga.supports.ts_nand.present = 1;
> >                         ts78xx_fpga.supports.ts_rng.present = 1;
> > +                       ts78xx_fpga.supports.ts_irqc.present = 1;
> >                         break;
> >                 default:
> >                         ts78xx_fpga.supports.ts_rtc.present = 0;
> >                         ts78xx_fpga.supports.ts_nand.present = 0;
> >                         ts78xx_fpga.supports.ts_rng.present = 0;
> > +                       ts78xx_fpga.supports.ts_irqc.present = 0;
> >                 }
> >         }
> >  }
> > @@ -371,6 +418,12 @@ static int ts78xx_fpga_load_devices(void)
> >  {
> >         int tmp, ret = 0;
> >  
> > +       if (ts78xx_fpga.supports.ts_irqc.present == 1) {
> > +               tmp = ts_irqc_load();
> > +               if (tmp)
> > +                       ts78xx_fpga.supports.ts_irqc.present = 0;
> > +               ret |= tmp;
> > +       }
> >         if (ts78xx_fpga.supports.ts_rtc.present == 1) {
> >                 tmp = ts78xx_ts_rtc_load();
> >                 if (tmp)
> > @@ -402,6 +455,8 @@ static int ts78xx_fpga_unload_devices(void)
> >                 ts78xx_ts_nand_unload();
> >         if (ts78xx_fpga.supports.ts_rng.present == 1)
> >                 ts78xx_ts_rng_unload();
> > +       if (ts78xx_fpga.supports.ts_irqc.present == 1)
> > +               ts_irqc_unload();
> >  
> >         return 0;
> >  }
> 
> I am absolutely *NOT* keen on adding more non-DT stuff in this day
> and
> age. The DT effort has been going on for over 10 years, and maybe it
> is time that this board makes the jump before we add anything new to
> it, specially given that there is a DT board for this platform.
ack, though adding OF logic should not be a problem
> 
> Arnd, what's your call on this?
> 
> > diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> > index 7ef9f5e696d3..d184fb435c5d 100644
> > --- a/drivers/irqchip/Kconfig
> > +++ b/drivers/irqchip/Kconfig
> > @@ -290,6 +290,18 @@ config TS4800_IRQ
> >         help
> >           Support for the TS-4800 FPGA IRQ controller
> >  
> > +config TS7800_IRQ
> > +       tristate "TS-7800 IRQ controller"
> > +       select IRQ_DOMAIN
> > +       depends on HAS_IOMEM
> > +       depends on MACH_TS78XX
> > +       help
> > +         Support for TS-7800 FPGA based IRQ controller.
> > +         This IRQ controller acts as a multiplexer chaining mapped
> > +         FPGA interrupts to a single system bridge interrupt.
> > +
> > +         If you have an FPGA IP corresponding to this driver, say
> > Y or M here.
> > +
> 
> How can the user tell they have this IP or another one?
This FPGA IP, is present on EmbeddedTS TS7800 boards, as indicated in
https://docs.embeddedts.com/TS-7800#FPGA_IRQ_Controller

>  Can they
> freely load it? Can the make *changes* to it?
FPGA byte stream is available from EmbeddedTS, but not sure if end user
can/should change it

> 
> >  config VERSATILE_FPGA_IRQ
> >         bool
> >         select IRQ_DOMAIN
> > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> > index 87b49a10962c..b022eece2042 100644
> > --- a/drivers/irqchip/Makefile
> > +++ b/drivers/irqchip/Makefile
> > @@ -58,6 +58,7 @@ obj-$(CONFIG_ARCH_VT8500)             += irq-
> > vt8500.o
> >  obj-$(CONFIG_ST_IRQCHIP)               += irq-st.o
> >  obj-$(CONFIG_TB10X_IRQC)               += irq-tb10x.o
> >  obj-$(CONFIG_TS4800_IRQ)               += irq-ts4800.o
> > +obj-$(CONFIG_TS7800_IRQ)               += irq-ts7800.o
> >  obj-$(CONFIG_XTENSA)                   += irq-xtensa-pic.o
> >  obj-$(CONFIG_XTENSA_MX)                        += irq-xtensa-mx.o
> >  obj-$(CONFIG_XILINX_INTC)              += irq-xilinx-intc.o
> > diff --git a/drivers/irqchip/irq-ts7800.c b/drivers/irqchip/irq-
> > ts7800.c
> > new file mode 100644
> > index 000000000000..178f43548b2a
> > --- /dev/null
> > +++ b/drivers/irqchip/irq-ts7800.c
> > @@ -0,0 +1,319 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * FPGA based IRQ contorller driver for TS-7800v1
> > + *
> > + * Copyright (C) 2022 Savoir-faire Linux Inc.
> > + *
> > + */
> > +
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
> > +#include <linux/irq.h>
> > +#include <linux/irqchip.h>
> > +#include <linux/irqchip/chained_irq.h>
> > +#include <linux/irqdomain.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_irq.h>
> > +#include <linux/platform_device.h>
> > +
> > +#define DRIVER_NAME "ts7800-irqc"
> > +
> > +#define IRQ_MASK_REG 0x4
> > +#define IRQ_STATUS_REG 0x8
> > +#define TS7800_IRQ_NUM 0x20
> > +
> > +/*
> > + * FPGA IRQ Controller, list of all mappable/chainable interrupts
> > + *
> > + * IRQ  Description
> > + * ---  -----------
> > + * 0x0  dma_cpu_pci_dack_o
> > + * 0x1  dma_fpga_dack_o
> > + * 0x2  SD Busy#
> > + * 0x3  isa_irq3
> > + * 0x4  isa_irq4
> > + * 0x5  isa_irq5
> > + * 0x6  isa_irq6
> > + * 0x7  isa_irq7
> > + * 0x8  Reserved
> > + * 0x9  isa_irq9
> > + * 0xa  isa_irq10
> > + * 0xb  isa_irq11
> > + * 0xc  isa_irq12
> > + * 0xd  isa_irq14
> > + * 0xe  isa_irq15
> > + * 0x10:0x19  tsuart_irqs
> > + *
> > + * To map or enable a specific FPGA interrupt, add its
> > corresponding number to 'enabled_mappings'.
> > + * Example:
> > + *  1. For 'dma_cpu_pci_dack_o' (FPGA based DMA controller), add
> > 0x0 to 'enabled_mappings'
> > + *  2. For 'tsuart_irqs' (FPGA based UARTs), add their FPGA
> > interrupt range of 0x10-0x19
> > + *     to 'enabled_mappings'
> > + *
> > + * By default only mapped/enabled interrupts will be
> > chained/multiplexed,
> > + * these chained interrupts will then be available to other device
> > drivers to request via the
> > + * corresponding Linux IRQs.
> > + *
> > + * Each mapped FPGA interrupt will have a corresponding Linux IRQ,
> > this new IRQ will be appended
> > + * to the system's current list of Linux IRQs, on TS78xx, the
> > first mapped FPGA interrupt will have
> > + * a corresponding new Linux IRQ starting at 65. The next mapped
> > FPGA interrupt will have a Linux
> > + * IRQ of 66 and so forth.
> > + *
> > + * Example-1:
> > + *  In order for a device driver, say the FPGA based DMA
> > controller driver 'TS-DMAC',
> > + *  to use either of the corresponding FPGA interrupts
> > 'dma_cpu_pci_dack_o' and 'dma_fpga_dack_o',
> > + *  the 'TS-DMAC' driver has to:
> > + *  1. add FPGA interrupt numbers 0x0 and 0x1 to
> > 'enabled_mappings', in this file, and
> > + *  2. request the corresponding Linux IRQ numbers 65 and 66
> > respectively in its implementation.
> > + *
> > + * Eample-2:
> > + *  In order for another device driver, say the FPGA based
> > 'TSUART' driver, to use the related FPGA
> > + *  interrupts, the 'TSUART' driver has to:
> > + *  1. append FPGA interrupt numbers 0x10, 0x11, 0x12, 0x13, 0x14,
> > 0x15, 0x16, 0x17, 0x18, 0x19 to
> > + *     'enabled_mappings', in this file, and
> > + *  2. request the corresponding Linux IRQ numbers, these IRQs
> > could start from 65 till 74, if no
> > + *     previous FPGA interrupts where mapped. However, if other
> > FPGA interrupts, say those of
> > + *     'TS-DMAC' in Example-1 were also mapped/enabled, then the
> > correct corresponding Linux IRQs
> > + *     would start from 67 till 76.
> > + */
> 
> See why this really should be a DT-based driver?
agreed, though when this board starts using Open Firmware's Device-Tree
scheme, will port this driver accordingly 

>  And please keep
> comments to less than 80 chars per line.
ack
> 
> > +
> > +static irq_hw_number_t enabled_mappings[] = { 0x0 };
> > +
> > +struct ts7800_irq_data {
> > +       int mpp7_virq;
> > +       void __iomem *base;
> > +       void __iomem *bridge;
> > +       struct irq_domain *domain;
> > +       struct irq_chip irq_chip;
> > +};
> > +
> > +static void ts7800_irq_mask(struct irq_data *d)
> > +{
> > +       struct ts7800_irq_data *data =
> > irq_data_get_irq_chip_data(d);
> > +       u32 fpga_mask_reg = readl(data->base + IRQ_MASK_REG);
> > +       u32 mask_bits = 1 << d->hwirq;
> > +
> > +       writel(fpga_mask_reg & ~mask_bits, data->base +
> > IRQ_MASK_REG);
> > +       writel(fpga_mask_reg & ~mask_bits, data->bridge +
> > IRQ_MASK_REG);
> 
> I know your HW is UP, but seeing these RMW sequences without a lock
> makes me jump. You probably want to either forbid this driver on SMP
> systems, or add the required locks.
this is single CPU, nevertheless we'll add locking
> 
> > +}
> > +
> > +static void ts7800_irq_unmask(struct irq_data *d)
> > +{
> > +       struct ts7800_irq_data *data =
> > irq_data_get_irq_chip_data(d);
> > +       u32 fpga_mask_reg = readl(data->base + IRQ_MASK_REG);
> > +       u32 mask_bits = 1 << d->hwirq;
> > +
> > +       writel(fpga_mask_reg | mask_bits, data->base +
> > IRQ_MASK_REG);
> > +       writel(fpga_mask_reg | mask_bits, data->bridge +
> > IRQ_MASK_REG);
> > +}
> > +
> > +static int ts7800_irqdomain_map(struct irq_domain *d, unsigned int
> > irq,
> > +                               irq_hw_number_t hwirq)
> > +{
> > +       struct ts7800_irq_data *data = d->host_data;
> > +
> > +       irq_set_chip_and_handler(irq, &data->irq_chip,
> > handle_simple_irq);
> 
> Are you sure of this? Edge and level interrupts have very different
> semantics, and handle_simple_irq seems like a massive cop-out.
ok, we'll update to 'handle_edge_irq' and 'ts7800_irq_ack' logic
instead

> 
> > +       irq_set_chip_data(irq, data);
> > +       irq_set_noprobe(irq);
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct irq_domain_ops ts7800_ic_ops = {
> > +       .map = ts7800_irqdomain_map,
> > +       .xlate = irq_domain_xlate_onecell,
> 
> I don't really get the usage model of this thing. Who provides the
> interrupt specifier?
ack, to be removed
> 
> > +};
> > +
> > +static void ts7800_ic_chained_handle_irq(struct irq_desc *desc)
> > +{
> > +       struct ts7800_irq_data *data =
> > irq_desc_get_handler_data(desc);
> > +       struct irq_chip *chip = irq_desc_get_chip(desc);
> > +       u32 mask_bits = readl(data->base + IRQ_STATUS_REG);
> > +       u32 status = mask_bits;
> > +
> > +       chained_irq_enter(chip, desc);
> > +
> > +       if (unlikely(status == 0)) {
> > +               handle_bad_irq(desc);
> > +               goto out;
> > +       }
> > +
> > +       do {
> > +               unsigned int bit = __ffs(status);
> > +               int irq = irq_find_mapping(data->domain, bit);
> > +
> > +               status &= ~(1 << bit);
> > +
> > +               if (irq && (mask_bits & BIT(bit))) {
> > +                       u32 x32 = readl(data->bridge);
> > +
> > +                       generic_handle_irq(irq);
> 
> Please use generic handle_dopmain_irq().
this is a chained/multiplexed logic, starts with 'chained_irq_enter'
calls 'generic_handle_irq' and terminates with 'chained_irq_exit',
we followed same logic in other similar drivers, 'qcom-irq-combiner.c',
'exynos-combiner.c', etc
> 
> > +
> > +                       x32 &= ~(mask_bits & BIT(bit));
> > +                       writel(x32, data->bridge);
> 
> What does this write do? If this is an Ack of the interrupt, you've
> just lost an edge.
to be removed, and use 'ts7800_irq_ack' logic instead

> 
> > +               }
> > +       } while (status);
> > +
> > +out:
> > +       chained_irq_exit(chip, desc);
> > +}
> > +
> > +static int ts7800_ic_probe(struct platform_device *pdev)
> > +{
> > +       struct ts7800_irq_data *data = NULL;
> > +       struct irq_chip *irq_chip = NULL;
> > +       struct resource *mem_res = NULL, *brdg_res = NULL, *irq_res
> > = NULL;
> > +       unsigned int irqdomain;
> > +       int i, ret;
> > +
> > +       data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> > +       if (IS_ERR_OR_NULL(data)) {
> > +               dev_err(&pdev->dev,
> > +                       "Failed to allocate TS7800 data, error
> > %ld\n",
> > +                       PTR_ERR(data));
> > +               ret = PTR_ERR(data);
> > +               goto devm_kzalloc_err;
> > +       }
> > +
> > +       mem_res = platform_get_resource_byname(pdev,
> > IORESOURCE_MEM, "ts_irqc");
> > +       if (IS_ERR_OR_NULL(mem_res)) {
> > +               dev_err(&pdev->dev,
> > +                       "Failed to get platform memory resource,
> > error %ld\n",
> > +                       PTR_ERR(mem_res));
> > +               ret = PTR_ERR(mem_res);
> > +               goto pltfrm_get_res_mem_err;
> > +       }
> > +
> > +       data->base = devm_ioremap_resource(&pdev->dev, mem_res);
> > +       if (IS_ERR_OR_NULL(data->base)) {
> > +               dev_err(&pdev->dev,
> > +                       "Failed to IO map mem-region %s, error
> > %ld\n",
> > +                       mem_res->name, PTR_ERR(data->base));
> > +               ret = PTR_ERR(data->base);
> > +               goto devm_ioremap_res_mem_err;
> > +       }
> > +
> > +       brdg_res =
> > +               platform_get_resource_byname(pdev, IORESOURCE_MEM,
> > "ts_bridge");
> 
> Don't break assignments.
ack
> 
> > +       if (IS_ERR_OR_NULL(brdg_res)) {
> > +               dev_err(&pdev->dev,
> > +                       "Failed to get platform bridge resource,
> > error %ld\n",
> > +                       PTR_ERR(brdg_res));
> > +               ret = PTR_ERR(brdg_res);
> > +               goto pltfrm_get_res_brdg_err;
> > +       }
> > +
> > +       data->bridge = devm_ioremap_resource(&pdev->dev, brdg_res);
> > +       if (IS_ERR_OR_NULL(data->bridge)) {
> > +               dev_err(&pdev->dev,
> > +                       "Failed to IO map bridge-region %s, error
> > %ld\n",
> > +                       mem_res->name, PTR_ERR(data->bridge));
> > +               ret = PTR_ERR(data->bridge);
> > +               goto devm_ioremap_res_brdge_err;
> > +       }
> > +
> > +       irq_res = platform_get_resource_byname(pdev,
> > IORESOURCE_IRQ,
> > +                                              "ts_irqc_parent");
> > +       if (IS_ERR_OR_NULL(irq_res)) {
> > +               dev_err(&pdev->dev,
> > +                       "Failed to get platform parent irq
> > resource, error %ld\n",
> > +                       PTR_ERR(irq_res));
> > +               ret = PTR_ERR(irq_res);
> > +               goto pltfrm_get_res_irq_err;
> > +       }
> > +
> > +       data->mpp7_virq = irq_res->start;
> > +       writel(0x0, data->base + IRQ_MASK_REG);
> > +       writel(0x0, data->bridge + IRQ_MASK_REG);
> > +       writel(0x0, data->bridge);
> > +
> > +       irq_chip = &data->irq_chip;
> > +       irq_chip->name = dev_name(&pdev->dev);
> 
> No. We don't pretty-print these things. And really, the irq_chip
> structure should be as static as possible (if possible const).
ack
> 
> > +       irq_chip->irq_mask = ts7800_irq_mask;
> > +       irq_chip->irq_unmask = ts7800_irq_unmask;
> > +
> > +       data->domain = irq_domain_add_linear(pdev->dev.of_node,
> > TS7800_IRQ_NUM,
> > +                                            &ts7800_ic_ops, data);
> > +       if (IS_ERR_OR_NULL(data->domain)) {
> > +               dev_err(&pdev->dev, "cannot add IRQ domain\n");
> > +               ret = PTR_ERR(data->domain);
> > +               goto irq_domain_add_linear_err;
> > +       }
> > +
> > +       for (i = 0; i < ARRAY_SIZE(enabled_mappings); ++i) {
> > +               irqdomain =
> > +                       irq_create_mapping(data->domain,
> > enabled_mappings[i]);
> > +       }
> > +
> > +       irq_set_chained_handler_and_data(data->mpp7_virq,
> > +                                       
> > ts7800_ic_chained_handle_irq, data);
> > +
> > +       irq_set_status_flags(data->mpp7_virq,
> > +                            IRQ_DISABLE_UNLAZY | IRQ_LEVEL |
> > IRQ_NOPROBE);
> 
> Why the IRQ_DISABLE_UNLAZY flag?
ack, to be removed
> 
> > +
> > +       platform_set_drvdata(pdev, data);
> > +
> > +       return 0;
> > +
> > +irq_domain_add_linear_err:
> > +pltfrm_get_res_irq_err:
> > +       devm_iounmap(&pdev->dev, data->bridge);
> > +
> > +devm_ioremap_res_brdge_err:
> > +pltfrm_get_res_brdg_err:
> > +       devm_iounmap(&pdev->dev, data->base);
> > +
> > +devm_ioremap_res_mem_err:
> > +pltfrm_get_res_mem_err:
> > +       devm_kfree(&pdev->dev, data);
> > +
> > +devm_kzalloc_err:
> > +       return ret;
> > +}
> > +
> > +static int ts7800_ic_remove(struct platform_device *pdev)
> > +{
> > +       struct ts7800_irq_data *data = platform_get_drvdata(pdev);
> > +       int i;
> > +
> > +       if (!IS_ERR_OR_NULL(data)) {
> > +               irq_set_chained_handler_and_data(data->mpp7_virq,
> > NULL, NULL);
> > +
> > +               for (i = 0; i < ARRAY_SIZE(enabled_mappings); ++i)
> > +                       irq_dispose_mapping(irq_find_mapping(
> > +                               data->domain,
> > enabled_mappings[i]));
> > +
> > +               irq_domain_remove(data->domain);
> > +       }
> > +
> > +       return 0;
> > +}
> 
> We don't remove interrupt controllers. What happens if someone still
> had a mapping?
becomes permanent 

# lsmod
Module Size Used by Not tainted
ts7800v1_sdmmc 45056 0 
ts_dmac 24576 1 
virt_dma 16384 1 ts_dmac
irq_ts7800 16384 0 [permanent]

# rmmod irq_ts7800
rmmod: can't unload module 'irq_ts7800': Device or resource busy
# 
> 
> > +
> > +static const struct platform_device_id ts7800v1_ic_ids[] = {
> > +       {
> > +               .name = DRIVER_NAME,
> > +       },
> > +       {
> > +               /* sentinel */
> > +       }
> > +};
> > +
> > +MODULE_DEVICE_TABLE(platform, ts7800v1_ic_ids);
> > +
> > +static struct platform_driver ts7800_ic_driver = {
> > +       .probe  = ts7800_ic_probe,
> > +       .remove = ts7800_ic_remove,
> > +       .id_table       = ts7800v1_ic_ids,
> > +       .driver = {
> > +               .name = DRIVER_NAME,
> > +               .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> > +       },
> > +};
> > +module_platform_driver(ts7800_ic_driver);
> 
> Please use the existing helpers.
ack
> 
> > +
> > +MODULE_ALIAS("platform:ts7800-irqc");
> > +MODULE_DESCRIPTION("TS-7800v1 FPGA based IRQ controller Driver");
> > +MODULE_AUTHOR("Firas Ashkar <firas.ashkar@savoirfairelinux.com>");
> > +MODULE_LICENSE("GPL");
> 
>         M.
>
  

Patch

diff --git a/arch/arm/mach-orion5x/ts78xx-fpga.h b/arch/arm/mach-orion5x/ts78xx-fpga.h
index 2f4fe3ca5c1a..ed8378893208 100644
--- a/arch/arm/mach-orion5x/ts78xx-fpga.h
+++ b/arch/arm/mach-orion5x/ts78xx-fpga.h
@@ -32,6 +32,7 @@  struct fpga_devices {
 	struct fpga_device	ts_rtc;
 	struct fpga_device	ts_nand;
 	struct fpga_device	ts_rng;
+	struct fpga_device	ts_irqc;
 };
 
 struct ts78xx_fpga_data {
diff --git a/arch/arm/mach-orion5x/ts78xx-setup.c b/arch/arm/mach-orion5x/ts78xx-setup.c
index af810e7ccd79..d319a68b7160 100644
--- a/arch/arm/mach-orion5x/ts78xx-setup.c
+++ b/arch/arm/mach-orion5x/ts78xx-setup.c
@@ -322,6 +322,49 @@  static void ts78xx_ts_rng_unload(void)
 	platform_device_del(&ts78xx_ts_rng_device);
 }
 
+/*****************************************************************************
+ * fpga irq controller
+ ****************************************************************************/
+#define TS_IRQC_CTRL (TS78XX_FPGA_REGS_PHYS_BASE + 0x200)
+#define TS_BRIDGE_CTRL (ORION5X_REGS_PHYS_BASE + 0x20400)
+#define TS_IRQC_PARENT 0x2
+static struct resource ts_irqc_resources[] = {
+	DEFINE_RES_MEM_NAMED(TS_IRQC_CTRL, 0x100, "ts_irqc"),
+	DEFINE_RES_MEM_NAMED(TS_BRIDGE_CTRL, 0x10, "ts_bridge"),
+	DEFINE_RES_IRQ_NAMED(TS_IRQC_PARENT, "ts_irqc_parent"),
+};
+
+static struct platform_device ts_irqc_device = {
+	.name = "ts7800-irqc",
+	.id = -1,
+	.resource = ts_irqc_resources,
+	.num_resources = ARRAY_SIZE(ts_irqc_resources),
+};
+
+static int ts_irqc_load(void)
+{
+	int rc;
+
+	if (ts78xx_fpga.supports.ts_irqc.init == 0) {
+		rc = platform_device_register(&ts_irqc_device);
+		if (!rc)
+			ts78xx_fpga.supports.ts_irqc.init = 1;
+	} else {
+		rc = platform_device_add(&ts_irqc_device);
+	}
+
+	if (rc)
+		pr_info("FPGA based IRQ controller could not be registered: %d\n",
+			rc);
+
+	return rc;
+}
+
+static void ts_irqc_unload(void)
+{
+	platform_device_del(&ts_irqc_device);
+}
+
 /*****************************************************************************
  * FPGA 'hotplug' support code
  ****************************************************************************/
@@ -330,6 +373,7 @@  static void ts78xx_fpga_devices_zero_init(void)
 	ts78xx_fpga.supports.ts_rtc.init = 0;
 	ts78xx_fpga.supports.ts_nand.init = 0;
 	ts78xx_fpga.supports.ts_rng.init = 0;
+	ts78xx_fpga.supports.ts_irqc.init = 0;
 }
 
 static void ts78xx_fpga_supports(void)
@@ -348,6 +392,7 @@  static void ts78xx_fpga_supports(void)
 		ts78xx_fpga.supports.ts_rtc.present = 1;
 		ts78xx_fpga.supports.ts_nand.present = 1;
 		ts78xx_fpga.supports.ts_rng.present = 1;
+		ts78xx_fpga.supports.ts_irqc.present = 1;
 		break;
 	default:
 		/* enable devices if magic matches */
@@ -358,11 +403,13 @@  static void ts78xx_fpga_supports(void)
 			ts78xx_fpga.supports.ts_rtc.present = 1;
 			ts78xx_fpga.supports.ts_nand.present = 1;
 			ts78xx_fpga.supports.ts_rng.present = 1;
+			ts78xx_fpga.supports.ts_irqc.present = 1;
 			break;
 		default:
 			ts78xx_fpga.supports.ts_rtc.present = 0;
 			ts78xx_fpga.supports.ts_nand.present = 0;
 			ts78xx_fpga.supports.ts_rng.present = 0;
+			ts78xx_fpga.supports.ts_irqc.present = 0;
 		}
 	}
 }
@@ -371,6 +418,12 @@  static int ts78xx_fpga_load_devices(void)
 {
 	int tmp, ret = 0;
 
+	if (ts78xx_fpga.supports.ts_irqc.present == 1) {
+		tmp = ts_irqc_load();
+		if (tmp)
+			ts78xx_fpga.supports.ts_irqc.present = 0;
+		ret |= tmp;
+	}
 	if (ts78xx_fpga.supports.ts_rtc.present == 1) {
 		tmp = ts78xx_ts_rtc_load();
 		if (tmp)
@@ -402,6 +455,8 @@  static int ts78xx_fpga_unload_devices(void)
 		ts78xx_ts_nand_unload();
 	if (ts78xx_fpga.supports.ts_rng.present == 1)
 		ts78xx_ts_rng_unload();
+	if (ts78xx_fpga.supports.ts_irqc.present == 1)
+		ts_irqc_unload();
 
 	return 0;
 }
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 7ef9f5e696d3..d184fb435c5d 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -290,6 +290,18 @@  config TS4800_IRQ
 	help
 	  Support for the TS-4800 FPGA IRQ controller
 
+config TS7800_IRQ
+	tristate "TS-7800 IRQ controller"
+	select IRQ_DOMAIN
+	depends on HAS_IOMEM
+	depends on MACH_TS78XX
+	help
+	  Support for TS-7800 FPGA based IRQ controller.
+	  This IRQ controller acts as a multiplexer chaining mapped
+	  FPGA interrupts to a single system bridge interrupt.
+
+	  If you have an FPGA IP corresponding to this driver, say Y or M here.
+
 config VERSATILE_FPGA_IRQ
 	bool
 	select IRQ_DOMAIN
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 87b49a10962c..b022eece2042 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -58,6 +58,7 @@  obj-$(CONFIG_ARCH_VT8500)		+= irq-vt8500.o
 obj-$(CONFIG_ST_IRQCHIP)		+= irq-st.o
 obj-$(CONFIG_TB10X_IRQC)		+= irq-tb10x.o
 obj-$(CONFIG_TS4800_IRQ)		+= irq-ts4800.o
+obj-$(CONFIG_TS7800_IRQ)		+= irq-ts7800.o
 obj-$(CONFIG_XTENSA)			+= irq-xtensa-pic.o
 obj-$(CONFIG_XTENSA_MX)			+= irq-xtensa-mx.o
 obj-$(CONFIG_XILINX_INTC)		+= irq-xilinx-intc.o
diff --git a/drivers/irqchip/irq-ts7800.c b/drivers/irqchip/irq-ts7800.c
new file mode 100644
index 000000000000..178f43548b2a
--- /dev/null
+++ b/drivers/irqchip/irq-ts7800.c
@@ -0,0 +1,319 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * FPGA based IRQ contorller driver for TS-7800v1
+ *
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "ts7800-irqc"
+
+#define IRQ_MASK_REG 0x4
+#define IRQ_STATUS_REG 0x8
+#define TS7800_IRQ_NUM 0x20
+
+/*
+ * FPGA IRQ Controller, list of all mappable/chainable interrupts
+ *
+ * IRQ  Description
+ * ---  -----------
+ * 0x0  dma_cpu_pci_dack_o
+ * 0x1  dma_fpga_dack_o
+ * 0x2  SD Busy#
+ * 0x3  isa_irq3
+ * 0x4  isa_irq4
+ * 0x5  isa_irq5
+ * 0x6  isa_irq6
+ * 0x7  isa_irq7
+ * 0x8  Reserved
+ * 0x9  isa_irq9
+ * 0xa  isa_irq10
+ * 0xb  isa_irq11
+ * 0xc  isa_irq12
+ * 0xd  isa_irq14
+ * 0xe  isa_irq15
+ * 0x10:0x19  tsuart_irqs
+ *
+ * To map or enable a specific FPGA interrupt, add its corresponding number to 'enabled_mappings'.
+ * Example:
+ *  1. For 'dma_cpu_pci_dack_o' (FPGA based DMA controller), add 0x0 to 'enabled_mappings'
+ *  2. For 'tsuart_irqs' (FPGA based UARTs), add their FPGA interrupt range of 0x10-0x19
+ *     to 'enabled_mappings'
+ *
+ * By default only mapped/enabled interrupts will be chained/multiplexed,
+ * these chained interrupts will then be available to other device drivers to request via the
+ * corresponding Linux IRQs.
+ *
+ * Each mapped FPGA interrupt will have a corresponding Linux IRQ, this new IRQ will be appended
+ * to the system's current list of Linux IRQs, on TS78xx, the first mapped FPGA interrupt will have
+ * a corresponding new Linux IRQ starting at 65. The next mapped FPGA interrupt will have a Linux
+ * IRQ of 66 and so forth.
+ *
+ * Example-1:
+ *  In order for a device driver, say the FPGA based DMA controller driver 'TS-DMAC',
+ *  to use either of the corresponding FPGA interrupts 'dma_cpu_pci_dack_o' and 'dma_fpga_dack_o',
+ *  the 'TS-DMAC' driver has to:
+ *  1. add FPGA interrupt numbers 0x0 and 0x1 to 'enabled_mappings', in this file, and
+ *  2. request the corresponding Linux IRQ numbers 65 and 66 respectively in its implementation.
+ *
+ * Eample-2:
+ *  In order for another device driver, say the FPGA based 'TSUART' driver, to use the related FPGA
+ *  interrupts, the 'TSUART' driver has to:
+ *  1. append FPGA interrupt numbers 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19 to
+ *     'enabled_mappings', in this file, and
+ *  2. request the corresponding Linux IRQ numbers, these IRQs could start from 65 till 74, if no
+ *     previous FPGA interrupts where mapped. However, if other FPGA interrupts, say those of
+ *     'TS-DMAC' in Example-1 were also mapped/enabled, then the correct corresponding Linux IRQs
+ *     would start from 67 till 76.
+ */
+
+static irq_hw_number_t enabled_mappings[] = { 0x0 };
+
+struct ts7800_irq_data {
+	int mpp7_virq;
+	void __iomem *base;
+	void __iomem *bridge;
+	struct irq_domain *domain;
+	struct irq_chip irq_chip;
+};
+
+static void ts7800_irq_mask(struct irq_data *d)
+{
+	struct ts7800_irq_data *data = irq_data_get_irq_chip_data(d);
+	u32 fpga_mask_reg = readl(data->base + IRQ_MASK_REG);
+	u32 mask_bits = 1 << d->hwirq;
+
+	writel(fpga_mask_reg & ~mask_bits, data->base + IRQ_MASK_REG);
+	writel(fpga_mask_reg & ~mask_bits, data->bridge + IRQ_MASK_REG);
+}
+
+static void ts7800_irq_unmask(struct irq_data *d)
+{
+	struct ts7800_irq_data *data = irq_data_get_irq_chip_data(d);
+	u32 fpga_mask_reg = readl(data->base + IRQ_MASK_REG);
+	u32 mask_bits = 1 << d->hwirq;
+
+	writel(fpga_mask_reg | mask_bits, data->base + IRQ_MASK_REG);
+	writel(fpga_mask_reg | mask_bits, data->bridge + IRQ_MASK_REG);
+}
+
+static int ts7800_irqdomain_map(struct irq_domain *d, unsigned int irq,
+				irq_hw_number_t hwirq)
+{
+	struct ts7800_irq_data *data = d->host_data;
+
+	irq_set_chip_and_handler(irq, &data->irq_chip, handle_simple_irq);
+	irq_set_chip_data(irq, data);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops ts7800_ic_ops = {
+	.map = ts7800_irqdomain_map,
+	.xlate = irq_domain_xlate_onecell,
+};
+
+static void ts7800_ic_chained_handle_irq(struct irq_desc *desc)
+{
+	struct ts7800_irq_data *data = irq_desc_get_handler_data(desc);
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	u32 mask_bits = readl(data->base + IRQ_STATUS_REG);
+	u32 status = mask_bits;
+
+	chained_irq_enter(chip, desc);
+
+	if (unlikely(status == 0)) {
+		handle_bad_irq(desc);
+		goto out;
+	}
+
+	do {
+		unsigned int bit = __ffs(status);
+		int irq = irq_find_mapping(data->domain, bit);
+
+		status &= ~(1 << bit);
+
+		if (irq && (mask_bits & BIT(bit))) {
+			u32 x32 = readl(data->bridge);
+
+			generic_handle_irq(irq);
+
+			x32 &= ~(mask_bits & BIT(bit));
+			writel(x32, data->bridge);
+		}
+	} while (status);
+
+out:
+	chained_irq_exit(chip, desc);
+}
+
+static int ts7800_ic_probe(struct platform_device *pdev)
+{
+	struct ts7800_irq_data *data = NULL;
+	struct irq_chip *irq_chip = NULL;
+	struct resource *mem_res = NULL, *brdg_res = NULL, *irq_res = NULL;
+	unsigned int irqdomain;
+	int i, ret;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (IS_ERR_OR_NULL(data)) {
+		dev_err(&pdev->dev,
+			"Failed to allocate TS7800 data, error %ld\n",
+			PTR_ERR(data));
+		ret = PTR_ERR(data);
+		goto devm_kzalloc_err;
+	}
+
+	mem_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ts_irqc");
+	if (IS_ERR_OR_NULL(mem_res)) {
+		dev_err(&pdev->dev,
+			"Failed to get platform memory resource, error %ld\n",
+			PTR_ERR(mem_res));
+		ret = PTR_ERR(mem_res);
+		goto pltfrm_get_res_mem_err;
+	}
+
+	data->base = devm_ioremap_resource(&pdev->dev, mem_res);
+	if (IS_ERR_OR_NULL(data->base)) {
+		dev_err(&pdev->dev,
+			"Failed to IO map mem-region %s, error %ld\n",
+			mem_res->name, PTR_ERR(data->base));
+		ret = PTR_ERR(data->base);
+		goto devm_ioremap_res_mem_err;
+	}
+
+	brdg_res =
+		platform_get_resource_byname(pdev, IORESOURCE_MEM, "ts_bridge");
+	if (IS_ERR_OR_NULL(brdg_res)) {
+		dev_err(&pdev->dev,
+			"Failed to get platform bridge resource, error %ld\n",
+			PTR_ERR(brdg_res));
+		ret = PTR_ERR(brdg_res);
+		goto pltfrm_get_res_brdg_err;
+	}
+
+	data->bridge = devm_ioremap_resource(&pdev->dev, brdg_res);
+	if (IS_ERR_OR_NULL(data->bridge)) {
+		dev_err(&pdev->dev,
+			"Failed to IO map bridge-region %s, error %ld\n",
+			mem_res->name, PTR_ERR(data->bridge));
+		ret = PTR_ERR(data->bridge);
+		goto devm_ioremap_res_brdge_err;
+	}
+
+	irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+					       "ts_irqc_parent");
+	if (IS_ERR_OR_NULL(irq_res)) {
+		dev_err(&pdev->dev,
+			"Failed to get platform parent irq resource, error %ld\n",
+			PTR_ERR(irq_res));
+		ret = PTR_ERR(irq_res);
+		goto pltfrm_get_res_irq_err;
+	}
+
+	data->mpp7_virq = irq_res->start;
+	writel(0x0, data->base + IRQ_MASK_REG);
+	writel(0x0, data->bridge + IRQ_MASK_REG);
+	writel(0x0, data->bridge);
+
+	irq_chip = &data->irq_chip;
+	irq_chip->name = dev_name(&pdev->dev);
+	irq_chip->irq_mask = ts7800_irq_mask;
+	irq_chip->irq_unmask = ts7800_irq_unmask;
+
+	data->domain = irq_domain_add_linear(pdev->dev.of_node, TS7800_IRQ_NUM,
+					     &ts7800_ic_ops, data);
+	if (IS_ERR_OR_NULL(data->domain)) {
+		dev_err(&pdev->dev, "cannot add IRQ domain\n");
+		ret = PTR_ERR(data->domain);
+		goto irq_domain_add_linear_err;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(enabled_mappings); ++i) {
+		irqdomain =
+			irq_create_mapping(data->domain, enabled_mappings[i]);
+	}
+
+	irq_set_chained_handler_and_data(data->mpp7_virq,
+					 ts7800_ic_chained_handle_irq, data);
+
+	irq_set_status_flags(data->mpp7_virq,
+			     IRQ_DISABLE_UNLAZY | IRQ_LEVEL | IRQ_NOPROBE);
+
+	platform_set_drvdata(pdev, data);
+
+	return 0;
+
+irq_domain_add_linear_err:
+pltfrm_get_res_irq_err:
+	devm_iounmap(&pdev->dev, data->bridge);
+
+devm_ioremap_res_brdge_err:
+pltfrm_get_res_brdg_err:
+	devm_iounmap(&pdev->dev, data->base);
+
+devm_ioremap_res_mem_err:
+pltfrm_get_res_mem_err:
+	devm_kfree(&pdev->dev, data);
+
+devm_kzalloc_err:
+	return ret;
+}
+
+static int ts7800_ic_remove(struct platform_device *pdev)
+{
+	struct ts7800_irq_data *data = platform_get_drvdata(pdev);
+	int i;
+
+	if (!IS_ERR_OR_NULL(data)) {
+		irq_set_chained_handler_and_data(data->mpp7_virq, NULL, NULL);
+
+		for (i = 0; i < ARRAY_SIZE(enabled_mappings); ++i)
+			irq_dispose_mapping(irq_find_mapping(
+				data->domain, enabled_mappings[i]));
+
+		irq_domain_remove(data->domain);
+	}
+
+	return 0;
+}
+
+static const struct platform_device_id ts7800v1_ic_ids[] = {
+	{
+		.name = DRIVER_NAME,
+	},
+	{
+		/* sentinel */
+	}
+};
+
+MODULE_DEVICE_TABLE(platform, ts7800v1_ic_ids);
+
+static struct platform_driver ts7800_ic_driver = {
+	.probe  = ts7800_ic_probe,
+	.remove = ts7800_ic_remove,
+	.id_table	= ts7800v1_ic_ids,
+	.driver = {
+		.name = DRIVER_NAME,
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+};
+module_platform_driver(ts7800_ic_driver);
+
+MODULE_ALIAS("platform:ts7800-irqc");
+MODULE_DESCRIPTION("TS-7800v1 FPGA based IRQ controller Driver");
+MODULE_AUTHOR("Firas Ashkar <firas.ashkar@savoirfairelinux.com>");
+MODULE_LICENSE("GPL");