summaryrefslogtreecommitdiffstats
path: root/drivers/irqchip/irq-uniphier-aidet.c
diff options
context:
space:
mode:
authorMasahiro Yamada <yamada.masahiro@socionext.com>2017-08-22 21:31:47 -0400
committerMarc Zyngier <marc.zyngier@arm.com>2017-08-23 05:08:44 -0400
commit5ed34d3a4387c8967801688f66b90ce0c7facda0 (patch)
tree3e0a96313cc54cba2807fcd011b1275a8f8ba1bd /drivers/irqchip/irq-uniphier-aidet.c
parent9bdd8b1cdeb6a873acb1d1e915d372e3440a4179 (diff)
irqchip: Add UniPhier AIDET irqchip driver
UniPhier SoCs contain AIDET (ARM Interrupt Detector). This is intended to provide additional features that are not covered by GIC. The main purpose is to provide logic inverter to support low level and falling edge trigger types for interrupt lines from on-board devices. Acked-by: Rob Herring <robh@kernel.org> Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com> Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Diffstat (limited to 'drivers/irqchip/irq-uniphier-aidet.c')
-rw-r--r--drivers/irqchip/irq-uniphier-aidet.c261
1 files changed, 261 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-uniphier-aidet.c b/drivers/irqchip/irq-uniphier-aidet.c
new file mode 100644
index 000000000000..7ba7f253470e
--- /dev/null
+++ b/drivers/irqchip/irq-uniphier-aidet.c
@@ -0,0 +1,261 @@
1/*
2 * Driver for UniPhier AIDET (ARM Interrupt Detector)
3 *
4 * Copyright (C) 2017 Socionext Inc.
5 * Author: Masahiro Yamada <yamada.masahiro@socionext.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
17#include <linux/bitops.h>
18#include <linux/init.h>
19#include <linux/irq.h>
20#include <linux/irqdomain.h>
21#include <linux/kernel.h>
22#include <linux/of.h>
23#include <linux/of_device.h>
24#include <linux/of_irq.h>
25#include <linux/platform_device.h>
26#include <linux/spinlock.h>
27
28#define UNIPHIER_AIDET_NR_IRQS 256
29
30#define UNIPHIER_AIDET_DETCONF 0x04 /* inverter register base */
31
32struct uniphier_aidet_priv {
33 struct irq_domain *domain;
34 void __iomem *reg_base;
35 spinlock_t lock;
36 u32 saved_vals[UNIPHIER_AIDET_NR_IRQS / 32];
37};
38
39static void uniphier_aidet_reg_update(struct uniphier_aidet_priv *priv,
40 unsigned int reg, u32 mask, u32 val)
41{
42 unsigned long flags;
43 u32 tmp;
44
45 spin_lock_irqsave(&priv->lock, flags);
46 tmp = readl_relaxed(priv->reg_base + reg);
47 tmp &= ~mask;
48 tmp |= mask & val;
49 writel_relaxed(tmp, priv->reg_base + reg);
50 spin_unlock_irqrestore(&priv->lock, flags);
51}
52
53static void uniphier_aidet_detconf_update(struct uniphier_aidet_priv *priv,
54 unsigned long index, unsigned int val)
55{
56 unsigned int reg;
57 u32 mask;
58
59 reg = UNIPHIER_AIDET_DETCONF + index / 32 * 4;
60 mask = BIT(index % 32);
61
62 uniphier_aidet_reg_update(priv, reg, mask, val ? mask : 0);
63}
64
65static int uniphier_aidet_irq_set_type(struct irq_data *data, unsigned int type)
66{
67 struct uniphier_aidet_priv *priv = data->chip_data;
68 unsigned int val;
69
70 /* enable inverter for active low triggers */
71 switch (type) {
72 case IRQ_TYPE_EDGE_RISING:
73 case IRQ_TYPE_LEVEL_HIGH:
74 val = 0;
75 break;
76 case IRQ_TYPE_EDGE_FALLING:
77 val = 1;
78 type = IRQ_TYPE_EDGE_RISING;
79 break;
80 case IRQ_TYPE_LEVEL_LOW:
81 val = 1;
82 type = IRQ_TYPE_LEVEL_HIGH;
83 break;
84 default:
85 return -EINVAL;
86 }
87
88 uniphier_aidet_detconf_update(priv, data->hwirq, val);
89
90 return irq_chip_set_type_parent(data, type);
91}
92
93static struct irq_chip uniphier_aidet_irq_chip = {
94 .name = "AIDET",
95 .irq_mask = irq_chip_mask_parent,
96 .irq_unmask = irq_chip_unmask_parent,
97 .irq_eoi = irq_chip_eoi_parent,
98 .irq_set_affinity = irq_chip_set_affinity_parent,
99 .irq_set_type = uniphier_aidet_irq_set_type,
100};
101
102static int uniphier_aidet_domain_translate(struct irq_domain *domain,
103 struct irq_fwspec *fwspec,
104 unsigned long *out_hwirq,
105 unsigned int *out_type)
106{
107 if (WARN_ON(fwspec->param_count < 2))
108 return -EINVAL;
109
110 *out_hwirq = fwspec->param[0];
111 *out_type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
112
113 return 0;
114}
115
116static int uniphier_aidet_domain_alloc(struct irq_domain *domain,
117 unsigned int virq, unsigned int nr_irqs,
118 void *arg)
119{
120 struct irq_fwspec parent_fwspec;
121 irq_hw_number_t hwirq;
122 unsigned int type;
123 int ret;
124
125 if (nr_irqs != 1)
126 return -EINVAL;
127
128 ret = uniphier_aidet_domain_translate(domain, arg, &hwirq, &type);
129 if (ret)
130 return ret;
131
132 switch (type) {
133 case IRQ_TYPE_EDGE_RISING:
134 case IRQ_TYPE_LEVEL_HIGH:
135 break;
136 case IRQ_TYPE_EDGE_FALLING:
137 type = IRQ_TYPE_EDGE_RISING;
138 break;
139 case IRQ_TYPE_LEVEL_LOW:
140 type = IRQ_TYPE_LEVEL_HIGH;
141 break;
142 default:
143 return -EINVAL;
144 }
145
146 if (hwirq >= UNIPHIER_AIDET_NR_IRQS)
147 return -ENXIO;
148
149 ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
150 &uniphier_aidet_irq_chip,
151 domain->host_data);
152 if (ret)
153 return ret;
154
155 /* parent is GIC */
156 parent_fwspec.fwnode = domain->parent->fwnode;
157 parent_fwspec.param_count = 3;
158 parent_fwspec.param[0] = 0; /* SPI */
159 parent_fwspec.param[1] = hwirq;
160 parent_fwspec.param[2] = type;
161
162 return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec);
163}
164
165static const struct irq_domain_ops uniphier_aidet_domain_ops = {
166 .alloc = uniphier_aidet_domain_alloc,
167 .free = irq_domain_free_irqs_common,
168 .translate = uniphier_aidet_domain_translate,
169};
170
171static int uniphier_aidet_probe(struct platform_device *pdev)
172{
173 struct device *dev = &pdev->dev;
174 struct device_node *parent_np;
175 struct irq_domain *parent_domain;
176 struct uniphier_aidet_priv *priv;
177 struct resource *res;
178
179 parent_np = of_irq_find_parent(dev->of_node);
180 if (!parent_np)
181 return -ENXIO;
182
183 parent_domain = irq_find_host(parent_np);
184 of_node_put(parent_np);
185 if (!parent_domain)
186 return -EPROBE_DEFER;
187
188 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
189 if (!priv)
190 return -ENOMEM;
191
192 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
193 priv->reg_base = devm_ioremap_resource(dev, res);
194 if (IS_ERR(priv->reg_base))
195 return PTR_ERR(priv->reg_base);
196
197 spin_lock_init(&priv->lock);
198
199 priv->domain = irq_domain_create_hierarchy(
200 parent_domain, 0,
201 UNIPHIER_AIDET_NR_IRQS,
202 of_node_to_fwnode(dev->of_node),
203 &uniphier_aidet_domain_ops, priv);
204 if (!priv->domain)
205 return -ENOMEM;
206
207 platform_set_drvdata(pdev, priv);
208
209 return 0;
210}
211
212static int __maybe_unused uniphier_aidet_suspend(struct device *dev)
213{
214 struct uniphier_aidet_priv *priv = dev_get_drvdata(dev);
215 int i;
216
217 for (i = 0; i < ARRAY_SIZE(priv->saved_vals); i++)
218 priv->saved_vals[i] = readl_relaxed(
219 priv->reg_base + UNIPHIER_AIDET_DETCONF + i * 4);
220
221 return 0;
222}
223
224static int __maybe_unused uniphier_aidet_resume(struct device *dev)
225{
226 struct uniphier_aidet_priv *priv = dev_get_drvdata(dev);
227 int i;
228
229 for (i = 0; i < ARRAY_SIZE(priv->saved_vals); i++)
230 writel_relaxed(priv->saved_vals[i],
231 priv->reg_base + UNIPHIER_AIDET_DETCONF + i * 4);
232
233 return 0;
234}
235
236static const struct dev_pm_ops uniphier_aidet_pm_ops = {
237 SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(uniphier_aidet_suspend,
238 uniphier_aidet_resume)
239};
240
241static const struct of_device_id uniphier_aidet_match[] = {
242 { .compatible = "socionext,uniphier-ld4-aidet" },
243 { .compatible = "socionext,uniphier-pro4-aidet" },
244 { .compatible = "socionext,uniphier-sld8-aidet" },
245 { .compatible = "socionext,uniphier-pro5-aidet" },
246 { .compatible = "socionext,uniphier-pxs2-aidet" },
247 { .compatible = "socionext,uniphier-ld11-aidet" },
248 { .compatible = "socionext,uniphier-ld20-aidet" },
249 { .compatible = "socionext,uniphier-pxs3-aidet" },
250 { /* sentinel */ }
251};
252
253static struct platform_driver uniphier_aidet_driver = {
254 .probe = uniphier_aidet_probe,
255 .driver = {
256 .name = "uniphier-aidet",
257 .of_match_table = uniphier_aidet_match,
258 .pm = &uniphier_aidet_pm_ops,
259 },
260};
261builtin_platform_driver(uniphier_aidet_driver);