aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/irqchip
diff options
context:
space:
mode:
authorMagnus Damm <damm@opensource.se>2013-02-27 03:15:01 -0500
committerSimon Horman <horms+renesas@verge.net.au>2013-03-18 08:26:06 -0400
commitfbc83b7f59dd8ed1154286b6de00b6d03c24a3c4 (patch)
tree3b1159be1b1ccc7f42516c52d73228969d289e35 /drivers/irqchip
parent0ca8712285e9e762ce4f5faf9f803b52e48c6837 (diff)
irqchip: Renesas IRQC driver
This patch adds a driver for external IRQ pins connected to the IRQC hardware block on recent SoCs from Renesas. The IRQC hardware block is used together with more recent ARM based SoCs using the GIC. As usual the GIC requires external IRQ trigger setup somewhere else which in this particular case happens to be IRQC. This driver implements the glue code needed to configure IRQ trigger and also handle mask/unmask and demux of external IRQ pins hooked up from the IRQC to the GIC. Tested on r8a73a4 but is designed to work with a wide range of SoCs. The driver requires one GIC SPI per external IRQ pin to operate. Each driver instance will handle up to 32 external IRQ pins. The SoCs using this driver are currently mainly used together with regular platform devices so this driver allows configuration via platform data to support things like static interrupt base address. DT support will be added incrementally in the not so distant future. Signed-off-by: Magnus Damm <damm@opensource.se> Tested-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de> Signed-off-by: Simon Horman <horms+renesas@verge.net.au>
Diffstat (limited to 'drivers/irqchip')
-rw-r--r--drivers/irqchip/Kconfig4
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-renesas-irqc.c298
3 files changed, 303 insertions, 0 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 0f5f1c3825bc..4a33351c25dc 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -29,6 +29,10 @@ config RENESAS_INTC_IRQPIN
29 bool 29 bool
30 select IRQ_DOMAIN 30 select IRQ_DOMAIN
31 31
32config RENESAS_IRQC
33 bool
34 select IRQ_DOMAIN
35
32config VERSATILE_FPGA_IRQ 36config VERSATILE_FPGA_IRQ
33 bool 37 bool
34 select IRQ_DOMAIN 38 select IRQ_DOMAIN
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 1aaa4073ab60..e41ceb9bec22 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -9,4 +9,5 @@ obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o
9obj-$(CONFIG_ARM_GIC) += irq-gic.o 9obj-$(CONFIG_ARM_GIC) += irq-gic.o
10obj-$(CONFIG_ARM_VIC) += irq-vic.o 10obj-$(CONFIG_ARM_VIC) += irq-vic.o
11obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o 11obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o
12obj-$(CONFIG_RENESAS_IRQC) += irq-renesas-irqc.o
12obj-$(CONFIG_VERSATILE_FPGA_IRQ) += irq-versatile-fpga.o 13obj-$(CONFIG_VERSATILE_FPGA_IRQ) += irq-versatile-fpga.o
diff --git a/drivers/irqchip/irq-renesas-irqc.c b/drivers/irqchip/irq-renesas-irqc.c
new file mode 100644
index 000000000000..95d69bfac982
--- /dev/null
+++ b/drivers/irqchip/irq-renesas-irqc.c
@@ -0,0 +1,298 @@
1/*
2 * Renesas IRQC Driver
3 *
4 * Copyright (C) 2013 Magnus Damm
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20#include <linux/init.h>
21#include <linux/platform_device.h>
22#include <linux/spinlock.h>
23#include <linux/interrupt.h>
24#include <linux/ioport.h>
25#include <linux/io.h>
26#include <linux/irq.h>
27#include <linux/irqdomain.h>
28#include <linux/err.h>
29#include <linux/slab.h>
30#include <linux/module.h>
31#include <linux/platform_data/irq-renesas-irqc.h>
32
33#define IRQC_IRQ_MAX 32 /* maximum 32 interrupts per driver instance */
34
35#define IRQC_REQ_STS 0x00
36#define IRQC_EN_STS 0x04
37#define IRQC_EN_SET 0x08
38#define IRQC_INT_CPU_BASE(n) (0x000 + ((n) * 0x10))
39#define DETECT_STATUS 0x100
40#define IRQC_CONFIG(n) (0x180 + ((n) * 0x04))
41
42struct irqc_irq {
43 int hw_irq;
44 int requested_irq;
45 int domain_irq;
46 struct irqc_priv *p;
47};
48
49struct irqc_priv {
50 void __iomem *iomem;
51 void __iomem *cpu_int_base;
52 struct irqc_irq irq[IRQC_IRQ_MAX];
53 struct renesas_irqc_config config;
54 unsigned int number_of_irqs;
55 struct platform_device *pdev;
56 struct irq_chip irq_chip;
57 struct irq_domain *irq_domain;
58};
59
60static void irqc_dbg(struct irqc_irq *i, char *str)
61{
62 dev_dbg(&i->p->pdev->dev, "%s (%d:%d:%d)\n",
63 str, i->requested_irq, i->hw_irq, i->domain_irq);
64}
65
66static void irqc_irq_enable(struct irq_data *d)
67{
68 struct irqc_priv *p = irq_data_get_irq_chip_data(d);
69 int hw_irq = irqd_to_hwirq(d);
70
71 irqc_dbg(&p->irq[hw_irq], "enable");
72 iowrite32(BIT(hw_irq), p->cpu_int_base + IRQC_EN_SET);
73}
74
75static void irqc_irq_disable(struct irq_data *d)
76{
77 struct irqc_priv *p = irq_data_get_irq_chip_data(d);
78 int hw_irq = irqd_to_hwirq(d);
79
80 irqc_dbg(&p->irq[hw_irq], "disable");
81 iowrite32(BIT(hw_irq), p->cpu_int_base + IRQC_EN_STS);
82}
83
84#define INTC_IRQ_SENSE_VALID 0x10
85#define INTC_IRQ_SENSE(x) (x + INTC_IRQ_SENSE_VALID)
86
87static unsigned char irqc_sense[IRQ_TYPE_SENSE_MASK + 1] = {
88 [IRQ_TYPE_LEVEL_LOW] = INTC_IRQ_SENSE(0x01),
89 [IRQ_TYPE_LEVEL_HIGH] = INTC_IRQ_SENSE(0x02),
90 [IRQ_TYPE_EDGE_FALLING] = INTC_IRQ_SENSE(0x04), /* Synchronous */
91 [IRQ_TYPE_EDGE_RISING] = INTC_IRQ_SENSE(0x08), /* Synchronous */
92 [IRQ_TYPE_EDGE_BOTH] = INTC_IRQ_SENSE(0x0c), /* Synchronous */
93};
94
95static int irqc_irq_set_type(struct irq_data *d, unsigned int type)
96{
97 struct irqc_priv *p = irq_data_get_irq_chip_data(d);
98 int hw_irq = irqd_to_hwirq(d);
99 unsigned char value = irqc_sense[type & IRQ_TYPE_SENSE_MASK];
100 unsigned long tmp;
101
102 irqc_dbg(&p->irq[hw_irq], "sense");
103
104 if (!(value & INTC_IRQ_SENSE_VALID))
105 return -EINVAL;
106
107 tmp = ioread32(p->iomem + IRQC_CONFIG(hw_irq));
108 tmp &= ~0x3f;
109 tmp |= value ^ INTC_IRQ_SENSE_VALID;
110 iowrite32(tmp, p->iomem + IRQC_CONFIG(hw_irq));
111 return 0;
112}
113
114static irqreturn_t irqc_irq_handler(int irq, void *dev_id)
115{
116 struct irqc_irq *i = dev_id;
117 struct irqc_priv *p = i->p;
118 unsigned long bit = BIT(i->hw_irq);
119
120 irqc_dbg(i, "demux1");
121
122 if (ioread32(p->iomem + DETECT_STATUS) & bit) {
123 iowrite32(bit, p->iomem + DETECT_STATUS);
124 irqc_dbg(i, "demux2");
125 generic_handle_irq(i->domain_irq);
126 return IRQ_HANDLED;
127 }
128 return IRQ_NONE;
129}
130
131static int irqc_irq_domain_map(struct irq_domain *h, unsigned int virq,
132 irq_hw_number_t hw)
133{
134 struct irqc_priv *p = h->host_data;
135
136 p->irq[hw].domain_irq = virq;
137 p->irq[hw].hw_irq = hw;
138
139 irqc_dbg(&p->irq[hw], "map");
140 irq_set_chip_data(virq, h->host_data);
141 irq_set_chip_and_handler(virq, &p->irq_chip, handle_level_irq);
142 set_irq_flags(virq, IRQF_VALID); /* kill me now */
143 return 0;
144}
145
146static struct irq_domain_ops irqc_irq_domain_ops = {
147 .map = irqc_irq_domain_map,
148};
149
150static int irqc_probe(struct platform_device *pdev)
151{
152 struct renesas_irqc_config *pdata = pdev->dev.platform_data;
153 struct irqc_priv *p;
154 struct resource *io;
155 struct resource *irq;
156 struct irq_chip *irq_chip;
157 const char *name = dev_name(&pdev->dev);
158 int ret;
159 int k;
160
161 p = kzalloc(sizeof(*p), GFP_KERNEL);
162 if (!p) {
163 dev_err(&pdev->dev, "failed to allocate driver data\n");
164 ret = -ENOMEM;
165 goto err0;
166 }
167
168 /* deal with driver instance configuration */
169 if (pdata)
170 memcpy(&p->config, pdata, sizeof(*pdata));
171
172 p->pdev = pdev;
173 platform_set_drvdata(pdev, p);
174
175 /* get hold of manadatory IOMEM */
176 io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
177 if (!io) {
178 dev_err(&pdev->dev, "not enough IOMEM resources\n");
179 ret = -EINVAL;
180 goto err1;
181 }
182
183 /* allow any number of IRQs between 1 and IRQC_IRQ_MAX */
184 for (k = 0; k < IRQC_IRQ_MAX; k++) {
185 irq = platform_get_resource(pdev, IORESOURCE_IRQ, k);
186 if (!irq)
187 break;
188
189 p->irq[k].p = p;
190 p->irq[k].requested_irq = irq->start;
191 }
192
193 p->number_of_irqs = k;
194 if (p->number_of_irqs < 1) {
195 dev_err(&pdev->dev, "not enough IRQ resources\n");
196 ret = -EINVAL;
197 goto err1;
198 }
199
200 /* ioremap IOMEM and setup read/write callbacks */
201 p->iomem = ioremap_nocache(io->start, resource_size(io));
202 if (!p->iomem) {
203 dev_err(&pdev->dev, "failed to remap IOMEM\n");
204 ret = -ENXIO;
205 goto err2;
206 }
207
208 p->cpu_int_base = p->iomem + IRQC_INT_CPU_BASE(0); /* SYS-SPI */
209
210 irq_chip = &p->irq_chip;
211 irq_chip->name = name;
212 irq_chip->irq_mask = irqc_irq_disable;
213 irq_chip->irq_unmask = irqc_irq_enable;
214 irq_chip->irq_enable = irqc_irq_enable;
215 irq_chip->irq_disable = irqc_irq_disable;
216 irq_chip->irq_set_type = irqc_irq_set_type;
217 irq_chip->flags = IRQCHIP_SKIP_SET_WAKE;
218
219 p->irq_domain = irq_domain_add_simple(pdev->dev.of_node,
220 p->number_of_irqs,
221 p->config.irq_base,
222 &irqc_irq_domain_ops, p);
223 if (!p->irq_domain) {
224 ret = -ENXIO;
225 dev_err(&pdev->dev, "cannot initialize irq domain\n");
226 goto err2;
227 }
228
229 /* request interrupts one by one */
230 for (k = 0; k < p->number_of_irqs; k++) {
231 if (request_irq(p->irq[k].requested_irq, irqc_irq_handler,
232 0, name, &p->irq[k])) {
233 dev_err(&pdev->dev, "failed to request IRQ\n");
234 ret = -ENOENT;
235 goto err3;
236 }
237 }
238
239 dev_info(&pdev->dev, "driving %d irqs\n", p->number_of_irqs);
240
241 /* warn in case of mismatch if irq base is specified */
242 if (p->config.irq_base) {
243 if (p->config.irq_base != p->irq[0].domain_irq)
244 dev_warn(&pdev->dev, "irq base mismatch (%d/%d)\n",
245 p->config.irq_base, p->irq[0].domain_irq);
246 }
247
248 return 0;
249err3:
250 for (; k >= 0; k--)
251 free_irq(p->irq[k - 1].requested_irq, &p->irq[k - 1]);
252
253 irq_domain_remove(p->irq_domain);
254err2:
255 iounmap(p->iomem);
256err1:
257 kfree(p);
258err0:
259 return ret;
260}
261
262static int irqc_remove(struct platform_device *pdev)
263{
264 struct irqc_priv *p = platform_get_drvdata(pdev);
265 int k;
266
267 for (k = 0; k < p->number_of_irqs; k++)
268 free_irq(p->irq[k].requested_irq, &p->irq[k]);
269
270 irq_domain_remove(p->irq_domain);
271 iounmap(p->iomem);
272 kfree(p);
273 return 0;
274}
275
276static struct platform_driver irqc_device_driver = {
277 .probe = irqc_probe,
278 .remove = irqc_remove,
279 .driver = {
280 .name = "renesas_irqc",
281 }
282};
283
284static int __init irqc_init(void)
285{
286 return platform_driver_register(&irqc_device_driver);
287}
288postcore_initcall(irqc_init);
289
290static void __exit irqc_exit(void)
291{
292 platform_driver_unregister(&irqc_device_driver);
293}
294module_exit(irqc_exit);
295
296MODULE_AUTHOR("Magnus Damm");
297MODULE_DESCRIPTION("Renesas IRQC Driver");
298MODULE_LICENSE("GPL v2");