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