1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Starfive Watchdog driver |
4 | * |
5 | * Copyright (C) 2022 StarFive Technology Co., Ltd. |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/iopoll.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/pm_runtime.h> |
14 | #include <linux/reset.h> |
15 | #include <linux/watchdog.h> |
16 | |
17 | /* JH7100 Watchdog register define */ |
18 | #define STARFIVE_WDT_JH7100_INTSTAUS 0x000 |
19 | #define STARFIVE_WDT_JH7100_CONTROL 0x104 |
20 | #define STARFIVE_WDT_JH7100_LOAD 0x108 |
21 | #define STARFIVE_WDT_JH7100_EN 0x110 |
22 | #define STARFIVE_WDT_JH7100_RELOAD 0x114 /* Write 0 or 1 to reload preset value */ |
23 | #define STARFIVE_WDT_JH7100_VALUE 0x118 |
24 | #define STARFIVE_WDT_JH7100_INTCLR 0x120 /* |
25 | * [0]: Write 1 to clear interrupt |
26 | * [1]: 1 mean clearing and 0 mean complete |
27 | * [31:2]: reserved. |
28 | */ |
29 | #define STARFIVE_WDT_JH7100_LOCK 0x13c /* write 0x378f0765 to unlock */ |
30 | |
31 | /* JH7110 Watchdog register define */ |
32 | #define STARFIVE_WDT_JH7110_LOAD 0x000 |
33 | #define STARFIVE_WDT_JH7110_VALUE 0x004 |
34 | #define STARFIVE_WDT_JH7110_CONTROL 0x008 /* |
35 | * [0]: reset enable; |
36 | * [1]: interrupt enable && watchdog enable |
37 | * [31:2]: reserved. |
38 | */ |
39 | #define STARFIVE_WDT_JH7110_INTCLR 0x00c /* clear intterupt and reload the counter */ |
40 | #define STARFIVE_WDT_JH7110_IMS 0x014 |
41 | #define STARFIVE_WDT_JH7110_LOCK 0xc00 /* write 0x1ACCE551 to unlock */ |
42 | |
43 | /* WDOGCONTROL */ |
44 | #define STARFIVE_WDT_ENABLE 0x1 |
45 | #define STARFIVE_WDT_EN_SHIFT 0 |
46 | #define STARFIVE_WDT_RESET_EN 0x1 |
47 | #define STARFIVE_WDT_JH7100_RST_EN_SHIFT 0 |
48 | #define STARFIVE_WDT_JH7110_RST_EN_SHIFT 1 |
49 | |
50 | /* WDOGLOCK */ |
51 | #define STARFIVE_WDT_JH7100_UNLOCK_KEY 0x378f0765 |
52 | #define STARFIVE_WDT_JH7110_UNLOCK_KEY 0x1acce551 |
53 | |
54 | /* WDOGINTCLR */ |
55 | #define STARFIVE_WDT_INTCLR 0x1 |
56 | #define STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT 1 /* Watchdog can clear interrupt when 0 */ |
57 | |
58 | #define STARFIVE_WDT_MAXCNT 0xffffffff |
59 | #define STARFIVE_WDT_DEFAULT_TIME (15) |
60 | #define STARFIVE_WDT_DELAY_US 0 |
61 | #define STARFIVE_WDT_TIMEOUT_US 10000 |
62 | |
63 | /* module parameter */ |
64 | #define STARFIVE_WDT_EARLY_ENA 0 |
65 | |
66 | static bool nowayout = WATCHDOG_NOWAYOUT; |
67 | static int heartbeat; |
68 | static bool early_enable = STARFIVE_WDT_EARLY_ENA; |
69 | |
70 | module_param(heartbeat, int, 0); |
71 | module_param(early_enable, bool, 0); |
72 | module_param(nowayout, bool, 0); |
73 | |
74 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default=" |
75 | __MODULE_STRING(STARFIVE_WDT_DEFAULT_TIME) ")" ); |
76 | MODULE_PARM_DESC(early_enable, |
77 | "Watchdog is started at boot time if set to 1, default=" |
78 | __MODULE_STRING(STARFIVE_WDT_EARLY_ENA)); |
79 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
80 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
81 | |
82 | struct starfive_wdt_variant { |
83 | unsigned int control; /* Watchdog Control Resgister for reset enable */ |
84 | unsigned int load; /* Watchdog Load register */ |
85 | unsigned int reload; /* Watchdog Reload Control register */ |
86 | unsigned int enable; /* Watchdog Enable Register */ |
87 | unsigned int value; /* Watchdog Counter Value Register */ |
88 | unsigned int int_clr; /* Watchdog Interrupt Clear Register */ |
89 | unsigned int unlock; /* Watchdog Lock Register */ |
90 | unsigned int int_status; /* Watchdog Interrupt Status Register */ |
91 | |
92 | u32 unlock_key; |
93 | char enrst_shift; |
94 | char en_shift; |
95 | bool intclr_check; /* whether need to check it before clearing interrupt */ |
96 | char intclr_ava_shift; |
97 | bool double_timeout; /* The watchdog need twice timeout to reboot */ |
98 | }; |
99 | |
100 | struct starfive_wdt { |
101 | struct watchdog_device wdd; |
102 | spinlock_t lock; /* spinlock for register handling */ |
103 | void __iomem *base; |
104 | struct clk *core_clk; |
105 | struct clk *apb_clk; |
106 | const struct starfive_wdt_variant *variant; |
107 | unsigned long freq; |
108 | u32 count; /* count of timeout */ |
109 | u32 reload; /* restore the count */ |
110 | }; |
111 | |
112 | /* Register layout and configuration for the JH7100 */ |
113 | static const struct starfive_wdt_variant starfive_wdt_jh7100_variant = { |
114 | .control = STARFIVE_WDT_JH7100_CONTROL, |
115 | .load = STARFIVE_WDT_JH7100_LOAD, |
116 | .reload = STARFIVE_WDT_JH7100_RELOAD, |
117 | .enable = STARFIVE_WDT_JH7100_EN, |
118 | .value = STARFIVE_WDT_JH7100_VALUE, |
119 | .int_clr = STARFIVE_WDT_JH7100_INTCLR, |
120 | .unlock = STARFIVE_WDT_JH7100_LOCK, |
121 | .unlock_key = STARFIVE_WDT_JH7100_UNLOCK_KEY, |
122 | .int_status = STARFIVE_WDT_JH7100_INTSTAUS, |
123 | .enrst_shift = STARFIVE_WDT_JH7100_RST_EN_SHIFT, |
124 | .en_shift = STARFIVE_WDT_EN_SHIFT, |
125 | .intclr_check = true, |
126 | .intclr_ava_shift = STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT, |
127 | .double_timeout = false, |
128 | }; |
129 | |
130 | /* Register layout and configuration for the JH7110 */ |
131 | static const struct starfive_wdt_variant starfive_wdt_jh7110_variant = { |
132 | .control = STARFIVE_WDT_JH7110_CONTROL, |
133 | .load = STARFIVE_WDT_JH7110_LOAD, |
134 | .enable = STARFIVE_WDT_JH7110_CONTROL, |
135 | .value = STARFIVE_WDT_JH7110_VALUE, |
136 | .int_clr = STARFIVE_WDT_JH7110_INTCLR, |
137 | .unlock = STARFIVE_WDT_JH7110_LOCK, |
138 | .unlock_key = STARFIVE_WDT_JH7110_UNLOCK_KEY, |
139 | .int_status = STARFIVE_WDT_JH7110_IMS, |
140 | .enrst_shift = STARFIVE_WDT_JH7110_RST_EN_SHIFT, |
141 | .en_shift = STARFIVE_WDT_EN_SHIFT, |
142 | .intclr_check = false, |
143 | .double_timeout = true, |
144 | }; |
145 | |
146 | static int starfive_wdt_enable_clock(struct starfive_wdt *wdt) |
147 | { |
148 | int ret; |
149 | |
150 | ret = clk_prepare_enable(clk: wdt->apb_clk); |
151 | if (ret) |
152 | return dev_err_probe(dev: wdt->wdd.parent, err: ret, fmt: "failed to enable apb clock\n" ); |
153 | |
154 | ret = clk_prepare_enable(clk: wdt->core_clk); |
155 | if (ret) |
156 | return dev_err_probe(dev: wdt->wdd.parent, err: ret, fmt: "failed to enable core clock\n" ); |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | static void starfive_wdt_disable_clock(struct starfive_wdt *wdt) |
162 | { |
163 | clk_disable_unprepare(clk: wdt->core_clk); |
164 | clk_disable_unprepare(clk: wdt->apb_clk); |
165 | } |
166 | |
167 | static inline int starfive_wdt_get_clock(struct starfive_wdt *wdt) |
168 | { |
169 | struct device *dev = wdt->wdd.parent; |
170 | |
171 | wdt->apb_clk = devm_clk_get(dev, id: "apb" ); |
172 | if (IS_ERR(ptr: wdt->apb_clk)) |
173 | return dev_err_probe(dev, err: PTR_ERR(ptr: wdt->apb_clk), fmt: "failed to get apb clock\n" ); |
174 | |
175 | wdt->core_clk = devm_clk_get(dev, id: "core" ); |
176 | if (IS_ERR(ptr: wdt->core_clk)) |
177 | return dev_err_probe(dev, err: PTR_ERR(ptr: wdt->core_clk), fmt: "failed to get core clock\n" ); |
178 | |
179 | return 0; |
180 | } |
181 | |
182 | static inline int starfive_wdt_reset_init(struct device *dev) |
183 | { |
184 | struct reset_control *rsts; |
185 | int ret; |
186 | |
187 | rsts = devm_reset_control_array_get_exclusive(dev); |
188 | if (IS_ERR(ptr: rsts)) |
189 | return dev_err_probe(dev, err: PTR_ERR(ptr: rsts), fmt: "failed to get resets\n" ); |
190 | |
191 | ret = reset_control_deassert(rstc: rsts); |
192 | if (ret) |
193 | return dev_err_probe(dev, err: ret, fmt: "failed to deassert resets\n" ); |
194 | |
195 | return 0; |
196 | } |
197 | |
198 | static u32 starfive_wdt_ticks_to_sec(struct starfive_wdt *wdt, u32 ticks) |
199 | { |
200 | return DIV_ROUND_CLOSEST(ticks, wdt->freq); |
201 | } |
202 | |
203 | /* Write unlock-key to unlock. Write other value to lock. */ |
204 | static void starfive_wdt_unlock(struct starfive_wdt *wdt) |
205 | { |
206 | spin_lock(lock: &wdt->lock); |
207 | writel(val: wdt->variant->unlock_key, addr: wdt->base + wdt->variant->unlock); |
208 | } |
209 | |
210 | static void starfive_wdt_lock(struct starfive_wdt *wdt) |
211 | { |
212 | writel(val: ~wdt->variant->unlock_key, addr: wdt->base + wdt->variant->unlock); |
213 | spin_unlock(lock: &wdt->lock); |
214 | } |
215 | |
216 | /* enable watchdog interrupt to reset/reboot */ |
217 | static void starfive_wdt_enable_reset(struct starfive_wdt *wdt) |
218 | { |
219 | u32 val; |
220 | |
221 | val = readl(addr: wdt->base + wdt->variant->control); |
222 | val |= STARFIVE_WDT_RESET_EN << wdt->variant->enrst_shift; |
223 | writel(val, addr: wdt->base + wdt->variant->control); |
224 | } |
225 | |
226 | /* interrupt status whether has been raised from the counter */ |
227 | static bool starfive_wdt_raise_irq_status(struct starfive_wdt *wdt) |
228 | { |
229 | return !!readl(addr: wdt->base + wdt->variant->int_status); |
230 | } |
231 | |
232 | /* waiting interrupt can be free to clear */ |
233 | static int starfive_wdt_wait_int_free(struct starfive_wdt *wdt) |
234 | { |
235 | u32 value; |
236 | |
237 | return readl_poll_timeout_atomic(wdt->base + wdt->variant->int_clr, value, |
238 | !(value & BIT(wdt->variant->intclr_ava_shift)), |
239 | STARFIVE_WDT_DELAY_US, STARFIVE_WDT_TIMEOUT_US); |
240 | } |
241 | |
242 | /* clear interrupt signal before initialization or reload */ |
243 | static int starfive_wdt_int_clr(struct starfive_wdt *wdt) |
244 | { |
245 | int ret; |
246 | |
247 | if (wdt->variant->intclr_check) { |
248 | ret = starfive_wdt_wait_int_free(wdt); |
249 | if (ret) |
250 | return dev_err_probe(dev: wdt->wdd.parent, err: ret, |
251 | fmt: "watchdog is not ready to clear interrupt.\n" ); |
252 | } |
253 | writel(STARFIVE_WDT_INTCLR, addr: wdt->base + wdt->variant->int_clr); |
254 | |
255 | return 0; |
256 | } |
257 | |
258 | static inline void starfive_wdt_set_count(struct starfive_wdt *wdt, u32 val) |
259 | { |
260 | writel(val, addr: wdt->base + wdt->variant->load); |
261 | } |
262 | |
263 | static inline u32 starfive_wdt_get_count(struct starfive_wdt *wdt) |
264 | { |
265 | return readl(addr: wdt->base + wdt->variant->value); |
266 | } |
267 | |
268 | /* enable watchdog */ |
269 | static inline void starfive_wdt_enable(struct starfive_wdt *wdt) |
270 | { |
271 | u32 val; |
272 | |
273 | val = readl(addr: wdt->base + wdt->variant->enable); |
274 | val |= STARFIVE_WDT_ENABLE << wdt->variant->en_shift; |
275 | writel(val, addr: wdt->base + wdt->variant->enable); |
276 | } |
277 | |
278 | /* disable watchdog */ |
279 | static inline void starfive_wdt_disable(struct starfive_wdt *wdt) |
280 | { |
281 | u32 val; |
282 | |
283 | val = readl(addr: wdt->base + wdt->variant->enable); |
284 | val &= ~(STARFIVE_WDT_ENABLE << wdt->variant->en_shift); |
285 | writel(val, addr: wdt->base + wdt->variant->enable); |
286 | } |
287 | |
288 | static inline void starfive_wdt_set_reload_count(struct starfive_wdt *wdt, u32 count) |
289 | { |
290 | starfive_wdt_set_count(wdt, val: count); |
291 | |
292 | /* 7100 need set any value to reload register and could reload value to counter */ |
293 | if (wdt->variant->reload) |
294 | writel(val: 0x1, addr: wdt->base + wdt->variant->reload); |
295 | } |
296 | |
297 | static unsigned int starfive_wdt_max_timeout(struct starfive_wdt *wdt) |
298 | { |
299 | if (wdt->variant->double_timeout) |
300 | return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, (wdt->freq / 2)) - 1; |
301 | |
302 | return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, wdt->freq) - 1; |
303 | } |
304 | |
305 | static unsigned int starfive_wdt_get_timeleft(struct watchdog_device *wdd) |
306 | { |
307 | struct starfive_wdt *wdt = watchdog_get_drvdata(wdd); |
308 | u32 count; |
309 | |
310 | /* |
311 | * If the watchdog takes twice timeout and set half count value, |
312 | * timeleft value should add the count value before first timeout. |
313 | */ |
314 | count = starfive_wdt_get_count(wdt); |
315 | if (wdt->variant->double_timeout && !starfive_wdt_raise_irq_status(wdt)) |
316 | count += wdt->count; |
317 | |
318 | return starfive_wdt_ticks_to_sec(wdt, ticks: count); |
319 | } |
320 | |
321 | static int starfive_wdt_keepalive(struct watchdog_device *wdd) |
322 | { |
323 | struct starfive_wdt *wdt = watchdog_get_drvdata(wdd); |
324 | int ret; |
325 | |
326 | starfive_wdt_unlock(wdt); |
327 | ret = starfive_wdt_int_clr(wdt); |
328 | if (ret) |
329 | goto exit; |
330 | |
331 | starfive_wdt_set_reload_count(wdt, count: wdt->count); |
332 | |
333 | exit: |
334 | /* exit with releasing spinlock and locking registers */ |
335 | starfive_wdt_lock(wdt); |
336 | return ret; |
337 | } |
338 | |
339 | static int starfive_wdt_start(struct starfive_wdt *wdt) |
340 | { |
341 | int ret; |
342 | |
343 | starfive_wdt_unlock(wdt); |
344 | /* disable watchdog, to be safe */ |
345 | starfive_wdt_disable(wdt); |
346 | |
347 | starfive_wdt_enable_reset(wdt); |
348 | ret = starfive_wdt_int_clr(wdt); |
349 | if (ret) |
350 | goto exit; |
351 | |
352 | starfive_wdt_set_count(wdt, val: wdt->count); |
353 | starfive_wdt_enable(wdt); |
354 | |
355 | exit: |
356 | starfive_wdt_lock(wdt); |
357 | return ret; |
358 | } |
359 | |
360 | static void starfive_wdt_stop(struct starfive_wdt *wdt) |
361 | { |
362 | starfive_wdt_unlock(wdt); |
363 | starfive_wdt_disable(wdt); |
364 | starfive_wdt_lock(wdt); |
365 | } |
366 | |
367 | static int starfive_wdt_pm_start(struct watchdog_device *wdd) |
368 | { |
369 | struct starfive_wdt *wdt = watchdog_get_drvdata(wdd); |
370 | int ret = pm_runtime_get_sync(dev: wdd->parent); |
371 | |
372 | if (ret < 0) |
373 | return ret; |
374 | |
375 | return starfive_wdt_start(wdt); |
376 | } |
377 | |
378 | static int starfive_wdt_pm_stop(struct watchdog_device *wdd) |
379 | { |
380 | struct starfive_wdt *wdt = watchdog_get_drvdata(wdd); |
381 | |
382 | starfive_wdt_stop(wdt); |
383 | return pm_runtime_put_sync(dev: wdd->parent); |
384 | } |
385 | |
386 | static int starfive_wdt_set_timeout(struct watchdog_device *wdd, |
387 | unsigned int timeout) |
388 | { |
389 | struct starfive_wdt *wdt = watchdog_get_drvdata(wdd); |
390 | unsigned long count = timeout * wdt->freq; |
391 | |
392 | /* some watchdogs take two timeouts to reset */ |
393 | if (wdt->variant->double_timeout) |
394 | count /= 2; |
395 | |
396 | wdt->count = count; |
397 | wdd->timeout = timeout; |
398 | |
399 | starfive_wdt_unlock(wdt); |
400 | starfive_wdt_disable(wdt); |
401 | starfive_wdt_set_reload_count(wdt, count: wdt->count); |
402 | starfive_wdt_enable(wdt); |
403 | starfive_wdt_lock(wdt); |
404 | |
405 | return 0; |
406 | } |
407 | |
408 | #define STARFIVE_WDT_OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) |
409 | |
410 | static const struct watchdog_info starfive_wdt_info = { |
411 | .options = STARFIVE_WDT_OPTIONS, |
412 | .identity = "StarFive Watchdog" , |
413 | }; |
414 | |
415 | static const struct watchdog_ops starfive_wdt_ops = { |
416 | .owner = THIS_MODULE, |
417 | .start = starfive_wdt_pm_start, |
418 | .stop = starfive_wdt_pm_stop, |
419 | .ping = starfive_wdt_keepalive, |
420 | .set_timeout = starfive_wdt_set_timeout, |
421 | .get_timeleft = starfive_wdt_get_timeleft, |
422 | }; |
423 | |
424 | static int starfive_wdt_probe(struct platform_device *pdev) |
425 | { |
426 | struct starfive_wdt *wdt; |
427 | int ret; |
428 | |
429 | wdt = devm_kzalloc(dev: &pdev->dev, size: sizeof(*wdt), GFP_KERNEL); |
430 | if (!wdt) |
431 | return -ENOMEM; |
432 | |
433 | wdt->base = devm_platform_ioremap_resource(pdev, index: 0); |
434 | if (IS_ERR(ptr: wdt->base)) |
435 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: wdt->base), fmt: "error mapping registers\n" ); |
436 | |
437 | wdt->wdd.parent = &pdev->dev; |
438 | ret = starfive_wdt_get_clock(wdt); |
439 | if (ret) |
440 | return ret; |
441 | |
442 | platform_set_drvdata(pdev, data: wdt); |
443 | pm_runtime_enable(dev: &pdev->dev); |
444 | if (pm_runtime_enabled(dev: &pdev->dev)) { |
445 | ret = pm_runtime_get_sync(dev: &pdev->dev); |
446 | if (ret < 0) |
447 | return ret; |
448 | } else { |
449 | /* runtime PM is disabled but clocks need to be enabled */ |
450 | ret = starfive_wdt_enable_clock(wdt); |
451 | if (ret) |
452 | return ret; |
453 | } |
454 | |
455 | ret = starfive_wdt_reset_init(dev: &pdev->dev); |
456 | if (ret) |
457 | goto err_exit; |
458 | |
459 | watchdog_set_drvdata(wdd: &wdt->wdd, data: wdt); |
460 | wdt->wdd.info = &starfive_wdt_info; |
461 | wdt->wdd.ops = &starfive_wdt_ops; |
462 | wdt->variant = of_device_get_match_data(dev: &pdev->dev); |
463 | spin_lock_init(&wdt->lock); |
464 | |
465 | wdt->freq = clk_get_rate(clk: wdt->core_clk); |
466 | if (!wdt->freq) { |
467 | dev_err(&pdev->dev, "get clock rate failed.\n" ); |
468 | ret = -EINVAL; |
469 | goto err_exit; |
470 | } |
471 | |
472 | wdt->wdd.min_timeout = 1; |
473 | wdt->wdd.max_timeout = starfive_wdt_max_timeout(wdt); |
474 | wdt->wdd.timeout = STARFIVE_WDT_DEFAULT_TIME; |
475 | watchdog_init_timeout(wdd: &wdt->wdd, timeout_parm: heartbeat, dev: &pdev->dev); |
476 | starfive_wdt_set_timeout(wdd: &wdt->wdd, timeout: wdt->wdd.timeout); |
477 | |
478 | watchdog_set_nowayout(wdd: &wdt->wdd, nowayout); |
479 | watchdog_stop_on_reboot(wdd: &wdt->wdd); |
480 | watchdog_stop_on_unregister(wdd: &wdt->wdd); |
481 | |
482 | if (early_enable) { |
483 | ret = starfive_wdt_start(wdt); |
484 | if (ret) |
485 | goto err_exit; |
486 | set_bit(WDOG_HW_RUNNING, addr: &wdt->wdd.status); |
487 | } else { |
488 | starfive_wdt_stop(wdt); |
489 | } |
490 | |
491 | ret = watchdog_register_device(&wdt->wdd); |
492 | if (ret) |
493 | goto err_exit; |
494 | |
495 | if (!early_enable) |
496 | pm_runtime_put_sync(dev: &pdev->dev); |
497 | |
498 | return 0; |
499 | |
500 | err_exit: |
501 | starfive_wdt_disable_clock(wdt); |
502 | pm_runtime_disable(dev: &pdev->dev); |
503 | |
504 | return ret; |
505 | } |
506 | |
507 | static int starfive_wdt_remove(struct platform_device *pdev) |
508 | { |
509 | struct starfive_wdt *wdt = platform_get_drvdata(pdev); |
510 | |
511 | starfive_wdt_stop(wdt); |
512 | watchdog_unregister_device(&wdt->wdd); |
513 | |
514 | if (pm_runtime_enabled(dev: &pdev->dev)) |
515 | pm_runtime_disable(dev: &pdev->dev); |
516 | else |
517 | /* disable clock without PM */ |
518 | starfive_wdt_disable_clock(wdt); |
519 | |
520 | return 0; |
521 | } |
522 | |
523 | static void starfive_wdt_shutdown(struct platform_device *pdev) |
524 | { |
525 | struct starfive_wdt *wdt = platform_get_drvdata(pdev); |
526 | |
527 | starfive_wdt_pm_stop(wdd: &wdt->wdd); |
528 | } |
529 | |
530 | static int starfive_wdt_suspend(struct device *dev) |
531 | { |
532 | struct starfive_wdt *wdt = dev_get_drvdata(dev); |
533 | |
534 | /* Save watchdog state, and turn it off. */ |
535 | wdt->reload = starfive_wdt_get_count(wdt); |
536 | |
537 | /* Note that WTCNT doesn't need to be saved. */ |
538 | starfive_wdt_stop(wdt); |
539 | |
540 | return pm_runtime_force_suspend(dev); |
541 | } |
542 | |
543 | static int starfive_wdt_resume(struct device *dev) |
544 | { |
545 | struct starfive_wdt *wdt = dev_get_drvdata(dev); |
546 | int ret; |
547 | |
548 | ret = pm_runtime_force_resume(dev); |
549 | if (ret) |
550 | return ret; |
551 | |
552 | starfive_wdt_unlock(wdt); |
553 | /* Restore watchdog state. */ |
554 | starfive_wdt_set_reload_count(wdt, count: wdt->reload); |
555 | starfive_wdt_lock(wdt); |
556 | |
557 | return starfive_wdt_start(wdt); |
558 | } |
559 | |
560 | static int starfive_wdt_runtime_suspend(struct device *dev) |
561 | { |
562 | struct starfive_wdt *wdt = dev_get_drvdata(dev); |
563 | |
564 | starfive_wdt_disable_clock(wdt); |
565 | |
566 | return 0; |
567 | } |
568 | |
569 | static int starfive_wdt_runtime_resume(struct device *dev) |
570 | { |
571 | struct starfive_wdt *wdt = dev_get_drvdata(dev); |
572 | |
573 | return starfive_wdt_enable_clock(wdt); |
574 | } |
575 | |
576 | static const struct dev_pm_ops starfive_wdt_pm_ops = { |
577 | RUNTIME_PM_OPS(starfive_wdt_runtime_suspend, starfive_wdt_runtime_resume, NULL) |
578 | SYSTEM_SLEEP_PM_OPS(starfive_wdt_suspend, starfive_wdt_resume) |
579 | }; |
580 | |
581 | static const struct of_device_id starfive_wdt_match[] = { |
582 | { .compatible = "starfive,jh7100-wdt" , .data = &starfive_wdt_jh7100_variant }, |
583 | { .compatible = "starfive,jh7110-wdt" , .data = &starfive_wdt_jh7110_variant }, |
584 | { /* sentinel */ } |
585 | }; |
586 | MODULE_DEVICE_TABLE(of, starfive_wdt_match); |
587 | |
588 | static struct platform_driver starfive_wdt_driver = { |
589 | .probe = starfive_wdt_probe, |
590 | .remove = starfive_wdt_remove, |
591 | .shutdown = starfive_wdt_shutdown, |
592 | .driver = { |
593 | .name = "starfive-wdt" , |
594 | .pm = pm_ptr(&starfive_wdt_pm_ops), |
595 | .of_match_table = starfive_wdt_match, |
596 | }, |
597 | }; |
598 | module_platform_driver(starfive_wdt_driver); |
599 | |
600 | MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>" ); |
601 | MODULE_AUTHOR("Samin Guo <samin.guo@starfivetech.com>" ); |
602 | MODULE_DESCRIPTION("StarFive Watchdog Device Driver" ); |
603 | MODULE_LICENSE("GPL" ); |
604 | |