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