1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * sch311x_wdt.c - Driver for the SCH311x Super-I/O chips |
4 | * integrated watchdog. |
5 | * |
6 | * (c) Copyright 2008 Wim Van Sebroeck <wim@iguana.be>. |
7 | * |
8 | * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor |
9 | * provide warranty for any of this software. This material is |
10 | * provided "AS-IS" and at no charge. |
11 | */ |
12 | |
13 | /* |
14 | * Includes, defines, variables, module parameters, ... |
15 | */ |
16 | |
17 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
18 | |
19 | /* Includes */ |
20 | #include <linux/module.h> /* For module specific items */ |
21 | #include <linux/moduleparam.h> /* For new moduleparam's */ |
22 | #include <linux/types.h> /* For standard types (like size_t) */ |
23 | #include <linux/errno.h> /* For the -ENODEV/... values */ |
24 | #include <linux/kernel.h> /* For printk/... */ |
25 | #include <linux/miscdevice.h> /* For struct miscdevice */ |
26 | #include <linux/watchdog.h> /* For the watchdog specific items */ |
27 | #include <linux/init.h> /* For __init/__exit/... */ |
28 | #include <linux/fs.h> /* For file operations */ |
29 | #include <linux/platform_device.h> /* For platform_driver framework */ |
30 | #include <linux/ioport.h> /* For io-port access */ |
31 | #include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ |
32 | #include <linux/uaccess.h> /* For copy_to_user/put_user/... */ |
33 | #include <linux/io.h> /* For inb/outb/... */ |
34 | |
35 | /* Module and version information */ |
36 | #define DRV_NAME "sch311x_wdt" |
37 | |
38 | /* Runtime registers */ |
39 | #define GP60 0x47 |
40 | #define WDT_TIME_OUT 0x65 |
41 | #define WDT_VAL 0x66 |
42 | #define WDT_CFG 0x67 |
43 | #define WDT_CTRL 0x68 |
44 | |
45 | /* internal variables */ |
46 | static unsigned long sch311x_wdt_is_open; |
47 | static char sch311x_wdt_expect_close; |
48 | static struct platform_device *sch311x_wdt_pdev; |
49 | |
50 | static int sch311x_ioports[] = { 0x2e, 0x4e, 0x162e, 0x164e, 0x00 }; |
51 | |
52 | static struct { /* The devices private data */ |
53 | /* the Runtime Register base address */ |
54 | unsigned short runtime_reg; |
55 | /* The card's boot status */ |
56 | int boot_status; |
57 | /* the lock for io operations */ |
58 | spinlock_t io_lock; |
59 | } sch311x_wdt_data; |
60 | |
61 | /* Module load parameters */ |
62 | static unsigned short force_id; |
63 | module_param(force_id, ushort, 0); |
64 | MODULE_PARM_DESC(force_id, "Override the detected device ID" ); |
65 | |
66 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ |
67 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ |
68 | module_param(timeout, int, 0); |
69 | MODULE_PARM_DESC(timeout, |
70 | "Watchdog timeout in seconds. 1<= timeout <=15300, default=" |
71 | __MODULE_STRING(WATCHDOG_TIMEOUT) "." ); |
72 | |
73 | static bool nowayout = WATCHDOG_NOWAYOUT; |
74 | module_param(nowayout, bool, 0); |
75 | MODULE_PARM_DESC(nowayout, |
76 | "Watchdog cannot be stopped once started (default=" |
77 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
78 | |
79 | /* |
80 | * Super-IO functions |
81 | */ |
82 | |
83 | static inline void sch311x_sio_enter(int sio_config_port) |
84 | { |
85 | outb(value: 0x55, port: sio_config_port); |
86 | } |
87 | |
88 | static inline void sch311x_sio_exit(int sio_config_port) |
89 | { |
90 | outb(value: 0xaa, port: sio_config_port); |
91 | } |
92 | |
93 | static inline int sch311x_sio_inb(int sio_config_port, int reg) |
94 | { |
95 | outb(value: reg, port: sio_config_port); |
96 | return inb(port: sio_config_port + 1); |
97 | } |
98 | |
99 | static inline void sch311x_sio_outb(int sio_config_port, int reg, int val) |
100 | { |
101 | outb(value: reg, port: sio_config_port); |
102 | outb(value: val, port: sio_config_port + 1); |
103 | } |
104 | |
105 | /* |
106 | * Watchdog Operations |
107 | */ |
108 | |
109 | static void sch311x_wdt_set_timeout(int t) |
110 | { |
111 | unsigned char timeout_unit = 0x80; |
112 | |
113 | /* When new timeout is bigger then 255 seconds, we will use minutes */ |
114 | if (t > 255) { |
115 | timeout_unit = 0; |
116 | t /= 60; |
117 | } |
118 | |
119 | /* -- Watchdog Timeout -- |
120 | * Bit 0-6 (Reserved) |
121 | * Bit 7 WDT Time-out Value Units Select |
122 | * (0 = Minutes, 1 = Seconds) |
123 | */ |
124 | outb(value: timeout_unit, port: sch311x_wdt_data.runtime_reg + WDT_TIME_OUT); |
125 | |
126 | /* -- Watchdog Timer Time-out Value -- |
127 | * Bit 0-7 Binary coded units (0=Disabled, 1..255) |
128 | */ |
129 | outb(value: t, port: sch311x_wdt_data.runtime_reg + WDT_VAL); |
130 | } |
131 | |
132 | static void sch311x_wdt_start(void) |
133 | { |
134 | unsigned char t; |
135 | |
136 | spin_lock(lock: &sch311x_wdt_data.io_lock); |
137 | |
138 | /* set watchdog's timeout */ |
139 | sch311x_wdt_set_timeout(t: timeout); |
140 | /* enable the watchdog */ |
141 | /* -- General Purpose I/O Bit 6.0 -- |
142 | * Bit 0, In/Out: 0 = Output, 1 = Input |
143 | * Bit 1, Polarity: 0 = No Invert, 1 = Invert |
144 | * Bit 2-3, Function select: 00 = GPI/O, 01 = LED1, 11 = WDT, |
145 | * 10 = Either Edge Triggered Intr.4 |
146 | * Bit 4-6 (Reserved) |
147 | * Bit 7, Output Type: 0 = Push Pull Bit, 1 = Open Drain |
148 | */ |
149 | t = inb(port: sch311x_wdt_data.runtime_reg + GP60); |
150 | outb(value: (t & ~0x0d) | 0x0c, port: sch311x_wdt_data.runtime_reg + GP60); |
151 | |
152 | spin_unlock(lock: &sch311x_wdt_data.io_lock); |
153 | |
154 | } |
155 | |
156 | static void sch311x_wdt_stop(void) |
157 | { |
158 | unsigned char t; |
159 | |
160 | spin_lock(lock: &sch311x_wdt_data.io_lock); |
161 | |
162 | /* stop the watchdog */ |
163 | t = inb(port: sch311x_wdt_data.runtime_reg + GP60); |
164 | outb(value: (t & ~0x0d) | 0x01, port: sch311x_wdt_data.runtime_reg + GP60); |
165 | /* disable timeout by setting it to 0 */ |
166 | sch311x_wdt_set_timeout(t: 0); |
167 | |
168 | spin_unlock(lock: &sch311x_wdt_data.io_lock); |
169 | } |
170 | |
171 | static void sch311x_wdt_keepalive(void) |
172 | { |
173 | spin_lock(lock: &sch311x_wdt_data.io_lock); |
174 | sch311x_wdt_set_timeout(t: timeout); |
175 | spin_unlock(lock: &sch311x_wdt_data.io_lock); |
176 | } |
177 | |
178 | static int sch311x_wdt_set_heartbeat(int t) |
179 | { |
180 | if (t < 1 || t > (255*60)) |
181 | return -EINVAL; |
182 | |
183 | /* When new timeout is bigger then 255 seconds, |
184 | * we will round up to minutes (with a max of 255) */ |
185 | if (t > 255) |
186 | t = (((t - 1) / 60) + 1) * 60; |
187 | |
188 | timeout = t; |
189 | return 0; |
190 | } |
191 | |
192 | static void sch311x_wdt_get_status(int *status) |
193 | { |
194 | unsigned char new_status; |
195 | |
196 | *status = 0; |
197 | |
198 | spin_lock(lock: &sch311x_wdt_data.io_lock); |
199 | |
200 | /* -- Watchdog timer control -- |
201 | * Bit 0 Status Bit: 0 = Timer counting, 1 = Timeout occurred |
202 | * Bit 1 Reserved |
203 | * Bit 2 Force Timeout: 1 = Forces WD timeout event (self-cleaning) |
204 | * Bit 3 P20 Force Timeout enabled: |
205 | * 0 = P20 activity does not generate the WD timeout event |
206 | * 1 = P20 Allows rising edge of P20, from the keyboard |
207 | * controller, to force the WD timeout event. |
208 | * Bit 4-7 Reserved |
209 | */ |
210 | new_status = inb(port: sch311x_wdt_data.runtime_reg + WDT_CTRL); |
211 | if (new_status & 0x01) |
212 | *status |= WDIOF_CARDRESET; |
213 | |
214 | spin_unlock(lock: &sch311x_wdt_data.io_lock); |
215 | } |
216 | |
217 | /* |
218 | * /dev/watchdog handling |
219 | */ |
220 | |
221 | static ssize_t sch311x_wdt_write(struct file *file, const char __user *buf, |
222 | size_t count, loff_t *ppos) |
223 | { |
224 | if (count) { |
225 | if (!nowayout) { |
226 | size_t i; |
227 | |
228 | sch311x_wdt_expect_close = 0; |
229 | |
230 | for (i = 0; i != count; i++) { |
231 | char c; |
232 | if (get_user(c, buf + i)) |
233 | return -EFAULT; |
234 | if (c == 'V') |
235 | sch311x_wdt_expect_close = 42; |
236 | } |
237 | } |
238 | sch311x_wdt_keepalive(); |
239 | } |
240 | return count; |
241 | } |
242 | |
243 | static long sch311x_wdt_ioctl(struct file *file, unsigned int cmd, |
244 | unsigned long arg) |
245 | { |
246 | int status; |
247 | int new_timeout; |
248 | void __user *argp = (void __user *)arg; |
249 | int __user *p = argp; |
250 | static const struct watchdog_info ident = { |
251 | .options = WDIOF_KEEPALIVEPING | |
252 | WDIOF_SETTIMEOUT | |
253 | WDIOF_MAGICCLOSE, |
254 | .firmware_version = 1, |
255 | .identity = DRV_NAME, |
256 | }; |
257 | |
258 | switch (cmd) { |
259 | case WDIOC_GETSUPPORT: |
260 | if (copy_to_user(to: argp, from: &ident, n: sizeof(ident))) |
261 | return -EFAULT; |
262 | break; |
263 | |
264 | case WDIOC_GETSTATUS: |
265 | { |
266 | sch311x_wdt_get_status(status: &status); |
267 | return put_user(status, p); |
268 | } |
269 | case WDIOC_GETBOOTSTATUS: |
270 | return put_user(sch311x_wdt_data.boot_status, p); |
271 | |
272 | case WDIOC_SETOPTIONS: |
273 | { |
274 | int options, retval = -EINVAL; |
275 | |
276 | if (get_user(options, p)) |
277 | return -EFAULT; |
278 | if (options & WDIOS_DISABLECARD) { |
279 | sch311x_wdt_stop(); |
280 | retval = 0; |
281 | } |
282 | if (options & WDIOS_ENABLECARD) { |
283 | sch311x_wdt_start(); |
284 | retval = 0; |
285 | } |
286 | return retval; |
287 | } |
288 | case WDIOC_KEEPALIVE: |
289 | sch311x_wdt_keepalive(); |
290 | break; |
291 | |
292 | case WDIOC_SETTIMEOUT: |
293 | if (get_user(new_timeout, p)) |
294 | return -EFAULT; |
295 | if (sch311x_wdt_set_heartbeat(t: new_timeout)) |
296 | return -EINVAL; |
297 | sch311x_wdt_keepalive(); |
298 | fallthrough; |
299 | case WDIOC_GETTIMEOUT: |
300 | return put_user(timeout, p); |
301 | default: |
302 | return -ENOTTY; |
303 | } |
304 | return 0; |
305 | } |
306 | |
307 | static int sch311x_wdt_open(struct inode *inode, struct file *file) |
308 | { |
309 | if (test_and_set_bit(nr: 0, addr: &sch311x_wdt_is_open)) |
310 | return -EBUSY; |
311 | /* |
312 | * Activate |
313 | */ |
314 | sch311x_wdt_start(); |
315 | return stream_open(inode, filp: file); |
316 | } |
317 | |
318 | static int sch311x_wdt_close(struct inode *inode, struct file *file) |
319 | { |
320 | if (sch311x_wdt_expect_close == 42) { |
321 | sch311x_wdt_stop(); |
322 | } else { |
323 | pr_crit("Unexpected close, not stopping watchdog!\n" ); |
324 | sch311x_wdt_keepalive(); |
325 | } |
326 | clear_bit(nr: 0, addr: &sch311x_wdt_is_open); |
327 | sch311x_wdt_expect_close = 0; |
328 | return 0; |
329 | } |
330 | |
331 | /* |
332 | * Kernel Interfaces |
333 | */ |
334 | |
335 | static const struct file_operations sch311x_wdt_fops = { |
336 | .owner = THIS_MODULE, |
337 | .llseek = no_llseek, |
338 | .write = sch311x_wdt_write, |
339 | .unlocked_ioctl = sch311x_wdt_ioctl, |
340 | .compat_ioctl = compat_ptr_ioctl, |
341 | .open = sch311x_wdt_open, |
342 | .release = sch311x_wdt_close, |
343 | }; |
344 | |
345 | static struct miscdevice sch311x_wdt_miscdev = { |
346 | .minor = WATCHDOG_MINOR, |
347 | .name = "watchdog" , |
348 | .fops = &sch311x_wdt_fops, |
349 | }; |
350 | |
351 | /* |
352 | * Init & exit routines |
353 | */ |
354 | |
355 | static int sch311x_wdt_probe(struct platform_device *pdev) |
356 | { |
357 | struct device *dev = &pdev->dev; |
358 | int err; |
359 | |
360 | spin_lock_init(&sch311x_wdt_data.io_lock); |
361 | |
362 | if (!request_region(sch311x_wdt_data.runtime_reg + GP60, 1, DRV_NAME)) { |
363 | dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n" , |
364 | sch311x_wdt_data.runtime_reg + GP60, |
365 | sch311x_wdt_data.runtime_reg + GP60); |
366 | err = -EBUSY; |
367 | goto exit; |
368 | } |
369 | |
370 | if (!request_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4, |
371 | DRV_NAME)) { |
372 | dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n" , |
373 | sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, |
374 | sch311x_wdt_data.runtime_reg + WDT_CTRL); |
375 | err = -EBUSY; |
376 | goto exit_release_region; |
377 | } |
378 | |
379 | /* Make sure that the watchdog is not running */ |
380 | sch311x_wdt_stop(); |
381 | |
382 | /* Disable keyboard and mouse interaction and interrupt */ |
383 | /* -- Watchdog timer configuration -- |
384 | * Bit 0 Reserved |
385 | * Bit 1 Keyboard enable: 0* = No Reset, 1 = Reset WDT upon KBD Intr. |
386 | * Bit 2 Mouse enable: 0* = No Reset, 1 = Reset WDT upon Mouse Intr |
387 | * Bit 3 Reserved |
388 | * Bit 4-7 WDT Interrupt Mapping: (0000* = Disabled, |
389 | * 0001=IRQ1, 0010=(Invalid), 0011=IRQ3 to 1111=IRQ15) |
390 | */ |
391 | outb(value: 0, port: sch311x_wdt_data.runtime_reg + WDT_CFG); |
392 | |
393 | /* Check that the heartbeat value is within it's range ; |
394 | * if not reset to the default */ |
395 | if (sch311x_wdt_set_heartbeat(t: timeout)) { |
396 | sch311x_wdt_set_heartbeat(WATCHDOG_TIMEOUT); |
397 | dev_info(dev, "timeout value must be 1<=x<=15300, using %d\n" , |
398 | timeout); |
399 | } |
400 | |
401 | /* Get status at boot */ |
402 | sch311x_wdt_get_status(status: &sch311x_wdt_data.boot_status); |
403 | |
404 | sch311x_wdt_miscdev.parent = dev; |
405 | |
406 | err = misc_register(misc: &sch311x_wdt_miscdev); |
407 | if (err != 0) { |
408 | dev_err(dev, "cannot register miscdev on minor=%d (err=%d)\n" , |
409 | WATCHDOG_MINOR, err); |
410 | goto exit_release_region2; |
411 | } |
412 | |
413 | dev_info(dev, |
414 | "SMSC SCH311x WDT initialized. timeout=%d sec (nowayout=%d)\n" , |
415 | timeout, nowayout); |
416 | |
417 | return 0; |
418 | |
419 | exit_release_region2: |
420 | release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4); |
421 | exit_release_region: |
422 | release_region(sch311x_wdt_data.runtime_reg + GP60, 1); |
423 | sch311x_wdt_data.runtime_reg = 0; |
424 | exit: |
425 | return err; |
426 | } |
427 | |
428 | static void sch311x_wdt_remove(struct platform_device *pdev) |
429 | { |
430 | /* Stop the timer before we leave */ |
431 | if (!nowayout) |
432 | sch311x_wdt_stop(); |
433 | |
434 | /* Deregister */ |
435 | misc_deregister(misc: &sch311x_wdt_miscdev); |
436 | release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4); |
437 | release_region(sch311x_wdt_data.runtime_reg + GP60, 1); |
438 | sch311x_wdt_data.runtime_reg = 0; |
439 | } |
440 | |
441 | static void sch311x_wdt_shutdown(struct platform_device *dev) |
442 | { |
443 | /* Turn the WDT off if we have a soft shutdown */ |
444 | sch311x_wdt_stop(); |
445 | } |
446 | |
447 | static struct platform_driver sch311x_wdt_driver = { |
448 | .probe = sch311x_wdt_probe, |
449 | .remove_new = sch311x_wdt_remove, |
450 | .shutdown = sch311x_wdt_shutdown, |
451 | .driver = { |
452 | .name = DRV_NAME, |
453 | }, |
454 | }; |
455 | |
456 | static int __init sch311x_detect(int sio_config_port, unsigned short *addr) |
457 | { |
458 | int err = 0, reg; |
459 | unsigned short base_addr; |
460 | unsigned char dev_id; |
461 | |
462 | sch311x_sio_enter(sio_config_port); |
463 | |
464 | /* Check device ID. We currently know about: |
465 | * SCH3112 (0x7c), SCH3114 (0x7d), and SCH3116 (0x7f). */ |
466 | reg = force_id ? force_id : sch311x_sio_inb(sio_config_port, reg: 0x20); |
467 | if (!(reg == 0x7c || reg == 0x7d || reg == 0x7f)) { |
468 | err = -ENODEV; |
469 | goto exit; |
470 | } |
471 | dev_id = reg == 0x7c ? 2 : reg == 0x7d ? 4 : 6; |
472 | |
473 | /* Select logical device A (runtime registers) */ |
474 | sch311x_sio_outb(sio_config_port, reg: 0x07, val: 0x0a); |
475 | |
476 | /* Check if Logical Device Register is currently active */ |
477 | if ((sch311x_sio_inb(sio_config_port, reg: 0x30) & 0x01) == 0) |
478 | pr_info("Seems that LDN 0x0a is not active...\n" ); |
479 | |
480 | /* Get the base address of the runtime registers */ |
481 | base_addr = (sch311x_sio_inb(sio_config_port, reg: 0x60) << 8) | |
482 | sch311x_sio_inb(sio_config_port, reg: 0x61); |
483 | if (!base_addr) { |
484 | pr_err("Base address not set\n" ); |
485 | err = -ENODEV; |
486 | goto exit; |
487 | } |
488 | *addr = base_addr; |
489 | |
490 | pr_info("Found an SMSC SCH311%d chip at 0x%04x\n" , dev_id, base_addr); |
491 | |
492 | exit: |
493 | sch311x_sio_exit(sio_config_port); |
494 | return err; |
495 | } |
496 | |
497 | static int __init sch311x_wdt_init(void) |
498 | { |
499 | int err, i, found = 0; |
500 | unsigned short addr = 0; |
501 | |
502 | for (i = 0; !found && sch311x_ioports[i]; i++) |
503 | if (sch311x_detect(sio_config_port: sch311x_ioports[i], addr: &addr) == 0) |
504 | found++; |
505 | |
506 | if (!found) |
507 | return -ENODEV; |
508 | |
509 | sch311x_wdt_data.runtime_reg = addr; |
510 | |
511 | err = platform_driver_register(&sch311x_wdt_driver); |
512 | if (err) |
513 | return err; |
514 | |
515 | sch311x_wdt_pdev = platform_device_register_simple(DRV_NAME, id: addr, |
516 | NULL, num: 0); |
517 | |
518 | if (IS_ERR(ptr: sch311x_wdt_pdev)) { |
519 | err = PTR_ERR(ptr: sch311x_wdt_pdev); |
520 | goto unreg_platform_driver; |
521 | } |
522 | |
523 | return 0; |
524 | |
525 | unreg_platform_driver: |
526 | platform_driver_unregister(&sch311x_wdt_driver); |
527 | return err; |
528 | } |
529 | |
530 | static void __exit sch311x_wdt_exit(void) |
531 | { |
532 | platform_device_unregister(sch311x_wdt_pdev); |
533 | platform_driver_unregister(&sch311x_wdt_driver); |
534 | } |
535 | |
536 | module_init(sch311x_wdt_init); |
537 | module_exit(sch311x_wdt_exit); |
538 | |
539 | MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>" ); |
540 | MODULE_DESCRIPTION("SMSC SCH311x WatchDog Timer Driver" ); |
541 | MODULE_LICENSE("GPL" ); |
542 | |