diff options
author | Linus Torvalds <torvalds@woody.osdl.org> | 2006-12-01 19:47:26 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.osdl.org> | 2006-12-01 19:47:26 -0500 |
commit | b6ef977b608b01e0f338afd9445cab5436c61e00 (patch) | |
tree | 5af86a7d3e222ff55292655b06601fa5798c0564 /drivers/char | |
parent | 9ba5aa3126873a9c29999b78de20a52c5f87d389 (diff) | |
parent | bdbf77d6707a52bdeff223d0a60df12d086d21d7 (diff) |
Merge master.kernel.org:/pub/scm/linux/kernel/git/wim/linux-2.6-watchdog
* master.kernel.org:/pub/scm/linux/kernel/git/wim/linux-2.6-watchdog:
[WATCHDOG] MIPS RM9000 on-chip watchdog device - patch 4
[WATCHDOG] MIPS RM9000 on-chip watchdog device - patch 3
[WATCHDOG] MIPS RM9000 on-chip watchdog device - patch 2
[WATCHDOG] MIPS RM9000 on-chip watchdog device - patch 1
[WATCHDOG] MIPS RM9000 on-chip watchdog device
[WATCHDOG] Add iTCO vendor specific support
[WATCHDOG] sc1200wdt.c pnp unregister fix.
[WATCHDOG] config.h removal
[WATCHDOG] NS pc87413-wdt Watchdog driver - fixes
[WATCHDOG] NS pc87413-wdt Watchdog driver v1.1
[WATCHDOG] NS pc87413-wdt Watchdog driver
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/watchdog/Kconfig | 32 | ||||
-rw-r--r-- | drivers/char/watchdog/Makefile | 4 | ||||
-rw-r--r-- | drivers/char/watchdog/iTCO_vendor_support.c | 307 | ||||
-rw-r--r-- | drivers/char/watchdog/iTCO_wdt.c | 29 | ||||
-rw-r--r-- | drivers/char/watchdog/pc87413_wdt.c | 635 | ||||
-rw-r--r-- | drivers/char/watchdog/rm9k_wdt.c | 420 |
6 files changed, 1423 insertions, 4 deletions
diff --git a/drivers/char/watchdog/Kconfig b/drivers/char/watchdog/Kconfig index 0187b1185323..ea09d0c974ea 100644 --- a/drivers/char/watchdog/Kconfig +++ b/drivers/char/watchdog/Kconfig | |||
@@ -340,6 +340,14 @@ config ITCO_WDT | |||
340 | To compile this driver as a module, choose M here: the | 340 | To compile this driver as a module, choose M here: the |
341 | module will be called iTCO_wdt. | 341 | module will be called iTCO_wdt. |
342 | 342 | ||
343 | config ITCO_VENDOR_SUPPORT | ||
344 | bool "Intel TCO Timer/Watchdog Specific Vendor Support" | ||
345 | depends on ITCO_WDT | ||
346 | ---help--- | ||
347 | Add vendor specific support to the intel TCO timer based watchdog | ||
348 | devices. At this moment we only have additional support for some | ||
349 | SuperMicro Inc. motherboards. | ||
350 | |||
343 | config SC1200_WDT | 351 | config SC1200_WDT |
344 | tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog" | 352 | tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog" |
345 | depends on WATCHDOG && X86 | 353 | depends on WATCHDOG && X86 |
@@ -363,6 +371,20 @@ config SCx200_WDT | |||
363 | 371 | ||
364 | If compiled as a module, it will be called scx200_wdt. | 372 | If compiled as a module, it will be called scx200_wdt. |
365 | 373 | ||
374 | config PC87413_WDT | ||
375 | tristate "NS PC87413 watchdog" | ||
376 | depends on WATCHDOG && X86 | ||
377 | ---help--- | ||
378 | This is the driver for the hardware watchdog on the PC87413 chipset | ||
379 | This watchdog simply watches your kernel to make sure it doesn't | ||
380 | freeze, and if it does, it reboots your computer after a certain | ||
381 | amount of time. | ||
382 | |||
383 | To compile this driver as a module, choose M here: the | ||
384 | module will be called pc87413_wdt. | ||
385 | |||
386 | Most people will say N. | ||
387 | |||
366 | config 60XX_WDT | 388 | config 60XX_WDT |
367 | tristate "SBC-60XX Watchdog Timer" | 389 | tristate "SBC-60XX Watchdog Timer" |
368 | depends on WATCHDOG && X86 | 390 | depends on WATCHDOG && X86 |
@@ -553,6 +575,16 @@ config INDYDOG | |||
553 | timer expired and no process has written to /dev/watchdog during | 575 | timer expired and no process has written to /dev/watchdog during |
554 | that time. | 576 | that time. |
555 | 577 | ||
578 | config WDT_RM9K_GPI | ||
579 | tristate "RM9000/GPI hardware watchdog" | ||
580 | depends on WATCHDOG && CPU_RM9000 | ||
581 | help | ||
582 | Watchdog implementation using the GPI hardware found on | ||
583 | PMC-Sierra RM9xxx CPUs. | ||
584 | |||
585 | To compile this driver as a module, choose M here: the | ||
586 | module will be called rm9k_wdt. | ||
587 | |||
556 | # S390 Architecture | 588 | # S390 Architecture |
557 | 589 | ||
558 | config ZVM_WATCHDOG | 590 | config ZVM_WATCHDOG |
diff --git a/drivers/char/watchdog/Makefile b/drivers/char/watchdog/Makefile index 36440497047c..2cd8ff8d10ac 100644 --- a/drivers/char/watchdog/Makefile +++ b/drivers/char/watchdog/Makefile | |||
@@ -47,9 +47,10 @@ obj-$(CONFIG_IBMASR) += ibmasr.o | |||
47 | obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o | 47 | obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o |
48 | obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o | 48 | obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o |
49 | obj-$(CONFIG_I8XX_TCO) += i8xx_tco.o | 49 | obj-$(CONFIG_I8XX_TCO) += i8xx_tco.o |
50 | obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o | 50 | obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o iTCO_vendor_support.o |
51 | obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o | 51 | obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o |
52 | obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o | 52 | obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o |
53 | obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o | ||
53 | obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o | 54 | obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o |
54 | obj-$(CONFIG_SBC8360_WDT) += sbc8360.o | 55 | obj-$(CONFIG_SBC8360_WDT) += sbc8360.o |
55 | obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o | 56 | obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o |
@@ -72,6 +73,7 @@ obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o | |||
72 | 73 | ||
73 | # MIPS Architecture | 74 | # MIPS Architecture |
74 | obj-$(CONFIG_INDYDOG) += indydog.o | 75 | obj-$(CONFIG_INDYDOG) += indydog.o |
76 | obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o | ||
75 | 77 | ||
76 | # S390 Architecture | 78 | # S390 Architecture |
77 | 79 | ||
diff --git a/drivers/char/watchdog/iTCO_vendor_support.c b/drivers/char/watchdog/iTCO_vendor_support.c new file mode 100644 index 000000000000..415083990097 --- /dev/null +++ b/drivers/char/watchdog/iTCO_vendor_support.c | |||
@@ -0,0 +1,307 @@ | |||
1 | /* | ||
2 | * intel TCO vendor specific watchdog driver support | ||
3 | * | ||
4 | * (c) Copyright 2006 Wim Van Sebroeck <wim@iguana.be>. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License | ||
8 | * as published by the Free Software Foundation; either version | ||
9 | * 2 of the License, or (at your option) any later version. | ||
10 | * | ||
11 | * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor | ||
12 | * provide warranty for any of this software. This material is | ||
13 | * provided "AS-IS" and at no charge. | ||
14 | */ | ||
15 | |||
16 | /* | ||
17 | * Includes, defines, variables, module parameters, ... | ||
18 | */ | ||
19 | |||
20 | /* Module and version information */ | ||
21 | #define DRV_NAME "iTCO_vendor_support" | ||
22 | #define DRV_VERSION "1.01" | ||
23 | #define DRV_RELDATE "11-Nov-2006" | ||
24 | #define PFX DRV_NAME ": " | ||
25 | |||
26 | /* Includes */ | ||
27 | #include <linux/module.h> /* For module specific items */ | ||
28 | #include <linux/moduleparam.h> /* For new moduleparam's */ | ||
29 | #include <linux/types.h> /* For standard types (like size_t) */ | ||
30 | #include <linux/errno.h> /* For the -ENODEV/... values */ | ||
31 | #include <linux/kernel.h> /* For printk/panic/... */ | ||
32 | #include <linux/init.h> /* For __init/__exit/... */ | ||
33 | #include <linux/ioport.h> /* For io-port access */ | ||
34 | |||
35 | #include <asm/io.h> /* For inb/outb/... */ | ||
36 | |||
37 | /* iTCO defines */ | ||
38 | #define SMI_EN acpibase + 0x30 /* SMI Control and Enable Register */ | ||
39 | #define TCOBASE acpibase + 0x60 /* TCO base address */ | ||
40 | #define TCO1_STS TCOBASE + 0x04 /* TCO1 Status Register */ | ||
41 | |||
42 | /* List of vendor support modes */ | ||
43 | #define SUPERMICRO_OLD_BOARD 1 /* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */ | ||
44 | #define SUPERMICRO_NEW_BOARD 2 /* SuperMicro Pentium 4 / Xeon 4 / EMT64T Era Systems */ | ||
45 | |||
46 | static int vendorsupport = 0; | ||
47 | module_param(vendorsupport, int, 0); | ||
48 | MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default=0 (none), 1=SuperMicro Pent3, 2=SuperMicro Pent4+"); | ||
49 | |||
50 | /* | ||
51 | * Vendor Specific Support | ||
52 | */ | ||
53 | |||
54 | /* | ||
55 | * Vendor Support: 1 | ||
56 | * Board: Super Micro Computer Inc. 370SSE+-OEM1/P3TSSE | ||
57 | * iTCO chipset: ICH2 | ||
58 | * | ||
59 | * Code contributed by: R. Seretny <lkpatches@paypc.com> | ||
60 | * Documentation obtained by R. Seretny from SuperMicro Technical Support | ||
61 | * | ||
62 | * To enable Watchdog function: | ||
63 | * BIOS setup -> Power -> TCO Logic SMI Enable -> Within5Minutes | ||
64 | * This setting enables SMI to clear the watchdog expired flag. | ||
65 | * If BIOS or CPU fail which may cause SMI hang, then system will | ||
66 | * reboot. When application starts to use watchdog function, | ||
67 | * application has to take over the control from SMI. | ||
68 | * | ||
69 | * For P3TSSE, J36 jumper needs to be removed to enable the Watchdog | ||
70 | * function. | ||
71 | * | ||
72 | * Note: The system will reboot when Expire Flag is set TWICE. | ||
73 | * So, if the watchdog timer is 20 seconds, then the maximum hang | ||
74 | * time is about 40 seconds, and the minimum hang time is about | ||
75 | * 20.6 seconds. | ||
76 | */ | ||
77 | |||
78 | static void supermicro_old_pre_start(unsigned long acpibase) | ||
79 | { | ||
80 | unsigned long val32; | ||
81 | |||
82 | val32 = inl(SMI_EN); | ||
83 | val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */ | ||
84 | outl(val32, SMI_EN); /* Needed to activate watchdog */ | ||
85 | } | ||
86 | |||
87 | static void supermicro_old_pre_stop(unsigned long acpibase) | ||
88 | { | ||
89 | unsigned long val32; | ||
90 | |||
91 | val32 = inl(SMI_EN); | ||
92 | val32 &= 0x00002000; /* Turn on SMI clearing watchdog */ | ||
93 | outl(val32, SMI_EN); /* Needed to deactivate watchdog */ | ||
94 | } | ||
95 | |||
96 | static void supermicro_old_pre_keepalive(unsigned long acpibase) | ||
97 | { | ||
98 | /* Reload TCO Timer (done in iTCO_wdt_keepalive) + */ | ||
99 | /* Clear "Expire Flag" (Bit 3 of TC01_STS register) */ | ||
100 | outb(0x08, TCO1_STS); | ||
101 | } | ||
102 | |||
103 | /* | ||
104 | * Vendor Support: 2 | ||
105 | * Board: Super Micro Computer Inc. P4SBx, P4DPx | ||
106 | * iTCO chipset: ICH4 | ||
107 | * | ||
108 | * Code contributed by: R. Seretny <lkpatches@paypc.com> | ||
109 | * Documentation obtained by R. Seretny from SuperMicro Technical Support | ||
110 | * | ||
111 | * To enable Watchdog function: | ||
112 | * 1. BIOS | ||
113 | * For P4SBx: | ||
114 | * BIOS setup -> Advanced -> Integrated Peripherals -> Watch Dog Feature | ||
115 | * For P4DPx: | ||
116 | * BIOS setup -> Advanced -> I/O Device Configuration -> Watch Dog | ||
117 | * This setting enables or disables Watchdog function. When enabled, the | ||
118 | * default watchdog timer is set to be 5 minutes (about 4’35”). It is | ||
119 | * enough to load and run the OS. The application (service or driver) has | ||
120 | * to take over the control once OS is running up and before watchdog | ||
121 | * expires. | ||
122 | * | ||
123 | * 2. JUMPER | ||
124 | * For P4SBx: JP39 | ||
125 | * For P4DPx: JP37 | ||
126 | * This jumper is used for safety. Closed is enabled. This jumper | ||
127 | * prevents user enables watchdog in BIOS by accident. | ||
128 | * | ||
129 | * To enable Watch Dog function, both BIOS and JUMPER must be enabled. | ||
130 | * | ||
131 | * The documentation lists motherboards P4SBx and P4DPx series as of | ||
132 | * 20-March-2002. However, this code works flawlessly with much newer | ||
133 | * motherboards, such as my X6DHR-8G2 (SuperServer 6014H-82). | ||
134 | * | ||
135 | * The original iTCO driver as written does not actually reset the | ||
136 | * watchdog timer on these machines, as a result they reboot after five | ||
137 | * minutes. | ||
138 | * | ||
139 | * NOTE: You may leave the Watchdog function disabled in the SuperMicro | ||
140 | * BIOS to avoid a "boot-race"... This driver will enable watchdog | ||
141 | * functionality even if it's disabled in the BIOS once the /dev/watchdog | ||
142 | * file is opened. | ||
143 | */ | ||
144 | |||
145 | /* I/O Port's */ | ||
146 | #define SM_REGINDEX 0x2e /* SuperMicro ICH4+ Register Index */ | ||
147 | #define SM_DATAIO 0x2f /* SuperMicro ICH4+ Register Data I/O */ | ||
148 | |||
149 | /* Control Register's */ | ||
150 | #define SM_CTLPAGESW 0x07 /* SuperMicro ICH4+ Control Page Switch */ | ||
151 | #define SM_CTLPAGE 0x08 /* SuperMicro ICH4+ Control Page Num */ | ||
152 | |||
153 | #define SM_WATCHENABLE 0x30 /* Watchdog enable: Bit 0: 0=off, 1=on */ | ||
154 | |||
155 | #define SM_WATCHPAGE 0x87 /* Watchdog unlock control page */ | ||
156 | |||
157 | #define SM_ENDWATCH 0xAA /* Watchdog lock control page */ | ||
158 | |||
159 | #define SM_COUNTMODE 0xf5 /* Watchdog count mode select */ | ||
160 | /* (Bit 3: 0 = seconds, 1 = minutes */ | ||
161 | |||
162 | #define SM_WATCHTIMER 0xf6 /* 8-bits, Watchdog timer counter (RW) */ | ||
163 | |||
164 | #define SM_RESETCONTROL 0xf7 /* Watchdog reset control */ | ||
165 | /* Bit 6: timer is reset by kbd interrupt */ | ||
166 | /* Bit 7: timer is reset by mouse interrupt */ | ||
167 | |||
168 | static void supermicro_new_unlock_watchdog(void) | ||
169 | { | ||
170 | outb(SM_WATCHPAGE, SM_REGINDEX); /* Write 0x87 to port 0x2e twice */ | ||
171 | outb(SM_WATCHPAGE, SM_REGINDEX); | ||
172 | |||
173 | outb(SM_CTLPAGESW, SM_REGINDEX); /* Switch to watchdog control page */ | ||
174 | outb(SM_CTLPAGE, SM_DATAIO); | ||
175 | } | ||
176 | |||
177 | static void supermicro_new_lock_watchdog(void) | ||
178 | { | ||
179 | outb(SM_ENDWATCH, SM_REGINDEX); | ||
180 | } | ||
181 | |||
182 | static void supermicro_new_pre_start(unsigned int heartbeat) | ||
183 | { | ||
184 | unsigned int val; | ||
185 | |||
186 | supermicro_new_unlock_watchdog(); | ||
187 | |||
188 | /* Watchdog timer setting needs to be in seconds*/ | ||
189 | outb(SM_COUNTMODE, SM_REGINDEX); | ||
190 | val = inb(SM_DATAIO); | ||
191 | val &= 0xF7; | ||
192 | outb(val, SM_DATAIO); | ||
193 | |||
194 | /* Write heartbeat interval to WDOG */ | ||
195 | outb (SM_WATCHTIMER, SM_REGINDEX); | ||
196 | outb((heartbeat & 255), SM_DATAIO); | ||
197 | |||
198 | /* Make sure keyboard/mouse interrupts don't interfere */ | ||
199 | outb(SM_RESETCONTROL, SM_REGINDEX); | ||
200 | val = inb(SM_DATAIO); | ||
201 | val &= 0x3f; | ||
202 | outb(val, SM_DATAIO); | ||
203 | |||
204 | /* enable watchdog by setting bit 0 of Watchdog Enable to 1 */ | ||
205 | outb(SM_WATCHENABLE, SM_REGINDEX); | ||
206 | val = inb(SM_DATAIO); | ||
207 | val |= 0x01; | ||
208 | outb(val, SM_DATAIO); | ||
209 | |||
210 | supermicro_new_lock_watchdog(); | ||
211 | } | ||
212 | |||
213 | static void supermicro_new_pre_stop(void) | ||
214 | { | ||
215 | unsigned int val; | ||
216 | |||
217 | supermicro_new_unlock_watchdog(); | ||
218 | |||
219 | /* disable watchdog by setting bit 0 of Watchdog Enable to 0 */ | ||
220 | outb(SM_WATCHENABLE, SM_REGINDEX); | ||
221 | val = inb(SM_DATAIO); | ||
222 | val &= 0xFE; | ||
223 | outb(val, SM_DATAIO); | ||
224 | |||
225 | supermicro_new_lock_watchdog(); | ||
226 | } | ||
227 | |||
228 | static void supermicro_new_pre_set_heartbeat(unsigned int heartbeat) | ||
229 | { | ||
230 | supermicro_new_unlock_watchdog(); | ||
231 | |||
232 | /* reset watchdog timeout to heartveat value */ | ||
233 | outb(SM_WATCHTIMER, SM_REGINDEX); | ||
234 | outb((heartbeat & 255), SM_DATAIO); | ||
235 | |||
236 | supermicro_new_lock_watchdog(); | ||
237 | } | ||
238 | |||
239 | /* | ||
240 | * Generic Support Functions | ||
241 | */ | ||
242 | |||
243 | void iTCO_vendor_pre_start(unsigned long acpibase, | ||
244 | unsigned int heartbeat) | ||
245 | { | ||
246 | if (vendorsupport == SUPERMICRO_OLD_BOARD) | ||
247 | supermicro_old_pre_start(acpibase); | ||
248 | else if (vendorsupport == SUPERMICRO_NEW_BOARD) | ||
249 | supermicro_new_pre_start(heartbeat); | ||
250 | } | ||
251 | EXPORT_SYMBOL(iTCO_vendor_pre_start); | ||
252 | |||
253 | void iTCO_vendor_pre_stop(unsigned long acpibase) | ||
254 | { | ||
255 | if (vendorsupport == SUPERMICRO_OLD_BOARD) | ||
256 | supermicro_old_pre_stop(acpibase); | ||
257 | else if (vendorsupport == SUPERMICRO_NEW_BOARD) | ||
258 | supermicro_new_pre_stop(); | ||
259 | } | ||
260 | EXPORT_SYMBOL(iTCO_vendor_pre_stop); | ||
261 | |||
262 | void iTCO_vendor_pre_keepalive(unsigned long acpibase, unsigned int heartbeat) | ||
263 | { | ||
264 | if (vendorsupport == SUPERMICRO_OLD_BOARD) | ||
265 | supermicro_old_pre_keepalive(acpibase); | ||
266 | else if (vendorsupport == SUPERMICRO_NEW_BOARD) | ||
267 | supermicro_new_pre_set_heartbeat(heartbeat); | ||
268 | } | ||
269 | EXPORT_SYMBOL(iTCO_vendor_pre_keepalive); | ||
270 | |||
271 | void iTCO_vendor_pre_set_heartbeat(unsigned int heartbeat) | ||
272 | { | ||
273 | if (vendorsupport == SUPERMICRO_NEW_BOARD) | ||
274 | supermicro_new_pre_set_heartbeat(heartbeat); | ||
275 | } | ||
276 | EXPORT_SYMBOL(iTCO_vendor_pre_set_heartbeat); | ||
277 | |||
278 | int iTCO_vendor_check_noreboot_on(void) | ||
279 | { | ||
280 | switch(vendorsupport) { | ||
281 | case SUPERMICRO_OLD_BOARD: | ||
282 | return 0; | ||
283 | default: | ||
284 | return 1; | ||
285 | } | ||
286 | } | ||
287 | EXPORT_SYMBOL(iTCO_vendor_check_noreboot_on); | ||
288 | |||
289 | static int __init iTCO_vendor_init_module(void) | ||
290 | { | ||
291 | printk (KERN_INFO PFX "vendor-support=%d\n", vendorsupport); | ||
292 | return 0; | ||
293 | } | ||
294 | |||
295 | static void __exit iTCO_vendor_exit_module(void) | ||
296 | { | ||
297 | printk (KERN_INFO PFX "Module Unloaded\n"); | ||
298 | } | ||
299 | |||
300 | module_init(iTCO_vendor_init_module); | ||
301 | module_exit(iTCO_vendor_exit_module); | ||
302 | |||
303 | MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>, R. Seretny <lkpatches@paypc.com>"); | ||
304 | MODULE_DESCRIPTION("Intel TCO Vendor Specific WatchDog Timer Driver Support"); | ||
305 | MODULE_VERSION(DRV_VERSION); | ||
306 | MODULE_LICENSE("GPL"); | ||
307 | |||
diff --git a/drivers/char/watchdog/iTCO_wdt.c b/drivers/char/watchdog/iTCO_wdt.c index b6f29cb8bd39..7eac922df867 100644 --- a/drivers/char/watchdog/iTCO_wdt.c +++ b/drivers/char/watchdog/iTCO_wdt.c | |||
@@ -48,8 +48,8 @@ | |||
48 | 48 | ||
49 | /* Module and version information */ | 49 | /* Module and version information */ |
50 | #define DRV_NAME "iTCO_wdt" | 50 | #define DRV_NAME "iTCO_wdt" |
51 | #define DRV_VERSION "1.00" | 51 | #define DRV_VERSION "1.01" |
52 | #define DRV_RELDATE "08-Oct-2006" | 52 | #define DRV_RELDATE "11-Nov-2006" |
53 | #define PFX DRV_NAME ": " | 53 | #define PFX DRV_NAME ": " |
54 | 54 | ||
55 | /* Includes */ | 55 | /* Includes */ |
@@ -189,6 +189,21 @@ static int nowayout = WATCHDOG_NOWAYOUT; | |||
189 | module_param(nowayout, int, 0); | 189 | module_param(nowayout, int, 0); |
190 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | 190 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); |
191 | 191 | ||
192 | /* iTCO Vendor Specific Support hooks */ | ||
193 | #ifdef CONFIG_ITCO_VENDOR_SUPPORT | ||
194 | extern void iTCO_vendor_pre_start(unsigned long, unsigned int); | ||
195 | extern void iTCO_vendor_pre_stop(unsigned long); | ||
196 | extern void iTCO_vendor_pre_keepalive(unsigned long, unsigned int); | ||
197 | extern void iTCO_vendor_pre_set_heartbeat(unsigned int); | ||
198 | extern int iTCO_vendor_check_noreboot_on(void); | ||
199 | #else | ||
200 | #define iTCO_vendor_pre_start(acpibase, heartbeat) {} | ||
201 | #define iTCO_vendor_pre_stop(acpibase) {} | ||
202 | #define iTCO_vendor_pre_keepalive(acpibase,heartbeat) {} | ||
203 | #define iTCO_vendor_pre_set_heartbeat(heartbeat) {} | ||
204 | #define iTCO_vendor_check_noreboot_on() 1 /* 1=check noreboot; 0=don't check */ | ||
205 | #endif | ||
206 | |||
192 | /* | 207 | /* |
193 | * Some TCO specific functions | 208 | * Some TCO specific functions |
194 | */ | 209 | */ |
@@ -249,6 +264,8 @@ static int iTCO_wdt_start(void) | |||
249 | 264 | ||
250 | spin_lock(&iTCO_wdt_private.io_lock); | 265 | spin_lock(&iTCO_wdt_private.io_lock); |
251 | 266 | ||
267 | iTCO_vendor_pre_start(iTCO_wdt_private.ACPIBASE, heartbeat); | ||
268 | |||
252 | /* disable chipset's NO_REBOOT bit */ | 269 | /* disable chipset's NO_REBOOT bit */ |
253 | if (iTCO_wdt_unset_NO_REBOOT_bit()) { | 270 | if (iTCO_wdt_unset_NO_REBOOT_bit()) { |
254 | printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n"); | 271 | printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n"); |
@@ -273,6 +290,8 @@ static int iTCO_wdt_stop(void) | |||
273 | 290 | ||
274 | spin_lock(&iTCO_wdt_private.io_lock); | 291 | spin_lock(&iTCO_wdt_private.io_lock); |
275 | 292 | ||
293 | iTCO_vendor_pre_stop(iTCO_wdt_private.ACPIBASE); | ||
294 | |||
276 | /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */ | 295 | /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */ |
277 | val = inw(TCO1_CNT); | 296 | val = inw(TCO1_CNT); |
278 | val |= 0x0800; | 297 | val |= 0x0800; |
@@ -293,6 +312,8 @@ static int iTCO_wdt_keepalive(void) | |||
293 | { | 312 | { |
294 | spin_lock(&iTCO_wdt_private.io_lock); | 313 | spin_lock(&iTCO_wdt_private.io_lock); |
295 | 314 | ||
315 | iTCO_vendor_pre_keepalive(iTCO_wdt_private.ACPIBASE, heartbeat); | ||
316 | |||
296 | /* Reload the timer by writing to the TCO Timer Counter register */ | 317 | /* Reload the timer by writing to the TCO Timer Counter register */ |
297 | if (iTCO_wdt_private.iTCO_version == 2) { | 318 | if (iTCO_wdt_private.iTCO_version == 2) { |
298 | outw(0x01, TCO_RLD); | 319 | outw(0x01, TCO_RLD); |
@@ -319,6 +340,8 @@ static int iTCO_wdt_set_heartbeat(int t) | |||
319 | ((iTCO_wdt_private.iTCO_version == 1) && (tmrval > 0x03f))) | 340 | ((iTCO_wdt_private.iTCO_version == 1) && (tmrval > 0x03f))) |
320 | return -EINVAL; | 341 | return -EINVAL; |
321 | 342 | ||
343 | iTCO_vendor_pre_set_heartbeat(tmrval); | ||
344 | |||
322 | /* Write new heartbeat to watchdog */ | 345 | /* Write new heartbeat to watchdog */ |
323 | if (iTCO_wdt_private.iTCO_version == 2) { | 346 | if (iTCO_wdt_private.iTCO_version == 2) { |
324 | spin_lock(&iTCO_wdt_private.io_lock); | 347 | spin_lock(&iTCO_wdt_private.io_lock); |
@@ -569,7 +592,7 @@ static int iTCO_wdt_init(struct pci_dev *pdev, const struct pci_device_id *ent, | |||
569 | } | 592 | } |
570 | 593 | ||
571 | /* Check chipset's NO_REBOOT bit */ | 594 | /* Check chipset's NO_REBOOT bit */ |
572 | if (iTCO_wdt_unset_NO_REBOOT_bit()) { | 595 | if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) { |
573 | printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n"); | 596 | printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n"); |
574 | ret = -ENODEV; /* Cannot reset NO_REBOOT bit */ | 597 | ret = -ENODEV; /* Cannot reset NO_REBOOT bit */ |
575 | goto out; | 598 | goto out; |
diff --git a/drivers/char/watchdog/pc87413_wdt.c b/drivers/char/watchdog/pc87413_wdt.c new file mode 100644 index 000000000000..1d447e32af41 --- /dev/null +++ b/drivers/char/watchdog/pc87413_wdt.c | |||
@@ -0,0 +1,635 @@ | |||
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 Sven Anders, <anders@anduras.de> | ||
7 | * and Marcus Junker, <junker@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 Sven Anders, Marcus Junker 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.1 | ||
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/version.h> | ||
34 | |||
35 | #include <asm/io.h> | ||
36 | #include <asm/uaccess.h> | ||
37 | #include <asm/system.h> | ||
38 | |||
39 | /* #define DEBUG 1 */ | ||
40 | |||
41 | #define DEFAULT_TIMEOUT 1 /* 1 minute */ | ||
42 | #define MAX_TIMEOUT 255 | ||
43 | |||
44 | #define VERSION "1.1" | ||
45 | #define MODNAME "pc87413 WDT" | ||
46 | #define PFX MODNAME ": " | ||
47 | #define DPFX MODNAME " - DEBUG: " | ||
48 | |||
49 | #define WDT_INDEX_IO_PORT (io+0) /* I/O port base (index register) */ | ||
50 | #define WDT_DATA_IO_PORT (WDT_INDEX_IO_PORT+1) | ||
51 | #define SWC_LDN 0x04 | ||
52 | #define SIOCFG2 0x22 /* Serial IO register */ | ||
53 | #define WDCTL 0x10 /* Watchdog-Timer-Controll-Register */ | ||
54 | #define WDTO 0x11 /* Watchdog timeout register */ | ||
55 | #define WDCFG 0x12 /* Watchdog config register */ | ||
56 | |||
57 | static int io = 0x2E; /* Address used on Portwell Boards */ | ||
58 | |||
59 | static int timeout = DEFAULT_TIMEOUT; /* timeout value */ | ||
60 | static unsigned long timer_enabled = 0; /* is the timer enabled? */ | ||
61 | |||
62 | static char expect_close; /* is the close expected? */ | ||
63 | |||
64 | static spinlock_t io_lock; /* to guard the watchdog from io races */ | ||
65 | |||
66 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
67 | |||
68 | /* -- Low level function ----------------------------------------*/ | ||
69 | |||
70 | /* Select pins for Watchdog output */ | ||
71 | |||
72 | static inline void pc87413_select_wdt_out (void) | ||
73 | { | ||
74 | unsigned int cr_data = 0; | ||
75 | |||
76 | /* Step 1: Select multiple pin,pin55,as WDT output */ | ||
77 | |||
78 | outb_p(SIOCFG2, WDT_INDEX_IO_PORT); | ||
79 | |||
80 | cr_data = inb (WDT_DATA_IO_PORT); | ||
81 | |||
82 | cr_data |= 0x80; /* Set Bit7 to 1*/ | ||
83 | outb_p(SIOCFG2, WDT_INDEX_IO_PORT); | ||
84 | |||
85 | outb_p(cr_data, WDT_DATA_IO_PORT); | ||
86 | |||
87 | #ifdef DEBUG | ||
88 | printk(KERN_INFO DPFX "Select multiple pin,pin55,as WDT output:" | ||
89 | " Bit7 to 1: %d\n", cr_data); | ||
90 | #endif | ||
91 | } | ||
92 | |||
93 | /* Enable SWC functions */ | ||
94 | |||
95 | static inline void pc87413_enable_swc(void) | ||
96 | { | ||
97 | unsigned int cr_data=0; | ||
98 | |||
99 | /* Step 2: Enable SWC functions */ | ||
100 | |||
101 | outb_p(0x07, WDT_INDEX_IO_PORT); /* Point SWC_LDN (LDN=4) */ | ||
102 | outb_p(SWC_LDN, WDT_DATA_IO_PORT); | ||
103 | |||
104 | outb_p(0x30, WDT_INDEX_IO_PORT); /* Read Index 0x30 First */ | ||
105 | cr_data = inb(WDT_DATA_IO_PORT); | ||
106 | cr_data |= 0x01; /* Set Bit0 to 1 */ | ||
107 | outb_p(0x30, WDT_INDEX_IO_PORT); | ||
108 | outb_p(cr_data, WDT_DATA_IO_PORT); /* Index0x30_bit0P1 */ | ||
109 | |||
110 | #ifdef DEBUG | ||
111 | printk(KERN_INFO DPFX "pc87413 - Enable SWC functions\n"); | ||
112 | #endif | ||
113 | } | ||
114 | |||
115 | /* Read SWC I/O base address */ | ||
116 | |||
117 | static 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 | /* Step 3: Read SWC I/O Base Address */ | ||
123 | |||
124 | outb_p(0x60, WDT_INDEX_IO_PORT); /* Read Index 0x60 */ | ||
125 | addr_h = inb(WDT_DATA_IO_PORT); | ||
126 | |||
127 | outb_p(0x61, WDT_INDEX_IO_PORT); /* Read Index 0x61 */ | ||
128 | |||
129 | addr_l = inb(WDT_DATA_IO_PORT); | ||
130 | |||
131 | swc_base_addr = (addr_h << 8) + addr_l; | ||
132 | |||
133 | #ifdef DEBUG | ||
134 | printk(KERN_INFO DPFX "Read SWC I/O Base Address: low %d, high %d," | ||
135 | " res %d\n", addr_l, addr_h, swc_base_addr); | ||
136 | #endif | ||
137 | |||
138 | return swc_base_addr; | ||
139 | } | ||
140 | |||
141 | /* Select Bank 3 of SWC */ | ||
142 | |||
143 | static inline void pc87413_swc_bank3(unsigned int swc_base_addr) | ||
144 | { | ||
145 | /* Step 4: Select Bank3 of SWC */ | ||
146 | |||
147 | outb_p(inb(swc_base_addr + 0x0f) | 0x03, swc_base_addr + 0x0f); | ||
148 | |||
149 | #ifdef DEBUG | ||
150 | printk(KERN_INFO DPFX "Select Bank3 of SWC\n"); | ||
151 | #endif | ||
152 | } | ||
153 | |||
154 | /* Set watchdog timeout to x minutes */ | ||
155 | |||
156 | static inline void pc87413_programm_wdto(unsigned int swc_base_addr, | ||
157 | char pc87413_time) | ||
158 | { | ||
159 | /* Step 5: Programm WDTO, Twd. */ | ||
160 | |||
161 | outb_p(pc87413_time, swc_base_addr + WDTO); | ||
162 | |||
163 | #ifdef DEBUG | ||
164 | printk(KERN_INFO DPFX "Set WDTO to %d minutes\n", pc87413_time); | ||
165 | #endif | ||
166 | } | ||
167 | |||
168 | /* Enable WDEN */ | ||
169 | |||
170 | static inline void pc87413_enable_wden(unsigned int swc_base_addr) | ||
171 | { | ||
172 | /* Step 6: Enable WDEN */ | ||
173 | |||
174 | outb_p(inb (swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL); | ||
175 | |||
176 | #ifdef DEBUG | ||
177 | printk(KERN_INFO DPFX "Enable WDEN\n"); | ||
178 | #endif | ||
179 | } | ||
180 | |||
181 | /* Enable SW_WD_TREN */ | ||
182 | static inline void pc87413_enable_sw_wd_tren(unsigned int swc_base_addr) | ||
183 | { | ||
184 | /* Enable SW_WD_TREN */ | ||
185 | |||
186 | outb_p(inb (swc_base_addr + WDCFG) | 0x80, swc_base_addr + WDCFG); | ||
187 | |||
188 | #ifdef DEBUG | ||
189 | printk(KERN_INFO DPFX "Enable SW_WD_TREN\n"); | ||
190 | #endif | ||
191 | } | ||
192 | |||
193 | /* Disable SW_WD_TREN */ | ||
194 | |||
195 | static inline void pc87413_disable_sw_wd_tren(unsigned int swc_base_addr) | ||
196 | { | ||
197 | /* Disable SW_WD_TREN */ | ||
198 | |||
199 | outb_p(inb (swc_base_addr + WDCFG) & 0x7f, swc_base_addr + WDCFG); | ||
200 | |||
201 | #ifdef DEBUG | ||
202 | printk(KERN_INFO DPFX "pc87413 - Disable SW_WD_TREN\n"); | ||
203 | #endif | ||
204 | } | ||
205 | |||
206 | /* Enable SW_WD_TRG */ | ||
207 | |||
208 | static inline void pc87413_enable_sw_wd_trg(unsigned int swc_base_addr) | ||
209 | { | ||
210 | /* Enable SW_WD_TRG */ | ||
211 | |||
212 | outb_p(inb (swc_base_addr + WDCTL) | 0x80, swc_base_addr + WDCTL); | ||
213 | |||
214 | #ifdef DEBUG | ||
215 | printk(KERN_INFO DPFX "pc87413 - Enable SW_WD_TRG\n"); | ||
216 | #endif | ||
217 | } | ||
218 | |||
219 | /* Disable SW_WD_TRG */ | ||
220 | |||
221 | static inline void pc87413_disable_sw_wd_trg(unsigned int swc_base_addr) | ||
222 | { | ||
223 | /* Disable SW_WD_TRG */ | ||
224 | |||
225 | outb_p(inb (swc_base_addr + WDCTL) & 0x7f, swc_base_addr + WDCTL); | ||
226 | |||
227 | #ifdef DEBUG | ||
228 | printk(KERN_INFO DPFX "Disable SW_WD_TRG\n"); | ||
229 | #endif | ||
230 | } | ||
231 | |||
232 | /* -- Higher level functions ------------------------------------*/ | ||
233 | |||
234 | /* Enable the watchdog */ | ||
235 | |||
236 | static void pc87413_enable(void) | ||
237 | { | ||
238 | unsigned int swc_base_addr; | ||
239 | |||
240 | spin_lock(&io_lock); | ||
241 | |||
242 | pc87413_select_wdt_out(); | ||
243 | pc87413_enable_swc(); | ||
244 | swc_base_addr = pc87413_get_swc_base(); | ||
245 | pc87413_swc_bank3(swc_base_addr); | ||
246 | pc87413_programm_wdto(swc_base_addr, timeout); | ||
247 | pc87413_enable_wden(swc_base_addr); | ||
248 | pc87413_enable_sw_wd_tren(swc_base_addr); | ||
249 | pc87413_enable_sw_wd_trg(swc_base_addr); | ||
250 | |||
251 | spin_unlock(&io_lock); | ||
252 | } | ||
253 | |||
254 | /* Disable the watchdog */ | ||
255 | |||
256 | static void pc87413_disable(void) | ||
257 | { | ||
258 | unsigned int swc_base_addr; | ||
259 | |||
260 | spin_lock(&io_lock); | ||
261 | |||
262 | pc87413_select_wdt_out(); | ||
263 | pc87413_enable_swc(); | ||
264 | swc_base_addr = pc87413_get_swc_base(); | ||
265 | pc87413_swc_bank3(swc_base_addr); | ||
266 | pc87413_disable_sw_wd_tren(swc_base_addr); | ||
267 | pc87413_disable_sw_wd_trg(swc_base_addr); | ||
268 | pc87413_programm_wdto(swc_base_addr, 0); | ||
269 | |||
270 | spin_unlock(&io_lock); | ||
271 | } | ||
272 | |||
273 | /* Refresh the watchdog */ | ||
274 | |||
275 | static void pc87413_refresh(void) | ||
276 | { | ||
277 | unsigned int swc_base_addr; | ||
278 | |||
279 | spin_lock(&io_lock); | ||
280 | |||
281 | pc87413_select_wdt_out(); | ||
282 | pc87413_enable_swc(); | ||
283 | swc_base_addr = pc87413_get_swc_base(); | ||
284 | pc87413_swc_bank3(swc_base_addr); | ||
285 | pc87413_disable_sw_wd_tren(swc_base_addr); | ||
286 | pc87413_disable_sw_wd_trg(swc_base_addr); | ||
287 | pc87413_programm_wdto(swc_base_addr, timeout); | ||
288 | pc87413_enable_wden(swc_base_addr); | ||
289 | pc87413_enable_sw_wd_tren(swc_base_addr); | ||
290 | pc87413_enable_sw_wd_trg(swc_base_addr); | ||
291 | |||
292 | spin_unlock(&io_lock); | ||
293 | } | ||
294 | |||
295 | /* -- File operations -------------------------------------------*/ | ||
296 | |||
297 | /** | ||
298 | * pc87413_open: | ||
299 | * @inode: inode of device | ||
300 | * @file: file handle to device | ||
301 | * | ||
302 | */ | ||
303 | |||
304 | static int pc87413_open(struct inode *inode, struct file *file) | ||
305 | { | ||
306 | /* /dev/watchdog can only be opened once */ | ||
307 | |||
308 | if (test_and_set_bit(0, &timer_enabled)) | ||
309 | return -EBUSY; | ||
310 | |||
311 | if (nowayout) | ||
312 | __module_get(THIS_MODULE); | ||
313 | |||
314 | /* Reload and activate timer */ | ||
315 | pc87413_refresh(); | ||
316 | |||
317 | printk(KERN_INFO MODNAME "Watchdog enabled. Timeout set to" | ||
318 | " %d minute(s).\n", timeout); | ||
319 | |||
320 | return nonseekable_open(inode, file); | ||
321 | } | ||
322 | |||
323 | /** | ||
324 | * pc87413_release: | ||
325 | * @inode: inode to board | ||
326 | * @file: file handle to board | ||
327 | * | ||
328 | * The watchdog has a configurable API. There is a religious dispute | ||
329 | * between people who want their watchdog to be able to shut down and | ||
330 | * those who want to be sure if the watchdog manager dies the machine | ||
331 | * reboots. In the former case we disable the counters, in the latter | ||
332 | * case you have to open it again very soon. | ||
333 | */ | ||
334 | |||
335 | static int pc87413_release(struct inode *inode, struct file *file) | ||
336 | { | ||
337 | /* Shut off the timer. */ | ||
338 | |||
339 | if (expect_close == 42) { | ||
340 | pc87413_disable(); | ||
341 | printk(KERN_INFO MODNAME "Watchdog disabled," | ||
342 | " sleeping again...\n"); | ||
343 | } else { | ||
344 | printk(KERN_CRIT MODNAME "Unexpected close, not stopping" | ||
345 | " watchdog!\n"); | ||
346 | pc87413_refresh(); | ||
347 | } | ||
348 | |||
349 | clear_bit(0, &timer_enabled); | ||
350 | expect_close = 0; | ||
351 | |||
352 | return 0; | ||
353 | } | ||
354 | |||
355 | /** | ||
356 | * pc87413_status: | ||
357 | * | ||
358 | * return, if the watchdog is enabled (timeout is set...) | ||
359 | */ | ||
360 | |||
361 | |||
362 | static int pc87413_status(void) | ||
363 | { | ||
364 | return 0; /* currently not supported */ | ||
365 | } | ||
366 | |||
367 | /** | ||
368 | * pc87413_write: | ||
369 | * @file: file handle to the watchdog | ||
370 | * @data: data buffer to write | ||
371 | * @len: length in bytes | ||
372 | * @ppos: pointer to the position to write. No seeks allowed | ||
373 | * | ||
374 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
375 | * write of data will do, as we we don't define content meaning. | ||
376 | */ | ||
377 | |||
378 | static ssize_t pc87413_write(struct file *file, const char __user *data, | ||
379 | size_t len, loff_t *ppos) | ||
380 | { | ||
381 | /* See if we got the magic character 'V' and reload the timer */ | ||
382 | if (len) { | ||
383 | if (!nowayout) { | ||
384 | size_t i; | ||
385 | |||
386 | /* reset expect flag */ | ||
387 | expect_close = 0; | ||
388 | |||
389 | /* scan to see whether or not we got the magic character */ | ||
390 | for (i = 0; i != len; i++) { | ||
391 | char c; | ||
392 | if (get_user(c, data+i)) | ||
393 | return -EFAULT; | ||
394 | if (c == 'V') | ||
395 | expect_close = 42; | ||
396 | } | ||
397 | } | ||
398 | |||
399 | /* someone wrote to us, we should reload the timer */ | ||
400 | pc87413_refresh(); | ||
401 | } | ||
402 | return len; | ||
403 | } | ||
404 | |||
405 | /** | ||
406 | * pc87413_ioctl: | ||
407 | * @inode: inode of the device | ||
408 | * @file: file handle to the device | ||
409 | * @cmd: watchdog command | ||
410 | * @arg: argument pointer | ||
411 | * | ||
412 | * The watchdog API defines a common set of functions for all watchdogs | ||
413 | * according to their available features. We only actually usefully support | ||
414 | * querying capabilities and current status. | ||
415 | */ | ||
416 | |||
417 | static int pc87413_ioctl(struct inode *inode, struct file *file, | ||
418 | unsigned int cmd, unsigned long arg) | ||
419 | { | ||
420 | int new_timeout; | ||
421 | |||
422 | union { | ||
423 | struct watchdog_info __user *ident; | ||
424 | int __user *i; | ||
425 | } uarg; | ||
426 | |||
427 | static struct watchdog_info ident = { | ||
428 | .options = WDIOF_KEEPALIVEPING | | ||
429 | WDIOF_SETTIMEOUT | | ||
430 | WDIOF_MAGICCLOSE, | ||
431 | .firmware_version = 1, | ||
432 | .identity = "PC87413(HF/F) watchdog" | ||
433 | }; | ||
434 | |||
435 | uarg.i = (int __user *)arg; | ||
436 | |||
437 | switch(cmd) { | ||
438 | default: | ||
439 | return -ENOTTY; | ||
440 | |||
441 | case WDIOC_GETSUPPORT: | ||
442 | return copy_to_user(uarg.ident, &ident, | ||
443 | sizeof(ident)) ? -EFAULT : 0; | ||
444 | |||
445 | case WDIOC_GETSTATUS: | ||
446 | return put_user(pc87413_status(), uarg.i); | ||
447 | |||
448 | case WDIOC_GETBOOTSTATUS: | ||
449 | return put_user(0, uarg.i); | ||
450 | |||
451 | case WDIOC_KEEPALIVE: | ||
452 | pc87413_refresh(); | ||
453 | #ifdef DEBUG | ||
454 | printk(KERN_INFO DPFX "keepalive\n"); | ||
455 | #endif | ||
456 | return 0; | ||
457 | |||
458 | case WDIOC_SETTIMEOUT: | ||
459 | if (get_user(new_timeout, uarg.i)) | ||
460 | return -EFAULT; | ||
461 | |||
462 | // the API states this is given in secs | ||
463 | new_timeout /= 60; | ||
464 | |||
465 | if (new_timeout < 0 || new_timeout > MAX_TIMEOUT) | ||
466 | return -EINVAL; | ||
467 | |||
468 | timeout = new_timeout; | ||
469 | pc87413_refresh(); | ||
470 | |||
471 | // fall through and return the new timeout... | ||
472 | |||
473 | case WDIOC_GETTIMEOUT: | ||
474 | |||
475 | new_timeout = timeout * 60; | ||
476 | |||
477 | return put_user(new_timeout, uarg.i); | ||
478 | |||
479 | case WDIOC_SETOPTIONS: | ||
480 | { | ||
481 | int options, retval = -EINVAL; | ||
482 | |||
483 | if (get_user(options, uarg.i)) | ||
484 | return -EFAULT; | ||
485 | |||
486 | if (options & WDIOS_DISABLECARD) { | ||
487 | pc87413_disable(); | ||
488 | retval = 0; | ||
489 | } | ||
490 | |||
491 | if (options & WDIOS_ENABLECARD) { | ||
492 | pc87413_enable(); | ||
493 | retval = 0; | ||
494 | } | ||
495 | |||
496 | return retval; | ||
497 | } | ||
498 | } | ||
499 | } | ||
500 | |||
501 | /* -- Notifier funtions -----------------------------------------*/ | ||
502 | |||
503 | /** | ||
504 | * notify_sys: | ||
505 | * @this: our notifier block | ||
506 | * @code: the event being reported | ||
507 | * @unused: unused | ||
508 | * | ||
509 | * Our notifier is called on system shutdowns. We want to turn the card | ||
510 | * off at reboot otherwise the machine will reboot again during memory | ||
511 | * test or worse yet during the following fsck. This would suck, in fact | ||
512 | * trust me - if it happens it does suck. | ||
513 | */ | ||
514 | |||
515 | static int pc87413_notify_sys(struct notifier_block *this, | ||
516 | unsigned long code, | ||
517 | void *unused) | ||
518 | { | ||
519 | if (code == SYS_DOWN || code == SYS_HALT) | ||
520 | { | ||
521 | /* Turn the card off */ | ||
522 | pc87413_disable(); | ||
523 | } | ||
524 | return NOTIFY_DONE; | ||
525 | } | ||
526 | |||
527 | /* -- Module's structures ---------------------------------------*/ | ||
528 | |||
529 | static struct file_operations pc87413_fops = { | ||
530 | .owner = THIS_MODULE, | ||
531 | .llseek = no_llseek, | ||
532 | .write = pc87413_write, | ||
533 | .ioctl = pc87413_ioctl, | ||
534 | .open = pc87413_open, | ||
535 | .release = pc87413_release, | ||
536 | }; | ||
537 | |||
538 | static struct notifier_block pc87413_notifier = | ||
539 | { | ||
540 | .notifier_call = pc87413_notify_sys, | ||
541 | }; | ||
542 | |||
543 | static struct miscdevice pc87413_miscdev= | ||
544 | { | ||
545 | .minor = WATCHDOG_MINOR, | ||
546 | .name = "watchdog", | ||
547 | .fops = &pc87413_fops | ||
548 | }; | ||
549 | |||
550 | /* -- Module init functions -------------------------------------*/ | ||
551 | |||
552 | /** | ||
553 | * pc87413_init: module's "constructor" | ||
554 | * | ||
555 | * Set up the WDT watchdog board. All we have to do is grab the | ||
556 | * resources we require and bitch if anyone beat us to them. | ||
557 | * The open() function will actually kick the board off. | ||
558 | */ | ||
559 | |||
560 | static int __init pc87413_init(void) | ||
561 | { | ||
562 | int ret; | ||
563 | |||
564 | spin_lock_init(&io_lock); | ||
565 | |||
566 | printk(KERN_INFO PFX "Version " VERSION " at io 0x%X\n", WDT_INDEX_IO_PORT); | ||
567 | |||
568 | /* request_region(io, 2, "pc87413"); */ | ||
569 | |||
570 | ret = register_reboot_notifier(&pc87413_notifier); | ||
571 | if (ret != 0) { | ||
572 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
573 | ret); | ||
574 | } | ||
575 | |||
576 | ret = misc_register(&pc87413_miscdev); | ||
577 | |||
578 | if (ret != 0) { | ||
579 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
580 | WATCHDOG_MINOR, ret); | ||
581 | unregister_reboot_notifier(&pc87413_notifier); | ||
582 | return ret; | ||
583 | } | ||
584 | |||
585 | printk(KERN_INFO PFX "initialized. timeout=%d min \n", timeout); | ||
586 | |||
587 | pc87413_enable(); | ||
588 | |||
589 | return 0; | ||
590 | } | ||
591 | |||
592 | /** | ||
593 | * pc87413_exit: module's "destructor" | ||
594 | * | ||
595 | * Unload the watchdog. You cannot do this with any file handles open. | ||
596 | * If your watchdog is set to continue ticking on close and you unload | ||
597 | * it, well it keeps ticking. We won't get the interrupt but the board | ||
598 | * will not touch PC memory so all is fine. You just have to load a new | ||
599 | * module in 60 seconds or reboot. | ||
600 | */ | ||
601 | |||
602 | static void __exit pc87413_exit(void) | ||
603 | { | ||
604 | /* Stop the timer before we leave */ | ||
605 | if (!nowayout) | ||
606 | { | ||
607 | pc87413_disable(); | ||
608 | printk(KERN_INFO MODNAME "Watchdog disabled.\n"); | ||
609 | } | ||
610 | |||
611 | misc_deregister(&pc87413_miscdev); | ||
612 | unregister_reboot_notifier(&pc87413_notifier); | ||
613 | /* release_region(io,2); */ | ||
614 | |||
615 | printk(MODNAME " watchdog component driver removed.\n"); | ||
616 | } | ||
617 | |||
618 | module_init(pc87413_init); | ||
619 | module_exit(pc87413_exit); | ||
620 | |||
621 | MODULE_AUTHOR("Sven Anders <anders@anduras.de>, Marcus Junker <junker@anduras.de>,"); | ||
622 | MODULE_DESCRIPTION("PC87413 WDT driver"); | ||
623 | MODULE_LICENSE("GPL"); | ||
624 | |||
625 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
626 | |||
627 | module_param(io, int, 0); | ||
628 | MODULE_PARM_DESC(io, MODNAME " I/O port (default: " __MODULE_STRING(io) ")."); | ||
629 | |||
630 | module_param(timeout, int, 0); | ||
631 | MODULE_PARM_DESC(timeout, "Watchdog timeout in minutes (default=" __MODULE_STRING(timeout) ")."); | ||
632 | |||
633 | module_param(nowayout, int, 0); | ||
634 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
635 | |||
diff --git a/drivers/char/watchdog/rm9k_wdt.c b/drivers/char/watchdog/rm9k_wdt.c new file mode 100644 index 000000000000..ec3909371c21 --- /dev/null +++ b/drivers/char/watchdog/rm9k_wdt.c | |||
@@ -0,0 +1,420 @@ | |||
1 | /* | ||
2 | * Watchdog implementation for GPI h/w found on PMC-Sierra RM9xxx | ||
3 | * chips. | ||
4 | * | ||
5 | * Copyright (C) 2004 by Basler Vision Technologies AG | ||
6 | * Author: Thomas Koeller <thomas.koeller@baslerweb.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
21 | */ | ||
22 | |||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/moduleparam.h> | ||
26 | #include <linux/interrupt.h> | ||
27 | #include <linux/fs.h> | ||
28 | #include <linux/reboot.h> | ||
29 | #include <linux/notifier.h> | ||
30 | #include <linux/miscdevice.h> | ||
31 | #include <linux/watchdog.h> | ||
32 | #include <asm/io.h> | ||
33 | #include <asm/atomic.h> | ||
34 | #include <asm/processor.h> | ||
35 | #include <asm/uaccess.h> | ||
36 | #include <asm/system.h> | ||
37 | #include <asm/rm9k-ocd.h> | ||
38 | |||
39 | #include <rm9k_wdt.h> | ||
40 | |||
41 | |||
42 | #define CLOCK 125000000 | ||
43 | #define MAX_TIMEOUT_SECONDS 32 | ||
44 | #define CPCCR 0x0080 | ||
45 | #define CPGIG1SR 0x0044 | ||
46 | #define CPGIG1ER 0x0054 | ||
47 | |||
48 | |||
49 | /* Function prototypes */ | ||
50 | static irqreturn_t wdt_gpi_irqhdl(int, void *, struct pt_regs *); | ||
51 | static void wdt_gpi_start(void); | ||
52 | static void wdt_gpi_stop(void); | ||
53 | static void wdt_gpi_set_timeout(unsigned int); | ||
54 | static int wdt_gpi_open(struct inode *, struct file *); | ||
55 | static int wdt_gpi_release(struct inode *, struct file *); | ||
56 | static ssize_t wdt_gpi_write(struct file *, const char __user *, size_t, loff_t *); | ||
57 | static long wdt_gpi_ioctl(struct file *, unsigned int, unsigned long); | ||
58 | static int wdt_gpi_notify(struct notifier_block *, unsigned long, void *); | ||
59 | static const struct resource *wdt_gpi_get_resource(struct platform_device *, const char *, unsigned int); | ||
60 | static int __init wdt_gpi_probe(struct device *); | ||
61 | static int __exit wdt_gpi_remove(struct device *); | ||
62 | |||
63 | |||
64 | static const char wdt_gpi_name[] = "wdt_gpi"; | ||
65 | static atomic_t opencnt; | ||
66 | static int expect_close; | ||
67 | static int locked; | ||
68 | |||
69 | |||
70 | /* These are set from device resources */ | ||
71 | static void __iomem * wd_regs; | ||
72 | static unsigned int wd_irq, wd_ctr; | ||
73 | |||
74 | |||
75 | /* Module arguments */ | ||
76 | static int timeout = MAX_TIMEOUT_SECONDS; | ||
77 | module_param(timeout, int, 0444); | ||
78 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); | ||
79 | |||
80 | static unsigned long resetaddr = 0xbffdc200; | ||
81 | module_param(resetaddr, ulong, 0444); | ||
82 | MODULE_PARM_DESC(resetaddr, "Address to write to to force a reset"); | ||
83 | |||
84 | static unsigned long flagaddr = 0xbffdc104; | ||
85 | module_param(flagaddr, ulong, 0444); | ||
86 | MODULE_PARM_DESC(flagaddr, "Address to write to boot flags to"); | ||
87 | |||
88 | static int powercycle; | ||
89 | module_param(powercycle, bool, 0444); | ||
90 | MODULE_PARM_DESC(powercycle, "Cycle power if watchdog expires"); | ||
91 | |||
92 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
93 | module_param(nowayout, bool, 0444); | ||
94 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be disabled once started"); | ||
95 | |||
96 | |||
97 | /* Interrupt handler */ | ||
98 | static irqreturn_t wdt_gpi_irqhdl(int irq, void *ctxt, struct pt_regs *regs) | ||
99 | { | ||
100 | if (!unlikely(__raw_readl(wd_regs + 0x0008) & 0x1)) | ||
101 | return IRQ_NONE; | ||
102 | __raw_writel(0x1, wd_regs + 0x0008); | ||
103 | |||
104 | |||
105 | printk(KERN_CRIT "%s: watchdog expired - resetting system\n", | ||
106 | wdt_gpi_name); | ||
107 | |||
108 | *(volatile char *) flagaddr |= 0x01; | ||
109 | *(volatile char *) resetaddr = powercycle ? 0x01 : 0x2; | ||
110 | iob(); | ||
111 | while (1) | ||
112 | cpu_relax(); | ||
113 | } | ||
114 | |||
115 | |||
116 | /* Watchdog functions */ | ||
117 | static void wdt_gpi_start(void) | ||
118 | { | ||
119 | u32 reg; | ||
120 | |||
121 | lock_titan_regs(); | ||
122 | reg = titan_readl(CPGIG1ER); | ||
123 | titan_writel(reg | (0x100 << wd_ctr), CPGIG1ER); | ||
124 | iob(); | ||
125 | unlock_titan_regs(); | ||
126 | } | ||
127 | |||
128 | static void wdt_gpi_stop(void) | ||
129 | { | ||
130 | u32 reg; | ||
131 | |||
132 | lock_titan_regs(); | ||
133 | reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); | ||
134 | titan_writel(reg, CPCCR); | ||
135 | reg = titan_readl(CPGIG1ER); | ||
136 | titan_writel(reg & ~(0x100 << wd_ctr), CPGIG1ER); | ||
137 | iob(); | ||
138 | unlock_titan_regs(); | ||
139 | } | ||
140 | |||
141 | static void wdt_gpi_set_timeout(unsigned int to) | ||
142 | { | ||
143 | u32 reg; | ||
144 | const u32 wdval = (to * CLOCK) & ~0x0000000f; | ||
145 | |||
146 | lock_titan_regs(); | ||
147 | reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); | ||
148 | titan_writel(reg, CPCCR); | ||
149 | wmb(); | ||
150 | __raw_writel(wdval, wd_regs + 0x0000); | ||
151 | wmb(); | ||
152 | titan_writel(reg | (0x2 << (wd_ctr * 4)), CPCCR); | ||
153 | wmb(); | ||
154 | titan_writel(reg | (0x5 << (wd_ctr * 4)), CPCCR); | ||
155 | iob(); | ||
156 | unlock_titan_regs(); | ||
157 | } | ||
158 | |||
159 | |||
160 | /* /dev/watchdog operations */ | ||
161 | static int wdt_gpi_open(struct inode *inode, struct file *file) | ||
162 | { | ||
163 | int res; | ||
164 | |||
165 | if (unlikely(atomic_dec_if_positive(&opencnt) < 0)) | ||
166 | return -EBUSY; | ||
167 | |||
168 | expect_close = 0; | ||
169 | if (locked) { | ||
170 | module_put(THIS_MODULE); | ||
171 | free_irq(wd_irq, &miscdev); | ||
172 | locked = 0; | ||
173 | } | ||
174 | |||
175 | res = request_irq(wd_irq, wdt_gpi_irqhdl, SA_SHIRQ | SA_INTERRUPT, | ||
176 | wdt_gpi_name, &miscdev); | ||
177 | if (unlikely(res)) | ||
178 | return res; | ||
179 | |||
180 | wdt_gpi_set_timeout(timeout); | ||
181 | wdt_gpi_start(); | ||
182 | |||
183 | printk(KERN_INFO "%s: watchdog started, timeout = %u seconds\n", | ||
184 | wdt_gpi_name, timeout); | ||
185 | return nonseekable_open(inode, file); | ||
186 | } | ||
187 | |||
188 | static int wdt_gpi_release(struct inode *inode, struct file *file) | ||
189 | { | ||
190 | if (nowayout) { | ||
191 | printk(KERN_INFO "%s: no way out - watchdog left running\n", | ||
192 | wdt_gpi_name); | ||
193 | __module_get(THIS_MODULE); | ||
194 | locked = 1; | ||
195 | } else { | ||
196 | if (expect_close) { | ||
197 | wdt_gpi_stop(); | ||
198 | free_irq(wd_irq, &miscdev); | ||
199 | printk(KERN_INFO "%s: watchdog stopped\n", wdt_gpi_name); | ||
200 | } else { | ||
201 | printk(KERN_CRIT "%s: unexpected close() -" | ||
202 | " watchdog left running\n", | ||
203 | wdt_gpi_name); | ||
204 | wdt_gpi_set_timeout(timeout); | ||
205 | __module_get(THIS_MODULE); | ||
206 | locked = 1; | ||
207 | } | ||
208 | } | ||
209 | |||
210 | atomic_inc(&opencnt); | ||
211 | return 0; | ||
212 | } | ||
213 | |||
214 | static ssize_t | ||
215 | wdt_gpi_write(struct file *f, const char __user *d, size_t s, loff_t *o) | ||
216 | { | ||
217 | char val; | ||
218 | |||
219 | wdt_gpi_set_timeout(timeout); | ||
220 | expect_close = (s > 0) && !get_user(val, d) && (val == 'V'); | ||
221 | return s ? 1 : 0; | ||
222 | } | ||
223 | |||
224 | static long | ||
225 | wdt_gpi_ioctl(struct file *f, unsigned int cmd, unsigned long arg) | ||
226 | { | ||
227 | long res = -ENOTTY; | ||
228 | const long size = _IOC_SIZE(cmd); | ||
229 | int stat; | ||
230 | void __user *argp = (void __user *)arg; | ||
231 | static struct watchdog_info wdinfo = { | ||
232 | .identity = "RM9xxx/GPI watchdog", | ||
233 | .firmware_version = 0, | ||
234 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | ||
235 | }; | ||
236 | |||
237 | if (unlikely(_IOC_TYPE(cmd) != WATCHDOG_IOCTL_BASE)) | ||
238 | return -ENOTTY; | ||
239 | |||
240 | if ((_IOC_DIR(cmd) & _IOC_READ) | ||
241 | && !access_ok(VERIFY_WRITE, arg, size)) | ||
242 | return -EFAULT; | ||
243 | |||
244 | if ((_IOC_DIR(cmd) & _IOC_WRITE) | ||
245 | && !access_ok(VERIFY_READ, arg, size)) | ||
246 | return -EFAULT; | ||
247 | |||
248 | expect_close = 0; | ||
249 | |||
250 | switch (cmd) { | ||
251 | case WDIOC_GETSUPPORT: | ||
252 | wdinfo.options = nowayout ? | ||
253 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING : | ||
254 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE; | ||
255 | res = __copy_to_user(argp, &wdinfo, size) ? -EFAULT : size; | ||
256 | break; | ||
257 | |||
258 | case WDIOC_GETSTATUS: | ||
259 | break; | ||
260 | |||
261 | case WDIOC_GETBOOTSTATUS: | ||
262 | stat = (*(volatile char *) flagaddr & 0x01) | ||
263 | ? WDIOF_CARDRESET : 0; | ||
264 | res = __copy_to_user(argp, &stat, size) ? | ||
265 | -EFAULT : size; | ||
266 | break; | ||
267 | |||
268 | case WDIOC_SETOPTIONS: | ||
269 | break; | ||
270 | |||
271 | case WDIOC_KEEPALIVE: | ||
272 | wdt_gpi_set_timeout(timeout); | ||
273 | res = size; | ||
274 | break; | ||
275 | |||
276 | case WDIOC_SETTIMEOUT: | ||
277 | { | ||
278 | int val; | ||
279 | if (unlikely(__copy_from_user(&val, argp, size))) { | ||
280 | res = -EFAULT; | ||
281 | break; | ||
282 | } | ||
283 | |||
284 | if (val > MAX_TIMEOUT_SECONDS) | ||
285 | val = MAX_TIMEOUT_SECONDS; | ||
286 | timeout = val; | ||
287 | wdt_gpi_set_timeout(val); | ||
288 | res = size; | ||
289 | printk(KERN_INFO "%s: timeout set to %u seconds\n", | ||
290 | wdt_gpi_name, timeout); | ||
291 | } | ||
292 | break; | ||
293 | |||
294 | case WDIOC_GETTIMEOUT: | ||
295 | res = __copy_to_user(argp, &timeout, size) ? | ||
296 | -EFAULT : size; | ||
297 | break; | ||
298 | } | ||
299 | |||
300 | return res; | ||
301 | } | ||
302 | |||
303 | |||
304 | /* Shutdown notifier */ | ||
305 | static int | ||
306 | wdt_gpi_notify(struct notifier_block *this, unsigned long code, void *unused) | ||
307 | { | ||
308 | if (code == SYS_DOWN || code == SYS_HALT) | ||
309 | wdt_gpi_stop(); | ||
310 | |||
311 | return NOTIFY_DONE; | ||
312 | } | ||
313 | |||
314 | |||
315 | /* Kernel interfaces */ | ||
316 | static struct file_operations fops = { | ||
317 | .owner = THIS_MODULE, | ||
318 | .open = wdt_gpi_open, | ||
319 | .release = wdt_gpi_release, | ||
320 | .write = wdt_gpi_write, | ||
321 | .unlocked_ioctl = wdt_gpi_ioctl, | ||
322 | }; | ||
323 | |||
324 | static struct miscdevice miscdev = { | ||
325 | .minor = WATCHDOG_MINOR, | ||
326 | .name = wdt_gpi_name, | ||
327 | .fops = &fops, | ||
328 | }; | ||
329 | |||
330 | static struct notifier_block wdt_gpi_shutdown = { | ||
331 | .notifier_call = wdt_gpi_notify, | ||
332 | }; | ||
333 | |||
334 | |||
335 | /* Init & exit procedures */ | ||
336 | static const struct resource * | ||
337 | wdt_gpi_get_resource(struct platform_device *pdv, const char *name, | ||
338 | unsigned int type) | ||
339 | { | ||
340 | char buf[80]; | ||
341 | if (snprintf(buf, sizeof buf, "%s_0", name) >= sizeof buf) | ||
342 | return NULL; | ||
343 | return platform_get_resource_byname(pdv, type, buf); | ||
344 | } | ||
345 | |||
346 | /* No hotplugging on the platform bus - use __init */ | ||
347 | static int __init wdt_gpi_probe(struct device *dev) | ||
348 | { | ||
349 | int res; | ||
350 | struct platform_device * const pdv = to_platform_device(dev); | ||
351 | const struct resource | ||
352 | * const rr = wdt_gpi_get_resource(pdv, WDT_RESOURCE_REGS, | ||
353 | IORESOURCE_MEM), | ||
354 | * const ri = wdt_gpi_get_resource(pdv, WDT_RESOURCE_IRQ, | ||
355 | IORESOURCE_IRQ), | ||
356 | * const rc = wdt_gpi_get_resource(pdv, WDT_RESOURCE_COUNTER, | ||
357 | 0); | ||
358 | |||
359 | if (unlikely(!rr || !ri || !rc)) | ||
360 | return -ENXIO; | ||
361 | |||
362 | wd_regs = ioremap_nocache(rr->start, rr->end + 1 - rr->start); | ||
363 | if (unlikely(!wd_regs)) | ||
364 | return -ENOMEM; | ||
365 | wd_irq = ri->start; | ||
366 | wd_ctr = rc->start; | ||
367 | res = misc_register(&miscdev); | ||
368 | if (res) | ||
369 | iounmap(wd_regs); | ||
370 | else | ||
371 | register_reboot_notifier(&wdt_gpi_shutdown); | ||
372 | return res; | ||
373 | } | ||
374 | |||
375 | static int __exit wdt_gpi_remove(struct device *dev) | ||
376 | { | ||
377 | int res; | ||
378 | |||
379 | unregister_reboot_notifier(&wdt_gpi_shutdown); | ||
380 | res = misc_deregister(&miscdev); | ||
381 | iounmap(wd_regs); | ||
382 | wd_regs = NULL; | ||
383 | return res; | ||
384 | } | ||
385 | |||
386 | |||
387 | /* Device driver init & exit */ | ||
388 | static struct device_driver wdt_gpi_driver = { | ||
389 | .name = (char *) wdt_gpi_name, | ||
390 | .bus = &platform_bus_type, | ||
391 | .owner = THIS_MODULE, | ||
392 | .probe = wdt_gpi_probe, | ||
393 | .remove = __exit_p(wdt_gpi_remove), | ||
394 | .shutdown = NULL, | ||
395 | .suspend = NULL, | ||
396 | .resume = NULL, | ||
397 | }; | ||
398 | |||
399 | static int __init wdt_gpi_init_module(void) | ||
400 | { | ||
401 | atomic_set(&opencnt, 1); | ||
402 | if (timeout > MAX_TIMEOUT_SECONDS) | ||
403 | timeout = MAX_TIMEOUT_SECONDS; | ||
404 | return driver_register(&wdt_gpi_driver); | ||
405 | } | ||
406 | |||
407 | static void __exit wdt_gpi_cleanup_module(void) | ||
408 | { | ||
409 | driver_unregister(&wdt_gpi_driver); | ||
410 | } | ||
411 | |||
412 | module_init(wdt_gpi_init_module); | ||
413 | module_exit(wdt_gpi_cleanup_module); | ||
414 | |||
415 | MODULE_AUTHOR("Thomas Koeller <thomas.koeller@baslerweb.com>"); | ||
416 | MODULE_DESCRIPTION("Basler eXcite watchdog driver for gpi devices"); | ||
417 | MODULE_VERSION("0.1"); | ||
418 | MODULE_LICENSE("GPL"); | ||
419 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
420 | |||