Commit | Line | Data |
---|---|---|
2e62c498 | 1 | // SPDX-License-Identifier: GPL-2.0 |
6adb730d MM |
2 | /* |
3 | * Copyright (C) 2013 Broadcom Corporation | |
4 | * | |
6adb730d MM |
5 | */ |
6 | ||
6e2ac20e | 7 | #include <linux/debugfs.h> |
6adb730d MM |
8 | #include <linux/delay.h> |
9 | #include <linux/err.h> | |
10 | #include <linux/io.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/of_address.h> | |
13 | #include <linux/platform_device.h> | |
14 | #include <linux/watchdog.h> | |
15 | ||
16 | #define SECWDOG_CTRL_REG 0x00000000 | |
17 | #define SECWDOG_COUNT_REG 0x00000004 | |
18 | ||
19 | #define SECWDOG_RESERVED_MASK 0x1dffffff | |
20 | #define SECWDOG_WD_LOAD_FLAG 0x10000000 | |
21 | #define SECWDOG_EN_MASK 0x08000000 | |
22 | #define SECWDOG_SRSTEN_MASK 0x04000000 | |
23 | #define SECWDOG_RES_MASK 0x00f00000 | |
24 | #define SECWDOG_COUNT_MASK 0x000fffff | |
25 | ||
26 | #define SECWDOG_MAX_COUNT SECWDOG_COUNT_MASK | |
27 | #define SECWDOG_CLKS_SHIFT 20 | |
28 | #define SECWDOG_MAX_RES 15 | |
29 | #define SECWDOG_DEFAULT_RESOLUTION 4 | |
30 | #define SECWDOG_MAX_TRY 1000 | |
31 | ||
32 | #define SECS_TO_TICKS(x, w) ((x) << (w)->resolution) | |
33 | #define TICKS_TO_SECS(x, w) ((x) >> (w)->resolution) | |
34 | ||
35 | #define BCM_KONA_WDT_NAME "bcm_kona_wdt" | |
36 | ||
37 | struct bcm_kona_wdt { | |
38 | void __iomem *base; | |
39 | /* | |
40 | * One watchdog tick is 1/(2^resolution) seconds. Resolution can take | |
41 | * the values 0-15, meaning one tick can be 1s to 30.52us. Our default | |
42 | * resolution of 4 means one tick is 62.5ms. | |
43 | * | |
44 | * The watchdog counter is 20 bits. Depending on resolution, the maximum | |
45 | * counter value of 0xfffff expires after about 12 days (resolution 0) | |
46 | * down to only 32s (resolution 15). The default resolution of 4 gives | |
47 | * us a maximum of about 18 hours and 12 minutes before the watchdog | |
48 | * times out. | |
49 | */ | |
50 | int resolution; | |
51 | spinlock_t lock; | |
6e2ac20e MM |
52 | #ifdef CONFIG_BCM_KONA_WDT_DEBUG |
53 | unsigned long busy_count; | |
54 | struct dentry *debugfs; | |
55 | #endif | |
6adb730d MM |
56 | }; |
57 | ||
6e2ac20e | 58 | static int secure_register_read(struct bcm_kona_wdt *wdt, uint32_t offset) |
6adb730d MM |
59 | { |
60 | uint32_t val; | |
61 | unsigned count = 0; | |
62 | ||
63 | /* | |
64 | * If the WD_LOAD_FLAG is set, the watchdog counter field is being | |
65 | * updated in hardware. Once the WD timer is updated in hardware, it | |
66 | * gets cleared. | |
67 | */ | |
68 | do { | |
69 | if (unlikely(count > 1)) | |
70 | udelay(5); | |
6e2ac20e | 71 | val = readl_relaxed(wdt->base + offset); |
6adb730d MM |
72 | count++; |
73 | } while ((val & SECWDOG_WD_LOAD_FLAG) && count < SECWDOG_MAX_TRY); | |
74 | ||
6e2ac20e MM |
75 | #ifdef CONFIG_BCM_KONA_WDT_DEBUG |
76 | /* Remember the maximum number iterations due to WD_LOAD_FLAG */ | |
77 | if (count > wdt->busy_count) | |
78 | wdt->busy_count = count; | |
79 | #endif | |
80 | ||
6adb730d MM |
81 | /* This is the only place we return a negative value. */ |
82 | if (val & SECWDOG_WD_LOAD_FLAG) | |
83 | return -ETIMEDOUT; | |
84 | ||
85 | /* We always mask out reserved bits. */ | |
86 | val &= SECWDOG_RESERVED_MASK; | |
87 | ||
88 | return val; | |
89 | } | |
90 | ||
6e2ac20e MM |
91 | #ifdef CONFIG_BCM_KONA_WDT_DEBUG |
92 | ||
57808f44 | 93 | static int bcm_kona_show(struct seq_file *s, void *data) |
6e2ac20e | 94 | { |
e1dbde29 | 95 | int ctl_val, cur_val; |
6e2ac20e MM |
96 | unsigned long flags; |
97 | struct bcm_kona_wdt *wdt = s->private; | |
98 | ||
e1dbde29 JP |
99 | if (!wdt) { |
100 | seq_puts(s, "No device pointer\n"); | |
101 | return 0; | |
102 | } | |
6e2ac20e MM |
103 | |
104 | spin_lock_irqsave(&wdt->lock, flags); | |
105 | ctl_val = secure_register_read(wdt, SECWDOG_CTRL_REG); | |
106 | cur_val = secure_register_read(wdt, SECWDOG_COUNT_REG); | |
107 | spin_unlock_irqrestore(&wdt->lock, flags); | |
108 | ||
109 | if (ctl_val < 0 || cur_val < 0) { | |
e1dbde29 | 110 | seq_puts(s, "Error accessing hardware\n"); |
6e2ac20e MM |
111 | } else { |
112 | int ctl, cur, ctl_sec, cur_sec, res; | |
113 | ||
114 | ctl = ctl_val & SECWDOG_COUNT_MASK; | |
115 | res = (ctl_val & SECWDOG_RES_MASK) >> SECWDOG_CLKS_SHIFT; | |
116 | cur = cur_val & SECWDOG_COUNT_MASK; | |
117 | ctl_sec = TICKS_TO_SECS(ctl, wdt); | |
118 | cur_sec = TICKS_TO_SECS(cur, wdt); | |
e1dbde29 JP |
119 | seq_printf(s, |
120 | "Resolution: %d / %d\n" | |
121 | "Control: %d s / %d (%#x) ticks\n" | |
122 | "Current: %d s / %d (%#x) ticks\n" | |
123 | "Busy count: %lu\n", | |
124 | res, wdt->resolution, | |
125 | ctl_sec, ctl, ctl, | |
126 | cur_sec, cur, cur, | |
127 | wdt->busy_count); | |
6e2ac20e MM |
128 | } |
129 | ||
e1dbde29 | 130 | return 0; |
6e2ac20e MM |
131 | } |
132 | ||
57808f44 | 133 | DEFINE_SHOW_ATTRIBUTE(bcm_kona); |
6e2ac20e MM |
134 | |
135 | static void bcm_kona_wdt_debug_init(struct platform_device *pdev) | |
136 | { | |
137 | struct dentry *dir; | |
138 | struct bcm_kona_wdt *wdt = platform_get_drvdata(pdev); | |
139 | ||
140 | if (!wdt) | |
141 | return; | |
142 | ||
143 | wdt->debugfs = NULL; | |
144 | ||
145 | dir = debugfs_create_dir(BCM_KONA_WDT_NAME, NULL); | |
146 | if (IS_ERR_OR_NULL(dir)) | |
147 | return; | |
148 | ||
149 | if (debugfs_create_file("info", S_IFREG | S_IRUGO, dir, wdt, | |
57808f44 | 150 | &bcm_kona_fops)) |
6e2ac20e MM |
151 | wdt->debugfs = dir; |
152 | else | |
153 | debugfs_remove_recursive(dir); | |
154 | } | |
155 | ||
156 | static void bcm_kona_wdt_debug_exit(struct platform_device *pdev) | |
157 | { | |
158 | struct bcm_kona_wdt *wdt = platform_get_drvdata(pdev); | |
159 | ||
160 | if (wdt && wdt->debugfs) { | |
161 | debugfs_remove_recursive(wdt->debugfs); | |
162 | wdt->debugfs = NULL; | |
163 | } | |
164 | } | |
165 | ||
166 | #else | |
167 | ||
168 | static void bcm_kona_wdt_debug_init(struct platform_device *pdev) {} | |
169 | static void bcm_kona_wdt_debug_exit(struct platform_device *pdev) {} | |
170 | ||
171 | #endif /* CONFIG_BCM_KONA_WDT_DEBUG */ | |
172 | ||
6adb730d MM |
173 | static int bcm_kona_wdt_ctrl_reg_modify(struct bcm_kona_wdt *wdt, |
174 | unsigned mask, unsigned newval) | |
175 | { | |
176 | int val; | |
177 | unsigned long flags; | |
178 | int ret = 0; | |
179 | ||
180 | spin_lock_irqsave(&wdt->lock, flags); | |
181 | ||
6e2ac20e | 182 | val = secure_register_read(wdt, SECWDOG_CTRL_REG); |
6adb730d MM |
183 | if (val < 0) { |
184 | ret = val; | |
185 | } else { | |
186 | val &= ~mask; | |
187 | val |= newval; | |
188 | writel_relaxed(val, wdt->base + SECWDOG_CTRL_REG); | |
189 | } | |
190 | ||
191 | spin_unlock_irqrestore(&wdt->lock, flags); | |
192 | ||
193 | return ret; | |
194 | } | |
195 | ||
196 | static int bcm_kona_wdt_set_resolution_reg(struct bcm_kona_wdt *wdt) | |
197 | { | |
198 | if (wdt->resolution > SECWDOG_MAX_RES) | |
199 | return -EINVAL; | |
200 | ||
201 | return bcm_kona_wdt_ctrl_reg_modify(wdt, SECWDOG_RES_MASK, | |
202 | wdt->resolution << SECWDOG_CLKS_SHIFT); | |
203 | } | |
204 | ||
205 | static int bcm_kona_wdt_set_timeout_reg(struct watchdog_device *wdog, | |
206 | unsigned watchdog_flags) | |
207 | { | |
208 | struct bcm_kona_wdt *wdt = watchdog_get_drvdata(wdog); | |
209 | ||
210 | return bcm_kona_wdt_ctrl_reg_modify(wdt, SECWDOG_COUNT_MASK, | |
211 | SECS_TO_TICKS(wdog->timeout, wdt) | | |
212 | watchdog_flags); | |
213 | } | |
214 | ||
215 | static int bcm_kona_wdt_set_timeout(struct watchdog_device *wdog, | |
216 | unsigned int t) | |
217 | { | |
218 | wdog->timeout = t; | |
219 | return 0; | |
220 | } | |
221 | ||
222 | static unsigned int bcm_kona_wdt_get_timeleft(struct watchdog_device *wdog) | |
223 | { | |
224 | struct bcm_kona_wdt *wdt = watchdog_get_drvdata(wdog); | |
225 | int val; | |
226 | unsigned long flags; | |
227 | ||
228 | spin_lock_irqsave(&wdt->lock, flags); | |
6e2ac20e | 229 | val = secure_register_read(wdt, SECWDOG_COUNT_REG); |
6adb730d MM |
230 | spin_unlock_irqrestore(&wdt->lock, flags); |
231 | ||
232 | if (val < 0) | |
233 | return val; | |
234 | ||
235 | return TICKS_TO_SECS(val & SECWDOG_COUNT_MASK, wdt); | |
236 | } | |
237 | ||
238 | static int bcm_kona_wdt_start(struct watchdog_device *wdog) | |
239 | { | |
240 | return bcm_kona_wdt_set_timeout_reg(wdog, | |
241 | SECWDOG_EN_MASK | SECWDOG_SRSTEN_MASK); | |
242 | } | |
243 | ||
244 | static int bcm_kona_wdt_stop(struct watchdog_device *wdog) | |
245 | { | |
246 | struct bcm_kona_wdt *wdt = watchdog_get_drvdata(wdog); | |
247 | ||
248 | return bcm_kona_wdt_ctrl_reg_modify(wdt, SECWDOG_EN_MASK | | |
249 | SECWDOG_SRSTEN_MASK, 0); | |
250 | } | |
251 | ||
b893e344 | 252 | static const struct watchdog_ops bcm_kona_wdt_ops = { |
6adb730d MM |
253 | .owner = THIS_MODULE, |
254 | .start = bcm_kona_wdt_start, | |
255 | .stop = bcm_kona_wdt_stop, | |
256 | .set_timeout = bcm_kona_wdt_set_timeout, | |
257 | .get_timeleft = bcm_kona_wdt_get_timeleft, | |
258 | }; | |
259 | ||
6c368932 | 260 | static const struct watchdog_info bcm_kona_wdt_info = { |
6adb730d MM |
261 | .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | |
262 | WDIOF_KEEPALIVEPING, | |
263 | .identity = "Broadcom Kona Watchdog Timer", | |
264 | }; | |
265 | ||
266 | static struct watchdog_device bcm_kona_wdt_wdd = { | |
267 | .info = &bcm_kona_wdt_info, | |
268 | .ops = &bcm_kona_wdt_ops, | |
269 | .min_timeout = 1, | |
270 | .max_timeout = SECWDOG_MAX_COUNT >> SECWDOG_DEFAULT_RESOLUTION, | |
271 | .timeout = SECWDOG_MAX_COUNT >> SECWDOG_DEFAULT_RESOLUTION, | |
272 | }; | |
273 | ||
6adb730d MM |
274 | static int bcm_kona_wdt_probe(struct platform_device *pdev) |
275 | { | |
276 | struct device *dev = &pdev->dev; | |
277 | struct bcm_kona_wdt *wdt; | |
6adb730d MM |
278 | int ret; |
279 | ||
280 | wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); | |
281 | if (!wdt) | |
282 | return -ENOMEM; | |
283 | ||
fedf266f EA |
284 | spin_lock_init(&wdt->lock); |
285 | ||
0f0a6a28 | 286 | wdt->base = devm_platform_ioremap_resource(pdev, 0); |
6adb730d MM |
287 | if (IS_ERR(wdt->base)) |
288 | return -ENODEV; | |
289 | ||
290 | wdt->resolution = SECWDOG_DEFAULT_RESOLUTION; | |
291 | ret = bcm_kona_wdt_set_resolution_reg(wdt); | |
292 | if (ret) { | |
293 | dev_err(dev, "Failed to set resolution (error: %d)", ret); | |
294 | return ret; | |
295 | } | |
296 | ||
6adb730d MM |
297 | platform_set_drvdata(pdev, wdt); |
298 | watchdog_set_drvdata(&bcm_kona_wdt_wdd, wdt); | |
4062ec06 | 299 | bcm_kona_wdt_wdd.parent = dev; |
6adb730d MM |
300 | |
301 | ret = bcm_kona_wdt_set_timeout_reg(&bcm_kona_wdt_wdd, 0); | |
302 | if (ret) { | |
303 | dev_err(dev, "Failed set watchdog timeout"); | |
304 | return ret; | |
305 | } | |
306 | ||
4062ec06 GR |
307 | watchdog_stop_on_reboot(&bcm_kona_wdt_wdd); |
308 | watchdog_stop_on_unregister(&bcm_kona_wdt_wdd); | |
309 | ret = devm_watchdog_register_device(dev, &bcm_kona_wdt_wdd); | |
6adb730d MM |
310 | if (ret) { |
311 | dev_err(dev, "Failed to register watchdog device"); | |
312 | return ret; | |
313 | } | |
314 | ||
6e2ac20e | 315 | bcm_kona_wdt_debug_init(pdev); |
6adb730d MM |
316 | dev_dbg(dev, "Broadcom Kona Watchdog Timer"); |
317 | ||
318 | return 0; | |
319 | } | |
320 | ||
321 | static int bcm_kona_wdt_remove(struct platform_device *pdev) | |
322 | { | |
6e2ac20e | 323 | bcm_kona_wdt_debug_exit(pdev); |
6adb730d MM |
324 | dev_dbg(&pdev->dev, "Watchdog driver disabled"); |
325 | ||
326 | return 0; | |
327 | } | |
328 | ||
329 | static const struct of_device_id bcm_kona_wdt_of_match[] = { | |
330 | { .compatible = "brcm,kona-wdt", }, | |
331 | {}, | |
332 | }; | |
333 | MODULE_DEVICE_TABLE(of, bcm_kona_wdt_of_match); | |
334 | ||
335 | static struct platform_driver bcm_kona_wdt_driver = { | |
336 | .driver = { | |
337 | .name = BCM_KONA_WDT_NAME, | |
6adb730d MM |
338 | .of_match_table = bcm_kona_wdt_of_match, |
339 | }, | |
340 | .probe = bcm_kona_wdt_probe, | |
341 | .remove = bcm_kona_wdt_remove, | |
6adb730d MM |
342 | }; |
343 | ||
344 | module_platform_driver(bcm_kona_wdt_driver); | |
345 | ||
346 | MODULE_ALIAS("platform:" BCM_KONA_WDT_NAME); | |
347 | MODULE_AUTHOR("Markus Mayer <mmayer@broadcom.com>"); | |
348 | MODULE_DESCRIPTION("Broadcom Kona Watchdog Driver"); | |
349 | MODULE_LICENSE("GPL v2"); |