1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC |
4 | * |
5 | * Authors: |
6 | * Serge Semin <Sergey.Semin@baikalelectronics.ru> |
7 | * |
8 | * Baikal-T1 APB-bus driver |
9 | */ |
10 | |
11 | #include <linux/kernel.h> |
12 | #include <linux/module.h> |
13 | #include <linux/types.h> |
14 | #include <linux/device.h> |
15 | #include <linux/atomic.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/interrupt.h> |
18 | #include <linux/io.h> |
19 | #include <linux/nmi.h> |
20 | #include <linux/of.h> |
21 | #include <linux/regmap.h> |
22 | #include <linux/clk.h> |
23 | #include <linux/reset.h> |
24 | #include <linux/time64.h> |
25 | #include <linux/sysfs.h> |
26 | |
27 | #define APB_EHB_ISR 0x00 |
28 | #define APB_EHB_ISR_PENDING BIT(0) |
29 | #define APB_EHB_ISR_MASK BIT(1) |
30 | #define APB_EHB_ADDR 0x04 |
31 | #define APB_EHB_TIMEOUT 0x08 |
32 | |
33 | #define APB_EHB_TIMEOUT_MIN 0x000003FFU |
34 | #define APB_EHB_TIMEOUT_MAX 0xFFFFFFFFU |
35 | |
36 | /* |
37 | * struct bt1_apb - Baikal-T1 APB EHB private data |
38 | * @dev: Pointer to the device structure. |
39 | * @regs: APB EHB registers map. |
40 | * @res: No-device error injection memory region. |
41 | * @irq: Errors IRQ number. |
42 | * @rate: APB-bus reference clock rate. |
43 | * @pclk: APB-reference clock. |
44 | * @prst: APB domain reset line. |
45 | * @count: Number of errors detected. |
46 | */ |
47 | struct bt1_apb { |
48 | struct device *dev; |
49 | |
50 | struct regmap *regs; |
51 | void __iomem *res; |
52 | int irq; |
53 | |
54 | unsigned long rate; |
55 | struct clk *pclk; |
56 | |
57 | struct reset_control *prst; |
58 | |
59 | atomic_t count; |
60 | }; |
61 | |
62 | static const struct regmap_config bt1_apb_regmap_cfg = { |
63 | .reg_bits = 32, |
64 | .val_bits = 32, |
65 | .reg_stride = 4, |
66 | .max_register = APB_EHB_TIMEOUT, |
67 | .fast_io = true |
68 | }; |
69 | |
70 | static inline unsigned long bt1_apb_n_to_timeout_us(struct bt1_apb *apb, u32 n) |
71 | { |
72 | u64 timeout = (u64)n * USEC_PER_SEC; |
73 | |
74 | do_div(timeout, apb->rate); |
75 | |
76 | return timeout; |
77 | |
78 | } |
79 | |
80 | static inline unsigned long bt1_apb_timeout_to_n_us(struct bt1_apb *apb, |
81 | unsigned long timeout) |
82 | { |
83 | u64 n = (u64)timeout * apb->rate; |
84 | |
85 | do_div(n, USEC_PER_SEC); |
86 | |
87 | return n; |
88 | |
89 | } |
90 | |
91 | static irqreturn_t bt1_apb_isr(int irq, void *data) |
92 | { |
93 | struct bt1_apb *apb = data; |
94 | u32 addr = 0; |
95 | |
96 | regmap_read(map: apb->regs, APB_EHB_ADDR, val: &addr); |
97 | |
98 | dev_crit_ratelimited(apb->dev, |
99 | "APB-bus fault %d: Slave access timeout at 0x%08x\n" , |
100 | atomic_inc_return(&apb->count), |
101 | addr); |
102 | |
103 | /* |
104 | * Print backtrace on each CPU. This might be pointless if the fault |
105 | * has happened on the same CPU as the IRQ handler is executed or |
106 | * the other core proceeded further execution despite the error. |
107 | * But if it's not, by looking at the trace we would get straight to |
108 | * the cause of the problem. |
109 | */ |
110 | trigger_all_cpu_backtrace(); |
111 | |
112 | regmap_update_bits(map: apb->regs, APB_EHB_ISR, APB_EHB_ISR_PENDING, val: 0); |
113 | |
114 | return IRQ_HANDLED; |
115 | } |
116 | |
117 | static void bt1_apb_clear_data(void *data) |
118 | { |
119 | struct bt1_apb *apb = data; |
120 | struct platform_device *pdev = to_platform_device(apb->dev); |
121 | |
122 | platform_set_drvdata(pdev, NULL); |
123 | } |
124 | |
125 | static struct bt1_apb *bt1_apb_create_data(struct platform_device *pdev) |
126 | { |
127 | struct device *dev = &pdev->dev; |
128 | struct bt1_apb *apb; |
129 | int ret; |
130 | |
131 | apb = devm_kzalloc(dev, size: sizeof(*apb), GFP_KERNEL); |
132 | if (!apb) |
133 | return ERR_PTR(error: -ENOMEM); |
134 | |
135 | ret = devm_add_action(dev, bt1_apb_clear_data, apb); |
136 | if (ret) { |
137 | dev_err(dev, "Can't add APB EHB data clear action\n" ); |
138 | return ERR_PTR(error: ret); |
139 | } |
140 | |
141 | apb->dev = dev; |
142 | atomic_set(v: &apb->count, i: 0); |
143 | platform_set_drvdata(pdev, data: apb); |
144 | |
145 | return apb; |
146 | } |
147 | |
148 | static int bt1_apb_request_regs(struct bt1_apb *apb) |
149 | { |
150 | struct platform_device *pdev = to_platform_device(apb->dev); |
151 | void __iomem *regs; |
152 | |
153 | regs = devm_platform_ioremap_resource_byname(pdev, name: "ehb" ); |
154 | if (IS_ERR(ptr: regs)) { |
155 | dev_err(apb->dev, "Couldn't map APB EHB registers\n" ); |
156 | return PTR_ERR(ptr: regs); |
157 | } |
158 | |
159 | apb->regs = devm_regmap_init_mmio(apb->dev, regs, &bt1_apb_regmap_cfg); |
160 | if (IS_ERR(ptr: apb->regs)) { |
161 | dev_err(apb->dev, "Couldn't create APB EHB regmap\n" ); |
162 | return PTR_ERR(ptr: apb->regs); |
163 | } |
164 | |
165 | apb->res = devm_platform_ioremap_resource_byname(pdev, name: "nodev" ); |
166 | if (IS_ERR(ptr: apb->res)) |
167 | dev_err(apb->dev, "Couldn't map reserved region\n" ); |
168 | |
169 | return PTR_ERR_OR_ZERO(ptr: apb->res); |
170 | } |
171 | |
172 | static int bt1_apb_request_rst(struct bt1_apb *apb) |
173 | { |
174 | int ret; |
175 | |
176 | apb->prst = devm_reset_control_get_optional_exclusive(dev: apb->dev, id: "prst" ); |
177 | if (IS_ERR(ptr: apb->prst)) |
178 | return dev_err_probe(dev: apb->dev, err: PTR_ERR(ptr: apb->prst), |
179 | fmt: "Couldn't get reset control line\n" ); |
180 | |
181 | ret = reset_control_deassert(rstc: apb->prst); |
182 | if (ret) |
183 | dev_err(apb->dev, "Failed to deassert the reset line\n" ); |
184 | |
185 | return ret; |
186 | } |
187 | |
188 | static void bt1_apb_disable_clk(void *data) |
189 | { |
190 | struct bt1_apb *apb = data; |
191 | |
192 | clk_disable_unprepare(clk: apb->pclk); |
193 | } |
194 | |
195 | static int bt1_apb_request_clk(struct bt1_apb *apb) |
196 | { |
197 | int ret; |
198 | |
199 | apb->pclk = devm_clk_get(dev: apb->dev, id: "pclk" ); |
200 | if (IS_ERR(ptr: apb->pclk)) |
201 | return dev_err_probe(dev: apb->dev, err: PTR_ERR(ptr: apb->pclk), |
202 | fmt: "Couldn't get APB clock descriptor\n" ); |
203 | |
204 | ret = clk_prepare_enable(clk: apb->pclk); |
205 | if (ret) { |
206 | dev_err(apb->dev, "Couldn't enable the APB clock\n" ); |
207 | return ret; |
208 | } |
209 | |
210 | ret = devm_add_action_or_reset(apb->dev, bt1_apb_disable_clk, apb); |
211 | if (ret) { |
212 | dev_err(apb->dev, "Can't add APB EHB clocks disable action\n" ); |
213 | return ret; |
214 | } |
215 | |
216 | apb->rate = clk_get_rate(clk: apb->pclk); |
217 | if (!apb->rate) { |
218 | dev_err(apb->dev, "Invalid clock rate\n" ); |
219 | return -EINVAL; |
220 | } |
221 | |
222 | return 0; |
223 | } |
224 | |
225 | static void bt1_apb_clear_irq(void *data) |
226 | { |
227 | struct bt1_apb *apb = data; |
228 | |
229 | regmap_update_bits(map: apb->regs, APB_EHB_ISR, APB_EHB_ISR_MASK, val: 0); |
230 | } |
231 | |
232 | static int bt1_apb_request_irq(struct bt1_apb *apb) |
233 | { |
234 | struct platform_device *pdev = to_platform_device(apb->dev); |
235 | int ret; |
236 | |
237 | apb->irq = platform_get_irq(pdev, 0); |
238 | if (apb->irq < 0) |
239 | return apb->irq; |
240 | |
241 | ret = devm_request_irq(dev: apb->dev, irq: apb->irq, handler: bt1_apb_isr, IRQF_SHARED, |
242 | devname: "bt1-apb" , dev_id: apb); |
243 | if (ret) { |
244 | dev_err(apb->dev, "Couldn't request APB EHB IRQ\n" ); |
245 | return ret; |
246 | } |
247 | |
248 | ret = devm_add_action(apb->dev, bt1_apb_clear_irq, apb); |
249 | if (ret) { |
250 | dev_err(apb->dev, "Can't add APB EHB IRQs clear action\n" ); |
251 | return ret; |
252 | } |
253 | |
254 | /* Unmask IRQ and clear it' pending flag. */ |
255 | regmap_update_bits(map: apb->regs, APB_EHB_ISR, |
256 | APB_EHB_ISR_PENDING | APB_EHB_ISR_MASK, |
257 | APB_EHB_ISR_MASK); |
258 | |
259 | return 0; |
260 | } |
261 | |
262 | static ssize_t count_show(struct device *dev, struct device_attribute *attr, |
263 | char *buf) |
264 | { |
265 | struct bt1_apb *apb = dev_get_drvdata(dev); |
266 | |
267 | return scnprintf(buf, PAGE_SIZE, fmt: "%d\n" , atomic_read(v: &apb->count)); |
268 | } |
269 | static DEVICE_ATTR_RO(count); |
270 | |
271 | static ssize_t timeout_show(struct device *dev, struct device_attribute *attr, |
272 | char *buf) |
273 | { |
274 | struct bt1_apb *apb = dev_get_drvdata(dev); |
275 | unsigned long timeout; |
276 | int ret; |
277 | u32 n; |
278 | |
279 | ret = regmap_read(map: apb->regs, APB_EHB_TIMEOUT, val: &n); |
280 | if (ret) |
281 | return ret; |
282 | |
283 | timeout = bt1_apb_n_to_timeout_us(apb, n); |
284 | |
285 | return scnprintf(buf, PAGE_SIZE, fmt: "%lu\n" , timeout); |
286 | } |
287 | |
288 | static ssize_t timeout_store(struct device *dev, |
289 | struct device_attribute *attr, |
290 | const char *buf, size_t count) |
291 | { |
292 | struct bt1_apb *apb = dev_get_drvdata(dev); |
293 | unsigned long timeout; |
294 | int ret; |
295 | u32 n; |
296 | |
297 | if (kstrtoul(s: buf, base: 0, res: &timeout) < 0) |
298 | return -EINVAL; |
299 | |
300 | n = bt1_apb_timeout_to_n_us(apb, timeout); |
301 | n = clamp(n, APB_EHB_TIMEOUT_MIN, APB_EHB_TIMEOUT_MAX); |
302 | |
303 | ret = regmap_write(map: apb->regs, APB_EHB_TIMEOUT, val: n); |
304 | |
305 | return ret ?: count; |
306 | } |
307 | static DEVICE_ATTR_RW(timeout); |
308 | |
309 | static ssize_t inject_error_show(struct device *dev, |
310 | struct device_attribute *attr, char *buf) |
311 | { |
312 | return scnprintf(buf, PAGE_SIZE, fmt: "Error injection: nodev irq\n" ); |
313 | } |
314 | |
315 | static ssize_t inject_error_store(struct device *dev, |
316 | struct device_attribute *attr, |
317 | const char *data, size_t count) |
318 | { |
319 | struct bt1_apb *apb = dev_get_drvdata(dev); |
320 | |
321 | /* |
322 | * Either dummy read from the unmapped address in the APB IO area |
323 | * or manually set the IRQ status. |
324 | */ |
325 | if (sysfs_streq(s1: data, s2: "nodev" )) |
326 | readl(addr: apb->res); |
327 | else if (sysfs_streq(s1: data, s2: "irq" )) |
328 | regmap_update_bits(map: apb->regs, APB_EHB_ISR, APB_EHB_ISR_PENDING, |
329 | APB_EHB_ISR_PENDING); |
330 | else |
331 | return -EINVAL; |
332 | |
333 | return count; |
334 | } |
335 | static DEVICE_ATTR_RW(inject_error); |
336 | |
337 | static struct attribute *bt1_apb_sysfs_attrs[] = { |
338 | &dev_attr_count.attr, |
339 | &dev_attr_timeout.attr, |
340 | &dev_attr_inject_error.attr, |
341 | NULL |
342 | }; |
343 | ATTRIBUTE_GROUPS(bt1_apb_sysfs); |
344 | |
345 | static void bt1_apb_remove_sysfs(void *data) |
346 | { |
347 | struct bt1_apb *apb = data; |
348 | |
349 | device_remove_groups(dev: apb->dev, groups: bt1_apb_sysfs_groups); |
350 | } |
351 | |
352 | static int bt1_apb_init_sysfs(struct bt1_apb *apb) |
353 | { |
354 | int ret; |
355 | |
356 | ret = device_add_groups(dev: apb->dev, groups: bt1_apb_sysfs_groups); |
357 | if (ret) { |
358 | dev_err(apb->dev, "Failed to create EHB APB sysfs nodes\n" ); |
359 | return ret; |
360 | } |
361 | |
362 | ret = devm_add_action_or_reset(apb->dev, bt1_apb_remove_sysfs, apb); |
363 | if (ret) |
364 | dev_err(apb->dev, "Can't add APB EHB sysfs remove action\n" ); |
365 | |
366 | return ret; |
367 | } |
368 | |
369 | static int bt1_apb_probe(struct platform_device *pdev) |
370 | { |
371 | struct bt1_apb *apb; |
372 | int ret; |
373 | |
374 | apb = bt1_apb_create_data(pdev); |
375 | if (IS_ERR(ptr: apb)) |
376 | return PTR_ERR(ptr: apb); |
377 | |
378 | ret = bt1_apb_request_regs(apb); |
379 | if (ret) |
380 | return ret; |
381 | |
382 | ret = bt1_apb_request_rst(apb); |
383 | if (ret) |
384 | return ret; |
385 | |
386 | ret = bt1_apb_request_clk(apb); |
387 | if (ret) |
388 | return ret; |
389 | |
390 | ret = bt1_apb_request_irq(apb); |
391 | if (ret) |
392 | return ret; |
393 | |
394 | ret = bt1_apb_init_sysfs(apb); |
395 | if (ret) |
396 | return ret; |
397 | |
398 | return 0; |
399 | } |
400 | |
401 | static const struct of_device_id bt1_apb_of_match[] = { |
402 | { .compatible = "baikal,bt1-apb" }, |
403 | { } |
404 | }; |
405 | MODULE_DEVICE_TABLE(of, bt1_apb_of_match); |
406 | |
407 | static struct platform_driver bt1_apb_driver = { |
408 | .probe = bt1_apb_probe, |
409 | .driver = { |
410 | .name = "bt1-apb" , |
411 | .of_match_table = bt1_apb_of_match |
412 | } |
413 | }; |
414 | module_platform_driver(bt1_apb_driver); |
415 | |
416 | MODULE_AUTHOR("Serge Semin <Sergey.Semin@baikalelectronics.ru>" ); |
417 | MODULE_DESCRIPTION("Baikal-T1 APB-bus driver" ); |
418 | |