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