Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
5ed34d3a MY |
2 | /* |
3 | * Driver for UniPhier AIDET (ARM Interrupt Detector) | |
4 | * | |
5 | * Copyright (C) 2017 Socionext Inc. | |
6 | * Author: Masahiro Yamada <yamada.masahiro@socionext.com> | |
5ed34d3a MY |
7 | */ |
8 | ||
9 | #include <linux/bitops.h> | |
10 | #include <linux/init.h> | |
11 | #include <linux/irq.h> | |
12 | #include <linux/irqdomain.h> | |
13 | #include <linux/kernel.h> | |
14 | #include <linux/of.h> | |
15 | #include <linux/of_device.h> | |
16 | #include <linux/of_irq.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/spinlock.h> | |
19 | ||
20 | #define UNIPHIER_AIDET_NR_IRQS 256 | |
21 | ||
22 | #define UNIPHIER_AIDET_DETCONF 0x04 /* inverter register base */ | |
23 | ||
24 | struct uniphier_aidet_priv { | |
25 | struct irq_domain *domain; | |
26 | void __iomem *reg_base; | |
27 | spinlock_t lock; | |
28 | u32 saved_vals[UNIPHIER_AIDET_NR_IRQS / 32]; | |
29 | }; | |
30 | ||
31 | static void uniphier_aidet_reg_update(struct uniphier_aidet_priv *priv, | |
32 | unsigned int reg, u32 mask, u32 val) | |
33 | { | |
34 | unsigned long flags; | |
35 | u32 tmp; | |
36 | ||
37 | spin_lock_irqsave(&priv->lock, flags); | |
38 | tmp = readl_relaxed(priv->reg_base + reg); | |
39 | tmp &= ~mask; | |
40 | tmp |= mask & val; | |
41 | writel_relaxed(tmp, priv->reg_base + reg); | |
42 | spin_unlock_irqrestore(&priv->lock, flags); | |
43 | } | |
44 | ||
45 | static void uniphier_aidet_detconf_update(struct uniphier_aidet_priv *priv, | |
46 | unsigned long index, unsigned int val) | |
47 | { | |
48 | unsigned int reg; | |
49 | u32 mask; | |
50 | ||
51 | reg = UNIPHIER_AIDET_DETCONF + index / 32 * 4; | |
52 | mask = BIT(index % 32); | |
53 | ||
54 | uniphier_aidet_reg_update(priv, reg, mask, val ? mask : 0); | |
55 | } | |
56 | ||
57 | static int uniphier_aidet_irq_set_type(struct irq_data *data, unsigned int type) | |
58 | { | |
59 | struct uniphier_aidet_priv *priv = data->chip_data; | |
60 | unsigned int val; | |
61 | ||
62 | /* enable inverter for active low triggers */ | |
63 | switch (type) { | |
64 | case IRQ_TYPE_EDGE_RISING: | |
65 | case IRQ_TYPE_LEVEL_HIGH: | |
66 | val = 0; | |
67 | break; | |
68 | case IRQ_TYPE_EDGE_FALLING: | |
69 | val = 1; | |
70 | type = IRQ_TYPE_EDGE_RISING; | |
71 | break; | |
72 | case IRQ_TYPE_LEVEL_LOW: | |
73 | val = 1; | |
74 | type = IRQ_TYPE_LEVEL_HIGH; | |
75 | break; | |
76 | default: | |
77 | return -EINVAL; | |
78 | } | |
79 | ||
80 | uniphier_aidet_detconf_update(priv, data->hwirq, val); | |
81 | ||
82 | return irq_chip_set_type_parent(data, type); | |
83 | } | |
84 | ||
85 | static struct irq_chip uniphier_aidet_irq_chip = { | |
86 | .name = "AIDET", | |
87 | .irq_mask = irq_chip_mask_parent, | |
88 | .irq_unmask = irq_chip_unmask_parent, | |
89 | .irq_eoi = irq_chip_eoi_parent, | |
90 | .irq_set_affinity = irq_chip_set_affinity_parent, | |
91 | .irq_set_type = uniphier_aidet_irq_set_type, | |
92 | }; | |
93 | ||
94 | static int uniphier_aidet_domain_translate(struct irq_domain *domain, | |
95 | struct irq_fwspec *fwspec, | |
96 | unsigned long *out_hwirq, | |
97 | unsigned int *out_type) | |
98 | { | |
99 | if (WARN_ON(fwspec->param_count < 2)) | |
100 | return -EINVAL; | |
101 | ||
102 | *out_hwirq = fwspec->param[0]; | |
103 | *out_type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; | |
104 | ||
105 | return 0; | |
106 | } | |
107 | ||
108 | static int uniphier_aidet_domain_alloc(struct irq_domain *domain, | |
109 | unsigned int virq, unsigned int nr_irqs, | |
110 | void *arg) | |
111 | { | |
112 | struct irq_fwspec parent_fwspec; | |
113 | irq_hw_number_t hwirq; | |
114 | unsigned int type; | |
115 | int ret; | |
116 | ||
117 | if (nr_irqs != 1) | |
118 | return -EINVAL; | |
119 | ||
120 | ret = uniphier_aidet_domain_translate(domain, arg, &hwirq, &type); | |
121 | if (ret) | |
122 | return ret; | |
123 | ||
124 | switch (type) { | |
125 | case IRQ_TYPE_EDGE_RISING: | |
126 | case IRQ_TYPE_LEVEL_HIGH: | |
127 | break; | |
128 | case IRQ_TYPE_EDGE_FALLING: | |
129 | type = IRQ_TYPE_EDGE_RISING; | |
130 | break; | |
131 | case IRQ_TYPE_LEVEL_LOW: | |
132 | type = IRQ_TYPE_LEVEL_HIGH; | |
133 | break; | |
134 | default: | |
135 | return -EINVAL; | |
136 | } | |
137 | ||
138 | if (hwirq >= UNIPHIER_AIDET_NR_IRQS) | |
139 | return -ENXIO; | |
140 | ||
141 | ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, | |
142 | &uniphier_aidet_irq_chip, | |
143 | domain->host_data); | |
144 | if (ret) | |
145 | return ret; | |
146 | ||
147 | /* parent is GIC */ | |
148 | parent_fwspec.fwnode = domain->parent->fwnode; | |
149 | parent_fwspec.param_count = 3; | |
150 | parent_fwspec.param[0] = 0; /* SPI */ | |
151 | parent_fwspec.param[1] = hwirq; | |
152 | parent_fwspec.param[2] = type; | |
153 | ||
154 | return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec); | |
155 | } | |
156 | ||
157 | static const struct irq_domain_ops uniphier_aidet_domain_ops = { | |
158 | .alloc = uniphier_aidet_domain_alloc, | |
159 | .free = irq_domain_free_irqs_common, | |
160 | .translate = uniphier_aidet_domain_translate, | |
161 | }; | |
162 | ||
163 | static int uniphier_aidet_probe(struct platform_device *pdev) | |
164 | { | |
165 | struct device *dev = &pdev->dev; | |
166 | struct device_node *parent_np; | |
167 | struct irq_domain *parent_domain; | |
168 | struct uniphier_aidet_priv *priv; | |
5ed34d3a MY |
169 | |
170 | parent_np = of_irq_find_parent(dev->of_node); | |
171 | if (!parent_np) | |
172 | return -ENXIO; | |
173 | ||
174 | parent_domain = irq_find_host(parent_np); | |
175 | of_node_put(parent_np); | |
176 | if (!parent_domain) | |
177 | return -EPROBE_DEFER; | |
178 | ||
179 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
180 | if (!priv) | |
181 | return -ENOMEM; | |
182 | ||
e89327f6 | 183 | priv->reg_base = devm_platform_ioremap_resource(pdev, 0); |
5ed34d3a MY |
184 | if (IS_ERR(priv->reg_base)) |
185 | return PTR_ERR(priv->reg_base); | |
186 | ||
187 | spin_lock_init(&priv->lock); | |
188 | ||
189 | priv->domain = irq_domain_create_hierarchy( | |
190 | parent_domain, 0, | |
191 | UNIPHIER_AIDET_NR_IRQS, | |
192 | of_node_to_fwnode(dev->of_node), | |
193 | &uniphier_aidet_domain_ops, priv); | |
194 | if (!priv->domain) | |
195 | return -ENOMEM; | |
196 | ||
197 | platform_set_drvdata(pdev, priv); | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
202 | static int __maybe_unused uniphier_aidet_suspend(struct device *dev) | |
203 | { | |
204 | struct uniphier_aidet_priv *priv = dev_get_drvdata(dev); | |
205 | int i; | |
206 | ||
207 | for (i = 0; i < ARRAY_SIZE(priv->saved_vals); i++) | |
208 | priv->saved_vals[i] = readl_relaxed( | |
209 | priv->reg_base + UNIPHIER_AIDET_DETCONF + i * 4); | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | static int __maybe_unused uniphier_aidet_resume(struct device *dev) | |
215 | { | |
216 | struct uniphier_aidet_priv *priv = dev_get_drvdata(dev); | |
217 | int i; | |
218 | ||
219 | for (i = 0; i < ARRAY_SIZE(priv->saved_vals); i++) | |
220 | writel_relaxed(priv->saved_vals[i], | |
221 | priv->reg_base + UNIPHIER_AIDET_DETCONF + i * 4); | |
222 | ||
223 | return 0; | |
224 | } | |
225 | ||
226 | static const struct dev_pm_ops uniphier_aidet_pm_ops = { | |
227 | SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(uniphier_aidet_suspend, | |
228 | uniphier_aidet_resume) | |
229 | }; | |
230 | ||
231 | static const struct of_device_id uniphier_aidet_match[] = { | |
232 | { .compatible = "socionext,uniphier-ld4-aidet" }, | |
233 | { .compatible = "socionext,uniphier-pro4-aidet" }, | |
234 | { .compatible = "socionext,uniphier-sld8-aidet" }, | |
235 | { .compatible = "socionext,uniphier-pro5-aidet" }, | |
236 | { .compatible = "socionext,uniphier-pxs2-aidet" }, | |
237 | { .compatible = "socionext,uniphier-ld11-aidet" }, | |
238 | { .compatible = "socionext,uniphier-ld20-aidet" }, | |
239 | { .compatible = "socionext,uniphier-pxs3-aidet" }, | |
e3f056a7 | 240 | { .compatible = "socionext,uniphier-nx1-aidet" }, |
5ed34d3a MY |
241 | { /* sentinel */ } |
242 | }; | |
243 | ||
244 | static struct platform_driver uniphier_aidet_driver = { | |
245 | .probe = uniphier_aidet_probe, | |
246 | .driver = { | |
247 | .name = "uniphier-aidet", | |
248 | .of_match_table = uniphier_aidet_match, | |
249 | .pm = &uniphier_aidet_pm_ops, | |
250 | }, | |
251 | }; | |
252 | builtin_platform_driver(uniphier_aidet_driver); |