[PATCH v2] irqchip: add TS7800v1 fpga based controller driver

Firas Ashkar
2022-10-20T14:14:31Z

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
$

$ 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>
---

Notes:
Changes in V2
* limit the commit message
* format comments in source code
* use raw spin locks to protect mask/unmask ops
* use 'handle_edge_irq' and 'irq_ack' logic
* remove 'irq_domain_xlate_onecell'
* remove unnecessary status flags
* use 'builtin_platform_driver' helper routine

: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 e975607fa4d5 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 | 347 +++++++++++++++++++++++++++
5 files changed, 416 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;
}
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..e975607fa4d5
--- /dev/null
+++ b/drivers/irqchip/irq-ts7800.c
@@ -0,0 +1,347 @@
+// 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, 0x1 };
+
+struct ts7800_irq_data {
+ int mpp7_virq;
+ raw_spinlock_t lock;
+ 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;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&data->lock, flags);
+ writel(fpga_mask_reg & ~mask_bits, data->base + IRQ_MASK_REG);
+ writel(fpga_mask_reg & ~mask_bits, data->bridge + IRQ_MASK_REG);
+ raw_spin_unlock_irqrestore(&data->lock, flags);
+}
+
+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;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&data->lock, flags);
+ writel(fpga_mask_reg | mask_bits, data->base + IRQ_MASK_REG);
+ writel(fpga_mask_reg | mask_bits, data->bridge + IRQ_MASK_REG);
+ raw_spin_unlock_irqrestore(&data->lock, flags);
+}
+
+static void ts7800_irq_ack(struct irq_data *d)
+{
+ struct ts7800_irq_data *data = irq_data_get_irq_chip_data(d);
+ u32 status = readl(data->bridge);
+ u32 mask_bits = 1 << d->hwirq;
+ unsigned int bit = __ffs(mask_bits);
+ unsigned long flags;
+
+ status &= ~(1 << bit);
+
+ raw_spin_lock_irqsave(&data->lock, flags);
+ writel(status, data->bridge);
+ raw_spin_unlock_irqrestore(&data->lock, flags);
+}
+
+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_edge_irq);
+ irq_clear_status_flags(irq, IRQ_LEVEL);
+ irq_set_chip_data(irq, data);
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static void ts7800_irqdomain_unmap(struct irq_domain *d, unsigned int irq)
+{
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops ts7800_ic_ops = {
+ .map = ts7800_irqdomain_map,
+ .unmap = ts7800_irqdomain_unmap
+};
+
+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_MASK_REG);
+ u32 status = readl(data->bridge);
+
+ 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);
+
+ if (irq && (mask_bits & BIT(bit)))
+ generic_handle_irq(irq);
+
+ status &= ~(1 << bit);
+
+ } while (status);
+
+out:
+ chained_irq_exit(chip, desc);
+}
+
+static int ts7800_ic_probe(struct platform_device *pdev)
+{
+ struct ts7800_irq_data *data = 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;
+ }
+
+ raw_spin_lock_init(&data->lock);
+ data->mpp7_virq = irq_res->start;
+ data->irq_chip.name = dev_name(&pdev->dev);
+ data->irq_chip.irq_mask = ts7800_irq_mask;
+ data->irq_chip.irq_unmask = ts7800_irq_unmask;
+ data->irq_chip.irq_ack = ts7800_irq_ack;
+
+ writel(0x0, data->bridge);
+ writel(0x0, data->bridge + IRQ_MASK_REG);
+ writel(0x0, data->base + IRQ_MASK_REG);
+
+ 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);
+
+ 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,
+ },
+};
+builtin_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");
--
2.34.1