1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#include <linux/module.h>
22#include <linux/types.h>
23#include <linux/miscdevice.h>
24#include <linux/watchdog.h>
25#include <linux/ioport.h>
26#include <linux/delay.h>
27#include <linux/notifier.h>
28#include <linux/fs.h>
29#include <linux/reboot.h>
30#include <linux/init.h>
31#include <linux/spinlock.h>
32#include <linux/moduleparam.h>
33#include <linux/io.h>
34#include <linux/uaccess.h>
35
36#include <asm/system.h>
37
38
39
40#define DEFAULT_TIMEOUT 1
41#define MAX_TIMEOUT 255
42
43#define VERSION "1.1"
44#define MODNAME "pc87413 WDT"
45#define PFX MODNAME ": "
46#define DPFX MODNAME " - DEBUG: "
47
48#define WDT_INDEX_IO_PORT (io+0)
49#define WDT_DATA_IO_PORT (WDT_INDEX_IO_PORT+1)
50#define SWC_LDN 0x04
51#define SIOCFG2 0x22
52#define WDCTL 0x10
53#define WDTO 0x11
54#define WDCFG 0x12
55
56static int io = 0x2E;
57
58static int timeout = DEFAULT_TIMEOUT;
59static unsigned long timer_enabled;
60
61static char expect_close;
62
63static DEFINE_SPINLOCK(io_lock);
64
65static int nowayout = WATCHDOG_NOWAYOUT;
66
67
68
69
70
71static inline void pc87413_select_wdt_out(void)
72{
73 unsigned int cr_data = 0;
74
75
76
77 outb_p(SIOCFG2, WDT_INDEX_IO_PORT);
78
79 cr_data = inb(WDT_DATA_IO_PORT);
80
81 cr_data |= 0x80;
82 outb_p(SIOCFG2, WDT_INDEX_IO_PORT);
83
84 outb_p(cr_data, WDT_DATA_IO_PORT);
85
86#ifdef DEBUG
87 printk(KERN_INFO DPFX
88 "Select multiple pin,pin55,as WDT output: Bit7 to 1: %d\n",
89 cr_data);
90#endif
91}
92
93
94
95static inline void pc87413_enable_swc(void)
96{
97 unsigned int cr_data = 0;
98
99
100
101 outb_p(0x07, WDT_INDEX_IO_PORT);
102 outb_p(SWC_LDN, WDT_DATA_IO_PORT);
103
104 outb_p(0x30, WDT_INDEX_IO_PORT);
105 cr_data = inb(WDT_DATA_IO_PORT);
106 cr_data |= 0x01;
107 outb_p(0x30, WDT_INDEX_IO_PORT);
108 outb_p(cr_data, WDT_DATA_IO_PORT);
109
110#ifdef DEBUG
111 printk(KERN_INFO DPFX "pc87413 - Enable SWC functions\n");
112#endif
113}
114
115
116
117static inline unsigned int pc87413_get_swc_base(void)
118{
119 unsigned int swc_base_addr = 0;
120 unsigned char addr_l, addr_h = 0;
121
122
123
124 outb_p(0x60, WDT_INDEX_IO_PORT);
125 addr_h = inb(WDT_DATA_IO_PORT);
126
127 outb_p(0x61, WDT_INDEX_IO_PORT);
128
129 addr_l = inb(WDT_DATA_IO_PORT);
130
131 swc_base_addr = (addr_h << 8) + addr_l;
132#ifdef DEBUG
133 printk(KERN_INFO DPFX
134 "Read SWC I/O Base Address: low %d, high %d, res %d\n",
135 addr_l, addr_h, swc_base_addr);
136#endif
137 return swc_base_addr;
138}
139
140
141
142static inline void pc87413_swc_bank3(unsigned int swc_base_addr)
143{
144
145 outb_p(inb(swc_base_addr + 0x0f) | 0x03, swc_base_addr + 0x0f);
146#ifdef DEBUG
147 printk(KERN_INFO DPFX "Select Bank3 of SWC\n");
148#endif
149}
150
151
152
153static inline void pc87413_programm_wdto(unsigned int swc_base_addr,
154 char pc87413_time)
155{
156
157 outb_p(pc87413_time, swc_base_addr + WDTO);
158#ifdef DEBUG
159 printk(KERN_INFO DPFX "Set WDTO to %d minutes\n", pc87413_time);
160#endif
161}
162
163
164
165static inline void pc87413_enable_wden(unsigned int swc_base_addr)
166{
167
168 outb_p(inb(swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL);
169#ifdef DEBUG
170 printk(KERN_INFO DPFX "Enable WDEN\n");
171#endif
172}
173
174
175static inline void pc87413_enable_sw_wd_tren(unsigned int swc_base_addr)
176{
177
178 outb_p(inb(swc_base_addr + WDCFG) | 0x80, swc_base_addr + WDCFG);
179#ifdef DEBUG
180 printk(KERN_INFO DPFX "Enable SW_WD_TREN\n");
181#endif
182}
183
184
185
186static inline void pc87413_disable_sw_wd_tren(unsigned int swc_base_addr)
187{
188
189 outb_p(inb(swc_base_addr + WDCFG) & 0x7f, swc_base_addr + WDCFG);
190#ifdef DEBUG
191 printk(KERN_INFO DPFX "pc87413 - Disable SW_WD_TREN\n");
192#endif
193}
194
195
196
197static inline void pc87413_enable_sw_wd_trg(unsigned int swc_base_addr)
198{
199
200 outb_p(inb(swc_base_addr + WDCTL) | 0x80, swc_base_addr + WDCTL);
201#ifdef DEBUG
202 printk(KERN_INFO DPFX "pc87413 - Enable SW_WD_TRG\n");
203#endif
204}
205
206
207
208static inline void pc87413_disable_sw_wd_trg(unsigned int swc_base_addr)
209{
210
211 outb_p(inb(swc_base_addr + WDCTL) & 0x7f, swc_base_addr + WDCTL);
212#ifdef DEBUG
213 printk(KERN_INFO DPFX "Disable SW_WD_TRG\n");
214#endif
215}
216
217
218
219
220
221static void pc87413_enable(void)
222{
223 unsigned int swc_base_addr;
224
225 spin_lock(&io_lock);
226
227 pc87413_select_wdt_out();
228 pc87413_enable_swc();
229 swc_base_addr = pc87413_get_swc_base();
230 pc87413_swc_bank3(swc_base_addr);
231 pc87413_programm_wdto(swc_base_addr, timeout);
232 pc87413_enable_wden(swc_base_addr);
233 pc87413_enable_sw_wd_tren(swc_base_addr);
234 pc87413_enable_sw_wd_trg(swc_base_addr);
235
236 spin_unlock(&io_lock);
237}
238
239
240
241static void pc87413_disable(void)
242{
243 unsigned int swc_base_addr;
244
245 spin_lock(&io_lock);
246
247 pc87413_select_wdt_out();
248 pc87413_enable_swc();
249 swc_base_addr = pc87413_get_swc_base();
250 pc87413_swc_bank3(swc_base_addr);
251 pc87413_disable_sw_wd_tren(swc_base_addr);
252 pc87413_disable_sw_wd_trg(swc_base_addr);
253 pc87413_programm_wdto(swc_base_addr, 0);
254
255 spin_unlock(&io_lock);
256}
257
258
259
260static void pc87413_refresh(void)
261{
262 unsigned int swc_base_addr;
263
264 spin_lock(&io_lock);
265
266 pc87413_select_wdt_out();
267 pc87413_enable_swc();
268 swc_base_addr = pc87413_get_swc_base();
269 pc87413_swc_bank3(swc_base_addr);
270 pc87413_disable_sw_wd_tren(swc_base_addr);
271 pc87413_disable_sw_wd_trg(swc_base_addr);
272 pc87413_programm_wdto(swc_base_addr, timeout);
273 pc87413_enable_wden(swc_base_addr);
274 pc87413_enable_sw_wd_tren(swc_base_addr);
275 pc87413_enable_sw_wd_trg(swc_base_addr);
276
277 spin_unlock(&io_lock);
278}
279
280
281
282
283
284
285
286
287
288
289static int pc87413_open(struct inode *inode, struct file *file)
290{
291
292
293 if (test_and_set_bit(0, &timer_enabled))
294 return -EBUSY;
295
296 if (nowayout)
297 __module_get(THIS_MODULE);
298
299
300 pc87413_refresh();
301
302 printk(KERN_INFO MODNAME
303 "Watchdog enabled. Timeout set to %d minute(s).\n", timeout);
304
305 return nonseekable_open(inode, file);
306}
307
308
309
310
311
312
313
314
315
316
317
318
319
320static int pc87413_release(struct inode *inode, struct file *file)
321{
322
323
324 if (expect_close == 42) {
325 pc87413_disable();
326 printk(KERN_INFO MODNAME
327 "Watchdog disabled, sleeping again...\n");
328 } else {
329 printk(KERN_CRIT MODNAME
330 "Unexpected close, not stopping watchdog!\n");
331 pc87413_refresh();
332 }
333 clear_bit(0, &timer_enabled);
334 expect_close = 0;
335 return 0;
336}
337
338
339
340
341
342
343
344
345static int pc87413_status(void)
346{
347 return 0;
348}
349
350
351
352
353
354
355
356
357
358
359
360
361static ssize_t pc87413_write(struct file *file, const char __user *data,
362 size_t len, loff_t *ppos)
363{
364
365 if (len) {
366 if (!nowayout) {
367 size_t i;
368
369
370 expect_close = 0;
371
372
373
374 for (i = 0; i != len; i++) {
375 char c;
376 if (get_user(c, data + i))
377 return -EFAULT;
378 if (c == 'V')
379 expect_close = 42;
380 }
381 }
382
383
384 pc87413_refresh();
385 }
386 return len;
387}
388
389
390
391
392
393
394
395
396
397
398
399
400static long pc87413_ioctl(struct file *file, unsigned int cmd,
401 unsigned long arg)
402{
403 int new_timeout;
404
405 union {
406 struct watchdog_info __user *ident;
407 int __user *i;
408 } uarg;
409
410 static struct watchdog_info ident = {
411 .options = WDIOF_KEEPALIVEPING |
412 WDIOF_SETTIMEOUT |
413 WDIOF_MAGICCLOSE,
414 .firmware_version = 1,
415 .identity = "PC87413(HF/F) watchdog",
416 };
417
418 uarg.i = (int __user *)arg;
419
420 switch (cmd) {
421 case WDIOC_GETSUPPORT:
422 return copy_to_user(uarg.ident, &ident,
423 sizeof(ident)) ? -EFAULT : 0;
424 case WDIOC_GETSTATUS:
425 return put_user(pc87413_status(), uarg.i);
426 case WDIOC_GETBOOTSTATUS:
427 return put_user(0, uarg.i);
428 case WDIOC_SETOPTIONS:
429 {
430 int options, retval = -EINVAL;
431 if (get_user(options, uarg.i))
432 return -EFAULT;
433 if (options & WDIOS_DISABLECARD) {
434 pc87413_disable();
435 retval = 0;
436 }
437 if (options & WDIOS_ENABLECARD) {
438 pc87413_enable();
439 retval = 0;
440 }
441 return retval;
442 }
443 case WDIOC_KEEPALIVE:
444 pc87413_refresh();
445#ifdef DEBUG
446 printk(KERN_INFO DPFX "keepalive\n");
447#endif
448 return 0;
449 case WDIOC_SETTIMEOUT:
450 if (get_user(new_timeout, uarg.i))
451 return -EFAULT;
452
453 new_timeout /= 60;
454 if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
455 return -EINVAL;
456 timeout = new_timeout;
457 pc87413_refresh();
458
459 case WDIOC_GETTIMEOUT:
460 new_timeout = timeout * 60;
461 return put_user(new_timeout, uarg.i);
462 default:
463 return -ENOTTY;
464 }
465}
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481static int pc87413_notify_sys(struct notifier_block *this,
482 unsigned long code,
483 void *unused)
484{
485 if (code == SYS_DOWN || code == SYS_HALT)
486
487 pc87413_disable();
488 return NOTIFY_DONE;
489}
490
491
492
493static const struct file_operations pc87413_fops = {
494 .owner = THIS_MODULE,
495 .llseek = no_llseek,
496 .write = pc87413_write,
497 .unlocked_ioctl = pc87413_ioctl,
498 .open = pc87413_open,
499 .release = pc87413_release,
500};
501
502static struct notifier_block pc87413_notifier = {
503 .notifier_call = pc87413_notify_sys,
504};
505
506static struct miscdevice pc87413_miscdev = {
507 .minor = WATCHDOG_MINOR,
508 .name = "watchdog",
509 .fops = &pc87413_fops,
510};
511
512
513
514
515
516
517
518
519
520
521
522static int __init pc87413_init(void)
523{
524 int ret;
525
526 printk(KERN_INFO PFX "Version " VERSION " at io 0x%X\n",
527 WDT_INDEX_IO_PORT);
528
529
530
531 ret = register_reboot_notifier(&pc87413_notifier);
532 if (ret != 0) {
533 printk(KERN_ERR PFX
534 "cannot register reboot notifier (err=%d)\n", ret);
535 }
536
537 ret = misc_register(&pc87413_miscdev);
538 if (ret != 0) {
539 printk(KERN_ERR PFX
540 "cannot register miscdev on minor=%d (err=%d)\n",
541 WATCHDOG_MINOR, ret);
542 unregister_reboot_notifier(&pc87413_notifier);
543 return ret;
544 }
545 printk(KERN_INFO PFX "initialized. timeout=%d min \n", timeout);
546 pc87413_enable();
547 return 0;
548}
549
550
551
552
553
554
555
556
557
558
559
560static void __exit pc87413_exit(void)
561{
562
563 if (!nowayout) {
564 pc87413_disable();
565 printk(KERN_INFO MODNAME "Watchdog disabled.\n");
566 }
567
568 misc_deregister(&pc87413_miscdev);
569 unregister_reboot_notifier(&pc87413_notifier);
570
571
572 printk(KERN_INFO MODNAME " watchdog component driver removed.\n");
573}
574
575module_init(pc87413_init);
576module_exit(pc87413_exit);
577
578MODULE_AUTHOR("Sven Anders <anders@anduras.de>, "
579 "Marcus Junker <junker@anduras.de>,");
580MODULE_DESCRIPTION("PC87413 WDT driver");
581MODULE_LICENSE("GPL");
582
583MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
584
585module_param(io, int, 0);
586MODULE_PARM_DESC(io, MODNAME " I/O port (default: " __MODULE_STRING(io) ").");
587
588module_param(timeout, int, 0);
589MODULE_PARM_DESC(timeout,
590 "Watchdog timeout in minutes (default="
591 __MODULE_STRING(timeout) ").");
592
593module_param(nowayout, int, 0);
594MODULE_PARM_DESC(nowayout,
595 "Watchdog cannot be stopped once started (default="
596 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
597
598