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);
262