[PATCH v6] arm: pxa: support ICP DAS LP-8x4x FPGA irq

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




ICP DAS LP-8x4x contains FPGA chip. The chip functions as an interrupt
source providing 16 additional interrupts among other things. The
interrupt lines are muxed to a GPIO pin of a 2nd level PXA-GPIO
interrupt controller. GPIO pins of the 2nd level controller 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.
Drivers for the on-CPU IRQs and GPIO-IRQs are loaded using
postcore_initcall(). We need to have all irq domain drivers loaded prior
to DT parsing in order to allow normal initialization of IRQ resources
with DT.

Signed-off-by: Sergei Ianovich <ynvich@xxxxxxxxx>
Reviewed-by: Linus Walleij <linus.walleij@xxxxxxxxxx>
CC: Arnd Bergmann <arnd@xxxxxxxx>
CC: Rob Herring <robh@xxxxxxxxxx>
CC: Marc Zyngier <marc.zyngier@xxxxxxx>

   v5..v6
   fixes according to Rob Herring review comments:
   * drop wildcards in binding and file names
   * rename alias 'fpga'->'fpga_irq'

   fixes according to Marc Zyngier review comments:
   * use writeb/readb not iowrite8/ioread8
   * redefine 2nd mask using inversion on 1st
   * add comments

   v4..v5
   * constify struct of_device_id
   * drop irq number from handler signature

   v3.2..v4
   * move DTS binding to a different patch (8/21)

   v3.1..v3.2
   fixes to apply Linus Walleij's "Reviewed-by":
   * add kerneldoc comment for state container struct
   * rename irq -> hwirq for clarity
   * drop overzealous error checks from the hotpaths

   v3..v3.1
   fixes according to Linus Walleij review comments:
   * update commit message
   * use state container instead of global variables
   * get hardware irq nums from irq_data, don't calculate them
   * use BIT() macro
   * add defines for system irq register masks
   * replace cycle control variable with break
   * use better names for resource variables
   * add a linear domain instead of a legacy one
   * use irq_create_mapping() instead of irq_alloc_desc()

   v2..v3
   * no changes (except number 09/16 -> 11/21)

   v0..v2
   * extract irqchip and move to drivers/irqchip/
   * use device tree
   * use devm helpers where possible
---
 .../interrupt-controller/icpdas-lp8x4x-irq.txt     |  49 ++++
 drivers/irqchip/Kconfig                            |   5 +
 drivers/irqchip/Makefile                           |   1 +
 drivers/irqchip/irq-lp8841.c                       | 246 +++++++++++++++++++++
 4 files changed, 301 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/interrupt-controller/icpdas-lp8x4x-irq.txt
 create mode 100644 drivers/irqchip/irq-lp8841.c

diff --git a/Documentation/devicetree/bindings/interrupt-controller/icpdas-lp8x4x-irq.txt b/Documentation/devicetree/bindings/interrupt-controller/icpdas-lp8x4x-irq.txt
new file mode 100644
index 0000000..a72109b
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/icpdas-lp8x4x-irq.txt
@@ -0,0 +1,49 @@
+ICP DAS LP-8841 FPGA Interrupt Controller
+
+ICP DAS LP-8841 contains FPGA chip. The chip functions as a interrupt
+source providing 16 additional interrupts among other things.
+
+Required properties:
+- compatible : should be "icpdas,lp8x4x-irq"
+
+- 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
+
+Optional properties:
+
+- interrupt-parent : should provide a link to interrupt controller either
+		     explicitly and implicitly from a parent node
+
+Example:
+
+fpgairq: irq@9006 {
+	compatible = "icpdas,lp8841-irq";
+	reg = <0x9006 0x16>;
+	interrupt-parent = <&gpio>;
+	interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
+	#interrupt-cells = <1>;
+	interrupt-controller;
+	status = "okay";
+};
+
+serial@9050 {
+	compatible = "icpdas,lp8841-uart";
+	reg =  <0x9050 0x10
+		0x9030 0x02>;
+	interrupts = <13>;
+	status = "okay";
+};
+
+serial@9060 {
+	compatible = "icpdas,lp8841-uart";
+	reg =  <0x9060 0x10
+		0x9032 0x02>;
+	interrupts = <14>;
+	status = "okay";
+};
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index fb50911..786073b 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -218,3 +218,8 @@ config IRQ_MXS
 	def_bool y if MACH_ASM9260 || ARCH_MXS
 	select IRQ_DOMAIN
 	select STMP_DEVICE
+
+config LP8841_IRQ
+	bool
+	def_bool y if MACH_PXA27X_DT
+	select IRQ_DOMAIN
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 18caacb..05313d1 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -59,3 +59,4 @@ obj-$(CONFIG_ARCH_SA1100)		+= irq-sa11x0.o
 obj-$(CONFIG_INGENIC_IRQ)		+= irq-ingenic.o
 obj-$(CONFIG_IMX_GPCV2)			+= irq-imx-gpcv2.o
 obj-$(CONFIG_PIC32_EVIC)		+= irq-pic32-evic.o
+obj-$(CONFIG_LP8841_IRQ)		+= irq-lp8841.o
diff --git a/drivers/irqchip/irq-lp8841.c b/drivers/irqchip/irq-lp8841.c
new file mode 100644
index 0000000..6a019f0
--- /dev/null
+++ b/drivers/irqchip/irq-lp8841.c
@@ -0,0 +1,246 @@
+/*
+ *  linux/drivers/irqchip/irq-lp8841.c
+ *
+ *  Support for ICP DAS LP-8841 FPGA irq
+ *  Copyright (C) 2013 Sergei Ianovich <ynvich@xxxxxxxxx>
+ *
+ *  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 MODULE_NAME		"irq-lp8841"
+
+#define EOI			0x00000000
+#define INSINT			0x00000002
+#define ENSYSINT		0x00000004
+#define PRIMINT			0x00000006
+#define PRIMINT_MASK		0xe0
+#define SECOINT			0x00000008
+#define SECOINT_MASK		(~(u8)PRIMINT_MASK)
+#define ENRISEINT		0x0000000A
+#define CLRRISEINT		0x0000000C
+#define ENHILVINT		0x0000000E
+#define CLRHILVINT		0x00000010
+#define ENFALLINT		0x00000012
+#define CLRFALLINT		0x00000014
+#define IRQ_MEM_SIZE		0x00000016
+#define LP8841_NUM_IRQ_DEFAULT	16
+
+/**
+ * struct lp8841_irq_data - LP8841 custom irq controller state container
+ * @base:               base IO memory address
+ * @irq_domain:         Interrupt translation domain; responsible for mapping
+ *                      between hwirq number and linux irq number
+ * @irq_sys_enabled:    mask keeping track of interrupts enabled in the
+ *                      register which vendor calls 'system'
+ * @irq_high_enabled:   mask keeping track of interrupts enabled in the
+ *                      register which vendor calls 'high'
+ *
+ * The structure implements State Container from
+ * Documentation/driver-model/design-patterns.txt
+ */
+
+struct lp8841_irq_data {
+	void			*base;
+	struct irq_domain	*domain;
+	unsigned char		irq_sys_enabled;
+	unsigned char		irq_high_enabled;
+};
+
+static void lp8841_mask_irq(struct irq_data *d)
+{
+	unsigned mask;
+	unsigned long hwirq = d->hwirq;
+	struct lp8841_irq_data *host = irq_data_get_irq_chip_data(d);
+
+	if (hwirq < 8) {
+		host->irq_high_enabled &= ~BIT(hwirq);
+
+		mask = readb(host->base + ENHILVINT);
+		mask &= ~BIT(hwirq);
+		writeb(mask, host->base + ENHILVINT);
+	} else {
+		hwirq -= 8;
+		host->irq_sys_enabled &= ~BIT(hwirq);
+
+		mask = readb(host->base + ENSYSINT);
+		mask &= ~BIT(hwirq);
+		writeb(mask, host->base + ENSYSINT);
+	}
+}
+
+static void lp8841_unmask_irq(struct irq_data *d)
+{
+	unsigned mask;
+	unsigned long hwirq = d->hwirq;
+	struct lp8841_irq_data *host = irq_data_get_irq_chip_data(d);
+
+	if (hwirq < 8) {
+		host->irq_high_enabled |= BIT(hwirq);
+		mask = readb(host->base + CLRHILVINT);
+		mask |= BIT(hwirq);
+		writeb(mask, host->base + CLRHILVINT);
+
+		mask = readb(host->base + ENHILVINT);
+		mask |= BIT(hwirq);
+		writeb(mask, host->base + ENHILVINT);
+	} else {
+		hwirq -= 8;
+		host->irq_sys_enabled |= BIT(hwirq);
+
+		mask = readb(host->base + SECOINT);
+		mask |= BIT(hwirq);
+		writeb(mask, host->base + SECOINT);
+
+		mask = readb(host->base + ENSYSINT);
+		mask |= BIT(hwirq);
+		writeb(mask, host->base + ENSYSINT);
+	}
+}
+
+static struct irq_chip lp8841_irq_chip = {
+	.name			= "FPGA",
+	.irq_ack		= lp8841_mask_irq,
+	.irq_mask		= lp8841_mask_irq,
+	.irq_mask_ack		= lp8841_mask_irq,
+	.irq_unmask		= lp8841_unmask_irq,
+};
+
+static void lp8841_irq_handler(struct irq_desc *desc)
+{
+	int n;
+	unsigned long mask;
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	struct lp8841_irq_data *host = irq_desc_get_handler_data(desc);
+
+	chained_irq_enter(chip, desc);
+
+	for (;;) {
+		mask = readb(host->base + CLRHILVINT) & 0xff;
+		/* load two registers into a single byte */
+		mask |= (readb(host->base + SECOINT) & SECOINT_MASK) << 8;
+		mask |= (readb(host->base + PRIMINT) & PRIMINT_MASK) << 8;
+		if (mask == 0)
+			break;
+		for_each_set_bit(n, &mask, BITS_PER_LONG)
+			generic_handle_irq(irq_find_mapping(host->domain, n));
+	}
+
+	writeb(0, host->base + EOI);
+	chained_irq_exit(chip, desc);
+}
+
+static int lp8841_irq_domain_map(struct irq_domain *d, unsigned int irq,
+				 irq_hw_number_t hw)
+{
+	struct lp8841_irq_data *host = d->host_data;
+	int err;
+
+	err = irq_set_chip_data(irq, host);
+	if (err < 0)
+		return err;
+
+	irq_set_chip_and_handler(irq, &lp8841_irq_chip, handle_level_irq);
+	irq_set_probe(irq);
+	return 0;
+}
+
+const struct irq_domain_ops lp8841_irq_domain_ops = {
+	.map	= lp8841_irq_domain_map,
+	.xlate	= irq_domain_xlate_onecell,
+};
+
+static const struct of_device_id lp8841_irq_dt_ids[] = {
+	{ .compatible = "icpdas,lp8841-irq", },
+	{}
+};
+
+/*
+ * REVISIT probing will need to rewritten when PXA is converted to DT
+ */
+
+static int lp8841_irq_probe(struct platform_device *pdev)
+{
+	struct resource *res_mem;
+	int irq;
+	struct device_node *np = pdev->dev.of_node;
+	struct lp8841_irq_data *host;
+	int i, err;
+
+	irq = platform_get_irq(pdev, 0);
+	if (IS_ERR_VALUE(irq)) {
+		dev_err(&pdev->dev, "bad irq %i\n", irq);
+		return irq;
+	}
+
+	res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res_mem || resource_size(res_mem) < IRQ_MEM_SIZE) {
+		dev_err(&pdev->dev, "bad IOmem %p\n", res_mem);
+		if (res_mem)
+			dev_err(&pdev->dev, "bad start %p or size %u\n",
+					(void *) res_mem->start,
+					resource_size(res_mem));
+		return -ENODEV;
+	}
+
+	host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
+	if (!host)
+		return -ENOMEM;
+
+	host->base = devm_ioremap_resource(&pdev->dev, res_mem);
+	if (!host->base) {
+		dev_err(&pdev->dev, "Failed to ioremap %p\n", host->base);
+		return -EFAULT;
+	}
+
+	host->domain = irq_domain_add_linear(np, LP8841_NUM_IRQ_DEFAULT,
+				       &lp8841_irq_domain_ops, host);
+	if (!host->domain) {
+		dev_err(&pdev->dev, "Failed to add IRQ domain\n");
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < LP8841_NUM_IRQ_DEFAULT; i++) {
+		err = irq_create_mapping(host->domain, i);
+		if (err < 0)
+			dev_err(&pdev->dev, "Failed to map IRQ %i\n", i);
+	}
+
+	/* Initialize chip registers */
+	writeb(0, host->base + CLRRISEINT);
+	writeb(0, host->base + ENRISEINT);
+	writeb(0, host->base + CLRFALLINT);
+	writeb(0, host->base + ENFALLINT);
+	writeb(0, host->base + CLRHILVINT);
+	writeb(0, host->base + ENHILVINT);
+	writeb(0, host->base + ENSYSINT);
+	writeb(0, host->base + SECOINT);
+
+	irq_set_handler_data(irq, host);
+	irq_set_chained_handler(irq, lp8841_irq_handler);
+
+	pr_info(MODULE_NAME ": %i IRQs\n", LP8841_NUM_IRQ_DEFAULT);
+	return 0;
+}
+
+static struct platform_driver lp8841_irq_driver = {
+	.probe		= lp8841_irq_probe,
+	.driver		= {
+		.name	= MODULE_NAME,
+		.of_match_table = lp8841_irq_dt_ids,
+	},
+};
+
+static int __init lp8841_irq_init(void)
+{
+	return platform_driver_register(&lp8841_irq_driver);
+}
+device_initcall(lp8841_irq_init);
-- 
2.7.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux