1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * w83627hf/thf WDT driver |
4 | * |
5 | * (c) Copyright 2013 Guenter Roeck |
6 | * converted to watchdog infrastructure |
7 | * |
8 | * (c) Copyright 2007 Vlad Drukker <vlad@storewiz.com> |
9 | * added support for W83627THF. |
10 | * |
11 | * (c) Copyright 2003,2007 Pádraig Brady <P@draigBrady.com> |
12 | * |
13 | * Based on advantechwdt.c which is based on wdt.c. |
14 | * Original copyright messages: |
15 | * |
16 | * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> |
17 | * |
18 | * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, |
19 | * All Rights Reserved. |
20 | * |
21 | * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide |
22 | * warranty for any of this software. This material is provided |
23 | * "AS-IS" and at no charge. |
24 | * |
25 | * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> |
26 | */ |
27 | |
28 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
29 | |
30 | #include <linux/module.h> |
31 | #include <linux/moduleparam.h> |
32 | #include <linux/types.h> |
33 | #include <linux/watchdog.h> |
34 | #include <linux/ioport.h> |
35 | #include <linux/init.h> |
36 | #include <linux/io.h> |
37 | #include <linux/dmi.h> |
38 | |
39 | #define WATCHDOG_NAME "w83627hf/thf/hg/dhg WDT" |
40 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ |
41 | |
42 | static int wdt_io; |
43 | static int cr_wdt_timeout; /* WDT timeout register */ |
44 | static int cr_wdt_control; /* WDT control register */ |
45 | static int cr_wdt_csr; /* WDT control & status register */ |
46 | static int wdt_cfg_enter = 0x87;/* key to unlock configuration space */ |
47 | static int wdt_cfg_leave = 0xAA;/* key to lock configuration space */ |
48 | |
49 | enum chips { w83627hf, w83627s, w83697hf, w83697ug, w83637hf, w83627thf, |
50 | w83687thf, w83627ehf, w83627dhg, w83627uhg, w83667hg, w83627dhg_p, |
51 | w83667hg_b, nct6775, nct6776, nct6779, nct6791, nct6792, nct6793, |
52 | nct6795, nct6796, nct6102, nct6116 }; |
53 | |
54 | static int timeout; /* in seconds */ |
55 | module_param(timeout, int, 0); |
56 | MODULE_PARM_DESC(timeout, |
57 | "Watchdog timeout in seconds. 1 <= timeout <= 255, default=" |
58 | __MODULE_STRING(WATCHDOG_TIMEOUT) "." ); |
59 | |
60 | static bool nowayout = WATCHDOG_NOWAYOUT; |
61 | module_param(nowayout, bool, 0); |
62 | MODULE_PARM_DESC(nowayout, |
63 | "Watchdog cannot be stopped once started (default=" |
64 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
65 | |
66 | static int early_disable; |
67 | module_param(early_disable, int, 0); |
68 | MODULE_PARM_DESC(early_disable, "Disable watchdog at boot time (default=0)" ); |
69 | |
70 | /* |
71 | * Kernel methods. |
72 | */ |
73 | |
74 | #define WDT_EFER (wdt_io+0) /* Extended Function Enable Registers */ |
75 | #define WDT_EFIR (wdt_io+0) /* Extended Function Index Register |
76 | (same as EFER) */ |
77 | #define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */ |
78 | |
79 | #define W83627HF_LD_WDT 0x08 |
80 | |
81 | #define W83627HF_ID 0x52 |
82 | #define W83627S_ID 0x59 |
83 | #define W83697HF_ID 0x60 |
84 | #define W83697UG_ID 0x68 |
85 | #define W83637HF_ID 0x70 |
86 | #define W83627THF_ID 0x82 |
87 | #define W83687THF_ID 0x85 |
88 | #define W83627EHF_ID 0x88 |
89 | #define W83627DHG_ID 0xa0 |
90 | #define W83627UHG_ID 0xa2 |
91 | #define W83667HG_ID 0xa5 |
92 | #define W83627DHG_P_ID 0xb0 |
93 | #define W83667HG_B_ID 0xb3 |
94 | #define NCT6775_ID 0xb4 |
95 | #define NCT6776_ID 0xc3 |
96 | #define NCT6102_ID 0xc4 |
97 | #define NCT6116_ID 0xd2 |
98 | #define NCT6779_ID 0xc5 |
99 | #define NCT6791_ID 0xc8 |
100 | #define NCT6792_ID 0xc9 |
101 | #define NCT6793_ID 0xd1 |
102 | #define NCT6795_ID 0xd3 |
103 | #define NCT6796_ID 0xd4 /* also NCT9697D, NCT9698D */ |
104 | |
105 | #define W83627HF_WDT_TIMEOUT 0xf6 |
106 | #define W83697HF_WDT_TIMEOUT 0xf4 |
107 | #define NCT6102D_WDT_TIMEOUT 0xf1 |
108 | |
109 | #define W83627HF_WDT_CONTROL 0xf5 |
110 | #define W83697HF_WDT_CONTROL 0xf3 |
111 | #define NCT6102D_WDT_CONTROL 0xf0 |
112 | |
113 | #define W836X7HF_WDT_CSR 0xf7 |
114 | #define NCT6102D_WDT_CSR 0xf2 |
115 | |
116 | #define WDT_CSR_STATUS 0x10 |
117 | #define WDT_CSR_KBD 0x40 |
118 | #define WDT_CSR_MOUSE 0x80 |
119 | |
120 | static void superio_outb(int reg, int val) |
121 | { |
122 | outb(value: reg, WDT_EFER); |
123 | outb(value: val, WDT_EFDR); |
124 | } |
125 | |
126 | static inline int superio_inb(int reg) |
127 | { |
128 | outb(value: reg, WDT_EFER); |
129 | return inb(WDT_EFDR); |
130 | } |
131 | |
132 | static int superio_enter(void) |
133 | { |
134 | if (!request_muxed_region(wdt_io, 2, WATCHDOG_NAME)) |
135 | return -EBUSY; |
136 | |
137 | outb_p(value: wdt_cfg_enter, WDT_EFER); /* Enter extended function mode */ |
138 | outb_p(value: wdt_cfg_enter, WDT_EFER); /* Again according to manual */ |
139 | |
140 | return 0; |
141 | } |
142 | |
143 | static void superio_select(int ld) |
144 | { |
145 | superio_outb(reg: 0x07, val: ld); |
146 | } |
147 | |
148 | static void superio_exit(void) |
149 | { |
150 | outb_p(value: wdt_cfg_leave, WDT_EFER); /* Leave extended function mode */ |
151 | release_region(wdt_io, 2); |
152 | } |
153 | |
154 | static int w83627hf_init(struct watchdog_device *wdog, enum chips chip) |
155 | { |
156 | int ret; |
157 | unsigned char t; |
158 | |
159 | ret = superio_enter(); |
160 | if (ret) |
161 | return ret; |
162 | |
163 | superio_select(W83627HF_LD_WDT); |
164 | |
165 | /* set CR30 bit 0 to activate GPIO2 */ |
166 | t = superio_inb(reg: 0x30); |
167 | if (!(t & 0x01)) |
168 | superio_outb(reg: 0x30, val: t | 0x01); |
169 | |
170 | switch (chip) { |
171 | case w83627hf: |
172 | case w83627s: |
173 | t = superio_inb(reg: 0x2B) & ~0x10; |
174 | superio_outb(reg: 0x2B, val: t); /* set GPIO24 to WDT0 */ |
175 | break; |
176 | case w83697hf: |
177 | /* Set pin 119 to WDTO# mode (= CR29, WDT0) */ |
178 | t = superio_inb(reg: 0x29) & ~0x60; |
179 | t |= 0x20; |
180 | superio_outb(reg: 0x29, val: t); |
181 | break; |
182 | case w83697ug: |
183 | /* Set pin 118 to WDTO# mode */ |
184 | t = superio_inb(reg: 0x2b) & ~0x04; |
185 | superio_outb(reg: 0x2b, val: t); |
186 | break; |
187 | case w83627thf: |
188 | t = (superio_inb(reg: 0x2B) & ~0x08) | 0x04; |
189 | superio_outb(reg: 0x2B, val: t); /* set GPIO3 to WDT0 */ |
190 | break; |
191 | case w83627dhg: |
192 | case w83627dhg_p: |
193 | t = superio_inb(reg: 0x2D) & ~0x01; /* PIN77 -> WDT0# */ |
194 | superio_outb(reg: 0x2D, val: t); /* set GPIO5 to WDT0 */ |
195 | t = superio_inb(reg: cr_wdt_control); |
196 | t |= 0x02; /* enable the WDTO# output low pulse |
197 | * to the KBRST# pin */ |
198 | superio_outb(reg: cr_wdt_control, val: t); |
199 | break; |
200 | case w83637hf: |
201 | break; |
202 | case w83687thf: |
203 | t = superio_inb(reg: 0x2C) & ~0x80; /* PIN47 -> WDT0# */ |
204 | superio_outb(reg: 0x2C, val: t); |
205 | break; |
206 | case w83627ehf: |
207 | case w83627uhg: |
208 | case w83667hg: |
209 | case w83667hg_b: |
210 | case nct6775: |
211 | case nct6776: |
212 | case nct6779: |
213 | case nct6791: |
214 | case nct6792: |
215 | case nct6793: |
216 | case nct6795: |
217 | case nct6796: |
218 | case nct6102: |
219 | case nct6116: |
220 | /* |
221 | * These chips have a fixed WDTO# output pin (W83627UHG), |
222 | * or support more than one WDTO# output pin. |
223 | * Don't touch its configuration, and hope the BIOS |
224 | * does the right thing. |
225 | */ |
226 | t = superio_inb(reg: cr_wdt_control); |
227 | t |= 0x02; /* enable the WDTO# output low pulse |
228 | * to the KBRST# pin */ |
229 | superio_outb(reg: cr_wdt_control, val: t); |
230 | break; |
231 | default: |
232 | break; |
233 | } |
234 | |
235 | t = superio_inb(reg: cr_wdt_timeout); |
236 | if (t != 0) { |
237 | if (early_disable) { |
238 | pr_warn("Stopping previously enabled watchdog until userland kicks in\n" ); |
239 | superio_outb(reg: cr_wdt_timeout, val: 0); |
240 | } else { |
241 | pr_info("Watchdog already running. Resetting timeout to %d sec\n" , |
242 | wdog->timeout); |
243 | superio_outb(reg: cr_wdt_timeout, val: wdog->timeout); |
244 | } |
245 | } |
246 | |
247 | /* set second mode & disable keyboard turning off watchdog */ |
248 | t = superio_inb(reg: cr_wdt_control) & ~0x0C; |
249 | superio_outb(reg: cr_wdt_control, val: t); |
250 | |
251 | t = superio_inb(reg: cr_wdt_csr); |
252 | if (t & WDT_CSR_STATUS) |
253 | wdog->bootstatus |= WDIOF_CARDRESET; |
254 | |
255 | /* reset status, disable keyboard & mouse turning off watchdog */ |
256 | t &= ~(WDT_CSR_STATUS | WDT_CSR_KBD | WDT_CSR_MOUSE); |
257 | superio_outb(reg: cr_wdt_csr, val: t); |
258 | |
259 | superio_exit(); |
260 | |
261 | return 0; |
262 | } |
263 | |
264 | static int wdt_set_time(unsigned int timeout) |
265 | { |
266 | int ret; |
267 | |
268 | ret = superio_enter(); |
269 | if (ret) |
270 | return ret; |
271 | |
272 | superio_select(W83627HF_LD_WDT); |
273 | superio_outb(reg: cr_wdt_timeout, val: timeout); |
274 | superio_exit(); |
275 | |
276 | return 0; |
277 | } |
278 | |
279 | static int wdt_start(struct watchdog_device *wdog) |
280 | { |
281 | return wdt_set_time(timeout: wdog->timeout); |
282 | } |
283 | |
284 | static int wdt_stop(struct watchdog_device *wdog) |
285 | { |
286 | return wdt_set_time(timeout: 0); |
287 | } |
288 | |
289 | static int wdt_set_timeout(struct watchdog_device *wdog, unsigned int timeout) |
290 | { |
291 | wdog->timeout = timeout; |
292 | |
293 | return 0; |
294 | } |
295 | |
296 | static unsigned int wdt_get_time(struct watchdog_device *wdog) |
297 | { |
298 | unsigned int timeleft; |
299 | int ret; |
300 | |
301 | ret = superio_enter(); |
302 | if (ret) |
303 | return 0; |
304 | |
305 | superio_select(W83627HF_LD_WDT); |
306 | timeleft = superio_inb(reg: cr_wdt_timeout); |
307 | superio_exit(); |
308 | |
309 | return timeleft; |
310 | } |
311 | |
312 | /* |
313 | * Kernel Interfaces |
314 | */ |
315 | |
316 | static const struct watchdog_info wdt_info = { |
317 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, |
318 | .identity = "W83627HF Watchdog" , |
319 | }; |
320 | |
321 | static const struct watchdog_ops wdt_ops = { |
322 | .owner = THIS_MODULE, |
323 | .start = wdt_start, |
324 | .stop = wdt_stop, |
325 | .set_timeout = wdt_set_timeout, |
326 | .get_timeleft = wdt_get_time, |
327 | }; |
328 | |
329 | static struct watchdog_device wdt_dev = { |
330 | .info = &wdt_info, |
331 | .ops = &wdt_ops, |
332 | .timeout = WATCHDOG_TIMEOUT, |
333 | .min_timeout = 1, |
334 | .max_timeout = 255, |
335 | }; |
336 | |
337 | /* |
338 | * The WDT needs to learn about soft shutdowns in order to |
339 | * turn the timebomb registers off. |
340 | */ |
341 | |
342 | static int wdt_find(int addr) |
343 | { |
344 | u8 val; |
345 | int ret; |
346 | |
347 | cr_wdt_timeout = W83627HF_WDT_TIMEOUT; |
348 | cr_wdt_control = W83627HF_WDT_CONTROL; |
349 | cr_wdt_csr = W836X7HF_WDT_CSR; |
350 | |
351 | ret = superio_enter(); |
352 | if (ret) |
353 | return ret; |
354 | superio_select(W83627HF_LD_WDT); |
355 | val = superio_inb(reg: 0x20); |
356 | switch (val) { |
357 | case W83627HF_ID: |
358 | ret = w83627hf; |
359 | break; |
360 | case W83627S_ID: |
361 | ret = w83627s; |
362 | break; |
363 | case W83697HF_ID: |
364 | ret = w83697hf; |
365 | cr_wdt_timeout = W83697HF_WDT_TIMEOUT; |
366 | cr_wdt_control = W83697HF_WDT_CONTROL; |
367 | break; |
368 | case W83697UG_ID: |
369 | ret = w83697ug; |
370 | cr_wdt_timeout = W83697HF_WDT_TIMEOUT; |
371 | cr_wdt_control = W83697HF_WDT_CONTROL; |
372 | break; |
373 | case W83637HF_ID: |
374 | ret = w83637hf; |
375 | break; |
376 | case W83627THF_ID: |
377 | ret = w83627thf; |
378 | break; |
379 | case W83687THF_ID: |
380 | ret = w83687thf; |
381 | break; |
382 | case W83627EHF_ID: |
383 | ret = w83627ehf; |
384 | break; |
385 | case W83627DHG_ID: |
386 | ret = w83627dhg; |
387 | break; |
388 | case W83627DHG_P_ID: |
389 | ret = w83627dhg_p; |
390 | break; |
391 | case W83627UHG_ID: |
392 | ret = w83627uhg; |
393 | break; |
394 | case W83667HG_ID: |
395 | ret = w83667hg; |
396 | break; |
397 | case W83667HG_B_ID: |
398 | ret = w83667hg_b; |
399 | break; |
400 | case NCT6775_ID: |
401 | ret = nct6775; |
402 | break; |
403 | case NCT6776_ID: |
404 | ret = nct6776; |
405 | break; |
406 | case NCT6779_ID: |
407 | ret = nct6779; |
408 | break; |
409 | case NCT6791_ID: |
410 | ret = nct6791; |
411 | break; |
412 | case NCT6792_ID: |
413 | ret = nct6792; |
414 | break; |
415 | case NCT6793_ID: |
416 | ret = nct6793; |
417 | break; |
418 | case NCT6795_ID: |
419 | ret = nct6795; |
420 | break; |
421 | case NCT6796_ID: |
422 | ret = nct6796; |
423 | break; |
424 | case NCT6102_ID: |
425 | ret = nct6102; |
426 | cr_wdt_timeout = NCT6102D_WDT_TIMEOUT; |
427 | cr_wdt_control = NCT6102D_WDT_CONTROL; |
428 | cr_wdt_csr = NCT6102D_WDT_CSR; |
429 | break; |
430 | case NCT6116_ID: |
431 | ret = nct6116; |
432 | cr_wdt_timeout = NCT6102D_WDT_TIMEOUT; |
433 | cr_wdt_control = NCT6102D_WDT_CONTROL; |
434 | cr_wdt_csr = NCT6102D_WDT_CSR; |
435 | break; |
436 | case 0xff: |
437 | ret = -ENODEV; |
438 | break; |
439 | default: |
440 | ret = -ENODEV; |
441 | pr_err("Unsupported chip ID: 0x%02x\n" , val); |
442 | break; |
443 | } |
444 | superio_exit(); |
445 | return ret; |
446 | } |
447 | |
448 | /* |
449 | * On some systems, the NCT6791D comes with a companion chip and the |
450 | * watchdog function is in this companion chip. We must use a different |
451 | * unlocking sequence to access the companion chip. |
452 | */ |
453 | static int __init wdt_use_alt_key(const struct dmi_system_id *d) |
454 | { |
455 | wdt_cfg_enter = 0x88; |
456 | wdt_cfg_leave = 0xBB; |
457 | |
458 | return 0; |
459 | } |
460 | |
461 | static const struct dmi_system_id wdt_dmi_table[] __initconst = { |
462 | { |
463 | .matches = { |
464 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "INVES" ), |
465 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "CTS" ), |
466 | DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "INVES" ), |
467 | DMI_EXACT_MATCH(DMI_BOARD_NAME, "SHARKBAY" ), |
468 | }, |
469 | .callback = wdt_use_alt_key, |
470 | }, |
471 | {} |
472 | }; |
473 | |
474 | static int __init wdt_init(void) |
475 | { |
476 | int ret; |
477 | int chip; |
478 | static const char * const chip_name[] = { |
479 | "W83627HF" , |
480 | "W83627S" , |
481 | "W83697HF" , |
482 | "W83697UG" , |
483 | "W83637HF" , |
484 | "W83627THF" , |
485 | "W83687THF" , |
486 | "W83627EHF" , |
487 | "W83627DHG" , |
488 | "W83627UHG" , |
489 | "W83667HG" , |
490 | "W83667DHG-P" , |
491 | "W83667HG-B" , |
492 | "NCT6775" , |
493 | "NCT6776" , |
494 | "NCT6779" , |
495 | "NCT6791" , |
496 | "NCT6792" , |
497 | "NCT6793" , |
498 | "NCT6795" , |
499 | "NCT6796" , |
500 | "NCT6102" , |
501 | "NCT6116" , |
502 | }; |
503 | |
504 | /* Apply system-specific quirks */ |
505 | dmi_check_system(list: wdt_dmi_table); |
506 | |
507 | wdt_io = 0x2e; |
508 | chip = wdt_find(addr: 0x2e); |
509 | if (chip < 0) { |
510 | wdt_io = 0x4e; |
511 | chip = wdt_find(addr: 0x4e); |
512 | if (chip < 0) |
513 | return chip; |
514 | } |
515 | |
516 | pr_info("WDT driver for %s Super I/O chip initialising\n" , |
517 | chip_name[chip]); |
518 | |
519 | watchdog_init_timeout(wdd: &wdt_dev, timeout_parm: timeout, NULL); |
520 | watchdog_set_nowayout(wdd: &wdt_dev, nowayout); |
521 | watchdog_stop_on_reboot(wdd: &wdt_dev); |
522 | |
523 | ret = w83627hf_init(wdog: &wdt_dev, chip); |
524 | if (ret) { |
525 | pr_err("failed to initialize watchdog (err=%d)\n" , ret); |
526 | return ret; |
527 | } |
528 | |
529 | ret = watchdog_register_device(&wdt_dev); |
530 | if (ret) |
531 | return ret; |
532 | |
533 | pr_info("initialized. timeout=%d sec (nowayout=%d)\n" , |
534 | wdt_dev.timeout, nowayout); |
535 | |
536 | return ret; |
537 | } |
538 | |
539 | static void __exit wdt_exit(void) |
540 | { |
541 | watchdog_unregister_device(&wdt_dev); |
542 | } |
543 | |
544 | module_init(wdt_init); |
545 | module_exit(wdt_exit); |
546 | |
547 | MODULE_LICENSE("GPL" ); |
548 | MODULE_AUTHOR("Pádraig Brady <P@draigBrady.com>" ); |
549 | MODULE_DESCRIPTION("w83627hf/thf WDT driver" ); |
550 | |