diff options
Diffstat (limited to 'drivers/char/watchdog')
36 files changed, 14255 insertions, 0 deletions
diff --git a/drivers/char/watchdog/Kconfig b/drivers/char/watchdog/Kconfig new file mode 100644 index 000000000000..06a31da2381c --- /dev/null +++ b/drivers/char/watchdog/Kconfig | |||
@@ -0,0 +1,549 @@ | |||
1 | # | ||
2 | # Watchdog device configuration | ||
3 | # | ||
4 | |||
5 | menu "Watchdog Cards" | ||
6 | |||
7 | config WATCHDOG | ||
8 | bool "Watchdog Timer Support" | ||
9 | ---help--- | ||
10 | If you say Y here (and to one of the following options) and create a | ||
11 | character special file /dev/watchdog with major number 10 and minor | ||
12 | number 130 using mknod ("man mknod"), you will get a watchdog, i.e.: | ||
13 | subsequently opening the file and then failing to write to it for | ||
14 | longer than 1 minute will result in rebooting the machine. This | ||
15 | could be useful for a networked machine that needs to come back | ||
16 | online as fast as possible after a lock-up. There's both a watchdog | ||
17 | implementation entirely in software (which can sometimes fail to | ||
18 | reboot the machine) and a driver for hardware watchdog boards, which | ||
19 | are more robust and can also keep track of the temperature inside | ||
20 | your computer. For details, read <file:Documentation/watchdog/watchdog.txt> | ||
21 | in the kernel source. | ||
22 | |||
23 | The watchdog is usually used together with the watchdog daemon | ||
24 | which is available from | ||
25 | <ftp://ibiblio.org/pub/Linux/system/daemons/watchdog/>. This daemon can | ||
26 | also monitor NFS connections and can reboot the machine when the process | ||
27 | table is full. | ||
28 | |||
29 | If unsure, say N. | ||
30 | |||
31 | config WATCHDOG_NOWAYOUT | ||
32 | bool "Disable watchdog shutdown on close" | ||
33 | depends on WATCHDOG | ||
34 | help | ||
35 | The default watchdog behaviour (which you get if you say N here) is | ||
36 | to stop the timer if the process managing it closes the file | ||
37 | /dev/watchdog. It's always remotely possible that this process might | ||
38 | get killed. If you say Y here, the watchdog cannot be stopped once | ||
39 | it has been started. | ||
40 | |||
41 | # | ||
42 | # General Watchdog drivers | ||
43 | # | ||
44 | |||
45 | comment "Watchdog Device Drivers" | ||
46 | depends on WATCHDOG | ||
47 | |||
48 | # Architecture Independant | ||
49 | |||
50 | config SOFT_WATCHDOG | ||
51 | tristate "Software watchdog" | ||
52 | depends on WATCHDOG | ||
53 | help | ||
54 | A software monitoring watchdog. This will fail to reboot your system | ||
55 | from some situations that the hardware watchdog will recover | ||
56 | from. Equally it's a lot cheaper to install. | ||
57 | |||
58 | To compile this driver as a module, choose M here: the | ||
59 | module will be called softdog. | ||
60 | |||
61 | # ARM Architecture | ||
62 | |||
63 | config 21285_WATCHDOG | ||
64 | tristate "DC21285 watchdog" | ||
65 | depends on WATCHDOG && FOOTBRIDGE | ||
66 | help | ||
67 | The Intel Footbridge chip contains a builtin watchdog circuit. Say Y | ||
68 | here if you wish to use this. Alternatively say M to compile the | ||
69 | driver as a module, which will be called wdt285. | ||
70 | |||
71 | This driver does not work on all machines. In particular, early CATS | ||
72 | boards have hardware problems that will cause the machine to simply | ||
73 | lock up if the watchdog fires. | ||
74 | |||
75 | "If in doubt, leave it out" - say N. | ||
76 | |||
77 | config 977_WATCHDOG | ||
78 | tristate "NetWinder WB83C977 watchdog" | ||
79 | depends on WATCHDOG && FOOTBRIDGE && ARCH_NETWINDER | ||
80 | help | ||
81 | Say Y here to include support for the WB977 watchdog included in | ||
82 | NetWinder machines. Alternatively say M to compile the driver as | ||
83 | a module, which will be called wdt977. | ||
84 | |||
85 | Not sure? It's safe to say N. | ||
86 | |||
87 | config IXP4XX_WATCHDOG | ||
88 | tristate "IXP4xx Watchdog" | ||
89 | depends on WATCHDOG && ARCH_IXP4XX | ||
90 | help | ||
91 | Say Y here if to include support for the watchdog timer | ||
92 | in the Intel IXP4xx network processors. This driver can | ||
93 | be built as a module by choosing M. The module will | ||
94 | be called ixp4xx_wdt. | ||
95 | |||
96 | Note: The internal IXP4xx watchdog does a soft CPU reset | ||
97 | which doesn't reset any peripherals. There are circumstances | ||
98 | where the watchdog will fail to reset the board correctly | ||
99 | (e.g., if the boot ROM is in an unreadable state). | ||
100 | |||
101 | Say N if you are unsure. | ||
102 | |||
103 | config IXP2000_WATCHDOG | ||
104 | tristate "IXP2000 Watchdog" | ||
105 | depends on WATCHDOG && ARCH_IXP2000 | ||
106 | help | ||
107 | Say Y here if to include support for the watchdog timer | ||
108 | in the Intel IXP2000(2400, 2800, 2850) network processors. | ||
109 | This driver can be built as a module by choosing M. The module | ||
110 | will be called ixp2000_wdt. | ||
111 | |||
112 | Say N if you are unsure. | ||
113 | |||
114 | config S3C2410_WATCHDOG | ||
115 | tristate "S3C2410 Watchdog" | ||
116 | depends on WATCHDOG && ARCH_S3C2410 | ||
117 | help | ||
118 | Watchdog timer block in the Samsung S3C2410 chips. This will | ||
119 | reboot the system when the timer expires with the watchdog | ||
120 | enabled. | ||
121 | |||
122 | The driver is limited by the speed of the system's PCLK | ||
123 | signal, so with reasonbaly fast systems (PCLK around 50-66MHz) | ||
124 | then watchdog intervals of over approximately 20seconds are | ||
125 | unavailable. | ||
126 | |||
127 | The driver can be built as a module by choosing M, and will | ||
128 | be called s3c2410_wdt | ||
129 | |||
130 | config SA1100_WATCHDOG | ||
131 | tristate "SA1100/PXA2xx watchdog" | ||
132 | depends on WATCHDOG && ( ARCH_SA1100 || ARCH_PXA ) | ||
133 | help | ||
134 | Watchdog timer embedded into SA11x0 and PXA2xx chips. This will | ||
135 | reboot your system when timeout is reached. | ||
136 | |||
137 | NOTE: once enabled, this timer cannot be disabled. | ||
138 | |||
139 | To compile this driver as a module, choose M here: the | ||
140 | module will be called sa1100_wdt. | ||
141 | |||
142 | # X86 (i386 + ia64 + x86_64) Architecture | ||
143 | |||
144 | config ACQUIRE_WDT | ||
145 | tristate "Acquire SBC Watchdog Timer" | ||
146 | depends on WATCHDOG && X86 | ||
147 | ---help--- | ||
148 | This is the driver for the hardware watchdog on Single Board | ||
149 | Computers produced by Acquire Inc (and others). This watchdog | ||
150 | simply watches your kernel to make sure it doesn't freeze, and if | ||
151 | it does, it reboots your computer after a certain amount of time. | ||
152 | |||
153 | To compile this driver as a module, choose M here: the | ||
154 | module will be called acquirewdt. | ||
155 | |||
156 | Most people will say N. | ||
157 | |||
158 | config ADVANTECH_WDT | ||
159 | tristate "Advantech SBC Watchdog Timer" | ||
160 | depends on WATCHDOG && X86 | ||
161 | help | ||
162 | If you are configuring a Linux kernel for the Advantech single-board | ||
163 | computer, say `Y' here to support its built-in watchdog timer | ||
164 | feature. More information can be found at | ||
165 | <http://www.advantech.com.tw/products/> | ||
166 | |||
167 | config ALIM1535_WDT | ||
168 | tristate "ALi M1535 PMU Watchdog Timer" | ||
169 | depends on WATCHDOG && X86 && PCI | ||
170 | ---help--- | ||
171 | This is the driver for the hardware watchdog on the ALi M1535 PMU. | ||
172 | |||
173 | To compile this driver as a module, choose M here: the | ||
174 | module will be called alim1535_wdt. | ||
175 | |||
176 | Most people will say N. | ||
177 | |||
178 | config ALIM7101_WDT | ||
179 | tristate "ALi M7101 PMU Computer Watchdog" | ||
180 | depends on WATCHDOG && X86 && PCI | ||
181 | help | ||
182 | This is the driver for the hardware watchdog on the ALi M7101 PMU | ||
183 | as used in the x86 Cobalt servers. | ||
184 | |||
185 | To compile this driver as a module, choose M here: the | ||
186 | module will be called alim7101_wdt. | ||
187 | |||
188 | Most people will say N. | ||
189 | |||
190 | config SC520_WDT | ||
191 | tristate "AMD Elan SC520 processor Watchdog" | ||
192 | depends on WATCHDOG && X86 | ||
193 | help | ||
194 | This is the driver for the hardware watchdog built in to the | ||
195 | AMD "Elan" SC520 microcomputer commonly used in embedded systems. | ||
196 | This watchdog simply watches your kernel to make sure it doesn't | ||
197 | freeze, and if it does, it reboots your computer after a certain | ||
198 | amount of time. | ||
199 | |||
200 | You can compile this driver directly into the kernel, or use | ||
201 | it as a module. The module will be called sc520_wdt. | ||
202 | |||
203 | config EUROTECH_WDT | ||
204 | tristate "Eurotech CPU-1220/1410 Watchdog Timer" | ||
205 | depends on WATCHDOG && X86 | ||
206 | help | ||
207 | Enable support for the watchdog timer on the Eurotech CPU-1220 and | ||
208 | CPU-1410 cards. These are PC/104 SBCs. Spec sheets and product | ||
209 | information are at <http://www.eurotech.it/>. | ||
210 | |||
211 | config IB700_WDT | ||
212 | tristate "IB700 SBC Watchdog Timer" | ||
213 | depends on WATCHDOG && X86 | ||
214 | ---help--- | ||
215 | This is the driver for the hardware watchdog on the IB700 Single | ||
216 | Board Computer produced by TMC Technology (www.tmc-uk.com). This watchdog | ||
217 | simply watches your kernel to make sure it doesn't freeze, and if | ||
218 | it does, it reboots your computer after a certain amount of time. | ||
219 | |||
220 | This driver is like the WDT501 driver but for slightly different hardware. | ||
221 | |||
222 | To compile this driver as a module, choose M here: the | ||
223 | module will be called ib700wdt. | ||
224 | |||
225 | Most people will say N. | ||
226 | |||
227 | config WAFER_WDT | ||
228 | tristate "ICP Wafer 5823 Single Board Computer Watchdog" | ||
229 | depends on WATCHDOG && X86 | ||
230 | help | ||
231 | This is a driver for the hardware watchdog on the ICP Wafer 5823 | ||
232 | Single Board Computer (and probably other similar models). | ||
233 | |||
234 | To compile this driver as a module, choose M here: the | ||
235 | module will be called wafer5823wdt. | ||
236 | |||
237 | config I8XX_TCO | ||
238 | tristate "Intel i8xx TCO Timer/Watchdog" | ||
239 | depends on WATCHDOG && (X86 || IA64) && PCI | ||
240 | ---help--- | ||
241 | Hardware driver for the TCO timer built into the Intel 82801 | ||
242 | I/O Controller Hub family. The TCO (Total Cost of Ownership) | ||
243 | timer is a watchdog timer that will reboot the machine after | ||
244 | its second expiration. The expiration time can be configured | ||
245 | with the "heartbeat" parameter. | ||
246 | |||
247 | On some motherboards the driver may fail to reset the chipset's | ||
248 | NO_REBOOT flag which prevents the watchdog from rebooting the | ||
249 | machine. If this is the case you will get a kernel message like | ||
250 | "failed to reset NO_REBOOT flag, reboot disabled by hardware". | ||
251 | |||
252 | To compile this driver as a module, choose M here: the | ||
253 | module will be called i8xx_tco. | ||
254 | |||
255 | config SC1200_WDT | ||
256 | tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog" | ||
257 | depends on WATCHDOG && X86 | ||
258 | help | ||
259 | This is a driver for National Semiconductor PC87307/PC97307 hardware | ||
260 | watchdog cards as found on the SC1200. This watchdog is mainly used | ||
261 | for power management purposes and can be used to power down the device | ||
262 | during inactivity periods (includes interrupt activity monitoring). | ||
263 | |||
264 | To compile this driver as a module, choose M here: the | ||
265 | module will be called sc1200wdt. | ||
266 | |||
267 | Most people will say N. | ||
268 | |||
269 | config SCx200_WDT | ||
270 | tristate "National Semiconductor SCx200 Watchdog" | ||
271 | depends on WATCHDOG && SCx200 && PCI | ||
272 | help | ||
273 | Enable the built-in watchdog timer support on the National | ||
274 | Semiconductor SCx200 processors. | ||
275 | |||
276 | If compiled as a module, it will be called scx200_wdt. | ||
277 | |||
278 | config 60XX_WDT | ||
279 | tristate "SBC-60XX Watchdog Timer" | ||
280 | depends on WATCHDOG && X86 | ||
281 | help | ||
282 | This driver can be used with the watchdog timer found on some | ||
283 | single board computers, namely the 6010 PII based computer. | ||
284 | It may well work with other cards. It reads port 0x443 to enable | ||
285 | and re-set the watchdog timer, and reads port 0x45 to disable | ||
286 | the watchdog. If you have a card that behave in similar ways, | ||
287 | you can probably make this driver work with your card as well. | ||
288 | |||
289 | You can compile this driver directly into the kernel, or use | ||
290 | it as a module. The module will be called sbc60xxwdt. | ||
291 | |||
292 | config CPU5_WDT | ||
293 | tristate "SMA CPU5 Watchdog" | ||
294 | depends on WATCHDOG && X86 | ||
295 | ---help--- | ||
296 | TBD. | ||
297 | To compile this driver as a module, choose M here: the | ||
298 | module will be called cpu5wdt. | ||
299 | |||
300 | config W83627HF_WDT | ||
301 | tristate "W83627HF Watchdog Timer" | ||
302 | depends on WATCHDOG && X86 | ||
303 | ---help--- | ||
304 | This is the driver for the hardware watchdog on the W83627HF chipset | ||
305 | as used in Advantech PC-9578 and Tyan S2721-533 motherboards | ||
306 | (and likely others). This watchdog simply watches your kernel to | ||
307 | make sure it doesn't freeze, and if it does, it reboots your computer | ||
308 | after a certain amount of time. | ||
309 | |||
310 | To compile this driver as a module, choose M here: the | ||
311 | module will be called w83627hf_wdt. | ||
312 | |||
313 | Most people will say N. | ||
314 | |||
315 | config W83877F_WDT | ||
316 | tristate "W83877F (EMACS) Watchdog Timer" | ||
317 | depends on WATCHDOG && X86 | ||
318 | ---help--- | ||
319 | This is the driver for the hardware watchdog on the W83877F chipset | ||
320 | as used in EMACS PC-104 motherboards (and likely others). This | ||
321 | watchdog simply watches your kernel to make sure it doesn't freeze, | ||
322 | and if it does, it reboots your computer after a certain amount of | ||
323 | time. | ||
324 | |||
325 | To compile this driver as a module, choose M here: the | ||
326 | module will be called w83877f_wdt. | ||
327 | |||
328 | Most people will say N. | ||
329 | |||
330 | config MACHZ_WDT | ||
331 | tristate "ZF MachZ Watchdog" | ||
332 | depends on WATCHDOG && X86 | ||
333 | ---help--- | ||
334 | If you are using a ZF Micro MachZ processor, say Y here, otherwise | ||
335 | N. This is the driver for the watchdog timer builtin on that | ||
336 | processor using ZF-Logic interface. This watchdog simply watches | ||
337 | your kernel to make sure it doesn't freeze, and if it does, it | ||
338 | reboots your computer after a certain amount of time. | ||
339 | |||
340 | To compile this driver as a module, choose M here: the | ||
341 | module will be called machzwd. | ||
342 | |||
343 | # PowerPC Architecture | ||
344 | |||
345 | config 8xx_WDT | ||
346 | tristate "MPC8xx Watchdog Timer" | ||
347 | depends on WATCHDOG && 8xx | ||
348 | |||
349 | # MIPS Architecture | ||
350 | |||
351 | config INDYDOG | ||
352 | tristate "Indy/I2 Hardware Watchdog" | ||
353 | depends on WATCHDOG && SGI_IP22 | ||
354 | help | ||
355 | Hardwaredriver for the Indy's/I2's watchdog. This is a | ||
356 | watchdog timer that will reboot the machine after a 60 second | ||
357 | timer expired and no process has written to /dev/watchdog during | ||
358 | that time. | ||
359 | |||
360 | # S390 Architecture | ||
361 | |||
362 | config ZVM_WATCHDOG | ||
363 | tristate "z/VM Watchdog Timer" | ||
364 | depends on WATCHDOG && ARCH_S390 | ||
365 | help | ||
366 | IBM s/390 and zSeries machines running under z/VM 5.1 or later | ||
367 | provide a virtual watchdog timer to their guest that cause a | ||
368 | user define Control Program command to be executed after a | ||
369 | timeout. | ||
370 | |||
371 | To compile this driver as a module, choose M here. The module | ||
372 | will be called vmwatchdog. | ||
373 | |||
374 | # SUPERH Architecture | ||
375 | |||
376 | config SH_WDT | ||
377 | tristate "SuperH Watchdog" | ||
378 | depends on WATCHDOG && SUPERH | ||
379 | help | ||
380 | This driver adds watchdog support for the integrated watchdog in the | ||
381 | SuperH processors. If you have one of these processors and wish | ||
382 | to have watchdog support enabled, say Y, otherwise say N. | ||
383 | |||
384 | As a side note, saying Y here will automatically boost HZ to 1000 | ||
385 | so that the timer has a chance to clear the overflow counter. On | ||
386 | slower systems (such as the SH-2 and SH-3) this will likely yield | ||
387 | some performance issues. As such, the WDT should be avoided here | ||
388 | unless it is absolutely necessary. | ||
389 | |||
390 | To compile this driver as a module, choose M here: the | ||
391 | module will be called shwdt. | ||
392 | |||
393 | # SPARC64 Architecture | ||
394 | |||
395 | config WATCHDOG_CP1XXX | ||
396 | tristate "CP1XXX Hardware Watchdog support" | ||
397 | depends on WATCHDOG && SPARC64 && PCI | ||
398 | ---help--- | ||
399 | This is the driver for the hardware watchdog timers present on | ||
400 | Sun Microsystems CompactPCI models CP1400 and CP1500. | ||
401 | |||
402 | To compile this driver as a module, choose M here: the | ||
403 | module will be called cpwatchdog. | ||
404 | |||
405 | If you do not have a CompactPCI model CP1400 or CP1500, or | ||
406 | another UltraSPARC-IIi-cEngine boardset with hardware watchdog, | ||
407 | you should say N to this option. | ||
408 | |||
409 | config WATCHDOG_RIO | ||
410 | tristate "RIO Hardware Watchdog support" | ||
411 | depends on WATCHDOG && SPARC64 && PCI | ||
412 | help | ||
413 | Say Y here to support the hardware watchdog capability on Sun RIO | ||
414 | machines. The watchdog timeout period is normally one minute but | ||
415 | can be changed with a boot-time parameter. | ||
416 | |||
417 | # | ||
418 | # ISA-based Watchdog Cards | ||
419 | # | ||
420 | |||
421 | comment "ISA-based Watchdog Cards" | ||
422 | depends on WATCHDOG && ISA | ||
423 | |||
424 | config PCWATCHDOG | ||
425 | tristate "Berkshire Products ISA-PC Watchdog" | ||
426 | depends on WATCHDOG && ISA | ||
427 | ---help--- | ||
428 | This is the driver for the Berkshire Products ISA-PC Watchdog card. | ||
429 | This card simply watches your kernel to make sure it doesn't freeze, | ||
430 | and if it does, it reboots your computer after a certain amount of | ||
431 | time. This driver is like the WDT501 driver but for different | ||
432 | hardware. Please read <file:Documentation/watchdog/pcwd-watchdog.txt>. The PC | ||
433 | watchdog cards can be ordered from <http://www.berkprod.com/>. | ||
434 | |||
435 | To compile this driver as a module, choose M here: the | ||
436 | module will be called pcwd. | ||
437 | |||
438 | Most people will say N. | ||
439 | |||
440 | config MIXCOMWD | ||
441 | tristate "Mixcom Watchdog" | ||
442 | depends on WATCHDOG && ISA | ||
443 | ---help--- | ||
444 | This is a driver for the Mixcom hardware watchdog cards. This | ||
445 | watchdog simply watches your kernel to make sure it doesn't freeze, | ||
446 | and if it does, it reboots your computer after a certain amount of | ||
447 | time. | ||
448 | |||
449 | To compile this driver as a module, choose M here: the | ||
450 | module will be called mixcomwd. | ||
451 | |||
452 | Most people will say N. | ||
453 | |||
454 | config WDT | ||
455 | tristate "WDT Watchdog timer" | ||
456 | depends on WATCHDOG && ISA | ||
457 | ---help--- | ||
458 | If you have a WDT500P or WDT501P watchdog board, say Y here, | ||
459 | otherwise N. It is not possible to probe for this board, which means | ||
460 | that you have to inform the kernel about the IO port and IRQ that | ||
461 | is needed (you can do this via the io and irq parameters) | ||
462 | |||
463 | To compile this driver as a module, choose M here: the | ||
464 | module will be called wdt. | ||
465 | |||
466 | config WDT_501 | ||
467 | bool "WDT501 features" | ||
468 | depends on WDT | ||
469 | help | ||
470 | Saying Y here and creating a character special file /dev/temperature | ||
471 | with major number 10 and minor number 131 ("man mknod") will give | ||
472 | you a thermometer inside your computer: reading from | ||
473 | /dev/temperature yields one byte, the temperature in degrees | ||
474 | Fahrenheit. This works only if you have a WDT501P watchdog board | ||
475 | installed. | ||
476 | |||
477 | If you want to enable the Fan Tachometer on the WDT501P, then you | ||
478 | can do this via the tachometer parameter. Only do this if you have a | ||
479 | fan tachometer actually set up. | ||
480 | |||
481 | # | ||
482 | # PCI-based Watchdog Cards | ||
483 | # | ||
484 | |||
485 | comment "PCI-based Watchdog Cards" | ||
486 | depends on WATCHDOG && PCI | ||
487 | |||
488 | config PCIPCWATCHDOG | ||
489 | tristate "Berkshire Products PCI-PC Watchdog" | ||
490 | depends on WATCHDOG && PCI | ||
491 | ---help--- | ||
492 | This is the driver for the Berkshire Products PCI-PC Watchdog card. | ||
493 | This card simply watches your kernel to make sure it doesn't freeze, | ||
494 | and if it does, it reboots your computer after a certain amount of | ||
495 | time. The card can also monitor the internal temperature of the PC. | ||
496 | More info is available at <http://www.berkprod.com/pci_pc_watchdog.htm>. | ||
497 | |||
498 | To compile this driver as a module, choose M here: the | ||
499 | module will be called pcwd_pci. | ||
500 | |||
501 | Most people will say N. | ||
502 | |||
503 | config WDTPCI | ||
504 | tristate "PCI-WDT500/501 Watchdog timer" | ||
505 | depends on WATCHDOG && PCI | ||
506 | ---help--- | ||
507 | If you have a PCI-WDT500/501 watchdog board, say Y here, otherwise N. | ||
508 | |||
509 | To compile this driver as a module, choose M here: the | ||
510 | module will be called wdt_pci. | ||
511 | |||
512 | config WDT_501_PCI | ||
513 | bool "PCI-WDT501 features" | ||
514 | depends on WDTPCI | ||
515 | help | ||
516 | Saying Y here and creating a character special file /dev/temperature | ||
517 | with major number 10 and minor number 131 ("man mknod") will give | ||
518 | you a thermometer inside your computer: reading from | ||
519 | /dev/temperature yields one byte, the temperature in degrees | ||
520 | Fahrenheit. This works only if you have a PCI-WDT501 watchdog board | ||
521 | installed. | ||
522 | |||
523 | If you want to enable the Fan Tachometer on the PCI-WDT501, then you | ||
524 | can do this via the tachometer parameter. Only do this if you have a | ||
525 | fan tachometer actually set up. | ||
526 | |||
527 | # | ||
528 | # USB-based Watchdog Cards | ||
529 | # | ||
530 | |||
531 | comment "USB-based Watchdog Cards" | ||
532 | depends on WATCHDOG && USB | ||
533 | |||
534 | config USBPCWATCHDOG | ||
535 | tristate "Berkshire Products USB-PC Watchdog" | ||
536 | depends on WATCHDOG && USB | ||
537 | ---help--- | ||
538 | This is the driver for the Berkshire Products USB-PC Watchdog card. | ||
539 | This card simply watches your kernel to make sure it doesn't freeze, | ||
540 | and if it does, it reboots your computer after a certain amount of | ||
541 | time. The card can also monitor the internal temperature of the PC. | ||
542 | More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>. | ||
543 | |||
544 | To compile this driver as a module, choose M here: the | ||
545 | module will be called pcwd_usb. | ||
546 | |||
547 | Most people will say N. | ||
548 | |||
549 | endmenu | ||
diff --git a/drivers/char/watchdog/Makefile b/drivers/char/watchdog/Makefile new file mode 100644 index 000000000000..1cd27efa35c1 --- /dev/null +++ b/drivers/char/watchdog/Makefile | |||
@@ -0,0 +1,42 @@ | |||
1 | # | ||
2 | # Makefile for the WatchDog device drivers. | ||
3 | # | ||
4 | |||
5 | obj-$(CONFIG_PCWATCHDOG) += pcwd.o | ||
6 | obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o | ||
7 | obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o | ||
8 | obj-$(CONFIG_IB700_WDT) += ib700wdt.o | ||
9 | obj-$(CONFIG_MIXCOMWD) += mixcomwd.o | ||
10 | obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o | ||
11 | obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o | ||
12 | obj-$(CONFIG_WDT) += wdt.o | ||
13 | obj-$(CONFIG_WDTPCI) += wdt_pci.o | ||
14 | obj-$(CONFIG_21285_WATCHDOG) += wdt285.o | ||
15 | obj-$(CONFIG_977_WATCHDOG) += wdt977.o | ||
16 | obj-$(CONFIG_I8XX_TCO) += i8xx_tco.o | ||
17 | obj-$(CONFIG_MACHZ_WDT) += machzwd.o | ||
18 | obj-$(CONFIG_SH_WDT) += shwdt.o | ||
19 | obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o | ||
20 | obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o | ||
21 | obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o | ||
22 | obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o | ||
23 | obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o | ||
24 | obj-$(CONFIG_SC520_WDT) += sc520_wdt.o | ||
25 | obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o | ||
26 | obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o | ||
27 | obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o | ||
28 | obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o | ||
29 | obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o | ||
30 | obj-$(CONFIG_INDYDOG) += indydog.o | ||
31 | obj-$(CONFIG_PCIPCWATCHDOG) += pcwd_pci.o | ||
32 | obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o | ||
33 | obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o | ||
34 | obj-$(CONFIG_IXP2000_WATCHDOG) += ixp2000_wdt.o | ||
35 | obj-$(CONFIG_8xx_WDT) += mpc8xx_wdt.o | ||
36 | |||
37 | # Only one watchdog can succeed. We probe the hardware watchdog | ||
38 | # drivers first, then the softdog driver. This means if your hardware | ||
39 | # watchdog dies or is 'borrowed' for some reason the software watchdog | ||
40 | # still gives you some cover. | ||
41 | |||
42 | obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o | ||
diff --git a/drivers/char/watchdog/acquirewdt.c b/drivers/char/watchdog/acquirewdt.c new file mode 100644 index 000000000000..8f302121741b --- /dev/null +++ b/drivers/char/watchdog/acquirewdt.c | |||
@@ -0,0 +1,332 @@ | |||
1 | /* | ||
2 | * Acquire Single Board Computer Watchdog Timer driver | ||
3 | * | ||
4 | * Based on wdt.c. Original copyright messages: | ||
5 | * | ||
6 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
7 | * http://www.redhat.com | ||
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 Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
15 | * warranty for any of this software. This material is provided | ||
16 | * "AS-IS" and at no charge. | ||
17 | * | ||
18 | * (c) Copyright 1995 Alan Cox <alan@redhat.com> | ||
19 | * | ||
20 | * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> | ||
21 | * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT | ||
22 | * Can't add timeout - driver doesn't allow changing value | ||
23 | */ | ||
24 | |||
25 | /* | ||
26 | * Theory of Operation: | ||
27 | * The Watch-Dog Timer is provided to ensure that standalone | ||
28 | * Systems can always recover from catastrophic conditions that | ||
29 | * caused the CPU to crash. This condition may have occured by | ||
30 | * external EMI or a software bug. When the CPU stops working | ||
31 | * correctly, hardware on the board will either perform a hardware | ||
32 | * reset (cold boot) or a non-maskable interrupt (NMI) to bring the | ||
33 | * system back to a known state. | ||
34 | * | ||
35 | * The Watch-Dog Timer is controlled by two I/O Ports. | ||
36 | * 443 hex - Read - Enable or refresh the Watch-Dog Timer | ||
37 | * 043 hex - Read - Disable the Watch-Dog Timer | ||
38 | * | ||
39 | * To enable the Watch-Dog Timer, a read from I/O port 443h must | ||
40 | * be performed. This will enable and activate the countdown timer | ||
41 | * which will eventually time out and either reset the CPU or cause | ||
42 | * an NMI depending on the setting of a jumper. To ensure that this | ||
43 | * reset condition does not occur, the Watch-Dog Timer must be | ||
44 | * periodically refreshed by reading the same I/O port 443h. | ||
45 | * The Watch-Dog Timer is disabled by reading I/O port 043h. | ||
46 | * | ||
47 | * The Watch-Dog Timer Time-Out Period is set via jumpers. | ||
48 | * It can be 1, 2, 10, 20, 110 or 220 seconds. | ||
49 | */ | ||
50 | |||
51 | #include <linux/module.h> | ||
52 | #include <linux/moduleparam.h> | ||
53 | #include <linux/types.h> | ||
54 | #include <linux/miscdevice.h> | ||
55 | #include <linux/watchdog.h> | ||
56 | #include <linux/fs.h> | ||
57 | #include <linux/ioport.h> | ||
58 | #include <linux/notifier.h> | ||
59 | #include <linux/reboot.h> | ||
60 | #include <linux/init.h> | ||
61 | |||
62 | #include <asm/io.h> | ||
63 | #include <asm/uaccess.h> | ||
64 | #include <asm/system.h> | ||
65 | |||
66 | #define WATCHDOG_NAME "Acquire WDT" | ||
67 | #define PFX WATCHDOG_NAME ": " | ||
68 | #define WATCHDOG_HEARTBEAT 0 /* There is no way to see what the correct time-out period is */ | ||
69 | |||
70 | static unsigned long acq_is_open; | ||
71 | static char expect_close; | ||
72 | |||
73 | /* | ||
74 | * You must set these - there is no sane way to probe for this board. | ||
75 | */ | ||
76 | |||
77 | static int wdt_stop = 0x43; | ||
78 | module_param(wdt_stop, int, 0); | ||
79 | MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)"); | ||
80 | |||
81 | static int wdt_start = 0x443; | ||
82 | module_param(wdt_start, int, 0); | ||
83 | MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)"); | ||
84 | |||
85 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
86 | static int nowayout = 1; | ||
87 | #else | ||
88 | static int nowayout = 0; | ||
89 | #endif | ||
90 | |||
91 | module_param(nowayout, int, 0); | ||
92 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
93 | |||
94 | /* | ||
95 | * Kernel methods. | ||
96 | */ | ||
97 | |||
98 | static void acq_keepalive(void) | ||
99 | { | ||
100 | /* Write a watchdog value */ | ||
101 | inb_p(wdt_start); | ||
102 | } | ||
103 | |||
104 | static void acq_stop(void) | ||
105 | { | ||
106 | /* Turn the card off */ | ||
107 | inb_p(wdt_stop); | ||
108 | } | ||
109 | |||
110 | /* | ||
111 | * /dev/watchdog handling. | ||
112 | */ | ||
113 | |||
114 | static ssize_t acq_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
115 | { | ||
116 | /* See if we got the magic character 'V' and reload the timer */ | ||
117 | if(count) { | ||
118 | if (!nowayout) { | ||
119 | size_t i; | ||
120 | |||
121 | /* note: just in case someone wrote the magic character | ||
122 | * five months ago... */ | ||
123 | expect_close = 0; | ||
124 | |||
125 | /* scan to see whether or not we got the magic character */ | ||
126 | for (i = 0; i != count; i++) { | ||
127 | char c; | ||
128 | if (get_user(c, buf + i)) | ||
129 | return -EFAULT; | ||
130 | if (c == 'V') | ||
131 | expect_close = 42; | ||
132 | } | ||
133 | } | ||
134 | |||
135 | /* Well, anyhow someone wrote to us, we should return that favour */ | ||
136 | acq_keepalive(); | ||
137 | } | ||
138 | return count; | ||
139 | } | ||
140 | |||
141 | static int acq_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
142 | unsigned long arg) | ||
143 | { | ||
144 | int options, retval = -EINVAL; | ||
145 | void __user *argp = (void __user *)arg; | ||
146 | int __user *p = argp; | ||
147 | static struct watchdog_info ident = | ||
148 | { | ||
149 | .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | ||
150 | .firmware_version = 1, | ||
151 | .identity = "Acquire WDT", | ||
152 | }; | ||
153 | |||
154 | switch(cmd) | ||
155 | { | ||
156 | case WDIOC_GETSUPPORT: | ||
157 | return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; | ||
158 | |||
159 | case WDIOC_GETSTATUS: | ||
160 | case WDIOC_GETBOOTSTATUS: | ||
161 | return put_user(0, p); | ||
162 | |||
163 | case WDIOC_KEEPALIVE: | ||
164 | acq_keepalive(); | ||
165 | return 0; | ||
166 | |||
167 | case WDIOC_GETTIMEOUT: | ||
168 | return put_user(WATCHDOG_HEARTBEAT, p); | ||
169 | |||
170 | case WDIOC_SETOPTIONS: | ||
171 | { | ||
172 | if (get_user(options, p)) | ||
173 | return -EFAULT; | ||
174 | |||
175 | if (options & WDIOS_DISABLECARD) | ||
176 | { | ||
177 | acq_stop(); | ||
178 | retval = 0; | ||
179 | } | ||
180 | |||
181 | if (options & WDIOS_ENABLECARD) | ||
182 | { | ||
183 | acq_keepalive(); | ||
184 | retval = 0; | ||
185 | } | ||
186 | |||
187 | return retval; | ||
188 | } | ||
189 | |||
190 | default: | ||
191 | return -ENOIOCTLCMD; | ||
192 | } | ||
193 | } | ||
194 | |||
195 | static int acq_open(struct inode *inode, struct file *file) | ||
196 | { | ||
197 | if (test_and_set_bit(0, &acq_is_open)) | ||
198 | return -EBUSY; | ||
199 | |||
200 | if (nowayout) | ||
201 | __module_get(THIS_MODULE); | ||
202 | |||
203 | /* Activate */ | ||
204 | acq_keepalive(); | ||
205 | return nonseekable_open(inode, file); | ||
206 | } | ||
207 | |||
208 | static int acq_close(struct inode *inode, struct file *file) | ||
209 | { | ||
210 | if (expect_close == 42) { | ||
211 | acq_stop(); | ||
212 | } else { | ||
213 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
214 | acq_keepalive(); | ||
215 | } | ||
216 | clear_bit(0, &acq_is_open); | ||
217 | expect_close = 0; | ||
218 | return 0; | ||
219 | } | ||
220 | |||
221 | /* | ||
222 | * Notifier for system down | ||
223 | */ | ||
224 | |||
225 | static int acq_notify_sys(struct notifier_block *this, unsigned long code, | ||
226 | void *unused) | ||
227 | { | ||
228 | if(code==SYS_DOWN || code==SYS_HALT) { | ||
229 | /* Turn the WDT off */ | ||
230 | acq_stop(); | ||
231 | } | ||
232 | return NOTIFY_DONE; | ||
233 | } | ||
234 | |||
235 | /* | ||
236 | * Kernel Interfaces | ||
237 | */ | ||
238 | |||
239 | static struct file_operations acq_fops = { | ||
240 | .owner = THIS_MODULE, | ||
241 | .llseek = no_llseek, | ||
242 | .write = acq_write, | ||
243 | .ioctl = acq_ioctl, | ||
244 | .open = acq_open, | ||
245 | .release = acq_close, | ||
246 | }; | ||
247 | |||
248 | static struct miscdevice acq_miscdev= | ||
249 | { | ||
250 | .minor = WATCHDOG_MINOR, | ||
251 | .name = "watchdog", | ||
252 | .fops = &acq_fops, | ||
253 | }; | ||
254 | |||
255 | /* | ||
256 | * The WDT card needs to learn about soft shutdowns in order to | ||
257 | * turn the timebomb registers off. | ||
258 | */ | ||
259 | |||
260 | static struct notifier_block acq_notifier = | ||
261 | { | ||
262 | .notifier_call = acq_notify_sys, | ||
263 | }; | ||
264 | |||
265 | static int __init acq_init(void) | ||
266 | { | ||
267 | int ret; | ||
268 | |||
269 | printk(KERN_INFO "WDT driver for Acquire single board computer initialising.\n"); | ||
270 | |||
271 | if (wdt_stop != wdt_start) { | ||
272 | if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) { | ||
273 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
274 | wdt_stop); | ||
275 | ret = -EIO; | ||
276 | goto out; | ||
277 | } | ||
278 | } | ||
279 | |||
280 | if (!request_region(wdt_start, 1, WATCHDOG_NAME)) { | ||
281 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
282 | wdt_start); | ||
283 | ret = -EIO; | ||
284 | goto unreg_stop; | ||
285 | } | ||
286 | |||
287 | ret = register_reboot_notifier(&acq_notifier); | ||
288 | if (ret != 0) { | ||
289 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
290 | ret); | ||
291 | goto unreg_regions; | ||
292 | } | ||
293 | |||
294 | ret = misc_register(&acq_miscdev); | ||
295 | if (ret != 0) { | ||
296 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
297 | WATCHDOG_MINOR, ret); | ||
298 | goto unreg_reboot; | ||
299 | } | ||
300 | |||
301 | printk (KERN_INFO PFX "initialized. (nowayout=%d)\n", | ||
302 | nowayout); | ||
303 | |||
304 | return 0; | ||
305 | |||
306 | unreg_reboot: | ||
307 | unregister_reboot_notifier(&acq_notifier); | ||
308 | unreg_regions: | ||
309 | release_region(wdt_start, 1); | ||
310 | unreg_stop: | ||
311 | if (wdt_stop != wdt_start) | ||
312 | release_region(wdt_stop, 1); | ||
313 | out: | ||
314 | return ret; | ||
315 | } | ||
316 | |||
317 | static void __exit acq_exit(void) | ||
318 | { | ||
319 | misc_deregister(&acq_miscdev); | ||
320 | unregister_reboot_notifier(&acq_notifier); | ||
321 | if(wdt_stop != wdt_start) | ||
322 | release_region(wdt_stop,1); | ||
323 | release_region(wdt_start,1); | ||
324 | } | ||
325 | |||
326 | module_init(acq_init); | ||
327 | module_exit(acq_exit); | ||
328 | |||
329 | MODULE_AUTHOR("David Woodhouse"); | ||
330 | MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver"); | ||
331 | MODULE_LICENSE("GPL"); | ||
332 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/advantechwdt.c b/drivers/char/watchdog/advantechwdt.c new file mode 100644 index 000000000000..ea73c8379bdd --- /dev/null +++ b/drivers/char/watchdog/advantechwdt.c | |||
@@ -0,0 +1,333 @@ | |||
1 | /* | ||
2 | * Advantech Single Board Computer WDT driver | ||
3 | * | ||
4 | * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> | ||
5 | * | ||
6 | * Based on acquirewdt.c which is based on wdt.c. | ||
7 | * Original copyright messages: | ||
8 | * | ||
9 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
10 | * http://www.redhat.com | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version | ||
15 | * 2 of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
18 | * warranty for any of this software. This material is provided | ||
19 | * "AS-IS" and at no charge. | ||
20 | * | ||
21 | * (c) Copyright 1995 Alan Cox <alan@redhat.com> | ||
22 | * | ||
23 | * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> | ||
24 | * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT | ||
25 | * | ||
26 | * 16-Oct-2002 Rob Radez <rob@osinvestor.com> | ||
27 | * Clean up ioctls, clean up init + exit, add expect close support, | ||
28 | * add wdt_start and wdt_stop as parameters. | ||
29 | */ | ||
30 | |||
31 | #include <linux/module.h> | ||
32 | #include <linux/moduleparam.h> | ||
33 | #include <linux/types.h> | ||
34 | #include <linux/miscdevice.h> | ||
35 | #include <linux/watchdog.h> | ||
36 | #include <linux/fs.h> | ||
37 | #include <linux/ioport.h> | ||
38 | #include <linux/notifier.h> | ||
39 | #include <linux/reboot.h> | ||
40 | #include <linux/init.h> | ||
41 | |||
42 | #include <asm/io.h> | ||
43 | #include <asm/uaccess.h> | ||
44 | #include <asm/system.h> | ||
45 | |||
46 | #define WATCHDOG_NAME "Advantech WDT" | ||
47 | #define PFX WATCHDOG_NAME ": " | ||
48 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ | ||
49 | |||
50 | static unsigned long advwdt_is_open; | ||
51 | static char adv_expect_close; | ||
52 | |||
53 | /* | ||
54 | * You must set these - there is no sane way to probe for this board. | ||
55 | * | ||
56 | * To enable or restart, write the timeout value in seconds (1 to 63) | ||
57 | * to I/O port wdt_start. To disable, read I/O port wdt_stop. | ||
58 | * Both are 0x443 for most boards (tested on a PCA-6276VE-00B1), but | ||
59 | * check your manual (at least the PCA-6159 seems to be different - | ||
60 | * the manual says wdt_stop is 0x43, not 0x443). | ||
61 | * (0x43 is also a write-only control register for the 8254 timer!) | ||
62 | */ | ||
63 | |||
64 | static int wdt_stop = 0x443; | ||
65 | module_param(wdt_stop, int, 0); | ||
66 | MODULE_PARM_DESC(wdt_stop, "Advantech WDT 'stop' io port (default 0x443)"); | ||
67 | |||
68 | static int wdt_start = 0x443; | ||
69 | module_param(wdt_start, int, 0); | ||
70 | MODULE_PARM_DESC(wdt_start, "Advantech WDT 'start' io port (default 0x443)"); | ||
71 | |||
72 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ | ||
73 | module_param(timeout, int, 0); | ||
74 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=63, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) "."); | ||
75 | |||
76 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
77 | static int nowayout = 1; | ||
78 | #else | ||
79 | static int nowayout = 0; | ||
80 | #endif | ||
81 | |||
82 | module_param(nowayout, int, 0); | ||
83 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
84 | |||
85 | /* | ||
86 | * Kernel methods. | ||
87 | */ | ||
88 | |||
89 | static void | ||
90 | advwdt_ping(void) | ||
91 | { | ||
92 | /* Write a watchdog value */ | ||
93 | outb_p(timeout, wdt_start); | ||
94 | } | ||
95 | |||
96 | static void | ||
97 | advwdt_disable(void) | ||
98 | { | ||
99 | inb_p(wdt_stop); | ||
100 | } | ||
101 | |||
102 | static ssize_t | ||
103 | advwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
104 | { | ||
105 | if (count) { | ||
106 | if (!nowayout) { | ||
107 | size_t i; | ||
108 | |||
109 | adv_expect_close = 0; | ||
110 | |||
111 | for (i = 0; i != count; i++) { | ||
112 | char c; | ||
113 | if (get_user(c, buf+i)) | ||
114 | return -EFAULT; | ||
115 | if (c == 'V') | ||
116 | adv_expect_close = 42; | ||
117 | } | ||
118 | } | ||
119 | advwdt_ping(); | ||
120 | } | ||
121 | return count; | ||
122 | } | ||
123 | |||
124 | static int | ||
125 | advwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
126 | unsigned long arg) | ||
127 | { | ||
128 | int new_timeout; | ||
129 | void __user *argp = (void __user *)arg; | ||
130 | int __user *p = argp; | ||
131 | static struct watchdog_info ident = { | ||
132 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
133 | .firmware_version = 1, | ||
134 | .identity = "Advantech WDT", | ||
135 | }; | ||
136 | |||
137 | switch (cmd) { | ||
138 | case WDIOC_GETSUPPORT: | ||
139 | if (copy_to_user(argp, &ident, sizeof(ident))) | ||
140 | return -EFAULT; | ||
141 | break; | ||
142 | |||
143 | case WDIOC_GETSTATUS: | ||
144 | case WDIOC_GETBOOTSTATUS: | ||
145 | return put_user(0, p); | ||
146 | |||
147 | case WDIOC_KEEPALIVE: | ||
148 | advwdt_ping(); | ||
149 | break; | ||
150 | |||
151 | case WDIOC_SETTIMEOUT: | ||
152 | if (get_user(new_timeout, p)) | ||
153 | return -EFAULT; | ||
154 | if ((new_timeout < 1) || (new_timeout > 63)) | ||
155 | return -EINVAL; | ||
156 | timeout = new_timeout; | ||
157 | advwdt_ping(); | ||
158 | /* Fall */ | ||
159 | |||
160 | case WDIOC_GETTIMEOUT: | ||
161 | return put_user(timeout, p); | ||
162 | |||
163 | case WDIOC_SETOPTIONS: | ||
164 | { | ||
165 | int options, retval = -EINVAL; | ||
166 | |||
167 | if (get_user(options, p)) | ||
168 | return -EFAULT; | ||
169 | |||
170 | if (options & WDIOS_DISABLECARD) { | ||
171 | advwdt_disable(); | ||
172 | retval = 0; | ||
173 | } | ||
174 | |||
175 | if (options & WDIOS_ENABLECARD) { | ||
176 | advwdt_ping(); | ||
177 | retval = 0; | ||
178 | } | ||
179 | |||
180 | return retval; | ||
181 | } | ||
182 | |||
183 | default: | ||
184 | return -ENOIOCTLCMD; | ||
185 | } | ||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | static int | ||
190 | advwdt_open(struct inode *inode, struct file *file) | ||
191 | { | ||
192 | if (test_and_set_bit(0, &advwdt_is_open)) | ||
193 | return -EBUSY; | ||
194 | /* | ||
195 | * Activate | ||
196 | */ | ||
197 | |||
198 | advwdt_ping(); | ||
199 | return nonseekable_open(inode, file); | ||
200 | } | ||
201 | |||
202 | static int | ||
203 | advwdt_close(struct inode *inode, struct file *file) | ||
204 | { | ||
205 | if (adv_expect_close == 42) { | ||
206 | advwdt_disable(); | ||
207 | } else { | ||
208 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
209 | advwdt_ping(); | ||
210 | } | ||
211 | clear_bit(0, &advwdt_is_open); | ||
212 | adv_expect_close = 0; | ||
213 | return 0; | ||
214 | } | ||
215 | |||
216 | /* | ||
217 | * Notifier for system down | ||
218 | */ | ||
219 | |||
220 | static int | ||
221 | advwdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
222 | void *unused) | ||
223 | { | ||
224 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
225 | /* Turn the WDT off */ | ||
226 | advwdt_disable(); | ||
227 | } | ||
228 | return NOTIFY_DONE; | ||
229 | } | ||
230 | |||
231 | /* | ||
232 | * Kernel Interfaces | ||
233 | */ | ||
234 | |||
235 | static struct file_operations advwdt_fops = { | ||
236 | .owner = THIS_MODULE, | ||
237 | .llseek = no_llseek, | ||
238 | .write = advwdt_write, | ||
239 | .ioctl = advwdt_ioctl, | ||
240 | .open = advwdt_open, | ||
241 | .release = advwdt_close, | ||
242 | }; | ||
243 | |||
244 | static struct miscdevice advwdt_miscdev = { | ||
245 | .minor = WATCHDOG_MINOR, | ||
246 | .name = "watchdog", | ||
247 | .fops = &advwdt_fops, | ||
248 | }; | ||
249 | |||
250 | /* | ||
251 | * The WDT needs to learn about soft shutdowns in order to | ||
252 | * turn the timebomb registers off. | ||
253 | */ | ||
254 | |||
255 | static struct notifier_block advwdt_notifier = { | ||
256 | .notifier_call = advwdt_notify_sys, | ||
257 | }; | ||
258 | |||
259 | static int __init | ||
260 | advwdt_init(void) | ||
261 | { | ||
262 | int ret; | ||
263 | |||
264 | printk(KERN_INFO "WDT driver for Advantech single board computer initialising.\n"); | ||
265 | |||
266 | if (timeout < 1 || timeout > 63) { | ||
267 | timeout = WATCHDOG_TIMEOUT; | ||
268 | printk (KERN_INFO PFX "timeout value must be 1<=x<=63, using %d\n", | ||
269 | timeout); | ||
270 | } | ||
271 | |||
272 | if (wdt_stop != wdt_start) { | ||
273 | if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) { | ||
274 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
275 | wdt_stop); | ||
276 | ret = -EIO; | ||
277 | goto out; | ||
278 | } | ||
279 | } | ||
280 | |||
281 | if (!request_region(wdt_start, 1, WATCHDOG_NAME)) { | ||
282 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
283 | wdt_start); | ||
284 | ret = -EIO; | ||
285 | goto unreg_stop; | ||
286 | } | ||
287 | |||
288 | ret = register_reboot_notifier(&advwdt_notifier); | ||
289 | if (ret != 0) { | ||
290 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
291 | ret); | ||
292 | goto unreg_regions; | ||
293 | } | ||
294 | |||
295 | ret = misc_register(&advwdt_miscdev); | ||
296 | if (ret != 0) { | ||
297 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
298 | WATCHDOG_MINOR, ret); | ||
299 | goto unreg_reboot; | ||
300 | } | ||
301 | |||
302 | printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", | ||
303 | timeout, nowayout); | ||
304 | |||
305 | out: | ||
306 | return ret; | ||
307 | unreg_reboot: | ||
308 | unregister_reboot_notifier(&advwdt_notifier); | ||
309 | unreg_regions: | ||
310 | release_region(wdt_start, 1); | ||
311 | unreg_stop: | ||
312 | if (wdt_stop != wdt_start) | ||
313 | release_region(wdt_stop, 1); | ||
314 | goto out; | ||
315 | } | ||
316 | |||
317 | static void __exit | ||
318 | advwdt_exit(void) | ||
319 | { | ||
320 | misc_deregister(&advwdt_miscdev); | ||
321 | unregister_reboot_notifier(&advwdt_notifier); | ||
322 | if(wdt_stop != wdt_start) | ||
323 | release_region(wdt_stop,1); | ||
324 | release_region(wdt_start,1); | ||
325 | } | ||
326 | |||
327 | module_init(advwdt_init); | ||
328 | module_exit(advwdt_exit); | ||
329 | |||
330 | MODULE_LICENSE("GPL"); | ||
331 | MODULE_AUTHOR("Marek Michalkiewicz <marekm@linux.org.pl>"); | ||
332 | MODULE_DESCRIPTION("Advantech Single Board Computer WDT driver"); | ||
333 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/alim1535_wdt.c b/drivers/char/watchdog/alim1535_wdt.c new file mode 100644 index 000000000000..35dcbf8be7d1 --- /dev/null +++ b/drivers/char/watchdog/alim1535_wdt.c | |||
@@ -0,0 +1,463 @@ | |||
1 | /* | ||
2 | * Watchdog for the 7101 PMU version found in the ALi M1535 chipsets | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License | ||
6 | * as published by the Free Software Foundation; either version | ||
7 | * 2 of the License, or (at your option) any later version. | ||
8 | */ | ||
9 | |||
10 | #include <linux/module.h> | ||
11 | #include <linux/moduleparam.h> | ||
12 | #include <linux/types.h> | ||
13 | #include <linux/miscdevice.h> | ||
14 | #include <linux/watchdog.h> | ||
15 | #include <linux/ioport.h> | ||
16 | #include <linux/notifier.h> | ||
17 | #include <linux/reboot.h> | ||
18 | #include <linux/init.h> | ||
19 | #include <linux/fs.h> | ||
20 | #include <linux/pci.h> | ||
21 | |||
22 | #include <asm/uaccess.h> | ||
23 | #include <asm/io.h> | ||
24 | |||
25 | #define WATCHDOG_NAME "ALi_M1535" | ||
26 | #define PFX WATCHDOG_NAME ": " | ||
27 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ | ||
28 | |||
29 | /* internal variables */ | ||
30 | static unsigned long ali_is_open; | ||
31 | static char ali_expect_release; | ||
32 | static struct pci_dev *ali_pci; | ||
33 | static u32 ali_timeout_bits; /* stores the computed timeout */ | ||
34 | static spinlock_t ali_lock; /* Guards the hardware */ | ||
35 | |||
36 | /* module parameters */ | ||
37 | static int timeout = WATCHDOG_TIMEOUT; | ||
38 | module_param(timeout, int, 0); | ||
39 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (0<timeout<18000, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
40 | |||
41 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
42 | static int nowayout = 1; | ||
43 | #else | ||
44 | static int nowayout = 0; | ||
45 | #endif | ||
46 | |||
47 | module_param(nowayout, int, 0); | ||
48 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
49 | |||
50 | /* | ||
51 | * ali_start - start watchdog countdown | ||
52 | * | ||
53 | * Starts the timer running providing the timer has a counter | ||
54 | * configuration set. | ||
55 | */ | ||
56 | |||
57 | static void ali_start(void) | ||
58 | { | ||
59 | u32 val; | ||
60 | |||
61 | spin_lock(&ali_lock); | ||
62 | |||
63 | pci_read_config_dword(ali_pci, 0xCC, &val); | ||
64 | val &= ~0x3F; /* Mask count */ | ||
65 | val |= (1<<25) | ali_timeout_bits; | ||
66 | pci_write_config_dword(ali_pci, 0xCC, val); | ||
67 | |||
68 | spin_unlock(&ali_lock); | ||
69 | } | ||
70 | |||
71 | /* | ||
72 | * ali_stop - stop the timer countdown | ||
73 | * | ||
74 | * Stop the ALi watchdog countdown | ||
75 | */ | ||
76 | |||
77 | static void ali_stop(void) | ||
78 | { | ||
79 | u32 val; | ||
80 | |||
81 | spin_lock(&ali_lock); | ||
82 | |||
83 | pci_read_config_dword(ali_pci, 0xCC, &val); | ||
84 | val &= ~0x3F; /* Mask count to zero (disabled) */ | ||
85 | val &= ~(1<<25);/* and for safety mask the reset enable */ | ||
86 | pci_write_config_dword(ali_pci, 0xCC, val); | ||
87 | |||
88 | spin_unlock(&ali_lock); | ||
89 | } | ||
90 | |||
91 | /* | ||
92 | * ali_keepalive - send a keepalive to the watchdog | ||
93 | * | ||
94 | * Send a keepalive to the timer (actually we restart the timer). | ||
95 | */ | ||
96 | |||
97 | static void ali_keepalive(void) | ||
98 | { | ||
99 | ali_start(); | ||
100 | } | ||
101 | |||
102 | /* | ||
103 | * ali_settimer - compute the timer reload value | ||
104 | * @t: time in seconds | ||
105 | * | ||
106 | * Computes the timeout values needed | ||
107 | */ | ||
108 | |||
109 | static int ali_settimer(int t) | ||
110 | { | ||
111 | if(t < 0) | ||
112 | return -EINVAL; | ||
113 | else if(t < 60) | ||
114 | ali_timeout_bits = t|(1<<6); | ||
115 | else if(t < 3600) | ||
116 | ali_timeout_bits = (t/60)|(1<<7); | ||
117 | else if(t < 18000) | ||
118 | ali_timeout_bits = (t/300)|(1<<6)|(1<<7); | ||
119 | else return -EINVAL; | ||
120 | |||
121 | timeout = t; | ||
122 | return 0; | ||
123 | } | ||
124 | |||
125 | /* | ||
126 | * /dev/watchdog handling | ||
127 | */ | ||
128 | |||
129 | /* | ||
130 | * ali_write - writes to ALi watchdog | ||
131 | * @file: file from VFS | ||
132 | * @data: user address of data | ||
133 | * @len: length of data | ||
134 | * @ppos: pointer to the file offset | ||
135 | * | ||
136 | * Handle a write to the ALi watchdog. Writing to the file pings | ||
137 | * the watchdog and resets it. Writing the magic 'V' sequence allows | ||
138 | * the next close to turn off the watchdog. | ||
139 | */ | ||
140 | |||
141 | static ssize_t ali_write(struct file *file, const char __user *data, | ||
142 | size_t len, loff_t * ppos) | ||
143 | { | ||
144 | /* See if we got the magic character 'V' and reload the timer */ | ||
145 | if (len) { | ||
146 | if (!nowayout) { | ||
147 | size_t i; | ||
148 | |||
149 | /* note: just in case someone wrote the magic character | ||
150 | * five months ago... */ | ||
151 | ali_expect_release = 0; | ||
152 | |||
153 | /* scan to see whether or not we got the magic character */ | ||
154 | for (i = 0; i != len; i++) { | ||
155 | char c; | ||
156 | if(get_user(c, data+i)) | ||
157 | return -EFAULT; | ||
158 | if (c == 'V') | ||
159 | ali_expect_release = 42; | ||
160 | } | ||
161 | } | ||
162 | |||
163 | /* someone wrote to us, we should reload the timer */ | ||
164 | ali_start(); | ||
165 | } | ||
166 | return len; | ||
167 | } | ||
168 | |||
169 | /* | ||
170 | * ali_ioctl - handle watchdog ioctls | ||
171 | * @inode: VFS inode | ||
172 | * @file: VFS file pointer | ||
173 | * @cmd: ioctl number | ||
174 | * @arg: arguments to the ioctl | ||
175 | * | ||
176 | * Handle the watchdog ioctls supported by the ALi driver. Really | ||
177 | * we want an extension to enable irq ack monitoring and the like | ||
178 | */ | ||
179 | |||
180 | static int ali_ioctl(struct inode *inode, struct file *file, | ||
181 | unsigned int cmd, unsigned long arg) | ||
182 | { | ||
183 | void __user *argp = (void __user *)arg; | ||
184 | int __user *p = argp; | ||
185 | static struct watchdog_info ident = { | ||
186 | .options = WDIOF_KEEPALIVEPING | | ||
187 | WDIOF_SETTIMEOUT | | ||
188 | WDIOF_MAGICCLOSE, | ||
189 | .firmware_version = 0, | ||
190 | .identity = "ALi M1535 WatchDog Timer", | ||
191 | }; | ||
192 | |||
193 | switch (cmd) { | ||
194 | case WDIOC_GETSUPPORT: | ||
195 | return copy_to_user(argp, &ident, | ||
196 | sizeof (ident)) ? -EFAULT : 0; | ||
197 | |||
198 | case WDIOC_GETSTATUS: | ||
199 | case WDIOC_GETBOOTSTATUS: | ||
200 | return put_user(0, p); | ||
201 | |||
202 | case WDIOC_KEEPALIVE: | ||
203 | ali_keepalive(); | ||
204 | return 0; | ||
205 | |||
206 | case WDIOC_SETOPTIONS: | ||
207 | { | ||
208 | int new_options, retval = -EINVAL; | ||
209 | |||
210 | if (get_user (new_options, p)) | ||
211 | return -EFAULT; | ||
212 | |||
213 | if (new_options & WDIOS_DISABLECARD) { | ||
214 | ali_stop(); | ||
215 | retval = 0; | ||
216 | } | ||
217 | |||
218 | if (new_options & WDIOS_ENABLECARD) { | ||
219 | ali_start(); | ||
220 | retval = 0; | ||
221 | } | ||
222 | |||
223 | return retval; | ||
224 | } | ||
225 | |||
226 | case WDIOC_SETTIMEOUT: | ||
227 | { | ||
228 | int new_timeout; | ||
229 | |||
230 | if (get_user(new_timeout, p)) | ||
231 | return -EFAULT; | ||
232 | |||
233 | if (ali_settimer(new_timeout)) | ||
234 | return -EINVAL; | ||
235 | |||
236 | ali_keepalive(); | ||
237 | /* Fall */ | ||
238 | } | ||
239 | |||
240 | case WDIOC_GETTIMEOUT: | ||
241 | return put_user(timeout, p); | ||
242 | |||
243 | default: | ||
244 | return -ENOIOCTLCMD; | ||
245 | } | ||
246 | } | ||
247 | |||
248 | /* | ||
249 | * ali_open - handle open of ali watchdog | ||
250 | * @inode: inode from VFS | ||
251 | * @file: file from VFS | ||
252 | * | ||
253 | * Open the ALi watchdog device. Ensure only one person opens it | ||
254 | * at a time. Also start the watchdog running. | ||
255 | */ | ||
256 | |||
257 | static int ali_open(struct inode *inode, struct file *file) | ||
258 | { | ||
259 | /* /dev/watchdog can only be opened once */ | ||
260 | if (test_and_set_bit(0, &ali_is_open)) | ||
261 | return -EBUSY; | ||
262 | |||
263 | /* Activate */ | ||
264 | ali_start(); | ||
265 | return nonseekable_open(inode, file); | ||
266 | } | ||
267 | |||
268 | /* | ||
269 | * ali_release - close an ALi watchdog | ||
270 | * @inode: inode from VFS | ||
271 | * @file: file from VFS | ||
272 | * | ||
273 | * Close the ALi watchdog device. Actual shutdown of the timer | ||
274 | * only occurs if the magic sequence has been set. | ||
275 | */ | ||
276 | |||
277 | static int ali_release(struct inode *inode, struct file *file) | ||
278 | { | ||
279 | /* | ||
280 | * Shut off the timer. | ||
281 | */ | ||
282 | if (ali_expect_release == 42) { | ||
283 | ali_stop(); | ||
284 | } else { | ||
285 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
286 | ali_keepalive(); | ||
287 | } | ||
288 | clear_bit(0, &ali_is_open); | ||
289 | ali_expect_release = 0; | ||
290 | return 0; | ||
291 | } | ||
292 | |||
293 | /* | ||
294 | * ali_notify_sys - System down notifier | ||
295 | * | ||
296 | * Notifier for system down | ||
297 | */ | ||
298 | |||
299 | |||
300 | static int ali_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
301 | { | ||
302 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
303 | /* Turn the WDT off */ | ||
304 | ali_stop(); | ||
305 | } | ||
306 | |||
307 | return NOTIFY_DONE; | ||
308 | } | ||
309 | |||
310 | /* | ||
311 | * Data for PCI driver interface | ||
312 | * | ||
313 | * This data only exists for exporting the supported | ||
314 | * PCI ids via MODULE_DEVICE_TABLE. We do not actually | ||
315 | * register a pci_driver, because someone else might one day | ||
316 | * want to register another driver on the same PCI id. | ||
317 | */ | ||
318 | |||
319 | static struct pci_device_id ali_pci_tbl[] = { | ||
320 | { PCI_VENDOR_ID_AL, 1535, PCI_ANY_ID, PCI_ANY_ID,}, | ||
321 | { 0, }, | ||
322 | }; | ||
323 | MODULE_DEVICE_TABLE(pci, ali_pci_tbl); | ||
324 | |||
325 | /* | ||
326 | * ali_find_watchdog - find a 1535 and 7101 | ||
327 | * | ||
328 | * Scans the PCI hardware for a 1535 series bridge and matching 7101 | ||
329 | * watchdog device. This may be overtight but it is better to be safe | ||
330 | */ | ||
331 | |||
332 | static int __init ali_find_watchdog(void) | ||
333 | { | ||
334 | struct pci_dev *pdev; | ||
335 | u32 wdog; | ||
336 | |||
337 | /* Check for a 1535 series bridge */ | ||
338 | pdev = pci_find_device(PCI_VENDOR_ID_AL, 0x1535, NULL); | ||
339 | if(pdev == NULL) | ||
340 | return -ENODEV; | ||
341 | |||
342 | /* Check for the a 7101 PMU */ | ||
343 | pdev = pci_find_device(PCI_VENDOR_ID_AL, 0x7101, NULL); | ||
344 | if(pdev == NULL) | ||
345 | return -ENODEV; | ||
346 | |||
347 | if(pci_enable_device(pdev)) | ||
348 | return -EIO; | ||
349 | |||
350 | ali_pci = pdev; | ||
351 | |||
352 | /* | ||
353 | * Initialize the timer bits | ||
354 | */ | ||
355 | pci_read_config_dword(pdev, 0xCC, &wdog); | ||
356 | |||
357 | wdog &= ~0x3F; /* Timer bits */ | ||
358 | wdog &= ~((1<<27)|(1<<26)|(1<<25)|(1<<24)); /* Issued events */ | ||
359 | wdog &= ~((1<<16)|(1<<13)|(1<<12)|(1<<11)|(1<<10)|(1<<9)); /* No monitor bits */ | ||
360 | |||
361 | pci_write_config_dword(pdev, 0xCC, wdog); | ||
362 | |||
363 | return 0; | ||
364 | } | ||
365 | |||
366 | /* | ||
367 | * Kernel Interfaces | ||
368 | */ | ||
369 | |||
370 | static struct file_operations ali_fops = { | ||
371 | .owner = THIS_MODULE, | ||
372 | .llseek = no_llseek, | ||
373 | .write = ali_write, | ||
374 | .ioctl = ali_ioctl, | ||
375 | .open = ali_open, | ||
376 | .release = ali_release, | ||
377 | }; | ||
378 | |||
379 | static struct miscdevice ali_miscdev = { | ||
380 | .minor = WATCHDOG_MINOR, | ||
381 | .name = "watchdog", | ||
382 | .fops = &ali_fops, | ||
383 | }; | ||
384 | |||
385 | static struct notifier_block ali_notifier = { | ||
386 | .notifier_call = ali_notify_sys, | ||
387 | }; | ||
388 | |||
389 | /* | ||
390 | * watchdog_init - module initialiser | ||
391 | * | ||
392 | * Scan for a suitable watchdog and if so initialize it. Return an error | ||
393 | * if we cannot, the error causes the module to unload | ||
394 | */ | ||
395 | |||
396 | static int __init watchdog_init(void) | ||
397 | { | ||
398 | int ret; | ||
399 | |||
400 | spin_lock_init(&ali_lock); | ||
401 | |||
402 | /* Check whether or not the hardware watchdog is there */ | ||
403 | if (ali_find_watchdog() != 0) { | ||
404 | return -ENODEV; | ||
405 | } | ||
406 | |||
407 | /* Check that the timeout value is within it's range ; if not reset to the default */ | ||
408 | if (timeout < 1 || timeout >= 18000) { | ||
409 | timeout = WATCHDOG_TIMEOUT; | ||
410 | printk(KERN_INFO PFX "timeout value must be 0<timeout<18000, using %d\n", | ||
411 | timeout); | ||
412 | } | ||
413 | |||
414 | /* Calculate the watchdog's timeout */ | ||
415 | ali_settimer(timeout); | ||
416 | |||
417 | ret = misc_register(&ali_miscdev); | ||
418 | if (ret != 0) { | ||
419 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
420 | WATCHDOG_MINOR, ret); | ||
421 | goto out; | ||
422 | } | ||
423 | |||
424 | ret = register_reboot_notifier(&ali_notifier); | ||
425 | if (ret != 0) { | ||
426 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
427 | ret); | ||
428 | goto unreg_miscdev; | ||
429 | } | ||
430 | |||
431 | printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", | ||
432 | timeout, nowayout); | ||
433 | |||
434 | out: | ||
435 | return ret; | ||
436 | unreg_miscdev: | ||
437 | misc_deregister(&ali_miscdev); | ||
438 | goto out; | ||
439 | } | ||
440 | |||
441 | /* | ||
442 | * watchdog_exit - module de-initialiser | ||
443 | * | ||
444 | * Called while unloading a successfully installed watchdog module. | ||
445 | */ | ||
446 | |||
447 | static void __exit watchdog_exit(void) | ||
448 | { | ||
449 | /* Stop the timer before we leave */ | ||
450 | ali_stop(); | ||
451 | |||
452 | /* Deregister */ | ||
453 | unregister_reboot_notifier(&ali_notifier); | ||
454 | misc_deregister(&ali_miscdev); | ||
455 | } | ||
456 | |||
457 | module_init(watchdog_init); | ||
458 | module_exit(watchdog_exit); | ||
459 | |||
460 | MODULE_AUTHOR("Alan Cox"); | ||
461 | MODULE_DESCRIPTION("ALi M1535 PMU Watchdog Timer driver"); | ||
462 | MODULE_LICENSE("GPL"); | ||
463 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/alim7101_wdt.c b/drivers/char/watchdog/alim7101_wdt.c new file mode 100644 index 000000000000..90c091d9e0f5 --- /dev/null +++ b/drivers/char/watchdog/alim7101_wdt.c | |||
@@ -0,0 +1,421 @@ | |||
1 | /* | ||
2 | * ALi M7101 PMU Computer Watchdog Timer driver | ||
3 | * | ||
4 | * Based on w83877f_wdt.c by Scott Jennings <linuxdrivers@oro.net> | ||
5 | * and the Cobalt kernel WDT timer driver by Tim Hockin | ||
6 | * <thockin@cobaltnet.com> | ||
7 | * | ||
8 | * (c)2002 Steve Hill <steve@navaho.co.uk> | ||
9 | * | ||
10 | * This WDT driver is different from most other Linux WDT | ||
11 | * drivers in that the driver will ping the watchdog by itself, | ||
12 | * because this particular WDT has a very short timeout (1.6 | ||
13 | * seconds) and it would be insane to count on any userspace | ||
14 | * daemon always getting scheduled within that time frame. | ||
15 | * | ||
16 | * Additions: | ||
17 | * Aug 23, 2004 - Added use_gpio module parameter for use on revision a1d PMUs | ||
18 | * found on very old cobalt hardware. | ||
19 | * -- Mike Waychison <michael.waychison@sun.com> | ||
20 | */ | ||
21 | |||
22 | #include <linux/module.h> | ||
23 | #include <linux/moduleparam.h> | ||
24 | #include <linux/types.h> | ||
25 | #include <linux/timer.h> | ||
26 | #include <linux/miscdevice.h> | ||
27 | #include <linux/watchdog.h> | ||
28 | #include <linux/ioport.h> | ||
29 | #include <linux/notifier.h> | ||
30 | #include <linux/reboot.h> | ||
31 | #include <linux/init.h> | ||
32 | #include <linux/fs.h> | ||
33 | #include <linux/pci.h> | ||
34 | |||
35 | #include <asm/io.h> | ||
36 | #include <asm/uaccess.h> | ||
37 | #include <asm/system.h> | ||
38 | |||
39 | #define OUR_NAME "alim7101_wdt" | ||
40 | #define PFX OUR_NAME ": " | ||
41 | |||
42 | #define WDT_ENABLE 0x9C | ||
43 | #define WDT_DISABLE 0x8C | ||
44 | |||
45 | #define ALI_7101_WDT 0x92 | ||
46 | #define ALI_7101_GPIO 0x7D | ||
47 | #define ALI_7101_GPIO_O 0x7E | ||
48 | #define ALI_WDT_ARM 0x01 | ||
49 | |||
50 | /* | ||
51 | * We're going to use a 1 second timeout. | ||
52 | * If we reset the watchdog every ~250ms we should be safe. */ | ||
53 | |||
54 | #define WDT_INTERVAL (HZ/4+1) | ||
55 | |||
56 | /* | ||
57 | * We must not require too good response from the userspace daemon. | ||
58 | * Here we require the userspace daemon to send us a heartbeat | ||
59 | * char to /dev/watchdog every 30 seconds. | ||
60 | */ | ||
61 | |||
62 | #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ | ||
63 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ | ||
64 | module_param(timeout, int, 0); | ||
65 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
66 | |||
67 | static int use_gpio = 0; /* Use the pic (for a1d revision alim7101) */ | ||
68 | module_param(use_gpio, int, 0); | ||
69 | MODULE_PARM_DESC(use_gpio, "Use the gpio watchdog. (required by old cobalt boards)"); | ||
70 | |||
71 | static void wdt_timer_ping(unsigned long); | ||
72 | static struct timer_list timer; | ||
73 | static unsigned long next_heartbeat; | ||
74 | static unsigned long wdt_is_open; | ||
75 | static char wdt_expect_close; | ||
76 | static struct pci_dev *alim7101_pmu; | ||
77 | |||
78 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
79 | static int nowayout = 1; | ||
80 | #else | ||
81 | static int nowayout = 0; | ||
82 | #endif | ||
83 | |||
84 | module_param(nowayout, int, 0); | ||
85 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
86 | |||
87 | /* | ||
88 | * Whack the dog | ||
89 | */ | ||
90 | |||
91 | static void wdt_timer_ping(unsigned long data) | ||
92 | { | ||
93 | /* If we got a heartbeat pulse within the WDT_US_INTERVAL | ||
94 | * we agree to ping the WDT | ||
95 | */ | ||
96 | char tmp; | ||
97 | |||
98 | if(time_before(jiffies, next_heartbeat)) | ||
99 | { | ||
100 | /* Ping the WDT (this is actually a disarm/arm sequence) */ | ||
101 | pci_read_config_byte(alim7101_pmu, 0x92, &tmp); | ||
102 | pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM)); | ||
103 | pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM)); | ||
104 | if (use_gpio) { | ||
105 | pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp); | ||
106 | pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp | ||
107 | | 0x20); | ||
108 | pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp | ||
109 | & ~0x20); | ||
110 | } | ||
111 | } else { | ||
112 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
113 | } | ||
114 | /* Re-set the timer interval */ | ||
115 | timer.expires = jiffies + WDT_INTERVAL; | ||
116 | add_timer(&timer); | ||
117 | } | ||
118 | |||
119 | /* | ||
120 | * Utility routines | ||
121 | */ | ||
122 | |||
123 | static void wdt_change(int writeval) | ||
124 | { | ||
125 | char tmp; | ||
126 | |||
127 | pci_read_config_byte(alim7101_pmu, ALI_7101_WDT, &tmp); | ||
128 | if (writeval == WDT_ENABLE) { | ||
129 | pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM)); | ||
130 | if (use_gpio) { | ||
131 | pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp); | ||
132 | pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp & ~0x20); | ||
133 | } | ||
134 | |||
135 | } else { | ||
136 | pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM)); | ||
137 | if (use_gpio) { | ||
138 | pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp); | ||
139 | pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp | 0x20); | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | |||
144 | static void wdt_startup(void) | ||
145 | { | ||
146 | next_heartbeat = jiffies + (timeout * HZ); | ||
147 | |||
148 | /* We must enable before we kick off the timer in case the timer | ||
149 | occurs as we ping it */ | ||
150 | |||
151 | wdt_change(WDT_ENABLE); | ||
152 | |||
153 | /* Start the timer */ | ||
154 | timer.expires = jiffies + WDT_INTERVAL; | ||
155 | add_timer(&timer); | ||
156 | |||
157 | |||
158 | printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); | ||
159 | } | ||
160 | |||
161 | static void wdt_turnoff(void) | ||
162 | { | ||
163 | /* Stop the timer */ | ||
164 | del_timer_sync(&timer); | ||
165 | wdt_change(WDT_DISABLE); | ||
166 | printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); | ||
167 | } | ||
168 | |||
169 | static void wdt_keepalive(void) | ||
170 | { | ||
171 | /* user land ping */ | ||
172 | next_heartbeat = jiffies + (timeout * HZ); | ||
173 | } | ||
174 | |||
175 | /* | ||
176 | * /dev/watchdog handling | ||
177 | */ | ||
178 | |||
179 | static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) | ||
180 | { | ||
181 | /* See if we got the magic character 'V' and reload the timer */ | ||
182 | if(count) { | ||
183 | if (!nowayout) { | ||
184 | size_t ofs; | ||
185 | |||
186 | /* note: just in case someone wrote the magic character | ||
187 | * five months ago... */ | ||
188 | wdt_expect_close = 0; | ||
189 | |||
190 | /* now scan */ | ||
191 | for (ofs = 0; ofs != count; ofs++) { | ||
192 | char c; | ||
193 | if (get_user(c, buf+ofs)) | ||
194 | return -EFAULT; | ||
195 | if (c == 'V') | ||
196 | wdt_expect_close = 42; | ||
197 | } | ||
198 | } | ||
199 | /* someone wrote to us, we should restart timer */ | ||
200 | wdt_keepalive(); | ||
201 | } | ||
202 | return count; | ||
203 | } | ||
204 | |||
205 | static int fop_open(struct inode * inode, struct file * file) | ||
206 | { | ||
207 | /* Just in case we're already talking to someone... */ | ||
208 | if(test_and_set_bit(0, &wdt_is_open)) | ||
209 | return -EBUSY; | ||
210 | /* Good, fire up the show */ | ||
211 | wdt_startup(); | ||
212 | return nonseekable_open(inode, file); | ||
213 | } | ||
214 | |||
215 | static int fop_close(struct inode * inode, struct file * file) | ||
216 | { | ||
217 | if(wdt_expect_close == 42) | ||
218 | wdt_turnoff(); | ||
219 | else { | ||
220 | /* wim: shouldn't there be a: del_timer(&timer); */ | ||
221 | printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); | ||
222 | } | ||
223 | clear_bit(0, &wdt_is_open); | ||
224 | wdt_expect_close = 0; | ||
225 | return 0; | ||
226 | } | ||
227 | |||
228 | static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) | ||
229 | { | ||
230 | void __user *argp = (void __user *)arg; | ||
231 | int __user *p = argp; | ||
232 | static struct watchdog_info ident = | ||
233 | { | ||
234 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
235 | .firmware_version = 1, | ||
236 | .identity = "ALiM7101", | ||
237 | }; | ||
238 | |||
239 | switch(cmd) | ||
240 | { | ||
241 | case WDIOC_GETSUPPORT: | ||
242 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
243 | case WDIOC_GETSTATUS: | ||
244 | case WDIOC_GETBOOTSTATUS: | ||
245 | return put_user(0, p); | ||
246 | case WDIOC_KEEPALIVE: | ||
247 | wdt_keepalive(); | ||
248 | return 0; | ||
249 | case WDIOC_SETOPTIONS: | ||
250 | { | ||
251 | int new_options, retval = -EINVAL; | ||
252 | |||
253 | if(get_user(new_options, p)) | ||
254 | return -EFAULT; | ||
255 | |||
256 | if(new_options & WDIOS_DISABLECARD) { | ||
257 | wdt_turnoff(); | ||
258 | retval = 0; | ||
259 | } | ||
260 | |||
261 | if(new_options & WDIOS_ENABLECARD) { | ||
262 | wdt_startup(); | ||
263 | retval = 0; | ||
264 | } | ||
265 | |||
266 | return retval; | ||
267 | } | ||
268 | case WDIOC_SETTIMEOUT: | ||
269 | { | ||
270 | int new_timeout; | ||
271 | |||
272 | if(get_user(new_timeout, p)) | ||
273 | return -EFAULT; | ||
274 | |||
275 | if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ | ||
276 | return -EINVAL; | ||
277 | |||
278 | timeout = new_timeout; | ||
279 | wdt_keepalive(); | ||
280 | /* Fall through */ | ||
281 | } | ||
282 | case WDIOC_GETTIMEOUT: | ||
283 | return put_user(timeout, p); | ||
284 | default: | ||
285 | return -ENOIOCTLCMD; | ||
286 | } | ||
287 | } | ||
288 | |||
289 | static struct file_operations wdt_fops = { | ||
290 | .owner= THIS_MODULE, | ||
291 | .llseek= no_llseek, | ||
292 | .write= fop_write, | ||
293 | .open= fop_open, | ||
294 | .release= fop_close, | ||
295 | .ioctl= fop_ioctl, | ||
296 | }; | ||
297 | |||
298 | static struct miscdevice wdt_miscdev = { | ||
299 | .minor=WATCHDOG_MINOR, | ||
300 | .name="watchdog", | ||
301 | .fops=&wdt_fops, | ||
302 | }; | ||
303 | |||
304 | /* | ||
305 | * Notifier for system down | ||
306 | */ | ||
307 | |||
308 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
309 | { | ||
310 | if (code==SYS_DOWN || code==SYS_HALT) | ||
311 | wdt_turnoff(); | ||
312 | |||
313 | if (code==SYS_RESTART) { | ||
314 | /* | ||
315 | * Cobalt devices have no way of rebooting themselves other than | ||
316 | * getting the watchdog to pull reset, so we restart the watchdog on | ||
317 | * reboot with no heartbeat | ||
318 | */ | ||
319 | wdt_change(WDT_ENABLE); | ||
320 | printk(KERN_INFO PFX "Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second.\n"); | ||
321 | } | ||
322 | return NOTIFY_DONE; | ||
323 | } | ||
324 | |||
325 | /* | ||
326 | * The WDT needs to learn about soft shutdowns in order to | ||
327 | * turn the timebomb registers off. | ||
328 | */ | ||
329 | |||
330 | static struct notifier_block wdt_notifier= | ||
331 | { | ||
332 | .notifier_call = wdt_notify_sys, | ||
333 | }; | ||
334 | |||
335 | static void __exit alim7101_wdt_unload(void) | ||
336 | { | ||
337 | wdt_turnoff(); | ||
338 | /* Deregister */ | ||
339 | misc_deregister(&wdt_miscdev); | ||
340 | unregister_reboot_notifier(&wdt_notifier); | ||
341 | } | ||
342 | |||
343 | static int __init alim7101_wdt_init(void) | ||
344 | { | ||
345 | int rc = -EBUSY; | ||
346 | struct pci_dev *ali1543_south; | ||
347 | char tmp; | ||
348 | |||
349 | printk(KERN_INFO PFX "Steve Hill <steve@navaho.co.uk>.\n"); | ||
350 | alim7101_pmu = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101,NULL); | ||
351 | if (!alim7101_pmu) { | ||
352 | printk(KERN_INFO PFX "ALi M7101 PMU not present - WDT not set\n"); | ||
353 | return -EBUSY; | ||
354 | } | ||
355 | |||
356 | /* Set the WDT in the PMU to 1 second */ | ||
357 | pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02); | ||
358 | |||
359 | ali1543_south = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, NULL); | ||
360 | if (!ali1543_south) { | ||
361 | printk(KERN_INFO PFX "ALi 1543 South-Bridge not present - WDT not set\n"); | ||
362 | return -EBUSY; | ||
363 | } | ||
364 | pci_read_config_byte(ali1543_south, 0x5e, &tmp); | ||
365 | if ((tmp & 0x1e) == 0x00) { | ||
366 | if (!use_gpio) { | ||
367 | printk(KERN_INFO PFX "Detected old alim7101 revision 'a1d'. If this is a cobalt board, set the 'use_gpio' module parameter.\n"); | ||
368 | return -EBUSY; | ||
369 | } | ||
370 | nowayout = 1; | ||
371 | } else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) { | ||
372 | printk(KERN_INFO PFX "ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n"); | ||
373 | return -EBUSY; | ||
374 | } | ||
375 | |||
376 | if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */ | ||
377 | { | ||
378 | timeout = WATCHDOG_TIMEOUT; | ||
379 | printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", | ||
380 | timeout); | ||
381 | } | ||
382 | |||
383 | init_timer(&timer); | ||
384 | timer.function = wdt_timer_ping; | ||
385 | timer.data = 1; | ||
386 | |||
387 | rc = misc_register(&wdt_miscdev); | ||
388 | if (rc) { | ||
389 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
390 | wdt_miscdev.minor, rc); | ||
391 | goto err_out; | ||
392 | } | ||
393 | |||
394 | rc = register_reboot_notifier(&wdt_notifier); | ||
395 | if (rc) { | ||
396 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
397 | rc); | ||
398 | goto err_out_miscdev; | ||
399 | } | ||
400 | |||
401 | if (nowayout) { | ||
402 | __module_get(THIS_MODULE); | ||
403 | } | ||
404 | |||
405 | printk(KERN_INFO PFX "WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n", | ||
406 | timeout, nowayout); | ||
407 | return 0; | ||
408 | |||
409 | err_out_miscdev: | ||
410 | misc_deregister(&wdt_miscdev); | ||
411 | err_out: | ||
412 | return rc; | ||
413 | } | ||
414 | |||
415 | module_init(alim7101_wdt_init); | ||
416 | module_exit(alim7101_wdt_unload); | ||
417 | |||
418 | MODULE_AUTHOR("Steve Hill"); | ||
419 | MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver"); | ||
420 | MODULE_LICENSE("GPL"); | ||
421 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/cpu5wdt.c b/drivers/char/watchdog/cpu5wdt.c new file mode 100644 index 000000000000..2865dac0a813 --- /dev/null +++ b/drivers/char/watchdog/cpu5wdt.c | |||
@@ -0,0 +1,303 @@ | |||
1 | /* | ||
2 | * sma cpu5 watchdog driver | ||
3 | * | ||
4 | * Copyright (C) 2003 Heiko Ronsdorf <hero@ihg.uni-duisburg.de> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #include <linux/module.h> | ||
23 | #include <linux/moduleparam.h> | ||
24 | #include <linux/types.h> | ||
25 | #include <linux/errno.h> | ||
26 | #include <linux/miscdevice.h> | ||
27 | #include <linux/fs.h> | ||
28 | #include <linux/init.h> | ||
29 | #include <linux/ioport.h> | ||
30 | #include <linux/timer.h> | ||
31 | #include <asm/io.h> | ||
32 | #include <asm/uaccess.h> | ||
33 | |||
34 | #include <linux/watchdog.h> | ||
35 | |||
36 | /* adjustable parameters */ | ||
37 | |||
38 | static int verbose = 0; | ||
39 | static int port = 0x91; | ||
40 | static int ticks = 10000; | ||
41 | |||
42 | #define PFX "cpu5wdt: " | ||
43 | |||
44 | #define CPU5WDT_EXTENT 0x0A | ||
45 | |||
46 | #define CPU5WDT_STATUS_REG 0x00 | ||
47 | #define CPU5WDT_TIME_A_REG 0x02 | ||
48 | #define CPU5WDT_TIME_B_REG 0x03 | ||
49 | #define CPU5WDT_MODE_REG 0x04 | ||
50 | #define CPU5WDT_TRIGGER_REG 0x07 | ||
51 | #define CPU5WDT_ENABLE_REG 0x08 | ||
52 | #define CPU5WDT_RESET_REG 0x09 | ||
53 | |||
54 | #define CPU5WDT_INTERVAL (HZ/10+1) | ||
55 | |||
56 | /* some device data */ | ||
57 | |||
58 | static struct { | ||
59 | struct semaphore stop; | ||
60 | volatile int running; | ||
61 | struct timer_list timer; | ||
62 | volatile int queue; | ||
63 | int default_ticks; | ||
64 | unsigned long inuse; | ||
65 | } cpu5wdt_device; | ||
66 | |||
67 | /* generic helper functions */ | ||
68 | |||
69 | static void cpu5wdt_trigger(unsigned long unused) | ||
70 | { | ||
71 | if ( verbose > 2 ) | ||
72 | printk(KERN_DEBUG PFX "trigger at %i ticks\n", ticks); | ||
73 | |||
74 | if( cpu5wdt_device.running ) | ||
75 | ticks--; | ||
76 | |||
77 | /* keep watchdog alive */ | ||
78 | outb(1, port + CPU5WDT_TRIGGER_REG); | ||
79 | |||
80 | /* requeue?? */ | ||
81 | if( cpu5wdt_device.queue && ticks ) { | ||
82 | cpu5wdt_device.timer.expires = jiffies + CPU5WDT_INTERVAL; | ||
83 | add_timer(&cpu5wdt_device.timer); | ||
84 | } | ||
85 | else { | ||
86 | /* ticks doesn't matter anyway */ | ||
87 | up(&cpu5wdt_device.stop); | ||
88 | } | ||
89 | |||
90 | } | ||
91 | |||
92 | static void cpu5wdt_reset(void) | ||
93 | { | ||
94 | ticks = cpu5wdt_device.default_ticks; | ||
95 | |||
96 | if ( verbose ) | ||
97 | printk(KERN_DEBUG PFX "reset (%i ticks)\n", (int) ticks); | ||
98 | |||
99 | } | ||
100 | |||
101 | static void cpu5wdt_start(void) | ||
102 | { | ||
103 | if ( !cpu5wdt_device.queue ) { | ||
104 | cpu5wdt_device.queue = 1; | ||
105 | outb(0, port + CPU5WDT_TIME_A_REG); | ||
106 | outb(0, port + CPU5WDT_TIME_B_REG); | ||
107 | outb(1, port + CPU5WDT_MODE_REG); | ||
108 | outb(0, port + CPU5WDT_RESET_REG); | ||
109 | outb(0, port + CPU5WDT_ENABLE_REG); | ||
110 | cpu5wdt_device.timer.expires = jiffies + CPU5WDT_INTERVAL; | ||
111 | add_timer(&cpu5wdt_device.timer); | ||
112 | } | ||
113 | /* if process dies, counter is not decremented */ | ||
114 | cpu5wdt_device.running++; | ||
115 | } | ||
116 | |||
117 | static int cpu5wdt_stop(void) | ||
118 | { | ||
119 | if ( cpu5wdt_device.running ) | ||
120 | cpu5wdt_device.running = 0; | ||
121 | |||
122 | ticks = cpu5wdt_device.default_ticks; | ||
123 | |||
124 | if ( verbose ) | ||
125 | printk(KERN_CRIT PFX "stop not possible\n"); | ||
126 | |||
127 | return -EIO; | ||
128 | } | ||
129 | |||
130 | /* filesystem operations */ | ||
131 | |||
132 | static int cpu5wdt_open(struct inode *inode, struct file *file) | ||
133 | { | ||
134 | if ( test_and_set_bit(0, &cpu5wdt_device.inuse) ) | ||
135 | return -EBUSY; | ||
136 | |||
137 | return nonseekable_open(inode, file); | ||
138 | } | ||
139 | |||
140 | static int cpu5wdt_release(struct inode *inode, struct file *file) | ||
141 | { | ||
142 | clear_bit(0, &cpu5wdt_device.inuse); | ||
143 | return 0; | ||
144 | } | ||
145 | |||
146 | static int cpu5wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) | ||
147 | { | ||
148 | void __user *argp = (void __user *)arg; | ||
149 | unsigned int value; | ||
150 | static struct watchdog_info ident = | ||
151 | { | ||
152 | .options = WDIOF_CARDRESET, | ||
153 | .identity = "CPU5 WDT", | ||
154 | }; | ||
155 | |||
156 | switch(cmd) { | ||
157 | case WDIOC_KEEPALIVE: | ||
158 | cpu5wdt_reset(); | ||
159 | break; | ||
160 | case WDIOC_GETSTATUS: | ||
161 | value = inb(port + CPU5WDT_STATUS_REG); | ||
162 | value = (value >> 2) & 1; | ||
163 | if ( copy_to_user(argp, &value, sizeof(int)) ) | ||
164 | return -EFAULT; | ||
165 | break; | ||
166 | case WDIOC_GETSUPPORT: | ||
167 | if ( copy_to_user(argp, &ident, sizeof(ident)) ) | ||
168 | return -EFAULT; | ||
169 | break; | ||
170 | case WDIOC_SETOPTIONS: | ||
171 | if ( copy_from_user(&value, argp, sizeof(int)) ) | ||
172 | return -EFAULT; | ||
173 | switch(value) { | ||
174 | case WDIOS_ENABLECARD: | ||
175 | cpu5wdt_start(); | ||
176 | break; | ||
177 | case WDIOS_DISABLECARD: | ||
178 | return cpu5wdt_stop(); | ||
179 | default: | ||
180 | return -EINVAL; | ||
181 | } | ||
182 | break; | ||
183 | default: | ||
184 | return -ENOIOCTLCMD; | ||
185 | } | ||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | static ssize_t cpu5wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
190 | { | ||
191 | if ( !count ) | ||
192 | return -EIO; | ||
193 | |||
194 | cpu5wdt_reset(); | ||
195 | |||
196 | return count; | ||
197 | } | ||
198 | |||
199 | static struct file_operations cpu5wdt_fops = { | ||
200 | .owner = THIS_MODULE, | ||
201 | .llseek = no_llseek, | ||
202 | .ioctl = cpu5wdt_ioctl, | ||
203 | .open = cpu5wdt_open, | ||
204 | .write = cpu5wdt_write, | ||
205 | .release = cpu5wdt_release, | ||
206 | }; | ||
207 | |||
208 | static struct miscdevice cpu5wdt_misc = { | ||
209 | .minor = WATCHDOG_MINOR, | ||
210 | .name = "watchdog", | ||
211 | .fops = &cpu5wdt_fops, | ||
212 | }; | ||
213 | |||
214 | /* init/exit function */ | ||
215 | |||
216 | static int __devinit cpu5wdt_init(void) | ||
217 | { | ||
218 | unsigned int val; | ||
219 | int err; | ||
220 | |||
221 | if ( verbose ) | ||
222 | printk(KERN_DEBUG PFX "port=0x%x, verbose=%i\n", port, verbose); | ||
223 | |||
224 | if ( (err = misc_register(&cpu5wdt_misc)) < 0 ) { | ||
225 | printk(KERN_ERR PFX "misc_register failed\n"); | ||
226 | goto no_misc; | ||
227 | } | ||
228 | |||
229 | if ( !request_region(port, CPU5WDT_EXTENT, PFX) ) { | ||
230 | printk(KERN_ERR PFX "request_region failed\n"); | ||
231 | err = -EBUSY; | ||
232 | goto no_port; | ||
233 | } | ||
234 | |||
235 | /* watchdog reboot? */ | ||
236 | val = inb(port + CPU5WDT_STATUS_REG); | ||
237 | val = (val >> 2) & 1; | ||
238 | if ( !val ) | ||
239 | printk(KERN_INFO PFX "sorry, was my fault\n"); | ||
240 | |||
241 | init_MUTEX_LOCKED(&cpu5wdt_device.stop); | ||
242 | cpu5wdt_device.queue = 0; | ||
243 | |||
244 | clear_bit(0, &cpu5wdt_device.inuse); | ||
245 | |||
246 | init_timer(&cpu5wdt_device.timer); | ||
247 | cpu5wdt_device.timer.function = cpu5wdt_trigger; | ||
248 | cpu5wdt_device.timer.data = 0; | ||
249 | |||
250 | cpu5wdt_device.default_ticks = ticks; | ||
251 | |||
252 | printk(KERN_INFO PFX "init success\n"); | ||
253 | |||
254 | return 0; | ||
255 | |||
256 | no_port: | ||
257 | misc_deregister(&cpu5wdt_misc); | ||
258 | no_misc: | ||
259 | return err; | ||
260 | } | ||
261 | |||
262 | static int __devinit cpu5wdt_init_module(void) | ||
263 | { | ||
264 | return cpu5wdt_init(); | ||
265 | } | ||
266 | |||
267 | static void __devexit cpu5wdt_exit(void) | ||
268 | { | ||
269 | if ( cpu5wdt_device.queue ) { | ||
270 | cpu5wdt_device.queue = 0; | ||
271 | down(&cpu5wdt_device.stop); | ||
272 | } | ||
273 | |||
274 | misc_deregister(&cpu5wdt_misc); | ||
275 | |||
276 | release_region(port, CPU5WDT_EXTENT); | ||
277 | |||
278 | } | ||
279 | |||
280 | static void __devexit cpu5wdt_exit_module(void) | ||
281 | { | ||
282 | cpu5wdt_exit(); | ||
283 | } | ||
284 | |||
285 | /* module entry points */ | ||
286 | |||
287 | module_init(cpu5wdt_init_module); | ||
288 | module_exit(cpu5wdt_exit_module); | ||
289 | |||
290 | MODULE_AUTHOR("Heiko Ronsdorf <hero@ihg.uni-duisburg.de>"); | ||
291 | MODULE_DESCRIPTION("sma cpu5 watchdog driver"); | ||
292 | MODULE_SUPPORTED_DEVICE("sma cpu5 watchdog"); | ||
293 | MODULE_LICENSE("GPL"); | ||
294 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
295 | |||
296 | module_param(port, int, 0); | ||
297 | MODULE_PARM_DESC(port, "base address of watchdog card, default is 0x91"); | ||
298 | |||
299 | module_param(verbose, int, 0); | ||
300 | MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)"); | ||
301 | |||
302 | module_param(ticks, int, 0); | ||
303 | MODULE_PARM_DESC(ticks, "count down ticks, default is 10000"); | ||
diff --git a/drivers/char/watchdog/eurotechwdt.c b/drivers/char/watchdog/eurotechwdt.c new file mode 100644 index 000000000000..d10e554a14d6 --- /dev/null +++ b/drivers/char/watchdog/eurotechwdt.c | |||
@@ -0,0 +1,474 @@ | |||
1 | /* | ||
2 | * Eurotech CPU-1220/1410 on board WDT driver | ||
3 | * | ||
4 | * (c) Copyright 2001 Ascensit <support@ascensit.com> | ||
5 | * (c) Copyright 2001 Rodolfo Giometti <giometti@ascensit.com> | ||
6 | * (c) Copyright 2002 Rob Radez <rob@osinvestor.com> | ||
7 | * | ||
8 | * Based on wdt.c. | ||
9 | * Original copyright messages: | ||
10 | * | ||
11 | * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
12 | * http://www.redhat.com | ||
13 | * | ||
14 | * This program is free software; you can redistribute it and/or | ||
15 | * modify it under the terms of the GNU General Public License | ||
16 | * as published by the Free Software Foundation; either version | ||
17 | * 2 of the License, or (at your option) any later version. | ||
18 | * | ||
19 | * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
20 | * warranty for any of this software. This material is provided | ||
21 | * "AS-IS" and at no charge. | ||
22 | * | ||
23 | * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>* | ||
24 | */ | ||
25 | |||
26 | /* Changelog: | ||
27 | * | ||
28 | * 2002/04/25 - Rob Radez | ||
29 | * clean up #includes | ||
30 | * clean up locking | ||
31 | * make __setup param unique | ||
32 | * proper options in watchdog_info | ||
33 | * add WDIOC_GETSTATUS and WDIOC_SETOPTIONS ioctls | ||
34 | * add expect_close support | ||
35 | * | ||
36 | * 2001 - Rodolfo Giometti | ||
37 | * Initial release | ||
38 | * | ||
39 | * 2002.05.30 - Joel Becker <joel.becker@oracle.com> | ||
40 | * Added Matt Domsch's nowayout module option. | ||
41 | */ | ||
42 | |||
43 | #include <linux/config.h> | ||
44 | #include <linux/interrupt.h> | ||
45 | #include <linux/module.h> | ||
46 | #include <linux/moduleparam.h> | ||
47 | #include <linux/types.h> | ||
48 | #include <linux/miscdevice.h> | ||
49 | #include <linux/watchdog.h> | ||
50 | #include <linux/fs.h> | ||
51 | #include <linux/ioport.h> | ||
52 | #include <linux/notifier.h> | ||
53 | #include <linux/reboot.h> | ||
54 | #include <linux/init.h> | ||
55 | |||
56 | #include <asm/io.h> | ||
57 | #include <asm/uaccess.h> | ||
58 | #include <asm/system.h> | ||
59 | |||
60 | static unsigned long eurwdt_is_open; | ||
61 | static int eurwdt_timeout; | ||
62 | static char eur_expect_close; | ||
63 | |||
64 | /* | ||
65 | * You must set these - there is no sane way to probe for this board. | ||
66 | * You can use eurwdt=x,y to set these now. | ||
67 | */ | ||
68 | |||
69 | static int io = 0x3f0; | ||
70 | static int irq = 10; | ||
71 | static char *ev = "int"; | ||
72 | |||
73 | #define WDT_TIMEOUT 60 /* 1 minute */ | ||
74 | |||
75 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
76 | static int nowayout = 1; | ||
77 | #else | ||
78 | static int nowayout = 0; | ||
79 | #endif | ||
80 | |||
81 | module_param(nowayout, int, 0); | ||
82 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
83 | |||
84 | /* | ||
85 | * Some symbolic names | ||
86 | */ | ||
87 | |||
88 | #define WDT_CTRL_REG 0x30 | ||
89 | #define WDT_OUTPIN_CFG 0xe2 | ||
90 | #define WDT_EVENT_INT 0x00 | ||
91 | #define WDT_EVENT_REBOOT 0x08 | ||
92 | #define WDT_UNIT_SEL 0xf1 | ||
93 | #define WDT_UNIT_SECS 0x80 | ||
94 | #define WDT_TIMEOUT_VAL 0xf2 | ||
95 | #define WDT_TIMER_CFG 0xf3 | ||
96 | |||
97 | |||
98 | module_param(io, int, 0); | ||
99 | MODULE_PARM_DESC(io, "Eurotech WDT io port (default=0x3f0)"); | ||
100 | module_param(irq, int, 0); | ||
101 | MODULE_PARM_DESC(irq, "Eurotech WDT irq (default=10)"); | ||
102 | module_param(ev, charp, 0); | ||
103 | MODULE_PARM_DESC(ev, "Eurotech WDT event type (default is `int')"); | ||
104 | |||
105 | |||
106 | /* | ||
107 | * Programming support | ||
108 | */ | ||
109 | |||
110 | static inline void eurwdt_write_reg(u8 index, u8 data) | ||
111 | { | ||
112 | outb(index, io); | ||
113 | outb(data, io+1); | ||
114 | } | ||
115 | |||
116 | static inline void eurwdt_lock_chip(void) | ||
117 | { | ||
118 | outb(0xaa, io); | ||
119 | } | ||
120 | |||
121 | static inline void eurwdt_unlock_chip(void) | ||
122 | { | ||
123 | outb(0x55, io); | ||
124 | eurwdt_write_reg(0x07, 0x08); /* set the logical device */ | ||
125 | } | ||
126 | |||
127 | static inline void eurwdt_set_timeout(int timeout) | ||
128 | { | ||
129 | eurwdt_write_reg(WDT_TIMEOUT_VAL, (u8) timeout); | ||
130 | } | ||
131 | |||
132 | static inline void eurwdt_disable_timer(void) | ||
133 | { | ||
134 | eurwdt_set_timeout(0); | ||
135 | } | ||
136 | |||
137 | static void eurwdt_activate_timer(void) | ||
138 | { | ||
139 | eurwdt_disable_timer(); | ||
140 | eurwdt_write_reg(WDT_CTRL_REG, 0x01); /* activate the WDT */ | ||
141 | eurwdt_write_reg(WDT_OUTPIN_CFG, !strcmp("int", ev) ? WDT_EVENT_INT : WDT_EVENT_REBOOT); | ||
142 | |||
143 | /* Setting interrupt line */ | ||
144 | if (irq == 2 || irq > 15 || irq < 0) { | ||
145 | printk(KERN_ERR ": invalid irq number\n"); | ||
146 | irq = 0; /* if invalid we disable interrupt */ | ||
147 | } | ||
148 | if (irq == 0) | ||
149 | printk(KERN_INFO ": interrupt disabled\n"); | ||
150 | |||
151 | eurwdt_write_reg(WDT_TIMER_CFG, irq<<4); | ||
152 | |||
153 | eurwdt_write_reg(WDT_UNIT_SEL, WDT_UNIT_SECS); /* we use seconds */ | ||
154 | eurwdt_set_timeout(0); /* the default timeout */ | ||
155 | } | ||
156 | |||
157 | |||
158 | /* | ||
159 | * Kernel methods. | ||
160 | */ | ||
161 | |||
162 | static irqreturn_t eurwdt_interrupt(int irq, void *dev_id, struct pt_regs *regs) | ||
163 | { | ||
164 | printk(KERN_CRIT "timeout WDT timeout\n"); | ||
165 | |||
166 | #ifdef ONLY_TESTING | ||
167 | printk(KERN_CRIT "Would Reboot.\n"); | ||
168 | #else | ||
169 | printk(KERN_CRIT "Initiating system reboot.\n"); | ||
170 | machine_restart(NULL); | ||
171 | #endif | ||
172 | return IRQ_HANDLED; | ||
173 | } | ||
174 | |||
175 | |||
176 | /** | ||
177 | * eurwdt_ping: | ||
178 | * | ||
179 | * Reload counter one with the watchdog timeout. | ||
180 | */ | ||
181 | |||
182 | static void eurwdt_ping(void) | ||
183 | { | ||
184 | /* Write the watchdog default value */ | ||
185 | eurwdt_set_timeout(eurwdt_timeout); | ||
186 | } | ||
187 | |||
188 | /** | ||
189 | * eurwdt_write: | ||
190 | * @file: file handle to the watchdog | ||
191 | * @buf: buffer to write (unused as data does not matter here | ||
192 | * @count: count of bytes | ||
193 | * @ppos: pointer to the position to write. No seeks allowed | ||
194 | * | ||
195 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
196 | * write of data will do, as we we don't define content meaning. | ||
197 | */ | ||
198 | |||
199 | static ssize_t eurwdt_write(struct file *file, const char __user *buf, | ||
200 | size_t count, loff_t *ppos) | ||
201 | { | ||
202 | if (count) { | ||
203 | if (!nowayout) { | ||
204 | size_t i; | ||
205 | |||
206 | eur_expect_close = 0; | ||
207 | |||
208 | for (i = 0; i != count; i++) { | ||
209 | char c; | ||
210 | if(get_user(c, buf+i)) | ||
211 | return -EFAULT; | ||
212 | if (c == 'V') | ||
213 | eur_expect_close = 42; | ||
214 | } | ||
215 | } | ||
216 | eurwdt_ping(); /* the default timeout */ | ||
217 | } | ||
218 | |||
219 | return count; | ||
220 | } | ||
221 | |||
222 | /** | ||
223 | * eurwdt_ioctl: | ||
224 | * @inode: inode of the device | ||
225 | * @file: file handle to the device | ||
226 | * @cmd: watchdog command | ||
227 | * @arg: argument pointer | ||
228 | * | ||
229 | * The watchdog API defines a common set of functions for all watchdogs | ||
230 | * according to their available features. | ||
231 | */ | ||
232 | |||
233 | static int eurwdt_ioctl(struct inode *inode, struct file *file, | ||
234 | unsigned int cmd, unsigned long arg) | ||
235 | { | ||
236 | void __user *argp = (void __user *)arg; | ||
237 | int __user *p = argp; | ||
238 | static struct watchdog_info ident = { | ||
239 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
240 | .firmware_version = 1, | ||
241 | .identity = "WDT Eurotech CPU-1220/1410", | ||
242 | }; | ||
243 | |||
244 | int time; | ||
245 | int options, retval = -EINVAL; | ||
246 | |||
247 | switch(cmd) { | ||
248 | default: | ||
249 | return -ENOIOCTLCMD; | ||
250 | |||
251 | case WDIOC_GETSUPPORT: | ||
252 | return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; | ||
253 | |||
254 | case WDIOC_GETSTATUS: | ||
255 | case WDIOC_GETBOOTSTATUS: | ||
256 | return put_user(0, p); | ||
257 | |||
258 | case WDIOC_KEEPALIVE: | ||
259 | eurwdt_ping(); | ||
260 | return 0; | ||
261 | |||
262 | case WDIOC_SETTIMEOUT: | ||
263 | if (copy_from_user(&time, p, sizeof(int))) | ||
264 | return -EFAULT; | ||
265 | |||
266 | /* Sanity check */ | ||
267 | if (time < 0 || time > 255) | ||
268 | return -EINVAL; | ||
269 | |||
270 | eurwdt_timeout = time; | ||
271 | eurwdt_set_timeout(time); | ||
272 | /* Fall */ | ||
273 | |||
274 | case WDIOC_GETTIMEOUT: | ||
275 | return put_user(eurwdt_timeout, p); | ||
276 | |||
277 | case WDIOC_SETOPTIONS: | ||
278 | if (get_user(options, p)) | ||
279 | return -EFAULT; | ||
280 | if (options & WDIOS_DISABLECARD) { | ||
281 | eurwdt_disable_timer(); | ||
282 | retval = 0; | ||
283 | } | ||
284 | if (options & WDIOS_ENABLECARD) { | ||
285 | eurwdt_activate_timer(); | ||
286 | eurwdt_ping(); | ||
287 | retval = 0; | ||
288 | } | ||
289 | return retval; | ||
290 | } | ||
291 | } | ||
292 | |||
293 | /** | ||
294 | * eurwdt_open: | ||
295 | * @inode: inode of device | ||
296 | * @file: file handle to device | ||
297 | * | ||
298 | * The misc device has been opened. The watchdog device is single | ||
299 | * open and on opening we load the counter. | ||
300 | */ | ||
301 | |||
302 | static int eurwdt_open(struct inode *inode, struct file *file) | ||
303 | { | ||
304 | if (test_and_set_bit(0, &eurwdt_is_open)) | ||
305 | return -EBUSY; | ||
306 | eurwdt_timeout = WDT_TIMEOUT; /* initial timeout */ | ||
307 | /* Activate the WDT */ | ||
308 | eurwdt_activate_timer(); | ||
309 | return nonseekable_open(inode, file); | ||
310 | } | ||
311 | |||
312 | /** | ||
313 | * eurwdt_release: | ||
314 | * @inode: inode to board | ||
315 | * @file: file handle to board | ||
316 | * | ||
317 | * The watchdog has a configurable API. There is a religious dispute | ||
318 | * between people who want their watchdog to be able to shut down and | ||
319 | * those who want to be sure if the watchdog manager dies the machine | ||
320 | * reboots. In the former case we disable the counters, in the latter | ||
321 | * case you have to open it again very soon. | ||
322 | */ | ||
323 | |||
324 | static int eurwdt_release(struct inode *inode, struct file *file) | ||
325 | { | ||
326 | if (eur_expect_close == 42) { | ||
327 | eurwdt_disable_timer(); | ||
328 | } else { | ||
329 | printk(KERN_CRIT "eurwdt: Unexpected close, not stopping watchdog!\n"); | ||
330 | eurwdt_ping(); | ||
331 | } | ||
332 | clear_bit(0, &eurwdt_is_open); | ||
333 | eur_expect_close = 0; | ||
334 | return 0; | ||
335 | } | ||
336 | |||
337 | /** | ||
338 | * eurwdt_notify_sys: | ||
339 | * @this: our notifier block | ||
340 | * @code: the event being reported | ||
341 | * @unused: unused | ||
342 | * | ||
343 | * Our notifier is called on system shutdowns. We want to turn the card | ||
344 | * off at reboot otherwise the machine will reboot again during memory | ||
345 | * test or worse yet during the following fsck. This would suck, in fact | ||
346 | * trust me - if it happens it does suck. | ||
347 | */ | ||
348 | |||
349 | static int eurwdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
350 | void *unused) | ||
351 | { | ||
352 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
353 | /* Turn the card off */ | ||
354 | eurwdt_disable_timer(); | ||
355 | } | ||
356 | |||
357 | return NOTIFY_DONE; | ||
358 | } | ||
359 | |||
360 | /* | ||
361 | * Kernel Interfaces | ||
362 | */ | ||
363 | |||
364 | |||
365 | static struct file_operations eurwdt_fops = { | ||
366 | .owner = THIS_MODULE, | ||
367 | .llseek = no_llseek, | ||
368 | .write = eurwdt_write, | ||
369 | .ioctl = eurwdt_ioctl, | ||
370 | .open = eurwdt_open, | ||
371 | .release = eurwdt_release, | ||
372 | }; | ||
373 | |||
374 | static struct miscdevice eurwdt_miscdev = { | ||
375 | .minor = WATCHDOG_MINOR, | ||
376 | .name = "watchdog", | ||
377 | .fops = &eurwdt_fops, | ||
378 | }; | ||
379 | |||
380 | /* | ||
381 | * The WDT card needs to learn about soft shutdowns in order to | ||
382 | * turn the timebomb registers off. | ||
383 | */ | ||
384 | |||
385 | static struct notifier_block eurwdt_notifier = { | ||
386 | .notifier_call = eurwdt_notify_sys, | ||
387 | }; | ||
388 | |||
389 | /** | ||
390 | * cleanup_module: | ||
391 | * | ||
392 | * Unload the watchdog. You cannot do this with any file handles open. | ||
393 | * If your watchdog is set to continue ticking on close and you unload | ||
394 | * it, well it keeps ticking. We won't get the interrupt but the board | ||
395 | * will not touch PC memory so all is fine. You just have to load a new | ||
396 | * module in 60 seconds or reboot. | ||
397 | */ | ||
398 | |||
399 | static void __exit eurwdt_exit(void) | ||
400 | { | ||
401 | eurwdt_lock_chip(); | ||
402 | |||
403 | misc_deregister(&eurwdt_miscdev); | ||
404 | |||
405 | unregister_reboot_notifier(&eurwdt_notifier); | ||
406 | release_region(io, 2); | ||
407 | free_irq(irq, NULL); | ||
408 | } | ||
409 | |||
410 | /** | ||
411 | * eurwdt_init: | ||
412 | * | ||
413 | * Set up the WDT watchdog board. After grabbing the resources | ||
414 | * we require we need also to unlock the device. | ||
415 | * The open() function will actually kick the board off. | ||
416 | */ | ||
417 | |||
418 | static int __init eurwdt_init(void) | ||
419 | { | ||
420 | int ret; | ||
421 | |||
422 | ret = misc_register(&eurwdt_miscdev); | ||
423 | if (ret) { | ||
424 | printk(KERN_ERR "eurwdt: can't misc_register on minor=%d\n", | ||
425 | WATCHDOG_MINOR); | ||
426 | goto out; | ||
427 | } | ||
428 | |||
429 | ret = request_irq(irq, eurwdt_interrupt, SA_INTERRUPT, "eurwdt", NULL); | ||
430 | if(ret) { | ||
431 | printk(KERN_ERR "eurwdt: IRQ %d is not free.\n", irq); | ||
432 | goto outmisc; | ||
433 | } | ||
434 | |||
435 | if (!request_region(io, 2, "eurwdt")) { | ||
436 | printk(KERN_ERR "eurwdt: IO %X is not free.\n", io); | ||
437 | ret = -EBUSY; | ||
438 | goto outirq; | ||
439 | } | ||
440 | |||
441 | ret = register_reboot_notifier(&eurwdt_notifier); | ||
442 | if (ret) { | ||
443 | printk(KERN_ERR "eurwdt: can't register reboot notifier (err=%d)\n", ret); | ||
444 | goto outreg; | ||
445 | } | ||
446 | |||
447 | eurwdt_unlock_chip(); | ||
448 | |||
449 | ret = 0; | ||
450 | printk(KERN_INFO "Eurotech WDT driver 0.01 at %X (Interrupt %d)" | ||
451 | " - timeout event: %s\n", | ||
452 | io, irq, (!strcmp("int", ev) ? "int" : "reboot")); | ||
453 | |||
454 | out: | ||
455 | return ret; | ||
456 | |||
457 | outreg: | ||
458 | release_region(io, 2); | ||
459 | |||
460 | outirq: | ||
461 | free_irq(irq, NULL); | ||
462 | |||
463 | outmisc: | ||
464 | misc_deregister(&eurwdt_miscdev); | ||
465 | goto out; | ||
466 | } | ||
467 | |||
468 | module_init(eurwdt_init); | ||
469 | module_exit(eurwdt_exit); | ||
470 | |||
471 | MODULE_AUTHOR("Rodolfo Giometti"); | ||
472 | MODULE_DESCRIPTION("Driver for Eurotech CPU-1220/1410 on board watchdog"); | ||
473 | MODULE_LICENSE("GPL"); | ||
474 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/i8xx_tco.c b/drivers/char/watchdog/i8xx_tco.c new file mode 100644 index 000000000000..c337978dc966 --- /dev/null +++ b/drivers/char/watchdog/i8xx_tco.c | |||
@@ -0,0 +1,535 @@ | |||
1 | /* | ||
2 | * i8xx_tco 0.07: TCO timer driver for i8xx chipsets | ||
3 | * | ||
4 | * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved. | ||
5 | * http://www.kernelconcepts.de | ||
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 kernel concepts nor Nils Faerber admit liability nor provide | ||
13 | * warranty for any of this software. This material is provided | ||
14 | * "AS-IS" and at no charge. | ||
15 | * | ||
16 | * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de> | ||
17 | * developed for | ||
18 | * Jentro AG, Haar/Munich (Germany) | ||
19 | * | ||
20 | * TCO timer driver for i8xx chipsets | ||
21 | * based on softdog.c by Alan Cox <alan@redhat.com> | ||
22 | * | ||
23 | * The TCO timer is implemented in the following I/O controller hubs: | ||
24 | * (See the intel documentation on http://developer.intel.com.) | ||
25 | * 82801AA (ICH) : document number 290655-003, 290677-014, | ||
26 | * 82801AB (ICHO) : document number 290655-003, 290677-014, | ||
27 | * 82801BA (ICH2) : document number 290687-002, 298242-027, | ||
28 | * 82801BAM (ICH2-M) : document number 290687-002, 298242-027, | ||
29 | * 82801CA (ICH3-S) : document number 290733-003, 290739-013, | ||
30 | * 82801CAM (ICH3-M) : document number 290716-001, 290718-007, | ||
31 | * 82801DB (ICH4) : document number 290744-001, 290745-020, | ||
32 | * 82801DBM (ICH4-M) : document number 252337-001, 252663-005, | ||
33 | * 82801E (C-ICH) : document number 273599-001, 273645-002, | ||
34 | * 82801EB (ICH5) : document number 252516-001, 252517-003, | ||
35 | * 82801ER (ICH5R) : document number 252516-001, 252517-003, | ||
36 | * 82801FB (ICH6) : document number 301473-002, 301474-007, | ||
37 | * 82801FR (ICH6R) : document number 301473-002, 301474-007, | ||
38 | * 82801FBM (ICH6-M) : document number 301473-002, 301474-007, | ||
39 | * 82801FW (ICH6W) : document number 301473-001, 301474-007, | ||
40 | * 82801FRW (ICH6RW) : document number 301473-001, 301474-007 | ||
41 | * | ||
42 | * 20000710 Nils Faerber | ||
43 | * Initial Version 0.01 | ||
44 | * 20000728 Nils Faerber | ||
45 | * 0.02 Fix for SMI_EN->TCO_EN bit, some cleanups | ||
46 | * 20011214 Matt Domsch <Matt_Domsch@dell.com> | ||
47 | * 0.03 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT | ||
48 | * Didn't add timeout option as i810_margin already exists. | ||
49 | * 20020224 Joel Becker, Wim Van Sebroeck | ||
50 | * 0.04 Support for 82801CA(M) chipset, timer margin needs to be > 3, | ||
51 | * add support for WDIOC_SETTIMEOUT and WDIOC_GETTIMEOUT. | ||
52 | * 20020412 Rob Radez <rob@osinvestor.com>, Wim Van Sebroeck | ||
53 | * 0.05 Fix possible timer_alive race, add expect close support, | ||
54 | * clean up ioctls (WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS and | ||
55 | * WDIOC_SETOPTIONS), made i810tco_getdevice __init, | ||
56 | * removed boot_status, removed tco_timer_read, | ||
57 | * added support for 82801DB and 82801E chipset, | ||
58 | * added support for 82801EB and 8280ER chipset, | ||
59 | * general cleanup. | ||
60 | * 20030921 Wim Van Sebroeck <wim@iguana.be> | ||
61 | * 0.06 change i810_margin to heartbeat, use module_param, | ||
62 | * added notify system support, renamed module to i8xx_tco. | ||
63 | * 20050128 Wim Van Sebroeck <wim@iguana.be> | ||
64 | * 0.07 Added support for the ICH4-M, ICH6, ICH6R, ICH6-M, ICH6W and ICH6RW | ||
65 | * chipsets. Also added support for the "undocumented" ICH7 chipset. | ||
66 | */ | ||
67 | |||
68 | /* | ||
69 | * Includes, defines, variables, module parameters, ... | ||
70 | */ | ||
71 | |||
72 | #include <linux/module.h> | ||
73 | #include <linux/moduleparam.h> | ||
74 | #include <linux/types.h> | ||
75 | #include <linux/miscdevice.h> | ||
76 | #include <linux/watchdog.h> | ||
77 | #include <linux/notifier.h> | ||
78 | #include <linux/reboot.h> | ||
79 | #include <linux/init.h> | ||
80 | #include <linux/fs.h> | ||
81 | #include <linux/pci.h> | ||
82 | #include <linux/ioport.h> | ||
83 | |||
84 | #include <asm/uaccess.h> | ||
85 | #include <asm/io.h> | ||
86 | |||
87 | #include "i8xx_tco.h" | ||
88 | |||
89 | /* Module and version information */ | ||
90 | #define TCO_VERSION "0.07" | ||
91 | #define TCO_MODULE_NAME "i8xx TCO timer" | ||
92 | #define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION | ||
93 | #define PFX TCO_MODULE_NAME ": " | ||
94 | |||
95 | /* internal variables */ | ||
96 | static unsigned int ACPIBASE; | ||
97 | static spinlock_t tco_lock; /* Guards the hardware */ | ||
98 | static unsigned long timer_alive; | ||
99 | static char tco_expect_close; | ||
100 | static struct pci_dev *i8xx_tco_pci; | ||
101 | |||
102 | /* module parameters */ | ||
103 | #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (2<heartbeat<39) */ | ||
104 | static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ | ||
105 | module_param(heartbeat, int, 0); | ||
106 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
107 | |||
108 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
109 | static int nowayout = 1; | ||
110 | #else | ||
111 | static int nowayout = 0; | ||
112 | #endif | ||
113 | |||
114 | module_param(nowayout, int, 0); | ||
115 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
116 | |||
117 | /* | ||
118 | * Some TCO specific functions | ||
119 | */ | ||
120 | |||
121 | static inline unsigned char seconds_to_ticks(int seconds) | ||
122 | { | ||
123 | /* the internal timer is stored as ticks which decrement | ||
124 | * every 0.6 seconds */ | ||
125 | return (seconds * 10) / 6; | ||
126 | } | ||
127 | |||
128 | static int tco_timer_start (void) | ||
129 | { | ||
130 | unsigned char val; | ||
131 | |||
132 | spin_lock(&tco_lock); | ||
133 | val = inb (TCO1_CNT + 1); | ||
134 | val &= 0xf7; | ||
135 | outb (val, TCO1_CNT + 1); | ||
136 | val = inb (TCO1_CNT + 1); | ||
137 | spin_unlock(&tco_lock); | ||
138 | |||
139 | if (val & 0x08) | ||
140 | return -1; | ||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | static int tco_timer_stop (void) | ||
145 | { | ||
146 | unsigned char val; | ||
147 | |||
148 | spin_lock(&tco_lock); | ||
149 | val = inb (TCO1_CNT + 1); | ||
150 | val |= 0x08; | ||
151 | outb (val, TCO1_CNT + 1); | ||
152 | val = inb (TCO1_CNT + 1); | ||
153 | spin_unlock(&tco_lock); | ||
154 | |||
155 | if ((val & 0x08) == 0) | ||
156 | return -1; | ||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static int tco_timer_keepalive (void) | ||
161 | { | ||
162 | spin_lock(&tco_lock); | ||
163 | outb (0x01, TCO1_RLD); | ||
164 | spin_unlock(&tco_lock); | ||
165 | return 0; | ||
166 | } | ||
167 | |||
168 | static int tco_timer_set_heartbeat (int t) | ||
169 | { | ||
170 | unsigned char val; | ||
171 | unsigned char tmrval; | ||
172 | |||
173 | tmrval = seconds_to_ticks(t); | ||
174 | /* from the specs: */ | ||
175 | /* "Values of 0h-3h are ignored and should not be attempted" */ | ||
176 | if (tmrval > 0x3f || tmrval < 0x04) | ||
177 | return -EINVAL; | ||
178 | |||
179 | /* Write new heartbeat to watchdog */ | ||
180 | spin_lock(&tco_lock); | ||
181 | val = inb (TCO1_TMR); | ||
182 | val &= 0xc0; | ||
183 | val |= tmrval; | ||
184 | outb (val, TCO1_TMR); | ||
185 | val = inb (TCO1_TMR); | ||
186 | spin_unlock(&tco_lock); | ||
187 | |||
188 | if ((val & 0x3f) != tmrval) | ||
189 | return -EINVAL; | ||
190 | |||
191 | heartbeat = t; | ||
192 | return 0; | ||
193 | } | ||
194 | |||
195 | /* | ||
196 | * /dev/watchdog handling | ||
197 | */ | ||
198 | |||
199 | static int i8xx_tco_open (struct inode *inode, struct file *file) | ||
200 | { | ||
201 | /* /dev/watchdog can only be opened once */ | ||
202 | if (test_and_set_bit(0, &timer_alive)) | ||
203 | return -EBUSY; | ||
204 | |||
205 | /* | ||
206 | * Reload and activate timer | ||
207 | */ | ||
208 | tco_timer_keepalive (); | ||
209 | tco_timer_start (); | ||
210 | return nonseekable_open(inode, file); | ||
211 | } | ||
212 | |||
213 | static int i8xx_tco_release (struct inode *inode, struct file *file) | ||
214 | { | ||
215 | /* | ||
216 | * Shut off the timer. | ||
217 | */ | ||
218 | if (tco_expect_close == 42) { | ||
219 | tco_timer_stop (); | ||
220 | } else { | ||
221 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
222 | tco_timer_keepalive (); | ||
223 | } | ||
224 | clear_bit(0, &timer_alive); | ||
225 | tco_expect_close = 0; | ||
226 | return 0; | ||
227 | } | ||
228 | |||
229 | static ssize_t i8xx_tco_write (struct file *file, const char __user *data, | ||
230 | size_t len, loff_t * ppos) | ||
231 | { | ||
232 | /* See if we got the magic character 'V' and reload the timer */ | ||
233 | if (len) { | ||
234 | if (!nowayout) { | ||
235 | size_t i; | ||
236 | |||
237 | /* note: just in case someone wrote the magic character | ||
238 | * five months ago... */ | ||
239 | tco_expect_close = 0; | ||
240 | |||
241 | /* scan to see whether or not we got the magic character */ | ||
242 | for (i = 0; i != len; i++) { | ||
243 | char c; | ||
244 | if(get_user(c, data+i)) | ||
245 | return -EFAULT; | ||
246 | if (c == 'V') | ||
247 | tco_expect_close = 42; | ||
248 | } | ||
249 | } | ||
250 | |||
251 | /* someone wrote to us, we should reload the timer */ | ||
252 | tco_timer_keepalive (); | ||
253 | } | ||
254 | return len; | ||
255 | } | ||
256 | |||
257 | static int i8xx_tco_ioctl (struct inode *inode, struct file *file, | ||
258 | unsigned int cmd, unsigned long arg) | ||
259 | { | ||
260 | int new_options, retval = -EINVAL; | ||
261 | int new_heartbeat; | ||
262 | void __user *argp = (void __user *)arg; | ||
263 | int __user *p = argp; | ||
264 | static struct watchdog_info ident = { | ||
265 | .options = WDIOF_SETTIMEOUT | | ||
266 | WDIOF_KEEPALIVEPING | | ||
267 | WDIOF_MAGICCLOSE, | ||
268 | .firmware_version = 0, | ||
269 | .identity = TCO_MODULE_NAME, | ||
270 | }; | ||
271 | |||
272 | switch (cmd) { | ||
273 | case WDIOC_GETSUPPORT: | ||
274 | return copy_to_user(argp, &ident, | ||
275 | sizeof (ident)) ? -EFAULT : 0; | ||
276 | |||
277 | case WDIOC_GETSTATUS: | ||
278 | case WDIOC_GETBOOTSTATUS: | ||
279 | return put_user (0, p); | ||
280 | |||
281 | case WDIOC_KEEPALIVE: | ||
282 | tco_timer_keepalive (); | ||
283 | return 0; | ||
284 | |||
285 | case WDIOC_SETOPTIONS: | ||
286 | { | ||
287 | if (get_user (new_options, p)) | ||
288 | return -EFAULT; | ||
289 | |||
290 | if (new_options & WDIOS_DISABLECARD) { | ||
291 | tco_timer_stop (); | ||
292 | retval = 0; | ||
293 | } | ||
294 | |||
295 | if (new_options & WDIOS_ENABLECARD) { | ||
296 | tco_timer_keepalive (); | ||
297 | tco_timer_start (); | ||
298 | retval = 0; | ||
299 | } | ||
300 | |||
301 | return retval; | ||
302 | } | ||
303 | |||
304 | case WDIOC_SETTIMEOUT: | ||
305 | { | ||
306 | if (get_user(new_heartbeat, p)) | ||
307 | return -EFAULT; | ||
308 | |||
309 | if (tco_timer_set_heartbeat(new_heartbeat)) | ||
310 | return -EINVAL; | ||
311 | |||
312 | tco_timer_keepalive (); | ||
313 | /* Fall */ | ||
314 | } | ||
315 | |||
316 | case WDIOC_GETTIMEOUT: | ||
317 | return put_user(heartbeat, p); | ||
318 | |||
319 | default: | ||
320 | return -ENOIOCTLCMD; | ||
321 | } | ||
322 | } | ||
323 | |||
324 | /* | ||
325 | * Notify system | ||
326 | */ | ||
327 | |||
328 | static int i8xx_tco_notify_sys (struct notifier_block *this, unsigned long code, void *unused) | ||
329 | { | ||
330 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
331 | /* Turn the WDT off */ | ||
332 | tco_timer_stop (); | ||
333 | } | ||
334 | |||
335 | return NOTIFY_DONE; | ||
336 | } | ||
337 | |||
338 | /* | ||
339 | * Kernel Interfaces | ||
340 | */ | ||
341 | |||
342 | static struct file_operations i8xx_tco_fops = { | ||
343 | .owner = THIS_MODULE, | ||
344 | .llseek = no_llseek, | ||
345 | .write = i8xx_tco_write, | ||
346 | .ioctl = i8xx_tco_ioctl, | ||
347 | .open = i8xx_tco_open, | ||
348 | .release = i8xx_tco_release, | ||
349 | }; | ||
350 | |||
351 | static struct miscdevice i8xx_tco_miscdev = { | ||
352 | .minor = WATCHDOG_MINOR, | ||
353 | .name = "watchdog", | ||
354 | .fops = &i8xx_tco_fops, | ||
355 | }; | ||
356 | |||
357 | static struct notifier_block i8xx_tco_notifier = { | ||
358 | .notifier_call = i8xx_tco_notify_sys, | ||
359 | }; | ||
360 | |||
361 | /* | ||
362 | * Data for PCI driver interface | ||
363 | * | ||
364 | * This data only exists for exporting the supported | ||
365 | * PCI ids via MODULE_DEVICE_TABLE. We do not actually | ||
366 | * register a pci_driver, because someone else might one day | ||
367 | * want to register another driver on the same PCI id. | ||
368 | */ | ||
369 | static struct pci_device_id i8xx_tco_pci_tbl[] = { | ||
370 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0, PCI_ANY_ID, PCI_ANY_ID, }, | ||
371 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0, PCI_ANY_ID, PCI_ANY_ID, }, | ||
372 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, PCI_ANY_ID, PCI_ANY_ID, }, | ||
373 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10, PCI_ANY_ID, PCI_ANY_ID, }, | ||
374 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, PCI_ANY_ID, PCI_ANY_ID, }, | ||
375 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12, PCI_ANY_ID, PCI_ANY_ID, }, | ||
376 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, PCI_ANY_ID, PCI_ANY_ID, }, | ||
377 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12, PCI_ANY_ID, PCI_ANY_ID, }, | ||
378 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801E_0, PCI_ANY_ID, PCI_ANY_ID, }, | ||
379 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, PCI_ANY_ID, PCI_ANY_ID, }, | ||
380 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_0, PCI_ANY_ID, PCI_ANY_ID, }, | ||
381 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, PCI_ANY_ID, PCI_ANY_ID, }, | ||
382 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_2, PCI_ANY_ID, PCI_ANY_ID, }, | ||
383 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0, PCI_ANY_ID, PCI_ANY_ID, }, | ||
384 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1, PCI_ANY_ID, PCI_ANY_ID, }, | ||
385 | { 0, }, /* End of list */ | ||
386 | }; | ||
387 | MODULE_DEVICE_TABLE (pci, i8xx_tco_pci_tbl); | ||
388 | |||
389 | /* | ||
390 | * Init & exit routines | ||
391 | */ | ||
392 | |||
393 | static unsigned char __init i8xx_tco_getdevice (void) | ||
394 | { | ||
395 | struct pci_dev *dev = NULL; | ||
396 | u8 val1, val2; | ||
397 | u16 badr; | ||
398 | /* | ||
399 | * Find the PCI device | ||
400 | */ | ||
401 | |||
402 | while ((dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { | ||
403 | if (pci_match_device(i8xx_tco_pci_tbl, dev)) { | ||
404 | i8xx_tco_pci = dev; | ||
405 | break; | ||
406 | } | ||
407 | } | ||
408 | |||
409 | if (i8xx_tco_pci) { | ||
410 | /* | ||
411 | * Find the ACPI base I/O address which is the base | ||
412 | * for the TCO registers (TCOBASE=ACPIBASE + 0x60) | ||
413 | * ACPIBASE is bits [15:7] from 0x40-0x43 | ||
414 | */ | ||
415 | pci_read_config_byte (i8xx_tco_pci, 0x40, &val1); | ||
416 | pci_read_config_byte (i8xx_tco_pci, 0x41, &val2); | ||
417 | badr = ((val2 << 1) | (val1 >> 7)) << 7; | ||
418 | ACPIBASE = badr; | ||
419 | /* Something's wrong here, ACPIBASE has to be set */ | ||
420 | if (badr == 0x0001 || badr == 0x0000) { | ||
421 | printk (KERN_ERR PFX "failed to get TCOBASE address\n"); | ||
422 | return 0; | ||
423 | } | ||
424 | /* | ||
425 | * Check chipset's NO_REBOOT bit | ||
426 | */ | ||
427 | pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1); | ||
428 | if (val1 & 0x02) { | ||
429 | val1 &= 0xfd; | ||
430 | pci_write_config_byte (i8xx_tco_pci, 0xd4, val1); | ||
431 | pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1); | ||
432 | if (val1 & 0x02) { | ||
433 | printk (KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n"); | ||
434 | return 0; /* Cannot reset NO_REBOOT bit */ | ||
435 | } | ||
436 | } | ||
437 | /* Set the TCO_EN bit in SMI_EN register */ | ||
438 | if (!request_region (SMI_EN + 1, 1, "i8xx TCO")) { | ||
439 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
440 | SMI_EN + 1); | ||
441 | return 0; | ||
442 | } | ||
443 | val1 = inb (SMI_EN + 1); | ||
444 | val1 &= 0xdf; | ||
445 | outb (val1, SMI_EN + 1); | ||
446 | release_region (SMI_EN + 1, 1); | ||
447 | return 1; | ||
448 | } | ||
449 | return 0; | ||
450 | } | ||
451 | |||
452 | static int __init watchdog_init (void) | ||
453 | { | ||
454 | int ret; | ||
455 | |||
456 | spin_lock_init(&tco_lock); | ||
457 | |||
458 | /* Check whether or not the hardware watchdog is there */ | ||
459 | if (!i8xx_tco_getdevice () || i8xx_tco_pci == NULL) | ||
460 | return -ENODEV; | ||
461 | |||
462 | if (!request_region (TCOBASE, 0x10, "i8xx TCO")) { | ||
463 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
464 | TCOBASE); | ||
465 | ret = -EIO; | ||
466 | goto out; | ||
467 | } | ||
468 | |||
469 | /* Clear out the (probably old) status */ | ||
470 | outb (0, TCO1_STS); | ||
471 | outb (3, TCO2_STS); | ||
472 | |||
473 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
474 | if (tco_timer_set_heartbeat (heartbeat)) { | ||
475 | heartbeat = WATCHDOG_HEARTBEAT; | ||
476 | tco_timer_set_heartbeat (heartbeat); | ||
477 | printk(KERN_INFO PFX "heartbeat value must be 2<heartbeat<39, using %d\n", | ||
478 | heartbeat); | ||
479 | } | ||
480 | |||
481 | ret = register_reboot_notifier(&i8xx_tco_notifier); | ||
482 | if (ret != 0) { | ||
483 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
484 | ret); | ||
485 | goto unreg_region; | ||
486 | } | ||
487 | |||
488 | ret = misc_register(&i8xx_tco_miscdev); | ||
489 | if (ret != 0) { | ||
490 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
491 | WATCHDOG_MINOR, ret); | ||
492 | goto unreg_notifier; | ||
493 | } | ||
494 | |||
495 | tco_timer_stop (); | ||
496 | |||
497 | printk (KERN_INFO PFX "initialized (0x%04x). heartbeat=%d sec (nowayout=%d)\n", | ||
498 | TCOBASE, heartbeat, nowayout); | ||
499 | |||
500 | return 0; | ||
501 | |||
502 | unreg_notifier: | ||
503 | unregister_reboot_notifier(&i8xx_tco_notifier); | ||
504 | unreg_region: | ||
505 | release_region (TCOBASE, 0x10); | ||
506 | out: | ||
507 | return ret; | ||
508 | } | ||
509 | |||
510 | static void __exit watchdog_cleanup (void) | ||
511 | { | ||
512 | u8 val; | ||
513 | |||
514 | /* Stop the timer before we leave */ | ||
515 | if (!nowayout) | ||
516 | tco_timer_stop (); | ||
517 | |||
518 | /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ | ||
519 | pci_read_config_byte (i8xx_tco_pci, 0xd4, &val); | ||
520 | val |= 0x02; | ||
521 | pci_write_config_byte (i8xx_tco_pci, 0xd4, val); | ||
522 | |||
523 | /* Deregister */ | ||
524 | misc_deregister (&i8xx_tco_miscdev); | ||
525 | unregister_reboot_notifier(&i8xx_tco_notifier); | ||
526 | release_region (TCOBASE, 0x10); | ||
527 | } | ||
528 | |||
529 | module_init(watchdog_init); | ||
530 | module_exit(watchdog_cleanup); | ||
531 | |||
532 | MODULE_AUTHOR("Nils Faerber"); | ||
533 | MODULE_DESCRIPTION("TCO timer driver for i8xx chipsets"); | ||
534 | MODULE_LICENSE("GPL"); | ||
535 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/i8xx_tco.h b/drivers/char/watchdog/i8xx_tco.h new file mode 100644 index 000000000000..cc14eb8ac3d6 --- /dev/null +++ b/drivers/char/watchdog/i8xx_tco.h | |||
@@ -0,0 +1,42 @@ | |||
1 | /* | ||
2 | * i8xx_tco: TCO timer driver for i8xx chipsets | ||
3 | * | ||
4 | * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved. | ||
5 | * http://www.kernelconcepts.de | ||
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 kernel concepts nor Nils Faerber admit liability nor provide | ||
13 | * warranty for any of this software. This material is provided | ||
14 | * "AS-IS" and at no charge. | ||
15 | * | ||
16 | * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de> | ||
17 | * developed for | ||
18 | * Jentro AG, Haar/Munich (Germany) | ||
19 | * | ||
20 | * TCO timer driver for i8xx chipsets | ||
21 | * based on softdog.c by Alan Cox <alan@redhat.com> | ||
22 | * | ||
23 | * For history and the complete list of supported I/O Controller Hub's | ||
24 | * see i8xx_tco.c | ||
25 | */ | ||
26 | |||
27 | |||
28 | /* | ||
29 | * Some address definitions for the TCO | ||
30 | */ | ||
31 | |||
32 | #define TCOBASE ACPIBASE + 0x60 /* TCO base address */ | ||
33 | #define TCO1_RLD TCOBASE + 0x00 /* TCO Timer Reload and Current Value */ | ||
34 | #define TCO1_TMR TCOBASE + 0x01 /* TCO Timer Initial Value */ | ||
35 | #define TCO1_DAT_IN TCOBASE + 0x02 /* TCO Data In Register */ | ||
36 | #define TCO1_DAT_OUT TCOBASE + 0x03 /* TCO Data Out Register */ | ||
37 | #define TCO1_STS TCOBASE + 0x04 /* TCO1 Status Register */ | ||
38 | #define TCO2_STS TCOBASE + 0x06 /* TCO2 Status Register */ | ||
39 | #define TCO1_CNT TCOBASE + 0x08 /* TCO1 Control Register */ | ||
40 | #define TCO2_CNT TCOBASE + 0x0a /* TCO2 Control Register */ | ||
41 | |||
42 | #define SMI_EN ACPIBASE + 0x30 /* SMI Control and Enable Register */ | ||
diff --git a/drivers/char/watchdog/ib700wdt.c b/drivers/char/watchdog/ib700wdt.c new file mode 100644 index 000000000000..d974f16e84d2 --- /dev/null +++ b/drivers/char/watchdog/ib700wdt.c | |||
@@ -0,0 +1,352 @@ | |||
1 | /* | ||
2 | * IB700 Single Board Computer WDT driver | ||
3 | * | ||
4 | * (c) Copyright 2001 Charles Howes <chowes@vsol.net> | ||
5 | * | ||
6 | * Based on advantechwdt.c which is based on acquirewdt.c which | ||
7 | * is based on wdt.c. | ||
8 | * | ||
9 | * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> | ||
10 | * | ||
11 | * Based on acquirewdt.c which is based on wdt.c. | ||
12 | * Original copyright messages: | ||
13 | * | ||
14 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
15 | * http://www.redhat.com | ||
16 | * | ||
17 | * This program is free software; you can redistribute it and/or | ||
18 | * modify it under the terms of the GNU General Public License | ||
19 | * as published by the Free Software Foundation; either version | ||
20 | * 2 of the License, or (at your option) any later version. | ||
21 | * | ||
22 | * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
23 | * warranty for any of this software. This material is provided | ||
24 | * "AS-IS" and at no charge. | ||
25 | * | ||
26 | * (c) Copyright 1995 Alan Cox <alan@redhat.com> | ||
27 | * | ||
28 | * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> | ||
29 | * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT | ||
30 | * Added timeout module option to override default | ||
31 | * | ||
32 | */ | ||
33 | |||
34 | #include <linux/config.h> | ||
35 | #include <linux/module.h> | ||
36 | #include <linux/types.h> | ||
37 | #include <linux/miscdevice.h> | ||
38 | #include <linux/watchdog.h> | ||
39 | #include <linux/ioport.h> | ||
40 | #include <linux/notifier.h> | ||
41 | #include <linux/fs.h> | ||
42 | #include <linux/reboot.h> | ||
43 | #include <linux/init.h> | ||
44 | #include <linux/spinlock.h> | ||
45 | #include <linux/moduleparam.h> | ||
46 | |||
47 | #include <asm/io.h> | ||
48 | #include <asm/uaccess.h> | ||
49 | #include <asm/system.h> | ||
50 | |||
51 | static unsigned long ibwdt_is_open; | ||
52 | static spinlock_t ibwdt_lock; | ||
53 | static char expect_close; | ||
54 | |||
55 | #define PFX "ib700wdt: " | ||
56 | |||
57 | /* | ||
58 | * | ||
59 | * Watchdog Timer Configuration | ||
60 | * | ||
61 | * The function of the watchdog timer is to reset the system | ||
62 | * automatically and is defined at I/O port 0443H. To enable the | ||
63 | * watchdog timer and allow the system to reset, write I/O port 0443H. | ||
64 | * To disable the timer, write I/O port 0441H for the system to stop the | ||
65 | * watchdog function. The timer has a tolerance of 20% for its | ||
66 | * intervals. | ||
67 | * | ||
68 | * The following describes how the timer should be programmed. | ||
69 | * | ||
70 | * Enabling Watchdog: | ||
71 | * MOV AX,000FH (Choose the values from 0 to F) | ||
72 | * MOV DX,0443H | ||
73 | * OUT DX,AX | ||
74 | * | ||
75 | * Disabling Watchdog: | ||
76 | * MOV AX,000FH (Any value is fine.) | ||
77 | * MOV DX,0441H | ||
78 | * OUT DX,AX | ||
79 | * | ||
80 | * Watchdog timer control table: | ||
81 | * Level Value Time/sec | Level Value Time/sec | ||
82 | * 1 F 0 | 9 7 16 | ||
83 | * 2 E 2 | 10 6 18 | ||
84 | * 3 D 4 | 11 5 20 | ||
85 | * 4 C 6 | 12 4 22 | ||
86 | * 5 B 8 | 13 3 24 | ||
87 | * 6 A 10 | 14 2 26 | ||
88 | * 7 9 12 | 15 1 28 | ||
89 | * 8 8 14 | 16 0 30 | ||
90 | * | ||
91 | */ | ||
92 | |||
93 | static int wd_times[] = { | ||
94 | 30, /* 0x0 */ | ||
95 | 28, /* 0x1 */ | ||
96 | 26, /* 0x2 */ | ||
97 | 24, /* 0x3 */ | ||
98 | 22, /* 0x4 */ | ||
99 | 20, /* 0x5 */ | ||
100 | 18, /* 0x6 */ | ||
101 | 16, /* 0x7 */ | ||
102 | 14, /* 0x8 */ | ||
103 | 12, /* 0x9 */ | ||
104 | 10, /* 0xA */ | ||
105 | 8, /* 0xB */ | ||
106 | 6, /* 0xC */ | ||
107 | 4, /* 0xD */ | ||
108 | 2, /* 0xE */ | ||
109 | 0, /* 0xF */ | ||
110 | }; | ||
111 | |||
112 | #define WDT_STOP 0x441 | ||
113 | #define WDT_START 0x443 | ||
114 | |||
115 | /* Default timeout */ | ||
116 | #define WD_TIMO 0 /* 30 seconds +/- 20%, from table */ | ||
117 | |||
118 | static int wd_margin = WD_TIMO; | ||
119 | |||
120 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
121 | static int nowayout = 1; | ||
122 | #else | ||
123 | static int nowayout = 0; | ||
124 | #endif | ||
125 | |||
126 | module_param(nowayout, int, 0); | ||
127 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
128 | |||
129 | |||
130 | /* | ||
131 | * Kernel methods. | ||
132 | */ | ||
133 | |||
134 | static void | ||
135 | ibwdt_ping(void) | ||
136 | { | ||
137 | /* Write a watchdog value */ | ||
138 | outb_p(wd_margin, WDT_START); | ||
139 | } | ||
140 | |||
141 | static ssize_t | ||
142 | ibwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
143 | { | ||
144 | if (count) { | ||
145 | if (!nowayout) { | ||
146 | size_t i; | ||
147 | |||
148 | /* In case it was set long ago */ | ||
149 | expect_close = 0; | ||
150 | |||
151 | for (i = 0; i != count; i++) { | ||
152 | char c; | ||
153 | if (get_user(c, buf + i)) | ||
154 | return -EFAULT; | ||
155 | if (c == 'V') | ||
156 | expect_close = 42; | ||
157 | } | ||
158 | } | ||
159 | ibwdt_ping(); | ||
160 | } | ||
161 | return count; | ||
162 | } | ||
163 | |||
164 | static int | ||
165 | ibwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
166 | unsigned long arg) | ||
167 | { | ||
168 | int i, new_margin; | ||
169 | void __user *argp = (void __user *)arg; | ||
170 | int __user *p = argp; | ||
171 | |||
172 | static struct watchdog_info ident = { | ||
173 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
174 | .firmware_version = 1, | ||
175 | .identity = "IB700 WDT", | ||
176 | }; | ||
177 | |||
178 | switch (cmd) { | ||
179 | case WDIOC_GETSUPPORT: | ||
180 | if (copy_to_user(argp, &ident, sizeof(ident))) | ||
181 | return -EFAULT; | ||
182 | break; | ||
183 | |||
184 | case WDIOC_GETSTATUS: | ||
185 | return put_user(0, p); | ||
186 | |||
187 | case WDIOC_KEEPALIVE: | ||
188 | ibwdt_ping(); | ||
189 | break; | ||
190 | |||
191 | case WDIOC_SETTIMEOUT: | ||
192 | if (get_user(new_margin, p)) | ||
193 | return -EFAULT; | ||
194 | if ((new_margin < 0) || (new_margin > 30)) | ||
195 | return -EINVAL; | ||
196 | for (i = 0x0F; i > -1; i--) | ||
197 | if (wd_times[i] > new_margin) | ||
198 | break; | ||
199 | wd_margin = i; | ||
200 | ibwdt_ping(); | ||
201 | /* Fall */ | ||
202 | |||
203 | case WDIOC_GETTIMEOUT: | ||
204 | return put_user(wd_times[wd_margin], p); | ||
205 | break; | ||
206 | |||
207 | default: | ||
208 | return -ENOIOCTLCMD; | ||
209 | } | ||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | static int | ||
214 | ibwdt_open(struct inode *inode, struct file *file) | ||
215 | { | ||
216 | spin_lock(&ibwdt_lock); | ||
217 | if (test_and_set_bit(0, &ibwdt_is_open)) { | ||
218 | spin_unlock(&ibwdt_lock); | ||
219 | return -EBUSY; | ||
220 | } | ||
221 | if (nowayout) | ||
222 | __module_get(THIS_MODULE); | ||
223 | |||
224 | /* Activate */ | ||
225 | ibwdt_ping(); | ||
226 | spin_unlock(&ibwdt_lock); | ||
227 | return nonseekable_open(inode, file); | ||
228 | } | ||
229 | |||
230 | static int | ||
231 | ibwdt_close(struct inode *inode, struct file *file) | ||
232 | { | ||
233 | spin_lock(&ibwdt_lock); | ||
234 | if (expect_close == 42) | ||
235 | outb_p(0, WDT_STOP); | ||
236 | else | ||
237 | printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n"); | ||
238 | |||
239 | clear_bit(0, &ibwdt_is_open); | ||
240 | expect_close = 0; | ||
241 | spin_unlock(&ibwdt_lock); | ||
242 | return 0; | ||
243 | } | ||
244 | |||
245 | /* | ||
246 | * Notifier for system down | ||
247 | */ | ||
248 | |||
249 | static int | ||
250 | ibwdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
251 | void *unused) | ||
252 | { | ||
253 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
254 | /* Turn the WDT off */ | ||
255 | outb_p(0, WDT_STOP); | ||
256 | } | ||
257 | return NOTIFY_DONE; | ||
258 | } | ||
259 | |||
260 | /* | ||
261 | * Kernel Interfaces | ||
262 | */ | ||
263 | |||
264 | static struct file_operations ibwdt_fops = { | ||
265 | .owner = THIS_MODULE, | ||
266 | .llseek = no_llseek, | ||
267 | .write = ibwdt_write, | ||
268 | .ioctl = ibwdt_ioctl, | ||
269 | .open = ibwdt_open, | ||
270 | .release = ibwdt_close, | ||
271 | }; | ||
272 | |||
273 | static struct miscdevice ibwdt_miscdev = { | ||
274 | .minor = WATCHDOG_MINOR, | ||
275 | .name = "watchdog", | ||
276 | .fops = &ibwdt_fops, | ||
277 | }; | ||
278 | |||
279 | /* | ||
280 | * The WDT needs to learn about soft shutdowns in order to | ||
281 | * turn the timebomb registers off. | ||
282 | */ | ||
283 | |||
284 | static struct notifier_block ibwdt_notifier = { | ||
285 | .notifier_call = ibwdt_notify_sys, | ||
286 | }; | ||
287 | |||
288 | static int __init ibwdt_init(void) | ||
289 | { | ||
290 | int res; | ||
291 | |||
292 | printk(KERN_INFO PFX "WDT driver for IB700 single board computer initialising.\n"); | ||
293 | |||
294 | spin_lock_init(&ibwdt_lock); | ||
295 | res = misc_register(&ibwdt_miscdev); | ||
296 | if (res) { | ||
297 | printk (KERN_ERR PFX "failed to register misc device\n"); | ||
298 | goto out_nomisc; | ||
299 | } | ||
300 | |||
301 | #if WDT_START != WDT_STOP | ||
302 | if (!request_region(WDT_STOP, 1, "IB700 WDT")) { | ||
303 | printk (KERN_ERR PFX "STOP method I/O %X is not available.\n", WDT_STOP); | ||
304 | res = -EIO; | ||
305 | goto out_nostopreg; | ||
306 | } | ||
307 | #endif | ||
308 | |||
309 | if (!request_region(WDT_START, 1, "IB700 WDT")) { | ||
310 | printk (KERN_ERR PFX "START method I/O %X is not available.\n", WDT_START); | ||
311 | res = -EIO; | ||
312 | goto out_nostartreg; | ||
313 | } | ||
314 | res = register_reboot_notifier(&ibwdt_notifier); | ||
315 | if (res) { | ||
316 | printk (KERN_ERR PFX "Failed to register reboot notifier.\n"); | ||
317 | goto out_noreboot; | ||
318 | } | ||
319 | return 0; | ||
320 | |||
321 | out_noreboot: | ||
322 | release_region(WDT_START, 1); | ||
323 | out_nostartreg: | ||
324 | #if WDT_START != WDT_STOP | ||
325 | release_region(WDT_STOP, 1); | ||
326 | #endif | ||
327 | out_nostopreg: | ||
328 | misc_deregister(&ibwdt_miscdev); | ||
329 | out_nomisc: | ||
330 | return res; | ||
331 | } | ||
332 | |||
333 | static void __exit | ||
334 | ibwdt_exit(void) | ||
335 | { | ||
336 | misc_deregister(&ibwdt_miscdev); | ||
337 | unregister_reboot_notifier(&ibwdt_notifier); | ||
338 | #if WDT_START != WDT_STOP | ||
339 | release_region(WDT_STOP,1); | ||
340 | #endif | ||
341 | release_region(WDT_START,1); | ||
342 | } | ||
343 | |||
344 | module_init(ibwdt_init); | ||
345 | module_exit(ibwdt_exit); | ||
346 | |||
347 | MODULE_AUTHOR("Charles Howes <chowes@vsol.net>"); | ||
348 | MODULE_DESCRIPTION("IB700 SBC watchdog driver"); | ||
349 | MODULE_LICENSE("GPL"); | ||
350 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
351 | |||
352 | /* end of ib700wdt.c */ | ||
diff --git a/drivers/char/watchdog/indydog.c b/drivers/char/watchdog/indydog.c new file mode 100644 index 000000000000..6af2c799b57e --- /dev/null +++ b/drivers/char/watchdog/indydog.c | |||
@@ -0,0 +1,221 @@ | |||
1 | /* | ||
2 | * IndyDog 0.3 A Hardware Watchdog Device for SGI IP22 | ||
3 | * | ||
4 | * (c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>, All Rights Reserved. | ||
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 | * based on softdog.c by Alan Cox <alan@redhat.com> | ||
12 | */ | ||
13 | |||
14 | #include <linux/module.h> | ||
15 | #include <linux/moduleparam.h> | ||
16 | #include <linux/config.h> | ||
17 | #include <linux/types.h> | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/fs.h> | ||
20 | #include <linux/mm.h> | ||
21 | #include <linux/miscdevice.h> | ||
22 | #include <linux/watchdog.h> | ||
23 | #include <linux/notifier.h> | ||
24 | #include <linux/reboot.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <asm/uaccess.h> | ||
27 | #include <asm/sgi/mc.h> | ||
28 | |||
29 | #define PFX "indydog: " | ||
30 | static int indydog_alive; | ||
31 | |||
32 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
33 | static int nowayout = 1; | ||
34 | #else | ||
35 | static int nowayout = 0; | ||
36 | #endif | ||
37 | |||
38 | #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ | ||
39 | |||
40 | module_param(nowayout, int, 0); | ||
41 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
42 | |||
43 | static void indydog_start(void) | ||
44 | { | ||
45 | u32 mc_ctrl0 = sgimc->cpuctrl0; | ||
46 | |||
47 | mc_ctrl0 = sgimc->cpuctrl0 | SGIMC_CCTRL0_WDOG; | ||
48 | sgimc->cpuctrl0 = mc_ctrl0; | ||
49 | } | ||
50 | |||
51 | static void indydog_stop(void) | ||
52 | { | ||
53 | u32 mc_ctrl0 = sgimc->cpuctrl0; | ||
54 | |||
55 | mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG; | ||
56 | sgimc->cpuctrl0 = mc_ctrl0; | ||
57 | |||
58 | printk(KERN_INFO PFX "Stopped watchdog timer.\n"); | ||
59 | } | ||
60 | |||
61 | static void indydog_ping(void) | ||
62 | { | ||
63 | sgimc->watchdogt = 0; | ||
64 | } | ||
65 | |||
66 | /* | ||
67 | * Allow only one person to hold it open | ||
68 | */ | ||
69 | static int indydog_open(struct inode *inode, struct file *file) | ||
70 | { | ||
71 | if (indydog_alive) | ||
72 | return -EBUSY; | ||
73 | |||
74 | if (nowayout) | ||
75 | __module_get(THIS_MODULE); | ||
76 | |||
77 | /* Activate timer */ | ||
78 | indydog_start(); | ||
79 | indydog_ping(); | ||
80 | |||
81 | indydog_alive = 1; | ||
82 | printk(KERN_INFO "Started watchdog timer.\n"); | ||
83 | |||
84 | return nonseekable_open(inode, file); | ||
85 | } | ||
86 | |||
87 | static int indydog_release(struct inode *inode, struct file *file) | ||
88 | { | ||
89 | /* Shut off the timer. | ||
90 | * Lock it in if it's a module and we defined ...NOWAYOUT */ | ||
91 | if (!nowayout) | ||
92 | indydog_stop(); /* Turn the WDT off */ | ||
93 | |||
94 | indydog_alive = 0; | ||
95 | |||
96 | return 0; | ||
97 | } | ||
98 | |||
99 | static ssize_t indydog_write(struct file *file, const char *data, size_t len, loff_t *ppos) | ||
100 | { | ||
101 | /* Refresh the timer. */ | ||
102 | if (len) { | ||
103 | indydog_ping(); | ||
104 | } | ||
105 | return len; | ||
106 | } | ||
107 | |||
108 | static int indydog_ioctl(struct inode *inode, struct file *file, | ||
109 | unsigned int cmd, unsigned long arg) | ||
110 | { | ||
111 | int options, retval = -EINVAL; | ||
112 | static struct watchdog_info ident = { | ||
113 | .options = WDIOF_KEEPALIVEPING | | ||
114 | WDIOF_MAGICCLOSE, | ||
115 | .firmware_version = 0, | ||
116 | .identity = "Hardware Watchdog for SGI IP22", | ||
117 | }; | ||
118 | |||
119 | switch (cmd) { | ||
120 | default: | ||
121 | return -ENOIOCTLCMD; | ||
122 | case WDIOC_GETSUPPORT: | ||
123 | if (copy_to_user((struct watchdog_info *)arg, | ||
124 | &ident, sizeof(ident))) | ||
125 | return -EFAULT; | ||
126 | return 0; | ||
127 | case WDIOC_GETSTATUS: | ||
128 | case WDIOC_GETBOOTSTATUS: | ||
129 | return put_user(0,(int *)arg); | ||
130 | case WDIOC_KEEPALIVE: | ||
131 | indydog_ping(); | ||
132 | return 0; | ||
133 | case WDIOC_GETTIMEOUT: | ||
134 | return put_user(WATCHDOG_TIMEOUT,(int *)arg); | ||
135 | case WDIOC_SETOPTIONS: | ||
136 | { | ||
137 | if (get_user(options, (int *)arg)) | ||
138 | return -EFAULT; | ||
139 | |||
140 | if (options & WDIOS_DISABLECARD) { | ||
141 | indydog_stop(); | ||
142 | retval = 0; | ||
143 | } | ||
144 | |||
145 | if (options & WDIOS_ENABLECARD) { | ||
146 | indydog_start(); | ||
147 | retval = 0; | ||
148 | } | ||
149 | |||
150 | return retval; | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | |||
155 | static int indydog_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
156 | { | ||
157 | if (code == SYS_DOWN || code == SYS_HALT) | ||
158 | indydog_stop(); /* Turn the WDT off */ | ||
159 | |||
160 | return NOTIFY_DONE; | ||
161 | } | ||
162 | |||
163 | static struct file_operations indydog_fops = { | ||
164 | .owner = THIS_MODULE, | ||
165 | .llseek = no_llseek, | ||
166 | .write = indydog_write, | ||
167 | .ioctl = indydog_ioctl, | ||
168 | .open = indydog_open, | ||
169 | .release = indydog_release, | ||
170 | }; | ||
171 | |||
172 | static struct miscdevice indydog_miscdev = { | ||
173 | .minor = WATCHDOG_MINOR, | ||
174 | .name = "watchdog", | ||
175 | .fops = &indydog_fops, | ||
176 | }; | ||
177 | |||
178 | static struct notifier_block indydog_notifier = { | ||
179 | .notifier_call = indydog_notify_sys, | ||
180 | }; | ||
181 | |||
182 | static char banner[] __initdata = | ||
183 | KERN_INFO PFX "Hardware Watchdog Timer for SGI IP22: 0.3\n"; | ||
184 | |||
185 | static int __init watchdog_init(void) | ||
186 | { | ||
187 | int ret; | ||
188 | |||
189 | ret = register_reboot_notifier(&indydog_notifier); | ||
190 | if (ret) { | ||
191 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
192 | ret); | ||
193 | return ret; | ||
194 | } | ||
195 | |||
196 | ret = misc_register(&indydog_miscdev); | ||
197 | if (ret) { | ||
198 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
199 | WATCHDOG_MINOR, ret); | ||
200 | unregister_reboot_notifier(&indydog_notifier); | ||
201 | return ret; | ||
202 | } | ||
203 | |||
204 | printk(banner); | ||
205 | |||
206 | return 0; | ||
207 | } | ||
208 | |||
209 | static void __exit watchdog_exit(void) | ||
210 | { | ||
211 | misc_deregister(&indydog_miscdev); | ||
212 | unregister_reboot_notifier(&indydog_notifier); | ||
213 | } | ||
214 | |||
215 | module_init(watchdog_init); | ||
216 | module_exit(watchdog_exit); | ||
217 | |||
218 | MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>"); | ||
219 | MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22"); | ||
220 | MODULE_LICENSE("GPL"); | ||
221 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/ixp2000_wdt.c b/drivers/char/watchdog/ixp2000_wdt.c new file mode 100644 index 000000000000..ab659d37b4d2 --- /dev/null +++ b/drivers/char/watchdog/ixp2000_wdt.c | |||
@@ -0,0 +1,219 @@ | |||
1 | /* | ||
2 | * drivers/watchdog/ixp2000_wdt.c | ||
3 | * | ||
4 | * Watchdog driver for Intel IXP2000 network processors | ||
5 | * | ||
6 | * Adapted from the IXP4xx watchdog driver by Lennert Buytenhek. | ||
7 | * The original version carries these notices: | ||
8 | * | ||
9 | * Author: Deepak Saxena <dsaxena@plexity.net> | ||
10 | * | ||
11 | * Copyright 2004 (c) MontaVista, Software, Inc. | ||
12 | * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu> | ||
13 | * | ||
14 | * This file is licensed under the terms of the GNU General Public | ||
15 | * License version 2. This program is licensed "as is" without any | ||
16 | * warranty of any kind, whether express or implied. | ||
17 | */ | ||
18 | |||
19 | #include <linux/config.h> | ||
20 | #include <linux/module.h> | ||
21 | #include <linux/moduleparam.h> | ||
22 | #include <linux/types.h> | ||
23 | #include <linux/kernel.h> | ||
24 | #include <linux/fs.h> | ||
25 | #include <linux/miscdevice.h> | ||
26 | #include <linux/watchdog.h> | ||
27 | #include <linux/init.h> | ||
28 | #include <linux/bitops.h> | ||
29 | |||
30 | #include <asm/hardware.h> | ||
31 | #include <asm/uaccess.h> | ||
32 | |||
33 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
34 | static int nowayout = 1; | ||
35 | #else | ||
36 | static int nowayout = 0; | ||
37 | #endif | ||
38 | static unsigned int heartbeat = 60; /* (secs) Default is 1 minute */ | ||
39 | static unsigned long wdt_status; | ||
40 | |||
41 | #define WDT_IN_USE 0 | ||
42 | #define WDT_OK_TO_CLOSE 1 | ||
43 | |||
44 | static unsigned long wdt_tick_rate; | ||
45 | |||
46 | static void | ||
47 | wdt_enable(void) | ||
48 | { | ||
49 | ixp2000_reg_write(IXP2000_RESET0, *(IXP2000_RESET0) | WDT_RESET_ENABLE); | ||
50 | ixp2000_reg_write(IXP2000_TWDE, WDT_ENABLE); | ||
51 | ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); | ||
52 | ixp2000_reg_write(IXP2000_T4_CTL, TIMER_DIVIDER_256 | TIMER_ENABLE); | ||
53 | } | ||
54 | |||
55 | static void | ||
56 | wdt_disable(void) | ||
57 | { | ||
58 | ixp2000_reg_write(IXP2000_T4_CTL, 0); | ||
59 | } | ||
60 | |||
61 | static void | ||
62 | wdt_keepalive(void) | ||
63 | { | ||
64 | ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); | ||
65 | } | ||
66 | |||
67 | static int | ||
68 | ixp2000_wdt_open(struct inode *inode, struct file *file) | ||
69 | { | ||
70 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | ||
71 | return -EBUSY; | ||
72 | |||
73 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
74 | |||
75 | wdt_enable(); | ||
76 | |||
77 | return nonseekable_open(inode, file); | ||
78 | } | ||
79 | |||
80 | static ssize_t | ||
81 | ixp2000_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) | ||
82 | { | ||
83 | if (len) { | ||
84 | if (!nowayout) { | ||
85 | size_t i; | ||
86 | |||
87 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
88 | |||
89 | for (i = 0; i != len; i++) { | ||
90 | char c; | ||
91 | |||
92 | if (get_user(c, data + i)) | ||
93 | return -EFAULT; | ||
94 | if (c == 'V') | ||
95 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
96 | } | ||
97 | } | ||
98 | wdt_keepalive(); | ||
99 | } | ||
100 | |||
101 | return len; | ||
102 | } | ||
103 | |||
104 | |||
105 | static struct watchdog_info ident = { | ||
106 | .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | | ||
107 | WDIOF_KEEPALIVEPING, | ||
108 | .identity = "IXP2000 Watchdog", | ||
109 | }; | ||
110 | |||
111 | static int | ||
112 | ixp2000_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
113 | unsigned long arg) | ||
114 | { | ||
115 | int ret = -ENOIOCTLCMD; | ||
116 | int time; | ||
117 | |||
118 | switch (cmd) { | ||
119 | case WDIOC_GETSUPPORT: | ||
120 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | ||
121 | sizeof(ident)) ? -EFAULT : 0; | ||
122 | break; | ||
123 | |||
124 | case WDIOC_GETSTATUS: | ||
125 | ret = put_user(0, (int *)arg); | ||
126 | break; | ||
127 | |||
128 | case WDIOC_GETBOOTSTATUS: | ||
129 | ret = put_user(0, (int *)arg); | ||
130 | break; | ||
131 | |||
132 | case WDIOC_SETTIMEOUT: | ||
133 | ret = get_user(time, (int *)arg); | ||
134 | if (ret) | ||
135 | break; | ||
136 | |||
137 | if (time <= 0 || time > 60) { | ||
138 | ret = -EINVAL; | ||
139 | break; | ||
140 | } | ||
141 | |||
142 | heartbeat = time; | ||
143 | wdt_keepalive(); | ||
144 | /* Fall through */ | ||
145 | |||
146 | case WDIOC_GETTIMEOUT: | ||
147 | ret = put_user(heartbeat, (int *)arg); | ||
148 | break; | ||
149 | |||
150 | case WDIOC_KEEPALIVE: | ||
151 | wdt_enable(); | ||
152 | ret = 0; | ||
153 | break; | ||
154 | } | ||
155 | |||
156 | return ret; | ||
157 | } | ||
158 | |||
159 | static int | ||
160 | ixp2000_wdt_release(struct inode *inode, struct file *file) | ||
161 | { | ||
162 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { | ||
163 | wdt_disable(); | ||
164 | } else { | ||
165 | printk(KERN_CRIT "WATCHDOG: Device closed unexpectdly - " | ||
166 | "timer will not stop\n"); | ||
167 | } | ||
168 | |||
169 | clear_bit(WDT_IN_USE, &wdt_status); | ||
170 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
171 | |||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | |||
176 | static struct file_operations ixp2000_wdt_fops = | ||
177 | { | ||
178 | .owner = THIS_MODULE, | ||
179 | .llseek = no_llseek, | ||
180 | .write = ixp2000_wdt_write, | ||
181 | .ioctl = ixp2000_wdt_ioctl, | ||
182 | .open = ixp2000_wdt_open, | ||
183 | .release = ixp2000_wdt_release, | ||
184 | }; | ||
185 | |||
186 | static struct miscdevice ixp2000_wdt_miscdev = | ||
187 | { | ||
188 | .minor = WATCHDOG_MINOR, | ||
189 | .name = "IXP2000 Watchdog", | ||
190 | .fops = &ixp2000_wdt_fops, | ||
191 | }; | ||
192 | |||
193 | static int __init ixp2000_wdt_init(void) | ||
194 | { | ||
195 | wdt_tick_rate = (*IXP2000_T1_CLD * HZ)/ 256;; | ||
196 | |||
197 | return misc_register(&ixp2000_wdt_miscdev); | ||
198 | } | ||
199 | |||
200 | static void __exit ixp2000_wdt_exit(void) | ||
201 | { | ||
202 | misc_deregister(&ixp2000_wdt_miscdev); | ||
203 | } | ||
204 | |||
205 | module_init(ixp2000_wdt_init); | ||
206 | module_exit(ixp2000_wdt_exit); | ||
207 | |||
208 | MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net">); | ||
209 | MODULE_DESCRIPTION("IXP2000 Network Processor Watchdog"); | ||
210 | |||
211 | module_param(heartbeat, int, 0); | ||
212 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); | ||
213 | |||
214 | module_param(nowayout, int, 0); | ||
215 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); | ||
216 | |||
217 | MODULE_LICENSE("GPL"); | ||
218 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
219 | |||
diff --git a/drivers/char/watchdog/ixp4xx_wdt.c b/drivers/char/watchdog/ixp4xx_wdt.c new file mode 100644 index 000000000000..82396e06c8a8 --- /dev/null +++ b/drivers/char/watchdog/ixp4xx_wdt.c | |||
@@ -0,0 +1,230 @@ | |||
1 | /* | ||
2 | * drivers/watchdog/ixp4xx_wdt.c | ||
3 | * | ||
4 | * Watchdog driver for Intel IXP4xx network processors | ||
5 | * | ||
6 | * Author: Deepak Saxena <dsaxena@plexity.net> | ||
7 | * | ||
8 | * Copyright 2004 (c) MontaVista, Software, Inc. | ||
9 | * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu> | ||
10 | * | ||
11 | * This file is licensed under the terms of the GNU General Public | ||
12 | * License version 2. This program is licensed "as is" without any | ||
13 | * warranty of any kind, whether express or implied. | ||
14 | */ | ||
15 | |||
16 | #include <linux/config.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/moduleparam.h> | ||
19 | #include <linux/types.h> | ||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/fs.h> | ||
22 | #include <linux/miscdevice.h> | ||
23 | #include <linux/watchdog.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/bitops.h> | ||
26 | |||
27 | #include <asm/hardware.h> | ||
28 | #include <asm/uaccess.h> | ||
29 | |||
30 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
31 | static int nowayout = 1; | ||
32 | #else | ||
33 | static int nowayout = 0; | ||
34 | #endif | ||
35 | static int heartbeat = 60; /* (secs) Default is 1 minute */ | ||
36 | static unsigned long wdt_status; | ||
37 | static unsigned long boot_status; | ||
38 | |||
39 | #define WDT_TICK_RATE (IXP4XX_PERIPHERAL_BUS_CLOCK * 1000000UL) | ||
40 | |||
41 | #define WDT_IN_USE 0 | ||
42 | #define WDT_OK_TO_CLOSE 1 | ||
43 | |||
44 | static void | ||
45 | wdt_enable(void) | ||
46 | { | ||
47 | *IXP4XX_OSWK = IXP4XX_WDT_KEY; | ||
48 | *IXP4XX_OSWE = 0; | ||
49 | *IXP4XX_OSWT = WDT_TICK_RATE * heartbeat; | ||
50 | *IXP4XX_OSWE = IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE; | ||
51 | *IXP4XX_OSWK = 0; | ||
52 | } | ||
53 | |||
54 | static void | ||
55 | wdt_disable(void) | ||
56 | { | ||
57 | *IXP4XX_OSWK = IXP4XX_WDT_KEY; | ||
58 | *IXP4XX_OSWE = 0; | ||
59 | *IXP4XX_OSWK = 0; | ||
60 | } | ||
61 | |||
62 | static int | ||
63 | ixp4xx_wdt_open(struct inode *inode, struct file *file) | ||
64 | { | ||
65 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | ||
66 | return -EBUSY; | ||
67 | |||
68 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
69 | |||
70 | wdt_enable(); | ||
71 | |||
72 | return nonseekable_open(inode, file); | ||
73 | } | ||
74 | |||
75 | static ssize_t | ||
76 | ixp4xx_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) | ||
77 | { | ||
78 | if (len) { | ||
79 | if (!nowayout) { | ||
80 | size_t i; | ||
81 | |||
82 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
83 | |||
84 | for (i = 0; i != len; i++) { | ||
85 | char c; | ||
86 | |||
87 | if (get_user(c, data + i)) | ||
88 | return -EFAULT; | ||
89 | if (c == 'V') | ||
90 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
91 | } | ||
92 | } | ||
93 | wdt_enable(); | ||
94 | } | ||
95 | |||
96 | return len; | ||
97 | } | ||
98 | |||
99 | static struct watchdog_info ident = { | ||
100 | .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | | ||
101 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
102 | .identity = "IXP4xx Watchdog", | ||
103 | }; | ||
104 | |||
105 | |||
106 | static int | ||
107 | ixp4xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
108 | unsigned long arg) | ||
109 | { | ||
110 | int ret = -ENOIOCTLCMD; | ||
111 | int time; | ||
112 | |||
113 | switch (cmd) { | ||
114 | case WDIOC_GETSUPPORT: | ||
115 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | ||
116 | sizeof(ident)) ? -EFAULT : 0; | ||
117 | break; | ||
118 | |||
119 | case WDIOC_GETSTATUS: | ||
120 | ret = put_user(0, (int *)arg); | ||
121 | break; | ||
122 | |||
123 | case WDIOC_GETBOOTSTATUS: | ||
124 | ret = put_user(boot_status, (int *)arg); | ||
125 | break; | ||
126 | |||
127 | case WDIOC_SETTIMEOUT: | ||
128 | ret = get_user(time, (int *)arg); | ||
129 | if (ret) | ||
130 | break; | ||
131 | |||
132 | if (time <= 0 || time > 60) { | ||
133 | ret = -EINVAL; | ||
134 | break; | ||
135 | } | ||
136 | |||
137 | heartbeat = time; | ||
138 | wdt_enable(); | ||
139 | /* Fall through */ | ||
140 | |||
141 | case WDIOC_GETTIMEOUT: | ||
142 | ret = put_user(heartbeat, (int *)arg); | ||
143 | break; | ||
144 | |||
145 | case WDIOC_KEEPALIVE: | ||
146 | wdt_enable(); | ||
147 | ret = 0; | ||
148 | break; | ||
149 | } | ||
150 | return ret; | ||
151 | } | ||
152 | |||
153 | static int | ||
154 | ixp4xx_wdt_release(struct inode *inode, struct file *file) | ||
155 | { | ||
156 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { | ||
157 | wdt_disable(); | ||
158 | } else { | ||
159 | printk(KERN_CRIT "WATCHDOG: Device closed unexpectdly - " | ||
160 | "timer will not stop\n"); | ||
161 | } | ||
162 | |||
163 | clear_bit(WDT_IN_USE, &wdt_status); | ||
164 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
165 | |||
166 | return 0; | ||
167 | } | ||
168 | |||
169 | |||
170 | static struct file_operations ixp4xx_wdt_fops = | ||
171 | { | ||
172 | .owner = THIS_MODULE, | ||
173 | .llseek = no_llseek, | ||
174 | .write = ixp4xx_wdt_write, | ||
175 | .ioctl = ixp4xx_wdt_ioctl, | ||
176 | .open = ixp4xx_wdt_open, | ||
177 | .release = ixp4xx_wdt_release, | ||
178 | }; | ||
179 | |||
180 | static struct miscdevice ixp4xx_wdt_miscdev = | ||
181 | { | ||
182 | .minor = WATCHDOG_MINOR, | ||
183 | .name = "IXP4xx Watchdog", | ||
184 | .fops = &ixp4xx_wdt_fops, | ||
185 | }; | ||
186 | |||
187 | static int __init ixp4xx_wdt_init(void) | ||
188 | { | ||
189 | int ret; | ||
190 | unsigned long processor_id; | ||
191 | |||
192 | asm("mrc p15, 0, %0, cr0, cr0, 0;" : "=r"(processor_id) :); | ||
193 | if (!(processor_id & 0xf)) { | ||
194 | printk("IXP4XXX Watchdog: Rev. A0 CPU detected - " | ||
195 | "watchdog disabled\n"); | ||
196 | |||
197 | return -ENODEV; | ||
198 | } | ||
199 | |||
200 | ret = misc_register(&ixp4xx_wdt_miscdev); | ||
201 | if (ret == 0) | ||
202 | printk("IXP4xx Watchdog Timer: heartbeat %d sec\n", heartbeat); | ||
203 | |||
204 | boot_status = (*IXP4XX_OSST & IXP4XX_OSST_TIMER_WARM_RESET) ? | ||
205 | WDIOF_CARDRESET : 0; | ||
206 | |||
207 | return ret; | ||
208 | } | ||
209 | |||
210 | static void __exit ixp4xx_wdt_exit(void) | ||
211 | { | ||
212 | misc_deregister(&ixp4xx_wdt_miscdev); | ||
213 | } | ||
214 | |||
215 | |||
216 | module_init(ixp4xx_wdt_init); | ||
217 | module_exit(ixp4xx_wdt_exit); | ||
218 | |||
219 | MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); | ||
220 | MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog"); | ||
221 | |||
222 | module_param(heartbeat, int, 0); | ||
223 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); | ||
224 | |||
225 | module_param(nowayout, int, 0); | ||
226 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); | ||
227 | |||
228 | MODULE_LICENSE("GPL"); | ||
229 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
230 | |||
diff --git a/drivers/char/watchdog/machzwd.c b/drivers/char/watchdog/machzwd.c new file mode 100644 index 000000000000..9da395fa7794 --- /dev/null +++ b/drivers/char/watchdog/machzwd.c | |||
@@ -0,0 +1,501 @@ | |||
1 | /* | ||
2 | * MachZ ZF-Logic Watchdog Timer driver for Linux | ||
3 | * | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or | ||
6 | * modify it under the terms of the GNU General Public License | ||
7 | * as published by the Free Software Foundation; either version | ||
8 | * 2 of the License, or (at your option) any later version. | ||
9 | * | ||
10 | * The author does NOT admit liability nor provide warranty for | ||
11 | * any of this software. This material is provided "AS-IS" in | ||
12 | * the hope that it may be useful for others. | ||
13 | * | ||
14 | * Author: Fernando Fuganti <fuganti@conectiva.com.br> | ||
15 | * | ||
16 | * Based on sbc60xxwdt.c by Jakob Oestergaard | ||
17 | * | ||
18 | * | ||
19 | * We have two timers (wd#1, wd#2) driven by a 32 KHz clock with the | ||
20 | * following periods: | ||
21 | * wd#1 - 2 seconds; | ||
22 | * wd#2 - 7.2 ms; | ||
23 | * After the expiration of wd#1, it can generate a NMI, SCI, SMI, or | ||
24 | * a system RESET and it starts wd#2 that unconditionaly will RESET | ||
25 | * the system when the counter reaches zero. | ||
26 | * | ||
27 | * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> | ||
28 | * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT | ||
29 | */ | ||
30 | |||
31 | #include <linux/config.h> | ||
32 | #include <linux/module.h> | ||
33 | #include <linux/moduleparam.h> | ||
34 | #include <linux/types.h> | ||
35 | #include <linux/timer.h> | ||
36 | #include <linux/jiffies.h> | ||
37 | #include <linux/miscdevice.h> | ||
38 | #include <linux/watchdog.h> | ||
39 | #include <linux/fs.h> | ||
40 | #include <linux/ioport.h> | ||
41 | #include <linux/notifier.h> | ||
42 | #include <linux/reboot.h> | ||
43 | #include <linux/init.h> | ||
44 | |||
45 | #include <asm/io.h> | ||
46 | #include <asm/uaccess.h> | ||
47 | #include <asm/system.h> | ||
48 | |||
49 | /* ports */ | ||
50 | #define ZF_IOBASE 0x218 | ||
51 | #define INDEX 0x218 | ||
52 | #define DATA_B 0x219 | ||
53 | #define DATA_W 0x21A | ||
54 | #define DATA_D 0x21A | ||
55 | |||
56 | /* indexes */ /* size */ | ||
57 | #define ZFL_VERSION 0x02 /* 16 */ | ||
58 | #define CONTROL 0x10 /* 16 */ | ||
59 | #define STATUS 0x12 /* 8 */ | ||
60 | #define COUNTER_1 0x0C /* 16 */ | ||
61 | #define COUNTER_2 0x0E /* 8 */ | ||
62 | #define PULSE_LEN 0x0F /* 8 */ | ||
63 | |||
64 | /* controls */ | ||
65 | #define ENABLE_WD1 0x0001 | ||
66 | #define ENABLE_WD2 0x0002 | ||
67 | #define RESET_WD1 0x0010 | ||
68 | #define RESET_WD2 0x0020 | ||
69 | #define GEN_SCI 0x0100 | ||
70 | #define GEN_NMI 0x0200 | ||
71 | #define GEN_SMI 0x0400 | ||
72 | #define GEN_RESET 0x0800 | ||
73 | |||
74 | |||
75 | /* utilities */ | ||
76 | |||
77 | #define WD1 0 | ||
78 | #define WD2 1 | ||
79 | |||
80 | #define zf_writew(port, data) { outb(port, INDEX); outw(data, DATA_W); } | ||
81 | #define zf_writeb(port, data) { outb(port, INDEX); outb(data, DATA_B); } | ||
82 | #define zf_get_ZFL_version() zf_readw(ZFL_VERSION) | ||
83 | |||
84 | |||
85 | static unsigned short zf_readw(unsigned char port) | ||
86 | { | ||
87 | outb(port, INDEX); | ||
88 | return inw(DATA_W); | ||
89 | } | ||
90 | |||
91 | |||
92 | MODULE_AUTHOR("Fernando Fuganti <fuganti@conectiva.com.br>"); | ||
93 | MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver"); | ||
94 | MODULE_LICENSE("GPL"); | ||
95 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
96 | |||
97 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
98 | static int nowayout = 1; | ||
99 | #else | ||
100 | static int nowayout = 0; | ||
101 | #endif | ||
102 | |||
103 | module_param(nowayout, int, 0); | ||
104 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
105 | |||
106 | #define PFX "machzwd" | ||
107 | |||
108 | static struct watchdog_info zf_info = { | ||
109 | .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | ||
110 | .firmware_version = 1, | ||
111 | .identity = "ZF-Logic watchdog", | ||
112 | }; | ||
113 | |||
114 | |||
115 | /* | ||
116 | * action refers to action taken when watchdog resets | ||
117 | * 0 = GEN_RESET | ||
118 | * 1 = GEN_SMI | ||
119 | * 2 = GEN_NMI | ||
120 | * 3 = GEN_SCI | ||
121 | * defaults to GEN_RESET (0) | ||
122 | */ | ||
123 | static int action = 0; | ||
124 | module_param(action, int, 0); | ||
125 | MODULE_PARM_DESC(action, "after watchdog resets, generate: 0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI"); | ||
126 | |||
127 | static int zf_action = GEN_RESET; | ||
128 | static unsigned long zf_is_open; | ||
129 | static char zf_expect_close; | ||
130 | static spinlock_t zf_lock; | ||
131 | static spinlock_t zf_port_lock; | ||
132 | static struct timer_list zf_timer; | ||
133 | static unsigned long next_heartbeat = 0; | ||
134 | |||
135 | |||
136 | /* timeout for user land heart beat (10 seconds) */ | ||
137 | #define ZF_USER_TIMEO (HZ*10) | ||
138 | |||
139 | /* timeout for hardware watchdog (~500ms) */ | ||
140 | #define ZF_HW_TIMEO (HZ/2) | ||
141 | |||
142 | /* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */ | ||
143 | #define ZF_CTIMEOUT 0xffff | ||
144 | |||
145 | #ifndef ZF_DEBUG | ||
146 | # define dprintk(format, args...) | ||
147 | #else | ||
148 | # define dprintk(format, args...) printk(KERN_DEBUG PFX ":%s:%d: " format, __FUNCTION__, __LINE__ , ## args) | ||
149 | #endif | ||
150 | |||
151 | |||
152 | static inline void zf_set_status(unsigned char new) | ||
153 | { | ||
154 | zf_writeb(STATUS, new); | ||
155 | } | ||
156 | |||
157 | |||
158 | /* CONTROL register functions */ | ||
159 | |||
160 | static inline unsigned short zf_get_control(void) | ||
161 | { | ||
162 | return zf_readw(CONTROL); | ||
163 | } | ||
164 | |||
165 | static inline void zf_set_control(unsigned short new) | ||
166 | { | ||
167 | zf_writew(CONTROL, new); | ||
168 | } | ||
169 | |||
170 | |||
171 | /* WD#? counter functions */ | ||
172 | /* | ||
173 | * Just set counter value | ||
174 | */ | ||
175 | |||
176 | static inline void zf_set_timer(unsigned short new, unsigned char n) | ||
177 | { | ||
178 | switch(n){ | ||
179 | case WD1: | ||
180 | zf_writew(COUNTER_1, new); | ||
181 | case WD2: | ||
182 | zf_writeb(COUNTER_2, new > 0xff ? 0xff : new); | ||
183 | default: | ||
184 | return; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | /* | ||
189 | * stop hardware timer | ||
190 | */ | ||
191 | static void zf_timer_off(void) | ||
192 | { | ||
193 | unsigned int ctrl_reg = 0; | ||
194 | unsigned long flags; | ||
195 | |||
196 | /* stop internal ping */ | ||
197 | del_timer_sync(&zf_timer); | ||
198 | |||
199 | spin_lock_irqsave(&zf_port_lock, flags); | ||
200 | /* stop watchdog timer */ | ||
201 | ctrl_reg = zf_get_control(); | ||
202 | ctrl_reg |= (ENABLE_WD1|ENABLE_WD2); /* disable wd1 and wd2 */ | ||
203 | ctrl_reg &= ~(ENABLE_WD1|ENABLE_WD2); | ||
204 | zf_set_control(ctrl_reg); | ||
205 | spin_unlock_irqrestore(&zf_port_lock, flags); | ||
206 | |||
207 | printk(KERN_INFO PFX ": Watchdog timer is now disabled\n"); | ||
208 | } | ||
209 | |||
210 | |||
211 | /* | ||
212 | * start hardware timer | ||
213 | */ | ||
214 | static void zf_timer_on(void) | ||
215 | { | ||
216 | unsigned int ctrl_reg = 0; | ||
217 | unsigned long flags; | ||
218 | |||
219 | spin_lock_irqsave(&zf_port_lock, flags); | ||
220 | |||
221 | zf_writeb(PULSE_LEN, 0xff); | ||
222 | |||
223 | zf_set_timer(ZF_CTIMEOUT, WD1); | ||
224 | |||
225 | /* user land ping */ | ||
226 | next_heartbeat = jiffies + ZF_USER_TIMEO; | ||
227 | |||
228 | /* start the timer for internal ping */ | ||
229 | zf_timer.expires = jiffies + ZF_HW_TIMEO; | ||
230 | |||
231 | add_timer(&zf_timer); | ||
232 | |||
233 | /* start watchdog timer */ | ||
234 | ctrl_reg = zf_get_control(); | ||
235 | ctrl_reg |= (ENABLE_WD1|zf_action); | ||
236 | zf_set_control(ctrl_reg); | ||
237 | spin_unlock_irqrestore(&zf_port_lock, flags); | ||
238 | |||
239 | printk(KERN_INFO PFX ": Watchdog timer is now enabled\n"); | ||
240 | } | ||
241 | |||
242 | |||
243 | static void zf_ping(unsigned long data) | ||
244 | { | ||
245 | unsigned int ctrl_reg = 0; | ||
246 | unsigned long flags; | ||
247 | |||
248 | zf_writeb(COUNTER_2, 0xff); | ||
249 | |||
250 | if(time_before(jiffies, next_heartbeat)){ | ||
251 | |||
252 | dprintk("time_before: %ld\n", next_heartbeat - jiffies); | ||
253 | |||
254 | /* | ||
255 | * reset event is activated by transition from 0 to 1 on | ||
256 | * RESET_WD1 bit and we assume that it is already zero... | ||
257 | */ | ||
258 | |||
259 | spin_lock_irqsave(&zf_port_lock, flags); | ||
260 | ctrl_reg = zf_get_control(); | ||
261 | ctrl_reg |= RESET_WD1; | ||
262 | zf_set_control(ctrl_reg); | ||
263 | |||
264 | /* ...and nothing changes until here */ | ||
265 | ctrl_reg &= ~(RESET_WD1); | ||
266 | zf_set_control(ctrl_reg); | ||
267 | spin_unlock_irqrestore(&zf_port_lock, flags); | ||
268 | |||
269 | zf_timer.expires = jiffies + ZF_HW_TIMEO; | ||
270 | add_timer(&zf_timer); | ||
271 | }else{ | ||
272 | printk(KERN_CRIT PFX ": I will reset your machine\n"); | ||
273 | } | ||
274 | } | ||
275 | |||
276 | static ssize_t zf_write(struct file *file, const char __user *buf, size_t count, | ||
277 | loff_t *ppos) | ||
278 | { | ||
279 | /* See if we got the magic character */ | ||
280 | if(count){ | ||
281 | |||
282 | /* | ||
283 | * no need to check for close confirmation | ||
284 | * no way to disable watchdog ;) | ||
285 | */ | ||
286 | if (!nowayout) { | ||
287 | size_t ofs; | ||
288 | |||
289 | /* | ||
290 | * note: just in case someone wrote the magic character | ||
291 | * five months ago... | ||
292 | */ | ||
293 | zf_expect_close = 0; | ||
294 | |||
295 | /* now scan */ | ||
296 | for (ofs = 0; ofs != count; ofs++){ | ||
297 | char c; | ||
298 | if (get_user(c, buf + ofs)) | ||
299 | return -EFAULT; | ||
300 | if (c == 'V'){ | ||
301 | zf_expect_close = 42; | ||
302 | dprintk("zf_expect_close = 42\n"); | ||
303 | } | ||
304 | } | ||
305 | } | ||
306 | |||
307 | /* | ||
308 | * Well, anyhow someone wrote to us, | ||
309 | * we should return that favour | ||
310 | */ | ||
311 | next_heartbeat = jiffies + ZF_USER_TIMEO; | ||
312 | dprintk("user ping at %ld\n", jiffies); | ||
313 | |||
314 | } | ||
315 | |||
316 | return count; | ||
317 | } | ||
318 | |||
319 | static int zf_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
320 | unsigned long arg) | ||
321 | { | ||
322 | void __user *argp = (void __user *)arg; | ||
323 | int __user *p = argp; | ||
324 | switch(cmd){ | ||
325 | case WDIOC_GETSUPPORT: | ||
326 | if (copy_to_user(argp, &zf_info, sizeof(zf_info))) | ||
327 | return -EFAULT; | ||
328 | break; | ||
329 | |||
330 | case WDIOC_GETSTATUS: | ||
331 | return put_user(0, p); | ||
332 | |||
333 | case WDIOC_KEEPALIVE: | ||
334 | zf_ping(0); | ||
335 | break; | ||
336 | |||
337 | default: | ||
338 | return -ENOIOCTLCMD; | ||
339 | } | ||
340 | |||
341 | return 0; | ||
342 | } | ||
343 | |||
344 | static int zf_open(struct inode *inode, struct file *file) | ||
345 | { | ||
346 | spin_lock(&zf_lock); | ||
347 | if(test_and_set_bit(0, &zf_is_open)) { | ||
348 | spin_unlock(&zf_lock); | ||
349 | return -EBUSY; | ||
350 | } | ||
351 | |||
352 | if (nowayout) | ||
353 | __module_get(THIS_MODULE); | ||
354 | |||
355 | spin_unlock(&zf_lock); | ||
356 | |||
357 | zf_timer_on(); | ||
358 | |||
359 | return nonseekable_open(inode, file); | ||
360 | } | ||
361 | |||
362 | static int zf_close(struct inode *inode, struct file *file) | ||
363 | { | ||
364 | if(zf_expect_close == 42){ | ||
365 | zf_timer_off(); | ||
366 | } else { | ||
367 | del_timer(&zf_timer); | ||
368 | printk(KERN_ERR PFX ": device file closed unexpectedly. Will not stop the WDT!\n"); | ||
369 | } | ||
370 | |||
371 | spin_lock(&zf_lock); | ||
372 | clear_bit(0, &zf_is_open); | ||
373 | spin_unlock(&zf_lock); | ||
374 | |||
375 | zf_expect_close = 0; | ||
376 | |||
377 | return 0; | ||
378 | } | ||
379 | |||
380 | /* | ||
381 | * Notifier for system down | ||
382 | */ | ||
383 | |||
384 | static int zf_notify_sys(struct notifier_block *this, unsigned long code, | ||
385 | void *unused) | ||
386 | { | ||
387 | if(code == SYS_DOWN || code == SYS_HALT){ | ||
388 | zf_timer_off(); | ||
389 | } | ||
390 | |||
391 | return NOTIFY_DONE; | ||
392 | } | ||
393 | |||
394 | |||
395 | |||
396 | |||
397 | static struct file_operations zf_fops = { | ||
398 | .owner = THIS_MODULE, | ||
399 | .llseek = no_llseek, | ||
400 | .write = zf_write, | ||
401 | .ioctl = zf_ioctl, | ||
402 | .open = zf_open, | ||
403 | .release = zf_close, | ||
404 | }; | ||
405 | |||
406 | static struct miscdevice zf_miscdev = { | ||
407 | .minor = WATCHDOG_MINOR, | ||
408 | .name = "watchdog", | ||
409 | .fops = &zf_fops, | ||
410 | }; | ||
411 | |||
412 | |||
413 | /* | ||
414 | * The device needs to learn about soft shutdowns in order to | ||
415 | * turn the timebomb registers off. | ||
416 | */ | ||
417 | static struct notifier_block zf_notifier = { | ||
418 | .notifier_call = zf_notify_sys, | ||
419 | }; | ||
420 | |||
421 | static void __init zf_show_action(int act) | ||
422 | { | ||
423 | char *str[] = { "RESET", "SMI", "NMI", "SCI" }; | ||
424 | |||
425 | printk(KERN_INFO PFX ": Watchdog using action = %s\n", str[act]); | ||
426 | } | ||
427 | |||
428 | static int __init zf_init(void) | ||
429 | { | ||
430 | int ret; | ||
431 | |||
432 | printk(KERN_INFO PFX ": MachZ ZF-Logic Watchdog driver initializing.\n"); | ||
433 | |||
434 | ret = zf_get_ZFL_version(); | ||
435 | printk("%#x\n", ret); | ||
436 | if((!ret) || (ret != 0xffff)){ | ||
437 | printk(KERN_WARNING PFX ": no ZF-Logic found\n"); | ||
438 | return -ENODEV; | ||
439 | } | ||
440 | |||
441 | if((action <= 3) && (action >= 0)){ | ||
442 | zf_action = zf_action>>action; | ||
443 | } else | ||
444 | action = 0; | ||
445 | |||
446 | zf_show_action(action); | ||
447 | |||
448 | spin_lock_init(&zf_lock); | ||
449 | spin_lock_init(&zf_port_lock); | ||
450 | |||
451 | ret = misc_register(&zf_miscdev); | ||
452 | if (ret){ | ||
453 | printk(KERN_ERR "can't misc_register on minor=%d\n", | ||
454 | WATCHDOG_MINOR); | ||
455 | goto out; | ||
456 | } | ||
457 | |||
458 | if(!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")){ | ||
459 | printk(KERN_ERR "cannot reserve I/O ports at %d\n", | ||
460 | ZF_IOBASE); | ||
461 | ret = -EBUSY; | ||
462 | goto no_region; | ||
463 | } | ||
464 | |||
465 | ret = register_reboot_notifier(&zf_notifier); | ||
466 | if(ret){ | ||
467 | printk(KERN_ERR "can't register reboot notifier (err=%d)\n", | ||
468 | ret); | ||
469 | goto no_reboot; | ||
470 | } | ||
471 | |||
472 | zf_set_status(0); | ||
473 | zf_set_control(0); | ||
474 | |||
475 | /* this is the timer that will do the hard work */ | ||
476 | init_timer(&zf_timer); | ||
477 | zf_timer.function = zf_ping; | ||
478 | zf_timer.data = 0; | ||
479 | |||
480 | return 0; | ||
481 | |||
482 | no_reboot: | ||
483 | release_region(ZF_IOBASE, 3); | ||
484 | no_region: | ||
485 | misc_deregister(&zf_miscdev); | ||
486 | out: | ||
487 | return ret; | ||
488 | } | ||
489 | |||
490 | |||
491 | static void __exit zf_exit(void) | ||
492 | { | ||
493 | zf_timer_off(); | ||
494 | |||
495 | misc_deregister(&zf_miscdev); | ||
496 | unregister_reboot_notifier(&zf_notifier); | ||
497 | release_region(ZF_IOBASE, 3); | ||
498 | } | ||
499 | |||
500 | module_init(zf_init); | ||
501 | module_exit(zf_exit); | ||
diff --git a/drivers/char/watchdog/mixcomwd.c b/drivers/char/watchdog/mixcomwd.c new file mode 100644 index 000000000000..3143e4a07535 --- /dev/null +++ b/drivers/char/watchdog/mixcomwd.c | |||
@@ -0,0 +1,306 @@ | |||
1 | /* | ||
2 | * MixCom Watchdog: A Simple Hardware Watchdog Device | ||
3 | * Based on Softdog driver by Alan Cox and PC Watchdog driver by Ken Hollis | ||
4 | * | ||
5 | * Author: Gergely Madarasz <gorgo@itc.hu> | ||
6 | * | ||
7 | * Copyright (c) 1999 ITConsult-Pro Co. <info@itc.hu> | ||
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 | * Version 0.1 (99/04/15): | ||
15 | * - first version | ||
16 | * | ||
17 | * Version 0.2 (99/06/16): | ||
18 | * - added kernel timer watchdog ping after close | ||
19 | * since the hardware does not support watchdog shutdown | ||
20 | * | ||
21 | * Version 0.3 (99/06/21): | ||
22 | * - added WDIOC_GETSTATUS and WDIOC_GETSUPPORT ioctl calls | ||
23 | * | ||
24 | * Version 0.3.1 (99/06/22): | ||
25 | * - allow module removal while internal timer is active, | ||
26 | * print warning about probable reset | ||
27 | * | ||
28 | * Version 0.4 (99/11/15): | ||
29 | * - support for one more type board | ||
30 | * | ||
31 | * Version 0.5 (2001/12/14) Matt Domsch <Matt_Domsch@dell.com> | ||
32 | * - added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT | ||
33 | * | ||
34 | */ | ||
35 | |||
36 | #define VERSION "0.5" | ||
37 | |||
38 | #include <linux/module.h> | ||
39 | #include <linux/moduleparam.h> | ||
40 | #include <linux/config.h> | ||
41 | #include <linux/types.h> | ||
42 | #include <linux/miscdevice.h> | ||
43 | #include <linux/ioport.h> | ||
44 | #include <linux/watchdog.h> | ||
45 | #include <linux/fs.h> | ||
46 | #include <linux/reboot.h> | ||
47 | #include <linux/init.h> | ||
48 | #include <asm/uaccess.h> | ||
49 | #include <asm/io.h> | ||
50 | |||
51 | static int mixcomwd_ioports[] = { 0x180, 0x280, 0x380, 0x000 }; | ||
52 | |||
53 | #define MIXCOM_WATCHDOG_OFFSET 0xc10 | ||
54 | #define MIXCOM_ID 0x11 | ||
55 | #define FLASHCOM_WATCHDOG_OFFSET 0x4 | ||
56 | #define FLASHCOM_ID 0x18 | ||
57 | |||
58 | static unsigned long mixcomwd_opened; /* long req'd for setbit --RR */ | ||
59 | |||
60 | static int watchdog_port; | ||
61 | static int mixcomwd_timer_alive; | ||
62 | static struct timer_list mixcomwd_timer = TIMER_INITIALIZER(NULL, 0, 0); | ||
63 | static char expect_close; | ||
64 | |||
65 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
66 | static int nowayout = 1; | ||
67 | #else | ||
68 | static int nowayout = 0; | ||
69 | #endif | ||
70 | |||
71 | module_param(nowayout, int, 0); | ||
72 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
73 | |||
74 | static void mixcomwd_ping(void) | ||
75 | { | ||
76 | outb_p(55,watchdog_port); | ||
77 | return; | ||
78 | } | ||
79 | |||
80 | static void mixcomwd_timerfun(unsigned long d) | ||
81 | { | ||
82 | mixcomwd_ping(); | ||
83 | |||
84 | mod_timer(&mixcomwd_timer,jiffies+ 5*HZ); | ||
85 | } | ||
86 | |||
87 | /* | ||
88 | * Allow only one person to hold it open | ||
89 | */ | ||
90 | |||
91 | static int mixcomwd_open(struct inode *inode, struct file *file) | ||
92 | { | ||
93 | if(test_and_set_bit(0,&mixcomwd_opened)) { | ||
94 | return -EBUSY; | ||
95 | } | ||
96 | mixcomwd_ping(); | ||
97 | |||
98 | if (nowayout) { | ||
99 | /* | ||
100 | * fops_get() code via open() has already done | ||
101 | * a try_module_get() so it is safe to do the | ||
102 | * __module_get(). | ||
103 | */ | ||
104 | __module_get(THIS_MODULE); | ||
105 | } else { | ||
106 | if(mixcomwd_timer_alive) { | ||
107 | del_timer(&mixcomwd_timer); | ||
108 | mixcomwd_timer_alive=0; | ||
109 | } | ||
110 | } | ||
111 | return nonseekable_open(inode, file); | ||
112 | } | ||
113 | |||
114 | static int mixcomwd_release(struct inode *inode, struct file *file) | ||
115 | { | ||
116 | if (expect_close == 42) { | ||
117 | if(mixcomwd_timer_alive) { | ||
118 | printk(KERN_ERR "mixcomwd: release called while internal timer alive"); | ||
119 | return -EBUSY; | ||
120 | } | ||
121 | init_timer(&mixcomwd_timer); | ||
122 | mixcomwd_timer.expires=jiffies + 5 * HZ; | ||
123 | mixcomwd_timer.function=mixcomwd_timerfun; | ||
124 | mixcomwd_timer.data=0; | ||
125 | mixcomwd_timer_alive=1; | ||
126 | add_timer(&mixcomwd_timer); | ||
127 | } else { | ||
128 | printk(KERN_CRIT "mixcomwd: WDT device closed unexpectedly. WDT will not stop!\n"); | ||
129 | } | ||
130 | |||
131 | clear_bit(0,&mixcomwd_opened); | ||
132 | expect_close=0; | ||
133 | return 0; | ||
134 | } | ||
135 | |||
136 | |||
137 | static ssize_t mixcomwd_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) | ||
138 | { | ||
139 | if(len) | ||
140 | { | ||
141 | if (!nowayout) { | ||
142 | size_t i; | ||
143 | |||
144 | /* In case it was set long ago */ | ||
145 | expect_close = 0; | ||
146 | |||
147 | for (i = 0; i != len; i++) { | ||
148 | char c; | ||
149 | if (get_user(c, data + i)) | ||
150 | return -EFAULT; | ||
151 | if (c == 'V') | ||
152 | expect_close = 42; | ||
153 | } | ||
154 | } | ||
155 | mixcomwd_ping(); | ||
156 | } | ||
157 | return len; | ||
158 | } | ||
159 | |||
160 | static int mixcomwd_ioctl(struct inode *inode, struct file *file, | ||
161 | unsigned int cmd, unsigned long arg) | ||
162 | { | ||
163 | void __user *argp = (void __user *)arg; | ||
164 | int __user *p = argp; | ||
165 | int status; | ||
166 | static struct watchdog_info ident = { | ||
167 | .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | ||
168 | .firmware_version = 1, | ||
169 | .identity = "MixCOM watchdog", | ||
170 | }; | ||
171 | |||
172 | switch(cmd) | ||
173 | { | ||
174 | case WDIOC_GETSTATUS: | ||
175 | status=mixcomwd_opened; | ||
176 | if (!nowayout) { | ||
177 | status|=mixcomwd_timer_alive; | ||
178 | } | ||
179 | if (copy_to_user(p, &status, sizeof(int))) { | ||
180 | return -EFAULT; | ||
181 | } | ||
182 | break; | ||
183 | case WDIOC_GETSUPPORT: | ||
184 | if (copy_to_user(argp, &ident, sizeof(ident))) { | ||
185 | return -EFAULT; | ||
186 | } | ||
187 | break; | ||
188 | case WDIOC_KEEPALIVE: | ||
189 | mixcomwd_ping(); | ||
190 | break; | ||
191 | default: | ||
192 | return -ENOIOCTLCMD; | ||
193 | } | ||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | static struct file_operations mixcomwd_fops= | ||
198 | { | ||
199 | .owner = THIS_MODULE, | ||
200 | .llseek = no_llseek, | ||
201 | .write = mixcomwd_write, | ||
202 | .ioctl = mixcomwd_ioctl, | ||
203 | .open = mixcomwd_open, | ||
204 | .release = mixcomwd_release, | ||
205 | }; | ||
206 | |||
207 | static struct miscdevice mixcomwd_miscdev= | ||
208 | { | ||
209 | .minor = WATCHDOG_MINOR, | ||
210 | .name = "watchdog", | ||
211 | .fops = &mixcomwd_fops, | ||
212 | }; | ||
213 | |||
214 | static int __init mixcomwd_checkcard(int port) | ||
215 | { | ||
216 | int id; | ||
217 | |||
218 | port += MIXCOM_WATCHDOG_OFFSET; | ||
219 | if (!request_region(port, 1, "MixCOM watchdog")) { | ||
220 | return 0; | ||
221 | } | ||
222 | |||
223 | id=inb_p(port) & 0x3f; | ||
224 | if(id!=MIXCOM_ID) { | ||
225 | release_region(port, 1); | ||
226 | return 0; | ||
227 | } | ||
228 | return port; | ||
229 | } | ||
230 | |||
231 | static int __init flashcom_checkcard(int port) | ||
232 | { | ||
233 | int id; | ||
234 | |||
235 | port += FLASHCOM_WATCHDOG_OFFSET; | ||
236 | if (!request_region(port, 1, "MixCOM watchdog")) { | ||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | id=inb_p(port); | ||
241 | if(id!=FLASHCOM_ID) { | ||
242 | release_region(port, 1); | ||
243 | return 0; | ||
244 | } | ||
245 | return port; | ||
246 | } | ||
247 | |||
248 | static int __init mixcomwd_init(void) | ||
249 | { | ||
250 | int i; | ||
251 | int ret; | ||
252 | int found=0; | ||
253 | |||
254 | for (i = 0; !found && mixcomwd_ioports[i] != 0; i++) { | ||
255 | watchdog_port = mixcomwd_checkcard(mixcomwd_ioports[i]); | ||
256 | if (watchdog_port) { | ||
257 | found = 1; | ||
258 | } | ||
259 | } | ||
260 | |||
261 | /* The FlashCOM card can be set up at 0x300 -> 0x378, in 0x8 jumps */ | ||
262 | for (i = 0x300; !found && i < 0x380; i+=0x8) { | ||
263 | watchdog_port = flashcom_checkcard(i); | ||
264 | if (watchdog_port) { | ||
265 | found = 1; | ||
266 | } | ||
267 | } | ||
268 | |||
269 | if (!found) { | ||
270 | printk("mixcomwd: No card detected, or port not available.\n"); | ||
271 | return -ENODEV; | ||
272 | } | ||
273 | |||
274 | ret = misc_register(&mixcomwd_miscdev); | ||
275 | if (ret) | ||
276 | { | ||
277 | release_region(watchdog_port, 1); | ||
278 | return ret; | ||
279 | } | ||
280 | |||
281 | printk(KERN_INFO "MixCOM watchdog driver v%s, watchdog port at 0x%3x\n",VERSION,watchdog_port); | ||
282 | |||
283 | return 0; | ||
284 | } | ||
285 | |||
286 | static void __exit mixcomwd_exit(void) | ||
287 | { | ||
288 | if (!nowayout) { | ||
289 | if(mixcomwd_timer_alive) { | ||
290 | printk(KERN_WARNING "mixcomwd: I quit now, hardware will" | ||
291 | " probably reboot!\n"); | ||
292 | del_timer(&mixcomwd_timer); | ||
293 | mixcomwd_timer_alive=0; | ||
294 | } | ||
295 | } | ||
296 | release_region(watchdog_port,1); | ||
297 | misc_deregister(&mixcomwd_miscdev); | ||
298 | } | ||
299 | |||
300 | module_init(mixcomwd_init); | ||
301 | module_exit(mixcomwd_exit); | ||
302 | |||
303 | MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>"); | ||
304 | MODULE_DESCRIPTION("MixCom Watchdog driver"); | ||
305 | MODULE_LICENSE("GPL"); | ||
306 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/mpc8xx_wdt.c b/drivers/char/watchdog/mpc8xx_wdt.c new file mode 100644 index 000000000000..56d62ba7c6ce --- /dev/null +++ b/drivers/char/watchdog/mpc8xx_wdt.c | |||
@@ -0,0 +1,164 @@ | |||
1 | /* | ||
2 | * mpc8xx_wdt.c - MPC8xx watchdog userspace interface | ||
3 | * | ||
4 | * Author: Florian Schirmer <jolt@tuxbox.org> | ||
5 | * | ||
6 | * 2002 (c) Florian Schirmer <jolt@tuxbox.org> This file is licensed under | ||
7 | * the terms of the GNU General Public License version 2. This program | ||
8 | * is licensed "as is" without any warranty of any kind, whether express | ||
9 | * or implied. | ||
10 | */ | ||
11 | |||
12 | #include <linux/config.h> | ||
13 | #include <linux/fs.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/miscdevice.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/watchdog.h> | ||
19 | #include <asm/8xx_immap.h> | ||
20 | #include <asm/uaccess.h> | ||
21 | #include <syslib/m8xx_wdt.h> | ||
22 | |||
23 | static unsigned long wdt_opened; | ||
24 | static int wdt_status; | ||
25 | |||
26 | static void mpc8xx_wdt_handler_disable(void) | ||
27 | { | ||
28 | volatile immap_t *imap = (volatile immap_t *)IMAP_ADDR; | ||
29 | |||
30 | imap->im_sit.sit_piscr &= ~(PISCR_PIE | PISCR_PTE); | ||
31 | |||
32 | printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler deactivated\n"); | ||
33 | } | ||
34 | |||
35 | static void mpc8xx_wdt_handler_enable(void) | ||
36 | { | ||
37 | volatile immap_t *imap = (volatile immap_t *)IMAP_ADDR; | ||
38 | |||
39 | imap->im_sit.sit_piscr |= PISCR_PIE | PISCR_PTE; | ||
40 | |||
41 | printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler activated\n"); | ||
42 | } | ||
43 | |||
44 | static int mpc8xx_wdt_open(struct inode *inode, struct file *file) | ||
45 | { | ||
46 | if (test_and_set_bit(0, &wdt_opened)) | ||
47 | return -EBUSY; | ||
48 | |||
49 | m8xx_wdt_reset(); | ||
50 | mpc8xx_wdt_handler_disable(); | ||
51 | |||
52 | return 0; | ||
53 | } | ||
54 | |||
55 | static int mpc8xx_wdt_release(struct inode *inode, struct file *file) | ||
56 | { | ||
57 | m8xx_wdt_reset(); | ||
58 | |||
59 | #if !defined(CONFIG_WATCHDOG_NOWAYOUT) | ||
60 | mpc8xx_wdt_handler_enable(); | ||
61 | #endif | ||
62 | |||
63 | clear_bit(0, &wdt_opened); | ||
64 | |||
65 | return 0; | ||
66 | } | ||
67 | |||
68 | static ssize_t mpc8xx_wdt_write(struct file *file, const char *data, size_t len, | ||
69 | loff_t * ppos) | ||
70 | { | ||
71 | if (ppos != &file->f_pos) | ||
72 | return -ESPIPE; | ||
73 | |||
74 | if (len) | ||
75 | m8xx_wdt_reset(); | ||
76 | |||
77 | return len; | ||
78 | } | ||
79 | |||
80 | static int mpc8xx_wdt_ioctl(struct inode *inode, struct file *file, | ||
81 | unsigned int cmd, unsigned long arg) | ||
82 | { | ||
83 | int timeout; | ||
84 | static struct watchdog_info info = { | ||
85 | .options = WDIOF_KEEPALIVEPING, | ||
86 | .firmware_version = 0, | ||
87 | .identity = "MPC8xx watchdog", | ||
88 | }; | ||
89 | |||
90 | switch (cmd) { | ||
91 | case WDIOC_GETSUPPORT: | ||
92 | if (copy_to_user((void *)arg, &info, sizeof(info))) | ||
93 | return -EFAULT; | ||
94 | break; | ||
95 | |||
96 | case WDIOC_GETSTATUS: | ||
97 | case WDIOC_GETBOOTSTATUS: | ||
98 | if (put_user(wdt_status, (int *)arg)) | ||
99 | return -EFAULT; | ||
100 | wdt_status &= ~WDIOF_KEEPALIVEPING; | ||
101 | break; | ||
102 | |||
103 | case WDIOC_GETTEMP: | ||
104 | return -EOPNOTSUPP; | ||
105 | |||
106 | case WDIOC_SETOPTIONS: | ||
107 | return -EOPNOTSUPP; | ||
108 | |||
109 | case WDIOC_KEEPALIVE: | ||
110 | m8xx_wdt_reset(); | ||
111 | wdt_status |= WDIOF_KEEPALIVEPING; | ||
112 | break; | ||
113 | |||
114 | case WDIOC_SETTIMEOUT: | ||
115 | return -EOPNOTSUPP; | ||
116 | |||
117 | case WDIOC_GETTIMEOUT: | ||
118 | timeout = m8xx_wdt_get_timeout(); | ||
119 | if (put_user(timeout, (int *)arg)) | ||
120 | return -EFAULT; | ||
121 | break; | ||
122 | |||
123 | default: | ||
124 | return -ENOIOCTLCMD; | ||
125 | } | ||
126 | |||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | static struct file_operations mpc8xx_wdt_fops = { | ||
131 | .owner = THIS_MODULE, | ||
132 | .llseek = no_llseek, | ||
133 | .write = mpc8xx_wdt_write, | ||
134 | .ioctl = mpc8xx_wdt_ioctl, | ||
135 | .open = mpc8xx_wdt_open, | ||
136 | .release = mpc8xx_wdt_release, | ||
137 | }; | ||
138 | |||
139 | static struct miscdevice mpc8xx_wdt_miscdev = { | ||
140 | .minor = WATCHDOG_MINOR, | ||
141 | .name = "watchdog", | ||
142 | .fops = &mpc8xx_wdt_fops, | ||
143 | }; | ||
144 | |||
145 | static int __init mpc8xx_wdt_init(void) | ||
146 | { | ||
147 | return misc_register(&mpc8xx_wdt_miscdev); | ||
148 | } | ||
149 | |||
150 | static void __exit mpc8xx_wdt_exit(void) | ||
151 | { | ||
152 | misc_deregister(&mpc8xx_wdt_miscdev); | ||
153 | |||
154 | m8xx_wdt_reset(); | ||
155 | mpc8xx_wdt_handler_enable(); | ||
156 | } | ||
157 | |||
158 | module_init(mpc8xx_wdt_init); | ||
159 | module_exit(mpc8xx_wdt_exit); | ||
160 | |||
161 | MODULE_AUTHOR("Florian Schirmer <jolt@tuxbox.org>"); | ||
162 | MODULE_DESCRIPTION("MPC8xx watchdog driver"); | ||
163 | MODULE_LICENSE("GPL"); | ||
164 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/pcwd.c b/drivers/char/watchdog/pcwd.c new file mode 100644 index 000000000000..592dca108866 --- /dev/null +++ b/drivers/char/watchdog/pcwd.c | |||
@@ -0,0 +1,926 @@ | |||
1 | /* | ||
2 | * PC Watchdog Driver | ||
3 | * by Ken Hollis (khollis@bitgate.com) | ||
4 | * | ||
5 | * Permission granted from Simon Machell (73244.1270@compuserve.com) | ||
6 | * Written for the Linux Kernel, and GPLed by Ken Hollis | ||
7 | * | ||
8 | * 960107 Added request_region routines, modulized the whole thing. | ||
9 | * 960108 Fixed end-of-file pointer (Thanks to Dan Hollis), added | ||
10 | * WD_TIMEOUT define. | ||
11 | * 960216 Added eof marker on the file, and changed verbose messages. | ||
12 | * 960716 Made functional and cosmetic changes to the source for | ||
13 | * inclusion in Linux 2.0.x kernels, thanks to Alan Cox. | ||
14 | * 960717 Removed read/seek routines, replaced with ioctl. Also, added | ||
15 | * check_region command due to Alan's suggestion. | ||
16 | * 960821 Made changes to compile in newer 2.0.x kernels. Added | ||
17 | * "cold reboot sense" entry. | ||
18 | * 960825 Made a few changes to code, deleted some defines and made | ||
19 | * typedefs to replace them. Made heartbeat reset only available | ||
20 | * via ioctl, and removed the write routine. | ||
21 | * 960828 Added new items for PC Watchdog Rev.C card. | ||
22 | * 960829 Changed around all of the IOCTLs, added new features, | ||
23 | * added watchdog disable/re-enable routines. Added firmware | ||
24 | * version reporting. Added read routine for temperature. | ||
25 | * Removed some extra defines, added an autodetect Revision | ||
26 | * routine. | ||
27 | * 961006 Revised some documentation, fixed some cosmetic bugs. Made | ||
28 | * drivers to panic the system if it's overheating at bootup. | ||
29 | * 961118 Changed some verbiage on some of the output, tidied up | ||
30 | * code bits, and added compatibility to 2.1.x. | ||
31 | * 970912 Enabled board on open and disable on close. | ||
32 | * 971107 Took account of recent VFS changes (broke read). | ||
33 | * 971210 Disable board on initialisation in case board already ticking. | ||
34 | * 971222 Changed open/close for temperature handling | ||
35 | * Michael Meskes <meskes@debian.org>. | ||
36 | * 980112 Used minor numbers from include/linux/miscdevice.h | ||
37 | * 990403 Clear reset status after reading control status register in | ||
38 | * pcwd_showprevstate(). [Marc Boucher <marc@mbsi.ca>] | ||
39 | * 990605 Made changes to code to support Firmware 1.22a, added | ||
40 | * fairly useless proc entry. | ||
41 | * 990610 removed said useless proc code for the merge <alan> | ||
42 | * 000403 Removed last traces of proc code. <davej> | ||
43 | * 011214 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT <Matt_Domsch@dell.com> | ||
44 | * Added timeout module option to override default | ||
45 | */ | ||
46 | |||
47 | /* | ||
48 | * A bells and whistles driver is available from http://www.pcwd.de/ | ||
49 | * More info available at http://www.berkprod.com/ or http://www.pcwatchdog.com/ | ||
50 | */ | ||
51 | |||
52 | #include <linux/module.h> | ||
53 | #include <linux/moduleparam.h> | ||
54 | #include <linux/types.h> | ||
55 | #include <linux/timer.h> | ||
56 | #include <linux/jiffies.h> | ||
57 | #include <linux/config.h> | ||
58 | #include <linux/wait.h> | ||
59 | #include <linux/slab.h> | ||
60 | #include <linux/ioport.h> | ||
61 | #include <linux/delay.h> | ||
62 | #include <linux/fs.h> | ||
63 | #include <linux/miscdevice.h> | ||
64 | #include <linux/watchdog.h> | ||
65 | #include <linux/notifier.h> | ||
66 | #include <linux/init.h> | ||
67 | #include <linux/spinlock.h> | ||
68 | #include <linux/reboot.h> | ||
69 | |||
70 | #include <asm/uaccess.h> | ||
71 | #include <asm/io.h> | ||
72 | |||
73 | #define WD_VER "1.16 (06/12/2004)" | ||
74 | #define PFX "pcwd: " | ||
75 | |||
76 | /* | ||
77 | * It should be noted that PCWD_REVISION_B was removed because A and B | ||
78 | * are essentially the same types of card, with the exception that B | ||
79 | * has temperature reporting. Since I didn't receive a Rev.B card, | ||
80 | * the Rev.B card is not supported. (It's a good thing too, as they | ||
81 | * are no longer in production.) | ||
82 | */ | ||
83 | #define PCWD_REVISION_A 1 | ||
84 | #define PCWD_REVISION_C 2 | ||
85 | |||
86 | /* | ||
87 | * These are the defines that describe the control status bits for the | ||
88 | * PC Watchdog card, revision A. | ||
89 | */ | ||
90 | #define WD_WDRST 0x01 /* Previously reset state */ | ||
91 | #define WD_T110 0x02 /* Temperature overheat sense */ | ||
92 | #define WD_HRTBT 0x04 /* Heartbeat sense */ | ||
93 | #define WD_RLY2 0x08 /* External relay triggered */ | ||
94 | #define WD_SRLY2 0x80 /* Software external relay triggered */ | ||
95 | |||
96 | /* | ||
97 | * These are the defines that describe the control status bits for the | ||
98 | * PC Watchdog card, revision C. | ||
99 | */ | ||
100 | #define WD_REVC_WTRP 0x01 /* Watchdog Trip status */ | ||
101 | #define WD_REVC_HRBT 0x02 /* Watchdog Heartbeat */ | ||
102 | #define WD_REVC_TTRP 0x04 /* Temperature Trip status */ | ||
103 | |||
104 | /* max. time we give an ISA watchdog card to process a command */ | ||
105 | /* 500ms for each 4 bit response (according to spec.) */ | ||
106 | #define ISA_COMMAND_TIMEOUT 1000 | ||
107 | |||
108 | /* Watchdog's internal commands */ | ||
109 | #define CMD_ISA_IDLE 0x00 | ||
110 | #define CMD_ISA_VERSION_INTEGER 0x01 | ||
111 | #define CMD_ISA_VERSION_TENTH 0x02 | ||
112 | #define CMD_ISA_VERSION_HUNDRETH 0x03 | ||
113 | #define CMD_ISA_VERSION_MINOR 0x04 | ||
114 | #define CMD_ISA_SWITCH_SETTINGS 0x05 | ||
115 | #define CMD_ISA_DELAY_TIME_2SECS 0x0A | ||
116 | #define CMD_ISA_DELAY_TIME_4SECS 0x0B | ||
117 | #define CMD_ISA_DELAY_TIME_8SECS 0x0C | ||
118 | |||
119 | /* | ||
120 | * We are using an kernel timer to do the pinging of the watchdog | ||
121 | * every ~500ms. We try to set the internal heartbeat of the | ||
122 | * watchdog to 2 ms. | ||
123 | */ | ||
124 | |||
125 | #define WDT_INTERVAL (HZ/2+1) | ||
126 | |||
127 | /* We can only use 1 card due to the /dev/watchdog restriction */ | ||
128 | static int cards_found; | ||
129 | |||
130 | /* internal variables */ | ||
131 | static atomic_t open_allowed = ATOMIC_INIT(1); | ||
132 | static char expect_close; | ||
133 | static struct timer_list timer; | ||
134 | static unsigned long next_heartbeat; | ||
135 | static int temp_panic; | ||
136 | static int revision; /* The card's revision */ | ||
137 | static int supports_temp; /* Wether or not the card has a temperature device */ | ||
138 | static int command_mode; /* Wether or not the card is in command mode */ | ||
139 | static int initial_status; /* The card's boot status */ | ||
140 | static int current_readport; /* The cards I/O address */ | ||
141 | static spinlock_t io_lock; | ||
142 | |||
143 | /* module parameters */ | ||
144 | #define WATCHDOG_HEARTBEAT 60 /* 60 sec default heartbeat */ | ||
145 | static int heartbeat = WATCHDOG_HEARTBEAT; | ||
146 | module_param(heartbeat, int, 0); | ||
147 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<=heartbeat<=7200, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
148 | |||
149 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
150 | static int nowayout = 1; | ||
151 | #else | ||
152 | static int nowayout = 0; | ||
153 | #endif | ||
154 | |||
155 | module_param(nowayout, int, 0); | ||
156 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
157 | |||
158 | /* | ||
159 | * Internal functions | ||
160 | */ | ||
161 | |||
162 | static int send_isa_command(int cmd) | ||
163 | { | ||
164 | int i; | ||
165 | int control_status; | ||
166 | int port0, last_port0; /* Double read for stabilising */ | ||
167 | |||
168 | /* The WCMD bit must be 1 and the command is only 4 bits in size */ | ||
169 | control_status = (cmd & 0x0F) | 0x80; | ||
170 | outb_p(control_status, current_readport + 2); | ||
171 | udelay(ISA_COMMAND_TIMEOUT); | ||
172 | |||
173 | port0 = inb_p(current_readport); | ||
174 | for (i = 0; i < 25; ++i) { | ||
175 | last_port0 = port0; | ||
176 | port0 = inb_p(current_readport); | ||
177 | |||
178 | if (port0 == last_port0) | ||
179 | break; /* Data is stable */ | ||
180 | |||
181 | udelay (250); | ||
182 | } | ||
183 | |||
184 | return port0; | ||
185 | } | ||
186 | |||
187 | static int set_command_mode(void) | ||
188 | { | ||
189 | int i, found=0, count=0; | ||
190 | |||
191 | /* Set the card into command mode */ | ||
192 | spin_lock(&io_lock); | ||
193 | while ((!found) && (count < 3)) { | ||
194 | i = send_isa_command(CMD_ISA_IDLE); | ||
195 | |||
196 | if (i == 0x00) | ||
197 | found = 1; | ||
198 | else if (i == 0xF3) { | ||
199 | /* Card does not like what we've done to it */ | ||
200 | outb_p(0x00, current_readport + 2); | ||
201 | udelay(1200); /* Spec says wait 1ms */ | ||
202 | outb_p(0x00, current_readport + 2); | ||
203 | udelay(ISA_COMMAND_TIMEOUT); | ||
204 | } | ||
205 | count++; | ||
206 | } | ||
207 | spin_unlock(&io_lock); | ||
208 | command_mode = found; | ||
209 | |||
210 | return(found); | ||
211 | } | ||
212 | |||
213 | static void unset_command_mode(void) | ||
214 | { | ||
215 | /* Set the card into normal mode */ | ||
216 | spin_lock(&io_lock); | ||
217 | outb_p(0x00, current_readport + 2); | ||
218 | udelay(ISA_COMMAND_TIMEOUT); | ||
219 | spin_unlock(&io_lock); | ||
220 | |||
221 | command_mode = 0; | ||
222 | } | ||
223 | |||
224 | static void pcwd_timer_ping(unsigned long data) | ||
225 | { | ||
226 | int wdrst_stat; | ||
227 | |||
228 | /* If we got a heartbeat pulse within the WDT_INTERVAL | ||
229 | * we agree to ping the WDT */ | ||
230 | if(time_before(jiffies, next_heartbeat)) { | ||
231 | /* Ping the watchdog */ | ||
232 | spin_lock(&io_lock); | ||
233 | if (revision == PCWD_REVISION_A) { | ||
234 | /* Rev A cards are reset by setting the WD_WDRST bit in register 1 */ | ||
235 | wdrst_stat = inb_p(current_readport); | ||
236 | wdrst_stat &= 0x0F; | ||
237 | wdrst_stat |= WD_WDRST; | ||
238 | |||
239 | outb_p(wdrst_stat, current_readport + 1); | ||
240 | } else { | ||
241 | /* Re-trigger watchdog by writing to port 0 */ | ||
242 | outb_p(0x00, current_readport); | ||
243 | } | ||
244 | |||
245 | /* Re-set the timer interval */ | ||
246 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
247 | |||
248 | spin_unlock(&io_lock); | ||
249 | } else { | ||
250 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
251 | } | ||
252 | } | ||
253 | |||
254 | static int pcwd_start(void) | ||
255 | { | ||
256 | int stat_reg; | ||
257 | |||
258 | next_heartbeat = jiffies + (heartbeat * HZ); | ||
259 | |||
260 | /* Start the timer */ | ||
261 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
262 | |||
263 | /* Enable the port */ | ||
264 | if (revision == PCWD_REVISION_C) { | ||
265 | spin_lock(&io_lock); | ||
266 | outb_p(0x00, current_readport + 3); | ||
267 | udelay(ISA_COMMAND_TIMEOUT); | ||
268 | stat_reg = inb_p(current_readport + 2); | ||
269 | spin_unlock(&io_lock); | ||
270 | if (stat_reg & 0x10) { | ||
271 | printk(KERN_INFO PFX "Could not start watchdog\n"); | ||
272 | return -EIO; | ||
273 | } | ||
274 | } | ||
275 | return 0; | ||
276 | } | ||
277 | |||
278 | static int pcwd_stop(void) | ||
279 | { | ||
280 | int stat_reg; | ||
281 | |||
282 | /* Stop the timer */ | ||
283 | del_timer(&timer); | ||
284 | |||
285 | /* Disable the board */ | ||
286 | if (revision == PCWD_REVISION_C) { | ||
287 | spin_lock(&io_lock); | ||
288 | outb_p(0xA5, current_readport + 3); | ||
289 | udelay(ISA_COMMAND_TIMEOUT); | ||
290 | outb_p(0xA5, current_readport + 3); | ||
291 | udelay(ISA_COMMAND_TIMEOUT); | ||
292 | stat_reg = inb_p(current_readport + 2); | ||
293 | spin_unlock(&io_lock); | ||
294 | if ((stat_reg & 0x10) == 0) { | ||
295 | printk(KERN_INFO PFX "Could not stop watchdog\n"); | ||
296 | return -EIO; | ||
297 | } | ||
298 | } | ||
299 | return 0; | ||
300 | } | ||
301 | |||
302 | static int pcwd_keepalive(void) | ||
303 | { | ||
304 | /* user land ping */ | ||
305 | next_heartbeat = jiffies + (heartbeat * HZ); | ||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static int pcwd_set_heartbeat(int t) | ||
310 | { | ||
311 | if ((t < 2) || (t > 7200)) /* arbitrary upper limit */ | ||
312 | return -EINVAL; | ||
313 | |||
314 | heartbeat = t; | ||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | static int pcwd_get_status(int *status) | ||
319 | { | ||
320 | int card_status; | ||
321 | |||
322 | *status=0; | ||
323 | spin_lock(&io_lock); | ||
324 | if (revision == PCWD_REVISION_A) | ||
325 | /* Rev A cards return status information from | ||
326 | * the base register, which is used for the | ||
327 | * temperature in other cards. */ | ||
328 | card_status = inb(current_readport); | ||
329 | else { | ||
330 | /* Rev C cards return card status in the base | ||
331 | * address + 1 register. And use different bits | ||
332 | * to indicate a card initiated reset, and an | ||
333 | * over-temperature condition. And the reboot | ||
334 | * status can be reset. */ | ||
335 | card_status = inb(current_readport + 1); | ||
336 | } | ||
337 | spin_unlock(&io_lock); | ||
338 | |||
339 | if (revision == PCWD_REVISION_A) { | ||
340 | if (card_status & WD_WDRST) | ||
341 | *status |= WDIOF_CARDRESET; | ||
342 | |||
343 | if (card_status & WD_T110) { | ||
344 | *status |= WDIOF_OVERHEAT; | ||
345 | if (temp_panic) { | ||
346 | printk (KERN_INFO PFX "Temperature overheat trip!\n"); | ||
347 | machine_power_off(); | ||
348 | } | ||
349 | } | ||
350 | } else { | ||
351 | if (card_status & WD_REVC_WTRP) | ||
352 | *status |= WDIOF_CARDRESET; | ||
353 | |||
354 | if (card_status & WD_REVC_TTRP) { | ||
355 | *status |= WDIOF_OVERHEAT; | ||
356 | if (temp_panic) { | ||
357 | printk (KERN_INFO PFX "Temperature overheat trip!\n"); | ||
358 | machine_power_off(); | ||
359 | } | ||
360 | } | ||
361 | } | ||
362 | |||
363 | return 0; | ||
364 | } | ||
365 | |||
366 | static int pcwd_clear_status(void) | ||
367 | { | ||
368 | if (revision == PCWD_REVISION_C) { | ||
369 | spin_lock(&io_lock); | ||
370 | outb_p(0x00, current_readport + 1); /* clear reset status */ | ||
371 | spin_unlock(&io_lock); | ||
372 | } | ||
373 | return 0; | ||
374 | } | ||
375 | |||
376 | static int pcwd_get_temperature(int *temperature) | ||
377 | { | ||
378 | /* check that port 0 gives temperature info and no command results */ | ||
379 | if (command_mode) | ||
380 | return -1; | ||
381 | |||
382 | *temperature = 0; | ||
383 | if (!supports_temp) | ||
384 | return -ENODEV; | ||
385 | |||
386 | /* | ||
387 | * Convert celsius to fahrenheit, since this was | ||
388 | * the decided 'standard' for this return value. | ||
389 | */ | ||
390 | spin_lock(&io_lock); | ||
391 | *temperature = ((inb(current_readport)) * 9 / 5) + 32; | ||
392 | spin_unlock(&io_lock); | ||
393 | |||
394 | return 0; | ||
395 | } | ||
396 | |||
397 | /* | ||
398 | * /dev/watchdog handling | ||
399 | */ | ||
400 | |||
401 | static int pcwd_ioctl(struct inode *inode, struct file *file, | ||
402 | unsigned int cmd, unsigned long arg) | ||
403 | { | ||
404 | int rv; | ||
405 | int status; | ||
406 | int temperature; | ||
407 | int new_heartbeat; | ||
408 | int __user *argp = (int __user *)arg; | ||
409 | static struct watchdog_info ident = { | ||
410 | .options = WDIOF_OVERHEAT | | ||
411 | WDIOF_CARDRESET | | ||
412 | WDIOF_KEEPALIVEPING | | ||
413 | WDIOF_SETTIMEOUT | | ||
414 | WDIOF_MAGICCLOSE, | ||
415 | .firmware_version = 1, | ||
416 | .identity = "PCWD", | ||
417 | }; | ||
418 | |||
419 | switch(cmd) { | ||
420 | default: | ||
421 | return -ENOIOCTLCMD; | ||
422 | |||
423 | case WDIOC_GETSUPPORT: | ||
424 | if(copy_to_user(argp, &ident, sizeof(ident))) | ||
425 | return -EFAULT; | ||
426 | return 0; | ||
427 | |||
428 | case WDIOC_GETSTATUS: | ||
429 | pcwd_get_status(&status); | ||
430 | return put_user(status, argp); | ||
431 | |||
432 | case WDIOC_GETBOOTSTATUS: | ||
433 | return put_user(initial_status, argp); | ||
434 | |||
435 | case WDIOC_GETTEMP: | ||
436 | if (pcwd_get_temperature(&temperature)) | ||
437 | return -EFAULT; | ||
438 | |||
439 | return put_user(temperature, argp); | ||
440 | |||
441 | case WDIOC_SETOPTIONS: | ||
442 | if (revision == PCWD_REVISION_C) | ||
443 | { | ||
444 | if(copy_from_user(&rv, argp, sizeof(int))) | ||
445 | return -EFAULT; | ||
446 | |||
447 | if (rv & WDIOS_DISABLECARD) | ||
448 | { | ||
449 | return pcwd_stop(); | ||
450 | } | ||
451 | |||
452 | if (rv & WDIOS_ENABLECARD) | ||
453 | { | ||
454 | return pcwd_start(); | ||
455 | } | ||
456 | |||
457 | if (rv & WDIOS_TEMPPANIC) | ||
458 | { | ||
459 | temp_panic = 1; | ||
460 | } | ||
461 | } | ||
462 | return -EINVAL; | ||
463 | |||
464 | case WDIOC_KEEPALIVE: | ||
465 | pcwd_keepalive(); | ||
466 | return 0; | ||
467 | |||
468 | case WDIOC_SETTIMEOUT: | ||
469 | if (get_user(new_heartbeat, argp)) | ||
470 | return -EFAULT; | ||
471 | |||
472 | if (pcwd_set_heartbeat(new_heartbeat)) | ||
473 | return -EINVAL; | ||
474 | |||
475 | pcwd_keepalive(); | ||
476 | /* Fall */ | ||
477 | |||
478 | case WDIOC_GETTIMEOUT: | ||
479 | return put_user(heartbeat, argp); | ||
480 | } | ||
481 | |||
482 | return 0; | ||
483 | } | ||
484 | |||
485 | static ssize_t pcwd_write(struct file *file, const char __user *buf, size_t len, | ||
486 | loff_t *ppos) | ||
487 | { | ||
488 | if (len) { | ||
489 | if (!nowayout) { | ||
490 | size_t i; | ||
491 | |||
492 | /* In case it was set long ago */ | ||
493 | expect_close = 0; | ||
494 | |||
495 | for (i = 0; i != len; i++) { | ||
496 | char c; | ||
497 | |||
498 | if (get_user(c, buf + i)) | ||
499 | return -EFAULT; | ||
500 | if (c == 'V') | ||
501 | expect_close = 42; | ||
502 | } | ||
503 | } | ||
504 | pcwd_keepalive(); | ||
505 | } | ||
506 | return len; | ||
507 | } | ||
508 | |||
509 | static int pcwd_open(struct inode *inode, struct file *file) | ||
510 | { | ||
511 | if (!atomic_dec_and_test(&open_allowed) ) { | ||
512 | atomic_inc( &open_allowed ); | ||
513 | return -EBUSY; | ||
514 | } | ||
515 | |||
516 | if (nowayout) | ||
517 | __module_get(THIS_MODULE); | ||
518 | |||
519 | /* Activate */ | ||
520 | pcwd_start(); | ||
521 | pcwd_keepalive(); | ||
522 | return nonseekable_open(inode, file); | ||
523 | } | ||
524 | |||
525 | static int pcwd_close(struct inode *inode, struct file *file) | ||
526 | { | ||
527 | if (expect_close == 42) { | ||
528 | pcwd_stop(); | ||
529 | } else { | ||
530 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
531 | pcwd_keepalive(); | ||
532 | } | ||
533 | expect_close = 0; | ||
534 | atomic_inc( &open_allowed ); | ||
535 | return 0; | ||
536 | } | ||
537 | |||
538 | /* | ||
539 | * /dev/temperature handling | ||
540 | */ | ||
541 | |||
542 | static ssize_t pcwd_temp_read(struct file *file, char __user *buf, size_t count, | ||
543 | loff_t *ppos) | ||
544 | { | ||
545 | int temperature; | ||
546 | |||
547 | if (pcwd_get_temperature(&temperature)) | ||
548 | return -EFAULT; | ||
549 | |||
550 | if (copy_to_user(buf, &temperature, 1)) | ||
551 | return -EFAULT; | ||
552 | |||
553 | return 1; | ||
554 | } | ||
555 | |||
556 | static int pcwd_temp_open(struct inode *inode, struct file *file) | ||
557 | { | ||
558 | if (!supports_temp) | ||
559 | return -ENODEV; | ||
560 | |||
561 | return nonseekable_open(inode, file); | ||
562 | } | ||
563 | |||
564 | static int pcwd_temp_close(struct inode *inode, struct file *file) | ||
565 | { | ||
566 | return 0; | ||
567 | } | ||
568 | |||
569 | /* | ||
570 | * Notify system | ||
571 | */ | ||
572 | |||
573 | static int pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
574 | { | ||
575 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
576 | /* Turn the WDT off */ | ||
577 | pcwd_stop(); | ||
578 | } | ||
579 | |||
580 | return NOTIFY_DONE; | ||
581 | } | ||
582 | |||
583 | /* | ||
584 | * Kernel Interfaces | ||
585 | */ | ||
586 | |||
587 | static struct file_operations pcwd_fops = { | ||
588 | .owner = THIS_MODULE, | ||
589 | .llseek = no_llseek, | ||
590 | .write = pcwd_write, | ||
591 | .ioctl = pcwd_ioctl, | ||
592 | .open = pcwd_open, | ||
593 | .release = pcwd_close, | ||
594 | }; | ||
595 | |||
596 | static struct miscdevice pcwd_miscdev = { | ||
597 | .minor = WATCHDOG_MINOR, | ||
598 | .name = "watchdog", | ||
599 | .fops = &pcwd_fops, | ||
600 | }; | ||
601 | |||
602 | static struct file_operations pcwd_temp_fops = { | ||
603 | .owner = THIS_MODULE, | ||
604 | .llseek = no_llseek, | ||
605 | .read = pcwd_temp_read, | ||
606 | .open = pcwd_temp_open, | ||
607 | .release = pcwd_temp_close, | ||
608 | }; | ||
609 | |||
610 | static struct miscdevice temp_miscdev = { | ||
611 | .minor = TEMP_MINOR, | ||
612 | .name = "temperature", | ||
613 | .fops = &pcwd_temp_fops, | ||
614 | }; | ||
615 | |||
616 | static struct notifier_block pcwd_notifier = { | ||
617 | .notifier_call = pcwd_notify_sys, | ||
618 | }; | ||
619 | |||
620 | /* | ||
621 | * Init & exit routines | ||
622 | */ | ||
623 | |||
624 | static inline void get_support(void) | ||
625 | { | ||
626 | if (inb(current_readport) != 0xF0) | ||
627 | supports_temp = 1; | ||
628 | } | ||
629 | |||
630 | static inline int get_revision(void) | ||
631 | { | ||
632 | int r = PCWD_REVISION_C; | ||
633 | |||
634 | spin_lock(&io_lock); | ||
635 | /* REV A cards use only 2 io ports; test | ||
636 | * presumes a floating bus reads as 0xff. */ | ||
637 | if ((inb(current_readport + 2) == 0xFF) || | ||
638 | (inb(current_readport + 3) == 0xFF)) | ||
639 | r=PCWD_REVISION_A; | ||
640 | spin_unlock(&io_lock); | ||
641 | |||
642 | return r; | ||
643 | } | ||
644 | |||
645 | static inline char *get_firmware(void) | ||
646 | { | ||
647 | int one, ten, hund, minor; | ||
648 | char *ret; | ||
649 | |||
650 | ret = kmalloc(6, GFP_KERNEL); | ||
651 | if(ret == NULL) | ||
652 | return NULL; | ||
653 | |||
654 | if (set_command_mode()) { | ||
655 | one = send_isa_command(CMD_ISA_VERSION_INTEGER); | ||
656 | ten = send_isa_command(CMD_ISA_VERSION_TENTH); | ||
657 | hund = send_isa_command(CMD_ISA_VERSION_HUNDRETH); | ||
658 | minor = send_isa_command(CMD_ISA_VERSION_MINOR); | ||
659 | sprintf(ret, "%c.%c%c%c", one, ten, hund, minor); | ||
660 | } | ||
661 | else | ||
662 | sprintf(ret, "ERROR"); | ||
663 | |||
664 | unset_command_mode(); | ||
665 | return(ret); | ||
666 | } | ||
667 | |||
668 | static inline int get_option_switches(void) | ||
669 | { | ||
670 | int rv=0; | ||
671 | |||
672 | if (set_command_mode()) { | ||
673 | /* Get switch settings */ | ||
674 | rv = send_isa_command(CMD_ISA_SWITCH_SETTINGS); | ||
675 | } | ||
676 | |||
677 | unset_command_mode(); | ||
678 | return(rv); | ||
679 | } | ||
680 | |||
681 | static int __devinit pcwatchdog_init(int base_addr) | ||
682 | { | ||
683 | int ret; | ||
684 | char *firmware; | ||
685 | int option_switches; | ||
686 | |||
687 | cards_found++; | ||
688 | if (cards_found == 1) | ||
689 | printk(KERN_INFO PFX "v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER); | ||
690 | |||
691 | if (cards_found > 1) { | ||
692 | printk(KERN_ERR PFX "This driver only supports 1 device\n"); | ||
693 | return -ENODEV; | ||
694 | } | ||
695 | |||
696 | if (base_addr == 0x0000) { | ||
697 | printk(KERN_ERR PFX "No I/O-Address for card detected\n"); | ||
698 | return -ENODEV; | ||
699 | } | ||
700 | current_readport = base_addr; | ||
701 | |||
702 | /* Check card's revision */ | ||
703 | revision = get_revision(); | ||
704 | |||
705 | if (!request_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4, "PCWD")) { | ||
706 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
707 | current_readport); | ||
708 | current_readport = 0x0000; | ||
709 | return -EIO; | ||
710 | } | ||
711 | |||
712 | /* Initial variables */ | ||
713 | supports_temp = 0; | ||
714 | temp_panic = 0; | ||
715 | initial_status = 0x0000; | ||
716 | |||
717 | /* get the boot_status */ | ||
718 | pcwd_get_status(&initial_status); | ||
719 | |||
720 | /* clear the "card caused reboot" flag */ | ||
721 | pcwd_clear_status(); | ||
722 | |||
723 | init_timer(&timer); | ||
724 | timer.function = pcwd_timer_ping; | ||
725 | timer.data = 0; | ||
726 | |||
727 | /* Disable the board */ | ||
728 | pcwd_stop(); | ||
729 | |||
730 | /* Check whether or not the card supports the temperature device */ | ||
731 | get_support(); | ||
732 | |||
733 | /* Get some extra info from the hardware (in command/debug/diag mode) */ | ||
734 | if (revision == PCWD_REVISION_A) | ||
735 | printk(KERN_INFO PFX "ISA-PC Watchdog (REV.A) detected at port 0x%04x\n", current_readport); | ||
736 | else if (revision == PCWD_REVISION_C) { | ||
737 | firmware = get_firmware(); | ||
738 | printk(KERN_INFO PFX "ISA-PC Watchdog (REV.C) detected at port 0x%04x (Firmware version: %s)\n", | ||
739 | current_readport, firmware); | ||
740 | kfree(firmware); | ||
741 | option_switches = get_option_switches(); | ||
742 | printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", | ||
743 | option_switches, | ||
744 | ((option_switches & 0x10) ? "ON" : "OFF"), | ||
745 | ((option_switches & 0x08) ? "ON" : "OFF")); | ||
746 | |||
747 | /* Reprogram internal heartbeat to 2 seconds */ | ||
748 | if (set_command_mode()) { | ||
749 | send_isa_command(CMD_ISA_DELAY_TIME_2SECS); | ||
750 | unset_command_mode(); | ||
751 | } | ||
752 | } else { | ||
753 | /* Should NEVER happen, unless get_revision() fails. */ | ||
754 | printk(KERN_INFO PFX "Unable to get revision\n"); | ||
755 | release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4); | ||
756 | current_readport = 0x0000; | ||
757 | return -1; | ||
758 | } | ||
759 | |||
760 | if (supports_temp) | ||
761 | printk(KERN_INFO PFX "Temperature Option Detected\n"); | ||
762 | |||
763 | if (initial_status & WDIOF_CARDRESET) | ||
764 | printk(KERN_INFO PFX "Previous reboot was caused by the card\n"); | ||
765 | |||
766 | if (initial_status & WDIOF_OVERHEAT) { | ||
767 | printk(KERN_EMERG PFX "Card senses a CPU Overheat. Panicking!\n"); | ||
768 | printk(KERN_EMERG PFX "CPU Overheat\n"); | ||
769 | } | ||
770 | |||
771 | if (initial_status == 0) | ||
772 | printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n"); | ||
773 | |||
774 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
775 | if (pcwd_set_heartbeat(heartbeat)) { | ||
776 | pcwd_set_heartbeat(WATCHDOG_HEARTBEAT); | ||
777 | printk(KERN_INFO PFX "heartbeat value must be 2<=heartbeat<=7200, using %d\n", | ||
778 | WATCHDOG_HEARTBEAT); | ||
779 | } | ||
780 | |||
781 | ret = register_reboot_notifier(&pcwd_notifier); | ||
782 | if (ret) { | ||
783 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
784 | ret); | ||
785 | release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4); | ||
786 | current_readport = 0x0000; | ||
787 | return ret; | ||
788 | } | ||
789 | |||
790 | if (supports_temp) { | ||
791 | ret = misc_register(&temp_miscdev); | ||
792 | if (ret) { | ||
793 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
794 | TEMP_MINOR, ret); | ||
795 | unregister_reboot_notifier(&pcwd_notifier); | ||
796 | release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4); | ||
797 | current_readport = 0x0000; | ||
798 | return ret; | ||
799 | } | ||
800 | } | ||
801 | |||
802 | ret = misc_register(&pcwd_miscdev); | ||
803 | if (ret) { | ||
804 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
805 | WATCHDOG_MINOR, ret); | ||
806 | if (supports_temp) | ||
807 | misc_deregister(&temp_miscdev); | ||
808 | unregister_reboot_notifier(&pcwd_notifier); | ||
809 | release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4); | ||
810 | current_readport = 0x0000; | ||
811 | return ret; | ||
812 | } | ||
813 | |||
814 | printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
815 | heartbeat, nowayout); | ||
816 | |||
817 | return 0; | ||
818 | } | ||
819 | |||
820 | static void __devexit pcwatchdog_exit(void) | ||
821 | { | ||
822 | /* Disable the board */ | ||
823 | if (!nowayout) | ||
824 | pcwd_stop(); | ||
825 | |||
826 | /* Deregister */ | ||
827 | misc_deregister(&pcwd_miscdev); | ||
828 | if (supports_temp) | ||
829 | misc_deregister(&temp_miscdev); | ||
830 | unregister_reboot_notifier(&pcwd_notifier); | ||
831 | release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4); | ||
832 | current_readport = 0x0000; | ||
833 | } | ||
834 | |||
835 | /* | ||
836 | * The ISA cards have a heartbeat bit in one of the registers, which | ||
837 | * register is card dependent. The heartbeat bit is monitored, and if | ||
838 | * found, is considered proof that a Berkshire card has been found. | ||
839 | * The initial rate is once per second at board start up, then twice | ||
840 | * per second for normal operation. | ||
841 | */ | ||
842 | static int __init pcwd_checkcard(int base_addr) | ||
843 | { | ||
844 | int port0, last_port0; /* Reg 0, in case it's REV A */ | ||
845 | int port1, last_port1; /* Register 1 for REV C cards */ | ||
846 | int i; | ||
847 | int retval; | ||
848 | |||
849 | if (!request_region (base_addr, 4, "PCWD")) { | ||
850 | printk (KERN_INFO PFX "Port 0x%04x unavailable\n", base_addr); | ||
851 | return 0; | ||
852 | } | ||
853 | |||
854 | retval = 0; | ||
855 | |||
856 | port0 = inb_p(base_addr); /* For REV A boards */ | ||
857 | port1 = inb_p(base_addr + 1); /* For REV C boards */ | ||
858 | if (port0 != 0xff || port1 != 0xff) { | ||
859 | /* Not an 'ff' from a floating bus, so must be a card! */ | ||
860 | for (i = 0; i < 4; ++i) { | ||
861 | |||
862 | msleep(500); | ||
863 | |||
864 | last_port0 = port0; | ||
865 | last_port1 = port1; | ||
866 | |||
867 | port0 = inb_p(base_addr); | ||
868 | port1 = inb_p(base_addr + 1); | ||
869 | |||
870 | /* Has either hearbeat bit changed? */ | ||
871 | if ((port0 ^ last_port0) & WD_HRTBT || | ||
872 | (port1 ^ last_port1) & WD_REVC_HRBT) { | ||
873 | retval = 1; | ||
874 | break; | ||
875 | } | ||
876 | } | ||
877 | } | ||
878 | release_region (base_addr, 4); | ||
879 | |||
880 | return retval; | ||
881 | } | ||
882 | |||
883 | /* | ||
884 | * These are the auto-probe addresses available. | ||
885 | * | ||
886 | * Revision A only uses ports 0x270 and 0x370. Revision C introduced 0x350. | ||
887 | * Revision A has an address range of 2 addresses, while Revision C has 4. | ||
888 | */ | ||
889 | static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 }; | ||
890 | |||
891 | static int __init pcwd_init_module(void) | ||
892 | { | ||
893 | int i, found = 0; | ||
894 | |||
895 | spin_lock_init(&io_lock); | ||
896 | |||
897 | for (i = 0; pcwd_ioports[i] != 0; i++) { | ||
898 | if (pcwd_checkcard(pcwd_ioports[i])) { | ||
899 | if (!(pcwatchdog_init(pcwd_ioports[i]))) | ||
900 | found++; | ||
901 | } | ||
902 | } | ||
903 | |||
904 | if (!found) { | ||
905 | printk (KERN_INFO PFX "No card detected, or port not available\n"); | ||
906 | return -ENODEV; | ||
907 | } | ||
908 | |||
909 | return 0; | ||
910 | } | ||
911 | |||
912 | static void __exit pcwd_cleanup_module(void) | ||
913 | { | ||
914 | if (current_readport) | ||
915 | pcwatchdog_exit(); | ||
916 | return; | ||
917 | } | ||
918 | |||
919 | module_init(pcwd_init_module); | ||
920 | module_exit(pcwd_cleanup_module); | ||
921 | |||
922 | MODULE_AUTHOR("Ken Hollis <kenji@bitgate.com>"); | ||
923 | MODULE_DESCRIPTION("Berkshire ISA-PC Watchdog driver"); | ||
924 | MODULE_LICENSE("GPL"); | ||
925 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
926 | MODULE_ALIAS_MISCDEV(TEMP_MINOR); | ||
diff --git a/drivers/char/watchdog/pcwd_pci.c b/drivers/char/watchdog/pcwd_pci.c new file mode 100644 index 000000000000..8ce066627326 --- /dev/null +++ b/drivers/char/watchdog/pcwd_pci.c | |||
@@ -0,0 +1,677 @@ | |||
1 | /* | ||
2 | * Berkshire PCI-PC Watchdog Card Driver | ||
3 | * | ||
4 | * (c) Copyright 2003 Wim Van Sebroeck <wim@iguana.be>. | ||
5 | * | ||
6 | * Based on source code of the following authors: | ||
7 | * Ken Hollis <kenji@bitgate.com>, | ||
8 | * Lindsay Harris <lindsay@bluegum.com>, | ||
9 | * Alan Cox <alan@redhat.com>, | ||
10 | * Matt Domsch <Matt_Domsch@dell.com>, | ||
11 | * Rob Radez <rob@osinvestor.com> | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version | ||
16 | * 2 of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor | ||
19 | * provide warranty for any of this software. This material is | ||
20 | * provided "AS-IS" and at no charge. | ||
21 | */ | ||
22 | |||
23 | /* | ||
24 | * A bells and whistles driver is available from http://www.pcwd.de/ | ||
25 | * More info available at http://www.berkprod.com/ or http://www.pcwatchdog.com/ | ||
26 | */ | ||
27 | |||
28 | /* | ||
29 | * Includes, defines, variables, module parameters, ... | ||
30 | */ | ||
31 | |||
32 | #include <linux/config.h> | ||
33 | #include <linux/module.h> | ||
34 | #include <linux/moduleparam.h> | ||
35 | #include <linux/types.h> | ||
36 | #include <linux/delay.h> | ||
37 | #include <linux/miscdevice.h> | ||
38 | #include <linux/watchdog.h> | ||
39 | #include <linux/notifier.h> | ||
40 | #include <linux/reboot.h> | ||
41 | #include <linux/init.h> | ||
42 | #include <linux/fs.h> | ||
43 | #include <linux/pci.h> | ||
44 | #include <linux/ioport.h> | ||
45 | #include <linux/spinlock.h> | ||
46 | |||
47 | #include <asm/uaccess.h> | ||
48 | #include <asm/io.h> | ||
49 | |||
50 | /* Module and version information */ | ||
51 | #define WATCHDOG_VERSION "1.01" | ||
52 | #define WATCHDOG_DATE "15 Mar 2005" | ||
53 | #define WATCHDOG_DRIVER_NAME "PCI-PC Watchdog" | ||
54 | #define WATCHDOG_NAME "pcwd_pci" | ||
55 | #define PFX WATCHDOG_NAME ": " | ||
56 | #define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION " (" WATCHDOG_DATE ")\n" | ||
57 | |||
58 | /* Stuff for the PCI ID's */ | ||
59 | #ifndef PCI_VENDOR_ID_QUICKLOGIC | ||
60 | #define PCI_VENDOR_ID_QUICKLOGIC 0x11e3 | ||
61 | #endif | ||
62 | |||
63 | #ifndef PCI_DEVICE_ID_WATCHDOG_PCIPCWD | ||
64 | #define PCI_DEVICE_ID_WATCHDOG_PCIPCWD 0x5030 | ||
65 | #endif | ||
66 | |||
67 | /* | ||
68 | * These are the defines that describe the control status bits for the | ||
69 | * PCI-PC Watchdog card. | ||
70 | */ | ||
71 | #define WD_PCI_WTRP 0x01 /* Watchdog Trip status */ | ||
72 | #define WD_PCI_HRBT 0x02 /* Watchdog Heartbeat */ | ||
73 | #define WD_PCI_TTRP 0x04 /* Temperature Trip status */ | ||
74 | |||
75 | /* according to documentation max. time to process a command for the pci | ||
76 | * watchdog card is 100 ms, so we give it 150 ms to do it's job */ | ||
77 | #define PCI_COMMAND_TIMEOUT 150 | ||
78 | |||
79 | /* Watchdog's internal commands */ | ||
80 | #define CMD_GET_STATUS 0x04 | ||
81 | #define CMD_GET_FIRMWARE_VERSION 0x08 | ||
82 | #define CMD_READ_WATCHDOG_TIMEOUT 0x18 | ||
83 | #define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 | ||
84 | |||
85 | /* We can only use 1 card due to the /dev/watchdog restriction */ | ||
86 | static int cards_found; | ||
87 | |||
88 | /* internal variables */ | ||
89 | static int temp_panic; | ||
90 | static unsigned long is_active; | ||
91 | static char expect_release; | ||
92 | static struct { | ||
93 | int supports_temp; /* Wether or not the card has a temperature device */ | ||
94 | int boot_status; /* The card's boot status */ | ||
95 | unsigned long io_addr; /* The cards I/O address */ | ||
96 | spinlock_t io_lock; | ||
97 | struct pci_dev *pdev; | ||
98 | } pcipcwd_private; | ||
99 | |||
100 | /* module parameters */ | ||
101 | #define WATCHDOG_HEARTBEAT 2 /* 2 sec default heartbeat */ | ||
102 | static int heartbeat = WATCHDOG_HEARTBEAT; | ||
103 | module_param(heartbeat, int, 0); | ||
104 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
105 | |||
106 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
107 | static int nowayout = 1; | ||
108 | #else | ||
109 | static int nowayout = 0; | ||
110 | #endif | ||
111 | |||
112 | module_param(nowayout, int, 0); | ||
113 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
114 | |||
115 | /* | ||
116 | * Internal functions | ||
117 | */ | ||
118 | |||
119 | static int send_command(int cmd, int *msb, int *lsb) | ||
120 | { | ||
121 | int got_response, count; | ||
122 | |||
123 | spin_lock(&pcipcwd_private.io_lock); | ||
124 | /* If a command requires data it should be written first. | ||
125 | * Data for commands with 8 bits of data should be written to port 4. | ||
126 | * Commands with 16 bits of data, should be written as LSB to port 4 | ||
127 | * and MSB to port 5. | ||
128 | * After the required data has been written then write the command to | ||
129 | * port 6. */ | ||
130 | outb_p(*lsb, pcipcwd_private.io_addr + 4); | ||
131 | outb_p(*msb, pcipcwd_private.io_addr + 5); | ||
132 | outb_p(cmd, pcipcwd_private.io_addr + 6); | ||
133 | |||
134 | /* wait till the pci card processed the command, signaled by | ||
135 | * the WRSP bit in port 2 and give it a max. timeout of | ||
136 | * PCI_COMMAND_TIMEOUT to process */ | ||
137 | got_response = inb_p(pcipcwd_private.io_addr + 2) & 0x40; | ||
138 | for (count = 0; (count < PCI_COMMAND_TIMEOUT) && (!got_response); count++) { | ||
139 | mdelay(1); | ||
140 | got_response = inb_p(pcipcwd_private.io_addr + 2) & 0x40; | ||
141 | } | ||
142 | |||
143 | if (got_response) { | ||
144 | /* read back response */ | ||
145 | *lsb = inb_p(pcipcwd_private.io_addr + 4); | ||
146 | *msb = inb_p(pcipcwd_private.io_addr + 5); | ||
147 | |||
148 | /* clear WRSP bit */ | ||
149 | inb_p(pcipcwd_private.io_addr + 6); | ||
150 | } | ||
151 | spin_unlock(&pcipcwd_private.io_lock); | ||
152 | |||
153 | return got_response; | ||
154 | } | ||
155 | |||
156 | static int pcipcwd_start(void) | ||
157 | { | ||
158 | int stat_reg; | ||
159 | |||
160 | spin_lock(&pcipcwd_private.io_lock); | ||
161 | outb_p(0x00, pcipcwd_private.io_addr + 3); | ||
162 | udelay(1000); | ||
163 | |||
164 | stat_reg = inb_p(pcipcwd_private.io_addr + 2); | ||
165 | spin_unlock(&pcipcwd_private.io_lock); | ||
166 | |||
167 | if (stat_reg & 0x10) { | ||
168 | printk(KERN_ERR PFX "Card timer not enabled\n"); | ||
169 | return -1; | ||
170 | } | ||
171 | |||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | static int pcipcwd_stop(void) | ||
176 | { | ||
177 | int stat_reg; | ||
178 | |||
179 | spin_lock(&pcipcwd_private.io_lock); | ||
180 | outb_p(0xA5, pcipcwd_private.io_addr + 3); | ||
181 | udelay(1000); | ||
182 | |||
183 | outb_p(0xA5, pcipcwd_private.io_addr + 3); | ||
184 | udelay(1000); | ||
185 | |||
186 | stat_reg = inb_p(pcipcwd_private.io_addr + 2); | ||
187 | spin_unlock(&pcipcwd_private.io_lock); | ||
188 | |||
189 | if (!(stat_reg & 0x10)) { | ||
190 | printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n"); | ||
191 | return -1; | ||
192 | } | ||
193 | |||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | static int pcipcwd_keepalive(void) | ||
198 | { | ||
199 | /* Re-trigger watchdog by writing to port 0 */ | ||
200 | outb_p(0x42, pcipcwd_private.io_addr); | ||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static int pcipcwd_set_heartbeat(int t) | ||
205 | { | ||
206 | int t_msb = t / 256; | ||
207 | int t_lsb = t % 256; | ||
208 | |||
209 | if ((t < 0x0001) || (t > 0xFFFF)) | ||
210 | return -EINVAL; | ||
211 | |||
212 | /* Write new heartbeat to watchdog */ | ||
213 | send_command(CMD_WRITE_WATCHDOG_TIMEOUT, &t_msb, &t_lsb); | ||
214 | |||
215 | heartbeat = t; | ||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | static int pcipcwd_get_status(int *status) | ||
220 | { | ||
221 | int new_status; | ||
222 | |||
223 | *status=0; | ||
224 | new_status = inb_p(pcipcwd_private.io_addr + 1); | ||
225 | if (new_status & WD_PCI_WTRP) | ||
226 | *status |= WDIOF_CARDRESET; | ||
227 | if (new_status & WD_PCI_TTRP) { | ||
228 | *status |= WDIOF_OVERHEAT; | ||
229 | if (temp_panic) | ||
230 | panic(PFX "Temperature overheat trip!\n"); | ||
231 | } | ||
232 | |||
233 | return 0; | ||
234 | } | ||
235 | |||
236 | static int pcipcwd_clear_status(void) | ||
237 | { | ||
238 | outb_p(0x01, pcipcwd_private.io_addr + 1); | ||
239 | return 0; | ||
240 | } | ||
241 | |||
242 | static int pcipcwd_get_temperature(int *temperature) | ||
243 | { | ||
244 | *temperature = 0; | ||
245 | if (!pcipcwd_private.supports_temp) | ||
246 | return -ENODEV; | ||
247 | |||
248 | /* | ||
249 | * Convert celsius to fahrenheit, since this was | ||
250 | * the decided 'standard' for this return value. | ||
251 | */ | ||
252 | *temperature = ((inb_p(pcipcwd_private.io_addr)) * 9 / 5) + 32; | ||
253 | |||
254 | return 0; | ||
255 | } | ||
256 | |||
257 | /* | ||
258 | * /dev/watchdog handling | ||
259 | */ | ||
260 | |||
261 | static ssize_t pcipcwd_write(struct file *file, const char __user *data, | ||
262 | size_t len, loff_t *ppos) | ||
263 | { | ||
264 | /* See if we got the magic character 'V' and reload the timer */ | ||
265 | if (len) { | ||
266 | if (!nowayout) { | ||
267 | size_t i; | ||
268 | |||
269 | /* note: just in case someone wrote the magic character | ||
270 | * five months ago... */ | ||
271 | expect_release = 0; | ||
272 | |||
273 | /* scan to see whether or not we got the magic character */ | ||
274 | for (i = 0; i != len; i++) { | ||
275 | char c; | ||
276 | if(get_user(c, data+i)) | ||
277 | return -EFAULT; | ||
278 | if (c == 'V') | ||
279 | expect_release = 42; | ||
280 | } | ||
281 | } | ||
282 | |||
283 | /* someone wrote to us, we should reload the timer */ | ||
284 | pcipcwd_keepalive(); | ||
285 | } | ||
286 | return len; | ||
287 | } | ||
288 | |||
289 | static int pcipcwd_ioctl(struct inode *inode, struct file *file, | ||
290 | unsigned int cmd, unsigned long arg) | ||
291 | { | ||
292 | void __user *argp = (void __user *)arg; | ||
293 | int __user *p = argp; | ||
294 | static struct watchdog_info ident = { | ||
295 | .options = WDIOF_OVERHEAT | | ||
296 | WDIOF_CARDRESET | | ||
297 | WDIOF_KEEPALIVEPING | | ||
298 | WDIOF_SETTIMEOUT | | ||
299 | WDIOF_MAGICCLOSE, | ||
300 | .firmware_version = 1, | ||
301 | .identity = WATCHDOG_DRIVER_NAME, | ||
302 | }; | ||
303 | |||
304 | switch (cmd) { | ||
305 | case WDIOC_GETSUPPORT: | ||
306 | return copy_to_user(argp, &ident, | ||
307 | sizeof (ident)) ? -EFAULT : 0; | ||
308 | |||
309 | case WDIOC_GETSTATUS: | ||
310 | { | ||
311 | int status; | ||
312 | |||
313 | pcipcwd_get_status(&status); | ||
314 | |||
315 | return put_user(status, p); | ||
316 | } | ||
317 | |||
318 | case WDIOC_GETBOOTSTATUS: | ||
319 | return put_user(pcipcwd_private.boot_status, p); | ||
320 | |||
321 | case WDIOC_GETTEMP: | ||
322 | { | ||
323 | int temperature; | ||
324 | |||
325 | if (pcipcwd_get_temperature(&temperature)) | ||
326 | return -EFAULT; | ||
327 | |||
328 | return put_user(temperature, p); | ||
329 | } | ||
330 | |||
331 | case WDIOC_KEEPALIVE: | ||
332 | pcipcwd_keepalive(); | ||
333 | return 0; | ||
334 | |||
335 | case WDIOC_SETOPTIONS: | ||
336 | { | ||
337 | int new_options, retval = -EINVAL; | ||
338 | |||
339 | if (get_user (new_options, p)) | ||
340 | return -EFAULT; | ||
341 | |||
342 | if (new_options & WDIOS_DISABLECARD) { | ||
343 | pcipcwd_stop(); | ||
344 | retval = 0; | ||
345 | } | ||
346 | |||
347 | if (new_options & WDIOS_ENABLECARD) { | ||
348 | pcipcwd_start(); | ||
349 | retval = 0; | ||
350 | } | ||
351 | |||
352 | if (new_options & WDIOS_TEMPPANIC) { | ||
353 | temp_panic = 1; | ||
354 | retval = 0; | ||
355 | } | ||
356 | |||
357 | return retval; | ||
358 | } | ||
359 | |||
360 | case WDIOC_SETTIMEOUT: | ||
361 | { | ||
362 | int new_heartbeat; | ||
363 | |||
364 | if (get_user(new_heartbeat, p)) | ||
365 | return -EFAULT; | ||
366 | |||
367 | if (pcipcwd_set_heartbeat(new_heartbeat)) | ||
368 | return -EINVAL; | ||
369 | |||
370 | pcipcwd_keepalive(); | ||
371 | /* Fall */ | ||
372 | } | ||
373 | |||
374 | case WDIOC_GETTIMEOUT: | ||
375 | return put_user(heartbeat, p); | ||
376 | |||
377 | default: | ||
378 | return -ENOIOCTLCMD; | ||
379 | } | ||
380 | } | ||
381 | |||
382 | static int pcipcwd_open(struct inode *inode, struct file *file) | ||
383 | { | ||
384 | /* /dev/watchdog can only be opened once */ | ||
385 | if (test_and_set_bit(0, &is_active)) | ||
386 | return -EBUSY; | ||
387 | |||
388 | /* Activate */ | ||
389 | pcipcwd_start(); | ||
390 | pcipcwd_keepalive(); | ||
391 | return nonseekable_open(inode, file); | ||
392 | } | ||
393 | |||
394 | static int pcipcwd_release(struct inode *inode, struct file *file) | ||
395 | { | ||
396 | /* | ||
397 | * Shut off the timer. | ||
398 | */ | ||
399 | if (expect_release == 42) { | ||
400 | pcipcwd_stop(); | ||
401 | } else { | ||
402 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
403 | pcipcwd_keepalive(); | ||
404 | } | ||
405 | expect_release = 0; | ||
406 | clear_bit(0, &is_active); | ||
407 | return 0; | ||
408 | } | ||
409 | |||
410 | /* | ||
411 | * /dev/temperature handling | ||
412 | */ | ||
413 | |||
414 | static ssize_t pcipcwd_temp_read(struct file *file, char __user *data, | ||
415 | size_t len, loff_t *ppos) | ||
416 | { | ||
417 | int temperature; | ||
418 | |||
419 | if (pcipcwd_get_temperature(&temperature)) | ||
420 | return -EFAULT; | ||
421 | |||
422 | if (copy_to_user (data, &temperature, 1)) | ||
423 | return -EFAULT; | ||
424 | |||
425 | return 1; | ||
426 | } | ||
427 | |||
428 | static int pcipcwd_temp_open(struct inode *inode, struct file *file) | ||
429 | { | ||
430 | if (!pcipcwd_private.supports_temp) | ||
431 | return -ENODEV; | ||
432 | |||
433 | return nonseekable_open(inode, file); | ||
434 | } | ||
435 | |||
436 | static int pcipcwd_temp_release(struct inode *inode, struct file *file) | ||
437 | { | ||
438 | return 0; | ||
439 | } | ||
440 | |||
441 | /* | ||
442 | * Notify system | ||
443 | */ | ||
444 | |||
445 | static int pcipcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
446 | { | ||
447 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
448 | /* Turn the WDT off */ | ||
449 | pcipcwd_stop(); | ||
450 | } | ||
451 | |||
452 | return NOTIFY_DONE; | ||
453 | } | ||
454 | |||
455 | /* | ||
456 | * Kernel Interfaces | ||
457 | */ | ||
458 | |||
459 | static struct file_operations pcipcwd_fops = { | ||
460 | .owner = THIS_MODULE, | ||
461 | .llseek = no_llseek, | ||
462 | .write = pcipcwd_write, | ||
463 | .ioctl = pcipcwd_ioctl, | ||
464 | .open = pcipcwd_open, | ||
465 | .release = pcipcwd_release, | ||
466 | }; | ||
467 | |||
468 | static struct miscdevice pcipcwd_miscdev = { | ||
469 | .minor = WATCHDOG_MINOR, | ||
470 | .name = "watchdog", | ||
471 | .fops = &pcipcwd_fops, | ||
472 | }; | ||
473 | |||
474 | static struct file_operations pcipcwd_temp_fops = { | ||
475 | .owner = THIS_MODULE, | ||
476 | .llseek = no_llseek, | ||
477 | .read = pcipcwd_temp_read, | ||
478 | .open = pcipcwd_temp_open, | ||
479 | .release = pcipcwd_temp_release, | ||
480 | }; | ||
481 | |||
482 | static struct miscdevice pcipcwd_temp_miscdev = { | ||
483 | .minor = TEMP_MINOR, | ||
484 | .name = "temperature", | ||
485 | .fops = &pcipcwd_temp_fops, | ||
486 | }; | ||
487 | |||
488 | static struct notifier_block pcipcwd_notifier = { | ||
489 | .notifier_call = pcipcwd_notify_sys, | ||
490 | }; | ||
491 | |||
492 | /* | ||
493 | * Init & exit routines | ||
494 | */ | ||
495 | |||
496 | static inline void check_temperature_support(void) | ||
497 | { | ||
498 | if (inb_p(pcipcwd_private.io_addr) != 0xF0) | ||
499 | pcipcwd_private.supports_temp = 1; | ||
500 | } | ||
501 | |||
502 | static int __devinit pcipcwd_card_init(struct pci_dev *pdev, | ||
503 | const struct pci_device_id *ent) | ||
504 | { | ||
505 | int ret = -EIO; | ||
506 | int got_fw_rev, fw_rev_major, fw_rev_minor; | ||
507 | char fw_ver_str[20]; | ||
508 | char option_switches; | ||
509 | |||
510 | cards_found++; | ||
511 | if (cards_found == 1) | ||
512 | printk(KERN_INFO PFX DRIVER_VERSION); | ||
513 | |||
514 | if (cards_found > 1) { | ||
515 | printk(KERN_ERR PFX "This driver only supports 1 device\n"); | ||
516 | return -ENODEV; | ||
517 | } | ||
518 | |||
519 | if (pci_enable_device(pdev)) { | ||
520 | printk(KERN_ERR PFX "Not possible to enable PCI Device\n"); | ||
521 | return -ENODEV; | ||
522 | } | ||
523 | |||
524 | if (pci_resource_start(pdev, 0) == 0x0000) { | ||
525 | printk(KERN_ERR PFX "No I/O-Address for card detected\n"); | ||
526 | ret = -ENODEV; | ||
527 | goto err_out_disable_device; | ||
528 | } | ||
529 | |||
530 | pcipcwd_private.pdev = pdev; | ||
531 | pcipcwd_private.io_addr = pci_resource_start(pdev, 0); | ||
532 | |||
533 | if (pci_request_regions(pdev, WATCHDOG_NAME)) { | ||
534 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
535 | (int) pcipcwd_private.io_addr); | ||
536 | ret = -EIO; | ||
537 | goto err_out_disable_device; | ||
538 | } | ||
539 | |||
540 | /* get the boot_status */ | ||
541 | pcipcwd_get_status(&pcipcwd_private.boot_status); | ||
542 | |||
543 | /* clear the "card caused reboot" flag */ | ||
544 | pcipcwd_clear_status(); | ||
545 | |||
546 | /* disable card */ | ||
547 | pcipcwd_stop(); | ||
548 | |||
549 | /* Check whether or not the card supports the temperature device */ | ||
550 | check_temperature_support(); | ||
551 | |||
552 | /* Get the Firmware Version */ | ||
553 | got_fw_rev = send_command(CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor); | ||
554 | if (got_fw_rev) { | ||
555 | sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor); | ||
556 | } else { | ||
557 | sprintf(fw_ver_str, "<card no answer>"); | ||
558 | } | ||
559 | |||
560 | /* Get switch settings */ | ||
561 | option_switches = inb_p(pcipcwd_private.io_addr + 3); | ||
562 | |||
563 | printk(KERN_INFO PFX "Found card at port 0x%04x (Firmware: %s) %s temp option\n", | ||
564 | (int) pcipcwd_private.io_addr, fw_ver_str, | ||
565 | (pcipcwd_private.supports_temp ? "with" : "without")); | ||
566 | |||
567 | printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", | ||
568 | option_switches, | ||
569 | ((option_switches & 0x10) ? "ON" : "OFF"), | ||
570 | ((option_switches & 0x08) ? "ON" : "OFF")); | ||
571 | |||
572 | if (pcipcwd_private.boot_status & WDIOF_CARDRESET) | ||
573 | printk(KERN_INFO PFX "Previous reset was caused by the Watchdog card\n"); | ||
574 | |||
575 | if (pcipcwd_private.boot_status & WDIOF_OVERHEAT) | ||
576 | printk(KERN_INFO PFX "Card sensed a CPU Overheat\n"); | ||
577 | |||
578 | if (pcipcwd_private.boot_status == 0) | ||
579 | printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n"); | ||
580 | |||
581 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
582 | if (pcipcwd_set_heartbeat(heartbeat)) { | ||
583 | pcipcwd_set_heartbeat(WATCHDOG_HEARTBEAT); | ||
584 | printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", | ||
585 | WATCHDOG_HEARTBEAT); | ||
586 | } | ||
587 | |||
588 | ret = register_reboot_notifier(&pcipcwd_notifier); | ||
589 | if (ret != 0) { | ||
590 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
591 | ret); | ||
592 | goto err_out_release_region; | ||
593 | } | ||
594 | |||
595 | if (pcipcwd_private.supports_temp) { | ||
596 | ret = misc_register(&pcipcwd_temp_miscdev); | ||
597 | if (ret != 0) { | ||
598 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
599 | TEMP_MINOR, ret); | ||
600 | goto err_out_unregister_reboot; | ||
601 | } | ||
602 | } | ||
603 | |||
604 | ret = misc_register(&pcipcwd_miscdev); | ||
605 | if (ret != 0) { | ||
606 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
607 | WATCHDOG_MINOR, ret); | ||
608 | goto err_out_misc_deregister; | ||
609 | } | ||
610 | |||
611 | printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
612 | heartbeat, nowayout); | ||
613 | |||
614 | return 0; | ||
615 | |||
616 | err_out_misc_deregister: | ||
617 | if (pcipcwd_private.supports_temp) | ||
618 | misc_deregister(&pcipcwd_temp_miscdev); | ||
619 | err_out_unregister_reboot: | ||
620 | unregister_reboot_notifier(&pcipcwd_notifier); | ||
621 | err_out_release_region: | ||
622 | pci_release_regions(pdev); | ||
623 | err_out_disable_device: | ||
624 | pci_disable_device(pdev); | ||
625 | return ret; | ||
626 | } | ||
627 | |||
628 | static void __devexit pcipcwd_card_exit(struct pci_dev *pdev) | ||
629 | { | ||
630 | /* Stop the timer before we leave */ | ||
631 | if (!nowayout) | ||
632 | pcipcwd_stop(); | ||
633 | |||
634 | /* Deregister */ | ||
635 | misc_deregister(&pcipcwd_miscdev); | ||
636 | if (pcipcwd_private.supports_temp) | ||
637 | misc_deregister(&pcipcwd_temp_miscdev); | ||
638 | unregister_reboot_notifier(&pcipcwd_notifier); | ||
639 | pci_release_regions(pdev); | ||
640 | pci_disable_device(pdev); | ||
641 | cards_found--; | ||
642 | } | ||
643 | |||
644 | static struct pci_device_id pcipcwd_pci_tbl[] = { | ||
645 | { PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_WATCHDOG_PCIPCWD, | ||
646 | PCI_ANY_ID, PCI_ANY_ID, }, | ||
647 | { 0 }, /* End of list */ | ||
648 | }; | ||
649 | MODULE_DEVICE_TABLE(pci, pcipcwd_pci_tbl); | ||
650 | |||
651 | static struct pci_driver pcipcwd_driver = { | ||
652 | .name = WATCHDOG_NAME, | ||
653 | .id_table = pcipcwd_pci_tbl, | ||
654 | .probe = pcipcwd_card_init, | ||
655 | .remove = __devexit_p(pcipcwd_card_exit), | ||
656 | }; | ||
657 | |||
658 | static int __init pcipcwd_init_module(void) | ||
659 | { | ||
660 | spin_lock_init (&pcipcwd_private.io_lock); | ||
661 | |||
662 | return pci_register_driver(&pcipcwd_driver); | ||
663 | } | ||
664 | |||
665 | static void __exit pcipcwd_cleanup_module(void) | ||
666 | { | ||
667 | pci_unregister_driver(&pcipcwd_driver); | ||
668 | } | ||
669 | |||
670 | module_init(pcipcwd_init_module); | ||
671 | module_exit(pcipcwd_cleanup_module); | ||
672 | |||
673 | MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>"); | ||
674 | MODULE_DESCRIPTION("Berkshire PCI-PC Watchdog driver"); | ||
675 | MODULE_LICENSE("GPL"); | ||
676 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
677 | MODULE_ALIAS_MISCDEV(TEMP_MINOR); | ||
diff --git a/drivers/char/watchdog/pcwd_usb.c b/drivers/char/watchdog/pcwd_usb.c new file mode 100644 index 000000000000..1127201d73b8 --- /dev/null +++ b/drivers/char/watchdog/pcwd_usb.c | |||
@@ -0,0 +1,796 @@ | |||
1 | /* | ||
2 | * Berkshire USB-PC Watchdog Card Driver | ||
3 | * | ||
4 | * (c) Copyright 2004 Wim Van Sebroeck <wim@iguana.be>. | ||
5 | * | ||
6 | * Based on source code of the following authors: | ||
7 | * Ken Hollis <kenji@bitgate.com>, | ||
8 | * Alan Cox <alan@redhat.com>, | ||
9 | * Matt Domsch <Matt_Domsch@dell.com>, | ||
10 | * Rob Radez <rob@osinvestor.com>, | ||
11 | * Greg Kroah-Hartman <greg@kroah.com> | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version | ||
16 | * 2 of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor | ||
19 | * provide warranty for any of this software. This material is | ||
20 | * provided "AS-IS" and at no charge. | ||
21 | * | ||
22 | * Thanks also to Simon Machell at Berkshire Products Inc. for | ||
23 | * providing the test hardware. More info is available at | ||
24 | * http://www.berkprod.com/ or http://www.pcwatchdog.com/ | ||
25 | */ | ||
26 | |||
27 | #include <linux/config.h> | ||
28 | #include <linux/kernel.h> | ||
29 | #include <linux/errno.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/slab.h> | ||
32 | #include <linux/module.h> | ||
33 | #include <linux/moduleparam.h> | ||
34 | #include <linux/types.h> | ||
35 | #include <linux/delay.h> | ||
36 | #include <linux/miscdevice.h> | ||
37 | #include <linux/watchdog.h> | ||
38 | #include <linux/notifier.h> | ||
39 | #include <linux/reboot.h> | ||
40 | #include <linux/fs.h> | ||
41 | #include <linux/smp_lock.h> | ||
42 | #include <linux/completion.h> | ||
43 | #include <asm/uaccess.h> | ||
44 | #include <linux/usb.h> | ||
45 | |||
46 | |||
47 | #ifdef CONFIG_USB_DEBUG | ||
48 | static int debug = 1; | ||
49 | #else | ||
50 | static int debug; | ||
51 | #endif | ||
52 | |||
53 | /* Use our own dbg macro */ | ||
54 | #undef dbg | ||
55 | #define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG PFX format "\n" , ## arg); } while (0) | ||
56 | |||
57 | |||
58 | /* Module and Version Information */ | ||
59 | #define DRIVER_VERSION "1.01" | ||
60 | #define DRIVER_DATE "15 Mar 2005" | ||
61 | #define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>" | ||
62 | #define DRIVER_DESC "Berkshire USB-PC Watchdog driver" | ||
63 | #define DRIVER_LICENSE "GPL" | ||
64 | #define DRIVER_NAME "pcwd_usb" | ||
65 | #define PFX DRIVER_NAME ": " | ||
66 | |||
67 | MODULE_AUTHOR(DRIVER_AUTHOR); | ||
68 | MODULE_DESCRIPTION(DRIVER_DESC); | ||
69 | MODULE_LICENSE(DRIVER_LICENSE); | ||
70 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
71 | MODULE_ALIAS_MISCDEV(TEMP_MINOR); | ||
72 | |||
73 | /* Module Parameters */ | ||
74 | module_param(debug, int, 0); | ||
75 | MODULE_PARM_DESC(debug, "Debug enabled or not"); | ||
76 | |||
77 | #define WATCHDOG_HEARTBEAT 2 /* 2 sec default heartbeat */ | ||
78 | static int heartbeat = WATCHDOG_HEARTBEAT; | ||
79 | module_param(heartbeat, int, 0); | ||
80 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
81 | |||
82 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
83 | static int nowayout = 1; | ||
84 | #else | ||
85 | static int nowayout = 0; | ||
86 | #endif | ||
87 | |||
88 | module_param(nowayout, int, 0); | ||
89 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
90 | |||
91 | /* The vendor and product id's for the USB-PC Watchdog card */ | ||
92 | #define USB_PCWD_VENDOR_ID 0x0c98 | ||
93 | #define USB_PCWD_PRODUCT_ID 0x1140 | ||
94 | |||
95 | /* table of devices that work with this driver */ | ||
96 | static struct usb_device_id usb_pcwd_table [] = { | ||
97 | { USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) }, | ||
98 | { } /* Terminating entry */ | ||
99 | }; | ||
100 | MODULE_DEVICE_TABLE (usb, usb_pcwd_table); | ||
101 | |||
102 | /* according to documentation max. time to process a command for the USB | ||
103 | * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */ | ||
104 | #define USB_COMMAND_TIMEOUT 250 | ||
105 | |||
106 | /* Watchdog's internal commands */ | ||
107 | #define CMD_READ_TEMP 0x02 /* Read Temperature; Re-trigger Watchdog */ | ||
108 | #define CMD_TRIGGER CMD_READ_TEMP | ||
109 | #define CMD_GET_STATUS 0x04 /* Get Status Information */ | ||
110 | #define CMD_GET_FIRMWARE_VERSION 0x08 /* Get Firmware Version */ | ||
111 | #define CMD_GET_DIP_SWITCH_SETTINGS 0x0c /* Get Dip Switch Settings */ | ||
112 | #define CMD_READ_WATCHDOG_TIMEOUT 0x18 /* Read Current Watchdog Time */ | ||
113 | #define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 /* Write Current Watchdog Time */ | ||
114 | #define CMD_ENABLE_WATCHDOG 0x30 /* Enable / Disable Watchdog */ | ||
115 | #define CMD_DISABLE_WATCHDOG CMD_ENABLE_WATCHDOG | ||
116 | |||
117 | /* Some defines that I like to be somewhere else like include/linux/usb_hid.h */ | ||
118 | #define HID_REQ_SET_REPORT 0x09 | ||
119 | #define HID_DT_REPORT (USB_TYPE_CLASS | 0x02) | ||
120 | |||
121 | /* We can only use 1 card due to the /dev/watchdog restriction */ | ||
122 | static int cards_found; | ||
123 | |||
124 | /* some internal variables */ | ||
125 | static unsigned long is_active; | ||
126 | static char expect_release; | ||
127 | |||
128 | /* Structure to hold all of our device specific stuff */ | ||
129 | struct usb_pcwd_private { | ||
130 | struct usb_device * udev; /* save off the usb device pointer */ | ||
131 | struct usb_interface * interface; /* the interface for this device */ | ||
132 | |||
133 | unsigned int interface_number; /* the interface number used for cmd's */ | ||
134 | |||
135 | unsigned char * intr_buffer; /* the buffer to intr data */ | ||
136 | dma_addr_t intr_dma; /* the dma address for the intr buffer */ | ||
137 | size_t intr_size; /* the size of the intr buffer */ | ||
138 | struct urb * intr_urb; /* the urb used for the intr pipe */ | ||
139 | |||
140 | unsigned char cmd_command; /* The command that is reported back */ | ||
141 | unsigned char cmd_data_msb; /* The data MSB that is reported back */ | ||
142 | unsigned char cmd_data_lsb; /* The data LSB that is reported back */ | ||
143 | atomic_t cmd_received; /* true if we received a report after a command */ | ||
144 | |||
145 | int exists; /* Wether or not the device exists */ | ||
146 | struct semaphore sem; /* locks this structure */ | ||
147 | }; | ||
148 | static struct usb_pcwd_private *usb_pcwd_device; | ||
149 | |||
150 | /* prevent races between open() and disconnect() */ | ||
151 | static DECLARE_MUTEX (disconnect_sem); | ||
152 | |||
153 | /* local function prototypes */ | ||
154 | static int usb_pcwd_probe (struct usb_interface *interface, const struct usb_device_id *id); | ||
155 | static void usb_pcwd_disconnect (struct usb_interface *interface); | ||
156 | |||
157 | /* usb specific object needed to register this driver with the usb subsystem */ | ||
158 | static struct usb_driver usb_pcwd_driver = { | ||
159 | .owner = THIS_MODULE, | ||
160 | .name = DRIVER_NAME, | ||
161 | .probe = usb_pcwd_probe, | ||
162 | .disconnect = usb_pcwd_disconnect, | ||
163 | .id_table = usb_pcwd_table, | ||
164 | }; | ||
165 | |||
166 | |||
167 | static void usb_pcwd_intr_done(struct urb *urb, struct pt_regs *regs) | ||
168 | { | ||
169 | struct usb_pcwd_private *usb_pcwd = (struct usb_pcwd_private *)urb->context; | ||
170 | unsigned char *data = usb_pcwd->intr_buffer; | ||
171 | int retval; | ||
172 | |||
173 | switch (urb->status) { | ||
174 | case 0: /* success */ | ||
175 | break; | ||
176 | case -ECONNRESET: /* unlink */ | ||
177 | case -ENOENT: | ||
178 | case -ESHUTDOWN: | ||
179 | /* this urb is terminated, clean up */ | ||
180 | dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); | ||
181 | return; | ||
182 | /* -EPIPE: should clear the halt */ | ||
183 | default: /* error */ | ||
184 | dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); | ||
185 | goto resubmit; | ||
186 | } | ||
187 | |||
188 | dbg("received following data cmd=0x%02x msb=0x%02x lsb=0x%02x", | ||
189 | data[0], data[1], data[2]); | ||
190 | |||
191 | usb_pcwd->cmd_command = data[0]; | ||
192 | usb_pcwd->cmd_data_msb = data[1]; | ||
193 | usb_pcwd->cmd_data_lsb = data[2]; | ||
194 | |||
195 | /* notify anyone waiting that the cmd has finished */ | ||
196 | atomic_set (&usb_pcwd->cmd_received, 1); | ||
197 | |||
198 | resubmit: | ||
199 | retval = usb_submit_urb (urb, GFP_ATOMIC); | ||
200 | if (retval) | ||
201 | printk(KERN_ERR PFX "can't resubmit intr, usb_submit_urb failed with result %d\n", | ||
202 | retval); | ||
203 | } | ||
204 | |||
205 | static int usb_pcwd_send_command(struct usb_pcwd_private *usb_pcwd, unsigned char cmd, | ||
206 | unsigned char *msb, unsigned char *lsb) | ||
207 | { | ||
208 | int got_response, count; | ||
209 | unsigned char buf[6]; | ||
210 | |||
211 | /* We will not send any commands if the USB PCWD device does not exist */ | ||
212 | if ((!usb_pcwd) || (!usb_pcwd->exists)) | ||
213 | return -1; | ||
214 | |||
215 | /* The USB PC Watchdog uses a 6 byte report format. The board currently uses | ||
216 | * only 3 of the six bytes of the report. */ | ||
217 | buf[0] = cmd; /* Byte 0 = CMD */ | ||
218 | buf[1] = *msb; /* Byte 1 = Data MSB */ | ||
219 | buf[2] = *lsb; /* Byte 2 = Data LSB */ | ||
220 | buf[3] = buf[4] = buf[5] = 0; /* All other bytes not used */ | ||
221 | |||
222 | dbg("sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x", | ||
223 | buf[0], buf[1], buf[2]); | ||
224 | |||
225 | atomic_set (&usb_pcwd->cmd_received, 0); | ||
226 | |||
227 | if (usb_control_msg(usb_pcwd->udev, usb_sndctrlpipe(usb_pcwd->udev, 0), | ||
228 | HID_REQ_SET_REPORT, HID_DT_REPORT, | ||
229 | 0x0200, usb_pcwd->interface_number, buf, sizeof(buf), | ||
230 | USB_COMMAND_TIMEOUT) != sizeof(buf)) { | ||
231 | dbg("usb_pcwd_send_command: error in usb_control_msg for cmd 0x%x 0x%x 0x%x\n", cmd, *msb, *lsb); | ||
232 | } | ||
233 | /* wait till the usb card processed the command, | ||
234 | * with a max. timeout of USB_COMMAND_TIMEOUT */ | ||
235 | got_response = 0; | ||
236 | for (count = 0; (count < USB_COMMAND_TIMEOUT) && (!got_response); count++) { | ||
237 | mdelay(1); | ||
238 | if (atomic_read (&usb_pcwd->cmd_received)) | ||
239 | got_response = 1; | ||
240 | } | ||
241 | |||
242 | if ((got_response) && (cmd == usb_pcwd->cmd_command)) { | ||
243 | /* read back response */ | ||
244 | *msb = usb_pcwd->cmd_data_msb; | ||
245 | *lsb = usb_pcwd->cmd_data_lsb; | ||
246 | } | ||
247 | |||
248 | return got_response; | ||
249 | } | ||
250 | |||
251 | static int usb_pcwd_start(struct usb_pcwd_private *usb_pcwd) | ||
252 | { | ||
253 | unsigned char msb = 0x00; | ||
254 | unsigned char lsb = 0x00; | ||
255 | int retval; | ||
256 | |||
257 | /* Enable Watchdog */ | ||
258 | retval = usb_pcwd_send_command(usb_pcwd, CMD_ENABLE_WATCHDOG, &msb, &lsb); | ||
259 | |||
260 | if ((retval == 0) || (lsb == 0)) { | ||
261 | printk(KERN_ERR PFX "Card did not acknowledge enable attempt\n"); | ||
262 | return -1; | ||
263 | } | ||
264 | |||
265 | return 0; | ||
266 | } | ||
267 | |||
268 | static int usb_pcwd_stop(struct usb_pcwd_private *usb_pcwd) | ||
269 | { | ||
270 | unsigned char msb = 0xA5; | ||
271 | unsigned char lsb = 0xC3; | ||
272 | int retval; | ||
273 | |||
274 | /* Disable Watchdog */ | ||
275 | retval = usb_pcwd_send_command(usb_pcwd, CMD_DISABLE_WATCHDOG, &msb, &lsb); | ||
276 | |||
277 | if ((retval == 0) || (lsb != 0)) { | ||
278 | printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n"); | ||
279 | return -1; | ||
280 | } | ||
281 | |||
282 | return 0; | ||
283 | } | ||
284 | |||
285 | static int usb_pcwd_keepalive(struct usb_pcwd_private *usb_pcwd) | ||
286 | { | ||
287 | unsigned char dummy; | ||
288 | |||
289 | /* Re-trigger Watchdog */ | ||
290 | usb_pcwd_send_command(usb_pcwd, CMD_TRIGGER, &dummy, &dummy); | ||
291 | |||
292 | return 0; | ||
293 | } | ||
294 | |||
295 | static int usb_pcwd_set_heartbeat(struct usb_pcwd_private *usb_pcwd, int t) | ||
296 | { | ||
297 | unsigned char msb = t / 256; | ||
298 | unsigned char lsb = t % 256; | ||
299 | |||
300 | if ((t < 0x0001) || (t > 0xFFFF)) | ||
301 | return -EINVAL; | ||
302 | |||
303 | /* Write new heartbeat to watchdog */ | ||
304 | usb_pcwd_send_command(usb_pcwd, CMD_WRITE_WATCHDOG_TIMEOUT, &msb, &lsb); | ||
305 | |||
306 | heartbeat = t; | ||
307 | return 0; | ||
308 | } | ||
309 | |||
310 | static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd, int *temperature) | ||
311 | { | ||
312 | unsigned char msb, lsb; | ||
313 | |||
314 | usb_pcwd_send_command(usb_pcwd, CMD_READ_TEMP, &msb, &lsb); | ||
315 | |||
316 | /* | ||
317 | * Convert celsius to fahrenheit, since this was | ||
318 | * the decided 'standard' for this return value. | ||
319 | */ | ||
320 | *temperature = (lsb * 9 / 5) + 32; | ||
321 | |||
322 | return 0; | ||
323 | } | ||
324 | |||
325 | /* | ||
326 | * /dev/watchdog handling | ||
327 | */ | ||
328 | |||
329 | static ssize_t usb_pcwd_write(struct file *file, const char __user *data, | ||
330 | size_t len, loff_t *ppos) | ||
331 | { | ||
332 | /* See if we got the magic character 'V' and reload the timer */ | ||
333 | if (len) { | ||
334 | if (!nowayout) { | ||
335 | size_t i; | ||
336 | |||
337 | /* note: just in case someone wrote the magic character | ||
338 | * five months ago... */ | ||
339 | expect_release = 0; | ||
340 | |||
341 | /* scan to see whether or not we got the magic character */ | ||
342 | for (i = 0; i != len; i++) { | ||
343 | char c; | ||
344 | if(get_user(c, data+i)) | ||
345 | return -EFAULT; | ||
346 | if (c == 'V') | ||
347 | expect_release = 42; | ||
348 | } | ||
349 | } | ||
350 | |||
351 | /* someone wrote to us, we should reload the timer */ | ||
352 | usb_pcwd_keepalive(usb_pcwd_device); | ||
353 | } | ||
354 | return len; | ||
355 | } | ||
356 | |||
357 | static int usb_pcwd_ioctl(struct inode *inode, struct file *file, | ||
358 | unsigned int cmd, unsigned long arg) | ||
359 | { | ||
360 | void __user *argp = (void __user *)arg; | ||
361 | int __user *p = argp; | ||
362 | static struct watchdog_info ident = { | ||
363 | .options = WDIOF_KEEPALIVEPING | | ||
364 | WDIOF_SETTIMEOUT | | ||
365 | WDIOF_MAGICCLOSE, | ||
366 | .firmware_version = 1, | ||
367 | .identity = DRIVER_NAME, | ||
368 | }; | ||
369 | |||
370 | switch (cmd) { | ||
371 | case WDIOC_GETSUPPORT: | ||
372 | return copy_to_user(argp, &ident, | ||
373 | sizeof (ident)) ? -EFAULT : 0; | ||
374 | |||
375 | case WDIOC_GETSTATUS: | ||
376 | case WDIOC_GETBOOTSTATUS: | ||
377 | return put_user(0, p); | ||
378 | |||
379 | case WDIOC_GETTEMP: | ||
380 | { | ||
381 | int temperature; | ||
382 | |||
383 | if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature)) | ||
384 | return -EFAULT; | ||
385 | |||
386 | return put_user(temperature, p); | ||
387 | } | ||
388 | |||
389 | case WDIOC_KEEPALIVE: | ||
390 | usb_pcwd_keepalive(usb_pcwd_device); | ||
391 | return 0; | ||
392 | |||
393 | case WDIOC_SETOPTIONS: | ||
394 | { | ||
395 | int new_options, retval = -EINVAL; | ||
396 | |||
397 | if (get_user (new_options, p)) | ||
398 | return -EFAULT; | ||
399 | |||
400 | if (new_options & WDIOS_DISABLECARD) { | ||
401 | usb_pcwd_stop(usb_pcwd_device); | ||
402 | retval = 0; | ||
403 | } | ||
404 | |||
405 | if (new_options & WDIOS_ENABLECARD) { | ||
406 | usb_pcwd_start(usb_pcwd_device); | ||
407 | retval = 0; | ||
408 | } | ||
409 | |||
410 | return retval; | ||
411 | } | ||
412 | |||
413 | case WDIOC_SETTIMEOUT: | ||
414 | { | ||
415 | int new_heartbeat; | ||
416 | |||
417 | if (get_user(new_heartbeat, p)) | ||
418 | return -EFAULT; | ||
419 | |||
420 | if (usb_pcwd_set_heartbeat(usb_pcwd_device, new_heartbeat)) | ||
421 | return -EINVAL; | ||
422 | |||
423 | usb_pcwd_keepalive(usb_pcwd_device); | ||
424 | /* Fall */ | ||
425 | } | ||
426 | |||
427 | case WDIOC_GETTIMEOUT: | ||
428 | return put_user(heartbeat, p); | ||
429 | |||
430 | default: | ||
431 | return -ENOIOCTLCMD; | ||
432 | } | ||
433 | } | ||
434 | |||
435 | static int usb_pcwd_open(struct inode *inode, struct file *file) | ||
436 | { | ||
437 | /* /dev/watchdog can only be opened once */ | ||
438 | if (test_and_set_bit(0, &is_active)) | ||
439 | return -EBUSY; | ||
440 | |||
441 | /* Activate */ | ||
442 | usb_pcwd_start(usb_pcwd_device); | ||
443 | usb_pcwd_keepalive(usb_pcwd_device); | ||
444 | return nonseekable_open(inode, file); | ||
445 | } | ||
446 | |||
447 | static int usb_pcwd_release(struct inode *inode, struct file *file) | ||
448 | { | ||
449 | /* | ||
450 | * Shut off the timer. | ||
451 | */ | ||
452 | if (expect_release == 42) { | ||
453 | usb_pcwd_stop(usb_pcwd_device); | ||
454 | } else { | ||
455 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
456 | usb_pcwd_keepalive(usb_pcwd_device); | ||
457 | } | ||
458 | expect_release = 0; | ||
459 | clear_bit(0, &is_active); | ||
460 | return 0; | ||
461 | } | ||
462 | |||
463 | /* | ||
464 | * /dev/temperature handling | ||
465 | */ | ||
466 | |||
467 | static ssize_t usb_pcwd_temperature_read(struct file *file, char __user *data, | ||
468 | size_t len, loff_t *ppos) | ||
469 | { | ||
470 | int temperature; | ||
471 | |||
472 | if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature)) | ||
473 | return -EFAULT; | ||
474 | |||
475 | if (copy_to_user(data, &temperature, 1)) | ||
476 | return -EFAULT; | ||
477 | |||
478 | return 1; | ||
479 | } | ||
480 | |||
481 | static int usb_pcwd_temperature_open(struct inode *inode, struct file *file) | ||
482 | { | ||
483 | return nonseekable_open(inode, file); | ||
484 | } | ||
485 | |||
486 | static int usb_pcwd_temperature_release(struct inode *inode, struct file *file) | ||
487 | { | ||
488 | return 0; | ||
489 | } | ||
490 | |||
491 | /* | ||
492 | * Notify system | ||
493 | */ | ||
494 | |||
495 | static int usb_pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
496 | { | ||
497 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
498 | /* Turn the WDT off */ | ||
499 | usb_pcwd_stop(usb_pcwd_device); | ||
500 | } | ||
501 | |||
502 | return NOTIFY_DONE; | ||
503 | } | ||
504 | |||
505 | /* | ||
506 | * Kernel Interfaces | ||
507 | */ | ||
508 | |||
509 | static struct file_operations usb_pcwd_fops = { | ||
510 | .owner = THIS_MODULE, | ||
511 | .llseek = no_llseek, | ||
512 | .write = usb_pcwd_write, | ||
513 | .ioctl = usb_pcwd_ioctl, | ||
514 | .open = usb_pcwd_open, | ||
515 | .release = usb_pcwd_release, | ||
516 | }; | ||
517 | |||
518 | static struct miscdevice usb_pcwd_miscdev = { | ||
519 | .minor = WATCHDOG_MINOR, | ||
520 | .name = "watchdog", | ||
521 | .fops = &usb_pcwd_fops, | ||
522 | }; | ||
523 | |||
524 | static struct file_operations usb_pcwd_temperature_fops = { | ||
525 | .owner = THIS_MODULE, | ||
526 | .llseek = no_llseek, | ||
527 | .read = usb_pcwd_temperature_read, | ||
528 | .open = usb_pcwd_temperature_open, | ||
529 | .release = usb_pcwd_temperature_release, | ||
530 | }; | ||
531 | |||
532 | static struct miscdevice usb_pcwd_temperature_miscdev = { | ||
533 | .minor = TEMP_MINOR, | ||
534 | .name = "temperature", | ||
535 | .fops = &usb_pcwd_temperature_fops, | ||
536 | }; | ||
537 | |||
538 | static struct notifier_block usb_pcwd_notifier = { | ||
539 | .notifier_call = usb_pcwd_notify_sys, | ||
540 | }; | ||
541 | |||
542 | /** | ||
543 | * usb_pcwd_delete | ||
544 | */ | ||
545 | static inline void usb_pcwd_delete (struct usb_pcwd_private *usb_pcwd) | ||
546 | { | ||
547 | if (usb_pcwd->intr_urb != NULL) | ||
548 | usb_free_urb (usb_pcwd->intr_urb); | ||
549 | if (usb_pcwd->intr_buffer != NULL) | ||
550 | usb_buffer_free(usb_pcwd->udev, usb_pcwd->intr_size, | ||
551 | usb_pcwd->intr_buffer, usb_pcwd->intr_dma); | ||
552 | kfree (usb_pcwd); | ||
553 | } | ||
554 | |||
555 | /** | ||
556 | * usb_pcwd_probe | ||
557 | * | ||
558 | * Called by the usb core when a new device is connected that it thinks | ||
559 | * this driver might be interested in. | ||
560 | */ | ||
561 | static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_device_id *id) | ||
562 | { | ||
563 | struct usb_device *udev = interface_to_usbdev(interface); | ||
564 | struct usb_host_interface *iface_desc; | ||
565 | struct usb_endpoint_descriptor *endpoint; | ||
566 | struct usb_pcwd_private *usb_pcwd = NULL; | ||
567 | int pipe, maxp; | ||
568 | int retval = -ENOMEM; | ||
569 | int got_fw_rev; | ||
570 | unsigned char fw_rev_major, fw_rev_minor; | ||
571 | char fw_ver_str[20]; | ||
572 | unsigned char option_switches, dummy; | ||
573 | |||
574 | cards_found++; | ||
575 | if (cards_found > 1) { | ||
576 | printk(KERN_ERR PFX "This driver only supports 1 device\n"); | ||
577 | return -ENODEV; | ||
578 | } | ||
579 | |||
580 | /* get the active interface descriptor */ | ||
581 | iface_desc = interface->cur_altsetting; | ||
582 | |||
583 | /* check out that we have a HID device */ | ||
584 | if (!(iface_desc->desc.bInterfaceClass == USB_CLASS_HID)) { | ||
585 | printk(KERN_ERR PFX "The device isn't a Human Interface Device\n"); | ||
586 | return -ENODEV; | ||
587 | } | ||
588 | |||
589 | /* check out the endpoint: it has to be Interrupt & IN */ | ||
590 | endpoint = &iface_desc->endpoint[0].desc; | ||
591 | |||
592 | if (!((endpoint->bEndpointAddress & USB_DIR_IN) && | ||
593 | ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) | ||
594 | == USB_ENDPOINT_XFER_INT))) { | ||
595 | /* we didn't find a Interrupt endpoint with direction IN */ | ||
596 | printk(KERN_ERR PFX "Couldn't find an INTR & IN endpoint\n"); | ||
597 | return -ENODEV; | ||
598 | } | ||
599 | |||
600 | /* get a handle to the interrupt data pipe */ | ||
601 | pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); | ||
602 | maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); | ||
603 | |||
604 | /* allocate memory for our device and initialize it */ | ||
605 | usb_pcwd = kmalloc (sizeof(struct usb_pcwd_private), GFP_KERNEL); | ||
606 | if (usb_pcwd == NULL) { | ||
607 | printk(KERN_ERR PFX "Out of memory\n"); | ||
608 | goto error; | ||
609 | } | ||
610 | memset (usb_pcwd, 0x00, sizeof (*usb_pcwd)); | ||
611 | |||
612 | usb_pcwd_device = usb_pcwd; | ||
613 | |||
614 | init_MUTEX (&usb_pcwd->sem); | ||
615 | usb_pcwd->udev = udev; | ||
616 | usb_pcwd->interface = interface; | ||
617 | usb_pcwd->interface_number = iface_desc->desc.bInterfaceNumber; | ||
618 | usb_pcwd->intr_size = (le16_to_cpu(endpoint->wMaxPacketSize) > 8 ? le16_to_cpu(endpoint->wMaxPacketSize) : 8); | ||
619 | |||
620 | /* set up the memory buffer's */ | ||
621 | if (!(usb_pcwd->intr_buffer = usb_buffer_alloc(udev, usb_pcwd->intr_size, SLAB_ATOMIC, &usb_pcwd->intr_dma))) { | ||
622 | printk(KERN_ERR PFX "Out of memory\n"); | ||
623 | goto error; | ||
624 | } | ||
625 | |||
626 | /* allocate the urb's */ | ||
627 | usb_pcwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL); | ||
628 | if (!usb_pcwd->intr_urb) { | ||
629 | printk(KERN_ERR PFX "Out of memory\n"); | ||
630 | goto error; | ||
631 | } | ||
632 | |||
633 | /* initialise the intr urb's */ | ||
634 | usb_fill_int_urb(usb_pcwd->intr_urb, udev, pipe, | ||
635 | usb_pcwd->intr_buffer, usb_pcwd->intr_size, | ||
636 | usb_pcwd_intr_done, usb_pcwd, endpoint->bInterval); | ||
637 | usb_pcwd->intr_urb->transfer_dma = usb_pcwd->intr_dma; | ||
638 | usb_pcwd->intr_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; | ||
639 | |||
640 | /* register our interrupt URB with the USB system */ | ||
641 | if (usb_submit_urb(usb_pcwd->intr_urb, GFP_KERNEL)) { | ||
642 | printk(KERN_ERR PFX "Problem registering interrupt URB\n"); | ||
643 | retval = -EIO; /* failure */ | ||
644 | goto error; | ||
645 | } | ||
646 | |||
647 | /* The device exists and can be communicated with */ | ||
648 | usb_pcwd->exists = 1; | ||
649 | |||
650 | /* disable card */ | ||
651 | usb_pcwd_stop(usb_pcwd); | ||
652 | |||
653 | /* Get the Firmware Version */ | ||
654 | got_fw_rev = usb_pcwd_send_command(usb_pcwd, CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor); | ||
655 | if (got_fw_rev) { | ||
656 | sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor); | ||
657 | } else { | ||
658 | sprintf(fw_ver_str, "<card no answer>"); | ||
659 | } | ||
660 | |||
661 | printk(KERN_INFO PFX "Found card (Firmware: %s) with temp option\n", | ||
662 | fw_ver_str); | ||
663 | |||
664 | /* Get switch settings */ | ||
665 | usb_pcwd_send_command(usb_pcwd, CMD_GET_DIP_SWITCH_SETTINGS, &dummy, &option_switches); | ||
666 | |||
667 | printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", | ||
668 | option_switches, | ||
669 | ((option_switches & 0x10) ? "ON" : "OFF"), | ||
670 | ((option_switches & 0x08) ? "ON" : "OFF")); | ||
671 | |||
672 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
673 | if (usb_pcwd_set_heartbeat(usb_pcwd, heartbeat)) { | ||
674 | usb_pcwd_set_heartbeat(usb_pcwd, WATCHDOG_HEARTBEAT); | ||
675 | printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", | ||
676 | WATCHDOG_HEARTBEAT); | ||
677 | } | ||
678 | |||
679 | retval = register_reboot_notifier(&usb_pcwd_notifier); | ||
680 | if (retval != 0) { | ||
681 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
682 | retval); | ||
683 | goto error; | ||
684 | } | ||
685 | |||
686 | retval = misc_register(&usb_pcwd_temperature_miscdev); | ||
687 | if (retval != 0) { | ||
688 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
689 | TEMP_MINOR, retval); | ||
690 | goto err_out_unregister_reboot; | ||
691 | } | ||
692 | |||
693 | retval = misc_register(&usb_pcwd_miscdev); | ||
694 | if (retval != 0) { | ||
695 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
696 | WATCHDOG_MINOR, retval); | ||
697 | goto err_out_misc_deregister; | ||
698 | } | ||
699 | |||
700 | /* we can register the device now, as it is ready */ | ||
701 | usb_set_intfdata (interface, usb_pcwd); | ||
702 | |||
703 | printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
704 | heartbeat, nowayout); | ||
705 | |||
706 | return 0; | ||
707 | |||
708 | err_out_misc_deregister: | ||
709 | misc_deregister(&usb_pcwd_temperature_miscdev); | ||
710 | err_out_unregister_reboot: | ||
711 | unregister_reboot_notifier(&usb_pcwd_notifier); | ||
712 | error: | ||
713 | usb_pcwd_delete (usb_pcwd); | ||
714 | usb_pcwd_device = NULL; | ||
715 | return retval; | ||
716 | } | ||
717 | |||
718 | |||
719 | /** | ||
720 | * usb_pcwd_disconnect | ||
721 | * | ||
722 | * Called by the usb core when the device is removed from the system. | ||
723 | * | ||
724 | * This routine guarantees that the driver will not submit any more urbs | ||
725 | * by clearing dev->udev. | ||
726 | */ | ||
727 | static void usb_pcwd_disconnect(struct usb_interface *interface) | ||
728 | { | ||
729 | struct usb_pcwd_private *usb_pcwd; | ||
730 | |||
731 | /* prevent races with open() */ | ||
732 | down (&disconnect_sem); | ||
733 | |||
734 | usb_pcwd = usb_get_intfdata (interface); | ||
735 | usb_set_intfdata (interface, NULL); | ||
736 | |||
737 | down (&usb_pcwd->sem); | ||
738 | |||
739 | /* Stop the timer before we leave */ | ||
740 | if (!nowayout) | ||
741 | usb_pcwd_stop(usb_pcwd); | ||
742 | |||
743 | /* We should now stop communicating with the USB PCWD device */ | ||
744 | usb_pcwd->exists = 0; | ||
745 | |||
746 | /* Deregister */ | ||
747 | misc_deregister(&usb_pcwd_miscdev); | ||
748 | misc_deregister(&usb_pcwd_temperature_miscdev); | ||
749 | unregister_reboot_notifier(&usb_pcwd_notifier); | ||
750 | |||
751 | up (&usb_pcwd->sem); | ||
752 | |||
753 | /* Delete the USB PCWD device */ | ||
754 | usb_pcwd_delete(usb_pcwd); | ||
755 | |||
756 | cards_found--; | ||
757 | |||
758 | up (&disconnect_sem); | ||
759 | |||
760 | printk(KERN_INFO PFX "USB PC Watchdog disconnected\n"); | ||
761 | } | ||
762 | |||
763 | |||
764 | |||
765 | /** | ||
766 | * usb_pcwd_init | ||
767 | */ | ||
768 | static int __init usb_pcwd_init(void) | ||
769 | { | ||
770 | int result; | ||
771 | |||
772 | /* register this driver with the USB subsystem */ | ||
773 | result = usb_register(&usb_pcwd_driver); | ||
774 | if (result) { | ||
775 | printk(KERN_ERR PFX "usb_register failed. Error number %d\n", | ||
776 | result); | ||
777 | return result; | ||
778 | } | ||
779 | |||
780 | printk(KERN_INFO PFX DRIVER_DESC " v" DRIVER_VERSION " (" DRIVER_DATE ")\n"); | ||
781 | return 0; | ||
782 | } | ||
783 | |||
784 | |||
785 | /** | ||
786 | * usb_pcwd_exit | ||
787 | */ | ||
788 | static void __exit usb_pcwd_exit(void) | ||
789 | { | ||
790 | /* deregister this driver with the USB subsystem */ | ||
791 | usb_deregister(&usb_pcwd_driver); | ||
792 | } | ||
793 | |||
794 | |||
795 | module_init (usb_pcwd_init); | ||
796 | module_exit (usb_pcwd_exit); | ||
diff --git a/drivers/char/watchdog/s3c2410_wdt.c b/drivers/char/watchdog/s3c2410_wdt.c new file mode 100644 index 000000000000..1699d2c28ce5 --- /dev/null +++ b/drivers/char/watchdog/s3c2410_wdt.c | |||
@@ -0,0 +1,516 @@ | |||
1 | /* linux/drivers/char/watchdog/s3c2410_wdt.c | ||
2 | * | ||
3 | * Copyright (c) 2004 Simtec Electronics | ||
4 | * Ben Dooks <ben@simtec.co.uk> | ||
5 | * | ||
6 | * S3C2410 Watchdog Timer Support | ||
7 | * | ||
8 | * Based on, softdog.c by Alan Cox, | ||
9 | * (c) Copyright 1996 Alan Cox <alan@redhat.com> | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | * | ||
16 | * This program is distributed in the hope that it will be useful, | ||
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
19 | * GNU General Public License for more details. | ||
20 | * | ||
21 | * You should have received a copy of the GNU General Public License | ||
22 | * along with this program; if not, write to the Free Software | ||
23 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
24 | * | ||
25 | * Changelog: | ||
26 | * 05-Oct-2004 BJD Added semaphore init to stop crashes on open | ||
27 | * Fixed tmr_count / wdt_count confusion | ||
28 | * Added configurable debug | ||
29 | * | ||
30 | * 11-Jan-2004 BJD Fixed divide-by-2 in timeout code | ||
31 | * | ||
32 | * 10-Mar-2005 LCVR Changed S3C2410_VA to S3C24XX_VA | ||
33 | */ | ||
34 | |||
35 | #include <linux/module.h> | ||
36 | #include <linux/moduleparam.h> | ||
37 | #include <linux/config.h> | ||
38 | #include <linux/types.h> | ||
39 | #include <linux/timer.h> | ||
40 | #include <linux/miscdevice.h> | ||
41 | #include <linux/watchdog.h> | ||
42 | #include <linux/fs.h> | ||
43 | #include <linux/notifier.h> | ||
44 | #include <linux/reboot.h> | ||
45 | #include <linux/init.h> | ||
46 | #include <linux/device.h> | ||
47 | #include <linux/interrupt.h> | ||
48 | |||
49 | #include <asm/uaccess.h> | ||
50 | #include <asm/io.h> | ||
51 | |||
52 | #include <asm/arch/map.h> | ||
53 | #include <asm/hardware/clock.h> | ||
54 | |||
55 | #undef S3C24XX_VA_WATCHDOG | ||
56 | #define S3C24XX_VA_WATCHDOG (0) | ||
57 | |||
58 | #include <asm/arch/regs-watchdog.h> | ||
59 | |||
60 | #define PFX "s3c2410-wdt: " | ||
61 | |||
62 | #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) | ||
63 | #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) | ||
64 | |||
65 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
66 | static int nowayout = 1; | ||
67 | #else | ||
68 | static int nowayout = 0; | ||
69 | #endif | ||
70 | |||
71 | static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; | ||
72 | static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; | ||
73 | static int soft_noboot = 0; | ||
74 | static int debug = 0; | ||
75 | |||
76 | module_param(tmr_margin, int, 0); | ||
77 | module_param(tmr_atboot, int, 0); | ||
78 | module_param(nowayout, int, 0); | ||
79 | module_param(soft_noboot, int, 0); | ||
80 | module_param(debug, int, 0); | ||
81 | |||
82 | MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); | ||
83 | |||
84 | MODULE_PARM_DESC(tmr_atboot, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT)); | ||
85 | |||
86 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
87 | |||
88 | MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); | ||
89 | |||
90 | MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)"); | ||
91 | |||
92 | |||
93 | typedef enum close_state { | ||
94 | CLOSE_STATE_NOT, | ||
95 | CLOSE_STATE_ALLOW=0x4021 | ||
96 | } close_state_t; | ||
97 | |||
98 | static DECLARE_MUTEX(open_lock); | ||
99 | |||
100 | static struct resource *wdt_mem; | ||
101 | static struct resource *wdt_irq; | ||
102 | static struct clk *wdt_clock; | ||
103 | static void __iomem *wdt_base; | ||
104 | static unsigned int wdt_count; | ||
105 | static close_state_t allow_close; | ||
106 | |||
107 | /* watchdog control routines */ | ||
108 | |||
109 | #define DBG(msg...) do { \ | ||
110 | if (debug) \ | ||
111 | printk(KERN_INFO msg); \ | ||
112 | } while(0) | ||
113 | |||
114 | /* functions */ | ||
115 | |||
116 | static int s3c2410wdt_keepalive(void) | ||
117 | { | ||
118 | writel(wdt_count, wdt_base + S3C2410_WTCNT); | ||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | static int s3c2410wdt_stop(void) | ||
123 | { | ||
124 | unsigned long wtcon; | ||
125 | |||
126 | wtcon = readl(wdt_base + S3C2410_WTCON); | ||
127 | wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); | ||
128 | writel(wtcon, wdt_base + S3C2410_WTCON); | ||
129 | |||
130 | return 0; | ||
131 | } | ||
132 | |||
133 | static int s3c2410wdt_start(void) | ||
134 | { | ||
135 | unsigned long wtcon; | ||
136 | |||
137 | s3c2410wdt_stop(); | ||
138 | |||
139 | wtcon = readl(wdt_base + S3C2410_WTCON); | ||
140 | wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; | ||
141 | |||
142 | if (soft_noboot) { | ||
143 | wtcon |= S3C2410_WTCON_INTEN; | ||
144 | wtcon &= ~S3C2410_WTCON_RSTEN; | ||
145 | } else { | ||
146 | wtcon &= ~S3C2410_WTCON_INTEN; | ||
147 | wtcon |= S3C2410_WTCON_RSTEN; | ||
148 | } | ||
149 | |||
150 | DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n", | ||
151 | __FUNCTION__, wdt_count, wtcon); | ||
152 | |||
153 | writel(wdt_count, wdt_base + S3C2410_WTDAT); | ||
154 | writel(wdt_count, wdt_base + S3C2410_WTCNT); | ||
155 | writel(wtcon, wdt_base + S3C2410_WTCON); | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static int s3c2410wdt_set_heartbeat(int timeout) | ||
161 | { | ||
162 | unsigned int freq = clk_get_rate(wdt_clock); | ||
163 | unsigned int count; | ||
164 | unsigned int divisor = 1; | ||
165 | unsigned long wtcon; | ||
166 | |||
167 | if (timeout < 1) | ||
168 | return -EINVAL; | ||
169 | |||
170 | freq /= 128; | ||
171 | count = timeout * freq; | ||
172 | |||
173 | DBG("%s: count=%d, timeout=%d, freq=%d\n", | ||
174 | __FUNCTION__, count, timeout, freq); | ||
175 | |||
176 | /* if the count is bigger than the watchdog register, | ||
177 | then work out what we need to do (and if) we can | ||
178 | actually make this value | ||
179 | */ | ||
180 | |||
181 | if (count >= 0x10000) { | ||
182 | for (divisor = 1; divisor <= 0x100; divisor++) { | ||
183 | if ((count / divisor) < 0x10000) | ||
184 | break; | ||
185 | } | ||
186 | |||
187 | if ((count / divisor) >= 0x10000) { | ||
188 | printk(KERN_ERR PFX "timeout %d too big\n", timeout); | ||
189 | return -EINVAL; | ||
190 | } | ||
191 | } | ||
192 | |||
193 | tmr_margin = timeout; | ||
194 | |||
195 | DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", | ||
196 | __FUNCTION__, timeout, divisor, count, count/divisor); | ||
197 | |||
198 | count /= divisor; | ||
199 | wdt_count = count; | ||
200 | |||
201 | /* update the pre-scaler */ | ||
202 | wtcon = readl(wdt_base + S3C2410_WTCON); | ||
203 | wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; | ||
204 | wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); | ||
205 | |||
206 | writel(count, wdt_base + S3C2410_WTDAT); | ||
207 | writel(wtcon, wdt_base + S3C2410_WTCON); | ||
208 | |||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | /* | ||
213 | * /dev/watchdog handling | ||
214 | */ | ||
215 | |||
216 | static int s3c2410wdt_open(struct inode *inode, struct file *file) | ||
217 | { | ||
218 | if(down_trylock(&open_lock)) | ||
219 | return -EBUSY; | ||
220 | |||
221 | if (nowayout) { | ||
222 | __module_get(THIS_MODULE); | ||
223 | } else { | ||
224 | allow_close = CLOSE_STATE_ALLOW; | ||
225 | } | ||
226 | |||
227 | /* start the timer */ | ||
228 | s3c2410wdt_start(); | ||
229 | return nonseekable_open(inode, file); | ||
230 | } | ||
231 | |||
232 | static int s3c2410wdt_release(struct inode *inode, struct file *file) | ||
233 | { | ||
234 | /* | ||
235 | * Shut off the timer. | ||
236 | * Lock it in if it's a module and we set nowayout | ||
237 | */ | ||
238 | if (allow_close == CLOSE_STATE_ALLOW) { | ||
239 | s3c2410wdt_stop(); | ||
240 | } else { | ||
241 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
242 | s3c2410wdt_keepalive(); | ||
243 | } | ||
244 | |||
245 | allow_close = CLOSE_STATE_NOT; | ||
246 | up(&open_lock); | ||
247 | return 0; | ||
248 | } | ||
249 | |||
250 | static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, | ||
251 | size_t len, loff_t *ppos) | ||
252 | { | ||
253 | /* | ||
254 | * Refresh the timer. | ||
255 | */ | ||
256 | if(len) { | ||
257 | if (!nowayout) { | ||
258 | size_t i; | ||
259 | |||
260 | /* In case it was set long ago */ | ||
261 | allow_close = CLOSE_STATE_NOT; | ||
262 | |||
263 | for (i = 0; i != len; i++) { | ||
264 | char c; | ||
265 | |||
266 | if (get_user(c, data + i)) | ||
267 | return -EFAULT; | ||
268 | if (c == 'V') | ||
269 | allow_close = CLOSE_STATE_ALLOW; | ||
270 | } | ||
271 | } | ||
272 | |||
273 | s3c2410wdt_keepalive(); | ||
274 | } | ||
275 | return len; | ||
276 | } | ||
277 | |||
278 | #define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | ||
279 | |||
280 | static struct watchdog_info s3c2410_wdt_ident = { | ||
281 | .options = OPTIONS, | ||
282 | .firmware_version = 0, | ||
283 | .identity = "S3C2410 Watchdog", | ||
284 | }; | ||
285 | |||
286 | |||
287 | static int s3c2410wdt_ioctl(struct inode *inode, struct file *file, | ||
288 | unsigned int cmd, unsigned long arg) | ||
289 | { | ||
290 | void __user *argp = (void __user *)arg; | ||
291 | int __user *p = argp; | ||
292 | int new_margin; | ||
293 | |||
294 | switch (cmd) { | ||
295 | default: | ||
296 | return -ENOIOCTLCMD; | ||
297 | |||
298 | case WDIOC_GETSUPPORT: | ||
299 | return copy_to_user(argp, &s3c2410_wdt_ident, | ||
300 | sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0; | ||
301 | |||
302 | case WDIOC_GETSTATUS: | ||
303 | case WDIOC_GETBOOTSTATUS: | ||
304 | return put_user(0, p); | ||
305 | |||
306 | case WDIOC_KEEPALIVE: | ||
307 | s3c2410wdt_keepalive(); | ||
308 | return 0; | ||
309 | |||
310 | case WDIOC_SETTIMEOUT: | ||
311 | if (get_user(new_margin, p)) | ||
312 | return -EFAULT; | ||
313 | |||
314 | if (s3c2410wdt_set_heartbeat(new_margin)) | ||
315 | return -EINVAL; | ||
316 | |||
317 | s3c2410wdt_keepalive(); | ||
318 | return put_user(tmr_margin, p); | ||
319 | |||
320 | case WDIOC_GETTIMEOUT: | ||
321 | return put_user(tmr_margin, p); | ||
322 | } | ||
323 | } | ||
324 | |||
325 | /* | ||
326 | * Notifier for system down | ||
327 | */ | ||
328 | |||
329 | static int s3c2410wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
330 | void *unused) | ||
331 | { | ||
332 | if(code==SYS_DOWN || code==SYS_HALT) { | ||
333 | /* Turn the WDT off */ | ||
334 | s3c2410wdt_stop(); | ||
335 | } | ||
336 | return NOTIFY_DONE; | ||
337 | } | ||
338 | |||
339 | /* kernel interface */ | ||
340 | |||
341 | static struct file_operations s3c2410wdt_fops = { | ||
342 | .owner = THIS_MODULE, | ||
343 | .llseek = no_llseek, | ||
344 | .write = s3c2410wdt_write, | ||
345 | .ioctl = s3c2410wdt_ioctl, | ||
346 | .open = s3c2410wdt_open, | ||
347 | .release = s3c2410wdt_release, | ||
348 | }; | ||
349 | |||
350 | static struct miscdevice s3c2410wdt_miscdev = { | ||
351 | .minor = WATCHDOG_MINOR, | ||
352 | .name = "watchdog", | ||
353 | .fops = &s3c2410wdt_fops, | ||
354 | }; | ||
355 | |||
356 | static struct notifier_block s3c2410wdt_notifier = { | ||
357 | .notifier_call = s3c2410wdt_notify_sys, | ||
358 | }; | ||
359 | |||
360 | /* interrupt handler code */ | ||
361 | |||
362 | static irqreturn_t s3c2410wdt_irq(int irqno, void *param, | ||
363 | struct pt_regs *regs) | ||
364 | { | ||
365 | printk(KERN_INFO PFX "Watchdog timer expired!\n"); | ||
366 | |||
367 | s3c2410wdt_keepalive(); | ||
368 | return IRQ_HANDLED; | ||
369 | } | ||
370 | /* device interface */ | ||
371 | |||
372 | static int s3c2410wdt_probe(struct device *dev) | ||
373 | { | ||
374 | struct platform_device *pdev = to_platform_device(dev); | ||
375 | struct resource *res; | ||
376 | int started = 0; | ||
377 | int ret; | ||
378 | int size; | ||
379 | |||
380 | DBG("%s: probe=%p, device=%p\n", __FUNCTION__, pdev, dev); | ||
381 | |||
382 | /* get the memory region for the watchdog timer */ | ||
383 | |||
384 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
385 | if (res == NULL) { | ||
386 | printk(KERN_INFO PFX "failed to get memory region resouce\n"); | ||
387 | return -ENOENT; | ||
388 | } | ||
389 | |||
390 | size = (res->end-res->start)+1; | ||
391 | wdt_mem = request_mem_region(res->start, size, pdev->name); | ||
392 | if (wdt_mem == NULL) { | ||
393 | printk(KERN_INFO PFX "failed to get memory region\n"); | ||
394 | return -ENOENT; | ||
395 | } | ||
396 | |||
397 | wdt_base = ioremap(res->start, size); | ||
398 | if (wdt_base == 0) { | ||
399 | printk(KERN_INFO PFX "failed to ioremap() region\n"); | ||
400 | return -EINVAL; | ||
401 | } | ||
402 | |||
403 | DBG("probe: mapped wdt_base=%p\n", wdt_base); | ||
404 | |||
405 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
406 | if (res == NULL) { | ||
407 | printk(KERN_INFO PFX "failed to get irq resource\n"); | ||
408 | return -ENOENT; | ||
409 | } | ||
410 | |||
411 | ret = request_irq(res->start, s3c2410wdt_irq, 0, pdev->name, dev); | ||
412 | if (ret != 0) { | ||
413 | printk(KERN_INFO PFX "failed to install irq (%d)\n", ret); | ||
414 | return ret; | ||
415 | } | ||
416 | |||
417 | wdt_clock = clk_get(dev, "watchdog"); | ||
418 | if (wdt_clock == NULL) { | ||
419 | printk(KERN_INFO PFX "failed to find watchdog clock source\n"); | ||
420 | return -ENOENT; | ||
421 | } | ||
422 | |||
423 | clk_use(wdt_clock); | ||
424 | clk_enable(wdt_clock); | ||
425 | |||
426 | /* see if we can actually set the requested timer margin, and if | ||
427 | * not, try the default value */ | ||
428 | |||
429 | if (s3c2410wdt_set_heartbeat(tmr_margin)) { | ||
430 | started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); | ||
431 | |||
432 | if (started == 0) { | ||
433 | printk(KERN_INFO PFX "tmr_margin value out of range, default %d used\n", | ||
434 | CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); | ||
435 | } else { | ||
436 | printk(KERN_INFO PFX "default timer value is out of range, cannot start\n"); | ||
437 | } | ||
438 | } | ||
439 | |||
440 | ret = register_reboot_notifier(&s3c2410wdt_notifier); | ||
441 | if (ret) { | ||
442 | printk (KERN_ERR PFX "cannot register reboot notifier (%d)\n", | ||
443 | ret); | ||
444 | return ret; | ||
445 | } | ||
446 | |||
447 | ret = misc_register(&s3c2410wdt_miscdev); | ||
448 | if (ret) { | ||
449 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (%d)\n", | ||
450 | WATCHDOG_MINOR, ret); | ||
451 | unregister_reboot_notifier(&s3c2410wdt_notifier); | ||
452 | return ret; | ||
453 | } | ||
454 | |||
455 | if (tmr_atboot && started == 0) { | ||
456 | printk(KERN_INFO PFX "Starting Watchdog Timer\n"); | ||
457 | s3c2410wdt_start(); | ||
458 | } | ||
459 | |||
460 | return 0; | ||
461 | } | ||
462 | |||
463 | static int s3c2410wdt_remove(struct device *dev) | ||
464 | { | ||
465 | if (wdt_mem != NULL) { | ||
466 | release_resource(wdt_mem); | ||
467 | kfree(wdt_mem); | ||
468 | wdt_mem = NULL; | ||
469 | } | ||
470 | |||
471 | if (wdt_irq != NULL) { | ||
472 | free_irq(wdt_irq->start, dev); | ||
473 | wdt_irq = NULL; | ||
474 | } | ||
475 | |||
476 | if (wdt_clock != NULL) { | ||
477 | clk_disable(wdt_clock); | ||
478 | clk_unuse(wdt_clock); | ||
479 | clk_put(wdt_clock); | ||
480 | wdt_clock = NULL; | ||
481 | } | ||
482 | |||
483 | misc_deregister(&s3c2410wdt_miscdev); | ||
484 | return 0; | ||
485 | } | ||
486 | |||
487 | static struct device_driver s3c2410wdt_driver = { | ||
488 | .name = "s3c2410-wdt", | ||
489 | .bus = &platform_bus_type, | ||
490 | .probe = s3c2410wdt_probe, | ||
491 | .remove = s3c2410wdt_remove, | ||
492 | }; | ||
493 | |||
494 | |||
495 | |||
496 | static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; | ||
497 | |||
498 | static int __init watchdog_init(void) | ||
499 | { | ||
500 | printk(banner); | ||
501 | return driver_register(&s3c2410wdt_driver); | ||
502 | } | ||
503 | |||
504 | static void __exit watchdog_exit(void) | ||
505 | { | ||
506 | driver_unregister(&s3c2410wdt_driver); | ||
507 | unregister_reboot_notifier(&s3c2410wdt_notifier); | ||
508 | } | ||
509 | |||
510 | module_init(watchdog_init); | ||
511 | module_exit(watchdog_exit); | ||
512 | |||
513 | MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); | ||
514 | MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); | ||
515 | MODULE_LICENSE("GPL"); | ||
516 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/sa1100_wdt.c b/drivers/char/watchdog/sa1100_wdt.c new file mode 100644 index 000000000000..34e8f7b15e30 --- /dev/null +++ b/drivers/char/watchdog/sa1100_wdt.c | |||
@@ -0,0 +1,223 @@ | |||
1 | /* | ||
2 | * Watchdog driver for the SA11x0/PXA2xx | ||
3 | * | ||
4 | * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> | ||
5 | * Based on SoftDog driver by Alan Cox <alan@redhat.com> | ||
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 Oleg Drokin nor iXcelerator.com admit liability nor provide | ||
13 | * warranty for any of this software. This material is provided | ||
14 | * "AS-IS" and at no charge. | ||
15 | * | ||
16 | * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> | ||
17 | * | ||
18 | * 27/11/2000 Initial release | ||
19 | */ | ||
20 | #include <linux/config.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/moduleparam.h> | ||
23 | #include <linux/types.h> | ||
24 | #include <linux/kernel.h> | ||
25 | #include <linux/fs.h> | ||
26 | #include <linux/miscdevice.h> | ||
27 | #include <linux/watchdog.h> | ||
28 | #include <linux/init.h> | ||
29 | |||
30 | #ifdef CONFIG_ARCH_PXA | ||
31 | #include <asm/arch/pxa-regs.h> | ||
32 | #endif | ||
33 | |||
34 | #include <asm/hardware.h> | ||
35 | #include <asm/bitops.h> | ||
36 | #include <asm/uaccess.h> | ||
37 | |||
38 | #define OSCR_FREQ CLOCK_TICK_RATE | ||
39 | #define SA1100_CLOSE_MAGIC (0x5afc4453) | ||
40 | |||
41 | static unsigned long sa1100wdt_users; | ||
42 | static int expect_close; | ||
43 | static int pre_margin; | ||
44 | static int boot_status; | ||
45 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
46 | static int nowayout = 1; | ||
47 | #else | ||
48 | static int nowayout = 0; | ||
49 | #endif | ||
50 | |||
51 | /* | ||
52 | * Allow only one person to hold it open | ||
53 | */ | ||
54 | static int sa1100dog_open(struct inode *inode, struct file *file) | ||
55 | { | ||
56 | nonseekable_open(inode, file); | ||
57 | if (test_and_set_bit(1,&sa1100wdt_users)) | ||
58 | return -EBUSY; | ||
59 | |||
60 | /* Activate SA1100 Watchdog timer */ | ||
61 | OSMR3 = OSCR + pre_margin; | ||
62 | OSSR = OSSR_M3; | ||
63 | OWER = OWER_WME; | ||
64 | OIER |= OIER_E3; | ||
65 | return 0; | ||
66 | } | ||
67 | |||
68 | /* | ||
69 | * Shut off the timer. | ||
70 | * Lock it in if it's a module and we defined ...NOWAYOUT | ||
71 | * Oddly, the watchdog can only be enabled, but we can turn off | ||
72 | * the interrupt, which appears to prevent the watchdog timing out. | ||
73 | */ | ||
74 | static int sa1100dog_release(struct inode *inode, struct file *file) | ||
75 | { | ||
76 | OSMR3 = OSCR + pre_margin; | ||
77 | |||
78 | if (expect_close == SA1100_CLOSE_MAGIC) { | ||
79 | OIER &= ~OIER_E3; | ||
80 | } else { | ||
81 | printk(KERN_CRIT "WATCHDOG: WDT device closed unexpectedly. WDT will not stop!\n"); | ||
82 | } | ||
83 | |||
84 | clear_bit(1, &sa1100wdt_users); | ||
85 | expect_close = 0; | ||
86 | |||
87 | return 0; | ||
88 | } | ||
89 | |||
90 | static ssize_t sa1100dog_write(struct file *file, const char *data, size_t len, loff_t *ppos) | ||
91 | { | ||
92 | if (len) { | ||
93 | if (!nowayout) { | ||
94 | size_t i; | ||
95 | |||
96 | expect_close = 0; | ||
97 | |||
98 | for (i = 0; i != len; i++) { | ||
99 | char c; | ||
100 | |||
101 | if (get_user(c, data + i)) | ||
102 | return -EFAULT; | ||
103 | if (c == 'V') | ||
104 | expect_close = SA1100_CLOSE_MAGIC; | ||
105 | } | ||
106 | } | ||
107 | /* Refresh OSMR3 timer. */ | ||
108 | OSMR3 = OSCR + pre_margin; | ||
109 | } | ||
110 | |||
111 | return len; | ||
112 | } | ||
113 | |||
114 | static struct watchdog_info ident = { | ||
115 | .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | | ||
116 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
117 | .identity = "SA1100 Watchdog", | ||
118 | }; | ||
119 | |||
120 | static int sa1100dog_ioctl(struct inode *inode, struct file *file, | ||
121 | unsigned int cmd, unsigned long arg) | ||
122 | { | ||
123 | int ret = -ENOIOCTLCMD; | ||
124 | int time; | ||
125 | |||
126 | switch (cmd) { | ||
127 | case WDIOC_GETSUPPORT: | ||
128 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | ||
129 | sizeof(ident)) ? -EFAULT : 0; | ||
130 | break; | ||
131 | |||
132 | case WDIOC_GETSTATUS: | ||
133 | ret = put_user(0, (int *)arg); | ||
134 | break; | ||
135 | |||
136 | case WDIOC_GETBOOTSTATUS: | ||
137 | ret = put_user(boot_status, (int *)arg); | ||
138 | break; | ||
139 | |||
140 | case WDIOC_SETTIMEOUT: | ||
141 | ret = get_user(time, (int *)arg); | ||
142 | if (ret) | ||
143 | break; | ||
144 | |||
145 | if (time <= 0 || time > 255) { | ||
146 | ret = -EINVAL; | ||
147 | break; | ||
148 | } | ||
149 | |||
150 | pre_margin = OSCR_FREQ * time; | ||
151 | OSMR3 = OSCR + pre_margin; | ||
152 | /*fall through*/ | ||
153 | |||
154 | case WDIOC_GETTIMEOUT: | ||
155 | ret = put_user(pre_margin / OSCR_FREQ, (int *)arg); | ||
156 | break; | ||
157 | |||
158 | case WDIOC_KEEPALIVE: | ||
159 | OSMR3 = OSCR + pre_margin; | ||
160 | ret = 0; | ||
161 | break; | ||
162 | } | ||
163 | return ret; | ||
164 | } | ||
165 | |||
166 | static struct file_operations sa1100dog_fops = | ||
167 | { | ||
168 | .owner = THIS_MODULE, | ||
169 | .llseek = no_llseek, | ||
170 | .write = sa1100dog_write, | ||
171 | .ioctl = sa1100dog_ioctl, | ||
172 | .open = sa1100dog_open, | ||
173 | .release = sa1100dog_release, | ||
174 | }; | ||
175 | |||
176 | static struct miscdevice sa1100dog_miscdev = | ||
177 | { | ||
178 | .minor = WATCHDOG_MINOR, | ||
179 | .name = "SA1100/PXA2xx watchdog", | ||
180 | .fops = &sa1100dog_fops, | ||
181 | }; | ||
182 | |||
183 | static int margin __initdata = 60; /* (secs) Default is 1 minute */ | ||
184 | |||
185 | static int __init sa1100dog_init(void) | ||
186 | { | ||
187 | int ret; | ||
188 | |||
189 | /* | ||
190 | * Read the reset status, and save it for later. If | ||
191 | * we suspend, RCSR will be cleared, and the watchdog | ||
192 | * reset reason will be lost. | ||
193 | */ | ||
194 | boot_status = (RCSR & RCSR_WDR) ? WDIOF_CARDRESET : 0; | ||
195 | pre_margin = OSCR_FREQ * margin; | ||
196 | |||
197 | ret = misc_register(&sa1100dog_miscdev); | ||
198 | if (ret == 0) | ||
199 | printk("SA1100/PXA2xx Watchdog Timer: timer margin %d sec\n", | ||
200 | margin); | ||
201 | |||
202 | return ret; | ||
203 | } | ||
204 | |||
205 | static void __exit sa1100dog_exit(void) | ||
206 | { | ||
207 | misc_deregister(&sa1100dog_miscdev); | ||
208 | } | ||
209 | |||
210 | module_init(sa1100dog_init); | ||
211 | module_exit(sa1100dog_exit); | ||
212 | |||
213 | MODULE_AUTHOR("Oleg Drokin <green@crimea.edu>"); | ||
214 | MODULE_DESCRIPTION("SA1100/PXA2xx Watchdog"); | ||
215 | |||
216 | module_param(margin, int, 0); | ||
217 | MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); | ||
218 | |||
219 | module_param(nowayout, int, 0); | ||
220 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); | ||
221 | |||
222 | MODULE_LICENSE("GPL"); | ||
223 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/sbc60xxwdt.c b/drivers/char/watchdog/sbc60xxwdt.c new file mode 100644 index 000000000000..d7de9880605a --- /dev/null +++ b/drivers/char/watchdog/sbc60xxwdt.c | |||
@@ -0,0 +1,413 @@ | |||
1 | /* | ||
2 | * 60xx Single Board Computer Watchdog Timer driver for Linux 2.2.x | ||
3 | * | ||
4 | * Based on acquirewdt.c by Alan Cox. | ||
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 | * The author does NOT admit liability nor provide warranty for | ||
12 | * any of this software. This material is provided "AS-IS" in | ||
13 | * the hope that it may be useful for others. | ||
14 | * | ||
15 | * (c) Copyright 2000 Jakob Oestergaard <jakob@unthought.net> | ||
16 | * | ||
17 | * 12/4 - 2000 [Initial revision] | ||
18 | * 25/4 - 2000 Added /dev/watchdog support | ||
19 | * 09/5 - 2001 [smj@oro.net] fixed fop_write to "return 1" on success | ||
20 | * 12/4 - 2002 [rob@osinvestor.com] eliminate fop_read | ||
21 | * fix possible wdt_is_open race | ||
22 | * add CONFIG_WATCHDOG_NOWAYOUT support | ||
23 | * remove lock_kernel/unlock_kernel pairs | ||
24 | * added KERN_* to printk's | ||
25 | * got rid of extraneous comments | ||
26 | * changed watchdog_info to correctly reflect what the driver offers | ||
27 | * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, WDIOC_SETTIMEOUT, | ||
28 | * WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls | ||
29 | * 09/8 - 2003 [wim@iguana.be] cleanup of trailing spaces | ||
30 | * use module_param | ||
31 | * made timeout (the emulated heartbeat) a module_param | ||
32 | * made the keepalive ping an internal subroutine | ||
33 | * made wdt_stop and wdt_start module params | ||
34 | * added extra printk's for startup problems | ||
35 | * added MODULE_AUTHOR and MODULE_DESCRIPTION info | ||
36 | * | ||
37 | * | ||
38 | * This WDT driver is different from the other Linux WDT | ||
39 | * drivers in the following ways: | ||
40 | * *) The driver will ping the watchdog by itself, because this | ||
41 | * particular WDT has a very short timeout (one second) and it | ||
42 | * would be insane to count on any userspace daemon always | ||
43 | * getting scheduled within that time frame. | ||
44 | * | ||
45 | */ | ||
46 | |||
47 | #include <linux/module.h> | ||
48 | #include <linux/moduleparam.h> | ||
49 | #include <linux/types.h> | ||
50 | #include <linux/timer.h> | ||
51 | #include <linux/jiffies.h> | ||
52 | #include <linux/miscdevice.h> | ||
53 | #include <linux/watchdog.h> | ||
54 | #include <linux/fs.h> | ||
55 | #include <linux/ioport.h> | ||
56 | #include <linux/notifier.h> | ||
57 | #include <linux/reboot.h> | ||
58 | #include <linux/init.h> | ||
59 | |||
60 | #include <asm/io.h> | ||
61 | #include <asm/uaccess.h> | ||
62 | #include <asm/system.h> | ||
63 | |||
64 | #define OUR_NAME "sbc60xxwdt" | ||
65 | #define PFX OUR_NAME ": " | ||
66 | |||
67 | /* | ||
68 | * You must set these - The driver cannot probe for the settings | ||
69 | */ | ||
70 | |||
71 | static int wdt_stop = 0x45; | ||
72 | module_param(wdt_stop, int, 0); | ||
73 | MODULE_PARM_DESC(wdt_stop, "SBC60xx WDT 'stop' io port (default 0x45)"); | ||
74 | |||
75 | static int wdt_start = 0x443; | ||
76 | module_param(wdt_start, int, 0); | ||
77 | MODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)"); | ||
78 | |||
79 | /* | ||
80 | * The 60xx board can use watchdog timeout values from one second | ||
81 | * to several minutes. The default is one second, so if we reset | ||
82 | * the watchdog every ~250ms we should be safe. | ||
83 | */ | ||
84 | |||
85 | #define WDT_INTERVAL (HZ/4+1) | ||
86 | |||
87 | /* | ||
88 | * We must not require too good response from the userspace daemon. | ||
89 | * Here we require the userspace daemon to send us a heartbeat | ||
90 | * char to /dev/watchdog every 30 seconds. | ||
91 | * If the daemon pulses us every 25 seconds, we can still afford | ||
92 | * a 5 second scheduling delay on the (high priority) daemon. That | ||
93 | * should be sufficient for a box under any load. | ||
94 | */ | ||
95 | |||
96 | #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ | ||
97 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ | ||
98 | module_param(timeout, int, 0); | ||
99 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
100 | |||
101 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
102 | static int nowayout = 1; | ||
103 | #else | ||
104 | static int nowayout = 0; | ||
105 | #endif | ||
106 | |||
107 | module_param(nowayout, int, 0); | ||
108 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
109 | |||
110 | static void wdt_timer_ping(unsigned long); | ||
111 | static struct timer_list timer; | ||
112 | static unsigned long next_heartbeat; | ||
113 | static unsigned long wdt_is_open; | ||
114 | static char wdt_expect_close; | ||
115 | |||
116 | /* | ||
117 | * Whack the dog | ||
118 | */ | ||
119 | |||
120 | static void wdt_timer_ping(unsigned long data) | ||
121 | { | ||
122 | /* If we got a heartbeat pulse within the WDT_US_INTERVAL | ||
123 | * we agree to ping the WDT | ||
124 | */ | ||
125 | if(time_before(jiffies, next_heartbeat)) | ||
126 | { | ||
127 | /* Ping the WDT by reading from wdt_start */ | ||
128 | inb_p(wdt_start); | ||
129 | /* Re-set the timer interval */ | ||
130 | timer.expires = jiffies + WDT_INTERVAL; | ||
131 | add_timer(&timer); | ||
132 | } else { | ||
133 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | /* | ||
138 | * Utility routines | ||
139 | */ | ||
140 | |||
141 | static void wdt_startup(void) | ||
142 | { | ||
143 | next_heartbeat = jiffies + (timeout * HZ); | ||
144 | |||
145 | /* Start the timer */ | ||
146 | timer.expires = jiffies + WDT_INTERVAL; | ||
147 | add_timer(&timer); | ||
148 | printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); | ||
149 | } | ||
150 | |||
151 | static void wdt_turnoff(void) | ||
152 | { | ||
153 | /* Stop the timer */ | ||
154 | del_timer(&timer); | ||
155 | inb_p(wdt_stop); | ||
156 | printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); | ||
157 | } | ||
158 | |||
159 | static void wdt_keepalive(void) | ||
160 | { | ||
161 | /* user land ping */ | ||
162 | next_heartbeat = jiffies + (timeout * HZ); | ||
163 | } | ||
164 | |||
165 | /* | ||
166 | * /dev/watchdog handling | ||
167 | */ | ||
168 | |||
169 | static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) | ||
170 | { | ||
171 | /* See if we got the magic character 'V' and reload the timer */ | ||
172 | if(count) | ||
173 | { | ||
174 | if (!nowayout) | ||
175 | { | ||
176 | size_t ofs; | ||
177 | |||
178 | /* note: just in case someone wrote the magic character | ||
179 | * five months ago... */ | ||
180 | wdt_expect_close = 0; | ||
181 | |||
182 | /* scan to see whether or not we got the magic character */ | ||
183 | for(ofs = 0; ofs != count; ofs++) | ||
184 | { | ||
185 | char c; | ||
186 | if(get_user(c, buf+ofs)) | ||
187 | return -EFAULT; | ||
188 | if(c == 'V') | ||
189 | wdt_expect_close = 42; | ||
190 | } | ||
191 | } | ||
192 | |||
193 | /* Well, anyhow someone wrote to us, we should return that favour */ | ||
194 | wdt_keepalive(); | ||
195 | } | ||
196 | return count; | ||
197 | } | ||
198 | |||
199 | static int fop_open(struct inode * inode, struct file * file) | ||
200 | { | ||
201 | nonseekable_open(inode, file); | ||
202 | |||
203 | /* Just in case we're already talking to someone... */ | ||
204 | if(test_and_set_bit(0, &wdt_is_open)) | ||
205 | return -EBUSY; | ||
206 | |||
207 | if (nowayout) | ||
208 | __module_get(THIS_MODULE); | ||
209 | |||
210 | /* Good, fire up the show */ | ||
211 | wdt_startup(); | ||
212 | return 0; | ||
213 | } | ||
214 | |||
215 | static int fop_close(struct inode * inode, struct file * file) | ||
216 | { | ||
217 | if(wdt_expect_close == 42) | ||
218 | wdt_turnoff(); | ||
219 | else { | ||
220 | del_timer(&timer); | ||
221 | printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); | ||
222 | } | ||
223 | clear_bit(0, &wdt_is_open); | ||
224 | wdt_expect_close = 0; | ||
225 | return 0; | ||
226 | } | ||
227 | |||
228 | static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
229 | unsigned long arg) | ||
230 | { | ||
231 | void __user *argp = (void __user *)arg; | ||
232 | int __user *p = argp; | ||
233 | static struct watchdog_info ident= | ||
234 | { | ||
235 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
236 | .firmware_version = 1, | ||
237 | .identity = "SBC60xx", | ||
238 | }; | ||
239 | |||
240 | switch(cmd) | ||
241 | { | ||
242 | default: | ||
243 | return -ENOIOCTLCMD; | ||
244 | case WDIOC_GETSUPPORT: | ||
245 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
246 | case WDIOC_GETSTATUS: | ||
247 | case WDIOC_GETBOOTSTATUS: | ||
248 | return put_user(0, p); | ||
249 | case WDIOC_KEEPALIVE: | ||
250 | wdt_keepalive(); | ||
251 | return 0; | ||
252 | case WDIOC_SETOPTIONS: | ||
253 | { | ||
254 | int new_options, retval = -EINVAL; | ||
255 | |||
256 | if(get_user(new_options, p)) | ||
257 | return -EFAULT; | ||
258 | |||
259 | if(new_options & WDIOS_DISABLECARD) { | ||
260 | wdt_turnoff(); | ||
261 | retval = 0; | ||
262 | } | ||
263 | |||
264 | if(new_options & WDIOS_ENABLECARD) { | ||
265 | wdt_startup(); | ||
266 | retval = 0; | ||
267 | } | ||
268 | |||
269 | return retval; | ||
270 | } | ||
271 | case WDIOC_SETTIMEOUT: | ||
272 | { | ||
273 | int new_timeout; | ||
274 | |||
275 | if(get_user(new_timeout, p)) | ||
276 | return -EFAULT; | ||
277 | |||
278 | if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ | ||
279 | return -EINVAL; | ||
280 | |||
281 | timeout = new_timeout; | ||
282 | wdt_keepalive(); | ||
283 | /* Fall through */ | ||
284 | } | ||
285 | case WDIOC_GETTIMEOUT: | ||
286 | return put_user(timeout, p); | ||
287 | } | ||
288 | } | ||
289 | |||
290 | static struct file_operations wdt_fops = { | ||
291 | .owner = THIS_MODULE, | ||
292 | .llseek = no_llseek, | ||
293 | .write = fop_write, | ||
294 | .open = fop_open, | ||
295 | .release = fop_close, | ||
296 | .ioctl = fop_ioctl, | ||
297 | }; | ||
298 | |||
299 | static struct miscdevice wdt_miscdev = { | ||
300 | .minor = WATCHDOG_MINOR, | ||
301 | .name = "watchdog", | ||
302 | .fops = &wdt_fops, | ||
303 | }; | ||
304 | |||
305 | /* | ||
306 | * Notifier for system down | ||
307 | */ | ||
308 | |||
309 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
310 | void *unused) | ||
311 | { | ||
312 | if(code==SYS_DOWN || code==SYS_HALT) | ||
313 | wdt_turnoff(); | ||
314 | return NOTIFY_DONE; | ||
315 | } | ||
316 | |||
317 | /* | ||
318 | * The WDT needs to learn about soft shutdowns in order to | ||
319 | * turn the timebomb registers off. | ||
320 | */ | ||
321 | |||
322 | static struct notifier_block wdt_notifier= | ||
323 | { | ||
324 | .notifier_call = wdt_notify_sys, | ||
325 | }; | ||
326 | |||
327 | static void __exit sbc60xxwdt_unload(void) | ||
328 | { | ||
329 | wdt_turnoff(); | ||
330 | |||
331 | /* Deregister */ | ||
332 | misc_deregister(&wdt_miscdev); | ||
333 | |||
334 | unregister_reboot_notifier(&wdt_notifier); | ||
335 | if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) | ||
336 | release_region(wdt_stop,1); | ||
337 | release_region(wdt_start,1); | ||
338 | } | ||
339 | |||
340 | static int __init sbc60xxwdt_init(void) | ||
341 | { | ||
342 | int rc = -EBUSY; | ||
343 | |||
344 | if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */ | ||
345 | { | ||
346 | timeout = WATCHDOG_TIMEOUT; | ||
347 | printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", | ||
348 | timeout); | ||
349 | } | ||
350 | |||
351 | if (!request_region(wdt_start, 1, "SBC 60XX WDT")) | ||
352 | { | ||
353 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
354 | wdt_start); | ||
355 | rc = -EIO; | ||
356 | goto err_out; | ||
357 | } | ||
358 | |||
359 | /* We cannot reserve 0x45 - the kernel already has! */ | ||
360 | if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) | ||
361 | { | ||
362 | if (!request_region(wdt_stop, 1, "SBC 60XX WDT")) | ||
363 | { | ||
364 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
365 | wdt_stop); | ||
366 | rc = -EIO; | ||
367 | goto err_out_region1; | ||
368 | } | ||
369 | } | ||
370 | |||
371 | init_timer(&timer); | ||
372 | timer.function = wdt_timer_ping; | ||
373 | timer.data = 0; | ||
374 | |||
375 | rc = misc_register(&wdt_miscdev); | ||
376 | if (rc) | ||
377 | { | ||
378 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
379 | wdt_miscdev.minor, rc); | ||
380 | goto err_out_region2; | ||
381 | } | ||
382 | |||
383 | rc = register_reboot_notifier(&wdt_notifier); | ||
384 | if (rc) | ||
385 | { | ||
386 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
387 | rc); | ||
388 | goto err_out_miscdev; | ||
389 | } | ||
390 | |||
391 | printk(KERN_INFO PFX "WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n", | ||
392 | timeout, nowayout); | ||
393 | |||
394 | return 0; | ||
395 | |||
396 | err_out_miscdev: | ||
397 | misc_deregister(&wdt_miscdev); | ||
398 | err_out_region2: | ||
399 | if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) | ||
400 | release_region(wdt_stop,1); | ||
401 | err_out_region1: | ||
402 | release_region(wdt_start,1); | ||
403 | err_out: | ||
404 | return rc; | ||
405 | } | ||
406 | |||
407 | module_init(sbc60xxwdt_init); | ||
408 | module_exit(sbc60xxwdt_unload); | ||
409 | |||
410 | MODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>"); | ||
411 | MODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver"); | ||
412 | MODULE_LICENSE("GPL"); | ||
413 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/sc1200wdt.c b/drivers/char/watchdog/sc1200wdt.c new file mode 100644 index 000000000000..24401e84729e --- /dev/null +++ b/drivers/char/watchdog/sc1200wdt.c | |||
@@ -0,0 +1,467 @@ | |||
1 | /* | ||
2 | * National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver | ||
3 | * (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>, | ||
4 | * All Rights Reserved. | ||
5 | * Based on wdt.c and wdt977.c by Alan Cox and Woody Suwalski respectively. | ||
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 | * The author(s) of this software shall not be held liable for damages | ||
13 | * of any nature resulting due to the use of this software. This | ||
14 | * software is provided AS-IS with no warranties. | ||
15 | * | ||
16 | * Changelog: | ||
17 | * 20020220 Zwane Mwaikambo Code based on datasheet, no hardware. | ||
18 | * 20020221 Zwane Mwaikambo Cleanups as suggested by Jeff Garzik and Alan Cox. | ||
19 | * 20020222 Zwane Mwaikambo Added probing. | ||
20 | * 20020225 Zwane Mwaikambo Added ISAPNP support. | ||
21 | * 20020412 Rob Radez Broke out start/stop functions | ||
22 | * <rob@osinvestor.com> Return proper status instead of temperature warning | ||
23 | * Add WDIOC_GETBOOTSTATUS and WDIOC_SETOPTIONS ioctls | ||
24 | * Fix CONFIG_WATCHDOG_NOWAYOUT | ||
25 | * 20020530 Joel Becker Add Matt Domsch's nowayout module option | ||
26 | * 20030116 Adam Belay Updated to the latest pnp code | ||
27 | * | ||
28 | */ | ||
29 | |||
30 | #include <linux/config.h> | ||
31 | #include <linux/module.h> | ||
32 | #include <linux/moduleparam.h> | ||
33 | #include <linux/miscdevice.h> | ||
34 | #include <linux/watchdog.h> | ||
35 | #include <linux/ioport.h> | ||
36 | #include <linux/spinlock.h> | ||
37 | #include <linux/notifier.h> | ||
38 | #include <linux/reboot.h> | ||
39 | #include <linux/init.h> | ||
40 | #include <linux/pnp.h> | ||
41 | #include <linux/fs.h> | ||
42 | #include <linux/pci.h> | ||
43 | |||
44 | #include <asm/semaphore.h> | ||
45 | #include <asm/io.h> | ||
46 | #include <asm/uaccess.h> | ||
47 | |||
48 | #define SC1200_MODULE_VER "build 20020303" | ||
49 | #define SC1200_MODULE_NAME "sc1200wdt" | ||
50 | #define PFX SC1200_MODULE_NAME ": " | ||
51 | |||
52 | #define MAX_TIMEOUT 255 /* 255 minutes */ | ||
53 | #define PMIR (io) /* Power Management Index Register */ | ||
54 | #define PMDR (io+1) /* Power Management Data Register */ | ||
55 | |||
56 | /* Data Register indexes */ | ||
57 | #define FER1 0x00 /* Function enable register 1 */ | ||
58 | #define FER2 0x01 /* Function enable register 2 */ | ||
59 | #define PMC1 0x02 /* Power Management Ctrl 1 */ | ||
60 | #define PMC2 0x03 /* Power Management Ctrl 2 */ | ||
61 | #define PMC3 0x04 /* Power Management Ctrl 3 */ | ||
62 | #define WDTO 0x05 /* Watchdog timeout register */ | ||
63 | #define WDCF 0x06 /* Watchdog config register */ | ||
64 | #define WDST 0x07 /* Watchdog status register */ | ||
65 | |||
66 | /* WDCF bitfields - which devices assert WDO */ | ||
67 | #define KBC_IRQ 0x01 /* Keyboard Controller */ | ||
68 | #define MSE_IRQ 0x02 /* Mouse */ | ||
69 | #define UART1_IRQ 0x03 /* Serial0 */ | ||
70 | #define UART2_IRQ 0x04 /* Serial1 */ | ||
71 | /* 5 -7 are reserved */ | ||
72 | |||
73 | static char banner[] __initdata = KERN_INFO PFX SC1200_MODULE_VER; | ||
74 | static int timeout = 1; | ||
75 | static int io = -1; | ||
76 | static int io_len = 2; /* for non plug and play */ | ||
77 | static struct semaphore open_sem; | ||
78 | static char expect_close; | ||
79 | static spinlock_t sc1200wdt_lock; /* io port access serialisation */ | ||
80 | |||
81 | #if defined CONFIG_PNP | ||
82 | static int isapnp = 1; | ||
83 | static struct pnp_dev *wdt_dev; | ||
84 | |||
85 | module_param(isapnp, int, 0); | ||
86 | MODULE_PARM_DESC(isapnp, "When set to 0 driver ISA PnP support will be disabled"); | ||
87 | #endif | ||
88 | |||
89 | module_param(io, int, 0); | ||
90 | MODULE_PARM_DESC(io, "io port"); | ||
91 | module_param(timeout, int, 0); | ||
92 | MODULE_PARM_DESC(timeout, "range is 0-255 minutes, default is 1"); | ||
93 | |||
94 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
95 | static int nowayout = 1; | ||
96 | #else | ||
97 | static int nowayout = 0; | ||
98 | #endif | ||
99 | |||
100 | module_param(nowayout, int, 0); | ||
101 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
102 | |||
103 | |||
104 | |||
105 | /* Read from Data Register */ | ||
106 | static inline void sc1200wdt_read_data(unsigned char index, unsigned char *data) | ||
107 | { | ||
108 | spin_lock(&sc1200wdt_lock); | ||
109 | outb_p(index, PMIR); | ||
110 | *data = inb(PMDR); | ||
111 | spin_unlock(&sc1200wdt_lock); | ||
112 | } | ||
113 | |||
114 | |||
115 | /* Write to Data Register */ | ||
116 | static inline void sc1200wdt_write_data(unsigned char index, unsigned char data) | ||
117 | { | ||
118 | spin_lock(&sc1200wdt_lock); | ||
119 | outb_p(index, PMIR); | ||
120 | outb(data, PMDR); | ||
121 | spin_unlock(&sc1200wdt_lock); | ||
122 | } | ||
123 | |||
124 | |||
125 | static void sc1200wdt_start(void) | ||
126 | { | ||
127 | unsigned char reg; | ||
128 | |||
129 | sc1200wdt_read_data(WDCF, ®); | ||
130 | /* assert WDO when any of the following interrupts are triggered too */ | ||
131 | reg |= (KBC_IRQ | MSE_IRQ | UART1_IRQ | UART2_IRQ); | ||
132 | sc1200wdt_write_data(WDCF, reg); | ||
133 | /* set the timeout and get the ball rolling */ | ||
134 | sc1200wdt_write_data(WDTO, timeout); | ||
135 | } | ||
136 | |||
137 | |||
138 | static void sc1200wdt_stop(void) | ||
139 | { | ||
140 | sc1200wdt_write_data(WDTO, 0); | ||
141 | } | ||
142 | |||
143 | |||
144 | /* This returns the status of the WDO signal, inactive high. */ | ||
145 | static inline int sc1200wdt_status(void) | ||
146 | { | ||
147 | unsigned char ret; | ||
148 | |||
149 | sc1200wdt_read_data(WDST, &ret); | ||
150 | /* If the bit is inactive, the watchdog is enabled, so return | ||
151 | * KEEPALIVEPING which is a bit of a kludge because there's nothing | ||
152 | * else for enabled/disabled status | ||
153 | */ | ||
154 | return (ret & 0x01) ? 0 : WDIOF_KEEPALIVEPING; /* bits 1 - 7 are undefined */ | ||
155 | } | ||
156 | |||
157 | |||
158 | static int sc1200wdt_open(struct inode *inode, struct file *file) | ||
159 | { | ||
160 | nonseekable_open(inode, file); | ||
161 | |||
162 | /* allow one at a time */ | ||
163 | if (down_trylock(&open_sem)) | ||
164 | return -EBUSY; | ||
165 | |||
166 | if (timeout > MAX_TIMEOUT) | ||
167 | timeout = MAX_TIMEOUT; | ||
168 | |||
169 | sc1200wdt_start(); | ||
170 | printk(KERN_INFO PFX "Watchdog enabled, timeout = %d min(s)", timeout); | ||
171 | |||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | |||
176 | static int sc1200wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) | ||
177 | { | ||
178 | int new_timeout; | ||
179 | void __user *argp = (void __user *)arg; | ||
180 | int __user *p = argp; | ||
181 | static struct watchdog_info ident = { | ||
182 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
183 | .firmware_version = 0, | ||
184 | .identity = "PC87307/PC97307", | ||
185 | }; | ||
186 | |||
187 | switch (cmd) { | ||
188 | default: | ||
189 | return -ENOIOCTLCMD; /* Keep Pavel Machek amused ;) */ | ||
190 | |||
191 | case WDIOC_GETSUPPORT: | ||
192 | if (copy_to_user(argp, &ident, sizeof ident)) | ||
193 | return -EFAULT; | ||
194 | return 0; | ||
195 | |||
196 | case WDIOC_GETSTATUS: | ||
197 | return put_user(sc1200wdt_status(), p); | ||
198 | |||
199 | case WDIOC_GETBOOTSTATUS: | ||
200 | return put_user(0, p); | ||
201 | |||
202 | case WDIOC_KEEPALIVE: | ||
203 | sc1200wdt_write_data(WDTO, timeout); | ||
204 | return 0; | ||
205 | |||
206 | case WDIOC_SETTIMEOUT: | ||
207 | if (get_user(new_timeout, p)) | ||
208 | return -EFAULT; | ||
209 | |||
210 | /* the API states this is given in secs */ | ||
211 | new_timeout /= 60; | ||
212 | if (new_timeout < 0 || new_timeout > MAX_TIMEOUT) | ||
213 | return -EINVAL; | ||
214 | |||
215 | timeout = new_timeout; | ||
216 | sc1200wdt_write_data(WDTO, timeout); | ||
217 | /* fall through and return the new timeout */ | ||
218 | |||
219 | case WDIOC_GETTIMEOUT: | ||
220 | return put_user(timeout * 60, p); | ||
221 | |||
222 | case WDIOC_SETOPTIONS: | ||
223 | { | ||
224 | int options, retval = -EINVAL; | ||
225 | |||
226 | if (get_user(options, p)) | ||
227 | return -EFAULT; | ||
228 | |||
229 | if (options & WDIOS_DISABLECARD) { | ||
230 | sc1200wdt_stop(); | ||
231 | retval = 0; | ||
232 | } | ||
233 | |||
234 | if (options & WDIOS_ENABLECARD) { | ||
235 | sc1200wdt_start(); | ||
236 | retval = 0; | ||
237 | } | ||
238 | |||
239 | return retval; | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | |||
244 | |||
245 | static int sc1200wdt_release(struct inode *inode, struct file *file) | ||
246 | { | ||
247 | if (expect_close == 42) { | ||
248 | sc1200wdt_stop(); | ||
249 | printk(KERN_INFO PFX "Watchdog disabled\n"); | ||
250 | } else { | ||
251 | sc1200wdt_write_data(WDTO, timeout); | ||
252 | printk(KERN_CRIT PFX "Unexpected close!, timeout = %d min(s)\n", timeout); | ||
253 | } | ||
254 | up(&open_sem); | ||
255 | expect_close = 0; | ||
256 | |||
257 | return 0; | ||
258 | } | ||
259 | |||
260 | |||
261 | static ssize_t sc1200wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) | ||
262 | { | ||
263 | if (len) { | ||
264 | if (!nowayout) { | ||
265 | size_t i; | ||
266 | |||
267 | expect_close = 0; | ||
268 | |||
269 | for (i = 0; i != len; i++) { | ||
270 | char c; | ||
271 | |||
272 | if (get_user(c, data+i)) | ||
273 | return -EFAULT; | ||
274 | if (c == 'V') | ||
275 | expect_close = 42; | ||
276 | } | ||
277 | } | ||
278 | |||
279 | sc1200wdt_write_data(WDTO, timeout); | ||
280 | return len; | ||
281 | } | ||
282 | |||
283 | return 0; | ||
284 | } | ||
285 | |||
286 | |||
287 | static int sc1200wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
288 | { | ||
289 | if (code == SYS_DOWN || code == SYS_HALT) | ||
290 | sc1200wdt_stop(); | ||
291 | |||
292 | return NOTIFY_DONE; | ||
293 | } | ||
294 | |||
295 | |||
296 | static struct notifier_block sc1200wdt_notifier = | ||
297 | { | ||
298 | .notifier_call = sc1200wdt_notify_sys, | ||
299 | }; | ||
300 | |||
301 | static struct file_operations sc1200wdt_fops = | ||
302 | { | ||
303 | .owner = THIS_MODULE, | ||
304 | .llseek = no_llseek, | ||
305 | .write = sc1200wdt_write, | ||
306 | .ioctl = sc1200wdt_ioctl, | ||
307 | .open = sc1200wdt_open, | ||
308 | .release = sc1200wdt_release, | ||
309 | }; | ||
310 | |||
311 | static struct miscdevice sc1200wdt_miscdev = | ||
312 | { | ||
313 | .minor = WATCHDOG_MINOR, | ||
314 | .name = "watchdog", | ||
315 | .fops = &sc1200wdt_fops, | ||
316 | }; | ||
317 | |||
318 | |||
319 | static int __init sc1200wdt_probe(void) | ||
320 | { | ||
321 | /* The probe works by reading the PMC3 register's default value of 0x0e | ||
322 | * there is one caveat, if the device disables the parallel port or any | ||
323 | * of the UARTs we won't be able to detect it. | ||
324 | * Nb. This could be done with accuracy by reading the SID registers, but | ||
325 | * we don't have access to those io regions. | ||
326 | */ | ||
327 | |||
328 | unsigned char reg; | ||
329 | |||
330 | sc1200wdt_read_data(PMC3, ®); | ||
331 | reg &= 0x0f; /* we don't want the UART busy bits */ | ||
332 | return (reg == 0x0e) ? 0 : -ENODEV; | ||
333 | } | ||
334 | |||
335 | |||
336 | #if defined CONFIG_PNP | ||
337 | |||
338 | static struct pnp_device_id scl200wdt_pnp_devices[] = { | ||
339 | /* National Semiconductor PC87307/PC97307 watchdog component */ | ||
340 | {.id = "NSC0800", .driver_data = 0}, | ||
341 | {.id = ""}, | ||
342 | }; | ||
343 | |||
344 | static int scl200wdt_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id) | ||
345 | { | ||
346 | /* this driver only supports one card at a time */ | ||
347 | if (wdt_dev || !isapnp) | ||
348 | return -EBUSY; | ||
349 | |||
350 | wdt_dev = dev; | ||
351 | io = pnp_port_start(wdt_dev, 0); | ||
352 | io_len = pnp_port_len(wdt_dev, 0); | ||
353 | |||
354 | if (!request_region(io, io_len, SC1200_MODULE_NAME)) { | ||
355 | printk(KERN_ERR PFX "Unable to register IO port %#x\n", io); | ||
356 | return -EBUSY; | ||
357 | } | ||
358 | |||
359 | printk(KERN_INFO "scl200wdt: PnP device found at io port %#x/%d\n", io, io_len); | ||
360 | return 0; | ||
361 | } | ||
362 | |||
363 | static void scl200wdt_pnp_remove(struct pnp_dev * dev) | ||
364 | { | ||
365 | if (wdt_dev){ | ||
366 | release_region(io, io_len); | ||
367 | wdt_dev = NULL; | ||
368 | } | ||
369 | } | ||
370 | |||
371 | static struct pnp_driver scl200wdt_pnp_driver = { | ||
372 | .name = "scl200wdt", | ||
373 | .id_table = scl200wdt_pnp_devices, | ||
374 | .probe = scl200wdt_pnp_probe, | ||
375 | .remove = scl200wdt_pnp_remove, | ||
376 | }; | ||
377 | |||
378 | #endif /* CONFIG_PNP */ | ||
379 | |||
380 | |||
381 | static int __init sc1200wdt_init(void) | ||
382 | { | ||
383 | int ret; | ||
384 | |||
385 | printk(banner); | ||
386 | |||
387 | spin_lock_init(&sc1200wdt_lock); | ||
388 | sema_init(&open_sem, 1); | ||
389 | |||
390 | #if defined CONFIG_PNP | ||
391 | if (isapnp) { | ||
392 | ret = pnp_register_driver(&scl200wdt_pnp_driver); | ||
393 | if (ret) | ||
394 | goto out_clean; | ||
395 | } | ||
396 | #endif | ||
397 | |||
398 | if (io == -1) { | ||
399 | printk(KERN_ERR PFX "io parameter must be specified\n"); | ||
400 | ret = -EINVAL; | ||
401 | goto out_clean; | ||
402 | } | ||
403 | |||
404 | #if defined CONFIG_PNP | ||
405 | /* now that the user has specified an IO port and we haven't detected | ||
406 | * any devices, disable pnp support */ | ||
407 | isapnp = 0; | ||
408 | pnp_unregister_driver(&scl200wdt_pnp_driver); | ||
409 | #endif | ||
410 | |||
411 | if (!request_region(io, io_len, SC1200_MODULE_NAME)) { | ||
412 | printk(KERN_ERR PFX "Unable to register IO port %#x\n", io); | ||
413 | ret = -EBUSY; | ||
414 | goto out_clean; | ||
415 | } | ||
416 | |||
417 | ret = sc1200wdt_probe(); | ||
418 | if (ret) | ||
419 | goto out_io; | ||
420 | |||
421 | ret = register_reboot_notifier(&sc1200wdt_notifier); | ||
422 | if (ret) { | ||
423 | printk(KERN_ERR PFX "Unable to register reboot notifier err = %d\n", ret); | ||
424 | goto out_io; | ||
425 | } | ||
426 | |||
427 | ret = misc_register(&sc1200wdt_miscdev); | ||
428 | if (ret) { | ||
429 | printk(KERN_ERR PFX "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR); | ||
430 | goto out_rbt; | ||
431 | } | ||
432 | |||
433 | /* ret = 0 */ | ||
434 | |||
435 | out_clean: | ||
436 | return ret; | ||
437 | |||
438 | out_rbt: | ||
439 | unregister_reboot_notifier(&sc1200wdt_notifier); | ||
440 | |||
441 | out_io: | ||
442 | release_region(io, io_len); | ||
443 | |||
444 | goto out_clean; | ||
445 | } | ||
446 | |||
447 | |||
448 | static void __exit sc1200wdt_exit(void) | ||
449 | { | ||
450 | misc_deregister(&sc1200wdt_miscdev); | ||
451 | unregister_reboot_notifier(&sc1200wdt_notifier); | ||
452 | |||
453 | #if defined CONFIG_PNP | ||
454 | if(isapnp) | ||
455 | pnp_unregister_driver(&scl200wdt_pnp_driver); | ||
456 | else | ||
457 | #endif | ||
458 | release_region(io, io_len); | ||
459 | } | ||
460 | |||
461 | module_init(sc1200wdt_init); | ||
462 | module_exit(sc1200wdt_exit); | ||
463 | |||
464 | MODULE_AUTHOR("Zwane Mwaikambo <zwane@commfireservices.com>"); | ||
465 | MODULE_DESCRIPTION("Driver for National Semiconductor PC87307/PC97307 watchdog component"); | ||
466 | MODULE_LICENSE("GPL"); | ||
467 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/sc520_wdt.c b/drivers/char/watchdog/sc520_wdt.c new file mode 100644 index 000000000000..f6d143e1900d --- /dev/null +++ b/drivers/char/watchdog/sc520_wdt.c | |||
@@ -0,0 +1,447 @@ | |||
1 | /* | ||
2 | * AMD Elan SC520 processor Watchdog Timer driver | ||
3 | * | ||
4 | * Based on acquirewdt.c by Alan Cox, | ||
5 | * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net> | ||
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 | * The authors do NOT admit liability nor provide warranty for | ||
13 | * any of this software. This material is provided "AS-IS" in | ||
14 | * the hope that it may be useful for others. | ||
15 | * | ||
16 | * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> | ||
17 | * 9/27 - 2001 [Initial release] | ||
18 | * | ||
19 | * Additional fixes Alan Cox | ||
20 | * - Fixed formatting | ||
21 | * - Removed debug printks | ||
22 | * - Fixed SMP built kernel deadlock | ||
23 | * - Switched to private locks not lock_kernel | ||
24 | * - Used ioremap/writew/readw | ||
25 | * - Added NOWAYOUT support | ||
26 | * 4/12 - 2002 Changes by Rob Radez <rob@osinvestor.com> | ||
27 | * - Change comments | ||
28 | * - Eliminate fop_llseek | ||
29 | * - Change CONFIG_WATCHDOG_NOWAYOUT semantics | ||
30 | * - Add KERN_* tags to printks | ||
31 | * - fix possible wdt_is_open race | ||
32 | * - Report proper capabilities in watchdog_info | ||
33 | * - Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT, | ||
34 | * GETTIMEOUT, SETOPTIONS} ioctls | ||
35 | * 09/8 - 2003 Changes by Wim Van Sebroeck <wim@iguana.be> | ||
36 | * - cleanup of trailing spaces | ||
37 | * - added extra printk's for startup problems | ||
38 | * - use module_param | ||
39 | * - made timeout (the emulated heartbeat) a module_param | ||
40 | * - made the keepalive ping an internal subroutine | ||
41 | * 3/27 - 2004 Changes by Sean Young <sean@mess.org> | ||
42 | * - set MMCR_BASE to 0xfffef000 | ||
43 | * - CBAR does not need to be read | ||
44 | * - removed debugging printks | ||
45 | * | ||
46 | * This WDT driver is different from most other Linux WDT | ||
47 | * drivers in that the driver will ping the watchdog by itself, | ||
48 | * because this particular WDT has a very short timeout (1.6 | ||
49 | * seconds) and it would be insane to count on any userspace | ||
50 | * daemon always getting scheduled within that time frame. | ||
51 | * | ||
52 | * This driver uses memory mapped IO, and spinlock. | ||
53 | */ | ||
54 | |||
55 | #include <linux/module.h> | ||
56 | #include <linux/moduleparam.h> | ||
57 | #include <linux/types.h> | ||
58 | #include <linux/timer.h> | ||
59 | #include <linux/miscdevice.h> | ||
60 | #include <linux/watchdog.h> | ||
61 | #include <linux/fs.h> | ||
62 | #include <linux/ioport.h> | ||
63 | #include <linux/notifier.h> | ||
64 | #include <linux/reboot.h> | ||
65 | #include <linux/init.h> | ||
66 | |||
67 | #include <asm/io.h> | ||
68 | #include <asm/uaccess.h> | ||
69 | #include <asm/system.h> | ||
70 | |||
71 | #define OUR_NAME "sc520_wdt" | ||
72 | #define PFX OUR_NAME ": " | ||
73 | |||
74 | /* | ||
75 | * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7) | ||
76 | * | ||
77 | * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s | ||
78 | * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s | ||
79 | * | ||
80 | * We will program the SC520 watchdog for a timeout of 2.01s. | ||
81 | * If we reset the watchdog every ~250ms we should be safe. | ||
82 | */ | ||
83 | |||
84 | #define WDT_INTERVAL (HZ/4+1) | ||
85 | |||
86 | /* | ||
87 | * We must not require too good response from the userspace daemon. | ||
88 | * Here we require the userspace daemon to send us a heartbeat | ||
89 | * char to /dev/watchdog every 30 seconds. | ||
90 | */ | ||
91 | |||
92 | #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ | ||
93 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ | ||
94 | module_param(timeout, int, 0); | ||
95 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
96 | |||
97 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
98 | static int nowayout = 1; | ||
99 | #else | ||
100 | static int nowayout = 0; | ||
101 | #endif | ||
102 | |||
103 | module_param(nowayout, int, 0); | ||
104 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
105 | |||
106 | /* | ||
107 | * AMD Elan SC520 - Watchdog Timer Registers | ||
108 | */ | ||
109 | #define MMCR_BASE 0xfffef000 /* The default base address */ | ||
110 | #define OFFS_WDTMRCTL 0xCB0 /* Watchdog Timer Control Register */ | ||
111 | |||
112 | /* WDT Control Register bit definitions */ | ||
113 | #define WDT_EXP_SEL_01 0x0001 /* [01] Time-out = 496 us (with 33 Mhz clk). */ | ||
114 | #define WDT_EXP_SEL_02 0x0002 /* [02] Time-out = 508 ms (with 33 Mhz clk). */ | ||
115 | #define WDT_EXP_SEL_03 0x0004 /* [03] Time-out = 1.02 s (with 33 Mhz clk). */ | ||
116 | #define WDT_EXP_SEL_04 0x0008 /* [04] Time-out = 2.03 s (with 33 Mhz clk). */ | ||
117 | #define WDT_EXP_SEL_05 0x0010 /* [05] Time-out = 4.07 s (with 33 Mhz clk). */ | ||
118 | #define WDT_EXP_SEL_06 0x0020 /* [06] Time-out = 8.13 s (with 33 Mhz clk). */ | ||
119 | #define WDT_EXP_SEL_07 0x0040 /* [07] Time-out = 16.27s (with 33 Mhz clk). */ | ||
120 | #define WDT_EXP_SEL_08 0x0080 /* [08] Time-out = 32.54s (with 33 Mhz clk). */ | ||
121 | #define WDT_IRQ_FLG 0x1000 /* [12] Interrupt Request Flag */ | ||
122 | #define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */ | ||
123 | #define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */ | ||
124 | |||
125 | static __u16 __iomem *wdtmrctl; | ||
126 | |||
127 | static void wdt_timer_ping(unsigned long); | ||
128 | static struct timer_list timer; | ||
129 | static unsigned long next_heartbeat; | ||
130 | static unsigned long wdt_is_open; | ||
131 | static char wdt_expect_close; | ||
132 | static spinlock_t wdt_spinlock; | ||
133 | |||
134 | /* | ||
135 | * Whack the dog | ||
136 | */ | ||
137 | |||
138 | static void wdt_timer_ping(unsigned long data) | ||
139 | { | ||
140 | /* If we got a heartbeat pulse within the WDT_US_INTERVAL | ||
141 | * we agree to ping the WDT | ||
142 | */ | ||
143 | if(time_before(jiffies, next_heartbeat)) | ||
144 | { | ||
145 | /* Ping the WDT */ | ||
146 | spin_lock(&wdt_spinlock); | ||
147 | writew(0xAAAA, wdtmrctl); | ||
148 | writew(0x5555, wdtmrctl); | ||
149 | spin_unlock(&wdt_spinlock); | ||
150 | |||
151 | /* Re-set the timer interval */ | ||
152 | timer.expires = jiffies + WDT_INTERVAL; | ||
153 | add_timer(&timer); | ||
154 | } else { | ||
155 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
156 | } | ||
157 | } | ||
158 | |||
159 | /* | ||
160 | * Utility routines | ||
161 | */ | ||
162 | |||
163 | static void wdt_config(int writeval) | ||
164 | { | ||
165 | __u16 dummy; | ||
166 | unsigned long flags; | ||
167 | |||
168 | /* buy some time (ping) */ | ||
169 | spin_lock_irqsave(&wdt_spinlock, flags); | ||
170 | dummy=readw(wdtmrctl); /* ensure write synchronization */ | ||
171 | writew(0xAAAA, wdtmrctl); | ||
172 | writew(0x5555, wdtmrctl); | ||
173 | /* unlock WDT = make WDT configuration register writable one time */ | ||
174 | writew(0x3333, wdtmrctl); | ||
175 | writew(0xCCCC, wdtmrctl); | ||
176 | /* write WDT configuration register */ | ||
177 | writew(writeval, wdtmrctl); | ||
178 | spin_unlock_irqrestore(&wdt_spinlock, flags); | ||
179 | } | ||
180 | |||
181 | static int wdt_startup(void) | ||
182 | { | ||
183 | next_heartbeat = jiffies + (timeout * HZ); | ||
184 | |||
185 | /* Start the timer */ | ||
186 | timer.expires = jiffies + WDT_INTERVAL; | ||
187 | add_timer(&timer); | ||
188 | |||
189 | /* Start the watchdog */ | ||
190 | wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04); | ||
191 | |||
192 | printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); | ||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | static int wdt_turnoff(void) | ||
197 | { | ||
198 | /* Stop the timer */ | ||
199 | del_timer(&timer); | ||
200 | |||
201 | /* Stop the watchdog */ | ||
202 | wdt_config(0); | ||
203 | |||
204 | printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); | ||
205 | return 0; | ||
206 | } | ||
207 | |||
208 | static int wdt_keepalive(void) | ||
209 | { | ||
210 | /* user land ping */ | ||
211 | next_heartbeat = jiffies + (timeout * HZ); | ||
212 | return 0; | ||
213 | } | ||
214 | |||
215 | static int wdt_set_heartbeat(int t) | ||
216 | { | ||
217 | if ((t < 1) || (t > 3600)) /* arbitrary upper limit */ | ||
218 | return -EINVAL; | ||
219 | |||
220 | timeout = t; | ||
221 | return 0; | ||
222 | } | ||
223 | |||
224 | /* | ||
225 | * /dev/watchdog handling | ||
226 | */ | ||
227 | |||
228 | static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) | ||
229 | { | ||
230 | /* See if we got the magic character 'V' and reload the timer */ | ||
231 | if(count) { | ||
232 | if (!nowayout) { | ||
233 | size_t ofs; | ||
234 | |||
235 | /* note: just in case someone wrote the magic character | ||
236 | * five months ago... */ | ||
237 | wdt_expect_close = 0; | ||
238 | |||
239 | /* now scan */ | ||
240 | for(ofs = 0; ofs != count; ofs++) { | ||
241 | char c; | ||
242 | if (get_user(c, buf + ofs)) | ||
243 | return -EFAULT; | ||
244 | if(c == 'V') | ||
245 | wdt_expect_close = 42; | ||
246 | } | ||
247 | } | ||
248 | |||
249 | /* Well, anyhow someone wrote to us, we should return that favour */ | ||
250 | wdt_keepalive(); | ||
251 | } | ||
252 | return count; | ||
253 | } | ||
254 | |||
255 | static int fop_open(struct inode * inode, struct file * file) | ||
256 | { | ||
257 | nonseekable_open(inode, file); | ||
258 | |||
259 | /* Just in case we're already talking to someone... */ | ||
260 | if(test_and_set_bit(0, &wdt_is_open)) | ||
261 | return -EBUSY; | ||
262 | if (nowayout) | ||
263 | __module_get(THIS_MODULE); | ||
264 | |||
265 | /* Good, fire up the show */ | ||
266 | wdt_startup(); | ||
267 | return 0; | ||
268 | } | ||
269 | |||
270 | static int fop_close(struct inode * inode, struct file * file) | ||
271 | { | ||
272 | if(wdt_expect_close == 42) { | ||
273 | wdt_turnoff(); | ||
274 | } else { | ||
275 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
276 | wdt_keepalive(); | ||
277 | } | ||
278 | clear_bit(0, &wdt_is_open); | ||
279 | wdt_expect_close = 0; | ||
280 | return 0; | ||
281 | } | ||
282 | |||
283 | static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
284 | unsigned long arg) | ||
285 | { | ||
286 | void __user *argp = (void __user *)arg; | ||
287 | int __user *p = argp; | ||
288 | static struct watchdog_info ident = { | ||
289 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
290 | .firmware_version = 1, | ||
291 | .identity = "SC520", | ||
292 | }; | ||
293 | |||
294 | switch(cmd) | ||
295 | { | ||
296 | default: | ||
297 | return -ENOIOCTLCMD; | ||
298 | case WDIOC_GETSUPPORT: | ||
299 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
300 | case WDIOC_GETSTATUS: | ||
301 | case WDIOC_GETBOOTSTATUS: | ||
302 | return put_user(0, p); | ||
303 | case WDIOC_KEEPALIVE: | ||
304 | wdt_keepalive(); | ||
305 | return 0; | ||
306 | case WDIOC_SETOPTIONS: | ||
307 | { | ||
308 | int new_options, retval = -EINVAL; | ||
309 | |||
310 | if(get_user(new_options, p)) | ||
311 | return -EFAULT; | ||
312 | |||
313 | if(new_options & WDIOS_DISABLECARD) { | ||
314 | wdt_turnoff(); | ||
315 | retval = 0; | ||
316 | } | ||
317 | |||
318 | if(new_options & WDIOS_ENABLECARD) { | ||
319 | wdt_startup(); | ||
320 | retval = 0; | ||
321 | } | ||
322 | |||
323 | return retval; | ||
324 | } | ||
325 | case WDIOC_SETTIMEOUT: | ||
326 | { | ||
327 | int new_timeout; | ||
328 | |||
329 | if(get_user(new_timeout, p)) | ||
330 | return -EFAULT; | ||
331 | |||
332 | if(wdt_set_heartbeat(new_timeout)) | ||
333 | return -EINVAL; | ||
334 | |||
335 | wdt_keepalive(); | ||
336 | /* Fall through */ | ||
337 | } | ||
338 | case WDIOC_GETTIMEOUT: | ||
339 | return put_user(timeout, p); | ||
340 | } | ||
341 | } | ||
342 | |||
343 | static struct file_operations wdt_fops = { | ||
344 | .owner = THIS_MODULE, | ||
345 | .llseek = no_llseek, | ||
346 | .write = fop_write, | ||
347 | .open = fop_open, | ||
348 | .release = fop_close, | ||
349 | .ioctl = fop_ioctl, | ||
350 | }; | ||
351 | |||
352 | static struct miscdevice wdt_miscdev = { | ||
353 | .minor = WATCHDOG_MINOR, | ||
354 | .name = "watchdog", | ||
355 | .fops = &wdt_fops, | ||
356 | }; | ||
357 | |||
358 | /* | ||
359 | * Notifier for system down | ||
360 | */ | ||
361 | |||
362 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
363 | void *unused) | ||
364 | { | ||
365 | if(code==SYS_DOWN || code==SYS_HALT) | ||
366 | wdt_turnoff(); | ||
367 | return NOTIFY_DONE; | ||
368 | } | ||
369 | |||
370 | /* | ||
371 | * The WDT needs to learn about soft shutdowns in order to | ||
372 | * turn the timebomb registers off. | ||
373 | */ | ||
374 | |||
375 | static struct notifier_block wdt_notifier = { | ||
376 | .notifier_call = wdt_notify_sys, | ||
377 | }; | ||
378 | |||
379 | static void __exit sc520_wdt_unload(void) | ||
380 | { | ||
381 | if (!nowayout) | ||
382 | wdt_turnoff(); | ||
383 | |||
384 | /* Deregister */ | ||
385 | misc_deregister(&wdt_miscdev); | ||
386 | unregister_reboot_notifier(&wdt_notifier); | ||
387 | iounmap(wdtmrctl); | ||
388 | } | ||
389 | |||
390 | static int __init sc520_wdt_init(void) | ||
391 | { | ||
392 | int rc = -EBUSY; | ||
393 | |||
394 | spin_lock_init(&wdt_spinlock); | ||
395 | |||
396 | init_timer(&timer); | ||
397 | timer.function = wdt_timer_ping; | ||
398 | timer.data = 0; | ||
399 | |||
400 | /* Check that the timeout value is within it's range ; if not reset to the default */ | ||
401 | if (wdt_set_heartbeat(timeout)) { | ||
402 | wdt_set_heartbeat(WATCHDOG_TIMEOUT); | ||
403 | printk(KERN_INFO PFX "timeout value must be 1<=timeout<=3600, using %d\n", | ||
404 | WATCHDOG_TIMEOUT); | ||
405 | } | ||
406 | |||
407 | wdtmrctl = ioremap((unsigned long)(MMCR_BASE + OFFS_WDTMRCTL), 2); | ||
408 | if (!wdtmrctl) { | ||
409 | printk(KERN_ERR PFX "Unable to remap memory\n"); | ||
410 | rc = -ENOMEM; | ||
411 | goto err_out_region2; | ||
412 | } | ||
413 | |||
414 | rc = register_reboot_notifier(&wdt_notifier); | ||
415 | if (rc) { | ||
416 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
417 | rc); | ||
418 | goto err_out_ioremap; | ||
419 | } | ||
420 | |||
421 | rc = misc_register(&wdt_miscdev); | ||
422 | if (rc) { | ||
423 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
424 | WATCHDOG_MINOR, rc); | ||
425 | goto err_out_notifier; | ||
426 | } | ||
427 | |||
428 | printk(KERN_INFO PFX "WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n", | ||
429 | timeout,nowayout); | ||
430 | |||
431 | return 0; | ||
432 | |||
433 | err_out_notifier: | ||
434 | unregister_reboot_notifier(&wdt_notifier); | ||
435 | err_out_ioremap: | ||
436 | iounmap(wdtmrctl); | ||
437 | err_out_region2: | ||
438 | return rc; | ||
439 | } | ||
440 | |||
441 | module_init(sc520_wdt_init); | ||
442 | module_exit(sc520_wdt_unload); | ||
443 | |||
444 | MODULE_AUTHOR("Scott and Bill Jennings"); | ||
445 | MODULE_DESCRIPTION("Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor"); | ||
446 | MODULE_LICENSE("GPL"); | ||
447 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/scx200_wdt.c b/drivers/char/watchdog/scx200_wdt.c new file mode 100644 index 000000000000..b569670e4ed5 --- /dev/null +++ b/drivers/char/watchdog/scx200_wdt.c | |||
@@ -0,0 +1,274 @@ | |||
1 | /* drivers/char/watchdog/scx200_wdt.c | ||
2 | |||
3 | National Semiconductor SCx200 Watchdog support | ||
4 | |||
5 | Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> | ||
6 | |||
7 | Some code taken from: | ||
8 | National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver | ||
9 | (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com> | ||
10 | |||
11 | This program is free software; you can redistribute it and/or | ||
12 | modify it under the terms of the GNU General Public License as | ||
13 | published by the Free Software Foundation; either version 2 of the | ||
14 | License, or (at your option) any later version. | ||
15 | |||
16 | The author(s) of this software shall not be held liable for damages | ||
17 | of any nature resulting due to the use of this software. This | ||
18 | software is provided AS-IS with no warranties. */ | ||
19 | |||
20 | #include <linux/config.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/moduleparam.h> | ||
23 | #include <linux/init.h> | ||
24 | #include <linux/miscdevice.h> | ||
25 | #include <linux/watchdog.h> | ||
26 | #include <linux/notifier.h> | ||
27 | #include <linux/reboot.h> | ||
28 | #include <linux/fs.h> | ||
29 | #include <linux/pci.h> | ||
30 | #include <linux/scx200.h> | ||
31 | |||
32 | #include <asm/uaccess.h> | ||
33 | #include <asm/io.h> | ||
34 | |||
35 | #define NAME "scx200_wdt" | ||
36 | |||
37 | MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>"); | ||
38 | MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver"); | ||
39 | MODULE_LICENSE("GPL"); | ||
40 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
41 | |||
42 | #ifndef CONFIG_WATCHDOG_NOWAYOUT | ||
43 | #define CONFIG_WATCHDOG_NOWAYOUT 0 | ||
44 | #endif | ||
45 | |||
46 | static int margin = 60; /* in seconds */ | ||
47 | module_param(margin, int, 0); | ||
48 | MODULE_PARM_DESC(margin, "Watchdog margin in seconds"); | ||
49 | |||
50 | static int nowayout = CONFIG_WATCHDOG_NOWAYOUT; | ||
51 | module_param(nowayout, int, 0); | ||
52 | MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); | ||
53 | |||
54 | static u16 wdto_restart; | ||
55 | static struct semaphore open_semaphore; | ||
56 | static char expect_close; | ||
57 | |||
58 | /* Bits of the WDCNFG register */ | ||
59 | #define W_ENABLE 0x00fa /* Enable watchdog */ | ||
60 | #define W_DISABLE 0x0000 /* Disable watchdog */ | ||
61 | |||
62 | /* The scaling factor for the timer, this depends on the value of W_ENABLE */ | ||
63 | #define W_SCALE (32768/1024) | ||
64 | |||
65 | static void scx200_wdt_ping(void) | ||
66 | { | ||
67 | outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO); | ||
68 | } | ||
69 | |||
70 | static void scx200_wdt_update_margin(void) | ||
71 | { | ||
72 | printk(KERN_INFO NAME ": timer margin %d seconds\n", margin); | ||
73 | wdto_restart = margin * W_SCALE; | ||
74 | } | ||
75 | |||
76 | static void scx200_wdt_enable(void) | ||
77 | { | ||
78 | printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d\n", | ||
79 | wdto_restart); | ||
80 | |||
81 | outw(0, scx200_cb_base + SCx200_WDT_WDTO); | ||
82 | outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS); | ||
83 | outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG); | ||
84 | |||
85 | scx200_wdt_ping(); | ||
86 | } | ||
87 | |||
88 | static void scx200_wdt_disable(void) | ||
89 | { | ||
90 | printk(KERN_DEBUG NAME ": disabling watchdog timer\n"); | ||
91 | |||
92 | outw(0, scx200_cb_base + SCx200_WDT_WDTO); | ||
93 | outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS); | ||
94 | outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG); | ||
95 | } | ||
96 | |||
97 | static int scx200_wdt_open(struct inode *inode, struct file *file) | ||
98 | { | ||
99 | /* only allow one at a time */ | ||
100 | if (down_trylock(&open_semaphore)) | ||
101 | return -EBUSY; | ||
102 | scx200_wdt_enable(); | ||
103 | |||
104 | return nonseekable_open(inode, file); | ||
105 | } | ||
106 | |||
107 | static int scx200_wdt_release(struct inode *inode, struct file *file) | ||
108 | { | ||
109 | if (expect_close != 42) { | ||
110 | printk(KERN_WARNING NAME ": watchdog device closed unexpectedly, will not disable the watchdog timer\n"); | ||
111 | } else if (!nowayout) { | ||
112 | scx200_wdt_disable(); | ||
113 | } | ||
114 | expect_close = 0; | ||
115 | up(&open_semaphore); | ||
116 | |||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | static int scx200_wdt_notify_sys(struct notifier_block *this, | ||
121 | unsigned long code, void *unused) | ||
122 | { | ||
123 | if (code == SYS_HALT || code == SYS_POWER_OFF) | ||
124 | if (!nowayout) | ||
125 | scx200_wdt_disable(); | ||
126 | |||
127 | return NOTIFY_DONE; | ||
128 | } | ||
129 | |||
130 | static struct notifier_block scx200_wdt_notifier = | ||
131 | { | ||
132 | .notifier_call = scx200_wdt_notify_sys, | ||
133 | }; | ||
134 | |||
135 | static ssize_t scx200_wdt_write(struct file *file, const char __user *data, | ||
136 | size_t len, loff_t *ppos) | ||
137 | { | ||
138 | /* check for a magic close character */ | ||
139 | if (len) | ||
140 | { | ||
141 | size_t i; | ||
142 | |||
143 | scx200_wdt_ping(); | ||
144 | |||
145 | expect_close = 0; | ||
146 | for (i = 0; i < len; ++i) { | ||
147 | char c; | ||
148 | if (get_user(c, data+i)) | ||
149 | return -EFAULT; | ||
150 | if (c == 'V') | ||
151 | expect_close = 42; | ||
152 | } | ||
153 | |||
154 | return len; | ||
155 | } | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static int scx200_wdt_ioctl(struct inode *inode, struct file *file, | ||
161 | unsigned int cmd, unsigned long arg) | ||
162 | { | ||
163 | void __user *argp = (void __user *)arg; | ||
164 | int __user *p = argp; | ||
165 | static struct watchdog_info ident = { | ||
166 | .identity = "NatSemi SCx200 Watchdog", | ||
167 | .firmware_version = 1, | ||
168 | .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING), | ||
169 | }; | ||
170 | int new_margin; | ||
171 | |||
172 | switch (cmd) { | ||
173 | default: | ||
174 | return -ENOIOCTLCMD; | ||
175 | case WDIOC_GETSUPPORT: | ||
176 | if(copy_to_user(argp, &ident, sizeof(ident))) | ||
177 | return -EFAULT; | ||
178 | return 0; | ||
179 | case WDIOC_GETSTATUS: | ||
180 | case WDIOC_GETBOOTSTATUS: | ||
181 | if (put_user(0, p)) | ||
182 | return -EFAULT; | ||
183 | return 0; | ||
184 | case WDIOC_KEEPALIVE: | ||
185 | scx200_wdt_ping(); | ||
186 | return 0; | ||
187 | case WDIOC_SETTIMEOUT: | ||
188 | if (get_user(new_margin, p)) | ||
189 | return -EFAULT; | ||
190 | if (new_margin < 1) | ||
191 | return -EINVAL; | ||
192 | margin = new_margin; | ||
193 | scx200_wdt_update_margin(); | ||
194 | scx200_wdt_ping(); | ||
195 | case WDIOC_GETTIMEOUT: | ||
196 | if (put_user(margin, p)) | ||
197 | return -EFAULT; | ||
198 | return 0; | ||
199 | } | ||
200 | } | ||
201 | |||
202 | static struct file_operations scx200_wdt_fops = { | ||
203 | .owner = THIS_MODULE, | ||
204 | .llseek = no_llseek, | ||
205 | .write = scx200_wdt_write, | ||
206 | .ioctl = scx200_wdt_ioctl, | ||
207 | .open = scx200_wdt_open, | ||
208 | .release = scx200_wdt_release, | ||
209 | }; | ||
210 | |||
211 | static struct miscdevice scx200_wdt_miscdev = { | ||
212 | .minor = WATCHDOG_MINOR, | ||
213 | .name = NAME, | ||
214 | .fops = &scx200_wdt_fops, | ||
215 | }; | ||
216 | |||
217 | static int __init scx200_wdt_init(void) | ||
218 | { | ||
219 | int r; | ||
220 | |||
221 | printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver\n"); | ||
222 | |||
223 | /* check that we have found the configuration block */ | ||
224 | if (!scx200_cb_present()) | ||
225 | return -ENODEV; | ||
226 | |||
227 | if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET, | ||
228 | SCx200_WDT_SIZE, | ||
229 | "NatSemi SCx200 Watchdog")) { | ||
230 | printk(KERN_WARNING NAME ": watchdog I/O region busy\n"); | ||
231 | return -EBUSY; | ||
232 | } | ||
233 | |||
234 | scx200_wdt_update_margin(); | ||
235 | scx200_wdt_disable(); | ||
236 | |||
237 | sema_init(&open_semaphore, 1); | ||
238 | |||
239 | r = misc_register(&scx200_wdt_miscdev); | ||
240 | if (r) { | ||
241 | release_region(scx200_cb_base + SCx200_WDT_OFFSET, | ||
242 | SCx200_WDT_SIZE); | ||
243 | return r; | ||
244 | } | ||
245 | |||
246 | r = register_reboot_notifier(&scx200_wdt_notifier); | ||
247 | if (r) { | ||
248 | printk(KERN_ERR NAME ": unable to register reboot notifier"); | ||
249 | misc_deregister(&scx200_wdt_miscdev); | ||
250 | release_region(scx200_cb_base + SCx200_WDT_OFFSET, | ||
251 | SCx200_WDT_SIZE); | ||
252 | return r; | ||
253 | } | ||
254 | |||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | static void __exit scx200_wdt_cleanup(void) | ||
259 | { | ||
260 | unregister_reboot_notifier(&scx200_wdt_notifier); | ||
261 | misc_deregister(&scx200_wdt_miscdev); | ||
262 | release_region(scx200_cb_base + SCx200_WDT_OFFSET, | ||
263 | SCx200_WDT_SIZE); | ||
264 | } | ||
265 | |||
266 | module_init(scx200_wdt_init); | ||
267 | module_exit(scx200_wdt_cleanup); | ||
268 | |||
269 | /* | ||
270 | Local variables: | ||
271 | compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules" | ||
272 | c-basic-offset: 8 | ||
273 | End: | ||
274 | */ | ||
diff --git a/drivers/char/watchdog/shwdt.c b/drivers/char/watchdog/shwdt.c new file mode 100644 index 000000000000..3bc9272a474c --- /dev/null +++ b/drivers/char/watchdog/shwdt.c | |||
@@ -0,0 +1,452 @@ | |||
1 | /* | ||
2 | * drivers/char/watchdog/shwdt.c | ||
3 | * | ||
4 | * Watchdog driver for integrated watchdog in the SuperH processors. | ||
5 | * | ||
6 | * Copyright (C) 2001, 2002, 2003 Paul Mundt <lethal@linux-sh.org> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms of the GNU General Public License as published by the | ||
10 | * Free Software Foundation; either version 2 of the License, or (at your | ||
11 | * option) any later version. | ||
12 | * | ||
13 | * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> | ||
14 | * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT | ||
15 | * | ||
16 | * 19-Apr-2002 Rob Radez <rob@osinvestor.com> | ||
17 | * Added expect close support, made emulated timeout runtime changeable | ||
18 | * general cleanups, add some ioctls | ||
19 | */ | ||
20 | #include <linux/config.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/moduleparam.h> | ||
23 | #include <linux/init.h> | ||
24 | #include <linux/types.h> | ||
25 | #include <linux/miscdevice.h> | ||
26 | #include <linux/watchdog.h> | ||
27 | #include <linux/reboot.h> | ||
28 | #include <linux/notifier.h> | ||
29 | #include <linux/ioport.h> | ||
30 | #include <linux/fs.h> | ||
31 | |||
32 | #include <asm/io.h> | ||
33 | #include <asm/uaccess.h> | ||
34 | #include <asm/watchdog.h> | ||
35 | |||
36 | #define PFX "shwdt: " | ||
37 | |||
38 | /* | ||
39 | * Default clock division ratio is 5.25 msecs. For an additional table of | ||
40 | * values, consult the asm-sh/watchdog.h. Overload this at module load | ||
41 | * time. | ||
42 | * | ||
43 | * In order for this to work reliably we need to have HZ set to 1000 or | ||
44 | * something quite higher than 100 (or we need a proper high-res timer | ||
45 | * implementation that will deal with this properly), otherwise the 10ms | ||
46 | * resolution of a jiffy is enough to trigger the overflow. For things like | ||
47 | * the SH-4 and SH-5, this isn't necessarily that big of a problem, though | ||
48 | * for the SH-2 and SH-3, this isn't recommended unless the WDT is absolutely | ||
49 | * necssary. | ||
50 | * | ||
51 | * As a result of this timing problem, the only modes that are particularly | ||
52 | * feasible are the 4096 and the 2048 divisors, which yeild 5.25 and 2.62ms | ||
53 | * overflow periods respectively. | ||
54 | * | ||
55 | * Also, since we can't really expect userspace to be responsive enough | ||
56 | * before the overflow happens, we maintain two seperate timers .. One in | ||
57 | * the kernel for clearing out WOVF every 2ms or so (again, this depends on | ||
58 | * HZ == 1000), and another for monitoring userspace writes to the WDT device. | ||
59 | * | ||
60 | * As such, we currently use a configurable heartbeat interval which defaults | ||
61 | * to 30s. In this case, the userspace daemon is only responsible for periodic | ||
62 | * writes to the device before the next heartbeat is scheduled. If the daemon | ||
63 | * misses its deadline, the kernel timer will allow the WDT to overflow. | ||
64 | */ | ||
65 | static int clock_division_ratio = WTCSR_CKS_4096; | ||
66 | |||
67 | #define next_ping_period(cks) msecs_to_jiffies(cks - 4) | ||
68 | |||
69 | static unsigned long shwdt_is_open; | ||
70 | static struct watchdog_info sh_wdt_info; | ||
71 | static char shwdt_expect_close; | ||
72 | static struct timer_list timer; | ||
73 | static unsigned long next_heartbeat; | ||
74 | |||
75 | #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ | ||
76 | static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ | ||
77 | |||
78 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
79 | static int nowayout = 1; | ||
80 | #else | ||
81 | static int nowayout = 0; | ||
82 | #endif | ||
83 | |||
84 | /** | ||
85 | * sh_wdt_start - Start the Watchdog | ||
86 | * | ||
87 | * Starts the watchdog. | ||
88 | */ | ||
89 | static void sh_wdt_start(void) | ||
90 | { | ||
91 | __u8 csr; | ||
92 | |||
93 | next_heartbeat = jiffies + (heartbeat * HZ); | ||
94 | mod_timer(&timer, next_ping_period(clock_division_ratio)); | ||
95 | |||
96 | csr = sh_wdt_read_csr(); | ||
97 | csr |= WTCSR_WT | clock_division_ratio; | ||
98 | sh_wdt_write_csr(csr); | ||
99 | |||
100 | sh_wdt_write_cnt(0); | ||
101 | |||
102 | /* | ||
103 | * These processors have a bit of an inconsistent initialization | ||
104 | * process.. starting with SH-3, RSTS was moved to WTCSR, and the | ||
105 | * RSTCSR register was removed. | ||
106 | * | ||
107 | * On the SH-2 however, in addition with bits being in different | ||
108 | * locations, we must deal with RSTCSR outright.. | ||
109 | */ | ||
110 | csr = sh_wdt_read_csr(); | ||
111 | csr |= WTCSR_TME; | ||
112 | csr &= ~WTCSR_RSTS; | ||
113 | sh_wdt_write_csr(csr); | ||
114 | |||
115 | #ifdef CONFIG_CPU_SH2 | ||
116 | /* | ||
117 | * Whoever came up with the RSTCSR semantics must've been smoking | ||
118 | * some of the good stuff, since in addition to the WTCSR/WTCNT write | ||
119 | * brain-damage, it's managed to fuck things up one step further.. | ||
120 | * | ||
121 | * If we need to clear the WOVF bit, the upper byte has to be 0xa5.. | ||
122 | * but if we want to touch RSTE or RSTS, the upper byte has to be | ||
123 | * 0x5a.. | ||
124 | */ | ||
125 | csr = sh_wdt_read_rstcsr(); | ||
126 | csr &= ~RSTCSR_RSTS; | ||
127 | sh_wdt_write_rstcsr(csr); | ||
128 | #endif | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * sh_wdt_stop - Stop the Watchdog | ||
133 | * | ||
134 | * Stops the watchdog. | ||
135 | */ | ||
136 | static void sh_wdt_stop(void) | ||
137 | { | ||
138 | __u8 csr; | ||
139 | |||
140 | del_timer(&timer); | ||
141 | |||
142 | csr = sh_wdt_read_csr(); | ||
143 | csr &= ~WTCSR_TME; | ||
144 | sh_wdt_write_csr(csr); | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * sh_wdt_keepalive - Keep the Userspace Watchdog Alive | ||
149 | * | ||
150 | * The Userspace watchdog got a KeepAlive: schedule the next heartbeat. | ||
151 | */ | ||
152 | static void sh_wdt_keepalive(void) | ||
153 | { | ||
154 | next_heartbeat = jiffies + (heartbeat * HZ); | ||
155 | } | ||
156 | |||
157 | /** | ||
158 | * sh_wdt_set_heartbeat - Set the Userspace Watchdog heartbeat | ||
159 | * | ||
160 | * Set the Userspace Watchdog heartbeat | ||
161 | */ | ||
162 | static int sh_wdt_set_heartbeat(int t) | ||
163 | { | ||
164 | if ((t < 1) || (t > 3600)) /* arbitrary upper limit */ | ||
165 | return -EINVAL; | ||
166 | |||
167 | heartbeat = t; | ||
168 | return 0; | ||
169 | } | ||
170 | |||
171 | /** | ||
172 | * sh_wdt_ping - Ping the Watchdog | ||
173 | * | ||
174 | * @data: Unused | ||
175 | * | ||
176 | * Clears overflow bit, resets timer counter. | ||
177 | */ | ||
178 | static void sh_wdt_ping(unsigned long data) | ||
179 | { | ||
180 | if (time_before(jiffies, next_heartbeat)) { | ||
181 | __u8 csr; | ||
182 | |||
183 | csr = sh_wdt_read_csr(); | ||
184 | csr &= ~WTCSR_IOVF; | ||
185 | sh_wdt_write_csr(csr); | ||
186 | |||
187 | sh_wdt_write_cnt(0); | ||
188 | |||
189 | mod_timer(&timer, next_ping_period(clock_division_ratio)); | ||
190 | } else { | ||
191 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
192 | } | ||
193 | } | ||
194 | |||
195 | /** | ||
196 | * sh_wdt_open - Open the Device | ||
197 | * | ||
198 | * @inode: inode of device | ||
199 | * @file: file handle of device | ||
200 | * | ||
201 | * Watchdog device is opened and started. | ||
202 | */ | ||
203 | static int sh_wdt_open(struct inode *inode, struct file *file) | ||
204 | { | ||
205 | if (test_and_set_bit(0, &shwdt_is_open)) | ||
206 | return -EBUSY; | ||
207 | if (nowayout) | ||
208 | __module_get(THIS_MODULE); | ||
209 | |||
210 | sh_wdt_start(); | ||
211 | |||
212 | return nonseekable_open(inode, file); | ||
213 | } | ||
214 | |||
215 | /** | ||
216 | * sh_wdt_close - Close the Device | ||
217 | * | ||
218 | * @inode: inode of device | ||
219 | * @file: file handle of device | ||
220 | * | ||
221 | * Watchdog device is closed and stopped. | ||
222 | */ | ||
223 | static int sh_wdt_close(struct inode *inode, struct file *file) | ||
224 | { | ||
225 | if (shwdt_expect_close == 42) { | ||
226 | sh_wdt_stop(); | ||
227 | } else { | ||
228 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
229 | sh_wdt_keepalive(); | ||
230 | } | ||
231 | |||
232 | clear_bit(0, &shwdt_is_open); | ||
233 | shwdt_expect_close = 0; | ||
234 | |||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | /** | ||
239 | * sh_wdt_write - Write to Device | ||
240 | * | ||
241 | * @file: file handle of device | ||
242 | * @buf: buffer to write | ||
243 | * @count: length of buffer | ||
244 | * @ppos: offset | ||
245 | * | ||
246 | * Pings the watchdog on write. | ||
247 | */ | ||
248 | static ssize_t sh_wdt_write(struct file *file, const char *buf, | ||
249 | size_t count, loff_t *ppos) | ||
250 | { | ||
251 | if (count) { | ||
252 | if (!nowayout) { | ||
253 | size_t i; | ||
254 | |||
255 | shwdt_expect_close = 0; | ||
256 | |||
257 | for (i = 0; i != count; i++) { | ||
258 | char c; | ||
259 | if (get_user(c, buf + i)) | ||
260 | return -EFAULT; | ||
261 | if (c == 'V') | ||
262 | shwdt_expect_close = 42; | ||
263 | } | ||
264 | } | ||
265 | sh_wdt_keepalive(); | ||
266 | } | ||
267 | |||
268 | return count; | ||
269 | } | ||
270 | |||
271 | /** | ||
272 | * sh_wdt_ioctl - Query Device | ||
273 | * | ||
274 | * @inode: inode of device | ||
275 | * @file: file handle of device | ||
276 | * @cmd: watchdog command | ||
277 | * @arg: argument | ||
278 | * | ||
279 | * Query basic information from the device or ping it, as outlined by the | ||
280 | * watchdog API. | ||
281 | */ | ||
282 | static int sh_wdt_ioctl(struct inode *inode, struct file *file, | ||
283 | unsigned int cmd, unsigned long arg) | ||
284 | { | ||
285 | int new_heartbeat; | ||
286 | int options, retval = -EINVAL; | ||
287 | |||
288 | switch (cmd) { | ||
289 | case WDIOC_GETSUPPORT: | ||
290 | return copy_to_user((struct watchdog_info *)arg, | ||
291 | &sh_wdt_info, | ||
292 | sizeof(sh_wdt_info)) ? -EFAULT : 0; | ||
293 | case WDIOC_GETSTATUS: | ||
294 | case WDIOC_GETBOOTSTATUS: | ||
295 | return put_user(0, (int *)arg); | ||
296 | case WDIOC_KEEPALIVE: | ||
297 | sh_wdt_keepalive(); | ||
298 | return 0; | ||
299 | case WDIOC_SETTIMEOUT: | ||
300 | if (get_user(new_heartbeat, (int *)arg)) | ||
301 | return -EFAULT; | ||
302 | |||
303 | if (sh_wdt_set_heartbeat(new_heartbeat)) | ||
304 | return -EINVAL; | ||
305 | |||
306 | sh_wdt_keepalive(); | ||
307 | /* Fall */ | ||
308 | case WDIOC_GETTIMEOUT: | ||
309 | return put_user(heartbeat, (int *)arg); | ||
310 | case WDIOC_SETOPTIONS: | ||
311 | if (get_user(options, (int *)arg)) | ||
312 | return -EFAULT; | ||
313 | |||
314 | if (options & WDIOS_DISABLECARD) { | ||
315 | sh_wdt_stop(); | ||
316 | retval = 0; | ||
317 | } | ||
318 | |||
319 | if (options & WDIOS_ENABLECARD) { | ||
320 | sh_wdt_start(); | ||
321 | retval = 0; | ||
322 | } | ||
323 | |||
324 | return retval; | ||
325 | default: | ||
326 | return -ENOIOCTLCMD; | ||
327 | } | ||
328 | |||
329 | return 0; | ||
330 | } | ||
331 | |||
332 | /** | ||
333 | * sh_wdt_notify_sys - Notifier Handler | ||
334 | * | ||
335 | * @this: notifier block | ||
336 | * @code: notifier event | ||
337 | * @unused: unused | ||
338 | * | ||
339 | * Handles specific events, such as turning off the watchdog during a | ||
340 | * shutdown event. | ||
341 | */ | ||
342 | static int sh_wdt_notify_sys(struct notifier_block *this, | ||
343 | unsigned long code, void *unused) | ||
344 | { | ||
345 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
346 | sh_wdt_stop(); | ||
347 | } | ||
348 | |||
349 | return NOTIFY_DONE; | ||
350 | } | ||
351 | |||
352 | static struct file_operations sh_wdt_fops = { | ||
353 | .owner = THIS_MODULE, | ||
354 | .llseek = no_llseek, | ||
355 | .write = sh_wdt_write, | ||
356 | .ioctl = sh_wdt_ioctl, | ||
357 | .open = sh_wdt_open, | ||
358 | .release = sh_wdt_close, | ||
359 | }; | ||
360 | |||
361 | static struct watchdog_info sh_wdt_info = { | ||
362 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
363 | .firmware_version = 1, | ||
364 | .identity = "SH WDT", | ||
365 | }; | ||
366 | |||
367 | static struct notifier_block sh_wdt_notifier = { | ||
368 | .notifier_call = sh_wdt_notify_sys, | ||
369 | }; | ||
370 | |||
371 | static struct miscdevice sh_wdt_miscdev = { | ||
372 | .minor = WATCHDOG_MINOR, | ||
373 | .name = "watchdog", | ||
374 | .fops = &sh_wdt_fops, | ||
375 | }; | ||
376 | |||
377 | /** | ||
378 | * sh_wdt_init - Initialize module | ||
379 | * | ||
380 | * Registers the device and notifier handler. Actual device | ||
381 | * initialization is handled by sh_wdt_open(). | ||
382 | */ | ||
383 | static int __init sh_wdt_init(void) | ||
384 | { | ||
385 | int rc; | ||
386 | |||
387 | if ((clock_division_ratio < 0x5) || (clock_division_ratio > 0x7)) { | ||
388 | clock_division_ratio = WTCSR_CKS_4096; | ||
389 | printk(KERN_INFO PFX "clock_division_ratio value must be 0x5<=x<=0x7, using %d\n", | ||
390 | clock_division_ratio); | ||
391 | } | ||
392 | |||
393 | if (sh_wdt_set_heartbeat(heartbeat)) | ||
394 | { | ||
395 | heartbeat = WATCHDOG_HEARTBEAT; | ||
396 | printk(KERN_INFO PFX "heartbeat value must be 1<=x<=3600, using %d\n", | ||
397 | heartbeat); | ||
398 | } | ||
399 | |||
400 | init_timer(&timer); | ||
401 | timer.function = sh_wdt_ping; | ||
402 | timer.data = 0; | ||
403 | |||
404 | rc = register_reboot_notifier(&sh_wdt_notifier); | ||
405 | if (rc) { | ||
406 | printk(KERN_ERR PFX "Can't register reboot notifier (err=%d)\n", rc); | ||
407 | return rc; | ||
408 | } | ||
409 | |||
410 | rc = misc_register(&sh_wdt_miscdev); | ||
411 | if (rc) { | ||
412 | printk(KERN_ERR PFX "Can't register miscdev on minor=%d (err=%d)\n", | ||
413 | sh_wdt_miscdev.minor, rc); | ||
414 | unregister_reboot_notifier(&sh_wdt_notifier); | ||
415 | return rc; | ||
416 | } | ||
417 | |||
418 | printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
419 | heartbeat, nowayout); | ||
420 | |||
421 | return 0; | ||
422 | } | ||
423 | |||
424 | /** | ||
425 | * sh_wdt_exit - Deinitialize module | ||
426 | * | ||
427 | * Unregisters the device and notifier handler. Actual device | ||
428 | * deinitialization is handled by sh_wdt_close(). | ||
429 | */ | ||
430 | static void __exit sh_wdt_exit(void) | ||
431 | { | ||
432 | misc_deregister(&sh_wdt_miscdev); | ||
433 | unregister_reboot_notifier(&sh_wdt_notifier); | ||
434 | } | ||
435 | |||
436 | MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); | ||
437 | MODULE_DESCRIPTION("SuperH watchdog driver"); | ||
438 | MODULE_LICENSE("GPL"); | ||
439 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
440 | |||
441 | module_param(clock_division_ratio, int, 0); | ||
442 | MODULE_PARM_DESC(clock_division_ratio, "Clock division ratio. Valid ranges are from 0x5 (1.31ms) to 0x7 (5.25ms). Defaults to 0x7."); | ||
443 | |||
444 | module_param(heartbeat, int, 0); | ||
445 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1<=heartbeat<=3600, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
446 | |||
447 | module_param(nowayout, int, 0); | ||
448 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
449 | |||
450 | module_init(sh_wdt_init); | ||
451 | module_exit(sh_wdt_exit); | ||
452 | |||
diff --git a/drivers/char/watchdog/softdog.c b/drivers/char/watchdog/softdog.c new file mode 100644 index 000000000000..117903498a01 --- /dev/null +++ b/drivers/char/watchdog/softdog.c | |||
@@ -0,0 +1,309 @@ | |||
1 | /* | ||
2 | * SoftDog 0.07: A Software Watchdog Device | ||
3 | * | ||
4 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
5 | * http://www.redhat.com | ||
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 Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
13 | * warranty for any of this software. This material is provided | ||
14 | * "AS-IS" and at no charge. | ||
15 | * | ||
16 | * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> | ||
17 | * | ||
18 | * Software only watchdog driver. Unlike its big brother the WDT501P | ||
19 | * driver this won't always recover a failed machine. | ||
20 | * | ||
21 | * 03/96: Angelo Haritsis <ah@doc.ic.ac.uk> : | ||
22 | * Modularised. | ||
23 | * Added soft_margin; use upon insmod to change the timer delay. | ||
24 | * NB: uses same minor as wdt (WATCHDOG_MINOR); we could use separate | ||
25 | * minors. | ||
26 | * | ||
27 | * 19980911 Alan Cox | ||
28 | * Made SMP safe for 2.3.x | ||
29 | * | ||
30 | * 20011127 Joel Becker (jlbec@evilplan.org> | ||
31 | * Added soft_noboot; Allows testing the softdog trigger without | ||
32 | * requiring a recompile. | ||
33 | * Added WDIOC_GETTIMEOUT and WDIOC_SETTIMOUT. | ||
34 | * | ||
35 | * 20020530 Joel Becker <joel.becker@oracle.com> | ||
36 | * Added Matt Domsch's nowayout module option. | ||
37 | */ | ||
38 | |||
39 | #include <linux/module.h> | ||
40 | #include <linux/moduleparam.h> | ||
41 | #include <linux/config.h> | ||
42 | #include <linux/types.h> | ||
43 | #include <linux/timer.h> | ||
44 | #include <linux/miscdevice.h> | ||
45 | #include <linux/watchdog.h> | ||
46 | #include <linux/fs.h> | ||
47 | #include <linux/notifier.h> | ||
48 | #include <linux/reboot.h> | ||
49 | #include <linux/init.h> | ||
50 | #include <asm/uaccess.h> | ||
51 | |||
52 | #define PFX "SoftDog: " | ||
53 | |||
54 | #define TIMER_MARGIN 60 /* Default is 60 seconds */ | ||
55 | static int soft_margin = TIMER_MARGIN; /* in seconds */ | ||
56 | module_param(soft_margin, int, 0); | ||
57 | MODULE_PARM_DESC(soft_margin, "Watchdog soft_margin in seconds. (0<soft_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")"); | ||
58 | |||
59 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
60 | static int nowayout = 1; | ||
61 | #else | ||
62 | static int nowayout = 0; | ||
63 | #endif | ||
64 | |||
65 | module_param(nowayout, int, 0); | ||
66 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
67 | |||
68 | #ifdef ONLY_TESTING | ||
69 | static int soft_noboot = 1; | ||
70 | #else | ||
71 | static int soft_noboot = 0; | ||
72 | #endif /* ONLY_TESTING */ | ||
73 | |||
74 | module_param(soft_noboot, int, 0); | ||
75 | MODULE_PARM_DESC(soft_noboot, "Softdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); | ||
76 | |||
77 | /* | ||
78 | * Our timer | ||
79 | */ | ||
80 | |||
81 | static void watchdog_fire(unsigned long); | ||
82 | |||
83 | static struct timer_list watchdog_ticktock = | ||
84 | TIMER_INITIALIZER(watchdog_fire, 0, 0); | ||
85 | static unsigned long timer_alive; | ||
86 | static char expect_close; | ||
87 | |||
88 | |||
89 | /* | ||
90 | * If the timer expires.. | ||
91 | */ | ||
92 | |||
93 | static void watchdog_fire(unsigned long data) | ||
94 | { | ||
95 | if (soft_noboot) | ||
96 | printk(KERN_CRIT PFX "Triggered - Reboot ignored.\n"); | ||
97 | else | ||
98 | { | ||
99 | printk(KERN_CRIT PFX "Initiating system reboot.\n"); | ||
100 | machine_restart(NULL); | ||
101 | printk(KERN_CRIT PFX "Reboot didn't ?????\n"); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | /* | ||
106 | * Softdog operations | ||
107 | */ | ||
108 | |||
109 | static int softdog_keepalive(void) | ||
110 | { | ||
111 | mod_timer(&watchdog_ticktock, jiffies+(soft_margin*HZ)); | ||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | static int softdog_stop(void) | ||
116 | { | ||
117 | del_timer(&watchdog_ticktock); | ||
118 | return 0; | ||
119 | } | ||
120 | |||
121 | static int softdog_set_heartbeat(int t) | ||
122 | { | ||
123 | if ((t < 0x0001) || (t > 0xFFFF)) | ||
124 | return -EINVAL; | ||
125 | |||
126 | soft_margin = t; | ||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | /* | ||
131 | * /dev/watchdog handling | ||
132 | */ | ||
133 | |||
134 | static int softdog_open(struct inode *inode, struct file *file) | ||
135 | { | ||
136 | if(test_and_set_bit(0, &timer_alive)) | ||
137 | return -EBUSY; | ||
138 | if (nowayout) | ||
139 | __module_get(THIS_MODULE); | ||
140 | /* | ||
141 | * Activate timer | ||
142 | */ | ||
143 | softdog_keepalive(); | ||
144 | return nonseekable_open(inode, file); | ||
145 | } | ||
146 | |||
147 | static int softdog_release(struct inode *inode, struct file *file) | ||
148 | { | ||
149 | /* | ||
150 | * Shut off the timer. | ||
151 | * Lock it in if it's a module and we set nowayout | ||
152 | */ | ||
153 | if (expect_close == 42) { | ||
154 | softdog_stop(); | ||
155 | } else { | ||
156 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
157 | softdog_keepalive(); | ||
158 | } | ||
159 | clear_bit(0, &timer_alive); | ||
160 | expect_close = 0; | ||
161 | return 0; | ||
162 | } | ||
163 | |||
164 | static ssize_t softdog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) | ||
165 | { | ||
166 | /* | ||
167 | * Refresh the timer. | ||
168 | */ | ||
169 | if(len) { | ||
170 | if (!nowayout) { | ||
171 | size_t i; | ||
172 | |||
173 | /* In case it was set long ago */ | ||
174 | expect_close = 0; | ||
175 | |||
176 | for (i = 0; i != len; i++) { | ||
177 | char c; | ||
178 | |||
179 | if (get_user(c, data + i)) | ||
180 | return -EFAULT; | ||
181 | if (c == 'V') | ||
182 | expect_close = 42; | ||
183 | } | ||
184 | } | ||
185 | softdog_keepalive(); | ||
186 | } | ||
187 | return len; | ||
188 | } | ||
189 | |||
190 | static int softdog_ioctl(struct inode *inode, struct file *file, | ||
191 | unsigned int cmd, unsigned long arg) | ||
192 | { | ||
193 | void __user *argp = (void __user *)arg; | ||
194 | int __user *p = argp; | ||
195 | int new_margin; | ||
196 | static struct watchdog_info ident = { | ||
197 | .options = WDIOF_SETTIMEOUT | | ||
198 | WDIOF_KEEPALIVEPING | | ||
199 | WDIOF_MAGICCLOSE, | ||
200 | .firmware_version = 0, | ||
201 | .identity = "Software Watchdog", | ||
202 | }; | ||
203 | switch (cmd) { | ||
204 | default: | ||
205 | return -ENOIOCTLCMD; | ||
206 | case WDIOC_GETSUPPORT: | ||
207 | return copy_to_user(argp, &ident, | ||
208 | sizeof(ident)) ? -EFAULT : 0; | ||
209 | case WDIOC_GETSTATUS: | ||
210 | case WDIOC_GETBOOTSTATUS: | ||
211 | return put_user(0, p); | ||
212 | case WDIOC_KEEPALIVE: | ||
213 | softdog_keepalive(); | ||
214 | return 0; | ||
215 | case WDIOC_SETTIMEOUT: | ||
216 | if (get_user(new_margin, p)) | ||
217 | return -EFAULT; | ||
218 | if (softdog_set_heartbeat(new_margin)) | ||
219 | return -EINVAL; | ||
220 | softdog_keepalive(); | ||
221 | /* Fall */ | ||
222 | case WDIOC_GETTIMEOUT: | ||
223 | return put_user(soft_margin, p); | ||
224 | } | ||
225 | } | ||
226 | |||
227 | /* | ||
228 | * Notifier for system down | ||
229 | */ | ||
230 | |||
231 | static int softdog_notify_sys(struct notifier_block *this, unsigned long code, | ||
232 | void *unused) | ||
233 | { | ||
234 | if(code==SYS_DOWN || code==SYS_HALT) { | ||
235 | /* Turn the WDT off */ | ||
236 | softdog_stop(); | ||
237 | } | ||
238 | return NOTIFY_DONE; | ||
239 | } | ||
240 | |||
241 | /* | ||
242 | * Kernel Interfaces | ||
243 | */ | ||
244 | |||
245 | static struct file_operations softdog_fops = { | ||
246 | .owner = THIS_MODULE, | ||
247 | .llseek = no_llseek, | ||
248 | .write = softdog_write, | ||
249 | .ioctl = softdog_ioctl, | ||
250 | .open = softdog_open, | ||
251 | .release = softdog_release, | ||
252 | }; | ||
253 | |||
254 | static struct miscdevice softdog_miscdev = { | ||
255 | .minor = WATCHDOG_MINOR, | ||
256 | .name = "watchdog", | ||
257 | .fops = &softdog_fops, | ||
258 | }; | ||
259 | |||
260 | static struct notifier_block softdog_notifier = { | ||
261 | .notifier_call = softdog_notify_sys, | ||
262 | }; | ||
263 | |||
264 | static char banner[] __initdata = KERN_INFO "Software Watchdog Timer: 0.07 initialized. soft_noboot=%d soft_margin=%d sec (nowayout= %d)\n"; | ||
265 | |||
266 | static int __init watchdog_init(void) | ||
267 | { | ||
268 | int ret; | ||
269 | |||
270 | /* Check that the soft_margin value is within it's range ; if not reset to the default */ | ||
271 | if (softdog_set_heartbeat(soft_margin)) { | ||
272 | softdog_set_heartbeat(TIMER_MARGIN); | ||
273 | printk(KERN_INFO PFX "soft_margin value must be 0<soft_margin<65536, using %d\n", | ||
274 | TIMER_MARGIN); | ||
275 | } | ||
276 | |||
277 | ret = register_reboot_notifier(&softdog_notifier); | ||
278 | if (ret) { | ||
279 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
280 | ret); | ||
281 | return ret; | ||
282 | } | ||
283 | |||
284 | ret = misc_register(&softdog_miscdev); | ||
285 | if (ret) { | ||
286 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
287 | WATCHDOG_MINOR, ret); | ||
288 | unregister_reboot_notifier(&softdog_notifier); | ||
289 | return ret; | ||
290 | } | ||
291 | |||
292 | printk(banner, soft_noboot, soft_margin, nowayout); | ||
293 | |||
294 | return 0; | ||
295 | } | ||
296 | |||
297 | static void __exit watchdog_exit(void) | ||
298 | { | ||
299 | misc_deregister(&softdog_miscdev); | ||
300 | unregister_reboot_notifier(&softdog_notifier); | ||
301 | } | ||
302 | |||
303 | module_init(watchdog_init); | ||
304 | module_exit(watchdog_exit); | ||
305 | |||
306 | MODULE_AUTHOR("Alan Cox"); | ||
307 | MODULE_DESCRIPTION("Software Watchdog Device Driver"); | ||
308 | MODULE_LICENSE("GPL"); | ||
309 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/w83627hf_wdt.c b/drivers/char/watchdog/w83627hf_wdt.c new file mode 100644 index 000000000000..813c97038f84 --- /dev/null +++ b/drivers/char/watchdog/w83627hf_wdt.c | |||
@@ -0,0 +1,362 @@ | |||
1 | /* | ||
2 | * w83627hf WDT driver | ||
3 | * | ||
4 | * (c) Copyright 2003 Pádraig Brady <P@draigBrady.com> | ||
5 | * | ||
6 | * Based on advantechwdt.c which is based on wdt.c. | ||
7 | * Original copyright messages: | ||
8 | * | ||
9 | * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> | ||
10 | * | ||
11 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
12 | * http://www.redhat.com | ||
13 | * | ||
14 | * This program is free software; you can redistribute it and/or | ||
15 | * modify it under the terms of the GNU General Public License | ||
16 | * as published by the Free Software Foundation; either version | ||
17 | * 2 of the License, or (at your option) any later version. | ||
18 | * | ||
19 | * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
20 | * warranty for any of this software. This material is provided | ||
21 | * "AS-IS" and at no charge. | ||
22 | * | ||
23 | * (c) Copyright 1995 Alan Cox <alan@redhat.com> | ||
24 | */ | ||
25 | |||
26 | #include <linux/module.h> | ||
27 | #include <linux/moduleparam.h> | ||
28 | #include <linux/types.h> | ||
29 | #include <linux/miscdevice.h> | ||
30 | #include <linux/watchdog.h> | ||
31 | #include <linux/fs.h> | ||
32 | #include <linux/ioport.h> | ||
33 | #include <linux/notifier.h> | ||
34 | #include <linux/reboot.h> | ||
35 | #include <linux/init.h> | ||
36 | |||
37 | #include <asm/io.h> | ||
38 | #include <asm/uaccess.h> | ||
39 | #include <asm/system.h> | ||
40 | |||
41 | #define WATCHDOG_NAME "w83627hf WDT" | ||
42 | #define PFX WATCHDOG_NAME ": " | ||
43 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ | ||
44 | |||
45 | static unsigned long wdt_is_open; | ||
46 | static char expect_close; | ||
47 | |||
48 | /* You must set this - there is no sane way to probe for this board. */ | ||
49 | static int wdt_io = 0x2E; | ||
50 | module_param(wdt_io, int, 0); | ||
51 | MODULE_PARM_DESC(wdt_io, "w83627hf WDT io port (default 0x2E)"); | ||
52 | |||
53 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ | ||
54 | module_param(timeout, int, 0); | ||
55 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=63, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) "."); | ||
56 | |||
57 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
58 | static int nowayout = 1; | ||
59 | #else | ||
60 | static int nowayout = 0; | ||
61 | #endif | ||
62 | |||
63 | module_param(nowayout, int, 0); | ||
64 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
65 | |||
66 | /* | ||
67 | * Kernel methods. | ||
68 | */ | ||
69 | |||
70 | #define WDT_EFER (wdt_io+0) /* Extended Function Enable Registers */ | ||
71 | #define WDT_EFIR (wdt_io+0) /* Extended Function Index Register (same as EFER) */ | ||
72 | #define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */ | ||
73 | |||
74 | static void | ||
75 | w83627hf_select_wd_register(void) | ||
76 | { | ||
77 | outb_p(0x87, WDT_EFER); /* Enter extended function mode */ | ||
78 | outb_p(0x87, WDT_EFER); /* Again according to manual */ | ||
79 | |||
80 | outb_p(0x07, WDT_EFER); /* point to logical device number reg */ | ||
81 | outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */ | ||
82 | outb_p(0x30, WDT_EFER); /* select CR30 */ | ||
83 | outb_p(0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */ | ||
84 | } | ||
85 | |||
86 | static void | ||
87 | w83627hf_unselect_wd_register(void) | ||
88 | { | ||
89 | outb_p(0xAA, WDT_EFER); /* Leave extended function mode */ | ||
90 | } | ||
91 | |||
92 | /* tyan motherboards seem to set F5 to 0x4C ? | ||
93 | * So explicitly init to appropriate value. */ | ||
94 | static void | ||
95 | w83627hf_init(void) | ||
96 | { | ||
97 | unsigned char t; | ||
98 | |||
99 | w83627hf_select_wd_register(); | ||
100 | |||
101 | outb_p(0xF5, WDT_EFER); /* Select CRF5 */ | ||
102 | t=inb_p(WDT_EFDR); /* read CRF5 */ | ||
103 | t&=~0x0C; /* set second mode & disable keyboard turning off watchdog */ | ||
104 | outb_p(t, WDT_EFDR); /* Write back to CRF5 */ | ||
105 | |||
106 | w83627hf_unselect_wd_register(); | ||
107 | } | ||
108 | |||
109 | static void | ||
110 | wdt_ctrl(int timeout) | ||
111 | { | ||
112 | w83627hf_select_wd_register(); | ||
113 | |||
114 | outb_p(0xF6, WDT_EFER); /* Select CRF6 */ | ||
115 | outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF6 */ | ||
116 | |||
117 | w83627hf_unselect_wd_register(); | ||
118 | } | ||
119 | |||
120 | static int | ||
121 | wdt_ping(void) | ||
122 | { | ||
123 | wdt_ctrl(timeout); | ||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | static int | ||
128 | wdt_disable(void) | ||
129 | { | ||
130 | wdt_ctrl(0); | ||
131 | return 0; | ||
132 | } | ||
133 | |||
134 | static int | ||
135 | wdt_set_heartbeat(int t) | ||
136 | { | ||
137 | if ((t < 1) || (t > 63)) | ||
138 | return -EINVAL; | ||
139 | |||
140 | timeout = t; | ||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | static ssize_t | ||
145 | wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
146 | { | ||
147 | if (count) { | ||
148 | if (!nowayout) { | ||
149 | size_t i; | ||
150 | |||
151 | expect_close = 0; | ||
152 | |||
153 | for (i = 0; i != count; i++) { | ||
154 | char c; | ||
155 | if (get_user(c, buf+i)) | ||
156 | return -EFAULT; | ||
157 | if (c == 'V') | ||
158 | expect_close = 42; | ||
159 | } | ||
160 | } | ||
161 | wdt_ping(); | ||
162 | } | ||
163 | return count; | ||
164 | } | ||
165 | |||
166 | static int | ||
167 | wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
168 | unsigned long arg) | ||
169 | { | ||
170 | void __user *argp = (void __user *)arg; | ||
171 | int __user *p = argp; | ||
172 | int new_timeout; | ||
173 | static struct watchdog_info ident = { | ||
174 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
175 | .firmware_version = 1, | ||
176 | .identity = "W83627HF WDT", | ||
177 | }; | ||
178 | |||
179 | switch (cmd) { | ||
180 | case WDIOC_GETSUPPORT: | ||
181 | if (copy_to_user(argp, &ident, sizeof(ident))) | ||
182 | return -EFAULT; | ||
183 | break; | ||
184 | |||
185 | case WDIOC_GETSTATUS: | ||
186 | case WDIOC_GETBOOTSTATUS: | ||
187 | return put_user(0, p); | ||
188 | |||
189 | case WDIOC_KEEPALIVE: | ||
190 | wdt_ping(); | ||
191 | break; | ||
192 | |||
193 | case WDIOC_SETTIMEOUT: | ||
194 | if (get_user(new_timeout, p)) | ||
195 | return -EFAULT; | ||
196 | if (wdt_set_heartbeat(new_timeout)) | ||
197 | return -EINVAL; | ||
198 | wdt_ping(); | ||
199 | /* Fall */ | ||
200 | |||
201 | case WDIOC_GETTIMEOUT: | ||
202 | return put_user(timeout, p); | ||
203 | |||
204 | case WDIOC_SETOPTIONS: | ||
205 | { | ||
206 | int options, retval = -EINVAL; | ||
207 | |||
208 | if (get_user(options, p)) | ||
209 | return -EFAULT; | ||
210 | |||
211 | if (options & WDIOS_DISABLECARD) { | ||
212 | wdt_disable(); | ||
213 | retval = 0; | ||
214 | } | ||
215 | |||
216 | if (options & WDIOS_ENABLECARD) { | ||
217 | wdt_ping(); | ||
218 | retval = 0; | ||
219 | } | ||
220 | |||
221 | return retval; | ||
222 | } | ||
223 | |||
224 | default: | ||
225 | return -ENOIOCTLCMD; | ||
226 | } | ||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | static int | ||
231 | wdt_open(struct inode *inode, struct file *file) | ||
232 | { | ||
233 | if (test_and_set_bit(0, &wdt_is_open)) | ||
234 | return -EBUSY; | ||
235 | /* | ||
236 | * Activate | ||
237 | */ | ||
238 | |||
239 | wdt_ping(); | ||
240 | return nonseekable_open(inode, file); | ||
241 | } | ||
242 | |||
243 | static int | ||
244 | wdt_close(struct inode *inode, struct file *file) | ||
245 | { | ||
246 | if (expect_close == 42) { | ||
247 | wdt_disable(); | ||
248 | } else { | ||
249 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
250 | wdt_ping(); | ||
251 | } | ||
252 | expect_close = 0; | ||
253 | clear_bit(0, &wdt_is_open); | ||
254 | return 0; | ||
255 | } | ||
256 | |||
257 | /* | ||
258 | * Notifier for system down | ||
259 | */ | ||
260 | |||
261 | static int | ||
262 | wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
263 | void *unused) | ||
264 | { | ||
265 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
266 | /* Turn the WDT off */ | ||
267 | wdt_disable(); | ||
268 | } | ||
269 | return NOTIFY_DONE; | ||
270 | } | ||
271 | |||
272 | /* | ||
273 | * Kernel Interfaces | ||
274 | */ | ||
275 | |||
276 | static struct file_operations wdt_fops = { | ||
277 | .owner = THIS_MODULE, | ||
278 | .llseek = no_llseek, | ||
279 | .write = wdt_write, | ||
280 | .ioctl = wdt_ioctl, | ||
281 | .open = wdt_open, | ||
282 | .release = wdt_close, | ||
283 | }; | ||
284 | |||
285 | static struct miscdevice wdt_miscdev = { | ||
286 | .minor = WATCHDOG_MINOR, | ||
287 | .name = "watchdog", | ||
288 | .fops = &wdt_fops, | ||
289 | }; | ||
290 | |||
291 | /* | ||
292 | * The WDT needs to learn about soft shutdowns in order to | ||
293 | * turn the timebomb registers off. | ||
294 | */ | ||
295 | |||
296 | static struct notifier_block wdt_notifier = { | ||
297 | .notifier_call = wdt_notify_sys, | ||
298 | }; | ||
299 | |||
300 | static int __init | ||
301 | wdt_init(void) | ||
302 | { | ||
303 | int ret; | ||
304 | |||
305 | printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF Super I/O chip initialising.\n"); | ||
306 | |||
307 | if (wdt_set_heartbeat(timeout)) { | ||
308 | wdt_set_heartbeat(WATCHDOG_TIMEOUT); | ||
309 | printk (KERN_INFO PFX "timeout value must be 1<=timeout<=63, using %d\n", | ||
310 | WATCHDOG_TIMEOUT); | ||
311 | } | ||
312 | |||
313 | if (!request_region(wdt_io, 1, WATCHDOG_NAME)) { | ||
314 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
315 | wdt_io); | ||
316 | ret = -EIO; | ||
317 | goto out; | ||
318 | } | ||
319 | |||
320 | w83627hf_init(); | ||
321 | |||
322 | ret = register_reboot_notifier(&wdt_notifier); | ||
323 | if (ret != 0) { | ||
324 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
325 | ret); | ||
326 | goto unreg_regions; | ||
327 | } | ||
328 | |||
329 | ret = misc_register(&wdt_miscdev); | ||
330 | if (ret != 0) { | ||
331 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
332 | WATCHDOG_MINOR, ret); | ||
333 | goto unreg_reboot; | ||
334 | } | ||
335 | |||
336 | printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", | ||
337 | timeout, nowayout); | ||
338 | |||
339 | out: | ||
340 | return ret; | ||
341 | unreg_reboot: | ||
342 | unregister_reboot_notifier(&wdt_notifier); | ||
343 | unreg_regions: | ||
344 | release_region(wdt_io, 1); | ||
345 | goto out; | ||
346 | } | ||
347 | |||
348 | static void __exit | ||
349 | wdt_exit(void) | ||
350 | { | ||
351 | misc_deregister(&wdt_miscdev); | ||
352 | unregister_reboot_notifier(&wdt_notifier); | ||
353 | release_region(wdt_io,1); | ||
354 | } | ||
355 | |||
356 | module_init(wdt_init); | ||
357 | module_exit(wdt_exit); | ||
358 | |||
359 | MODULE_LICENSE("GPL"); | ||
360 | MODULE_AUTHOR("Pádraig Brady <P@draigBrady.com>"); | ||
361 | MODULE_DESCRIPTION("w38627hf WDT driver"); | ||
362 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/w83877f_wdt.c b/drivers/char/watchdog/w83877f_wdt.c new file mode 100644 index 000000000000..bccbd4d6ac2d --- /dev/null +++ b/drivers/char/watchdog/w83877f_wdt.c | |||
@@ -0,0 +1,426 @@ | |||
1 | /* | ||
2 | * W83877F Computer Watchdog Timer driver | ||
3 | * | ||
4 | * Based on acquirewdt.c by Alan Cox, | ||
5 | * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net> | ||
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 | * The authors do NOT admit liability nor provide warranty for | ||
13 | * any of this software. This material is provided "AS-IS" in | ||
14 | * the hope that it may be useful for others. | ||
15 | * | ||
16 | * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> | ||
17 | * | ||
18 | * 4/19 - 2001 [Initial revision] | ||
19 | * 9/27 - 2001 Added spinlocking | ||
20 | * 4/12 - 2002 [rob@osinvestor.com] Eliminate extra comments | ||
21 | * Eliminate fop_read | ||
22 | * Eliminate extra spin_unlock | ||
23 | * Added KERN_* tags to printks | ||
24 | * add CONFIG_WATCHDOG_NOWAYOUT support | ||
25 | * fix possible wdt_is_open race | ||
26 | * changed watchdog_info to correctly reflect what the driver offers | ||
27 | * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, WDIOC_SETTIMEOUT, | ||
28 | * WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls | ||
29 | * 09/8 - 2003 [wim@iguana.be] cleanup of trailing spaces | ||
30 | * added extra printk's for startup problems | ||
31 | * use module_param | ||
32 | * made timeout (the emulated heartbeat) a module_param | ||
33 | * made the keepalive ping an internal subroutine | ||
34 | * | ||
35 | * This WDT driver is different from most other Linux WDT | ||
36 | * drivers in that the driver will ping the watchdog by itself, | ||
37 | * because this particular WDT has a very short timeout (1.6 | ||
38 | * seconds) and it would be insane to count on any userspace | ||
39 | * daemon always getting scheduled within that time frame. | ||
40 | */ | ||
41 | |||
42 | #include <linux/module.h> | ||
43 | #include <linux/moduleparam.h> | ||
44 | #include <linux/types.h> | ||
45 | #include <linux/timer.h> | ||
46 | #include <linux/jiffies.h> | ||
47 | #include <linux/miscdevice.h> | ||
48 | #include <linux/watchdog.h> | ||
49 | #include <linux/fs.h> | ||
50 | #include <linux/ioport.h> | ||
51 | #include <linux/notifier.h> | ||
52 | #include <linux/reboot.h> | ||
53 | #include <linux/init.h> | ||
54 | #include <asm/io.h> | ||
55 | #include <asm/uaccess.h> | ||
56 | #include <asm/system.h> | ||
57 | |||
58 | #define OUR_NAME "w83877f_wdt" | ||
59 | #define PFX OUR_NAME ": " | ||
60 | |||
61 | #define ENABLE_W83877F_PORT 0x3F0 | ||
62 | #define ENABLE_W83877F 0x87 | ||
63 | #define DISABLE_W83877F 0xAA | ||
64 | #define WDT_PING 0x443 | ||
65 | #define WDT_REGISTER 0x14 | ||
66 | #define WDT_ENABLE 0x9C | ||
67 | #define WDT_DISABLE 0x8C | ||
68 | |||
69 | /* | ||
70 | * The W83877F seems to be fixed at 1.6s timeout (at least on the | ||
71 | * EMACS PC-104 board I'm using). If we reset the watchdog every | ||
72 | * ~250ms we should be safe. */ | ||
73 | |||
74 | #define WDT_INTERVAL (HZ/4+1) | ||
75 | |||
76 | /* | ||
77 | * We must not require too good response from the userspace daemon. | ||
78 | * Here we require the userspace daemon to send us a heartbeat | ||
79 | * char to /dev/watchdog every 30 seconds. | ||
80 | */ | ||
81 | |||
82 | #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ | ||
83 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ | ||
84 | module_param(timeout, int, 0); | ||
85 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
86 | |||
87 | |||
88 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
89 | static int nowayout = 1; | ||
90 | #else | ||
91 | static int nowayout = 0; | ||
92 | #endif | ||
93 | |||
94 | module_param(nowayout, int, 0); | ||
95 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
96 | |||
97 | static void wdt_timer_ping(unsigned long); | ||
98 | static struct timer_list timer; | ||
99 | static unsigned long next_heartbeat; | ||
100 | static unsigned long wdt_is_open; | ||
101 | static char wdt_expect_close; | ||
102 | static spinlock_t wdt_spinlock; | ||
103 | |||
104 | /* | ||
105 | * Whack the dog | ||
106 | */ | ||
107 | |||
108 | static void wdt_timer_ping(unsigned long data) | ||
109 | { | ||
110 | /* If we got a heartbeat pulse within the WDT_US_INTERVAL | ||
111 | * we agree to ping the WDT | ||
112 | */ | ||
113 | if(time_before(jiffies, next_heartbeat)) | ||
114 | { | ||
115 | /* Ping the WDT */ | ||
116 | spin_lock(&wdt_spinlock); | ||
117 | |||
118 | /* Ping the WDT by reading from WDT_PING */ | ||
119 | inb_p(WDT_PING); | ||
120 | |||
121 | /* Re-set the timer interval */ | ||
122 | timer.expires = jiffies + WDT_INTERVAL; | ||
123 | add_timer(&timer); | ||
124 | |||
125 | spin_unlock(&wdt_spinlock); | ||
126 | |||
127 | } else { | ||
128 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
129 | } | ||
130 | } | ||
131 | |||
132 | /* | ||
133 | * Utility routines | ||
134 | */ | ||
135 | |||
136 | static void wdt_change(int writeval) | ||
137 | { | ||
138 | unsigned long flags; | ||
139 | spin_lock_irqsave(&wdt_spinlock, flags); | ||
140 | |||
141 | /* buy some time */ | ||
142 | inb_p(WDT_PING); | ||
143 | |||
144 | /* make W83877F available */ | ||
145 | outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT); | ||
146 | outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT); | ||
147 | |||
148 | /* enable watchdog */ | ||
149 | outb_p(WDT_REGISTER, ENABLE_W83877F_PORT); | ||
150 | outb_p(writeval, ENABLE_W83877F_PORT+1); | ||
151 | |||
152 | /* lock the W8387FF away */ | ||
153 | outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT); | ||
154 | |||
155 | spin_unlock_irqrestore(&wdt_spinlock, flags); | ||
156 | } | ||
157 | |||
158 | static void wdt_startup(void) | ||
159 | { | ||
160 | next_heartbeat = jiffies + (timeout * HZ); | ||
161 | |||
162 | /* Start the timer */ | ||
163 | timer.expires = jiffies + WDT_INTERVAL; | ||
164 | add_timer(&timer); | ||
165 | |||
166 | wdt_change(WDT_ENABLE); | ||
167 | |||
168 | printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); | ||
169 | } | ||
170 | |||
171 | static void wdt_turnoff(void) | ||
172 | { | ||
173 | /* Stop the timer */ | ||
174 | del_timer(&timer); | ||
175 | |||
176 | wdt_change(WDT_DISABLE); | ||
177 | |||
178 | printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); | ||
179 | } | ||
180 | |||
181 | static void wdt_keepalive(void) | ||
182 | { | ||
183 | /* user land ping */ | ||
184 | next_heartbeat = jiffies + (timeout * HZ); | ||
185 | } | ||
186 | |||
187 | /* | ||
188 | * /dev/watchdog handling | ||
189 | */ | ||
190 | |||
191 | static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) | ||
192 | { | ||
193 | /* See if we got the magic character 'V' and reload the timer */ | ||
194 | if(count) | ||
195 | { | ||
196 | if (!nowayout) | ||
197 | { | ||
198 | size_t ofs; | ||
199 | |||
200 | /* note: just in case someone wrote the magic character | ||
201 | * five months ago... */ | ||
202 | wdt_expect_close = 0; | ||
203 | |||
204 | /* scan to see whether or not we got the magic character */ | ||
205 | for(ofs = 0; ofs != count; ofs++) | ||
206 | { | ||
207 | char c; | ||
208 | if (get_user(c, buf + ofs)) | ||
209 | return -EFAULT; | ||
210 | if (c == 'V') | ||
211 | wdt_expect_close = 42; | ||
212 | } | ||
213 | } | ||
214 | |||
215 | /* someone wrote to us, we should restart timer */ | ||
216 | wdt_keepalive(); | ||
217 | } | ||
218 | return count; | ||
219 | } | ||
220 | |||
221 | static int fop_open(struct inode * inode, struct file * file) | ||
222 | { | ||
223 | /* Just in case we're already talking to someone... */ | ||
224 | if(test_and_set_bit(0, &wdt_is_open)) | ||
225 | return -EBUSY; | ||
226 | |||
227 | /* Good, fire up the show */ | ||
228 | wdt_startup(); | ||
229 | return nonseekable_open(inode, file); | ||
230 | } | ||
231 | |||
232 | static int fop_close(struct inode * inode, struct file * file) | ||
233 | { | ||
234 | if(wdt_expect_close == 42) | ||
235 | wdt_turnoff(); | ||
236 | else { | ||
237 | del_timer(&timer); | ||
238 | printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); | ||
239 | } | ||
240 | clear_bit(0, &wdt_is_open); | ||
241 | wdt_expect_close = 0; | ||
242 | return 0; | ||
243 | } | ||
244 | |||
245 | static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
246 | unsigned long arg) | ||
247 | { | ||
248 | void __user *argp = (void __user *)arg; | ||
249 | int __user *p = argp; | ||
250 | static struct watchdog_info ident= | ||
251 | { | ||
252 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
253 | .firmware_version = 1, | ||
254 | .identity = "W83877F", | ||
255 | }; | ||
256 | |||
257 | switch(cmd) | ||
258 | { | ||
259 | default: | ||
260 | return -ENOIOCTLCMD; | ||
261 | case WDIOC_GETSUPPORT: | ||
262 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
263 | case WDIOC_GETSTATUS: | ||
264 | case WDIOC_GETBOOTSTATUS: | ||
265 | return put_user(0, p); | ||
266 | case WDIOC_KEEPALIVE: | ||
267 | wdt_keepalive(); | ||
268 | return 0; | ||
269 | case WDIOC_SETOPTIONS: | ||
270 | { | ||
271 | int new_options, retval = -EINVAL; | ||
272 | |||
273 | if(get_user(new_options, p)) | ||
274 | return -EFAULT; | ||
275 | |||
276 | if(new_options & WDIOS_DISABLECARD) { | ||
277 | wdt_turnoff(); | ||
278 | retval = 0; | ||
279 | } | ||
280 | |||
281 | if(new_options & WDIOS_ENABLECARD) { | ||
282 | wdt_startup(); | ||
283 | retval = 0; | ||
284 | } | ||
285 | |||
286 | return retval; | ||
287 | } | ||
288 | case WDIOC_SETTIMEOUT: | ||
289 | { | ||
290 | int new_timeout; | ||
291 | |||
292 | if(get_user(new_timeout, p)) | ||
293 | return -EFAULT; | ||
294 | |||
295 | if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ | ||
296 | return -EINVAL; | ||
297 | |||
298 | timeout = new_timeout; | ||
299 | wdt_keepalive(); | ||
300 | /* Fall through */ | ||
301 | } | ||
302 | case WDIOC_GETTIMEOUT: | ||
303 | return put_user(timeout, p); | ||
304 | } | ||
305 | } | ||
306 | |||
307 | static struct file_operations wdt_fops = { | ||
308 | .owner = THIS_MODULE, | ||
309 | .llseek = no_llseek, | ||
310 | .write = fop_write, | ||
311 | .open = fop_open, | ||
312 | .release = fop_close, | ||
313 | .ioctl = fop_ioctl, | ||
314 | }; | ||
315 | |||
316 | static struct miscdevice wdt_miscdev = { | ||
317 | .minor = WATCHDOG_MINOR, | ||
318 | .name = "watchdog", | ||
319 | .fops = &wdt_fops, | ||
320 | }; | ||
321 | |||
322 | /* | ||
323 | * Notifier for system down | ||
324 | */ | ||
325 | |||
326 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
327 | void *unused) | ||
328 | { | ||
329 | if(code==SYS_DOWN || code==SYS_HALT) | ||
330 | wdt_turnoff(); | ||
331 | return NOTIFY_DONE; | ||
332 | } | ||
333 | |||
334 | /* | ||
335 | * The WDT needs to learn about soft shutdowns in order to | ||
336 | * turn the timebomb registers off. | ||
337 | */ | ||
338 | |||
339 | static struct notifier_block wdt_notifier= | ||
340 | { | ||
341 | .notifier_call = wdt_notify_sys, | ||
342 | }; | ||
343 | |||
344 | static void __exit w83877f_wdt_unload(void) | ||
345 | { | ||
346 | wdt_turnoff(); | ||
347 | |||
348 | /* Deregister */ | ||
349 | misc_deregister(&wdt_miscdev); | ||
350 | |||
351 | unregister_reboot_notifier(&wdt_notifier); | ||
352 | release_region(WDT_PING,1); | ||
353 | release_region(ENABLE_W83877F_PORT,2); | ||
354 | } | ||
355 | |||
356 | static int __init w83877f_wdt_init(void) | ||
357 | { | ||
358 | int rc = -EBUSY; | ||
359 | |||
360 | spin_lock_init(&wdt_spinlock); | ||
361 | |||
362 | if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */ | ||
363 | { | ||
364 | timeout = WATCHDOG_TIMEOUT; | ||
365 | printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", | ||
366 | timeout); | ||
367 | } | ||
368 | |||
369 | if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) | ||
370 | { | ||
371 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
372 | ENABLE_W83877F_PORT); | ||
373 | rc = -EIO; | ||
374 | goto err_out; | ||
375 | } | ||
376 | |||
377 | if (!request_region(WDT_PING, 1, "W8387FF WDT")) | ||
378 | { | ||
379 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
380 | WDT_PING); | ||
381 | rc = -EIO; | ||
382 | goto err_out_region1; | ||
383 | } | ||
384 | |||
385 | init_timer(&timer); | ||
386 | timer.function = wdt_timer_ping; | ||
387 | timer.data = 0; | ||
388 | |||
389 | rc = misc_register(&wdt_miscdev); | ||
390 | if (rc) | ||
391 | { | ||
392 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
393 | wdt_miscdev.minor, rc); | ||
394 | goto err_out_region2; | ||
395 | } | ||
396 | |||
397 | rc = register_reboot_notifier(&wdt_notifier); | ||
398 | if (rc) | ||
399 | { | ||
400 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
401 | rc); | ||
402 | goto err_out_miscdev; | ||
403 | } | ||
404 | |||
405 | printk(KERN_INFO PFX "WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n", | ||
406 | timeout, nowayout); | ||
407 | |||
408 | return 0; | ||
409 | |||
410 | err_out_miscdev: | ||
411 | misc_deregister(&wdt_miscdev); | ||
412 | err_out_region2: | ||
413 | release_region(WDT_PING,1); | ||
414 | err_out_region1: | ||
415 | release_region(ENABLE_W83877F_PORT,2); | ||
416 | err_out: | ||
417 | return rc; | ||
418 | } | ||
419 | |||
420 | module_init(w83877f_wdt_init); | ||
421 | module_exit(w83877f_wdt_unload); | ||
422 | |||
423 | MODULE_AUTHOR("Scott and Bill Jennings"); | ||
424 | MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip"); | ||
425 | MODULE_LICENSE("GPL"); | ||
426 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/wafer5823wdt.c b/drivers/char/watchdog/wafer5823wdt.c new file mode 100644 index 000000000000..abb0bea45c02 --- /dev/null +++ b/drivers/char/watchdog/wafer5823wdt.c | |||
@@ -0,0 +1,330 @@ | |||
1 | /* | ||
2 | * ICP Wafer 5823 Single Board Computer WDT driver | ||
3 | * http://www.icpamerica.com/wafer_5823.php | ||
4 | * May also work on other similar models | ||
5 | * | ||
6 | * (c) Copyright 2002 Justin Cormack <justin@street-vision.com> | ||
7 | * | ||
8 | * Release 0.02 | ||
9 | * | ||
10 | * Based on advantechwdt.c which is based on wdt.c. | ||
11 | * Original copyright messages: | ||
12 | * | ||
13 | * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
14 | * http://www.redhat.com | ||
15 | * | ||
16 | * This program is free software; you can redistribute it and/or | ||
17 | * modify it under the terms of the GNU General Public License | ||
18 | * as published by the Free Software Foundation; either version | ||
19 | * 2 of the License, or (at your option) any later version. | ||
20 | * | ||
21 | * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
22 | * warranty for any of this software. This material is provided | ||
23 | * "AS-IS" and at no charge. | ||
24 | * | ||
25 | * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> | ||
26 | * | ||
27 | */ | ||
28 | |||
29 | #include <linux/module.h> | ||
30 | #include <linux/moduleparam.h> | ||
31 | #include <linux/miscdevice.h> | ||
32 | #include <linux/watchdog.h> | ||
33 | #include <linux/fs.h> | ||
34 | #include <linux/ioport.h> | ||
35 | #include <linux/notifier.h> | ||
36 | #include <linux/reboot.h> | ||
37 | #include <linux/init.h> | ||
38 | #include <linux/spinlock.h> | ||
39 | #include <asm/io.h> | ||
40 | #include <asm/uaccess.h> | ||
41 | |||
42 | #define WATCHDOG_NAME "Wafer 5823 WDT" | ||
43 | #define PFX WATCHDOG_NAME ": " | ||
44 | #define WD_TIMO 60 /* 60 sec default timeout */ | ||
45 | |||
46 | static unsigned long wafwdt_is_open; | ||
47 | static char expect_close; | ||
48 | static spinlock_t wafwdt_lock; | ||
49 | |||
50 | /* | ||
51 | * You must set these - there is no sane way to probe for this board. | ||
52 | * | ||
53 | * To enable, write the timeout value in seconds (1 to 255) to I/O | ||
54 | * port WDT_START, then read the port to start the watchdog. To pat | ||
55 | * the dog, read port WDT_STOP to stop the timer, then read WDT_START | ||
56 | * to restart it again. | ||
57 | */ | ||
58 | |||
59 | static int wdt_stop = 0x843; | ||
60 | static int wdt_start = 0x443; | ||
61 | |||
62 | static int timeout = WD_TIMO; /* in seconds */ | ||
63 | module_param(timeout, int, 0); | ||
64 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=255, default=" __MODULE_STRING(WD_TIMO) "."); | ||
65 | |||
66 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
67 | static int nowayout = 1; | ||
68 | #else | ||
69 | static int nowayout = 0; | ||
70 | #endif | ||
71 | |||
72 | module_param(nowayout, int, 0); | ||
73 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
74 | |||
75 | static void wafwdt_ping(void) | ||
76 | { | ||
77 | /* pat watchdog */ | ||
78 | spin_lock(&wafwdt_lock); | ||
79 | inb_p(wdt_stop); | ||
80 | inb_p(wdt_start); | ||
81 | spin_unlock(&wafwdt_lock); | ||
82 | } | ||
83 | |||
84 | static void wafwdt_start(void) | ||
85 | { | ||
86 | /* start up watchdog */ | ||
87 | outb_p(timeout, wdt_start); | ||
88 | inb_p(wdt_start); | ||
89 | } | ||
90 | |||
91 | static void | ||
92 | wafwdt_stop(void) | ||
93 | { | ||
94 | /* stop watchdog */ | ||
95 | inb_p(wdt_stop); | ||
96 | } | ||
97 | |||
98 | static ssize_t wafwdt_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) | ||
99 | { | ||
100 | /* See if we got the magic character 'V' and reload the timer */ | ||
101 | if (count) { | ||
102 | if (!nowayout) { | ||
103 | size_t i; | ||
104 | |||
105 | /* In case it was set long ago */ | ||
106 | expect_close = 0; | ||
107 | |||
108 | /* scan to see whether or not we got the magic character */ | ||
109 | for (i = 0; i != count; i++) { | ||
110 | char c; | ||
111 | if (get_user(c, buf + i)) | ||
112 | return -EFAULT; | ||
113 | if (c == 'V') | ||
114 | expect_close = 42; | ||
115 | } | ||
116 | } | ||
117 | /* Well, anyhow someone wrote to us, we should return that favour */ | ||
118 | wafwdt_ping(); | ||
119 | } | ||
120 | return count; | ||
121 | } | ||
122 | |||
123 | static int wafwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
124 | unsigned long arg) | ||
125 | { | ||
126 | int new_timeout; | ||
127 | void __user *argp = (void __user *)arg; | ||
128 | int __user *p = argp; | ||
129 | static struct watchdog_info ident = { | ||
130 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
131 | .firmware_version = 1, | ||
132 | .identity = "Wafer 5823 WDT", | ||
133 | }; | ||
134 | |||
135 | switch (cmd) { | ||
136 | case WDIOC_GETSUPPORT: | ||
137 | if (copy_to_user(argp, &ident, sizeof (ident))) | ||
138 | return -EFAULT; | ||
139 | break; | ||
140 | |||
141 | case WDIOC_GETSTATUS: | ||
142 | case WDIOC_GETBOOTSTATUS: | ||
143 | return put_user(0, p); | ||
144 | |||
145 | case WDIOC_KEEPALIVE: | ||
146 | wafwdt_ping(); | ||
147 | break; | ||
148 | |||
149 | case WDIOC_SETTIMEOUT: | ||
150 | if (get_user(new_timeout, p)) | ||
151 | return -EFAULT; | ||
152 | if ((new_timeout < 1) || (new_timeout > 255)) | ||
153 | return -EINVAL; | ||
154 | timeout = new_timeout; | ||
155 | wafwdt_stop(); | ||
156 | wafwdt_start(); | ||
157 | /* Fall */ | ||
158 | case WDIOC_GETTIMEOUT: | ||
159 | return put_user(timeout, p); | ||
160 | |||
161 | case WDIOC_SETOPTIONS: | ||
162 | { | ||
163 | int options, retval = -EINVAL; | ||
164 | |||
165 | if (get_user(options, p)) | ||
166 | return -EFAULT; | ||
167 | |||
168 | if (options & WDIOS_DISABLECARD) { | ||
169 | wafwdt_start(); | ||
170 | retval = 0; | ||
171 | } | ||
172 | |||
173 | if (options & WDIOS_ENABLECARD) { | ||
174 | wafwdt_stop(); | ||
175 | retval = 0; | ||
176 | } | ||
177 | |||
178 | return retval; | ||
179 | } | ||
180 | |||
181 | default: | ||
182 | return -ENOIOCTLCMD; | ||
183 | } | ||
184 | return 0; | ||
185 | } | ||
186 | |||
187 | static int wafwdt_open(struct inode *inode, struct file *file) | ||
188 | { | ||
189 | if (test_and_set_bit(0, &wafwdt_is_open)) | ||
190 | return -EBUSY; | ||
191 | |||
192 | /* | ||
193 | * Activate | ||
194 | */ | ||
195 | wafwdt_start(); | ||
196 | return nonseekable_open(inode, file); | ||
197 | } | ||
198 | |||
199 | static int | ||
200 | wafwdt_close(struct inode *inode, struct file *file) | ||
201 | { | ||
202 | if (expect_close == 42) { | ||
203 | wafwdt_stop(); | ||
204 | } else { | ||
205 | printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n"); | ||
206 | wafwdt_ping(); | ||
207 | } | ||
208 | clear_bit(0, &wafwdt_is_open); | ||
209 | expect_close = 0; | ||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | /* | ||
214 | * Notifier for system down | ||
215 | */ | ||
216 | |||
217 | static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
218 | { | ||
219 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
220 | /* Turn the WDT off */ | ||
221 | wafwdt_stop(); | ||
222 | } | ||
223 | return NOTIFY_DONE; | ||
224 | } | ||
225 | |||
226 | /* | ||
227 | * Kernel Interfaces | ||
228 | */ | ||
229 | |||
230 | static struct file_operations wafwdt_fops = { | ||
231 | .owner = THIS_MODULE, | ||
232 | .llseek = no_llseek, | ||
233 | .write = wafwdt_write, | ||
234 | .ioctl = wafwdt_ioctl, | ||
235 | .open = wafwdt_open, | ||
236 | .release = wafwdt_close, | ||
237 | }; | ||
238 | |||
239 | static struct miscdevice wafwdt_miscdev = { | ||
240 | .minor = WATCHDOG_MINOR, | ||
241 | .name = "watchdog", | ||
242 | .fops = &wafwdt_fops, | ||
243 | }; | ||
244 | |||
245 | /* | ||
246 | * The WDT needs to learn about soft shutdowns in order to | ||
247 | * turn the timebomb registers off. | ||
248 | */ | ||
249 | |||
250 | static struct notifier_block wafwdt_notifier = { | ||
251 | .notifier_call = wafwdt_notify_sys, | ||
252 | }; | ||
253 | |||
254 | static int __init wafwdt_init(void) | ||
255 | { | ||
256 | int ret; | ||
257 | |||
258 | printk(KERN_INFO "WDT driver for Wafer 5823 single board computer initialising.\n"); | ||
259 | |||
260 | spin_lock_init(&wafwdt_lock); | ||
261 | |||
262 | if (timeout < 1 || timeout > 255) { | ||
263 | timeout = WD_TIMO; | ||
264 | printk (KERN_INFO PFX "timeout value must be 1<=x<=255, using %d\n", | ||
265 | timeout); | ||
266 | } | ||
267 | |||
268 | if (wdt_stop != wdt_start) { | ||
269 | if(!request_region(wdt_stop, 1, "Wafer 5823 WDT")) { | ||
270 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
271 | wdt_stop); | ||
272 | ret = -EIO; | ||
273 | goto error; | ||
274 | } | ||
275 | } | ||
276 | |||
277 | if(!request_region(wdt_start, 1, "Wafer 5823 WDT")) { | ||
278 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
279 | wdt_start); | ||
280 | ret = -EIO; | ||
281 | goto error2; | ||
282 | } | ||
283 | |||
284 | ret = register_reboot_notifier(&wafwdt_notifier); | ||
285 | if (ret != 0) { | ||
286 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
287 | ret); | ||
288 | goto error3; | ||
289 | } | ||
290 | |||
291 | ret = misc_register(&wafwdt_miscdev); | ||
292 | if (ret != 0) { | ||
293 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
294 | WATCHDOG_MINOR, ret); | ||
295 | goto error4; | ||
296 | } | ||
297 | |||
298 | printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", | ||
299 | timeout, nowayout); | ||
300 | |||
301 | return ret; | ||
302 | error4: | ||
303 | unregister_reboot_notifier(&wafwdt_notifier); | ||
304 | error3: | ||
305 | release_region(wdt_start, 1); | ||
306 | error2: | ||
307 | if (wdt_stop != wdt_start) | ||
308 | release_region(wdt_stop, 1); | ||
309 | error: | ||
310 | return ret; | ||
311 | } | ||
312 | |||
313 | static void __exit wafwdt_exit(void) | ||
314 | { | ||
315 | misc_deregister(&wafwdt_miscdev); | ||
316 | unregister_reboot_notifier(&wafwdt_notifier); | ||
317 | if(wdt_stop != wdt_start) | ||
318 | release_region(wdt_stop, 1); | ||
319 | release_region(wdt_start, 1); | ||
320 | } | ||
321 | |||
322 | module_init(wafwdt_init); | ||
323 | module_exit(wafwdt_exit); | ||
324 | |||
325 | MODULE_AUTHOR("Justin Cormack"); | ||
326 | MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver"); | ||
327 | MODULE_LICENSE("GPL"); | ||
328 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
329 | |||
330 | /* end of wafer5823wdt.c */ | ||
diff --git a/drivers/char/watchdog/wd501p.h b/drivers/char/watchdog/wd501p.h new file mode 100644 index 000000000000..84e60eb74337 --- /dev/null +++ b/drivers/char/watchdog/wd501p.h | |||
@@ -0,0 +1,52 @@ | |||
1 | /* | ||
2 | * Industrial Computer Source WDT500/501 driver | ||
3 | * | ||
4 | * (c) Copyright 1995 CymruNET Ltd | ||
5 | * Innovation Centre | ||
6 | * Singleton Park | ||
7 | * Swansea | ||
8 | * Wales | ||
9 | * UK | ||
10 | * SA2 8PP | ||
11 | * | ||
12 | * http://www.cymru.net | ||
13 | * | ||
14 | * This driver is provided under the GNU General Public License, incorporated | ||
15 | * herein by reference. The driver is provided without warranty or | ||
16 | * support. | ||
17 | * | ||
18 | * Release 0.04. | ||
19 | * | ||
20 | */ | ||
21 | |||
22 | #include <linux/config.h> | ||
23 | |||
24 | #define WDT_COUNT0 (io+0) | ||
25 | #define WDT_COUNT1 (io+1) | ||
26 | #define WDT_COUNT2 (io+2) | ||
27 | #define WDT_CR (io+3) | ||
28 | #define WDT_SR (io+4) /* Start buzzer on PCI write */ | ||
29 | #define WDT_RT (io+5) /* Stop buzzer on PCI write */ | ||
30 | #define WDT_BUZZER (io+6) /* PCI only: rd=disable, wr=enable */ | ||
31 | #define WDT_DC (io+7) | ||
32 | |||
33 | /* The following are only on the PCI card, they're outside of I/O space on | ||
34 | * the ISA card: */ | ||
35 | #define WDT_CLOCK (io+12) /* COUNT2: rd=16.67MHz, wr=2.0833MHz */ | ||
36 | /* inverted opto isolated reset output: */ | ||
37 | #define WDT_OPTONOTRST (io+13) /* wr=enable, rd=disable */ | ||
38 | /* opto isolated reset output: */ | ||
39 | #define WDT_OPTORST (io+14) /* wr=enable, rd=disable */ | ||
40 | /* programmable outputs: */ | ||
41 | #define WDT_PROGOUT (io+15) /* wr=enable, rd=disable */ | ||
42 | |||
43 | /* FAN 501 500 */ | ||
44 | #define WDC_SR_WCCR 1 /* Active low */ /* X X X */ | ||
45 | #define WDC_SR_TGOOD 2 /* X X - */ | ||
46 | #define WDC_SR_ISOI0 4 /* X X X */ | ||
47 | #define WDC_SR_ISII1 8 /* X X X */ | ||
48 | #define WDC_SR_FANGOOD 16 /* X - - */ | ||
49 | #define WDC_SR_PSUOVER 32 /* Active low */ /* X X - */ | ||
50 | #define WDC_SR_PSUUNDR 64 /* Active low */ /* X X - */ | ||
51 | #define WDC_SR_IRQ 128 /* Active low */ /* X X X */ | ||
52 | |||
diff --git a/drivers/char/watchdog/wdt.c b/drivers/char/watchdog/wdt.c new file mode 100644 index 000000000000..5684aa379886 --- /dev/null +++ b/drivers/char/watchdog/wdt.c | |||
@@ -0,0 +1,647 @@ | |||
1 | /* | ||
2 | * Industrial Computer Source WDT500/501 driver | ||
3 | * | ||
4 | * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
5 | * http://www.redhat.com | ||
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 Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
13 | * warranty for any of this software. This material is provided | ||
14 | * "AS-IS" and at no charge. | ||
15 | * | ||
16 | * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> | ||
17 | * | ||
18 | * Release 0.10. | ||
19 | * | ||
20 | * Fixes | ||
21 | * Dave Gregorich : Modularisation and minor bugs | ||
22 | * Alan Cox : Added the watchdog ioctl() stuff | ||
23 | * Alan Cox : Fixed the reboot problem (as noted by | ||
24 | * Matt Crocker). | ||
25 | * Alan Cox : Added wdt= boot option | ||
26 | * Alan Cox : Cleaned up copy/user stuff | ||
27 | * Tim Hockin : Added insmod parameters, comment cleanup | ||
28 | * Parameterized timeout | ||
29 | * Tigran Aivazian : Restructured wdt_init() to handle failures | ||
30 | * Joel Becker : Added WDIOC_GET/SETTIMEOUT | ||
31 | * Matt Domsch : Added nowayout module option | ||
32 | */ | ||
33 | |||
34 | #include <linux/config.h> | ||
35 | #include <linux/interrupt.h> | ||
36 | #include <linux/module.h> | ||
37 | #include <linux/moduleparam.h> | ||
38 | #include <linux/types.h> | ||
39 | #include <linux/miscdevice.h> | ||
40 | #include <linux/watchdog.h> | ||
41 | #include <linux/fs.h> | ||
42 | #include <linux/ioport.h> | ||
43 | #include <linux/notifier.h> | ||
44 | #include <linux/reboot.h> | ||
45 | #include <linux/init.h> | ||
46 | |||
47 | #include <asm/io.h> | ||
48 | #include <asm/uaccess.h> | ||
49 | #include <asm/system.h> | ||
50 | #include "wd501p.h" | ||
51 | |||
52 | static unsigned long wdt_is_open; | ||
53 | static char expect_close; | ||
54 | |||
55 | /* | ||
56 | * Module parameters | ||
57 | */ | ||
58 | |||
59 | #define WD_TIMO 60 /* Default heartbeat = 60 seconds */ | ||
60 | |||
61 | static int heartbeat = WD_TIMO; | ||
62 | static int wd_heartbeat; | ||
63 | module_param(heartbeat, int, 0); | ||
64 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")"); | ||
65 | |||
66 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
67 | static int nowayout = 1; | ||
68 | #else | ||
69 | static int nowayout = 0; | ||
70 | #endif | ||
71 | |||
72 | module_param(nowayout, int, 0); | ||
73 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
74 | |||
75 | /* You must set these - there is no sane way to probe for this board. */ | ||
76 | static int io=0x240; | ||
77 | static int irq=11; | ||
78 | |||
79 | module_param(io, int, 0); | ||
80 | MODULE_PARM_DESC(io, "WDT io port (default=0x240)"); | ||
81 | module_param(irq, int, 0); | ||
82 | MODULE_PARM_DESC(irq, "WDT irq (default=11)"); | ||
83 | |||
84 | #ifdef CONFIG_WDT_501 | ||
85 | /* Support for the Fan Tachometer on the WDT501-P */ | ||
86 | static int tachometer; | ||
87 | |||
88 | module_param(tachometer, int, 0); | ||
89 | MODULE_PARM_DESC(tachometer, "WDT501-P Fan Tachometer support (0=disable, default=0)"); | ||
90 | #endif /* CONFIG_WDT_501 */ | ||
91 | |||
92 | /* | ||
93 | * Programming support | ||
94 | */ | ||
95 | |||
96 | static void wdt_ctr_mode(int ctr, int mode) | ||
97 | { | ||
98 | ctr<<=6; | ||
99 | ctr|=0x30; | ||
100 | ctr|=(mode<<1); | ||
101 | outb_p(ctr, WDT_CR); | ||
102 | } | ||
103 | |||
104 | static void wdt_ctr_load(int ctr, int val) | ||
105 | { | ||
106 | outb_p(val&0xFF, WDT_COUNT0+ctr); | ||
107 | outb_p(val>>8, WDT_COUNT0+ctr); | ||
108 | } | ||
109 | |||
110 | /** | ||
111 | * wdt_start: | ||
112 | * | ||
113 | * Start the watchdog driver. | ||
114 | */ | ||
115 | |||
116 | static int wdt_start(void) | ||
117 | { | ||
118 | inb_p(WDT_DC); /* Disable watchdog */ | ||
119 | wdt_ctr_mode(0,3); /* Program CTR0 for Mode 3: Square Wave Generator */ | ||
120 | wdt_ctr_mode(1,2); /* Program CTR1 for Mode 2: Rate Generator */ | ||
121 | wdt_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */ | ||
122 | wdt_ctr_load(0, 8948); /* Count at 100Hz */ | ||
123 | wdt_ctr_load(1,wd_heartbeat); /* Heartbeat */ | ||
124 | wdt_ctr_load(2,65535); /* Length of reset pulse */ | ||
125 | outb_p(0, WDT_DC); /* Enable watchdog */ | ||
126 | return 0; | ||
127 | } | ||
128 | |||
129 | /** | ||
130 | * wdt_stop: | ||
131 | * | ||
132 | * Stop the watchdog driver. | ||
133 | */ | ||
134 | |||
135 | static int wdt_stop (void) | ||
136 | { | ||
137 | /* Turn the card off */ | ||
138 | inb_p(WDT_DC); /* Disable watchdog */ | ||
139 | wdt_ctr_load(2,0); /* 0 length reset pulses now */ | ||
140 | return 0; | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * wdt_ping: | ||
145 | * | ||
146 | * Reload counter one with the watchdog heartbeat. We don't bother reloading | ||
147 | * the cascade counter. | ||
148 | */ | ||
149 | |||
150 | static int wdt_ping(void) | ||
151 | { | ||
152 | /* Write a watchdog value */ | ||
153 | inb_p(WDT_DC); /* Disable watchdog */ | ||
154 | wdt_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */ | ||
155 | wdt_ctr_load(1,wd_heartbeat); /* Heartbeat */ | ||
156 | outb_p(0, WDT_DC); /* Enable watchdog */ | ||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | /** | ||
161 | * wdt_set_heartbeat: | ||
162 | * @t: the new heartbeat value that needs to be set. | ||
163 | * | ||
164 | * Set a new heartbeat value for the watchdog device. If the heartbeat value is | ||
165 | * incorrect we keep the old value and return -EINVAL. If successfull we | ||
166 | * return 0. | ||
167 | */ | ||
168 | static int wdt_set_heartbeat(int t) | ||
169 | { | ||
170 | if ((t < 1) || (t > 65535)) | ||
171 | return -EINVAL; | ||
172 | |||
173 | heartbeat = t; | ||
174 | wd_heartbeat = t * 100; | ||
175 | return 0; | ||
176 | } | ||
177 | |||
178 | /** | ||
179 | * wdt_get_status: | ||
180 | * @status: the new status. | ||
181 | * | ||
182 | * Extract the status information from a WDT watchdog device. There are | ||
183 | * several board variants so we have to know which bits are valid. Some | ||
184 | * bits default to one and some to zero in order to be maximally painful. | ||
185 | * | ||
186 | * we then map the bits onto the status ioctl flags. | ||
187 | */ | ||
188 | |||
189 | static int wdt_get_status(int *status) | ||
190 | { | ||
191 | unsigned char new_status=inb_p(WDT_SR); | ||
192 | |||
193 | *status=0; | ||
194 | if (new_status & WDC_SR_ISOI0) | ||
195 | *status |= WDIOF_EXTERN1; | ||
196 | if (new_status & WDC_SR_ISII1) | ||
197 | *status |= WDIOF_EXTERN2; | ||
198 | #ifdef CONFIG_WDT_501 | ||
199 | if (!(new_status & WDC_SR_TGOOD)) | ||
200 | *status |= WDIOF_OVERHEAT; | ||
201 | if (!(new_status & WDC_SR_PSUOVER)) | ||
202 | *status |= WDIOF_POWEROVER; | ||
203 | if (!(new_status & WDC_SR_PSUUNDR)) | ||
204 | *status |= WDIOF_POWERUNDER; | ||
205 | if (tachometer) { | ||
206 | if (!(new_status & WDC_SR_FANGOOD)) | ||
207 | *status |= WDIOF_FANFAULT; | ||
208 | } | ||
209 | #endif /* CONFIG_WDT_501 */ | ||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | #ifdef CONFIG_WDT_501 | ||
214 | /** | ||
215 | * wdt_get_temperature: | ||
216 | * | ||
217 | * Reports the temperature in degrees Fahrenheit. The API is in | ||
218 | * farenheit. It was designed by an imperial measurement luddite. | ||
219 | */ | ||
220 | |||
221 | static int wdt_get_temperature(int *temperature) | ||
222 | { | ||
223 | unsigned short c=inb_p(WDT_RT); | ||
224 | |||
225 | *temperature = (c * 11 / 15) + 7; | ||
226 | return 0; | ||
227 | } | ||
228 | #endif /* CONFIG_WDT_501 */ | ||
229 | |||
230 | /** | ||
231 | * wdt_interrupt: | ||
232 | * @irq: Interrupt number | ||
233 | * @dev_id: Unused as we don't allow multiple devices. | ||
234 | * @regs: Unused. | ||
235 | * | ||
236 | * Handle an interrupt from the board. These are raised when the status | ||
237 | * map changes in what the board considers an interesting way. That means | ||
238 | * a failure condition occurring. | ||
239 | */ | ||
240 | |||
241 | static irqreturn_t wdt_interrupt(int irq, void *dev_id, struct pt_regs *regs) | ||
242 | { | ||
243 | /* | ||
244 | * Read the status register see what is up and | ||
245 | * then printk it. | ||
246 | */ | ||
247 | unsigned char status=inb_p(WDT_SR); | ||
248 | |||
249 | printk(KERN_CRIT "WDT status %d\n", status); | ||
250 | |||
251 | #ifdef CONFIG_WDT_501 | ||
252 | if (!(status & WDC_SR_TGOOD)) | ||
253 | printk(KERN_CRIT "Overheat alarm.(%d)\n",inb_p(WDT_RT)); | ||
254 | if (!(status & WDC_SR_PSUOVER)) | ||
255 | printk(KERN_CRIT "PSU over voltage.\n"); | ||
256 | if (!(status & WDC_SR_PSUUNDR)) | ||
257 | printk(KERN_CRIT "PSU under voltage.\n"); | ||
258 | if (tachometer) { | ||
259 | if (!(status & WDC_SR_FANGOOD)) | ||
260 | printk(KERN_CRIT "Possible fan fault.\n"); | ||
261 | } | ||
262 | #endif /* CONFIG_WDT_501 */ | ||
263 | if (!(status & WDC_SR_WCCR)) | ||
264 | #ifdef SOFTWARE_REBOOT | ||
265 | #ifdef ONLY_TESTING | ||
266 | printk(KERN_CRIT "Would Reboot.\n"); | ||
267 | #else | ||
268 | printk(KERN_CRIT "Initiating system reboot.\n"); | ||
269 | machine_restart(NULL); | ||
270 | #endif | ||
271 | #else | ||
272 | printk(KERN_CRIT "Reset in 5ms.\n"); | ||
273 | #endif | ||
274 | return IRQ_HANDLED; | ||
275 | } | ||
276 | |||
277 | |||
278 | /** | ||
279 | * wdt_write: | ||
280 | * @file: file handle to the watchdog | ||
281 | * @buf: buffer to write (unused as data does not matter here | ||
282 | * @count: count of bytes | ||
283 | * @ppos: pointer to the position to write. No seeks allowed | ||
284 | * | ||
285 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
286 | * write of data will do, as we we don't define content meaning. | ||
287 | */ | ||
288 | |||
289 | static ssize_t wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
290 | { | ||
291 | if(count) { | ||
292 | if (!nowayout) { | ||
293 | size_t i; | ||
294 | |||
295 | /* In case it was set long ago */ | ||
296 | expect_close = 0; | ||
297 | |||
298 | for (i = 0; i != count; i++) { | ||
299 | char c; | ||
300 | if (get_user(c, buf + i)) | ||
301 | return -EFAULT; | ||
302 | if (c == 'V') | ||
303 | expect_close = 42; | ||
304 | } | ||
305 | } | ||
306 | wdt_ping(); | ||
307 | } | ||
308 | return count; | ||
309 | } | ||
310 | |||
311 | /** | ||
312 | * wdt_ioctl: | ||
313 | * @inode: inode of the device | ||
314 | * @file: file handle to the device | ||
315 | * @cmd: watchdog command | ||
316 | * @arg: argument pointer | ||
317 | * | ||
318 | * The watchdog API defines a common set of functions for all watchdogs | ||
319 | * according to their available features. We only actually usefully support | ||
320 | * querying capabilities and current status. | ||
321 | */ | ||
322 | |||
323 | static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
324 | unsigned long arg) | ||
325 | { | ||
326 | void __user *argp = (void __user *)arg; | ||
327 | int __user *p = argp; | ||
328 | int new_heartbeat; | ||
329 | int status; | ||
330 | |||
331 | static struct watchdog_info ident = { | ||
332 | .options = WDIOF_SETTIMEOUT| | ||
333 | WDIOF_MAGICCLOSE| | ||
334 | WDIOF_KEEPALIVEPING, | ||
335 | .firmware_version = 1, | ||
336 | .identity = "WDT500/501", | ||
337 | }; | ||
338 | |||
339 | /* Add options according to the card we have */ | ||
340 | ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2); | ||
341 | #ifdef CONFIG_WDT_501 | ||
342 | ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER); | ||
343 | if (tachometer) | ||
344 | ident.options |= WDIOF_FANFAULT; | ||
345 | #endif /* CONFIG_WDT_501 */ | ||
346 | |||
347 | switch(cmd) | ||
348 | { | ||
349 | default: | ||
350 | return -ENOIOCTLCMD; | ||
351 | case WDIOC_GETSUPPORT: | ||
352 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
353 | |||
354 | case WDIOC_GETSTATUS: | ||
355 | wdt_get_status(&status); | ||
356 | return put_user(status, p); | ||
357 | case WDIOC_GETBOOTSTATUS: | ||
358 | return put_user(0, p); | ||
359 | case WDIOC_KEEPALIVE: | ||
360 | wdt_ping(); | ||
361 | return 0; | ||
362 | case WDIOC_SETTIMEOUT: | ||
363 | if (get_user(new_heartbeat, p)) | ||
364 | return -EFAULT; | ||
365 | |||
366 | if (wdt_set_heartbeat(new_heartbeat)) | ||
367 | return -EINVAL; | ||
368 | |||
369 | wdt_ping(); | ||
370 | /* Fall */ | ||
371 | case WDIOC_GETTIMEOUT: | ||
372 | return put_user(heartbeat, p); | ||
373 | } | ||
374 | } | ||
375 | |||
376 | /** | ||
377 | * wdt_open: | ||
378 | * @inode: inode of device | ||
379 | * @file: file handle to device | ||
380 | * | ||
381 | * The watchdog device has been opened. The watchdog device is single | ||
382 | * open and on opening we load the counters. Counter zero is a 100Hz | ||
383 | * cascade, into counter 1 which downcounts to reboot. When the counter | ||
384 | * triggers counter 2 downcounts the length of the reset pulse which | ||
385 | * set set to be as long as possible. | ||
386 | */ | ||
387 | |||
388 | static int wdt_open(struct inode *inode, struct file *file) | ||
389 | { | ||
390 | if(test_and_set_bit(0, &wdt_is_open)) | ||
391 | return -EBUSY; | ||
392 | /* | ||
393 | * Activate | ||
394 | */ | ||
395 | wdt_start(); | ||
396 | return nonseekable_open(inode, file); | ||
397 | } | ||
398 | |||
399 | /** | ||
400 | * wdt_release: | ||
401 | * @inode: inode to board | ||
402 | * @file: file handle to board | ||
403 | * | ||
404 | * The watchdog has a configurable API. There is a religious dispute | ||
405 | * between people who want their watchdog to be able to shut down and | ||
406 | * those who want to be sure if the watchdog manager dies the machine | ||
407 | * reboots. In the former case we disable the counters, in the latter | ||
408 | * case you have to open it again very soon. | ||
409 | */ | ||
410 | |||
411 | static int wdt_release(struct inode *inode, struct file *file) | ||
412 | { | ||
413 | if (expect_close == 42) { | ||
414 | wdt_stop(); | ||
415 | clear_bit(0, &wdt_is_open); | ||
416 | } else { | ||
417 | printk(KERN_CRIT "wdt: WDT device closed unexpectedly. WDT will not stop!\n"); | ||
418 | wdt_ping(); | ||
419 | } | ||
420 | expect_close = 0; | ||
421 | return 0; | ||
422 | } | ||
423 | |||
424 | #ifdef CONFIG_WDT_501 | ||
425 | /** | ||
426 | * wdt_temp_read: | ||
427 | * @file: file handle to the watchdog board | ||
428 | * @buf: buffer to write 1 byte into | ||
429 | * @count: length of buffer | ||
430 | * @ptr: offset (no seek allowed) | ||
431 | * | ||
432 | * Temp_read reports the temperature in degrees Fahrenheit. The API is in | ||
433 | * farenheit. It was designed by an imperial measurement luddite. | ||
434 | */ | ||
435 | |||
436 | static ssize_t wdt_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ptr) | ||
437 | { | ||
438 | int temperature; | ||
439 | |||
440 | if (wdt_get_temperature(&temperature)) | ||
441 | return -EFAULT; | ||
442 | |||
443 | if (copy_to_user (buf, &temperature, 1)) | ||
444 | return -EFAULT; | ||
445 | |||
446 | return 1; | ||
447 | } | ||
448 | |||
449 | /** | ||
450 | * wdt_temp_open: | ||
451 | * @inode: inode of device | ||
452 | * @file: file handle to device | ||
453 | * | ||
454 | * The temperature device has been opened. | ||
455 | */ | ||
456 | |||
457 | static int wdt_temp_open(struct inode *inode, struct file *file) | ||
458 | { | ||
459 | return nonseekable_open(inode, file); | ||
460 | } | ||
461 | |||
462 | /** | ||
463 | * wdt_temp_release: | ||
464 | * @inode: inode to board | ||
465 | * @file: file handle to board | ||
466 | * | ||
467 | * The temperature device has been closed. | ||
468 | */ | ||
469 | |||
470 | static int wdt_temp_release(struct inode *inode, struct file *file) | ||
471 | { | ||
472 | return 0; | ||
473 | } | ||
474 | #endif /* CONFIG_WDT_501 */ | ||
475 | |||
476 | /** | ||
477 | * notify_sys: | ||
478 | * @this: our notifier block | ||
479 | * @code: the event being reported | ||
480 | * @unused: unused | ||
481 | * | ||
482 | * Our notifier is called on system shutdowns. We want to turn the card | ||
483 | * off at reboot otherwise the machine will reboot again during memory | ||
484 | * test or worse yet during the following fsck. This would suck, in fact | ||
485 | * trust me - if it happens it does suck. | ||
486 | */ | ||
487 | |||
488 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
489 | void *unused) | ||
490 | { | ||
491 | if(code==SYS_DOWN || code==SYS_HALT) { | ||
492 | /* Turn the card off */ | ||
493 | wdt_stop(); | ||
494 | } | ||
495 | return NOTIFY_DONE; | ||
496 | } | ||
497 | |||
498 | /* | ||
499 | * Kernel Interfaces | ||
500 | */ | ||
501 | |||
502 | |||
503 | static struct file_operations wdt_fops = { | ||
504 | .owner = THIS_MODULE, | ||
505 | .llseek = no_llseek, | ||
506 | .write = wdt_write, | ||
507 | .ioctl = wdt_ioctl, | ||
508 | .open = wdt_open, | ||
509 | .release = wdt_release, | ||
510 | }; | ||
511 | |||
512 | static struct miscdevice wdt_miscdev = { | ||
513 | .minor = WATCHDOG_MINOR, | ||
514 | .name = "watchdog", | ||
515 | .fops = &wdt_fops, | ||
516 | }; | ||
517 | |||
518 | #ifdef CONFIG_WDT_501 | ||
519 | static struct file_operations wdt_temp_fops = { | ||
520 | .owner = THIS_MODULE, | ||
521 | .llseek = no_llseek, | ||
522 | .read = wdt_temp_read, | ||
523 | .open = wdt_temp_open, | ||
524 | .release = wdt_temp_release, | ||
525 | }; | ||
526 | |||
527 | static struct miscdevice temp_miscdev = { | ||
528 | .minor = TEMP_MINOR, | ||
529 | .name = "temperature", | ||
530 | .fops = &wdt_temp_fops, | ||
531 | }; | ||
532 | #endif /* CONFIG_WDT_501 */ | ||
533 | |||
534 | /* | ||
535 | * The WDT card needs to learn about soft shutdowns in order to | ||
536 | * turn the timebomb registers off. | ||
537 | */ | ||
538 | |||
539 | static struct notifier_block wdt_notifier = { | ||
540 | .notifier_call = wdt_notify_sys, | ||
541 | }; | ||
542 | |||
543 | /** | ||
544 | * cleanup_module: | ||
545 | * | ||
546 | * Unload the watchdog. You cannot do this with any file handles open. | ||
547 | * If your watchdog is set to continue ticking on close and you unload | ||
548 | * it, well it keeps ticking. We won't get the interrupt but the board | ||
549 | * will not touch PC memory so all is fine. You just have to load a new | ||
550 | * module in 60 seconds or reboot. | ||
551 | */ | ||
552 | |||
553 | static void __exit wdt_exit(void) | ||
554 | { | ||
555 | misc_deregister(&wdt_miscdev); | ||
556 | #ifdef CONFIG_WDT_501 | ||
557 | misc_deregister(&temp_miscdev); | ||
558 | #endif /* CONFIG_WDT_501 */ | ||
559 | unregister_reboot_notifier(&wdt_notifier); | ||
560 | free_irq(irq, NULL); | ||
561 | release_region(io,8); | ||
562 | } | ||
563 | |||
564 | /** | ||
565 | * wdt_init: | ||
566 | * | ||
567 | * Set up the WDT watchdog board. All we have to do is grab the | ||
568 | * resources we require and bitch if anyone beat us to them. | ||
569 | * The open() function will actually kick the board off. | ||
570 | */ | ||
571 | |||
572 | static int __init wdt_init(void) | ||
573 | { | ||
574 | int ret; | ||
575 | |||
576 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
577 | if (wdt_set_heartbeat(heartbeat)) { | ||
578 | wdt_set_heartbeat(WD_TIMO); | ||
579 | printk(KERN_INFO "wdt: heartbeat value must be 0<heartbeat<65536, using %d\n", | ||
580 | WD_TIMO); | ||
581 | } | ||
582 | |||
583 | if (!request_region(io, 8, "wdt501p")) { | ||
584 | printk(KERN_ERR "wdt: I/O address 0x%04x already in use\n", io); | ||
585 | ret = -EBUSY; | ||
586 | goto out; | ||
587 | } | ||
588 | |||
589 | ret = request_irq(irq, wdt_interrupt, SA_INTERRUPT, "wdt501p", NULL); | ||
590 | if(ret) { | ||
591 | printk(KERN_ERR "wdt: IRQ %d is not free.\n", irq); | ||
592 | goto outreg; | ||
593 | } | ||
594 | |||
595 | ret = register_reboot_notifier(&wdt_notifier); | ||
596 | if(ret) { | ||
597 | printk(KERN_ERR "wdt: cannot register reboot notifier (err=%d)\n", ret); | ||
598 | goto outirq; | ||
599 | } | ||
600 | |||
601 | #ifdef CONFIG_WDT_501 | ||
602 | ret = misc_register(&temp_miscdev); | ||
603 | if (ret) { | ||
604 | printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n", | ||
605 | TEMP_MINOR, ret); | ||
606 | goto outrbt; | ||
607 | } | ||
608 | #endif /* CONFIG_WDT_501 */ | ||
609 | |||
610 | ret = misc_register(&wdt_miscdev); | ||
611 | if (ret) { | ||
612 | printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n", | ||
613 | WATCHDOG_MINOR, ret); | ||
614 | goto outmisc; | ||
615 | } | ||
616 | |||
617 | ret = 0; | ||
618 | printk(KERN_INFO "WDT500/501-P driver 0.10 at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)\n", | ||
619 | io, irq, heartbeat, nowayout); | ||
620 | #ifdef CONFIG_WDT_501 | ||
621 | printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled")); | ||
622 | #endif /* CONFIG_WDT_501 */ | ||
623 | |||
624 | out: | ||
625 | return ret; | ||
626 | |||
627 | outmisc: | ||
628 | #ifdef CONFIG_WDT_501 | ||
629 | misc_deregister(&temp_miscdev); | ||
630 | outrbt: | ||
631 | #endif /* CONFIG_WDT_501 */ | ||
632 | unregister_reboot_notifier(&wdt_notifier); | ||
633 | outirq: | ||
634 | free_irq(irq, NULL); | ||
635 | outreg: | ||
636 | release_region(io,8); | ||
637 | goto out; | ||
638 | } | ||
639 | |||
640 | module_init(wdt_init); | ||
641 | module_exit(wdt_exit); | ||
642 | |||
643 | MODULE_AUTHOR("Alan Cox"); | ||
644 | MODULE_DESCRIPTION("Driver for ISA ICS watchdog cards (WDT500/501)"); | ||
645 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
646 | MODULE_ALIAS_MISCDEV(TEMP_MINOR); | ||
647 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/char/watchdog/wdt285.c b/drivers/char/watchdog/wdt285.c new file mode 100644 index 000000000000..52825a1f1779 --- /dev/null +++ b/drivers/char/watchdog/wdt285.c | |||
@@ -0,0 +1,229 @@ | |||
1 | /* | ||
2 | * Intel 21285 watchdog driver | ||
3 | * Copyright (c) Phil Blundell <pb@nexus.co.uk>, 1998 | ||
4 | * | ||
5 | * based on | ||
6 | * | ||
7 | * SoftDog 0.05: A Software Watchdog Device | ||
8 | * | ||
9 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version | ||
14 | * 2 of the License, or (at your option) any later version. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/module.h> | ||
19 | #include <linux/moduleparam.h> | ||
20 | #include <linux/types.h> | ||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/fs.h> | ||
23 | #include <linux/mm.h> | ||
24 | #include <linux/miscdevice.h> | ||
25 | #include <linux/watchdog.h> | ||
26 | #include <linux/reboot.h> | ||
27 | #include <linux/init.h> | ||
28 | #include <linux/interrupt.h> | ||
29 | |||
30 | #include <asm/irq.h> | ||
31 | #include <asm/uaccess.h> | ||
32 | #include <asm/hardware.h> | ||
33 | #include <asm/mach-types.h> | ||
34 | #include <asm/hardware/dec21285.h> | ||
35 | |||
36 | /* | ||
37 | * Define this to stop the watchdog actually rebooting the machine. | ||
38 | */ | ||
39 | #undef ONLY_TESTING | ||
40 | |||
41 | static unsigned int soft_margin = 60; /* in seconds */ | ||
42 | static unsigned int reload; | ||
43 | static unsigned long timer_alive; | ||
44 | |||
45 | #ifdef ONLY_TESTING | ||
46 | /* | ||
47 | * If the timer expires.. | ||
48 | */ | ||
49 | static void watchdog_fire(int irq, void *dev_id, struct pt_regs *regs) | ||
50 | { | ||
51 | printk(KERN_CRIT "Watchdog: Would Reboot.\n"); | ||
52 | *CSR_TIMER4_CNTL = 0; | ||
53 | *CSR_TIMER4_CLR = 0; | ||
54 | } | ||
55 | #endif | ||
56 | |||
57 | /* | ||
58 | * Refresh the timer. | ||
59 | */ | ||
60 | static void watchdog_ping(void) | ||
61 | { | ||
62 | *CSR_TIMER4_LOAD = reload; | ||
63 | } | ||
64 | |||
65 | /* | ||
66 | * Allow only one person to hold it open | ||
67 | */ | ||
68 | static int watchdog_open(struct inode *inode, struct file *file) | ||
69 | { | ||
70 | int ret; | ||
71 | |||
72 | if (*CSR_SA110_CNTL & (1 << 13)) | ||
73 | return -EBUSY; | ||
74 | |||
75 | if (test_and_set_bit(1, &timer_alive)) | ||
76 | return -EBUSY; | ||
77 | |||
78 | reload = soft_margin * (mem_fclk_21285 / 256); | ||
79 | |||
80 | *CSR_TIMER4_CLR = 0; | ||
81 | watchdog_ping(); | ||
82 | *CSR_TIMER4_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_AUTORELOAD | ||
83 | | TIMER_CNTL_DIV256; | ||
84 | |||
85 | #ifdef ONLY_TESTING | ||
86 | ret = request_irq(IRQ_TIMER4, watchdog_fire, 0, "watchdog", NULL); | ||
87 | if (ret) { | ||
88 | *CSR_TIMER4_CNTL = 0; | ||
89 | clear_bit(1, &timer_alive); | ||
90 | } | ||
91 | #else | ||
92 | /* | ||
93 | * Setting this bit is irreversible; once enabled, there is | ||
94 | * no way to disable the watchdog. | ||
95 | */ | ||
96 | *CSR_SA110_CNTL |= 1 << 13; | ||
97 | |||
98 | ret = 0; | ||
99 | #endif | ||
100 | nonseekable_open(inode, file); | ||
101 | return ret; | ||
102 | } | ||
103 | |||
104 | /* | ||
105 | * Shut off the timer. | ||
106 | * Note: if we really have enabled the watchdog, there | ||
107 | * is no way to turn off. | ||
108 | */ | ||
109 | static int watchdog_release(struct inode *inode, struct file *file) | ||
110 | { | ||
111 | #ifdef ONLY_TESTING | ||
112 | free_irq(IRQ_TIMER4, NULL); | ||
113 | clear_bit(1, &timer_alive); | ||
114 | #endif | ||
115 | return 0; | ||
116 | } | ||
117 | |||
118 | static ssize_t | ||
119 | watchdog_write(struct file *file, const char *data, size_t len, loff_t *ppos) | ||
120 | { | ||
121 | /* | ||
122 | * Refresh the timer. | ||
123 | */ | ||
124 | if (len) | ||
125 | watchdog_ping(); | ||
126 | |||
127 | return len; | ||
128 | } | ||
129 | |||
130 | static struct watchdog_info ident = { | ||
131 | .options = WDIOF_SETTIMEOUT, | ||
132 | .identity = "Footbridge Watchdog", | ||
133 | }; | ||
134 | |||
135 | static int | ||
136 | watchdog_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
137 | unsigned long arg) | ||
138 | { | ||
139 | unsigned int new_margin; | ||
140 | int ret = -ENOIOCTLCMD; | ||
141 | |||
142 | switch(cmd) { | ||
143 | case WDIOC_GETSUPPORT: | ||
144 | ret = 0; | ||
145 | if (copy_to_user((void *)arg, &ident, sizeof(ident))) | ||
146 | ret = -EFAULT; | ||
147 | break; | ||
148 | |||
149 | case WDIOC_GETSTATUS: | ||
150 | case WDIOC_GETBOOTSTATUS: | ||
151 | ret = put_user(0,(int *)arg); | ||
152 | break; | ||
153 | |||
154 | case WDIOC_KEEPALIVE: | ||
155 | watchdog_ping(); | ||
156 | ret = 0; | ||
157 | break; | ||
158 | |||
159 | case WDIOC_SETTIMEOUT: | ||
160 | ret = get_user(new_margin, (int *)arg); | ||
161 | if (ret) | ||
162 | break; | ||
163 | |||
164 | /* Arbitrary, can't find the card's limits */ | ||
165 | if (new_margin < 0 || new_margin > 60) { | ||
166 | ret = -EINVAL; | ||
167 | break; | ||
168 | } | ||
169 | |||
170 | soft_margin = new_margin; | ||
171 | reload = soft_margin * (mem_fclk_21285 / 256); | ||
172 | watchdog_ping(); | ||
173 | /* Fall */ | ||
174 | case WDIOC_GETTIMEOUT: | ||
175 | ret = put_user(soft_margin, (int *)arg); | ||
176 | break; | ||
177 | } | ||
178 | return ret; | ||
179 | } | ||
180 | |||
181 | static struct file_operations watchdog_fops = { | ||
182 | .owner = THIS_MODULE, | ||
183 | .llseek = no_llseek, | ||
184 | .write = watchdog_write, | ||
185 | .ioctl = watchdog_ioctl, | ||
186 | .open = watchdog_open, | ||
187 | .release = watchdog_release, | ||
188 | }; | ||
189 | |||
190 | static struct miscdevice watchdog_miscdev = { | ||
191 | .minor = WATCHDOG_MINOR, | ||
192 | .name = "watchdog", | ||
193 | .fops = &watchdog_fops, | ||
194 | }; | ||
195 | |||
196 | static int __init footbridge_watchdog_init(void) | ||
197 | { | ||
198 | int retval; | ||
199 | |||
200 | if (machine_is_netwinder()) | ||
201 | return -ENODEV; | ||
202 | |||
203 | retval = misc_register(&watchdog_miscdev); | ||
204 | if (retval < 0) | ||
205 | return retval; | ||
206 | |||
207 | printk("Footbridge Watchdog Timer: 0.01, timer margin: %d sec\n", | ||
208 | soft_margin); | ||
209 | |||
210 | if (machine_is_cats()) | ||
211 | printk("Warning: Watchdog reset may not work on this machine.\n"); | ||
212 | return 0; | ||
213 | } | ||
214 | |||
215 | static void __exit footbridge_watchdog_exit(void) | ||
216 | { | ||
217 | misc_deregister(&watchdog_miscdev); | ||
218 | } | ||
219 | |||
220 | MODULE_AUTHOR("Phil Blundell <pb@nexus.co.uk>"); | ||
221 | MODULE_DESCRIPTION("Footbridge watchdog driver"); | ||
222 | MODULE_LICENSE("GPL"); | ||
223 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
224 | |||
225 | module_param(soft_margin, int, 0); | ||
226 | MODULE_PARM_DESC(soft_margin,"Watchdog timeout in seconds"); | ||
227 | |||
228 | module_init(footbridge_watchdog_init); | ||
229 | module_exit(footbridge_watchdog_exit); | ||
diff --git a/drivers/char/watchdog/wdt977.c b/drivers/char/watchdog/wdt977.c new file mode 100644 index 000000000000..072e9b214759 --- /dev/null +++ b/drivers/char/watchdog/wdt977.c | |||
@@ -0,0 +1,459 @@ | |||
1 | /* | ||
2 | * Wdt977 0.03: A Watchdog Device for Netwinder W83977AF chip | ||
3 | * | ||
4 | * (c) Copyright 1998 Rebel.com (Woody Suwalski <woody@netwinder.org>) | ||
5 | * | ||
6 | * ----------------------- | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License | ||
10 | * as published by the Free Software Foundation; either version | ||
11 | * 2 of the License, or (at your option) any later version. | ||
12 | * | ||
13 | * ----------------------- | ||
14 | * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> | ||
15 | * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT | ||
16 | * 19-Dec-2001 Woody Suwalski: Netwinder fixes, ioctl interface | ||
17 | * 06-Jan-2002 Woody Suwalski: For compatibility, convert all timeouts | ||
18 | * from minutes to seconds. | ||
19 | * 07-Jul-2003 Daniele Bellucci: Audit return code of misc_register in | ||
20 | * nwwatchdog_init. | ||
21 | */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/moduleparam.h> | ||
25 | #include <linux/config.h> | ||
26 | #include <linux/types.h> | ||
27 | #include <linux/kernel.h> | ||
28 | #include <linux/fs.h> | ||
29 | #include <linux/miscdevice.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/watchdog.h> | ||
32 | #include <linux/notifier.h> | ||
33 | #include <linux/reboot.h> | ||
34 | |||
35 | #include <asm/io.h> | ||
36 | #include <asm/system.h> | ||
37 | #include <asm/mach-types.h> | ||
38 | #include <asm/uaccess.h> | ||
39 | |||
40 | #define PFX "Wdt977: " | ||
41 | #define WATCHDOG_MINOR 130 | ||
42 | |||
43 | #define DEFAULT_TIMEOUT 60 /* default timeout in seconds */ | ||
44 | |||
45 | static int timeout = DEFAULT_TIMEOUT; | ||
46 | static int timeoutM; /* timeout in minutes */ | ||
47 | static unsigned long timer_alive; | ||
48 | static int testmode; | ||
49 | static char expect_close; | ||
50 | |||
51 | module_param(timeout, int, 0); | ||
52 | MODULE_PARM_DESC(timeout,"Watchdog timeout in seconds (60..15300), default=" __MODULE_STRING(DEFAULT_TIMEOUT) ")"); | ||
53 | module_param(testmode, int, 0); | ||
54 | MODULE_PARM_DESC(testmode,"Watchdog testmode (1 = no reboot), default=0"); | ||
55 | |||
56 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
57 | static int nowayout = 1; | ||
58 | #else | ||
59 | static int nowayout = 0; | ||
60 | #endif | ||
61 | |||
62 | module_param(nowayout, int, 0); | ||
63 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
64 | |||
65 | /* | ||
66 | * Start the watchdog | ||
67 | */ | ||
68 | |||
69 | static int wdt977_start(void) | ||
70 | { | ||
71 | /* unlock the SuperIO chip */ | ||
72 | outb(0x87,0x370); | ||
73 | outb(0x87,0x370); | ||
74 | |||
75 | /* select device Aux2 (device=8) and set watchdog regs F2, F3 and F4 | ||
76 | * F2 has the timeout in minutes | ||
77 | * F3 could be set to the POWER LED blink (with GP17 set to PowerLed) | ||
78 | * at timeout, and to reset timer on kbd/mouse activity (not impl.) | ||
79 | * F4 is used to just clear the TIMEOUT'ed state (bit 0) | ||
80 | */ | ||
81 | outb(0x07,0x370); | ||
82 | outb(0x08,0x371); | ||
83 | outb(0xF2,0x370); | ||
84 | outb(timeoutM,0x371); | ||
85 | outb(0xF3,0x370); | ||
86 | outb(0x00,0x371); /* another setting is 0E for kbd/mouse/LED */ | ||
87 | outb(0xF4,0x370); | ||
88 | outb(0x00,0x371); | ||
89 | |||
90 | /* at last select device Aux1 (dev=7) and set GP16 as a watchdog output */ | ||
91 | /* in test mode watch the bit 1 on F4 to indicate "triggered" */ | ||
92 | if (!testmode) | ||
93 | { | ||
94 | outb(0x07,0x370); | ||
95 | outb(0x07,0x371); | ||
96 | outb(0xE6,0x370); | ||
97 | outb(0x08,0x371); | ||
98 | } | ||
99 | |||
100 | /* lock the SuperIO chip */ | ||
101 | outb(0xAA,0x370); | ||
102 | |||
103 | printk(KERN_INFO PFX "activated.\n"); | ||
104 | |||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | /* | ||
109 | * Stop the watchdog | ||
110 | */ | ||
111 | |||
112 | static int wdt977_stop(void) | ||
113 | { | ||
114 | /* unlock the SuperIO chip */ | ||
115 | outb(0x87,0x370); | ||
116 | outb(0x87,0x370); | ||
117 | |||
118 | /* select device Aux2 (device=8) and set watchdog regs F2,F3 and F4 | ||
119 | * F3 is reset to its default state | ||
120 | * F4 can clear the TIMEOUT'ed state (bit 0) - back to default | ||
121 | * We can not use GP17 as a PowerLed, as we use its usage as a RedLed | ||
122 | */ | ||
123 | outb(0x07,0x370); | ||
124 | outb(0x08,0x371); | ||
125 | outb(0xF2,0x370); | ||
126 | outb(0xFF,0x371); | ||
127 | outb(0xF3,0x370); | ||
128 | outb(0x00,0x371); | ||
129 | outb(0xF4,0x370); | ||
130 | outb(0x00,0x371); | ||
131 | outb(0xF2,0x370); | ||
132 | outb(0x00,0x371); | ||
133 | |||
134 | /* at last select device Aux1 (dev=7) and set GP16 as a watchdog output */ | ||
135 | outb(0x07,0x370); | ||
136 | outb(0x07,0x371); | ||
137 | outb(0xE6,0x370); | ||
138 | outb(0x08,0x371); | ||
139 | |||
140 | /* lock the SuperIO chip */ | ||
141 | outb(0xAA,0x370); | ||
142 | |||
143 | printk(KERN_INFO PFX "shutdown.\n"); | ||
144 | |||
145 | return 0; | ||
146 | } | ||
147 | |||
148 | /* | ||
149 | * Send a keepalive ping to the watchdog | ||
150 | * This is done by simply re-writing the timeout to reg. 0xF2 | ||
151 | */ | ||
152 | |||
153 | static int wdt977_keepalive(void) | ||
154 | { | ||
155 | /* unlock the SuperIO chip */ | ||
156 | outb(0x87,0x370); | ||
157 | outb(0x87,0x370); | ||
158 | |||
159 | /* select device Aux2 (device=8) and kicks watchdog reg F2 */ | ||
160 | /* F2 has the timeout in minutes */ | ||
161 | outb(0x07,0x370); | ||
162 | outb(0x08,0x371); | ||
163 | outb(0xF2,0x370); | ||
164 | outb(timeoutM,0x371); | ||
165 | |||
166 | /* lock the SuperIO chip */ | ||
167 | outb(0xAA,0x370); | ||
168 | |||
169 | return 0; | ||
170 | } | ||
171 | |||
172 | /* | ||
173 | * Set the watchdog timeout value | ||
174 | */ | ||
175 | |||
176 | static int wdt977_set_timeout(int t) | ||
177 | { | ||
178 | int tmrval; | ||
179 | |||
180 | /* convert seconds to minutes, rounding up */ | ||
181 | tmrval = (t + 59) / 60; | ||
182 | |||
183 | if (machine_is_netwinder()) { | ||
184 | /* we have a hw bug somewhere, so each 977 minute is actually only 30sec | ||
185 | * this limits the max timeout to half of device max of 255 minutes... | ||
186 | */ | ||
187 | tmrval += tmrval; | ||
188 | } | ||
189 | |||
190 | if ((tmrval < 1) || (tmrval > 255)) | ||
191 | return -EINVAL; | ||
192 | |||
193 | /* timeout is the timeout in seconds, timeoutM is the timeout in minutes) */ | ||
194 | timeout = t; | ||
195 | timeoutM = tmrval; | ||
196 | return 0; | ||
197 | } | ||
198 | |||
199 | /* | ||
200 | * Get the watchdog status | ||
201 | */ | ||
202 | |||
203 | static int wdt977_get_status(int *status) | ||
204 | { | ||
205 | int new_status; | ||
206 | |||
207 | *status=0; | ||
208 | |||
209 | /* unlock the SuperIO chip */ | ||
210 | outb(0x87,0x370); | ||
211 | outb(0x87,0x370); | ||
212 | |||
213 | /* select device Aux2 (device=8) and read watchdog reg F4 */ | ||
214 | outb(0x07,0x370); | ||
215 | outb(0x08,0x371); | ||
216 | outb(0xF4,0x370); | ||
217 | new_status = inb(0x371); | ||
218 | |||
219 | /* lock the SuperIO chip */ | ||
220 | outb(0xAA,0x370); | ||
221 | |||
222 | if (new_status & 1) | ||
223 | *status |= WDIOF_CARDRESET; | ||
224 | |||
225 | return 0; | ||
226 | } | ||
227 | |||
228 | |||
229 | /* | ||
230 | * /dev/watchdog handling | ||
231 | */ | ||
232 | |||
233 | static int wdt977_open(struct inode *inode, struct file *file) | ||
234 | { | ||
235 | /* If the watchdog is alive we don't need to start it again */ | ||
236 | if( test_and_set_bit(0,&timer_alive) ) | ||
237 | return -EBUSY; | ||
238 | |||
239 | if (nowayout) | ||
240 | __module_get(THIS_MODULE); | ||
241 | |||
242 | wdt977_start(); | ||
243 | return nonseekable_open(inode, file); | ||
244 | } | ||
245 | |||
246 | static int wdt977_release(struct inode *inode, struct file *file) | ||
247 | { | ||
248 | /* | ||
249 | * Shut off the timer. | ||
250 | * Lock it in if it's a module and we set nowayout | ||
251 | */ | ||
252 | if (expect_close == 42) | ||
253 | { | ||
254 | wdt977_stop(); | ||
255 | clear_bit(0,&timer_alive); | ||
256 | } else { | ||
257 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
258 | wdt977_keepalive(); | ||
259 | } | ||
260 | expect_close = 0; | ||
261 | return 0; | ||
262 | } | ||
263 | |||
264 | |||
265 | /* | ||
266 | * wdt977_write: | ||
267 | * @file: file handle to the watchdog | ||
268 | * @buf: buffer to write (unused as data does not matter here | ||
269 | * @count: count of bytes | ||
270 | * @ppos: pointer to the position to write. No seeks allowed | ||
271 | * | ||
272 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
273 | * write of data will do, as we we don't define content meaning. | ||
274 | */ | ||
275 | |||
276 | static ssize_t wdt977_write(struct file *file, const char __user *buf, | ||
277 | size_t count, loff_t *ppos) | ||
278 | { | ||
279 | if (count) { | ||
280 | if (!nowayout) { | ||
281 | size_t i; | ||
282 | |||
283 | /* In case it was set long ago */ | ||
284 | expect_close = 0; | ||
285 | |||
286 | for (i = 0; i != count; i++) { | ||
287 | char c; | ||
288 | if (get_user(c, buf + i)) | ||
289 | return -EFAULT; | ||
290 | if (c == 'V') | ||
291 | expect_close = 42; | ||
292 | } | ||
293 | } | ||
294 | |||
295 | wdt977_keepalive(); | ||
296 | } | ||
297 | return count; | ||
298 | } | ||
299 | |||
300 | /* | ||
301 | * wdt977_ioctl: | ||
302 | * @inode: inode of the device | ||
303 | * @file: file handle to the device | ||
304 | * @cmd: watchdog command | ||
305 | * @arg: argument pointer | ||
306 | * | ||
307 | * The watchdog API defines a common set of functions for all watchdogs | ||
308 | * according to their available features. | ||
309 | */ | ||
310 | |||
311 | static struct watchdog_info ident = { | ||
312 | .options = WDIOF_SETTIMEOUT | | ||
313 | WDIOF_MAGICCLOSE | | ||
314 | WDIOF_KEEPALIVEPING, | ||
315 | .firmware_version = 1, | ||
316 | .identity = "Winbond 83977", | ||
317 | }; | ||
318 | |||
319 | static int wdt977_ioctl(struct inode *inode, struct file *file, | ||
320 | unsigned int cmd, unsigned long arg) | ||
321 | { | ||
322 | int status; | ||
323 | int new_options, retval = -EINVAL; | ||
324 | int new_timeout; | ||
325 | union { | ||
326 | struct watchdog_info __user *ident; | ||
327 | int __user *i; | ||
328 | } uarg; | ||
329 | |||
330 | uarg.i = (int __user *)arg; | ||
331 | |||
332 | switch(cmd) | ||
333 | { | ||
334 | default: | ||
335 | return -ENOIOCTLCMD; | ||
336 | |||
337 | case WDIOC_GETSUPPORT: | ||
338 | return copy_to_user(uarg.ident, &ident, | ||
339 | sizeof(ident)) ? -EFAULT : 0; | ||
340 | |||
341 | case WDIOC_GETSTATUS: | ||
342 | wdt977_get_status(&status); | ||
343 | return put_user(status, uarg.i); | ||
344 | |||
345 | case WDIOC_GETBOOTSTATUS: | ||
346 | return put_user(0, uarg.i); | ||
347 | |||
348 | case WDIOC_KEEPALIVE: | ||
349 | wdt977_keepalive(); | ||
350 | return 0; | ||
351 | |||
352 | case WDIOC_SETOPTIONS: | ||
353 | if (get_user (new_options, uarg.i)) | ||
354 | return -EFAULT; | ||
355 | |||
356 | if (new_options & WDIOS_DISABLECARD) { | ||
357 | wdt977_stop(); | ||
358 | retval = 0; | ||
359 | } | ||
360 | |||
361 | if (new_options & WDIOS_ENABLECARD) { | ||
362 | wdt977_start(); | ||
363 | retval = 0; | ||
364 | } | ||
365 | |||
366 | return retval; | ||
367 | |||
368 | case WDIOC_SETTIMEOUT: | ||
369 | if (get_user(new_timeout, uarg.i)) | ||
370 | return -EFAULT; | ||
371 | |||
372 | if (wdt977_set_timeout(new_timeout)) | ||
373 | return -EINVAL; | ||
374 | |||
375 | wdt977_keepalive(); | ||
376 | /* Fall */ | ||
377 | |||
378 | case WDIOC_GETTIMEOUT: | ||
379 | return put_user(timeout, uarg.i); | ||
380 | |||
381 | } | ||
382 | } | ||
383 | |||
384 | static int wdt977_notify_sys(struct notifier_block *this, unsigned long code, | ||
385 | void *unused) | ||
386 | { | ||
387 | if(code==SYS_DOWN || code==SYS_HALT) | ||
388 | wdt977_stop(); | ||
389 | return NOTIFY_DONE; | ||
390 | } | ||
391 | |||
392 | static struct file_operations wdt977_fops= | ||
393 | { | ||
394 | .owner = THIS_MODULE, | ||
395 | .llseek = no_llseek, | ||
396 | .write = wdt977_write, | ||
397 | .ioctl = wdt977_ioctl, | ||
398 | .open = wdt977_open, | ||
399 | .release = wdt977_release, | ||
400 | }; | ||
401 | |||
402 | static struct miscdevice wdt977_miscdev= | ||
403 | { | ||
404 | .minor = WATCHDOG_MINOR, | ||
405 | .name = "watchdog", | ||
406 | .fops = &wdt977_fops, | ||
407 | }; | ||
408 | |||
409 | static struct notifier_block wdt977_notifier = { | ||
410 | .notifier_call = wdt977_notify_sys, | ||
411 | }; | ||
412 | |||
413 | static int __init nwwatchdog_init(void) | ||
414 | { | ||
415 | int retval; | ||
416 | if (!machine_is_netwinder()) | ||
417 | return -ENODEV; | ||
418 | |||
419 | /* Check that the timeout value is within it's range ; if not reset to the default */ | ||
420 | if (wdt977_set_timeout(timeout)) { | ||
421 | wdt977_set_timeout(DEFAULT_TIMEOUT); | ||
422 | printk(KERN_INFO PFX "timeout value must be 60<timeout<15300, using %d\n", | ||
423 | DEFAULT_TIMEOUT); | ||
424 | } | ||
425 | |||
426 | retval = register_reboot_notifier(&wdt977_notifier); | ||
427 | if (retval) { | ||
428 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
429 | retval); | ||
430 | return retval; | ||
431 | } | ||
432 | |||
433 | retval = misc_register(&wdt977_miscdev); | ||
434 | if (retval) { | ||
435 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
436 | WATCHDOG_MINOR, retval); | ||
437 | unregister_reboot_notifier(&wdt977_notifier); | ||
438 | return retval; | ||
439 | } | ||
440 | |||
441 | printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d, testmode = %i)\n", | ||
442 | timeout, nowayout, testmode); | ||
443 | |||
444 | return 0; | ||
445 | } | ||
446 | |||
447 | static void __exit nwwatchdog_exit(void) | ||
448 | { | ||
449 | misc_deregister(&wdt977_miscdev); | ||
450 | unregister_reboot_notifier(&wdt977_notifier); | ||
451 | } | ||
452 | |||
453 | module_init(nwwatchdog_init); | ||
454 | module_exit(nwwatchdog_exit); | ||
455 | |||
456 | MODULE_AUTHOR("Woody Suwalski <woody@netwinder.org>"); | ||
457 | MODULE_DESCRIPTION("W83977AF Watchdog driver"); | ||
458 | MODULE_LICENSE("GPL"); | ||
459 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/char/watchdog/wdt_pci.c b/drivers/char/watchdog/wdt_pci.c new file mode 100644 index 000000000000..7651deda928c --- /dev/null +++ b/drivers/char/watchdog/wdt_pci.c | |||
@@ -0,0 +1,763 @@ | |||
1 | /* | ||
2 | * Industrial Computer Source PCI-WDT500/501 driver | ||
3 | * | ||
4 | * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
5 | * http://www.redhat.com | ||
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 Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
13 | * warranty for any of this software. This material is provided | ||
14 | * "AS-IS" and at no charge. | ||
15 | * | ||
16 | * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> | ||
17 | * | ||
18 | * Release 0.10. | ||
19 | * | ||
20 | * Fixes | ||
21 | * Dave Gregorich : Modularisation and minor bugs | ||
22 | * Alan Cox : Added the watchdog ioctl() stuff | ||
23 | * Alan Cox : Fixed the reboot problem (as noted by | ||
24 | * Matt Crocker). | ||
25 | * Alan Cox : Added wdt= boot option | ||
26 | * Alan Cox : Cleaned up copy/user stuff | ||
27 | * Tim Hockin : Added insmod parameters, comment cleanup | ||
28 | * Parameterized timeout | ||
29 | * JP Nollmann : Added support for PCI wdt501p | ||
30 | * Alan Cox : Split ISA and PCI cards into two drivers | ||
31 | * Jeff Garzik : PCI cleanups | ||
32 | * Tigran Aivazian : Restructured wdtpci_init_one() to handle failures | ||
33 | * Joel Becker : Added WDIOC_GET/SETTIMEOUT | ||
34 | * Zwane Mwaikambo : Magic char closing, locking changes, cleanups | ||
35 | * Matt Domsch : nowayout module option | ||
36 | */ | ||
37 | |||
38 | #include <linux/config.h> | ||
39 | #include <linux/interrupt.h> | ||
40 | #include <linux/module.h> | ||
41 | #include <linux/moduleparam.h> | ||
42 | #include <linux/types.h> | ||
43 | #include <linux/miscdevice.h> | ||
44 | #include <linux/watchdog.h> | ||
45 | #include <linux/ioport.h> | ||
46 | #include <linux/notifier.h> | ||
47 | #include <linux/reboot.h> | ||
48 | #include <linux/init.h> | ||
49 | #include <linux/fs.h> | ||
50 | #include <linux/pci.h> | ||
51 | |||
52 | #include <asm/io.h> | ||
53 | #include <asm/uaccess.h> | ||
54 | #include <asm/system.h> | ||
55 | |||
56 | #define WDT_IS_PCI | ||
57 | #include "wd501p.h" | ||
58 | |||
59 | #define PFX "wdt_pci: " | ||
60 | |||
61 | /* | ||
62 | * Until Access I/O gets their application for a PCI vendor ID approved, | ||
63 | * I don't think that it's appropriate to move these constants into the | ||
64 | * regular pci_ids.h file. -- JPN 2000/01/18 | ||
65 | */ | ||
66 | |||
67 | #ifndef PCI_VENDOR_ID_ACCESSIO | ||
68 | #define PCI_VENDOR_ID_ACCESSIO 0x494f | ||
69 | #endif | ||
70 | #ifndef PCI_DEVICE_ID_WDG_CSM | ||
71 | #define PCI_DEVICE_ID_WDG_CSM 0x22c0 | ||
72 | #endif | ||
73 | |||
74 | /* We can only use 1 card due to the /dev/watchdog restriction */ | ||
75 | static int dev_count; | ||
76 | |||
77 | static struct semaphore open_sem; | ||
78 | static spinlock_t wdtpci_lock; | ||
79 | static char expect_close; | ||
80 | |||
81 | static int io; | ||
82 | static int irq; | ||
83 | |||
84 | /* Default timeout */ | ||
85 | #define WD_TIMO 60 /* Default heartbeat = 60 seconds */ | ||
86 | |||
87 | static int heartbeat = WD_TIMO; | ||
88 | static int wd_heartbeat; | ||
89 | module_param(heartbeat, int, 0); | ||
90 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")"); | ||
91 | |||
92 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
93 | static int nowayout = 1; | ||
94 | #else | ||
95 | static int nowayout = 0; | ||
96 | #endif | ||
97 | |||
98 | module_param(nowayout, int, 0); | ||
99 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); | ||
100 | |||
101 | #ifdef CONFIG_WDT_501_PCI | ||
102 | /* Support for the Fan Tachometer on the PCI-WDT501 */ | ||
103 | static int tachometer; | ||
104 | |||
105 | module_param(tachometer, int, 0); | ||
106 | MODULE_PARM_DESC(tachometer, "PCI-WDT501 Fan Tachometer support (0=disable, default=0)"); | ||
107 | #endif /* CONFIG_WDT_501_PCI */ | ||
108 | |||
109 | /* | ||
110 | * Programming support | ||
111 | */ | ||
112 | |||
113 | static void wdtpci_ctr_mode(int ctr, int mode) | ||
114 | { | ||
115 | ctr<<=6; | ||
116 | ctr|=0x30; | ||
117 | ctr|=(mode<<1); | ||
118 | outb_p(ctr, WDT_CR); | ||
119 | } | ||
120 | |||
121 | static void wdtpci_ctr_load(int ctr, int val) | ||
122 | { | ||
123 | outb_p(val&0xFF, WDT_COUNT0+ctr); | ||
124 | outb_p(val>>8, WDT_COUNT0+ctr); | ||
125 | } | ||
126 | |||
127 | /** | ||
128 | * wdtpci_start: | ||
129 | * | ||
130 | * Start the watchdog driver. | ||
131 | */ | ||
132 | |||
133 | static int wdtpci_start(void) | ||
134 | { | ||
135 | unsigned long flags; | ||
136 | |||
137 | spin_lock_irqsave(&wdtpci_lock, flags); | ||
138 | |||
139 | /* | ||
140 | * "pet" the watchdog, as Access says. | ||
141 | * This resets the clock outputs. | ||
142 | */ | ||
143 | inb_p(WDT_DC); /* Disable watchdog */ | ||
144 | wdtpci_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */ | ||
145 | outb_p(0, WDT_DC); /* Enable watchdog */ | ||
146 | |||
147 | inb_p(WDT_DC); /* Disable watchdog */ | ||
148 | outb_p(0, WDT_CLOCK); /* 2.0833MHz clock */ | ||
149 | inb_p(WDT_BUZZER); /* disable */ | ||
150 | inb_p(WDT_OPTONOTRST); /* disable */ | ||
151 | inb_p(WDT_OPTORST); /* disable */ | ||
152 | inb_p(WDT_PROGOUT); /* disable */ | ||
153 | wdtpci_ctr_mode(0,3); /* Program CTR0 for Mode 3: Square Wave Generator */ | ||
154 | wdtpci_ctr_mode(1,2); /* Program CTR1 for Mode 2: Rate Generator */ | ||
155 | wdtpci_ctr_mode(2,1); /* Program CTR2 for Mode 1: Retriggerable One-Shot */ | ||
156 | wdtpci_ctr_load(0,20833); /* count at 100Hz */ | ||
157 | wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */ | ||
158 | /* DO NOT LOAD CTR2 on PCI card! -- JPN */ | ||
159 | outb_p(0, WDT_DC); /* Enable watchdog */ | ||
160 | |||
161 | spin_unlock_irqrestore(&wdtpci_lock, flags); | ||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | /** | ||
166 | * wdtpci_stop: | ||
167 | * | ||
168 | * Stop the watchdog driver. | ||
169 | */ | ||
170 | |||
171 | static int wdtpci_stop (void) | ||
172 | { | ||
173 | unsigned long flags; | ||
174 | |||
175 | /* Turn the card off */ | ||
176 | spin_lock_irqsave(&wdtpci_lock, flags); | ||
177 | inb_p(WDT_DC); /* Disable watchdog */ | ||
178 | wdtpci_ctr_load(2,0); /* 0 length reset pulses now */ | ||
179 | spin_unlock_irqrestore(&wdtpci_lock, flags); | ||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | /** | ||
184 | * wdtpci_ping: | ||
185 | * | ||
186 | * Reload counter one with the watchdog heartbeat. We don't bother reloading | ||
187 | * the cascade counter. | ||
188 | */ | ||
189 | |||
190 | static int wdtpci_ping(void) | ||
191 | { | ||
192 | unsigned long flags; | ||
193 | |||
194 | /* Write a watchdog value */ | ||
195 | spin_lock_irqsave(&wdtpci_lock, flags); | ||
196 | inb_p(WDT_DC); /* Disable watchdog */ | ||
197 | wdtpci_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */ | ||
198 | wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */ | ||
199 | outb_p(0, WDT_DC); /* Enable watchdog */ | ||
200 | spin_unlock_irqrestore(&wdtpci_lock, flags); | ||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | /** | ||
205 | * wdtpci_set_heartbeat: | ||
206 | * @t: the new heartbeat value that needs to be set. | ||
207 | * | ||
208 | * Set a new heartbeat value for the watchdog device. If the heartbeat value is | ||
209 | * incorrect we keep the old value and return -EINVAL. If successfull we | ||
210 | * return 0. | ||
211 | */ | ||
212 | static int wdtpci_set_heartbeat(int t) | ||
213 | { | ||
214 | /* Arbitrary, can't find the card's limits */ | ||
215 | if ((t < 1) || (t > 65535)) | ||
216 | return -EINVAL; | ||
217 | |||
218 | heartbeat = t; | ||
219 | wd_heartbeat = t * 100; | ||
220 | return 0; | ||
221 | } | ||
222 | |||
223 | /** | ||
224 | * wdtpci_get_status: | ||
225 | * @status: the new status. | ||
226 | * | ||
227 | * Extract the status information from a WDT watchdog device. There are | ||
228 | * several board variants so we have to know which bits are valid. Some | ||
229 | * bits default to one and some to zero in order to be maximally painful. | ||
230 | * | ||
231 | * we then map the bits onto the status ioctl flags. | ||
232 | */ | ||
233 | |||
234 | static int wdtpci_get_status(int *status) | ||
235 | { | ||
236 | unsigned char new_status=inb_p(WDT_SR); | ||
237 | |||
238 | *status=0; | ||
239 | if (new_status & WDC_SR_ISOI0) | ||
240 | *status |= WDIOF_EXTERN1; | ||
241 | if (new_status & WDC_SR_ISII1) | ||
242 | *status |= WDIOF_EXTERN2; | ||
243 | #ifdef CONFIG_WDT_501_PCI | ||
244 | if (!(new_status & WDC_SR_TGOOD)) | ||
245 | *status |= WDIOF_OVERHEAT; | ||
246 | if (!(new_status & WDC_SR_PSUOVER)) | ||
247 | *status |= WDIOF_POWEROVER; | ||
248 | if (!(new_status & WDC_SR_PSUUNDR)) | ||
249 | *status |= WDIOF_POWERUNDER; | ||
250 | if (tachometer) { | ||
251 | if (!(new_status & WDC_SR_FANGOOD)) | ||
252 | *status |= WDIOF_FANFAULT; | ||
253 | } | ||
254 | #endif /* CONFIG_WDT_501_PCI */ | ||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | #ifdef CONFIG_WDT_501_PCI | ||
259 | /** | ||
260 | * wdtpci_get_temperature: | ||
261 | * | ||
262 | * Reports the temperature in degrees Fahrenheit. The API is in | ||
263 | * farenheit. It was designed by an imperial measurement luddite. | ||
264 | */ | ||
265 | |||
266 | static int wdtpci_get_temperature(int *temperature) | ||
267 | { | ||
268 | unsigned short c=inb_p(WDT_RT); | ||
269 | |||
270 | *temperature = (c * 11 / 15) + 7; | ||
271 | return 0; | ||
272 | } | ||
273 | #endif /* CONFIG_WDT_501_PCI */ | ||
274 | |||
275 | /** | ||
276 | * wdtpci_interrupt: | ||
277 | * @irq: Interrupt number | ||
278 | * @dev_id: Unused as we don't allow multiple devices. | ||
279 | * @regs: Unused. | ||
280 | * | ||
281 | * Handle an interrupt from the board. These are raised when the status | ||
282 | * map changes in what the board considers an interesting way. That means | ||
283 | * a failure condition occurring. | ||
284 | */ | ||
285 | |||
286 | static irqreturn_t wdtpci_interrupt(int irq, void *dev_id, struct pt_regs *regs) | ||
287 | { | ||
288 | /* | ||
289 | * Read the status register see what is up and | ||
290 | * then printk it. | ||
291 | */ | ||
292 | unsigned char status=inb_p(WDT_SR); | ||
293 | |||
294 | printk(KERN_CRIT PFX "status %d\n", status); | ||
295 | |||
296 | #ifdef CONFIG_WDT_501_PCI | ||
297 | if (!(status & WDC_SR_TGOOD)) | ||
298 | printk(KERN_CRIT PFX "Overheat alarm.(%d)\n",inb_p(WDT_RT)); | ||
299 | if (!(status & WDC_SR_PSUOVER)) | ||
300 | printk(KERN_CRIT PFX "PSU over voltage.\n"); | ||
301 | if (!(status & WDC_SR_PSUUNDR)) | ||
302 | printk(KERN_CRIT PFX "PSU under voltage.\n"); | ||
303 | if (tachometer) { | ||
304 | if (!(status & WDC_SR_FANGOOD)) | ||
305 | printk(KERN_CRIT PFX "Possible fan fault.\n"); | ||
306 | } | ||
307 | #endif /* CONFIG_WDT_501_PCI */ | ||
308 | if (!(status&WDC_SR_WCCR)) | ||
309 | #ifdef SOFTWARE_REBOOT | ||
310 | #ifdef ONLY_TESTING | ||
311 | printk(KERN_CRIT PFX "Would Reboot.\n"); | ||
312 | #else | ||
313 | printk(KERN_CRIT PFX "Initiating system reboot.\n"); | ||
314 | machine_restart(NULL); | ||
315 | #endif | ||
316 | #else | ||
317 | printk(KERN_CRIT PFX "Reset in 5ms.\n"); | ||
318 | #endif | ||
319 | return IRQ_HANDLED; | ||
320 | } | ||
321 | |||
322 | |||
323 | /** | ||
324 | * wdtpci_write: | ||
325 | * @file: file handle to the watchdog | ||
326 | * @buf: buffer to write (unused as data does not matter here | ||
327 | * @count: count of bytes | ||
328 | * @ppos: pointer to the position to write. No seeks allowed | ||
329 | * | ||
330 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
331 | * write of data will do, as we we don't define content meaning. | ||
332 | */ | ||
333 | |||
334 | static ssize_t wdtpci_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
335 | { | ||
336 | if (count) { | ||
337 | if (!nowayout) { | ||
338 | size_t i; | ||
339 | |||
340 | expect_close = 0; | ||
341 | |||
342 | for (i = 0; i != count; i++) { | ||
343 | char c; | ||
344 | if(get_user(c, buf+i)) | ||
345 | return -EFAULT; | ||
346 | if (c == 'V') | ||
347 | expect_close = 42; | ||
348 | } | ||
349 | } | ||
350 | wdtpci_ping(); | ||
351 | } | ||
352 | |||
353 | return count; | ||
354 | } | ||
355 | |||
356 | /** | ||
357 | * wdtpci_ioctl: | ||
358 | * @inode: inode of the device | ||
359 | * @file: file handle to the device | ||
360 | * @cmd: watchdog command | ||
361 | * @arg: argument pointer | ||
362 | * | ||
363 | * The watchdog API defines a common set of functions for all watchdogs | ||
364 | * according to their available features. We only actually usefully support | ||
365 | * querying capabilities and current status. | ||
366 | */ | ||
367 | |||
368 | static int wdtpci_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
369 | unsigned long arg) | ||
370 | { | ||
371 | int new_heartbeat; | ||
372 | int status; | ||
373 | void __user *argp = (void __user *)arg; | ||
374 | int __user *p = argp; | ||
375 | |||
376 | static struct watchdog_info ident = { | ||
377 | .options = WDIOF_SETTIMEOUT| | ||
378 | WDIOF_MAGICCLOSE| | ||
379 | WDIOF_KEEPALIVEPING, | ||
380 | .firmware_version = 1, | ||
381 | .identity = "PCI-WDT500/501", | ||
382 | }; | ||
383 | |||
384 | /* Add options according to the card we have */ | ||
385 | ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2); | ||
386 | #ifdef CONFIG_WDT_501_PCI | ||
387 | ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER); | ||
388 | if (tachometer) | ||
389 | ident.options |= WDIOF_FANFAULT; | ||
390 | #endif /* CONFIG_WDT_501_PCI */ | ||
391 | |||
392 | switch(cmd) | ||
393 | { | ||
394 | default: | ||
395 | return -ENOIOCTLCMD; | ||
396 | case WDIOC_GETSUPPORT: | ||
397 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
398 | |||
399 | case WDIOC_GETSTATUS: | ||
400 | wdtpci_get_status(&status); | ||
401 | return put_user(status, p); | ||
402 | case WDIOC_GETBOOTSTATUS: | ||
403 | return put_user(0, p); | ||
404 | case WDIOC_KEEPALIVE: | ||
405 | wdtpci_ping(); | ||
406 | return 0; | ||
407 | case WDIOC_SETTIMEOUT: | ||
408 | if (get_user(new_heartbeat, p)) | ||
409 | return -EFAULT; | ||
410 | |||
411 | if (wdtpci_set_heartbeat(new_heartbeat)) | ||
412 | return -EINVAL; | ||
413 | |||
414 | wdtpci_ping(); | ||
415 | /* Fall */ | ||
416 | case WDIOC_GETTIMEOUT: | ||
417 | return put_user(heartbeat, p); | ||
418 | } | ||
419 | } | ||
420 | |||
421 | /** | ||
422 | * wdtpci_open: | ||
423 | * @inode: inode of device | ||
424 | * @file: file handle to device | ||
425 | * | ||
426 | * The watchdog device has been opened. The watchdog device is single | ||
427 | * open and on opening we load the counters. Counter zero is a 100Hz | ||
428 | * cascade, into counter 1 which downcounts to reboot. When the counter | ||
429 | * triggers counter 2 downcounts the length of the reset pulse which | ||
430 | * set set to be as long as possible. | ||
431 | */ | ||
432 | |||
433 | static int wdtpci_open(struct inode *inode, struct file *file) | ||
434 | { | ||
435 | if (down_trylock(&open_sem)) | ||
436 | return -EBUSY; | ||
437 | |||
438 | if (nowayout) { | ||
439 | __module_get(THIS_MODULE); | ||
440 | } | ||
441 | /* | ||
442 | * Activate | ||
443 | */ | ||
444 | wdtpci_start(); | ||
445 | return nonseekable_open(inode, file); | ||
446 | } | ||
447 | |||
448 | /** | ||
449 | * wdtpci_release: | ||
450 | * @inode: inode to board | ||
451 | * @file: file handle to board | ||
452 | * | ||
453 | * The watchdog has a configurable API. There is a religious dispute | ||
454 | * between people who want their watchdog to be able to shut down and | ||
455 | * those who want to be sure if the watchdog manager dies the machine | ||
456 | * reboots. In the former case we disable the counters, in the latter | ||
457 | * case you have to open it again very soon. | ||
458 | */ | ||
459 | |||
460 | static int wdtpci_release(struct inode *inode, struct file *file) | ||
461 | { | ||
462 | if (expect_close == 42) { | ||
463 | wdtpci_stop(); | ||
464 | } else { | ||
465 | printk(KERN_CRIT PFX "Unexpected close, not stopping timer!"); | ||
466 | wdtpci_ping(); | ||
467 | } | ||
468 | expect_close = 0; | ||
469 | up(&open_sem); | ||
470 | return 0; | ||
471 | } | ||
472 | |||
473 | #ifdef CONFIG_WDT_501_PCI | ||
474 | /** | ||
475 | * wdtpci_temp_read: | ||
476 | * @file: file handle to the watchdog board | ||
477 | * @buf: buffer to write 1 byte into | ||
478 | * @count: length of buffer | ||
479 | * @ptr: offset (no seek allowed) | ||
480 | * | ||
481 | * Read reports the temperature in degrees Fahrenheit. The API is in | ||
482 | * fahrenheit. It was designed by an imperial measurement luddite. | ||
483 | */ | ||
484 | |||
485 | static ssize_t wdtpci_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ptr) | ||
486 | { | ||
487 | int temperature; | ||
488 | |||
489 | if (wdtpci_get_temperature(&temperature)) | ||
490 | return -EFAULT; | ||
491 | |||
492 | if (copy_to_user (buf, &temperature, 1)) | ||
493 | return -EFAULT; | ||
494 | |||
495 | return 1; | ||
496 | } | ||
497 | |||
498 | /** | ||
499 | * wdtpci_temp_open: | ||
500 | * @inode: inode of device | ||
501 | * @file: file handle to device | ||
502 | * | ||
503 | * The temperature device has been opened. | ||
504 | */ | ||
505 | |||
506 | static int wdtpci_temp_open(struct inode *inode, struct file *file) | ||
507 | { | ||
508 | return nonseekable_open(inode, file); | ||
509 | } | ||
510 | |||
511 | /** | ||
512 | * wdtpci_temp_release: | ||
513 | * @inode: inode to board | ||
514 | * @file: file handle to board | ||
515 | * | ||
516 | * The temperature device has been closed. | ||
517 | */ | ||
518 | |||
519 | static int wdtpci_temp_release(struct inode *inode, struct file *file) | ||
520 | { | ||
521 | return 0; | ||
522 | } | ||
523 | #endif /* CONFIG_WDT_501_PCI */ | ||
524 | |||
525 | /** | ||
526 | * notify_sys: | ||
527 | * @this: our notifier block | ||
528 | * @code: the event being reported | ||
529 | * @unused: unused | ||
530 | * | ||
531 | * Our notifier is called on system shutdowns. We want to turn the card | ||
532 | * off at reboot otherwise the machine will reboot again during memory | ||
533 | * test or worse yet during the following fsck. This would suck, in fact | ||
534 | * trust me - if it happens it does suck. | ||
535 | */ | ||
536 | |||
537 | static int wdtpci_notify_sys(struct notifier_block *this, unsigned long code, | ||
538 | void *unused) | ||
539 | { | ||
540 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
541 | /* Turn the card off */ | ||
542 | wdtpci_stop(); | ||
543 | } | ||
544 | return NOTIFY_DONE; | ||
545 | } | ||
546 | |||
547 | /* | ||
548 | * Kernel Interfaces | ||
549 | */ | ||
550 | |||
551 | |||
552 | static struct file_operations wdtpci_fops = { | ||
553 | .owner = THIS_MODULE, | ||
554 | .llseek = no_llseek, | ||
555 | .write = wdtpci_write, | ||
556 | .ioctl = wdtpci_ioctl, | ||
557 | .open = wdtpci_open, | ||
558 | .release = wdtpci_release, | ||
559 | }; | ||
560 | |||
561 | static struct miscdevice wdtpci_miscdev = { | ||
562 | .minor = WATCHDOG_MINOR, | ||
563 | .name = "watchdog", | ||
564 | .fops = &wdtpci_fops, | ||
565 | }; | ||
566 | |||
567 | #ifdef CONFIG_WDT_501_PCI | ||
568 | static struct file_operations wdtpci_temp_fops = { | ||
569 | .owner = THIS_MODULE, | ||
570 | .llseek = no_llseek, | ||
571 | .read = wdtpci_temp_read, | ||
572 | .open = wdtpci_temp_open, | ||
573 | .release = wdtpci_temp_release, | ||
574 | }; | ||
575 | |||
576 | static struct miscdevice temp_miscdev = { | ||
577 | .minor = TEMP_MINOR, | ||
578 | .name = "temperature", | ||
579 | .fops = &wdtpci_temp_fops, | ||
580 | }; | ||
581 | #endif /* CONFIG_WDT_501_PCI */ | ||
582 | |||
583 | /* | ||
584 | * The WDT card needs to learn about soft shutdowns in order to | ||
585 | * turn the timebomb registers off. | ||
586 | */ | ||
587 | |||
588 | static struct notifier_block wdtpci_notifier = { | ||
589 | .notifier_call = wdtpci_notify_sys, | ||
590 | }; | ||
591 | |||
592 | |||
593 | static int __devinit wdtpci_init_one (struct pci_dev *dev, | ||
594 | const struct pci_device_id *ent) | ||
595 | { | ||
596 | int ret = -EIO; | ||
597 | |||
598 | dev_count++; | ||
599 | if (dev_count > 1) { | ||
600 | printk (KERN_ERR PFX "this driver only supports 1 device\n"); | ||
601 | return -ENODEV; | ||
602 | } | ||
603 | |||
604 | if (pci_enable_device (dev)) { | ||
605 | printk (KERN_ERR PFX "Not possible to enable PCI Device\n"); | ||
606 | return -ENODEV; | ||
607 | } | ||
608 | |||
609 | if (pci_resource_start (dev, 2) == 0x0000) { | ||
610 | printk (KERN_ERR PFX "No I/O-Address for card detected\n"); | ||
611 | ret = -ENODEV; | ||
612 | goto out_pci; | ||
613 | } | ||
614 | |||
615 | sema_init(&open_sem, 1); | ||
616 | spin_lock_init(&wdtpci_lock); | ||
617 | |||
618 | irq = dev->irq; | ||
619 | io = pci_resource_start (dev, 2); | ||
620 | |||
621 | if (request_region (io, 16, "wdt_pci") == NULL) { | ||
622 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", io); | ||
623 | goto out_pci; | ||
624 | } | ||
625 | |||
626 | if (request_irq (irq, wdtpci_interrupt, SA_INTERRUPT | SA_SHIRQ, | ||
627 | "wdt_pci", &wdtpci_miscdev)) { | ||
628 | printk (KERN_ERR PFX "IRQ %d is not free\n", irq); | ||
629 | goto out_reg; | ||
630 | } | ||
631 | |||
632 | printk ("PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%04x (Interrupt %d)\n", | ||
633 | io, irq); | ||
634 | |||
635 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
636 | if (wdtpci_set_heartbeat(heartbeat)) { | ||
637 | wdtpci_set_heartbeat(WD_TIMO); | ||
638 | printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", | ||
639 | WD_TIMO); | ||
640 | } | ||
641 | |||
642 | ret = register_reboot_notifier (&wdtpci_notifier); | ||
643 | if (ret) { | ||
644 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret); | ||
645 | goto out_irq; | ||
646 | } | ||
647 | |||
648 | #ifdef CONFIG_WDT_501_PCI | ||
649 | ret = misc_register (&temp_miscdev); | ||
650 | if (ret) { | ||
651 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
652 | TEMP_MINOR, ret); | ||
653 | goto out_rbt; | ||
654 | } | ||
655 | #endif /* CONFIG_WDT_501_PCI */ | ||
656 | |||
657 | ret = misc_register (&wdtpci_miscdev); | ||
658 | if (ret) { | ||
659 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
660 | WATCHDOG_MINOR, ret); | ||
661 | goto out_misc; | ||
662 | } | ||
663 | |||
664 | printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
665 | heartbeat, nowayout); | ||
666 | #ifdef CONFIG_WDT_501_PCI | ||
667 | printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled")); | ||
668 | #endif /* CONFIG_WDT_501_PCI */ | ||
669 | |||
670 | ret = 0; | ||
671 | out: | ||
672 | return ret; | ||
673 | |||
674 | out_misc: | ||
675 | #ifdef CONFIG_WDT_501_PCI | ||
676 | misc_deregister(&temp_miscdev); | ||
677 | out_rbt: | ||
678 | #endif /* CONFIG_WDT_501_PCI */ | ||
679 | unregister_reboot_notifier(&wdtpci_notifier); | ||
680 | out_irq: | ||
681 | free_irq(irq, &wdtpci_miscdev); | ||
682 | out_reg: | ||
683 | release_region (io, 16); | ||
684 | out_pci: | ||
685 | pci_disable_device(dev); | ||
686 | goto out; | ||
687 | } | ||
688 | |||
689 | |||
690 | static void __devexit wdtpci_remove_one (struct pci_dev *pdev) | ||
691 | { | ||
692 | /* here we assume only one device will ever have | ||
693 | * been picked up and registered by probe function */ | ||
694 | misc_deregister(&wdtpci_miscdev); | ||
695 | #ifdef CONFIG_WDT_501_PCI | ||
696 | misc_deregister(&temp_miscdev); | ||
697 | #endif /* CONFIG_WDT_501_PCI */ | ||
698 | unregister_reboot_notifier(&wdtpci_notifier); | ||
699 | free_irq(irq, &wdtpci_miscdev); | ||
700 | release_region(io, 16); | ||
701 | pci_disable_device(pdev); | ||
702 | dev_count--; | ||
703 | } | ||
704 | |||
705 | |||
706 | static struct pci_device_id wdtpci_pci_tbl[] = { | ||
707 | { | ||
708 | .vendor = PCI_VENDOR_ID_ACCESSIO, | ||
709 | .device = PCI_DEVICE_ID_WDG_CSM, | ||
710 | .subvendor = PCI_ANY_ID, | ||
711 | .subdevice = PCI_ANY_ID, | ||
712 | }, | ||
713 | { 0, }, /* terminate list */ | ||
714 | }; | ||
715 | MODULE_DEVICE_TABLE(pci, wdtpci_pci_tbl); | ||
716 | |||
717 | |||
718 | static struct pci_driver wdtpci_driver = { | ||
719 | .name = "wdt_pci", | ||
720 | .id_table = wdtpci_pci_tbl, | ||
721 | .probe = wdtpci_init_one, | ||
722 | .remove = __devexit_p(wdtpci_remove_one), | ||
723 | }; | ||
724 | |||
725 | |||
726 | /** | ||
727 | * wdtpci_cleanup: | ||
728 | * | ||
729 | * Unload the watchdog. You cannot do this with any file handles open. | ||
730 | * If your watchdog is set to continue ticking on close and you unload | ||
731 | * it, well it keeps ticking. We won't get the interrupt but the board | ||
732 | * will not touch PC memory so all is fine. You just have to load a new | ||
733 | * module in xx seconds or reboot. | ||
734 | */ | ||
735 | |||
736 | static void __exit wdtpci_cleanup(void) | ||
737 | { | ||
738 | pci_unregister_driver (&wdtpci_driver); | ||
739 | } | ||
740 | |||
741 | |||
742 | /** | ||
743 | * wdtpci_init: | ||
744 | * | ||
745 | * Set up the WDT watchdog board. All we have to do is grab the | ||
746 | * resources we require and bitch if anyone beat us to them. | ||
747 | * The open() function will actually kick the board off. | ||
748 | */ | ||
749 | |||
750 | static int __init wdtpci_init(void) | ||
751 | { | ||
752 | return pci_register_driver (&wdtpci_driver); | ||
753 | } | ||
754 | |||
755 | |||
756 | module_init(wdtpci_init); | ||
757 | module_exit(wdtpci_cleanup); | ||
758 | |||
759 | MODULE_AUTHOR("JP Nollmann, Alan Cox"); | ||
760 | MODULE_DESCRIPTION("Driver for the ICS PCI-WDT500/501 watchdog cards"); | ||
761 | MODULE_LICENSE("GPL"); | ||
762 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
763 | MODULE_ALIAS_MISCDEV(TEMP_MINOR); | ||