1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ACPI Hardware Watchdog (WDAT) driver. |
4 | * |
5 | * Copyright (C) 2016, Intel Corporation |
6 | * Author: Mika Westerberg <mika.westerberg@linux.intel.com> |
7 | */ |
8 | |
9 | #include <linux/acpi.h> |
10 | #include <linux/ioport.h> |
11 | #include <linux/module.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/pm.h> |
14 | #include <linux/watchdog.h> |
15 | |
16 | #define MAX_WDAT_ACTIONS ACPI_WDAT_ACTION_RESERVED |
17 | |
18 | /** |
19 | * struct wdat_instruction - Single ACPI WDAT instruction |
20 | * @entry: Copy of the ACPI table instruction |
21 | * @reg: Register the instruction is accessing |
22 | * @node: Next instruction in action sequence |
23 | */ |
24 | struct wdat_instruction { |
25 | struct acpi_wdat_entry entry; |
26 | void __iomem *reg; |
27 | struct list_head node; |
28 | }; |
29 | |
30 | /** |
31 | * struct wdat_wdt - ACPI WDAT watchdog device |
32 | * @pdev: Parent platform device |
33 | * @wdd: Watchdog core device |
34 | * @period: How long is one watchdog period in ms |
35 | * @stopped_in_sleep: Is this watchdog stopped by the firmware in S1-S5 |
36 | * @stopped: Was the watchdog stopped by the driver in suspend |
37 | * @instructions: An array of instruction lists indexed by an action number from |
38 | * the WDAT table. There can be %NULL entries for not implemented |
39 | * actions. |
40 | */ |
41 | struct wdat_wdt { |
42 | struct platform_device *pdev; |
43 | struct watchdog_device wdd; |
44 | unsigned int period; |
45 | bool stopped_in_sleep; |
46 | bool stopped; |
47 | struct list_head *instructions[MAX_WDAT_ACTIONS]; |
48 | }; |
49 | |
50 | #define to_wdat_wdt(wdd) container_of(wdd, struct wdat_wdt, wdd) |
51 | |
52 | static bool nowayout = WATCHDOG_NOWAYOUT; |
53 | module_param(nowayout, bool, 0); |
54 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
55 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
56 | |
57 | #define WDAT_DEFAULT_TIMEOUT 30 |
58 | |
59 | static int timeout = WDAT_DEFAULT_TIMEOUT; |
60 | module_param(timeout, int, 0); |
61 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" |
62 | __MODULE_STRING(WDAT_DEFAULT_TIMEOUT) ")" ); |
63 | |
64 | static int wdat_wdt_read(struct wdat_wdt *wdat, |
65 | const struct wdat_instruction *instr, u32 *value) |
66 | { |
67 | const struct acpi_generic_address *gas = &instr->entry.register_region; |
68 | |
69 | switch (gas->access_width) { |
70 | case 1: |
71 | *value = ioread8(instr->reg); |
72 | break; |
73 | case 2: |
74 | *value = ioread16(instr->reg); |
75 | break; |
76 | case 3: |
77 | *value = ioread32(instr->reg); |
78 | break; |
79 | default: |
80 | return -EINVAL; |
81 | } |
82 | |
83 | dev_dbg(&wdat->pdev->dev, "Read %#x from 0x%08llx\n" , *value, |
84 | gas->address); |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | static int wdat_wdt_write(struct wdat_wdt *wdat, |
90 | const struct wdat_instruction *instr, u32 value) |
91 | { |
92 | const struct acpi_generic_address *gas = &instr->entry.register_region; |
93 | |
94 | switch (gas->access_width) { |
95 | case 1: |
96 | iowrite8((u8)value, instr->reg); |
97 | break; |
98 | case 2: |
99 | iowrite16((u16)value, instr->reg); |
100 | break; |
101 | case 3: |
102 | iowrite32(value, instr->reg); |
103 | break; |
104 | default: |
105 | return -EINVAL; |
106 | } |
107 | |
108 | dev_dbg(&wdat->pdev->dev, "Wrote %#x to 0x%08llx\n" , value, |
109 | gas->address); |
110 | |
111 | return 0; |
112 | } |
113 | |
114 | static int wdat_wdt_run_action(struct wdat_wdt *wdat, unsigned int action, |
115 | u32 param, u32 *retval) |
116 | { |
117 | struct wdat_instruction *instr; |
118 | |
119 | if (action >= ARRAY_SIZE(wdat->instructions)) |
120 | return -EINVAL; |
121 | |
122 | if (!wdat->instructions[action]) |
123 | return -EOPNOTSUPP; |
124 | |
125 | dev_dbg(&wdat->pdev->dev, "Running action %#x\n" , action); |
126 | |
127 | /* Run each instruction sequentially */ |
128 | list_for_each_entry(instr, wdat->instructions[action], node) { |
129 | const struct acpi_wdat_entry *entry = &instr->entry; |
130 | const struct acpi_generic_address *gas; |
131 | u32 flags, value, mask, x, y; |
132 | bool preserve; |
133 | int ret; |
134 | |
135 | gas = &entry->register_region; |
136 | |
137 | preserve = entry->instruction & ACPI_WDAT_PRESERVE_REGISTER; |
138 | flags = entry->instruction & ~ACPI_WDAT_PRESERVE_REGISTER; |
139 | value = entry->value; |
140 | mask = entry->mask; |
141 | |
142 | switch (flags) { |
143 | case ACPI_WDAT_READ_VALUE: |
144 | ret = wdat_wdt_read(wdat, instr, value: &x); |
145 | if (ret) |
146 | return ret; |
147 | x >>= gas->bit_offset; |
148 | x &= mask; |
149 | if (retval) |
150 | *retval = x == value; |
151 | break; |
152 | |
153 | case ACPI_WDAT_READ_COUNTDOWN: |
154 | ret = wdat_wdt_read(wdat, instr, value: &x); |
155 | if (ret) |
156 | return ret; |
157 | x >>= gas->bit_offset; |
158 | x &= mask; |
159 | if (retval) |
160 | *retval = x; |
161 | break; |
162 | |
163 | case ACPI_WDAT_WRITE_VALUE: |
164 | x = value & mask; |
165 | x <<= gas->bit_offset; |
166 | if (preserve) { |
167 | ret = wdat_wdt_read(wdat, instr, value: &y); |
168 | if (ret) |
169 | return ret; |
170 | y = y & ~(mask << gas->bit_offset); |
171 | x |= y; |
172 | } |
173 | ret = wdat_wdt_write(wdat, instr, value: x); |
174 | if (ret) |
175 | return ret; |
176 | break; |
177 | |
178 | case ACPI_WDAT_WRITE_COUNTDOWN: |
179 | x = param; |
180 | x &= mask; |
181 | x <<= gas->bit_offset; |
182 | if (preserve) { |
183 | ret = wdat_wdt_read(wdat, instr, value: &y); |
184 | if (ret) |
185 | return ret; |
186 | y = y & ~(mask << gas->bit_offset); |
187 | x |= y; |
188 | } |
189 | ret = wdat_wdt_write(wdat, instr, value: x); |
190 | if (ret) |
191 | return ret; |
192 | break; |
193 | |
194 | default: |
195 | dev_err(&wdat->pdev->dev, "Unknown instruction: %u\n" , |
196 | flags); |
197 | return -EINVAL; |
198 | } |
199 | } |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | static int wdat_wdt_enable_reboot(struct wdat_wdt *wdat) |
205 | { |
206 | int ret; |
207 | |
208 | /* |
209 | * WDAT specification says that the watchdog is required to reboot |
210 | * the system when it fires. However, it also states that it is |
211 | * recommended to make it configurable through hardware register. We |
212 | * enable reboot now if it is configurable, just in case. |
213 | */ |
214 | ret = wdat_wdt_run_action(wdat, action: ACPI_WDAT_SET_REBOOT, param: 0, NULL); |
215 | if (ret && ret != -EOPNOTSUPP) { |
216 | dev_err(&wdat->pdev->dev, |
217 | "Failed to enable reboot when watchdog triggers\n" ); |
218 | return ret; |
219 | } |
220 | |
221 | return 0; |
222 | } |
223 | |
224 | static void wdat_wdt_boot_status(struct wdat_wdt *wdat) |
225 | { |
226 | u32 boot_status = 0; |
227 | int ret; |
228 | |
229 | ret = wdat_wdt_run_action(wdat, action: ACPI_WDAT_GET_STATUS, param: 0, retval: &boot_status); |
230 | if (ret && ret != -EOPNOTSUPP) { |
231 | dev_err(&wdat->pdev->dev, "Failed to read boot status\n" ); |
232 | return; |
233 | } |
234 | |
235 | if (boot_status) |
236 | wdat->wdd.bootstatus = WDIOF_CARDRESET; |
237 | |
238 | /* Clear the boot status in case BIOS did not do it */ |
239 | ret = wdat_wdt_run_action(wdat, action: ACPI_WDAT_SET_STATUS, param: 0, NULL); |
240 | if (ret && ret != -EOPNOTSUPP) |
241 | dev_err(&wdat->pdev->dev, "Failed to clear boot status\n" ); |
242 | } |
243 | |
244 | static void wdat_wdt_set_running(struct wdat_wdt *wdat) |
245 | { |
246 | u32 running = 0; |
247 | int ret; |
248 | |
249 | ret = wdat_wdt_run_action(wdat, action: ACPI_WDAT_GET_RUNNING_STATE, param: 0, |
250 | retval: &running); |
251 | if (ret && ret != -EOPNOTSUPP) |
252 | dev_err(&wdat->pdev->dev, "Failed to read running state\n" ); |
253 | |
254 | if (running) |
255 | set_bit(WDOG_HW_RUNNING, addr: &wdat->wdd.status); |
256 | } |
257 | |
258 | static int wdat_wdt_start(struct watchdog_device *wdd) |
259 | { |
260 | return wdat_wdt_run_action(to_wdat_wdt(wdd), |
261 | action: ACPI_WDAT_SET_RUNNING_STATE, param: 0, NULL); |
262 | } |
263 | |
264 | static int wdat_wdt_stop(struct watchdog_device *wdd) |
265 | { |
266 | return wdat_wdt_run_action(to_wdat_wdt(wdd), |
267 | action: ACPI_WDAT_SET_STOPPED_STATE, param: 0, NULL); |
268 | } |
269 | |
270 | static int wdat_wdt_ping(struct watchdog_device *wdd) |
271 | { |
272 | return wdat_wdt_run_action(to_wdat_wdt(wdd), action: ACPI_WDAT_RESET, param: 0, NULL); |
273 | } |
274 | |
275 | static int wdat_wdt_set_timeout(struct watchdog_device *wdd, |
276 | unsigned int timeout) |
277 | { |
278 | struct wdat_wdt *wdat = to_wdat_wdt(wdd); |
279 | unsigned int periods; |
280 | int ret; |
281 | |
282 | periods = timeout * 1000 / wdat->period; |
283 | ret = wdat_wdt_run_action(wdat, action: ACPI_WDAT_SET_COUNTDOWN, param: periods, NULL); |
284 | if (!ret) |
285 | wdd->timeout = timeout; |
286 | return ret; |
287 | } |
288 | |
289 | static unsigned int wdat_wdt_get_timeleft(struct watchdog_device *wdd) |
290 | { |
291 | struct wdat_wdt *wdat = to_wdat_wdt(wdd); |
292 | u32 periods = 0; |
293 | |
294 | wdat_wdt_run_action(wdat, action: ACPI_WDAT_GET_CURRENT_COUNTDOWN, param: 0, retval: &periods); |
295 | return periods * wdat->period / 1000; |
296 | } |
297 | |
298 | static const struct watchdog_info wdat_wdt_info = { |
299 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, |
300 | .firmware_version = 0, |
301 | .identity = "wdat_wdt" , |
302 | }; |
303 | |
304 | static struct watchdog_ops wdat_wdt_ops = { |
305 | .owner = THIS_MODULE, |
306 | .start = wdat_wdt_start, |
307 | .stop = wdat_wdt_stop, |
308 | .ping = wdat_wdt_ping, |
309 | .set_timeout = wdat_wdt_set_timeout, |
310 | }; |
311 | |
312 | static int wdat_wdt_probe(struct platform_device *pdev) |
313 | { |
314 | struct device *dev = &pdev->dev; |
315 | const struct acpi_wdat_entry *entries; |
316 | const struct acpi_table_wdat *tbl; |
317 | struct wdat_wdt *wdat; |
318 | struct resource *res; |
319 | void __iomem **regs; |
320 | acpi_status status; |
321 | int i, ret; |
322 | |
323 | status = acpi_get_table(ACPI_SIG_WDAT, instance: 0, |
324 | out_table: (struct acpi_table_header **)&tbl); |
325 | if (ACPI_FAILURE(status)) |
326 | return -ENODEV; |
327 | |
328 | wdat = devm_kzalloc(dev, size: sizeof(*wdat), GFP_KERNEL); |
329 | if (!wdat) |
330 | return -ENOMEM; |
331 | |
332 | regs = devm_kcalloc(dev, n: pdev->num_resources, size: sizeof(*regs), |
333 | GFP_KERNEL); |
334 | if (!regs) |
335 | return -ENOMEM; |
336 | |
337 | /* WDAT specification wants to have >= 1ms period */ |
338 | if (tbl->timer_period < 1) |
339 | return -EINVAL; |
340 | if (tbl->min_count > tbl->max_count) |
341 | return -EINVAL; |
342 | |
343 | wdat->period = tbl->timer_period; |
344 | wdat->wdd.min_timeout = DIV_ROUND_UP(wdat->period * tbl->min_count, 1000); |
345 | wdat->wdd.max_timeout = wdat->period * tbl->max_count / 1000; |
346 | wdat->stopped_in_sleep = tbl->flags & ACPI_WDAT_STOPPED; |
347 | wdat->wdd.info = &wdat_wdt_info; |
348 | wdat->wdd.ops = &wdat_wdt_ops; |
349 | wdat->pdev = pdev; |
350 | |
351 | /* Request and map all resources */ |
352 | for (i = 0; i < pdev->num_resources; i++) { |
353 | void __iomem *reg; |
354 | |
355 | res = &pdev->resource[i]; |
356 | if (resource_type(res) == IORESOURCE_MEM) { |
357 | reg = devm_ioremap_resource(dev, res); |
358 | if (IS_ERR(ptr: reg)) |
359 | return PTR_ERR(ptr: reg); |
360 | } else if (resource_type(res) == IORESOURCE_IO) { |
361 | reg = devm_ioport_map(dev, port: res->start, nr: 1); |
362 | if (!reg) |
363 | return -ENOMEM; |
364 | } else { |
365 | dev_err(dev, "Unsupported resource\n" ); |
366 | return -EINVAL; |
367 | } |
368 | |
369 | regs[i] = reg; |
370 | } |
371 | |
372 | entries = (struct acpi_wdat_entry *)(tbl + 1); |
373 | for (i = 0; i < tbl->entries; i++) { |
374 | const struct acpi_generic_address *gas; |
375 | struct wdat_instruction *instr; |
376 | struct list_head *instructions; |
377 | unsigned int action; |
378 | struct resource r; |
379 | int j; |
380 | |
381 | action = entries[i].action; |
382 | if (action >= MAX_WDAT_ACTIONS) { |
383 | dev_dbg(dev, "Skipping unknown action: %u\n" , action); |
384 | continue; |
385 | } |
386 | |
387 | instr = devm_kzalloc(dev, size: sizeof(*instr), GFP_KERNEL); |
388 | if (!instr) |
389 | return -ENOMEM; |
390 | |
391 | INIT_LIST_HEAD(list: &instr->node); |
392 | instr->entry = entries[i]; |
393 | |
394 | gas = &entries[i].register_region; |
395 | |
396 | memset(&r, 0, sizeof(r)); |
397 | r.start = gas->address; |
398 | r.end = r.start + ACPI_ACCESS_BYTE_WIDTH(gas->access_width) - 1; |
399 | if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { |
400 | r.flags = IORESOURCE_MEM; |
401 | } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { |
402 | r.flags = IORESOURCE_IO; |
403 | } else { |
404 | dev_dbg(dev, "Unsupported address space: %d\n" , |
405 | gas->space_id); |
406 | continue; |
407 | } |
408 | |
409 | /* Find the matching resource */ |
410 | for (j = 0; j < pdev->num_resources; j++) { |
411 | res = &pdev->resource[j]; |
412 | if (resource_contains(r1: res, r2: &r)) { |
413 | instr->reg = regs[j] + r.start - res->start; |
414 | break; |
415 | } |
416 | } |
417 | |
418 | if (!instr->reg) { |
419 | dev_err(dev, "I/O resource not found\n" ); |
420 | return -EINVAL; |
421 | } |
422 | |
423 | instructions = wdat->instructions[action]; |
424 | if (!instructions) { |
425 | instructions = devm_kzalloc(dev, |
426 | size: sizeof(*instructions), |
427 | GFP_KERNEL); |
428 | if (!instructions) |
429 | return -ENOMEM; |
430 | |
431 | INIT_LIST_HEAD(list: instructions); |
432 | wdat->instructions[action] = instructions; |
433 | } |
434 | |
435 | list_add_tail(new: &instr->node, head: instructions); |
436 | } |
437 | |
438 | if (wdat->instructions[ACPI_WDAT_GET_CURRENT_COUNTDOWN]) |
439 | wdat_wdt_ops.get_timeleft = wdat_wdt_get_timeleft; |
440 | |
441 | wdat_wdt_boot_status(wdat); |
442 | wdat_wdt_set_running(wdat); |
443 | |
444 | ret = wdat_wdt_enable_reboot(wdat); |
445 | if (ret) |
446 | return ret; |
447 | |
448 | platform_set_drvdata(pdev, data: wdat); |
449 | |
450 | /* |
451 | * Set initial timeout so that userspace has time to configure the |
452 | * watchdog properly after it has opened the device. In some cases |
453 | * the BIOS default is too short and causes immediate reboot. |
454 | */ |
455 | if (watchdog_timeout_invalid(wdd: &wdat->wdd, t: timeout)) { |
456 | dev_warn(dev, "Invalid timeout %d given, using %d\n" , |
457 | timeout, WDAT_DEFAULT_TIMEOUT); |
458 | timeout = WDAT_DEFAULT_TIMEOUT; |
459 | } |
460 | |
461 | ret = wdat_wdt_set_timeout(wdd: &wdat->wdd, timeout); |
462 | if (ret) |
463 | return ret; |
464 | |
465 | watchdog_set_nowayout(wdd: &wdat->wdd, nowayout); |
466 | watchdog_stop_on_reboot(wdd: &wdat->wdd); |
467 | watchdog_stop_on_unregister(wdd: &wdat->wdd); |
468 | return devm_watchdog_register_device(dev, &wdat->wdd); |
469 | } |
470 | |
471 | static int wdat_wdt_suspend_noirq(struct device *dev) |
472 | { |
473 | struct wdat_wdt *wdat = dev_get_drvdata(dev); |
474 | int ret; |
475 | |
476 | if (!watchdog_active(wdd: &wdat->wdd)) |
477 | return 0; |
478 | |
479 | /* |
480 | * We need to stop the watchdog if firmware is not doing it or if we |
481 | * are going suspend to idle (where firmware is not involved). If |
482 | * firmware is stopping the watchdog we kick it here one more time |
483 | * to give it some time. |
484 | */ |
485 | wdat->stopped = false; |
486 | if (acpi_target_system_state() == ACPI_STATE_S0 || |
487 | !wdat->stopped_in_sleep) { |
488 | ret = wdat_wdt_stop(wdd: &wdat->wdd); |
489 | if (!ret) |
490 | wdat->stopped = true; |
491 | } else { |
492 | ret = wdat_wdt_ping(wdd: &wdat->wdd); |
493 | } |
494 | |
495 | return ret; |
496 | } |
497 | |
498 | static int wdat_wdt_resume_noirq(struct device *dev) |
499 | { |
500 | struct wdat_wdt *wdat = dev_get_drvdata(dev); |
501 | int ret; |
502 | |
503 | if (!watchdog_active(wdd: &wdat->wdd)) |
504 | return 0; |
505 | |
506 | if (!wdat->stopped) { |
507 | /* |
508 | * Looks like the boot firmware reinitializes the watchdog |
509 | * before it hands off to the OS on resume from sleep so we |
510 | * stop and reprogram the watchdog here. |
511 | */ |
512 | ret = wdat_wdt_stop(wdd: &wdat->wdd); |
513 | if (ret) |
514 | return ret; |
515 | |
516 | ret = wdat_wdt_set_timeout(wdd: &wdat->wdd, timeout: wdat->wdd.timeout); |
517 | if (ret) |
518 | return ret; |
519 | |
520 | ret = wdat_wdt_enable_reboot(wdat); |
521 | if (ret) |
522 | return ret; |
523 | |
524 | ret = wdat_wdt_ping(wdd: &wdat->wdd); |
525 | if (ret) |
526 | return ret; |
527 | } |
528 | |
529 | return wdat_wdt_start(wdd: &wdat->wdd); |
530 | } |
531 | |
532 | static const struct dev_pm_ops wdat_wdt_pm_ops = { |
533 | NOIRQ_SYSTEM_SLEEP_PM_OPS(wdat_wdt_suspend_noirq, wdat_wdt_resume_noirq) |
534 | }; |
535 | |
536 | static struct platform_driver wdat_wdt_driver = { |
537 | .probe = wdat_wdt_probe, |
538 | .driver = { |
539 | .name = "wdat_wdt" , |
540 | .pm = pm_sleep_ptr(&wdat_wdt_pm_ops), |
541 | }, |
542 | }; |
543 | |
544 | module_platform_driver(wdat_wdt_driver); |
545 | |
546 | MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>" ); |
547 | MODULE_DESCRIPTION("ACPI Hardware Watchdog (WDAT) driver" ); |
548 | MODULE_LICENSE("GPL v2" ); |
549 | MODULE_ALIAS("platform:wdat_wdt" ); |
550 | |