aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Anders & Marcus Junker <anders@anduras.de, junker@anduras.de>2006-08-24 11:11:50 -0400
committerWim Van Sebroeck <wim@iguana.be>2006-10-15 14:04:59 -0400
commit789fc0adbaf3a3ca95a3894aedacfc01863e8ae3 (patch)
tree095a8e2abad707c245f16126925553fc44a58a4d
parentcbf40d3f04c2c76a58f1183bb4a9a82fefb842e3 (diff)
[WATCHDOG] NS pc87413-wdt Watchdog driver
New watchdog driver for the NS pc87413-wdt Watchdog Timer. Signed-off-by: Sven Anders <anders@anduras.de> Signed-off-by: Marcus Junker <junker@anduras.de> Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
-rw-r--r--drivers/char/watchdog/Kconfig14
-rw-r--r--drivers/char/watchdog/Makefile1
-rw-r--r--drivers/char/watchdog/pc87413_wdt.c610
3 files changed, 625 insertions, 0 deletions
diff --git a/drivers/char/watchdog/Kconfig b/drivers/char/watchdog/Kconfig
index 0187b1185323..d3e99982b69a 100644
--- a/drivers/char/watchdog/Kconfig
+++ b/drivers/char/watchdog/Kconfig
@@ -363,6 +363,20 @@ config SCx200_WDT
363 363
364 If compiled as a module, it will be called scx200_wdt. 364 If compiled as a module, it will be called scx200_wdt.
365 365
366config PC87413_WDT
367 tristate "NS PC87413 watchdog"
368 depends on WATCHDOG && X86
369 ---help---
370 This is the driver for the hardware watchdog on the PC87413 chipset
371 This watchdog simply watches your kernel to make sure it doesn't
372 freeze, and if it does, it reboots your computer after a certain
373 amount of time.
374
375 To compile this driver as a module, choose M here: the
376 module will be called pc87413_wdt.
377
378 Most people will say N.
379
366config 60XX_WDT 380config 60XX_WDT
367 tristate "SBC-60XX Watchdog Timer" 381 tristate "SBC-60XX Watchdog Timer"
368 depends on WATCHDOG && X86 382 depends on WATCHDOG && X86
diff --git a/drivers/char/watchdog/Makefile b/drivers/char/watchdog/Makefile
index 36440497047c..e6a9f6f1d4b7 100644
--- a/drivers/char/watchdog/Makefile
+++ b/drivers/char/watchdog/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_I8XX_TCO) += i8xx_tco.o
50obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o 50obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o
51obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o 51obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
52obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o 52obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
53obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
53obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o 54obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o
54obj-$(CONFIG_SBC8360_WDT) += sbc8360.o 55obj-$(CONFIG_SBC8360_WDT) += sbc8360.o
55obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o 56obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o
diff --git a/drivers/char/watchdog/pc87413_wdt.c b/drivers/char/watchdog/pc87413_wdt.c
new file mode 100644
index 000000000000..a6d42cf385e6
--- /dev/null
+++ b/drivers/char/watchdog/pc87413_wdt.c
@@ -0,0 +1,610 @@
1/*
2 * NS pc87413-wdt Watchdog Timer driver for Linux 2.6.x.x
3 *
4 * This code is based on wdt.c with original copyright
5 *
6 * (C) Copyright 2006 Marcus Junker, <junker@anduras.de>
7 * and Sven Anders, <anders@anduras.de>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version
12 * 2 of the License, or (at your option) any later version.
13 *
14 * Neither Marcus Junker, Sven Anders nor ANDURAS AG
15 * admit liability nor provide warranty for any of this software.
16 * This material is provided "AS-IS" and at no charge.
17 *
18 * Release 1.00.
19 *
20 */
21
22
23/* #define DEBUG 1 */
24
25#include <linux/config.h>
26#include <linux/module.h>
27#include <linux/types.h>
28#include <linux/miscdevice.h>
29#include <linux/watchdog.h>
30#include <linux/ioport.h>
31#include <linux/delay.h>
32#include <linux/notifier.h>
33#include <linux/fs.h>
34#include <linux/reboot.h>
35#include <linux/init.h>
36#include <linux/spinlock.h>
37#include <linux/moduleparam.h>
38#include <linux/version.h>
39
40#include <asm/io.h>
41#include <asm/uaccess.h>
42#include <asm/system.h>
43
44#define WATCHDOG_NAME "pc87413 WDT"
45#define PFX WATCHDOG_NAME ": "
46#define DPFX WATCHDOG_NAME " - 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 /* Serial IO register */
52#define WDCTL 0x10 /* Watchdog-Timer-Controll-Register */
53#define WDTO 0x11 /* Watchdog timeout register */
54#define WDCFG 0x12 /* Watchdog config register */
55
56#define WD_TIMEOUT 1 /* minutes (1 ... 255) */
57
58
59static int pc87413_is_open=0;
60
61/*
62 * You must set these - there is no sane way to probe for this board.
63 * You can use pc87413=x to set these now.
64 */
65
66
67
68/**
69 * module_params
70 *
71 * Setup options. The board isn't really probe-able so we have to
72 * get the user to tell us the configuration.
73 */
74
75static int io=0x2E; /* Normally used addres on Portwell Boards */
76module_param(io, int, 0);
77MODULE_PARM_DESC(wdt_io, WATCHDOG_NAME " io port (default 0x2E)");
78
79static int timeout = WD_TIMEOUT; /* in minutes */
80module_param(timeout, int, 0);
81MODULE_PARM_DESC(timeout, "Watchdog timeout in minutes. 1<= timeout <=63, default=" __MODULE_STRING(WD_TIMEOUT) ".");
82
83
84/******************************************
85 * Helper functions
86 ******************************************/
87
88static void
89pc87413_select_wdt_out (void)
90{
91
92 unsigned int cr_data=0;
93
94 /* Select multiple pin,pin55,as WDT output */
95
96 outb_p(SIOCFG2, WDT_INDEX_IO_PORT);
97
98 cr_data = inb (WDT_DATA_IO_PORT);
99
100 cr_data |= 0x80; /* Set Bit7 to 1*/
101 outb_p(SIOCFG2, WDT_INDEX_IO_PORT);
102
103 outb_p(cr_data, WDT_DATA_IO_PORT);
104
105
106 #ifdef DEBUG
107 printk(KERN_INFO DPFX "Select multiple pin,pin55,as WDT output: Bit7 to 1: %d\n", cr_data);
108 #endif
109}
110
111static void
112pc87413_enable_swc(void)
113{
114
115 unsigned int cr_data=0;
116
117 /* Step 2: Enable SWC functions */
118
119 outb_p(0x07, WDT_INDEX_IO_PORT); /* Point SWC_LDN (LDN=4) */
120 outb_p(SWC_LDN, WDT_DATA_IO_PORT);
121
122 outb_p(0x30, WDT_INDEX_IO_PORT); /* Read Index 0x30 First */
123 cr_data = inb (WDT_DATA_IO_PORT);
124 cr_data |= 0x01; /* Set Bit0 to 1 */
125 outb_p(0x30, WDT_INDEX_IO_PORT);
126 outb_p(cr_data, WDT_DATA_IO_PORT); /* Index0x30_bit0P1 */
127
128 #ifdef DEBUG
129 printk(KERN_INFO DPFX "pc87413 - Enable SWC functions\n");
130 #endif
131}
132
133static unsigned int
134pc87413_get_swc_base(void)
135{
136 unsigned int swc_base_addr = 0;
137 unsigned char addr_l, addr_h = 0;
138
139 /* Step 3: Read SWC I/O Base Address */
140
141 outb_p(0x60, WDT_INDEX_IO_PORT); /* Read Index 0x60 */
142 addr_h = inb (WDT_DATA_IO_PORT);
143
144 outb_p(0x61, WDT_INDEX_IO_PORT); /* Read Index 0x61 */
145
146 addr_l = inb (WDT_DATA_IO_PORT);
147
148
149 swc_base_addr = (addr_h << 8) + addr_l;
150
151 #ifdef DEBUG
152 printk(KERN_INFO DPFX "Read SWC I/O Base Address: low %d, high %d, res %d\n", addr_l, addr_h, swc_base_addr);
153 #endif
154
155 return swc_base_addr;
156}
157
158static void
159pc87413_swc_bank3(unsigned int swc_base_addr)
160{
161 /* Step 4: Select Bank3 of SWC */
162
163 outb_p (inb (swc_base_addr + 0x0f) | 0x03, swc_base_addr + 0x0f);
164
165 #ifdef DEBUG
166 printk(KERN_INFO DPFX "Select Bank3 of SWC\n");
167 #endif
168
169}
170
171static void
172pc87413_programm_wdto (unsigned int swc_base_addr, char pc87413_time)
173{
174 /* Step 5: Programm WDTO, Twd. */
175
176 outb_p (pc87413_time, swc_base_addr + WDTO);
177
178 #ifdef DEBUG
179 printk(KERN_INFO DPFX "Set WDTO to %d minutes\n", pc87413_time);
180 #endif
181}
182
183static void
184pc87413_enable_wden (unsigned int swc_base_addr)
185{
186 /* Step 6: Enable WDEN */
187
188 outb_p(inb (swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL);
189
190 #ifdef DEBUG
191 printk(KERN_INFO DPFX "Enable WDEN\n");
192 #endif
193}
194
195static void
196pc87413_enable_sw_wd_tren (unsigned int swc_base_addr)
197{
198 /* Enable SW_WD_TREN */
199
200 outb_p(inb (swc_base_addr + WDCFG) | 0x80, swc_base_addr + WDCFG);
201
202 #ifdef DEBUG
203 printk(KERN_INFO DPFX "Enable SW_WD_TREN\n");
204 #endif
205}
206
207static void
208pc87413_disable_sw_wd_tren (unsigned int swc_base_addr)
209{
210 /* Disable SW_WD_TREN */
211
212 outb_p(inb (swc_base_addr + WDCFG) & 0x7f, swc_base_addr + WDCFG);
213
214 #ifdef DEBUG
215 printk(KERN_INFO DPFX "pc87413 - Disable SW_WD_TREN\n");
216 #endif
217}
218
219
220static void
221pc87413_enable_sw_wd_trg (unsigned int swc_base_addr)
222{
223
224 /* Enable SW_WD_TRG */
225
226 outb_p(inb (swc_base_addr + WDCTL) | 0x80, swc_base_addr + WDCTL);
227
228 #ifdef DEBUG
229 printk(KERN_INFO DPFX "pc87413 - Enable SW_WD_TRG\n");
230 #endif
231}
232
233static void
234pc87413_disable_sw_wd_trg (unsigned int swc_base_addr)
235{
236
237 /* Disable SW_WD_TRG */
238
239 outb_p(inb (swc_base_addr + WDCTL) & 0x7f, swc_base_addr + WDCTL);
240
241 #ifdef DEBUG
242 printk(KERN_INFO DPFX "Disable SW_WD_TRG\n");
243 #endif
244}
245
246
247
248static void
249pc87413_disable(void)
250{
251 unsigned int swc_base_addr;
252
253 pc87413_select_wdt_out();
254 pc87413_enable_swc();
255 swc_base_addr = pc87413_get_swc_base();
256 pc87413_swc_bank3(swc_base_addr);
257 pc87413_disable_sw_wd_tren(swc_base_addr);
258 pc87413_disable_sw_wd_trg(swc_base_addr);
259 pc87413_programm_wdto(swc_base_addr, 0);
260}
261
262
263static void
264pc87413_refresh(char pc87413_time)
265{
266 unsigned int swc_base_addr;
267
268 pc87413_select_wdt_out();
269 pc87413_enable_swc();
270 swc_base_addr = pc87413_get_swc_base();
271 pc87413_swc_bank3(swc_base_addr);
272 pc87413_disable_sw_wd_tren(swc_base_addr);
273 pc87413_disable_sw_wd_trg(swc_base_addr);
274 pc87413_programm_wdto(swc_base_addr, pc87413_time);
275 pc87413_enable_wden(swc_base_addr);
276 pc87413_enable_sw_wd_tren(swc_base_addr);
277 pc87413_enable_sw_wd_trg(swc_base_addr);
278}
279
280
281static void
282pc87413_enable(char pc87413_time)
283{
284 unsigned int swc_base_addr;
285
286 pc87413_select_wdt_out();
287 pc87413_enable_swc();
288 swc_base_addr = pc87413_get_swc_base();
289 pc87413_swc_bank3(swc_base_addr);
290 pc87413_programm_wdto(swc_base_addr, pc87413_time);
291 pc87413_enable_wden(swc_base_addr);
292 pc87413_enable_sw_wd_tren(swc_base_addr);
293 pc87413_enable_sw_wd_trg(swc_base_addr);
294
295}
296
297
298/*******************************************
299 * Kernel methods.
300 *******************************************/
301
302
303/**
304 * pc87413_status:
305 *
306 * Extract the status information from a WDT watchdog device. There are
307 * several board variants so we have to know which bits are valid. Some
308 * bits default to one and some to zero in order to be maximally painful.
309 *
310 * we then map the bits onto the status ioctl flags.
311 */
312
313static int pc87413_status(void)
314{
315 /* Not supported */
316
317 return 1;
318}
319
320static long long pc87413_llseek(struct file *file, long long offset, int origin)
321{
322 return -ESPIPE;
323}
324
325/**
326 * pc87413_write:
327 * @file: file handle to the watchdog
328 * @buf: buffer to write (unused as data does not matter here
329 * @count: count of bytes
330 * @ppos: pointer to the position to write. No seeks allowed
331 *
332 * A write to a watchdog device is defined as a keepalive signal. Any
333 * write of data will do, as we we don't define content meaning.
334 */
335
336static ssize_t
337pc87413_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos)
338{
339 if (count) {
340 pc87413_refresh (WD_TIMEOUT);
341 #ifdef DEBUG
342 printk(KERN_INFO DPFX "Write\n");
343 #endif
344 }
345 return count;
346}
347
348/*
349 * Read reports the temperature in degrees Fahrenheit.
350 */
351static ssize_t
352pc87413_read(struct file *file, char *buf, size_t count, loff_t *ptr)
353{
354
355// char timeout;
356//
357// outb_p(0x08, WDT_EFER); /* Select locical device 8 */
358// outb_p(0x0F6, WDT_EFER); /* Select CRF6 */
359// timeout = inb(WDT_EFDR); /* Read Timeout counter from CRF6 */
360
361
362// if(copy_to_user(buf,&timeout,1))
363// return -EFAULT;
364 return 1;
365
366
367}
368
369 /**
370 * pc87413_ioctl:
371 * @inode: inode of the device
372 * @file: file handle to the device
373 * @cmd: watchdog command
374 * @arg: argument pointer
375 *
376 * The watchdog API defines a common set of functions for all watchdogs
377 * according to their available features. We only actually usefully support
378 * querying capabilities and current status.
379 */
380
381static int
382pc87413_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
383 unsigned long arg)
384{
385 static struct watchdog_info ident=
386 {
387 .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
388 .firmware_version = 1,
389 .identity = "pc87413(HF/F)"
390 };
391
392 ident.options=1; /* Mask down to the card we have */
393
394 switch(cmd)
395 {
396 default:
397 return -ENOIOCTLCMD;
398 case WDIOC_GETSUPPORT:
399 return copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident))?-EFAULT:0;
400 case WDIOC_GETSTATUS:
401 return put_user(pc87413_status(),(int *)arg);
402 case WDIOC_GETBOOTSTATUS:
403 return put_user(0, (int *)arg);
404 case WDIOC_KEEPALIVE:
405 pc87413_refresh(WD_TIMEOUT);
406 #ifdef DEBUG
407 printk(KERN_INFO DPFX "keepalive\n");
408 #endif
409 return 0;
410 }
411}
412
413/**
414 * pc87413_open:
415 * @inode: inode of device
416 * @file: file handle to device
417 *
418 * One of our two misc devices has been opened. The watchdog device is
419 * single open and on opening we load the counters. Counter zero is a
420 * 100Hz cascade, into counter 1 which downcounts to reboot. When the
421 * counter triggers counter 2 downcounts the length of the reset pulse
422 * which set set to be as long as possible.
423 */
424
425static int
426pc87413_open(struct inode *inode, struct file *file)
427{
428 switch(MINOR(inode->i_rdev))
429 {
430 case WATCHDOG_MINOR:
431 if(pc87413_is_open)
432 return -EBUSY;
433 /*
434 * Activate
435 */
436
437 pc87413_is_open=1;
438 pc87413_refresh(WD_TIMEOUT);
439 #ifdef DEBUG
440 printk(KERN_INFO DPFX "Open\n");
441 #endif
442 return 0;
443 case TEMP_MINOR:
444 return 0;
445 default:
446 return -ENODEV;
447 }
448}
449
450/**
451 * pc87413_close:
452 * @inode: inode to board
453 * @file: file handle to board
454 *
455 * The watchdog has a configurable API. There is a religious dispute
456 * between people who want their watchdog to be able to shut down and
457 * those who want to be sure if the watchdog manager dies the machine
458 * reboots. In the former case we disable the counters, in the latter
459 * case you have to open it again very soon.
460 */
461
462static int
463pc87413_release(struct inode *inode, struct file *file)
464{
465 if(MINOR(inode->i_rdev)==WATCHDOG_MINOR)
466 {
467#ifndef CONFIG_WATCHDOG_NOWAYOUT
468 pc87413_disable();
469#endif
470 pc87413_is_open=0;
471 #ifdef DEBUG
472 printk(KERN_INFO DPFX "Release\n");
473 #endif
474 }
475 return 0;
476}
477
478/**
479 * notify_sys:
480 * @this: our notifier block
481 * @code: the event being reported
482 * @unused: unused
483 *
484 * Our notifier is called on system shutdowns. We want to turn the card
485 * off at reboot otherwise the machine will reboot again during memory
486 * test or worse yet during the following fsck. This would suck, in fact
487 * trust me - if it happens it does suck.
488 */
489
490static int
491pc87413_notify_sys(struct notifier_block *this, unsigned long code,
492 void *unused)
493{
494 if(code==SYS_DOWN || code==SYS_HALT)
495 {
496 /* Turn the card off */
497 pc87413_disable();
498 }
499 return NOTIFY_DONE;
500}
501
502
503/*****************************************************
504 * Kernel Interfaces
505 *****************************************************/
506
507
508static struct file_operations pc87413_fops = {
509 .owner = THIS_MODULE,
510 .llseek = pc87413_llseek,
511 .read = pc87413_read,
512 .write = pc87413_write,
513 .ioctl = pc87413_ioctl,
514 .open = pc87413_open,
515 .release = pc87413_release,
516};
517
518static struct miscdevice pc87413_miscdev=
519{
520 .minor = WATCHDOG_MINOR,
521 .name = "watchdog",
522 .fops = &pc87413_fops
523};
524
525/*
526 * The WDT card needs to learn about soft shutdowns in order to
527 * turn the timebomb registers off.
528 */
529
530static struct notifier_block pc87413_notifier=
531{
532 pc87413_notify_sys,
533 NULL,
534 0
535};
536
537
538/**
539 * pc87413_exit:
540 *
541 * Unload the watchdog. You cannot do this with any file handles open.
542 * If your watchdog is set to continue ticking on close and you unload
543 * it, well it keeps ticking. We won't get the interrupt but the board
544 * will not touch PC memory so all is fine. You just have to load a new
545 * module in 60 seconds or reboot.
546 */
547
548static void
549pc87413_exit(void)
550{
551 pc87413_disable();
552 misc_deregister(&pc87413_miscdev);
553 unregister_reboot_notifier(&pc87413_notifier);
554 /* release_region(io,2); */
555}
556
557
558/**
559 * pc87413_init:
560 *
561 * Set up the WDT watchdog board. All we have to do is grab the
562 * resources we require and bitch if anyone beat us to them.
563 * The open() function will actually kick the board off.
564 */
565
566static int
567pc87413_init(void)
568{
569 int ret;
570
571 printk(KERN_INFO PFX "Version 1.00 at io 0x%X\n", WDT_INDEX_IO_PORT);
572
573
574 /* request_region(io, 2, "pc87413"); */
575
576
577 ret = register_reboot_notifier(&pc87413_notifier);
578 if (ret != 0) {
579 printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
580 ret);
581 }
582
583
584
585 ret = misc_register(&pc87413_miscdev);
586
587 if (ret != 0) {
588 printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
589 WATCHDOG_MINOR, ret);
590 unregister_reboot_notifier(&pc87413_notifier);
591 return ret;
592 }
593
594 printk (KERN_INFO PFX "initialized. timeout=%d min \n", timeout);
595
596
597 pc87413_enable(WD_TIMEOUT);
598
599 return 0;
600}
601
602
603module_init(pc87413_init);
604module_exit(pc87413_exit);
605
606MODULE_LICENSE("GPL");
607MODULE_AUTHOR("Marcus Junker <junker@anduras.de>, Sven Anders <anders@anduras.de>");
608MODULE_DESCRIPTION("PC87413 WDT driver");
609MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
610