ICP DAS LP-8x4x contains FPGA chip. The chip functions as a interrupt
source providing 16 additional interrupts among other things. The
interrupt lines are muxed to a GPIO pin. GPIO pins are in turn muxed
to a CPU interrupt line.
Until pxa is completely converted to device tree, it is impossible
to use IRQCHIP_DECLARE() and the irqdomain needs to added manually.

Signed-off-by: Sergei Ianovich <ynv...@gmail.com>
CC: Arnd Bergmann <a...@arndb.de>
CC: Linus Walleij <linus.wall...@linaro.org>
---
   v2..v3
   * no changes (except number 09/16 -> 10/21)

   v0..v2
   * extract irqchip and move to drivers/irqchip/
   * use device tree
   * use devm helpers where possible

 .../bindings/interrupt-controller/irq-lp8x4x.txt   |  49 +++++
 arch/arm/boot/dts/pxa27x-lp8x4x.dts                |  10 +
 drivers/irqchip/Kconfig                            |   5 +
 drivers/irqchip/Makefile                           |   1 +
 drivers/irqchip/irq-lp8x4x.c                       | 205 +++++++++++++++++++++
 5 files changed, 270 insertions(+)
 create mode 100644 
Documentation/devicetree/bindings/interrupt-controller/irq-lp8x4x.txt
 create mode 100644 drivers/irqchip/irq-lp8x4x.c

diff --git 
a/Documentation/devicetree/bindings/interrupt-controller/irq-lp8x4x.txt 
b/Documentation/devicetree/bindings/interrupt-controller/irq-lp8x4x.txt
new file mode 100644
index 0000000..c8940d2
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/irq-lp8x4x.txt
@@ -0,0 +1,49 @@
+ICP DAS LP-8x4x FPGA Interrupt Controller
+
+ICP DAS LP-8x4x contains FPGA chip. The chip functions as a interrupt
+source providing 16 additional interrupts among other things.
+
+Required properties:
+- compatible : should be "icpdas,irq-lp8x4x"
+
+- reg: physical base address of the controller and length of memory mapped
+  region.
+
+- interrupt-controller : identifies the node as an interrupt controller
+
+- #interrupt-cells : should be 1
+
+- interrupts : should provide interrupt
+
+- interrupt-parent : should provide a link to interrupt controller either
+                    explicitly and implicitly from a parent node
+
+Example:
+
+       fpga: fpga@17000006 {
+               compatible = "icpdas,irq-lp8x4x";
+               reg = <0x17000006 0x16>;
+               interrupt-parent = <&gpio>;
+               interrupts = <3 IRQ_TYPE_EDGE_RISING>;
+               #interrupt-cells = <1>;
+               interrupt-controller;
+               status = "okay";
+       };
+
+       uart@17009050 {
+               compatible = "icpdas,uart-lp8x4x";
+               reg = <0x17009050 0x10
+                      0x17009030 0x02>;
+               interrupt-parent = <&fpga>;
+               interrupts = <13>;
+               status = "okay";
+       };
+
+       uart@17009060 {
+               compatible = "icpdas,uart-lp8x4x";
+               reg = <0x17009060 0x10
+                      0x17009032 0x02>;
+               interrupt-parent = <&fpga>;
+               interrupts = <14>;
+               status = "okay";
+       };
diff --git a/arch/arm/boot/dts/pxa27x-lp8x4x.dts 
b/arch/arm/boot/dts/pxa27x-lp8x4x.dts
index 07856e0..78dfd2e 100644
--- a/arch/arm/boot/dts/pxa27x-lp8x4x.dts
+++ b/arch/arm/boot/dts/pxa27x-lp8x4x.dts
@@ -167,6 +167,16 @@
                                reg = <0xa000 0x1000
                                       0x901e 0x1>;
                        };
+
+                       fpgairq: irq@9006 {
+                               compatible = "icpdas,irq-lp8x4x";
+                               reg = <0x9006 0x16>;
+                               interrupt-parent = <&gpio>;
+                               interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
+                               #interrupt-cells = <1>;
+                               interrupt-controller;
+                               status = "okay";
+                       };
                };
        };
 };
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 3792a1a..7e22729 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -35,6 +35,11 @@ config IMGPDC_IRQ
        select GENERIC_IRQ_CHIP
        select IRQ_DOMAIN
 
+config LP8X4X_IRQ
+       bool
+       depends on OF
+       select IRQ_DOMAIN
+
 config ORION_IRQCHIP
        bool
        select IRQ_DOMAIN
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index c60b901..8a28927 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_RENESAS_IRQC)            += irq-renesas-irqc.o
 obj-$(CONFIG_VERSATILE_FPGA_IRQ)       += irq-versatile-fpga.o
 obj-$(CONFIG_ARCH_VT8500)              += irq-vt8500.o
 obj-$(CONFIG_TB10X_IRQC)               += irq-tb10x.o
+obj-$(CONFIG_LP8X4X_IRQ)               += irq-lp8x4x.o
diff --git a/drivers/irqchip/irq-lp8x4x.c b/drivers/irqchip/irq-lp8x4x.c
new file mode 100644
index 0000000..5d44880b
--- /dev/null
+++ b/drivers/irqchip/irq-lp8x4x.c
@@ -0,0 +1,205 @@
+/*
+ *  linux/drivers/irqchip/irq-lp8x4x.c
+ *
+ *  Support for ICP DAS LP-8x4x FPGA irq
+ *  Copyright (C) 2013 Sergei Ianovich <ynv...@gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation or any later version.
+ */
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#define EOI                    0x00000000
+#define INSINT                 0x00000002
+#define ENSYSINT               0x00000004
+#define PRIMINT                        0x00000006
+#define SECOINT                        0x00000008
+#define ENRISEINT              0x0000000A
+#define CLRRISEINT             0x0000000C
+#define ENHILVINT              0x0000000E
+#define CLRHILVINT             0x00000010
+#define ENFALLINT              0x00000012
+#define CLRFALLINT             0x00000014
+#define LP8X4X_IRQ_MEM_SIZE    0x00000016
+
+static unsigned char irq_sys_enabled;
+static unsigned char irq_high_enabled;
+static void *base;
+static int irq_base;
+static int num_irq = 16;
+
+static void lp8x4x_mask_irq(struct irq_data *d)
+{
+       unsigned mask;
+       int irq = d->irq - irq_base;
+
+       if (irq < 0 || irq > 15) {
+               pr_err("lp8x4x: wrong irq handler for irq %i\n", d->irq);
+               return;
+       }
+
+       if (irq < 8) {
+               irq_high_enabled &= ~(1 << irq);
+
+               mask = ioread8(base + ENHILVINT);
+               mask &= ~(1 << irq);
+               iowrite8(mask, base + ENHILVINT);
+       } else {
+               irq -= 8;
+               irq_sys_enabled &= ~(1 << irq);
+
+               mask = ioread8(base + ENSYSINT);
+               mask &= ~(1 << irq);
+               iowrite8(mask, base + ENSYSINT);
+       }
+}
+
+static void lp8x4x_unmask_irq(struct irq_data *d)
+{
+       unsigned mask;
+       int irq = d->irq - irq_base;
+
+       if (irq < 0 || irq > 15) {
+               pr_err("wrong irq handler for irq %i\n", d->irq);
+               return;
+       }
+
+       if (irq < 8) {
+               irq_high_enabled |= 1 << irq;
+               mask = ioread8(base + CLRHILVINT);
+               mask |= 1 << irq;
+               iowrite8(mask, base + CLRHILVINT);
+
+               mask = ioread8(base + ENHILVINT);
+               mask |= 1 << irq;
+               iowrite8(mask, base + ENHILVINT);
+       } else {
+               irq -= 8;
+               irq_sys_enabled |= 1 << irq;
+
+               mask = ioread8(base + SECOINT);
+               mask |= 1 << irq;
+               iowrite8(mask, base + SECOINT);
+
+               mask = ioread8(base + ENSYSINT);
+               mask |= 1 << irq;
+               iowrite8(mask, base + ENSYSINT);
+       }
+}
+
+static struct irq_chip lp8x4x_irq_chip = {
+       .name                   = "FPGA",
+       .irq_ack                = lp8x4x_mask_irq,
+       .irq_mask               = lp8x4x_mask_irq,
+       .irq_mask_ack           = lp8x4x_mask_irq,
+       .irq_unmask             = lp8x4x_unmask_irq,
+};
+
+static void lp8x4x_irq_handler(unsigned int irq, struct irq_desc *desc)
+{
+       int loop, n;
+       unsigned long mask;
+       struct irq_chip *chip = irq_desc_get_chip(desc);
+
+       chained_irq_enter(chip, desc);
+
+       do {
+               loop = 0;
+               mask = ioread8(base + CLRHILVINT) & 0xff;
+               mask |= (ioread8(base + SECOINT) & 0x1f) << 8;
+               mask |= (ioread8(base + PRIMINT) & 0xe0) << 8;
+               mask &= (irq_high_enabled | (irq_sys_enabled << 8));
+               for_each_set_bit(n, &mask, BITS_PER_LONG) {
+                       loop = 1;
+
+                       generic_handle_irq(irq_base + n);
+               }
+       } while (loop);
+
+       iowrite8(0, base + EOI);
+       chained_irq_exit(chip, desc);
+}
+
+static int lp8x4x_irq_domain_map(struct irq_domain *d, unsigned int irq,
+                                irq_hw_number_t hw)
+{
+       irq_set_chip_and_handler(irq, &lp8x4x_irq_chip,
+                                handle_level_irq);
+       set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+       return 0;
+}
+
+const struct irq_domain_ops lp8x4x_irq_domain_ops = {
+       .map    = lp8x4x_irq_domain_map,
+       .xlate  = irq_domain_xlate_onecell,
+};
+
+static struct of_device_id lp8x4x_irq_dt_ids[] = {
+       { .compatible = "icpdas,irq-lp8x4x", },
+       {}
+};
+
+static int lp8x4x_irq_probe(struct platform_device *pdev)
+{
+       struct resource *rm, *ri;
+       struct device_node *np = pdev->dev.of_node;
+       struct irq_domain *domain;
+
+       rm = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       ri = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (!rm || !ri || resource_size(rm) < LP8X4X_IRQ_MEM_SIZE)
+               return -ENODEV;
+
+       base = devm_ioremap_resource(&pdev->dev, rm);
+       if (!base) {
+               dev_err(&pdev->dev, "Failed to ioremap %p\n", base);
+               return -EFAULT;
+       }
+
+       irq_base = irq_alloc_descs(-1, 0, num_irq, 0);
+       if (irq_base < 0) {
+               dev_err(&pdev->dev, "Failed to allocate IRQ numbers\n");
+               return irq_base;
+       }
+
+       domain = irq_domain_add_legacy(np, num_irq, irq_base, 0,
+                                      &lp8x4x_irq_domain_ops, NULL);
+       if (!domain) {
+               dev_err(&pdev->dev, "Failed to add IRQ domain\n");
+               return -ENOMEM;
+       }
+
+       iowrite8(0, base + CLRRISEINT);
+       iowrite8(0, base + ENRISEINT);
+       iowrite8(0, base + CLRFALLINT);
+       iowrite8(0, base + ENFALLINT);
+       iowrite8(0, base + CLRHILVINT);
+       iowrite8(0, base + ENHILVINT);
+       iowrite8(0, base + ENSYSINT);
+       iowrite8(0, base + SECOINT);
+
+       irq_set_chained_handler(ri->start, lp8x4x_irq_handler);
+
+       return 0;
+}
+
+static struct platform_driver lp8x4x_irq_driver = {
+       .probe          = lp8x4x_irq_probe,
+       .driver         = {
+               .name   = "irq-lp8x4x",
+               .of_match_table = lp8x4x_irq_dt_ids,
+       },
+};
+
+static int __init lp8x4x_irq_init(void)
+{
+       return platform_driver_register(&lp8x4x_irq_driver);
+}
+postcore_initcall(lp8x4x_irq_init);
-- 
1.8.4.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to