diff options
author | Wim Van Sebroeck <wim@iguana.be> | 2007-08-17 04:38:02 -0400 |
---|---|---|
committer | Wim Van Sebroeck <wim@iguana.be> | 2007-10-18 06:39:03 -0400 |
commit | b7e04f8c61a46d742de23af5d7ca2b41b33e40ac (patch) | |
tree | c52a7ea568648e2d8ed0d423098b42298de2058b /drivers/watchdog | |
parent | d85714d81cc0408daddb68c10f7fd69eafe7c213 (diff) |
mv watchdog tree under drivers
move watchdog tree from drivers/char/watchdog to drivers/watchdog.
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Diffstat (limited to 'drivers/watchdog')
62 files changed, 25348 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig new file mode 100644 index 000000000000..37bddc1802de --- /dev/null +++ b/drivers/watchdog/Kconfig | |||
@@ -0,0 +1,853 @@ | |||
1 | # | ||
2 | # Watchdog device configuration | ||
3 | # | ||
4 | |||
5 | menuconfig WATCHDOG | ||
6 | bool "Watchdog Timer Support" | ||
7 | ---help--- | ||
8 | If you say Y here (and to one of the following options) and create a | ||
9 | character special file /dev/watchdog with major number 10 and minor | ||
10 | number 130 using mknod ("man mknod"), you will get a watchdog, i.e.: | ||
11 | subsequently opening the file and then failing to write to it for | ||
12 | longer than 1 minute will result in rebooting the machine. This | ||
13 | could be useful for a networked machine that needs to come back | ||
14 | on-line as fast as possible after a lock-up. There's both a watchdog | ||
15 | implementation entirely in software (which can sometimes fail to | ||
16 | reboot the machine) and a driver for hardware watchdog boards, which | ||
17 | are more robust and can also keep track of the temperature inside | ||
18 | your computer. For details, read <file:Documentation/watchdog/watchdog.txt> | ||
19 | in the kernel source. | ||
20 | |||
21 | The watchdog is usually used together with the watchdog daemon | ||
22 | which is available from | ||
23 | <ftp://ibiblio.org/pub/Linux/system/daemons/watchdog/>. This daemon can | ||
24 | also monitor NFS connections and can reboot the machine when the process | ||
25 | table is full. | ||
26 | |||
27 | If unsure, say N. | ||
28 | |||
29 | if WATCHDOG | ||
30 | |||
31 | config WATCHDOG_NOWAYOUT | ||
32 | bool "Disable watchdog shutdown on close" | ||
33 | help | ||
34 | The default watchdog behaviour (which you get if you say N here) is | ||
35 | to stop the timer if the process managing it closes the file | ||
36 | /dev/watchdog. It's always remotely possible that this process might | ||
37 | get killed. If you say Y here, the watchdog cannot be stopped once | ||
38 | it has been started. | ||
39 | |||
40 | # | ||
41 | # General Watchdog drivers | ||
42 | # | ||
43 | |||
44 | comment "Watchdog Device Drivers" | ||
45 | |||
46 | # Architecture Independent | ||
47 | |||
48 | config SOFT_WATCHDOG | ||
49 | tristate "Software watchdog" | ||
50 | help | ||
51 | A software monitoring watchdog. This will fail to reboot your system | ||
52 | from some situations that the hardware watchdog will recover | ||
53 | from. Equally it's a lot cheaper to install. | ||
54 | |||
55 | To compile this driver as a module, choose M here: the | ||
56 | module will be called softdog. | ||
57 | |||
58 | # ALPHA Architecture | ||
59 | |||
60 | # ARM Architecture | ||
61 | |||
62 | config AT91RM9200_WATCHDOG | ||
63 | tristate "AT91RM9200 watchdog" | ||
64 | depends on ARCH_AT91RM9200 | ||
65 | help | ||
66 | Watchdog timer embedded into AT91RM9200 chips. This will reboot your | ||
67 | system when the timeout is reached. | ||
68 | |||
69 | config 21285_WATCHDOG | ||
70 | tristate "DC21285 watchdog" | ||
71 | depends on FOOTBRIDGE | ||
72 | help | ||
73 | The Intel Footbridge chip contains a built-in watchdog circuit. Say Y | ||
74 | here if you wish to use this. Alternatively say M to compile the | ||
75 | driver as a module, which will be called wdt285. | ||
76 | |||
77 | This driver does not work on all machines. In particular, early CATS | ||
78 | boards have hardware problems that will cause the machine to simply | ||
79 | lock up if the watchdog fires. | ||
80 | |||
81 | "If in doubt, leave it out" - say N. | ||
82 | |||
83 | config 977_WATCHDOG | ||
84 | tristate "NetWinder WB83C977 watchdog" | ||
85 | depends on FOOTBRIDGE && ARCH_NETWINDER | ||
86 | help | ||
87 | Say Y here to include support for the WB977 watchdog included in | ||
88 | NetWinder machines. Alternatively say M to compile the driver as | ||
89 | a module, which will be called wdt977. | ||
90 | |||
91 | Not sure? It's safe to say N. | ||
92 | |||
93 | config IXP2000_WATCHDOG | ||
94 | tristate "IXP2000 Watchdog" | ||
95 | depends on ARCH_IXP2000 | ||
96 | help | ||
97 | Say Y here if to include support for the watchdog timer | ||
98 | in the Intel IXP2000(2400, 2800, 2850) network processors. | ||
99 | This driver can be built as a module by choosing M. The module | ||
100 | will be called ixp2000_wdt. | ||
101 | |||
102 | Say N if you are unsure. | ||
103 | |||
104 | config IXP4XX_WATCHDOG | ||
105 | tristate "IXP4xx Watchdog" | ||
106 | depends on ARCH_IXP4XX | ||
107 | help | ||
108 | Say Y here if to include support for the watchdog timer | ||
109 | in the Intel IXP4xx network processors. This driver can | ||
110 | be built as a module by choosing M. The module will | ||
111 | be called ixp4xx_wdt. | ||
112 | |||
113 | Note: The internal IXP4xx watchdog does a soft CPU reset | ||
114 | which doesn't reset any peripherals. There are circumstances | ||
115 | where the watchdog will fail to reset the board correctly | ||
116 | (e.g., if the boot ROM is in an unreadable state). | ||
117 | |||
118 | Say N if you are unsure. | ||
119 | |||
120 | config KS8695_WATCHDOG | ||
121 | tristate "KS8695 watchdog" | ||
122 | depends on ARCH_KS8695 | ||
123 | help | ||
124 | Watchdog timer embedded into KS8695 processor. This will reboot your | ||
125 | system when the timeout is reached. | ||
126 | |||
127 | config S3C2410_WATCHDOG | ||
128 | tristate "S3C2410 Watchdog" | ||
129 | depends on ARCH_S3C2410 | ||
130 | help | ||
131 | Watchdog timer block in the Samsung S3C2410 chips. This will | ||
132 | reboot the system when the timer expires with the watchdog | ||
133 | enabled. | ||
134 | |||
135 | The driver is limited by the speed of the system's PCLK | ||
136 | signal, so with reasonably fast systems (PCLK around 50-66MHz) | ||
137 | then watchdog intervals of over approximately 20seconds are | ||
138 | unavailable. | ||
139 | |||
140 | The driver can be built as a module by choosing M, and will | ||
141 | be called s3c2410_wdt | ||
142 | |||
143 | config SA1100_WATCHDOG | ||
144 | tristate "SA1100/PXA2xx watchdog" | ||
145 | depends on ARCH_SA1100 || ARCH_PXA | ||
146 | help | ||
147 | Watchdog timer embedded into SA11x0 and PXA2xx chips. This will | ||
148 | reboot your system when timeout is reached. | ||
149 | |||
150 | NOTE: once enabled, this timer cannot be disabled. | ||
151 | |||
152 | To compile this driver as a module, choose M here: the | ||
153 | module will be called sa1100_wdt. | ||
154 | |||
155 | config MPCORE_WATCHDOG | ||
156 | tristate "MPcore watchdog" | ||
157 | depends on ARM_MPCORE_PLATFORM && LOCAL_TIMERS | ||
158 | help | ||
159 | Watchdog timer embedded into the MPcore system. | ||
160 | |||
161 | To compile this driver as a module, choose M here: the | ||
162 | module will be called mpcore_wdt. | ||
163 | |||
164 | config EP93XX_WATCHDOG | ||
165 | tristate "EP93xx Watchdog" | ||
166 | depends on ARCH_EP93XX | ||
167 | help | ||
168 | Say Y here if to include support for the watchdog timer | ||
169 | embedded in the Cirrus Logic EP93xx family of devices. | ||
170 | |||
171 | To compile this driver as a module, choose M here: the | ||
172 | module will be called ep93xx_wdt. | ||
173 | |||
174 | config OMAP_WATCHDOG | ||
175 | tristate "OMAP Watchdog" | ||
176 | depends on ARCH_OMAP16XX || ARCH_OMAP24XX | ||
177 | help | ||
178 | Support for TI OMAP1610/OMAP1710/OMAP2420 watchdog. Say 'Y' here to | ||
179 | enable the OMAP1610/OMAP1710 watchdog timer. | ||
180 | |||
181 | config PNX4008_WATCHDOG | ||
182 | tristate "PNX4008 Watchdog" | ||
183 | depends on ARCH_PNX4008 | ||
184 | help | ||
185 | Say Y here if to include support for the watchdog timer | ||
186 | in the PNX4008 processor. | ||
187 | This driver can be built as a module by choosing M. The module | ||
188 | will be called pnx4008_wdt. | ||
189 | |||
190 | Say N if you are unsure. | ||
191 | |||
192 | config IOP_WATCHDOG | ||
193 | tristate "IOP Watchdog" | ||
194 | depends on PLAT_IOP | ||
195 | select WATCHDOG_NOWAYOUT if (ARCH_IOP32X || ARCH_IOP33X) | ||
196 | help | ||
197 | Say Y here if to include support for the watchdog timer | ||
198 | in the Intel IOP3XX & IOP13XX I/O Processors. This driver can | ||
199 | be built as a module by choosing M. The module will | ||
200 | be called iop_wdt. | ||
201 | |||
202 | Note: The IOP13XX watchdog does an Internal Bus Reset which will | ||
203 | affect both cores and the peripherals of the IOP. The ATU-X | ||
204 | and/or ATUe configuration registers will remain intact, but if | ||
205 | operating as an Root Complex and/or Central Resource, the PCI-X | ||
206 | and/or PCIe busses will also be reset. THIS IS A VERY BIG HAMMER. | ||
207 | |||
208 | config DAVINCI_WATCHDOG | ||
209 | tristate "DaVinci watchdog" | ||
210 | depends on ARCH_DAVINCI | ||
211 | help | ||
212 | Say Y here if to include support for the watchdog timer | ||
213 | in the DaVinci DM644x/DM646x processors. | ||
214 | To compile this driver as a module, choose M here: the | ||
215 | module will be called davinci_wdt. | ||
216 | |||
217 | NOTE: once enabled, this timer cannot be disabled. | ||
218 | Say N if you are unsure. | ||
219 | |||
220 | # ARM26 Architecture | ||
221 | |||
222 | # AVR32 Architecture | ||
223 | |||
224 | config AT32AP700X_WDT | ||
225 | tristate "AT32AP700x watchdog" | ||
226 | depends on CPU_AT32AP7000 | ||
227 | help | ||
228 | Watchdog timer embedded into AT32AP700x devices. This will reboot | ||
229 | your system when the timeout is reached. | ||
230 | |||
231 | # BLACKFIN Architecture | ||
232 | |||
233 | config BFIN_WDT | ||
234 | tristate "Blackfin On-Chip Watchdog Timer" | ||
235 | depends on BLACKFIN | ||
236 | ---help--- | ||
237 | If you say yes here you will get support for the Blackfin On-Chip | ||
238 | Watchdog Timer. If you have one of these processors and wish to | ||
239 | have watchdog support enabled, say Y, otherwise say N. | ||
240 | |||
241 | To compile this driver as a module, choose M here: the | ||
242 | module will be called bfin_wdt. | ||
243 | |||
244 | # CRIS Architecture | ||
245 | |||
246 | # FRV Architecture | ||
247 | |||
248 | # H8300 Architecture | ||
249 | |||
250 | # X86 (i386 + ia64 + x86_64) Architecture | ||
251 | |||
252 | config ACQUIRE_WDT | ||
253 | tristate "Acquire SBC Watchdog Timer" | ||
254 | depends on X86 | ||
255 | ---help--- | ||
256 | This is the driver for the hardware watchdog on Single Board | ||
257 | Computers produced by Acquire Inc (and others). This watchdog | ||
258 | simply watches your kernel to make sure it doesn't freeze, and if | ||
259 | it does, it reboots your computer after a certain amount of time. | ||
260 | |||
261 | To compile this driver as a module, choose M here: the | ||
262 | module will be called acquirewdt. | ||
263 | |||
264 | Most people will say N. | ||
265 | |||
266 | config ADVANTECH_WDT | ||
267 | tristate "Advantech SBC Watchdog Timer" | ||
268 | depends on X86 | ||
269 | help | ||
270 | If you are configuring a Linux kernel for the Advantech single-board | ||
271 | computer, say `Y' here to support its built-in watchdog timer | ||
272 | feature. More information can be found at | ||
273 | <http://www.advantech.com.tw/products/> | ||
274 | |||
275 | config ALIM1535_WDT | ||
276 | tristate "ALi M1535 PMU Watchdog Timer" | ||
277 | depends on X86 && PCI | ||
278 | ---help--- | ||
279 | This is the driver for the hardware watchdog on the ALi M1535 PMU. | ||
280 | |||
281 | To compile this driver as a module, choose M here: the | ||
282 | module will be called alim1535_wdt. | ||
283 | |||
284 | Most people will say N. | ||
285 | |||
286 | config ALIM7101_WDT | ||
287 | tristate "ALi M7101 PMU Computer Watchdog" | ||
288 | depends on X86 && PCI | ||
289 | help | ||
290 | This is the driver for the hardware watchdog on the ALi M7101 PMU | ||
291 | as used in the x86 Cobalt servers. | ||
292 | |||
293 | To compile this driver as a module, choose M here: the | ||
294 | module will be called alim7101_wdt. | ||
295 | |||
296 | Most people will say N. | ||
297 | |||
298 | config SC520_WDT | ||
299 | tristate "AMD Elan SC520 processor Watchdog" | ||
300 | depends on X86 | ||
301 | help | ||
302 | This is the driver for the hardware watchdog built in to the | ||
303 | AMD "Elan" SC520 microcomputer commonly used in embedded systems. | ||
304 | This watchdog simply watches your kernel to make sure it doesn't | ||
305 | freeze, and if it does, it reboots your computer after a certain | ||
306 | amount of time. | ||
307 | |||
308 | You can compile this driver directly into the kernel, or use | ||
309 | it as a module. The module will be called sc520_wdt. | ||
310 | |||
311 | config EUROTECH_WDT | ||
312 | tristate "Eurotech CPU-1220/1410 Watchdog Timer" | ||
313 | depends on X86 | ||
314 | help | ||
315 | Enable support for the watchdog timer on the Eurotech CPU-1220 and | ||
316 | CPU-1410 cards. These are PC/104 SBCs. Spec sheets and product | ||
317 | information are at <http://www.eurotech.it/>. | ||
318 | |||
319 | config IB700_WDT | ||
320 | tristate "IB700 SBC Watchdog Timer" | ||
321 | depends on X86 | ||
322 | ---help--- | ||
323 | This is the driver for the hardware watchdog on the IB700 Single | ||
324 | Board Computer produced by TMC Technology (www.tmc-uk.com). This watchdog | ||
325 | simply watches your kernel to make sure it doesn't freeze, and if | ||
326 | it does, it reboots your computer after a certain amount of time. | ||
327 | |||
328 | This driver is like the WDT501 driver but for slightly different hardware. | ||
329 | |||
330 | To compile this driver as a module, choose M here: the | ||
331 | module will be called ib700wdt. | ||
332 | |||
333 | Most people will say N. | ||
334 | |||
335 | config IBMASR | ||
336 | tristate "IBM Automatic Server Restart" | ||
337 | depends on X86 | ||
338 | help | ||
339 | This is the driver for the IBM Automatic Server Restart watchdog | ||
340 | timer built-in into some eServer xSeries machines. | ||
341 | |||
342 | To compile this driver as a module, choose M here: the | ||
343 | module will be called ibmasr. | ||
344 | |||
345 | config WAFER_WDT | ||
346 | tristate "ICP Wafer 5823 Single Board Computer Watchdog" | ||
347 | depends on X86 | ||
348 | help | ||
349 | This is a driver for the hardware watchdog on the ICP Wafer 5823 | ||
350 | Single Board Computer (and probably other similar models). | ||
351 | |||
352 | To compile this driver as a module, choose M here: the | ||
353 | module will be called wafer5823wdt. | ||
354 | |||
355 | config I6300ESB_WDT | ||
356 | tristate "Intel 6300ESB Timer/Watchdog" | ||
357 | depends on X86 && PCI | ||
358 | ---help--- | ||
359 | Hardware driver for the watchdog timer built into the Intel | ||
360 | 6300ESB controller hub. | ||
361 | |||
362 | To compile this driver as a module, choose M here: the | ||
363 | module will be called i6300esb. | ||
364 | |||
365 | config ITCO_WDT | ||
366 | tristate "Intel TCO Timer/Watchdog" | ||
367 | depends on (X86 || IA64) && PCI | ||
368 | ---help--- | ||
369 | Hardware driver for the intel TCO timer based watchdog devices. | ||
370 | These drivers are included in the Intel 82801 I/O Controller | ||
371 | Hub family (from ICH0 up to ICH8) and in the Intel 6300ESB | ||
372 | controller hub. | ||
373 | |||
374 | The TCO (Total Cost of Ownership) timer is a watchdog timer | ||
375 | that will reboot the machine after its second expiration. The | ||
376 | expiration time can be configured with the "heartbeat" parameter. | ||
377 | |||
378 | On some motherboards the driver may fail to reset the chipset's | ||
379 | NO_REBOOT flag which prevents the watchdog from rebooting the | ||
380 | machine. If this is the case you will get a kernel message like | ||
381 | "failed to reset NO_REBOOT flag, reboot disabled by hardware". | ||
382 | |||
383 | To compile this driver as a module, choose M here: the | ||
384 | module will be called iTCO_wdt. | ||
385 | |||
386 | config ITCO_VENDOR_SUPPORT | ||
387 | bool "Intel TCO Timer/Watchdog Specific Vendor Support" | ||
388 | depends on ITCO_WDT | ||
389 | ---help--- | ||
390 | Add vendor specific support to the intel TCO timer based watchdog | ||
391 | devices. At this moment we only have additional support for some | ||
392 | SuperMicro Inc. motherboards. | ||
393 | |||
394 | config SC1200_WDT | ||
395 | tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog" | ||
396 | depends on X86 | ||
397 | help | ||
398 | This is a driver for National Semiconductor PC87307/PC97307 hardware | ||
399 | watchdog cards as found on the SC1200. This watchdog is mainly used | ||
400 | for power management purposes and can be used to power down the device | ||
401 | during inactivity periods (includes interrupt activity monitoring). | ||
402 | |||
403 | To compile this driver as a module, choose M here: the | ||
404 | module will be called sc1200wdt. | ||
405 | |||
406 | Most people will say N. | ||
407 | |||
408 | config SCx200_WDT | ||
409 | tristate "National Semiconductor SCx200 Watchdog" | ||
410 | depends on SCx200 && PCI | ||
411 | help | ||
412 | Enable the built-in watchdog timer support on the National | ||
413 | Semiconductor SCx200 processors. | ||
414 | |||
415 | If compiled as a module, it will be called scx200_wdt. | ||
416 | |||
417 | config PC87413_WDT | ||
418 | tristate "NS PC87413 watchdog" | ||
419 | depends on X86 | ||
420 | ---help--- | ||
421 | This is the driver for the hardware watchdog on the PC87413 chipset | ||
422 | This watchdog simply watches your kernel to make sure it doesn't | ||
423 | freeze, and if it does, it reboots your computer after a certain | ||
424 | amount of time. | ||
425 | |||
426 | To compile this driver as a module, choose M here: the | ||
427 | module will be called pc87413_wdt. | ||
428 | |||
429 | Most people will say N. | ||
430 | |||
431 | config 60XX_WDT | ||
432 | tristate "SBC-60XX Watchdog Timer" | ||
433 | depends on X86 | ||
434 | help | ||
435 | This driver can be used with the watchdog timer found on some | ||
436 | single board computers, namely the 6010 PII based computer. | ||
437 | It may well work with other cards. It reads port 0x443 to enable | ||
438 | and re-set the watchdog timer, and reads port 0x45 to disable | ||
439 | the watchdog. If you have a card that behave in similar ways, | ||
440 | you can probably make this driver work with your card as well. | ||
441 | |||
442 | You can compile this driver directly into the kernel, or use | ||
443 | it as a module. The module will be called sbc60xxwdt. | ||
444 | |||
445 | config SBC8360_WDT | ||
446 | tristate "SBC8360 Watchdog Timer" | ||
447 | depends on X86 | ||
448 | ---help--- | ||
449 | |||
450 | This is the driver for the hardware watchdog on the SBC8360 Single | ||
451 | Board Computer produced by Axiomtek Co., Ltd. (www.axiomtek.com). | ||
452 | |||
453 | To compile this driver as a module, choose M here: the | ||
454 | module will be called sbc8360.ko. | ||
455 | |||
456 | Most people will say N. | ||
457 | |||
458 | config CPU5_WDT | ||
459 | tristate "SMA CPU5 Watchdog" | ||
460 | depends on X86 | ||
461 | ---help--- | ||
462 | TBD. | ||
463 | To compile this driver as a module, choose M here: the | ||
464 | module will be called cpu5wdt. | ||
465 | |||
466 | config SMSC37B787_WDT | ||
467 | tristate "Winbond SMsC37B787 Watchdog Timer" | ||
468 | depends on X86 | ||
469 | ---help--- | ||
470 | This is the driver for the hardware watchdog component on the | ||
471 | Winbond SMsC37B787 chipset as used on the NetRunner Mainboard | ||
472 | from Vision Systems and maybe others. | ||
473 | |||
474 | This watchdog simply watches your kernel to make sure it doesn't | ||
475 | freeze, and if it does, it reboots your computer after a certain | ||
476 | amount of time. | ||
477 | |||
478 | Usually a userspace daemon will notify the kernel WDT driver that | ||
479 | userspace is still alive, at regular intervals. | ||
480 | |||
481 | To compile this driver as a module, choose M here: the | ||
482 | module will be called smsc37b787_wdt. | ||
483 | |||
484 | Most people will say N. | ||
485 | |||
486 | config W83627HF_WDT | ||
487 | tristate "W83627HF Watchdog Timer" | ||
488 | depends on X86 | ||
489 | ---help--- | ||
490 | This is the driver for the hardware watchdog on the W83627HF chipset | ||
491 | as used in Advantech PC-9578 and Tyan S2721-533 motherboards | ||
492 | (and likely others). This watchdog simply watches your kernel to | ||
493 | make sure it doesn't freeze, and if it does, it reboots your computer | ||
494 | after a certain amount of time. | ||
495 | |||
496 | To compile this driver as a module, choose M here: the | ||
497 | module will be called w83627hf_wdt. | ||
498 | |||
499 | Most people will say N. | ||
500 | |||
501 | config W83697HF_WDT | ||
502 | tristate "W83697HF/W83697HG Watchdog Timer" | ||
503 | depends on X86 | ||
504 | ---help--- | ||
505 | This is the driver for the hardware watchdog on the W83697HF/HG | ||
506 | chipset as used in Dedibox/VIA motherboards (and likely others). | ||
507 | This watchdog simply watches your kernel to make sure it doesn't | ||
508 | freeze, and if it does, it reboots your computer after a certain | ||
509 | amount of time. | ||
510 | |||
511 | To compile this driver as a module, choose M here: the | ||
512 | module will be called w83697hf_wdt. | ||
513 | |||
514 | Most people will say N. | ||
515 | |||
516 | config W83877F_WDT | ||
517 | tristate "W83877F (EMACS) Watchdog Timer" | ||
518 | depends on X86 | ||
519 | ---help--- | ||
520 | This is the driver for the hardware watchdog on the W83877F chipset | ||
521 | as used in EMACS PC-104 motherboards (and likely others). This | ||
522 | watchdog simply watches your kernel to make sure it doesn't freeze, | ||
523 | and if it does, it reboots your computer after a certain amount of | ||
524 | time. | ||
525 | |||
526 | To compile this driver as a module, choose M here: the | ||
527 | module will be called w83877f_wdt. | ||
528 | |||
529 | Most people will say N. | ||
530 | |||
531 | config W83977F_WDT | ||
532 | tristate "W83977F (PCM-5335) Watchdog Timer" | ||
533 | depends on X86 | ||
534 | ---help--- | ||
535 | This is the driver for the hardware watchdog on the W83977F I/O chip | ||
536 | as used in AAEON's PCM-5335 SBC (and likely others). This | ||
537 | watchdog simply watches your kernel to make sure it doesn't freeze, | ||
538 | and if it does, it reboots your computer after a certain amount of | ||
539 | time. | ||
540 | |||
541 | To compile this driver as a module, choose M here: the | ||
542 | module will be called w83977f_wdt. | ||
543 | |||
544 | config MACHZ_WDT | ||
545 | tristate "ZF MachZ Watchdog" | ||
546 | depends on X86 | ||
547 | ---help--- | ||
548 | If you are using a ZF Micro MachZ processor, say Y here, otherwise | ||
549 | N. This is the driver for the watchdog timer built-in on that | ||
550 | processor using ZF-Logic interface. This watchdog simply watches | ||
551 | your kernel to make sure it doesn't freeze, and if it does, it | ||
552 | reboots your computer after a certain amount of time. | ||
553 | |||
554 | To compile this driver as a module, choose M here: the | ||
555 | module will be called machzwd. | ||
556 | |||
557 | config SBC_EPX_C3_WATCHDOG | ||
558 | tristate "Winsystems SBC EPX-C3 watchdog" | ||
559 | depends on X86 | ||
560 | ---help--- | ||
561 | This is the driver for the built-in watchdog timer on the EPX-C3 | ||
562 | Single-board computer made by Winsystems, Inc. | ||
563 | |||
564 | *Note*: This hardware watchdog is not probeable and thus there | ||
565 | is no way to know if writing to its IO address will corrupt | ||
566 | your system or have any real effect. The only way to be sure | ||
567 | that this driver does what you want is to make sure you | ||
568 | are running it on an EPX-C3 from Winsystems with the watchdog | ||
569 | timer at IO address 0x1ee and 0x1ef. It will write to both those | ||
570 | IO ports. Basically, the assumption is made that if you compile | ||
571 | this driver into your kernel and/or load it as a module, that you | ||
572 | know what you are doing and that you are in fact running on an | ||
573 | EPX-C3 board! | ||
574 | |||
575 | To compile this driver as a module, choose M here: the | ||
576 | module will be called sbc_epx_c3. | ||
577 | |||
578 | # M32R Architecture | ||
579 | |||
580 | # M68K Architecture | ||
581 | |||
582 | # M68KNOMMU Architecture | ||
583 | |||
584 | # MIPS Architecture | ||
585 | |||
586 | config INDYDOG | ||
587 | tristate "Indy/I2 Hardware Watchdog" | ||
588 | depends on SGI_IP22 | ||
589 | help | ||
590 | Hardware driver for the Indy's/I2's watchdog. This is a | ||
591 | watchdog timer that will reboot the machine after a 60 second | ||
592 | timer expired and no process has written to /dev/watchdog during | ||
593 | that time. | ||
594 | |||
595 | config WDT_MTX1 | ||
596 | tristate "MTX-1 Hardware Watchdog" | ||
597 | depends on MIPS_MTX1 | ||
598 | help | ||
599 | Hardware driver for the MTX-1 boards. This is a watchdog timer that | ||
600 | will reboot the machine after a 100 seconds timer expired. | ||
601 | |||
602 | config WDT_RM9K_GPI | ||
603 | tristate "RM9000/GPI hardware watchdog" | ||
604 | depends on CPU_RM9000 | ||
605 | help | ||
606 | Watchdog implementation using the GPI hardware found on | ||
607 | PMC-Sierra RM9xxx CPUs. | ||
608 | |||
609 | To compile this driver as a module, choose M here: the | ||
610 | module will be called rm9k_wdt. | ||
611 | |||
612 | # PARISC Architecture | ||
613 | |||
614 | # POWERPC Architecture | ||
615 | |||
616 | config MPC5200_WDT | ||
617 | tristate "MPC5200 Watchdog Timer" | ||
618 | depends on PPC_MPC52xx | ||
619 | |||
620 | config 8xx_WDT | ||
621 | tristate "MPC8xx Watchdog Timer" | ||
622 | depends on 8xx | ||
623 | |||
624 | config 83xx_WDT | ||
625 | tristate "MPC83xx Watchdog Timer" | ||
626 | depends on PPC_83xx | ||
627 | |||
628 | config MV64X60_WDT | ||
629 | tristate "MV64X60 (Marvell Discovery) Watchdog Timer" | ||
630 | depends on MV64X60 | ||
631 | |||
632 | config BOOKE_WDT | ||
633 | bool "PowerPC Book-E Watchdog Timer" | ||
634 | depends on BOOKE || 4xx | ||
635 | ---help--- | ||
636 | Please see Documentation/watchdog/watchdog-api.txt for | ||
637 | more information. | ||
638 | |||
639 | # PPC64 Architecture | ||
640 | |||
641 | config WATCHDOG_RTAS | ||
642 | tristate "RTAS watchdog" | ||
643 | depends on PPC_RTAS | ||
644 | help | ||
645 | This driver adds watchdog support for the RTAS watchdog. | ||
646 | |||
647 | To compile this driver as a module, choose M here. The module | ||
648 | will be called wdrtas. | ||
649 | |||
650 | # S390 Architecture | ||
651 | |||
652 | config ZVM_WATCHDOG | ||
653 | tristate "z/VM Watchdog Timer" | ||
654 | depends on S390 | ||
655 | help | ||
656 | IBM s/390 and zSeries machines running under z/VM 5.1 or later | ||
657 | provide a virtual watchdog timer to their guest that cause a | ||
658 | user define Control Program command to be executed after a | ||
659 | timeout. | ||
660 | |||
661 | To compile this driver as a module, choose M here. The module | ||
662 | will be called vmwatchdog. | ||
663 | |||
664 | # SUPERH (sh + sh64) Architecture | ||
665 | |||
666 | config SH_WDT | ||
667 | tristate "SuperH Watchdog" | ||
668 | depends on SUPERH && (CPU_SH3 || CPU_SH4) | ||
669 | help | ||
670 | This driver adds watchdog support for the integrated watchdog in the | ||
671 | SuperH processors. If you have one of these processors and wish | ||
672 | to have watchdog support enabled, say Y, otherwise say N. | ||
673 | |||
674 | As a side note, saying Y here will automatically boost HZ to 1000 | ||
675 | so that the timer has a chance to clear the overflow counter. On | ||
676 | slower systems (such as the SH-2 and SH-3) this will likely yield | ||
677 | some performance issues. As such, the WDT should be avoided here | ||
678 | unless it is absolutely necessary. | ||
679 | |||
680 | To compile this driver as a module, choose M here: the | ||
681 | module will be called shwdt. | ||
682 | |||
683 | config SH_WDT_MMAP | ||
684 | bool "Allow mmap of SH WDT" | ||
685 | default n | ||
686 | depends on SH_WDT | ||
687 | help | ||
688 | If you say Y here, user applications will be able to mmap the | ||
689 | WDT/CPG registers. | ||
690 | |||
691 | # SPARC Architecture | ||
692 | |||
693 | # SPARC64 Architecture | ||
694 | |||
695 | config WATCHDOG_CP1XXX | ||
696 | tristate "CP1XXX Hardware Watchdog support" | ||
697 | depends on SPARC64 && PCI | ||
698 | ---help--- | ||
699 | This is the driver for the hardware watchdog timers present on | ||
700 | Sun Microsystems CompactPCI models CP1400 and CP1500. | ||
701 | |||
702 | To compile this driver as a module, choose M here: the | ||
703 | module will be called cpwatchdog. | ||
704 | |||
705 | If you do not have a CompactPCI model CP1400 or CP1500, or | ||
706 | another UltraSPARC-IIi-cEngine boardset with hardware watchdog, | ||
707 | you should say N to this option. | ||
708 | |||
709 | config WATCHDOG_RIO | ||
710 | tristate "RIO Hardware Watchdog support" | ||
711 | depends on SPARC64 && PCI | ||
712 | help | ||
713 | Say Y here to support the hardware watchdog capability on Sun RIO | ||
714 | machines. The watchdog timeout period is normally one minute but | ||
715 | can be changed with a boot-time parameter. | ||
716 | |||
717 | # V850 Architecture | ||
718 | |||
719 | # XTENSA Architecture | ||
720 | |||
721 | # | ||
722 | # ISA-based Watchdog Cards | ||
723 | # | ||
724 | |||
725 | comment "ISA-based Watchdog Cards" | ||
726 | depends on ISA | ||
727 | |||
728 | config PCWATCHDOG | ||
729 | tristate "Berkshire Products ISA-PC Watchdog" | ||
730 | depends on ISA | ||
731 | ---help--- | ||
732 | This is the driver for the Berkshire Products ISA-PC Watchdog card. | ||
733 | This card simply watches your kernel to make sure it doesn't freeze, | ||
734 | and if it does, it reboots your computer after a certain amount of | ||
735 | time. This driver is like the WDT501 driver but for different | ||
736 | hardware. Please read <file:Documentation/watchdog/pcwd-watchdog.txt>. The PC | ||
737 | watchdog cards can be ordered from <http://www.berkprod.com/>. | ||
738 | |||
739 | To compile this driver as a module, choose M here: the | ||
740 | module will be called pcwd. | ||
741 | |||
742 | Most people will say N. | ||
743 | |||
744 | config MIXCOMWD | ||
745 | tristate "Mixcom Watchdog" | ||
746 | depends on ISA | ||
747 | ---help--- | ||
748 | This is a driver for the Mixcom hardware watchdog cards. This | ||
749 | watchdog simply watches your kernel to make sure it doesn't freeze, | ||
750 | and if it does, it reboots your computer after a certain amount of | ||
751 | time. | ||
752 | |||
753 | To compile this driver as a module, choose M here: the | ||
754 | module will be called mixcomwd. | ||
755 | |||
756 | Most people will say N. | ||
757 | |||
758 | config WDT | ||
759 | tristate "WDT Watchdog timer" | ||
760 | depends on ISA | ||
761 | ---help--- | ||
762 | If you have a WDT500P or WDT501P watchdog board, say Y here, | ||
763 | otherwise N. It is not possible to probe for this board, which means | ||
764 | that you have to inform the kernel about the IO port and IRQ that | ||
765 | is needed (you can do this via the io and irq parameters) | ||
766 | |||
767 | To compile this driver as a module, choose M here: the | ||
768 | module will be called wdt. | ||
769 | |||
770 | config WDT_501 | ||
771 | bool "WDT501 features" | ||
772 | depends on WDT | ||
773 | help | ||
774 | Saying Y here and creating a character special file /dev/temperature | ||
775 | with major number 10 and minor number 131 ("man mknod") will give | ||
776 | you a thermometer inside your computer: reading from | ||
777 | /dev/temperature yields one byte, the temperature in degrees | ||
778 | Fahrenheit. This works only if you have a WDT501P watchdog board | ||
779 | installed. | ||
780 | |||
781 | If you want to enable the Fan Tachometer on the WDT501P, then you | ||
782 | can do this via the tachometer parameter. Only do this if you have a | ||
783 | fan tachometer actually set up. | ||
784 | |||
785 | # | ||
786 | # PCI-based Watchdog Cards | ||
787 | # | ||
788 | |||
789 | comment "PCI-based Watchdog Cards" | ||
790 | depends on PCI | ||
791 | |||
792 | config PCIPCWATCHDOG | ||
793 | tristate "Berkshire Products PCI-PC Watchdog" | ||
794 | depends on PCI | ||
795 | ---help--- | ||
796 | This is the driver for the Berkshire Products PCI-PC Watchdog card. | ||
797 | This card simply watches your kernel to make sure it doesn't freeze, | ||
798 | and if it does, it reboots your computer after a certain amount of | ||
799 | time. The card can also monitor the internal temperature of the PC. | ||
800 | More info is available at <http://www.berkprod.com/pci_pc_watchdog.htm>. | ||
801 | |||
802 | To compile this driver as a module, choose M here: the | ||
803 | module will be called pcwd_pci. | ||
804 | |||
805 | Most people will say N. | ||
806 | |||
807 | config WDTPCI | ||
808 | tristate "PCI-WDT500/501 Watchdog timer" | ||
809 | depends on PCI | ||
810 | ---help--- | ||
811 | If you have a PCI-WDT500/501 watchdog board, say Y here, otherwise N. | ||
812 | |||
813 | To compile this driver as a module, choose M here: the | ||
814 | module will be called wdt_pci. | ||
815 | |||
816 | config WDT_501_PCI | ||
817 | bool "PCI-WDT501 features" | ||
818 | depends on WDTPCI | ||
819 | help | ||
820 | Saying Y here and creating a character special file /dev/temperature | ||
821 | with major number 10 and minor number 131 ("man mknod") will give | ||
822 | you a thermometer inside your computer: reading from | ||
823 | /dev/temperature yields one byte, the temperature in degrees | ||
824 | Fahrenheit. This works only if you have a PCI-WDT501 watchdog board | ||
825 | installed. | ||
826 | |||
827 | If you want to enable the Fan Tachometer on the PCI-WDT501, then you | ||
828 | can do this via the tachometer parameter. Only do this if you have a | ||
829 | fan tachometer actually set up. | ||
830 | |||
831 | # | ||
832 | # USB-based Watchdog Cards | ||
833 | # | ||
834 | |||
835 | comment "USB-based Watchdog Cards" | ||
836 | depends on USB | ||
837 | |||
838 | config USBPCWATCHDOG | ||
839 | tristate "Berkshire Products USB-PC Watchdog" | ||
840 | depends on USB | ||
841 | ---help--- | ||
842 | This is the driver for the Berkshire Products USB-PC Watchdog card. | ||
843 | This card simply watches your kernel to make sure it doesn't freeze, | ||
844 | and if it does, it reboots your computer after a certain amount of | ||
845 | time. The card can also monitor the internal temperature of the PC. | ||
846 | More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>. | ||
847 | |||
848 | To compile this driver as a module, choose M here: the | ||
849 | module will be called pcwd_usb. | ||
850 | |||
851 | Most people will say N. | ||
852 | |||
853 | endif # WATCHDOG | ||
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile new file mode 100644 index 000000000000..389f8b14ccc4 --- /dev/null +++ b/drivers/watchdog/Makefile | |||
@@ -0,0 +1,120 @@ | |||
1 | # | ||
2 | # Makefile for the WatchDog device drivers. | ||
3 | # | ||
4 | |||
5 | # Only one watchdog can succeed. We probe the ISA/PCI/USB based | ||
6 | # watchdog-cards first, then the architecture specific watchdog | ||
7 | # drivers and then the architecture independant "softdog" driver. | ||
8 | # This means that if your ISA/PCI/USB card isn't detected that | ||
9 | # you can fall back to an architecture specific driver and if | ||
10 | # that also fails then you can fall back to the software watchdog | ||
11 | # to give you some cover. | ||
12 | |||
13 | # ISA-based Watchdog Cards | ||
14 | obj-$(CONFIG_PCWATCHDOG) += pcwd.o | ||
15 | obj-$(CONFIG_MIXCOMWD) += mixcomwd.o | ||
16 | obj-$(CONFIG_WDT) += wdt.o | ||
17 | |||
18 | # PCI-based Watchdog Cards | ||
19 | obj-$(CONFIG_PCIPCWATCHDOG) += pcwd_pci.o | ||
20 | obj-$(CONFIG_WDTPCI) += wdt_pci.o | ||
21 | |||
22 | # USB-based Watchdog Cards | ||
23 | obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o | ||
24 | |||
25 | # ALPHA Architecture | ||
26 | |||
27 | # ARM Architecture | ||
28 | obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o | ||
29 | obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o | ||
30 | obj-$(CONFIG_21285_WATCHDOG) += wdt285.o | ||
31 | obj-$(CONFIG_977_WATCHDOG) += wdt977.o | ||
32 | obj-$(CONFIG_IXP2000_WATCHDOG) += ixp2000_wdt.o | ||
33 | obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o | ||
34 | obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o | ||
35 | obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o | ||
36 | obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o | ||
37 | obj-$(CONFIG_MPCORE_WATCHDOG) += mpcore_wdt.o | ||
38 | obj-$(CONFIG_EP93XX_WATCHDOG) += ep93xx_wdt.o | ||
39 | obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o | ||
40 | obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o | ||
41 | obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o | ||
42 | |||
43 | # ARM26 Architecture | ||
44 | |||
45 | # AVR32 Architecture | ||
46 | obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o | ||
47 | |||
48 | # BLACKFIN Architecture | ||
49 | obj-$(CONFIG_BFIN_WDT) += bfin_wdt.o | ||
50 | |||
51 | # CRIS Architecture | ||
52 | |||
53 | # FRV Architecture | ||
54 | |||
55 | # H8300 Architecture | ||
56 | |||
57 | # X86 (i386 + ia64 + x86_64) Architecture | ||
58 | obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o | ||
59 | obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o | ||
60 | obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o | ||
61 | obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o | ||
62 | obj-$(CONFIG_SC520_WDT) += sc520_wdt.o | ||
63 | obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o | ||
64 | obj-$(CONFIG_IB700_WDT) += ib700wdt.o | ||
65 | obj-$(CONFIG_IBMASR) += ibmasr.o | ||
66 | obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o | ||
67 | obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o | ||
68 | obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o iTCO_vendor_support.o | ||
69 | obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o | ||
70 | obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o | ||
71 | obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o | ||
72 | obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o | ||
73 | obj-$(CONFIG_SBC8360_WDT) += sbc8360.o | ||
74 | obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o | ||
75 | obj-$(CONFIG_SMSC37B787_WDT) += smsc37b787_wdt.o | ||
76 | obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o | ||
77 | obj-$(CONFIG_W83697HF_WDT) += w83697hf_wdt.o | ||
78 | obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o | ||
79 | obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o | ||
80 | obj-$(CONFIG_MACHZ_WDT) += machzwd.o | ||
81 | obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o | ||
82 | |||
83 | # M32R Architecture | ||
84 | |||
85 | # M68K Architecture | ||
86 | |||
87 | # M68KNOMMU Architecture | ||
88 | |||
89 | # MIPS Architecture | ||
90 | obj-$(CONFIG_INDYDOG) += indydog.o | ||
91 | obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o | ||
92 | obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o | ||
93 | |||
94 | # PARISC Architecture | ||
95 | |||
96 | # POWERPC Architecture | ||
97 | obj-$(CONFIG_8xx_WDT) += mpc8xx_wdt.o | ||
98 | obj-$(CONFIG_MPC5200_WDT) += mpc5200_wdt.o | ||
99 | obj-$(CONFIG_83xx_WDT) += mpc83xx_wdt.o | ||
100 | obj-$(CONFIG_MV64X60_WDT) += mv64x60_wdt.o | ||
101 | obj-$(CONFIG_BOOKE_WDT) += booke_wdt.o | ||
102 | |||
103 | # PPC64 Architecture | ||
104 | obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o | ||
105 | |||
106 | # S390 Architecture | ||
107 | |||
108 | # SUPERH (sh + sh64) Architecture | ||
109 | obj-$(CONFIG_SH_WDT) += shwdt.o | ||
110 | |||
111 | # SPARC Architecture | ||
112 | |||
113 | # SPARC64 Architecture | ||
114 | |||
115 | # V850 Architecture | ||
116 | |||
117 | # XTENSA Architecture | ||
118 | |||
119 | # Architecture Independant | ||
120 | obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o | ||
diff --git a/drivers/watchdog/acquirewdt.c b/drivers/watchdog/acquirewdt.c new file mode 100644 index 000000000000..85269c365a10 --- /dev/null +++ b/drivers/watchdog/acquirewdt.c | |||
@@ -0,0 +1,348 @@ | |||
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 | /* | ||
52 | * Includes, defines, variables, module parameters, ... | ||
53 | */ | ||
54 | |||
55 | /* Includes */ | ||
56 | #include <linux/module.h> /* For module specific items */ | ||
57 | #include <linux/moduleparam.h> /* For new moduleparam's */ | ||
58 | #include <linux/types.h> /* For standard types (like size_t) */ | ||
59 | #include <linux/errno.h> /* For the -ENODEV/... values */ | ||
60 | #include <linux/kernel.h> /* For printk/panic/... */ | ||
61 | #include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ | ||
62 | #include <linux/watchdog.h> /* For the watchdog specific items */ | ||
63 | #include <linux/fs.h> /* For file operations */ | ||
64 | #include <linux/ioport.h> /* For io-port access */ | ||
65 | #include <linux/platform_device.h> /* For platform_driver framework */ | ||
66 | #include <linux/init.h> /* For __init/__exit/... */ | ||
67 | |||
68 | #include <asm/uaccess.h> /* For copy_to_user/put_user/... */ | ||
69 | #include <asm/io.h> /* For inb/outb/... */ | ||
70 | |||
71 | /* Module information */ | ||
72 | #define DRV_NAME "acquirewdt" | ||
73 | #define PFX DRV_NAME ": " | ||
74 | #define WATCHDOG_NAME "Acquire WDT" | ||
75 | #define WATCHDOG_HEARTBEAT 0 /* There is no way to see what the correct time-out period is */ | ||
76 | |||
77 | /* internal variables */ | ||
78 | static struct platform_device *acq_platform_device; /* the watchdog platform device */ | ||
79 | static unsigned long acq_is_open; | ||
80 | static char expect_close; | ||
81 | |||
82 | /* module parameters */ | ||
83 | static int wdt_stop = 0x43; /* You must set this - there is no sane way to probe for this board. */ | ||
84 | module_param(wdt_stop, int, 0); | ||
85 | MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)"); | ||
86 | |||
87 | static int wdt_start = 0x443; /* You must set this - there is no sane way to probe for this board. */ | ||
88 | module_param(wdt_start, int, 0); | ||
89 | MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)"); | ||
90 | |||
91 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
92 | module_param(nowayout, int, 0); | ||
93 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
94 | |||
95 | /* | ||
96 | * Watchdog Operations | ||
97 | */ | ||
98 | |||
99 | static void acq_keepalive(void) | ||
100 | { | ||
101 | /* Write a watchdog value */ | ||
102 | inb_p(wdt_start); | ||
103 | } | ||
104 | |||
105 | static void acq_stop(void) | ||
106 | { | ||
107 | /* Turn the card off */ | ||
108 | inb_p(wdt_stop); | ||
109 | } | ||
110 | |||
111 | /* | ||
112 | * /dev/watchdog handling | ||
113 | */ | ||
114 | |||
115 | static ssize_t acq_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
116 | { | ||
117 | /* See if we got the magic character 'V' and reload the timer */ | ||
118 | if(count) { | ||
119 | if (!nowayout) { | ||
120 | size_t i; | ||
121 | |||
122 | /* note: just in case someone wrote the magic character | ||
123 | * five months ago... */ | ||
124 | expect_close = 0; | ||
125 | |||
126 | /* scan to see whether or not we got the magic character */ | ||
127 | for (i = 0; i != count; i++) { | ||
128 | char c; | ||
129 | if (get_user(c, buf + i)) | ||
130 | return -EFAULT; | ||
131 | if (c == 'V') | ||
132 | expect_close = 42; | ||
133 | } | ||
134 | } | ||
135 | |||
136 | /* Well, anyhow someone wrote to us, we should return that favour */ | ||
137 | acq_keepalive(); | ||
138 | } | ||
139 | return count; | ||
140 | } | ||
141 | |||
142 | static int acq_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
143 | unsigned long arg) | ||
144 | { | ||
145 | int options, retval = -EINVAL; | ||
146 | void __user *argp = (void __user *)arg; | ||
147 | int __user *p = argp; | ||
148 | static struct watchdog_info ident = | ||
149 | { | ||
150 | .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | ||
151 | .firmware_version = 1, | ||
152 | .identity = WATCHDOG_NAME, | ||
153 | }; | ||
154 | |||
155 | switch(cmd) | ||
156 | { | ||
157 | case WDIOC_GETSUPPORT: | ||
158 | return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; | ||
159 | |||
160 | case WDIOC_GETSTATUS: | ||
161 | case WDIOC_GETBOOTSTATUS: | ||
162 | return put_user(0, p); | ||
163 | |||
164 | case WDIOC_KEEPALIVE: | ||
165 | acq_keepalive(); | ||
166 | return 0; | ||
167 | |||
168 | case WDIOC_GETTIMEOUT: | ||
169 | return put_user(WATCHDOG_HEARTBEAT, p); | ||
170 | |||
171 | case WDIOC_SETOPTIONS: | ||
172 | { | ||
173 | if (get_user(options, p)) | ||
174 | return -EFAULT; | ||
175 | |||
176 | if (options & WDIOS_DISABLECARD) | ||
177 | { | ||
178 | acq_stop(); | ||
179 | retval = 0; | ||
180 | } | ||
181 | |||
182 | if (options & WDIOS_ENABLECARD) | ||
183 | { | ||
184 | acq_keepalive(); | ||
185 | retval = 0; | ||
186 | } | ||
187 | |||
188 | return retval; | ||
189 | } | ||
190 | |||
191 | default: | ||
192 | return -ENOTTY; | ||
193 | } | ||
194 | } | ||
195 | |||
196 | static int acq_open(struct inode *inode, struct file *file) | ||
197 | { | ||
198 | if (test_and_set_bit(0, &acq_is_open)) | ||
199 | return -EBUSY; | ||
200 | |||
201 | if (nowayout) | ||
202 | __module_get(THIS_MODULE); | ||
203 | |||
204 | /* Activate */ | ||
205 | acq_keepalive(); | ||
206 | return nonseekable_open(inode, file); | ||
207 | } | ||
208 | |||
209 | static int acq_close(struct inode *inode, struct file *file) | ||
210 | { | ||
211 | if (expect_close == 42) { | ||
212 | acq_stop(); | ||
213 | } else { | ||
214 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
215 | acq_keepalive(); | ||
216 | } | ||
217 | clear_bit(0, &acq_is_open); | ||
218 | expect_close = 0; | ||
219 | return 0; | ||
220 | } | ||
221 | |||
222 | /* | ||
223 | * Kernel Interfaces | ||
224 | */ | ||
225 | |||
226 | static const struct file_operations acq_fops = { | ||
227 | .owner = THIS_MODULE, | ||
228 | .llseek = no_llseek, | ||
229 | .write = acq_write, | ||
230 | .ioctl = acq_ioctl, | ||
231 | .open = acq_open, | ||
232 | .release = acq_close, | ||
233 | }; | ||
234 | |||
235 | static struct miscdevice acq_miscdev = { | ||
236 | .minor = WATCHDOG_MINOR, | ||
237 | .name = "watchdog", | ||
238 | .fops = &acq_fops, | ||
239 | }; | ||
240 | |||
241 | /* | ||
242 | * Init & exit routines | ||
243 | */ | ||
244 | |||
245 | static int __devinit acq_probe(struct platform_device *dev) | ||
246 | { | ||
247 | int ret; | ||
248 | |||
249 | if (wdt_stop != wdt_start) { | ||
250 | if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) { | ||
251 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
252 | wdt_stop); | ||
253 | ret = -EIO; | ||
254 | goto out; | ||
255 | } | ||
256 | } | ||
257 | |||
258 | if (!request_region(wdt_start, 1, WATCHDOG_NAME)) { | ||
259 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
260 | wdt_start); | ||
261 | ret = -EIO; | ||
262 | goto unreg_stop; | ||
263 | } | ||
264 | |||
265 | ret = misc_register(&acq_miscdev); | ||
266 | if (ret != 0) { | ||
267 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
268 | WATCHDOG_MINOR, ret); | ||
269 | goto unreg_regions; | ||
270 | } | ||
271 | |||
272 | printk (KERN_INFO PFX "initialized. (nowayout=%d)\n", | ||
273 | nowayout); | ||
274 | |||
275 | return 0; | ||
276 | |||
277 | unreg_regions: | ||
278 | release_region(wdt_start, 1); | ||
279 | unreg_stop: | ||
280 | if (wdt_stop != wdt_start) | ||
281 | release_region(wdt_stop, 1); | ||
282 | out: | ||
283 | return ret; | ||
284 | } | ||
285 | |||
286 | static int __devexit acq_remove(struct platform_device *dev) | ||
287 | { | ||
288 | misc_deregister(&acq_miscdev); | ||
289 | release_region(wdt_start,1); | ||
290 | if(wdt_stop != wdt_start) | ||
291 | release_region(wdt_stop,1); | ||
292 | |||
293 | return 0; | ||
294 | } | ||
295 | |||
296 | static void acq_shutdown(struct platform_device *dev) | ||
297 | { | ||
298 | /* Turn the WDT off if we have a soft shutdown */ | ||
299 | acq_stop(); | ||
300 | } | ||
301 | |||
302 | static struct platform_driver acquirewdt_driver = { | ||
303 | .probe = acq_probe, | ||
304 | .remove = __devexit_p(acq_remove), | ||
305 | .shutdown = acq_shutdown, | ||
306 | .driver = { | ||
307 | .owner = THIS_MODULE, | ||
308 | .name = DRV_NAME, | ||
309 | }, | ||
310 | }; | ||
311 | |||
312 | static int __init acq_init(void) | ||
313 | { | ||
314 | int err; | ||
315 | |||
316 | printk(KERN_INFO "WDT driver for Acquire single board computer initialising.\n"); | ||
317 | |||
318 | err = platform_driver_register(&acquirewdt_driver); | ||
319 | if (err) | ||
320 | return err; | ||
321 | |||
322 | acq_platform_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0); | ||
323 | if (IS_ERR(acq_platform_device)) { | ||
324 | err = PTR_ERR(acq_platform_device); | ||
325 | goto unreg_platform_driver; | ||
326 | } | ||
327 | |||
328 | return 0; | ||
329 | |||
330 | unreg_platform_driver: | ||
331 | platform_driver_unregister(&acquirewdt_driver); | ||
332 | return err; | ||
333 | } | ||
334 | |||
335 | static void __exit acq_exit(void) | ||
336 | { | ||
337 | platform_device_unregister(acq_platform_device); | ||
338 | platform_driver_unregister(&acquirewdt_driver); | ||
339 | printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); | ||
340 | } | ||
341 | |||
342 | module_init(acq_init); | ||
343 | module_exit(acq_exit); | ||
344 | |||
345 | MODULE_AUTHOR("David Woodhouse"); | ||
346 | MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver"); | ||
347 | MODULE_LICENSE("GPL"); | ||
348 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/advantechwdt.c b/drivers/watchdog/advantechwdt.c new file mode 100644 index 000000000000..8121cc247343 --- /dev/null +++ b/drivers/watchdog/advantechwdt.c | |||
@@ -0,0 +1,362 @@ | |||
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/platform_device.h> | ||
39 | #include <linux/init.h> | ||
40 | |||
41 | #include <asm/io.h> | ||
42 | #include <asm/uaccess.h> | ||
43 | #include <asm/system.h> | ||
44 | |||
45 | #define DRV_NAME "advantechwdt" | ||
46 | #define PFX DRV_NAME ": " | ||
47 | #define WATCHDOG_NAME "Advantech WDT" | ||
48 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ | ||
49 | |||
50 | static struct platform_device *advwdt_platform_device; /* the watchdog platform device */ | ||
51 | static unsigned long advwdt_is_open; | ||
52 | static char adv_expect_close; | ||
53 | |||
54 | /* | ||
55 | * You must set these - there is no sane way to probe for this board. | ||
56 | * | ||
57 | * To enable or restart, write the timeout value in seconds (1 to 63) | ||
58 | * to I/O port wdt_start. To disable, read I/O port wdt_stop. | ||
59 | * Both are 0x443 for most boards (tested on a PCA-6276VE-00B1), but | ||
60 | * check your manual (at least the PCA-6159 seems to be different - | ||
61 | * the manual says wdt_stop is 0x43, not 0x443). | ||
62 | * (0x43 is also a write-only control register for the 8254 timer!) | ||
63 | */ | ||
64 | |||
65 | static int wdt_stop = 0x443; | ||
66 | module_param(wdt_stop, int, 0); | ||
67 | MODULE_PARM_DESC(wdt_stop, "Advantech WDT 'stop' io port (default 0x443)"); | ||
68 | |||
69 | static int wdt_start = 0x443; | ||
70 | module_param(wdt_start, int, 0); | ||
71 | MODULE_PARM_DESC(wdt_start, "Advantech WDT 'start' io port (default 0x443)"); | ||
72 | |||
73 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ | ||
74 | module_param(timeout, int, 0); | ||
75 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=63, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) "."); | ||
76 | |||
77 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
78 | module_param(nowayout, int, 0); | ||
79 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
80 | |||
81 | /* | ||
82 | * Watchdog Operations | ||
83 | */ | ||
84 | |||
85 | static void | ||
86 | advwdt_ping(void) | ||
87 | { | ||
88 | /* Write a watchdog value */ | ||
89 | outb_p(timeout, wdt_start); | ||
90 | } | ||
91 | |||
92 | static void | ||
93 | advwdt_disable(void) | ||
94 | { | ||
95 | inb_p(wdt_stop); | ||
96 | } | ||
97 | |||
98 | static int | ||
99 | advwdt_set_heartbeat(int t) | ||
100 | { | ||
101 | if ((t < 1) || (t > 63)) | ||
102 | return -EINVAL; | ||
103 | |||
104 | timeout = t; | ||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | /* | ||
109 | * /dev/watchdog handling | ||
110 | */ | ||
111 | |||
112 | static ssize_t | ||
113 | advwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
114 | { | ||
115 | if (count) { | ||
116 | if (!nowayout) { | ||
117 | size_t i; | ||
118 | |||
119 | adv_expect_close = 0; | ||
120 | |||
121 | for (i = 0; i != count; i++) { | ||
122 | char c; | ||
123 | if (get_user(c, buf+i)) | ||
124 | return -EFAULT; | ||
125 | if (c == 'V') | ||
126 | adv_expect_close = 42; | ||
127 | } | ||
128 | } | ||
129 | advwdt_ping(); | ||
130 | } | ||
131 | return count; | ||
132 | } | ||
133 | |||
134 | static int | ||
135 | advwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
136 | unsigned long arg) | ||
137 | { | ||
138 | int new_timeout; | ||
139 | void __user *argp = (void __user *)arg; | ||
140 | int __user *p = argp; | ||
141 | static struct watchdog_info ident = { | ||
142 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
143 | .firmware_version = 1, | ||
144 | .identity = WATCHDOG_NAME, | ||
145 | }; | ||
146 | |||
147 | switch (cmd) { | ||
148 | case WDIOC_GETSUPPORT: | ||
149 | if (copy_to_user(argp, &ident, sizeof(ident))) | ||
150 | return -EFAULT; | ||
151 | break; | ||
152 | |||
153 | case WDIOC_GETSTATUS: | ||
154 | case WDIOC_GETBOOTSTATUS: | ||
155 | return put_user(0, p); | ||
156 | |||
157 | case WDIOC_KEEPALIVE: | ||
158 | advwdt_ping(); | ||
159 | break; | ||
160 | |||
161 | case WDIOC_SETTIMEOUT: | ||
162 | if (get_user(new_timeout, p)) | ||
163 | return -EFAULT; | ||
164 | if (advwdt_set_heartbeat(new_timeout)) | ||
165 | return -EINVAL; | ||
166 | advwdt_ping(); | ||
167 | /* Fall */ | ||
168 | |||
169 | case WDIOC_GETTIMEOUT: | ||
170 | return put_user(timeout, p); | ||
171 | |||
172 | case WDIOC_SETOPTIONS: | ||
173 | { | ||
174 | int options, retval = -EINVAL; | ||
175 | |||
176 | if (get_user(options, p)) | ||
177 | return -EFAULT; | ||
178 | |||
179 | if (options & WDIOS_DISABLECARD) { | ||
180 | advwdt_disable(); | ||
181 | retval = 0; | ||
182 | } | ||
183 | |||
184 | if (options & WDIOS_ENABLECARD) { | ||
185 | advwdt_ping(); | ||
186 | retval = 0; | ||
187 | } | ||
188 | |||
189 | return retval; | ||
190 | } | ||
191 | |||
192 | default: | ||
193 | return -ENOTTY; | ||
194 | } | ||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | static int | ||
199 | advwdt_open(struct inode *inode, struct file *file) | ||
200 | { | ||
201 | if (test_and_set_bit(0, &advwdt_is_open)) | ||
202 | return -EBUSY; | ||
203 | /* | ||
204 | * Activate | ||
205 | */ | ||
206 | |||
207 | advwdt_ping(); | ||
208 | return nonseekable_open(inode, file); | ||
209 | } | ||
210 | |||
211 | static int | ||
212 | advwdt_close(struct inode *inode, struct file *file) | ||
213 | { | ||
214 | if (adv_expect_close == 42) { | ||
215 | advwdt_disable(); | ||
216 | } else { | ||
217 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
218 | advwdt_ping(); | ||
219 | } | ||
220 | clear_bit(0, &advwdt_is_open); | ||
221 | adv_expect_close = 0; | ||
222 | return 0; | ||
223 | } | ||
224 | |||
225 | /* | ||
226 | * Kernel Interfaces | ||
227 | */ | ||
228 | |||
229 | static const struct file_operations advwdt_fops = { | ||
230 | .owner = THIS_MODULE, | ||
231 | .llseek = no_llseek, | ||
232 | .write = advwdt_write, | ||
233 | .ioctl = advwdt_ioctl, | ||
234 | .open = advwdt_open, | ||
235 | .release = advwdt_close, | ||
236 | }; | ||
237 | |||
238 | static struct miscdevice advwdt_miscdev = { | ||
239 | .minor = WATCHDOG_MINOR, | ||
240 | .name = "watchdog", | ||
241 | .fops = &advwdt_fops, | ||
242 | }; | ||
243 | |||
244 | /* | ||
245 | * Init & exit routines | ||
246 | */ | ||
247 | |||
248 | static int __devinit | ||
249 | advwdt_probe(struct platform_device *dev) | ||
250 | { | ||
251 | int ret; | ||
252 | |||
253 | if (wdt_stop != wdt_start) { | ||
254 | if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) { | ||
255 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
256 | wdt_stop); | ||
257 | ret = -EIO; | ||
258 | goto out; | ||
259 | } | ||
260 | } | ||
261 | |||
262 | if (!request_region(wdt_start, 1, WATCHDOG_NAME)) { | ||
263 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
264 | wdt_start); | ||
265 | ret = -EIO; | ||
266 | goto unreg_stop; | ||
267 | } | ||
268 | |||
269 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
270 | if (advwdt_set_heartbeat(timeout)) { | ||
271 | advwdt_set_heartbeat(WATCHDOG_TIMEOUT); | ||
272 | printk (KERN_INFO PFX "timeout value must be 1<=x<=63, using %d\n", | ||
273 | timeout); | ||
274 | } | ||
275 | |||
276 | ret = misc_register(&advwdt_miscdev); | ||
277 | if (ret != 0) { | ||
278 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
279 | WATCHDOG_MINOR, ret); | ||
280 | goto unreg_regions; | ||
281 | } | ||
282 | |||
283 | printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", | ||
284 | timeout, nowayout); | ||
285 | |||
286 | out: | ||
287 | return ret; | ||
288 | unreg_regions: | ||
289 | release_region(wdt_start, 1); | ||
290 | unreg_stop: | ||
291 | if (wdt_stop != wdt_start) | ||
292 | release_region(wdt_stop, 1); | ||
293 | goto out; | ||
294 | } | ||
295 | |||
296 | static int __devexit | ||
297 | advwdt_remove(struct platform_device *dev) | ||
298 | { | ||
299 | misc_deregister(&advwdt_miscdev); | ||
300 | release_region(wdt_start,1); | ||
301 | if(wdt_stop != wdt_start) | ||
302 | release_region(wdt_stop,1); | ||
303 | |||
304 | return 0; | ||
305 | } | ||
306 | |||
307 | static void | ||
308 | advwdt_shutdown(struct platform_device *dev) | ||
309 | { | ||
310 | /* Turn the WDT off if we have a soft shutdown */ | ||
311 | advwdt_disable(); | ||
312 | } | ||
313 | |||
314 | static struct platform_driver advwdt_driver = { | ||
315 | .probe = advwdt_probe, | ||
316 | .remove = __devexit_p(advwdt_remove), | ||
317 | .shutdown = advwdt_shutdown, | ||
318 | .driver = { | ||
319 | .owner = THIS_MODULE, | ||
320 | .name = DRV_NAME, | ||
321 | }, | ||
322 | }; | ||
323 | |||
324 | static int __init | ||
325 | advwdt_init(void) | ||
326 | { | ||
327 | int err; | ||
328 | |||
329 | printk(KERN_INFO "WDT driver for Advantech single board computer initialising.\n"); | ||
330 | |||
331 | err = platform_driver_register(&advwdt_driver); | ||
332 | if (err) | ||
333 | return err; | ||
334 | |||
335 | advwdt_platform_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0); | ||
336 | if (IS_ERR(advwdt_platform_device)) { | ||
337 | err = PTR_ERR(advwdt_platform_device); | ||
338 | goto unreg_platform_driver; | ||
339 | } | ||
340 | |||
341 | return 0; | ||
342 | |||
343 | unreg_platform_driver: | ||
344 | platform_driver_unregister(&advwdt_driver); | ||
345 | return err; | ||
346 | } | ||
347 | |||
348 | static void __exit | ||
349 | advwdt_exit(void) | ||
350 | { | ||
351 | platform_device_unregister(advwdt_platform_device); | ||
352 | platform_driver_unregister(&advwdt_driver); | ||
353 | printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); | ||
354 | } | ||
355 | |||
356 | module_init(advwdt_init); | ||
357 | module_exit(advwdt_exit); | ||
358 | |||
359 | MODULE_LICENSE("GPL"); | ||
360 | MODULE_AUTHOR("Marek Michalkiewicz <marekm@linux.org.pl>"); | ||
361 | MODULE_DESCRIPTION("Advantech Single Board Computer WDT driver"); | ||
362 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/alim1535_wdt.c b/drivers/watchdog/alim1535_wdt.c new file mode 100644 index 000000000000..c404fc69e7e6 --- /dev/null +++ b/drivers/watchdog/alim1535_wdt.c | |||
@@ -0,0 +1,465 @@ | |||
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 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
42 | module_param(nowayout, int, 0); | ||
43 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
44 | |||
45 | /* | ||
46 | * ali_start - start watchdog countdown | ||
47 | * | ||
48 | * Starts the timer running providing the timer has a counter | ||
49 | * configuration set. | ||
50 | */ | ||
51 | |||
52 | static void ali_start(void) | ||
53 | { | ||
54 | u32 val; | ||
55 | |||
56 | spin_lock(&ali_lock); | ||
57 | |||
58 | pci_read_config_dword(ali_pci, 0xCC, &val); | ||
59 | val &= ~0x3F; /* Mask count */ | ||
60 | val |= (1<<25) | ali_timeout_bits; | ||
61 | pci_write_config_dword(ali_pci, 0xCC, val); | ||
62 | |||
63 | spin_unlock(&ali_lock); | ||
64 | } | ||
65 | |||
66 | /* | ||
67 | * ali_stop - stop the timer countdown | ||
68 | * | ||
69 | * Stop the ALi watchdog countdown | ||
70 | */ | ||
71 | |||
72 | static void ali_stop(void) | ||
73 | { | ||
74 | u32 val; | ||
75 | |||
76 | spin_lock(&ali_lock); | ||
77 | |||
78 | pci_read_config_dword(ali_pci, 0xCC, &val); | ||
79 | val &= ~0x3F; /* Mask count to zero (disabled) */ | ||
80 | val &= ~(1<<25);/* and for safety mask the reset enable */ | ||
81 | pci_write_config_dword(ali_pci, 0xCC, val); | ||
82 | |||
83 | spin_unlock(&ali_lock); | ||
84 | } | ||
85 | |||
86 | /* | ||
87 | * ali_keepalive - send a keepalive to the watchdog | ||
88 | * | ||
89 | * Send a keepalive to the timer (actually we restart the timer). | ||
90 | */ | ||
91 | |||
92 | static void ali_keepalive(void) | ||
93 | { | ||
94 | ali_start(); | ||
95 | } | ||
96 | |||
97 | /* | ||
98 | * ali_settimer - compute the timer reload value | ||
99 | * @t: time in seconds | ||
100 | * | ||
101 | * Computes the timeout values needed | ||
102 | */ | ||
103 | |||
104 | static int ali_settimer(int t) | ||
105 | { | ||
106 | if(t < 0) | ||
107 | return -EINVAL; | ||
108 | else if(t < 60) | ||
109 | ali_timeout_bits = t|(1<<6); | ||
110 | else if(t < 3600) | ||
111 | ali_timeout_bits = (t/60)|(1<<7); | ||
112 | else if(t < 18000) | ||
113 | ali_timeout_bits = (t/300)|(1<<6)|(1<<7); | ||
114 | else return -EINVAL; | ||
115 | |||
116 | timeout = t; | ||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | /* | ||
121 | * /dev/watchdog handling | ||
122 | */ | ||
123 | |||
124 | /* | ||
125 | * ali_write - writes to ALi watchdog | ||
126 | * @file: file from VFS | ||
127 | * @data: user address of data | ||
128 | * @len: length of data | ||
129 | * @ppos: pointer to the file offset | ||
130 | * | ||
131 | * Handle a write to the ALi watchdog. Writing to the file pings | ||
132 | * the watchdog and resets it. Writing the magic 'V' sequence allows | ||
133 | * the next close to turn off the watchdog. | ||
134 | */ | ||
135 | |||
136 | static ssize_t ali_write(struct file *file, const char __user *data, | ||
137 | size_t len, loff_t * ppos) | ||
138 | { | ||
139 | /* See if we got the magic character 'V' and reload the timer */ | ||
140 | if (len) { | ||
141 | if (!nowayout) { | ||
142 | size_t i; | ||
143 | |||
144 | /* note: just in case someone wrote the magic character | ||
145 | * five months ago... */ | ||
146 | ali_expect_release = 0; | ||
147 | |||
148 | /* scan to see whether or not we got the magic character */ | ||
149 | for (i = 0; i != len; i++) { | ||
150 | char c; | ||
151 | if(get_user(c, data+i)) | ||
152 | return -EFAULT; | ||
153 | if (c == 'V') | ||
154 | ali_expect_release = 42; | ||
155 | } | ||
156 | } | ||
157 | |||
158 | /* someone wrote to us, we should reload the timer */ | ||
159 | ali_start(); | ||
160 | } | ||
161 | return len; | ||
162 | } | ||
163 | |||
164 | /* | ||
165 | * ali_ioctl - handle watchdog ioctls | ||
166 | * @inode: VFS inode | ||
167 | * @file: VFS file pointer | ||
168 | * @cmd: ioctl number | ||
169 | * @arg: arguments to the ioctl | ||
170 | * | ||
171 | * Handle the watchdog ioctls supported by the ALi driver. Really | ||
172 | * we want an extension to enable irq ack monitoring and the like | ||
173 | */ | ||
174 | |||
175 | static int ali_ioctl(struct inode *inode, struct file *file, | ||
176 | unsigned int cmd, unsigned long arg) | ||
177 | { | ||
178 | void __user *argp = (void __user *)arg; | ||
179 | int __user *p = argp; | ||
180 | static struct watchdog_info ident = { | ||
181 | .options = WDIOF_KEEPALIVEPING | | ||
182 | WDIOF_SETTIMEOUT | | ||
183 | WDIOF_MAGICCLOSE, | ||
184 | .firmware_version = 0, | ||
185 | .identity = "ALi M1535 WatchDog Timer", | ||
186 | }; | ||
187 | |||
188 | switch (cmd) { | ||
189 | case WDIOC_GETSUPPORT: | ||
190 | return copy_to_user(argp, &ident, | ||
191 | sizeof (ident)) ? -EFAULT : 0; | ||
192 | |||
193 | case WDIOC_GETSTATUS: | ||
194 | case WDIOC_GETBOOTSTATUS: | ||
195 | return put_user(0, p); | ||
196 | |||
197 | case WDIOC_KEEPALIVE: | ||
198 | ali_keepalive(); | ||
199 | return 0; | ||
200 | |||
201 | case WDIOC_SETOPTIONS: | ||
202 | { | ||
203 | int new_options, retval = -EINVAL; | ||
204 | |||
205 | if (get_user (new_options, p)) | ||
206 | return -EFAULT; | ||
207 | |||
208 | if (new_options & WDIOS_DISABLECARD) { | ||
209 | ali_stop(); | ||
210 | retval = 0; | ||
211 | } | ||
212 | |||
213 | if (new_options & WDIOS_ENABLECARD) { | ||
214 | ali_start(); | ||
215 | retval = 0; | ||
216 | } | ||
217 | |||
218 | return retval; | ||
219 | } | ||
220 | |||
221 | case WDIOC_SETTIMEOUT: | ||
222 | { | ||
223 | int new_timeout; | ||
224 | |||
225 | if (get_user(new_timeout, p)) | ||
226 | return -EFAULT; | ||
227 | |||
228 | if (ali_settimer(new_timeout)) | ||
229 | return -EINVAL; | ||
230 | |||
231 | ali_keepalive(); | ||
232 | /* Fall */ | ||
233 | } | ||
234 | |||
235 | case WDIOC_GETTIMEOUT: | ||
236 | return put_user(timeout, p); | ||
237 | |||
238 | default: | ||
239 | return -ENOTTY; | ||
240 | } | ||
241 | } | ||
242 | |||
243 | /* | ||
244 | * ali_open - handle open of ali watchdog | ||
245 | * @inode: inode from VFS | ||
246 | * @file: file from VFS | ||
247 | * | ||
248 | * Open the ALi watchdog device. Ensure only one person opens it | ||
249 | * at a time. Also start the watchdog running. | ||
250 | */ | ||
251 | |||
252 | static int ali_open(struct inode *inode, struct file *file) | ||
253 | { | ||
254 | /* /dev/watchdog can only be opened once */ | ||
255 | if (test_and_set_bit(0, &ali_is_open)) | ||
256 | return -EBUSY; | ||
257 | |||
258 | /* Activate */ | ||
259 | ali_start(); | ||
260 | return nonseekable_open(inode, file); | ||
261 | } | ||
262 | |||
263 | /* | ||
264 | * ali_release - close an ALi watchdog | ||
265 | * @inode: inode from VFS | ||
266 | * @file: file from VFS | ||
267 | * | ||
268 | * Close the ALi watchdog device. Actual shutdown of the timer | ||
269 | * only occurs if the magic sequence has been set. | ||
270 | */ | ||
271 | |||
272 | static int ali_release(struct inode *inode, struct file *file) | ||
273 | { | ||
274 | /* | ||
275 | * Shut off the timer. | ||
276 | */ | ||
277 | if (ali_expect_release == 42) { | ||
278 | ali_stop(); | ||
279 | } else { | ||
280 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
281 | ali_keepalive(); | ||
282 | } | ||
283 | clear_bit(0, &ali_is_open); | ||
284 | ali_expect_release = 0; | ||
285 | return 0; | ||
286 | } | ||
287 | |||
288 | /* | ||
289 | * ali_notify_sys - System down notifier | ||
290 | * | ||
291 | * Notifier for system down | ||
292 | */ | ||
293 | |||
294 | |||
295 | static int ali_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
296 | { | ||
297 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
298 | /* Turn the WDT off */ | ||
299 | ali_stop(); | ||
300 | } | ||
301 | |||
302 | return NOTIFY_DONE; | ||
303 | } | ||
304 | |||
305 | /* | ||
306 | * Data for PCI driver interface | ||
307 | * | ||
308 | * This data only exists for exporting the supported | ||
309 | * PCI ids via MODULE_DEVICE_TABLE. We do not actually | ||
310 | * register a pci_driver, because someone else might one day | ||
311 | * want to register another driver on the same PCI id. | ||
312 | */ | ||
313 | |||
314 | static struct pci_device_id ali_pci_tbl[] = { | ||
315 | { PCI_VENDOR_ID_AL, 0x1533, PCI_ANY_ID, PCI_ANY_ID,}, | ||
316 | { PCI_VENDOR_ID_AL, 0x1535, PCI_ANY_ID, PCI_ANY_ID,}, | ||
317 | { 0, }, | ||
318 | }; | ||
319 | MODULE_DEVICE_TABLE(pci, ali_pci_tbl); | ||
320 | |||
321 | /* | ||
322 | * ali_find_watchdog - find a 1535 and 7101 | ||
323 | * | ||
324 | * Scans the PCI hardware for a 1535 series bridge and matching 7101 | ||
325 | * watchdog device. This may be overtight but it is better to be safe | ||
326 | */ | ||
327 | |||
328 | static int __init ali_find_watchdog(void) | ||
329 | { | ||
330 | struct pci_dev *pdev; | ||
331 | u32 wdog; | ||
332 | |||
333 | /* Check for a 1533/1535 series bridge */ | ||
334 | pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x1535, NULL); | ||
335 | if (pdev == NULL) | ||
336 | pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x1533, NULL); | ||
337 | if (pdev == NULL) | ||
338 | return -ENODEV; | ||
339 | pci_dev_put(pdev); | ||
340 | |||
341 | /* Check for the a 7101 PMU */ | ||
342 | pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x7101, NULL); | ||
343 | if(pdev == NULL) | ||
344 | return -ENODEV; | ||
345 | |||
346 | if(pci_enable_device(pdev)) { | ||
347 | pci_dev_put(pdev); | ||
348 | return -EIO; | ||
349 | } | ||
350 | |||
351 | ali_pci = pdev; | ||
352 | |||
353 | /* | ||
354 | * Initialize the timer bits | ||
355 | */ | ||
356 | pci_read_config_dword(pdev, 0xCC, &wdog); | ||
357 | |||
358 | wdog &= ~0x3F; /* Timer bits */ | ||
359 | wdog &= ~((1<<27)|(1<<26)|(1<<25)|(1<<24)); /* Issued events */ | ||
360 | wdog &= ~((1<<16)|(1<<13)|(1<<12)|(1<<11)|(1<<10)|(1<<9)); /* No monitor bits */ | ||
361 | |||
362 | pci_write_config_dword(pdev, 0xCC, wdog); | ||
363 | |||
364 | return 0; | ||
365 | } | ||
366 | |||
367 | /* | ||
368 | * Kernel Interfaces | ||
369 | */ | ||
370 | |||
371 | static const struct file_operations ali_fops = { | ||
372 | .owner = THIS_MODULE, | ||
373 | .llseek = no_llseek, | ||
374 | .write = ali_write, | ||
375 | .ioctl = ali_ioctl, | ||
376 | .open = ali_open, | ||
377 | .release = ali_release, | ||
378 | }; | ||
379 | |||
380 | static struct miscdevice ali_miscdev = { | ||
381 | .minor = WATCHDOG_MINOR, | ||
382 | .name = "watchdog", | ||
383 | .fops = &ali_fops, | ||
384 | }; | ||
385 | |||
386 | static struct notifier_block ali_notifier = { | ||
387 | .notifier_call = ali_notify_sys, | ||
388 | }; | ||
389 | |||
390 | /* | ||
391 | * watchdog_init - module initialiser | ||
392 | * | ||
393 | * Scan for a suitable watchdog and if so initialize it. Return an error | ||
394 | * if we cannot, the error causes the module to unload | ||
395 | */ | ||
396 | |||
397 | static int __init watchdog_init(void) | ||
398 | { | ||
399 | int ret; | ||
400 | |||
401 | spin_lock_init(&ali_lock); | ||
402 | |||
403 | /* Check whether or not the hardware watchdog is there */ | ||
404 | if (ali_find_watchdog() != 0) { | ||
405 | return -ENODEV; | ||
406 | } | ||
407 | |||
408 | /* Check that the timeout value is within it's range ; if not reset to the default */ | ||
409 | if (timeout < 1 || timeout >= 18000) { | ||
410 | timeout = WATCHDOG_TIMEOUT; | ||
411 | printk(KERN_INFO PFX "timeout value must be 0<timeout<18000, using %d\n", | ||
412 | timeout); | ||
413 | } | ||
414 | |||
415 | /* Calculate the watchdog's timeout */ | ||
416 | ali_settimer(timeout); | ||
417 | |||
418 | ret = misc_register(&ali_miscdev); | ||
419 | if (ret != 0) { | ||
420 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
421 | WATCHDOG_MINOR, ret); | ||
422 | goto out; | ||
423 | } | ||
424 | |||
425 | ret = register_reboot_notifier(&ali_notifier); | ||
426 | if (ret != 0) { | ||
427 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
428 | ret); | ||
429 | goto unreg_miscdev; | ||
430 | } | ||
431 | |||
432 | printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", | ||
433 | timeout, nowayout); | ||
434 | |||
435 | out: | ||
436 | return ret; | ||
437 | unreg_miscdev: | ||
438 | misc_deregister(&ali_miscdev); | ||
439 | goto out; | ||
440 | } | ||
441 | |||
442 | /* | ||
443 | * watchdog_exit - module de-initialiser | ||
444 | * | ||
445 | * Called while unloading a successfully installed watchdog module. | ||
446 | */ | ||
447 | |||
448 | static void __exit watchdog_exit(void) | ||
449 | { | ||
450 | /* Stop the timer before we leave */ | ||
451 | ali_stop(); | ||
452 | |||
453 | /* Deregister */ | ||
454 | unregister_reboot_notifier(&ali_notifier); | ||
455 | misc_deregister(&ali_miscdev); | ||
456 | pci_dev_put(ali_pci); | ||
457 | } | ||
458 | |||
459 | module_init(watchdog_init); | ||
460 | module_exit(watchdog_exit); | ||
461 | |||
462 | MODULE_AUTHOR("Alan Cox"); | ||
463 | MODULE_DESCRIPTION("ALi M1535 PMU Watchdog Timer driver"); | ||
464 | MODULE_LICENSE("GPL"); | ||
465 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/alim7101_wdt.c b/drivers/watchdog/alim7101_wdt.c new file mode 100644 index 000000000000..67aed9f8c362 --- /dev/null +++ b/drivers/watchdog/alim7101_wdt.c | |||
@@ -0,0 +1,423 @@ | |||
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 DEFINE_TIMER(timer, wdt_timer_ping, 0, 1); | ||
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 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
79 | module_param(nowayout, int, 0); | ||
80 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | ||
81 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
82 | |||
83 | /* | ||
84 | * Whack the dog | ||
85 | */ | ||
86 | |||
87 | static void wdt_timer_ping(unsigned long data) | ||
88 | { | ||
89 | /* If we got a heartbeat pulse within the WDT_US_INTERVAL | ||
90 | * we agree to ping the WDT | ||
91 | */ | ||
92 | char tmp; | ||
93 | |||
94 | if(time_before(jiffies, next_heartbeat)) | ||
95 | { | ||
96 | /* Ping the WDT (this is actually a disarm/arm sequence) */ | ||
97 | pci_read_config_byte(alim7101_pmu, 0x92, &tmp); | ||
98 | pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM)); | ||
99 | pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM)); | ||
100 | if (use_gpio) { | ||
101 | pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp); | ||
102 | pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp | ||
103 | | 0x20); | ||
104 | pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp | ||
105 | & ~0x20); | ||
106 | } | ||
107 | } else { | ||
108 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
109 | } | ||
110 | /* Re-set the timer interval */ | ||
111 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
112 | } | ||
113 | |||
114 | /* | ||
115 | * Utility routines | ||
116 | */ | ||
117 | |||
118 | static void wdt_change(int writeval) | ||
119 | { | ||
120 | char tmp; | ||
121 | |||
122 | pci_read_config_byte(alim7101_pmu, ALI_7101_WDT, &tmp); | ||
123 | if (writeval == WDT_ENABLE) { | ||
124 | pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM)); | ||
125 | if (use_gpio) { | ||
126 | pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp); | ||
127 | pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp & ~0x20); | ||
128 | } | ||
129 | |||
130 | } else { | ||
131 | pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM)); | ||
132 | if (use_gpio) { | ||
133 | pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp); | ||
134 | pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp | 0x20); | ||
135 | } | ||
136 | } | ||
137 | } | ||
138 | |||
139 | static void wdt_startup(void) | ||
140 | { | ||
141 | next_heartbeat = jiffies + (timeout * HZ); | ||
142 | |||
143 | /* We must enable before we kick off the timer in case the timer | ||
144 | occurs as we ping it */ | ||
145 | |||
146 | wdt_change(WDT_ENABLE); | ||
147 | |||
148 | /* Start the timer */ | ||
149 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
150 | |||
151 | printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); | ||
152 | } | ||
153 | |||
154 | static void wdt_turnoff(void) | ||
155 | { | ||
156 | /* Stop the timer */ | ||
157 | del_timer_sync(&timer); | ||
158 | wdt_change(WDT_DISABLE); | ||
159 | printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); | ||
160 | } | ||
161 | |||
162 | static void wdt_keepalive(void) | ||
163 | { | ||
164 | /* user land ping */ | ||
165 | next_heartbeat = jiffies + (timeout * HZ); | ||
166 | } | ||
167 | |||
168 | /* | ||
169 | * /dev/watchdog handling | ||
170 | */ | ||
171 | |||
172 | static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) | ||
173 | { | ||
174 | /* See if we got the magic character 'V' and reload the timer */ | ||
175 | if(count) { | ||
176 | if (!nowayout) { | ||
177 | size_t ofs; | ||
178 | |||
179 | /* note: just in case someone wrote the magic character | ||
180 | * five months ago... */ | ||
181 | wdt_expect_close = 0; | ||
182 | |||
183 | /* now scan */ | ||
184 | for (ofs = 0; ofs != count; ofs++) { | ||
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 | /* someone wrote to us, we should restart timer */ | ||
193 | wdt_keepalive(); | ||
194 | } | ||
195 | return count; | ||
196 | } | ||
197 | |||
198 | static int fop_open(struct inode * inode, struct file * file) | ||
199 | { | ||
200 | /* Just in case we're already talking to someone... */ | ||
201 | if(test_and_set_bit(0, &wdt_is_open)) | ||
202 | return -EBUSY; | ||
203 | /* Good, fire up the show */ | ||
204 | wdt_startup(); | ||
205 | return nonseekable_open(inode, file); | ||
206 | } | ||
207 | |||
208 | static int fop_close(struct inode * inode, struct file * file) | ||
209 | { | ||
210 | if(wdt_expect_close == 42) | ||
211 | wdt_turnoff(); | ||
212 | else { | ||
213 | /* wim: shouldn't there be a: del_timer(&timer); */ | ||
214 | printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); | ||
215 | } | ||
216 | clear_bit(0, &wdt_is_open); | ||
217 | wdt_expect_close = 0; | ||
218 | return 0; | ||
219 | } | ||
220 | |||
221 | static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) | ||
222 | { | ||
223 | void __user *argp = (void __user *)arg; | ||
224 | int __user *p = argp; | ||
225 | static struct watchdog_info ident = | ||
226 | { | ||
227 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
228 | .firmware_version = 1, | ||
229 | .identity = "ALiM7101", | ||
230 | }; | ||
231 | |||
232 | switch(cmd) | ||
233 | { | ||
234 | case WDIOC_GETSUPPORT: | ||
235 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
236 | case WDIOC_GETSTATUS: | ||
237 | case WDIOC_GETBOOTSTATUS: | ||
238 | return put_user(0, p); | ||
239 | case WDIOC_KEEPALIVE: | ||
240 | wdt_keepalive(); | ||
241 | return 0; | ||
242 | case WDIOC_SETOPTIONS: | ||
243 | { | ||
244 | int new_options, retval = -EINVAL; | ||
245 | |||
246 | if(get_user(new_options, p)) | ||
247 | return -EFAULT; | ||
248 | |||
249 | if(new_options & WDIOS_DISABLECARD) { | ||
250 | wdt_turnoff(); | ||
251 | retval = 0; | ||
252 | } | ||
253 | |||
254 | if(new_options & WDIOS_ENABLECARD) { | ||
255 | wdt_startup(); | ||
256 | retval = 0; | ||
257 | } | ||
258 | |||
259 | return retval; | ||
260 | } | ||
261 | case WDIOC_SETTIMEOUT: | ||
262 | { | ||
263 | int new_timeout; | ||
264 | |||
265 | if(get_user(new_timeout, p)) | ||
266 | return -EFAULT; | ||
267 | |||
268 | if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ | ||
269 | return -EINVAL; | ||
270 | |||
271 | timeout = new_timeout; | ||
272 | wdt_keepalive(); | ||
273 | /* Fall through */ | ||
274 | } | ||
275 | case WDIOC_GETTIMEOUT: | ||
276 | return put_user(timeout, p); | ||
277 | default: | ||
278 | return -ENOTTY; | ||
279 | } | ||
280 | } | ||
281 | |||
282 | static const struct file_operations wdt_fops = { | ||
283 | .owner= THIS_MODULE, | ||
284 | .llseek= no_llseek, | ||
285 | .write= fop_write, | ||
286 | .open= fop_open, | ||
287 | .release= fop_close, | ||
288 | .ioctl= fop_ioctl, | ||
289 | }; | ||
290 | |||
291 | static struct miscdevice wdt_miscdev = { | ||
292 | .minor=WATCHDOG_MINOR, | ||
293 | .name="watchdog", | ||
294 | .fops=&wdt_fops, | ||
295 | }; | ||
296 | |||
297 | /* | ||
298 | * Notifier for system down | ||
299 | */ | ||
300 | |||
301 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
302 | { | ||
303 | if (code==SYS_DOWN || code==SYS_HALT) | ||
304 | wdt_turnoff(); | ||
305 | |||
306 | if (code==SYS_RESTART) { | ||
307 | /* | ||
308 | * Cobalt devices have no way of rebooting themselves other than | ||
309 | * getting the watchdog to pull reset, so we restart the watchdog on | ||
310 | * reboot with no heartbeat | ||
311 | */ | ||
312 | wdt_change(WDT_ENABLE); | ||
313 | printk(KERN_INFO PFX "Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second.\n"); | ||
314 | } | ||
315 | return NOTIFY_DONE; | ||
316 | } | ||
317 | |||
318 | /* | ||
319 | * The WDT needs to learn about soft shutdowns in order to | ||
320 | * turn the timebomb registers off. | ||
321 | */ | ||
322 | |||
323 | static struct notifier_block wdt_notifier= | ||
324 | { | ||
325 | .notifier_call = wdt_notify_sys, | ||
326 | }; | ||
327 | |||
328 | static void __exit alim7101_wdt_unload(void) | ||
329 | { | ||
330 | wdt_turnoff(); | ||
331 | /* Deregister */ | ||
332 | misc_deregister(&wdt_miscdev); | ||
333 | unregister_reboot_notifier(&wdt_notifier); | ||
334 | pci_dev_put(alim7101_pmu); | ||
335 | } | ||
336 | |||
337 | static int __init alim7101_wdt_init(void) | ||
338 | { | ||
339 | int rc = -EBUSY; | ||
340 | struct pci_dev *ali1543_south; | ||
341 | char tmp; | ||
342 | |||
343 | printk(KERN_INFO PFX "Steve Hill <steve@navaho.co.uk>.\n"); | ||
344 | alim7101_pmu = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101, | ||
345 | NULL); | ||
346 | if (!alim7101_pmu) { | ||
347 | printk(KERN_INFO PFX "ALi M7101 PMU not present - WDT not set\n"); | ||
348 | return -EBUSY; | ||
349 | } | ||
350 | |||
351 | /* Set the WDT in the PMU to 1 second */ | ||
352 | pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02); | ||
353 | |||
354 | ali1543_south = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, | ||
355 | NULL); | ||
356 | if (!ali1543_south) { | ||
357 | printk(KERN_INFO PFX "ALi 1543 South-Bridge not present - WDT not set\n"); | ||
358 | goto err_out; | ||
359 | } | ||
360 | pci_read_config_byte(ali1543_south, 0x5e, &tmp); | ||
361 | pci_dev_put(ali1543_south); | ||
362 | if ((tmp & 0x1e) == 0x00) { | ||
363 | if (!use_gpio) { | ||
364 | printk(KERN_INFO PFX "Detected old alim7101 revision 'a1d'. If this is a cobalt board, set the 'use_gpio' module parameter.\n"); | ||
365 | goto err_out; | ||
366 | } | ||
367 | nowayout = 1; | ||
368 | } else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) { | ||
369 | printk(KERN_INFO PFX "ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n"); | ||
370 | goto err_out; | ||
371 | } | ||
372 | |||
373 | if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */ | ||
374 | { | ||
375 | timeout = WATCHDOG_TIMEOUT; | ||
376 | printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", | ||
377 | timeout); | ||
378 | } | ||
379 | |||
380 | rc = misc_register(&wdt_miscdev); | ||
381 | if (rc) { | ||
382 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
383 | wdt_miscdev.minor, rc); | ||
384 | goto err_out; | ||
385 | } | ||
386 | |||
387 | rc = register_reboot_notifier(&wdt_notifier); | ||
388 | if (rc) { | ||
389 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
390 | rc); | ||
391 | goto err_out_miscdev; | ||
392 | } | ||
393 | |||
394 | if (nowayout) { | ||
395 | __module_get(THIS_MODULE); | ||
396 | } | ||
397 | |||
398 | printk(KERN_INFO PFX "WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n", | ||
399 | timeout, nowayout); | ||
400 | return 0; | ||
401 | |||
402 | err_out_miscdev: | ||
403 | misc_deregister(&wdt_miscdev); | ||
404 | err_out: | ||
405 | pci_dev_put(alim7101_pmu); | ||
406 | return rc; | ||
407 | } | ||
408 | |||
409 | module_init(alim7101_wdt_init); | ||
410 | module_exit(alim7101_wdt_unload); | ||
411 | |||
412 | static struct pci_device_id alim7101_pci_tbl[] __devinitdata = { | ||
413 | { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533) }, | ||
414 | { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) }, | ||
415 | { } | ||
416 | }; | ||
417 | |||
418 | MODULE_DEVICE_TABLE(pci, alim7101_pci_tbl); | ||
419 | |||
420 | MODULE_AUTHOR("Steve Hill"); | ||
421 | MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver"); | ||
422 | MODULE_LICENSE("GPL"); | ||
423 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/at32ap700x_wdt.c b/drivers/watchdog/at32ap700x_wdt.c new file mode 100644 index 000000000000..54a516169d07 --- /dev/null +++ b/drivers/watchdog/at32ap700x_wdt.c | |||
@@ -0,0 +1,386 @@ | |||
1 | /* | ||
2 | * Watchdog driver for Atmel AT32AP700X devices | ||
3 | * | ||
4 | * Copyright (C) 2005-2006 Atmel Corporation | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #include <linux/init.h> | ||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/moduleparam.h> | ||
15 | #include <linux/miscdevice.h> | ||
16 | #include <linux/fs.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/watchdog.h> | ||
19 | #include <linux/uaccess.h> | ||
20 | #include <linux/io.h> | ||
21 | #include <linux/spinlock.h> | ||
22 | |||
23 | #define TIMEOUT_MIN 1 | ||
24 | #define TIMEOUT_MAX 2 | ||
25 | #define TIMEOUT_DEFAULT TIMEOUT_MAX | ||
26 | |||
27 | /* module parameters */ | ||
28 | static int timeout = TIMEOUT_DEFAULT; | ||
29 | module_param(timeout, int, 0); | ||
30 | MODULE_PARM_DESC(timeout, | ||
31 | "Timeout value. Limited to be 1 or 2 seconds. (default=" | ||
32 | __MODULE_STRING(TIMEOUT_DEFAULT) ")"); | ||
33 | |||
34 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
35 | module_param(nowayout, int, 0); | ||
36 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | ||
37 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
38 | |||
39 | /* Watchdog registers and write/read macro */ | ||
40 | #define WDT_CTRL 0x00 | ||
41 | #define WDT_CTRL_EN 0 | ||
42 | #define WDT_CTRL_PSEL 8 | ||
43 | #define WDT_CTRL_KEY 24 | ||
44 | |||
45 | #define WDT_CLR 0x04 | ||
46 | |||
47 | #define WDT_BIT(name) (1 << WDT_##name) | ||
48 | #define WDT_BF(name, value) ((value) << WDT_##name) | ||
49 | |||
50 | #define wdt_readl(dev, reg) \ | ||
51 | __raw_readl((dev)->regs + WDT_##reg) | ||
52 | #define wdt_writel(dev, reg, value) \ | ||
53 | __raw_writel((value), (dev)->regs + WDT_##reg) | ||
54 | |||
55 | struct wdt_at32ap700x { | ||
56 | void __iomem *regs; | ||
57 | spinlock_t io_lock; | ||
58 | int timeout; | ||
59 | unsigned long users; | ||
60 | struct miscdevice miscdev; | ||
61 | }; | ||
62 | |||
63 | static struct wdt_at32ap700x *wdt; | ||
64 | static char expect_release; | ||
65 | |||
66 | /* | ||
67 | * Disable the watchdog. | ||
68 | */ | ||
69 | static inline void at32_wdt_stop(void) | ||
70 | { | ||
71 | unsigned long psel; | ||
72 | |||
73 | spin_lock(&wdt->io_lock); | ||
74 | psel = wdt_readl(wdt, CTRL) & WDT_BF(CTRL_PSEL, 0x0f); | ||
75 | wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0x55)); | ||
76 | wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0xaa)); | ||
77 | spin_unlock(&wdt->io_lock); | ||
78 | } | ||
79 | |||
80 | /* | ||
81 | * Enable and reset the watchdog. | ||
82 | */ | ||
83 | static inline void at32_wdt_start(void) | ||
84 | { | ||
85 | /* 0xf is 2^16 divider = 2 sec, 0xe is 2^15 divider = 1 sec */ | ||
86 | unsigned long psel = (wdt->timeout > 1) ? 0xf : 0xe; | ||
87 | |||
88 | spin_lock(&wdt->io_lock); | ||
89 | wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN) | ||
90 | | WDT_BF(CTRL_PSEL, psel) | ||
91 | | WDT_BF(CTRL_KEY, 0x55)); | ||
92 | wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN) | ||
93 | | WDT_BF(CTRL_PSEL, psel) | ||
94 | | WDT_BF(CTRL_KEY, 0xaa)); | ||
95 | spin_unlock(&wdt->io_lock); | ||
96 | } | ||
97 | |||
98 | /* | ||
99 | * Pat the watchdog timer. | ||
100 | */ | ||
101 | static inline void at32_wdt_pat(void) | ||
102 | { | ||
103 | spin_lock(&wdt->io_lock); | ||
104 | wdt_writel(wdt, CLR, 0x42); | ||
105 | spin_unlock(&wdt->io_lock); | ||
106 | } | ||
107 | |||
108 | /* | ||
109 | * Watchdog device is opened, and watchdog starts running. | ||
110 | */ | ||
111 | static int at32_wdt_open(struct inode *inode, struct file *file) | ||
112 | { | ||
113 | if (test_and_set_bit(1, &wdt->users)) | ||
114 | return -EBUSY; | ||
115 | |||
116 | at32_wdt_start(); | ||
117 | return nonseekable_open(inode, file); | ||
118 | } | ||
119 | |||
120 | /* | ||
121 | * Close the watchdog device. | ||
122 | */ | ||
123 | static int at32_wdt_close(struct inode *inode, struct file *file) | ||
124 | { | ||
125 | if (expect_release == 42) { | ||
126 | at32_wdt_stop(); | ||
127 | } else { | ||
128 | dev_dbg(wdt->miscdev.parent, | ||
129 | "Unexpected close, not stopping watchdog!\n"); | ||
130 | at32_wdt_pat(); | ||
131 | } | ||
132 | clear_bit(1, &wdt->users); | ||
133 | expect_release = 0; | ||
134 | return 0; | ||
135 | } | ||
136 | |||
137 | /* | ||
138 | * Change the watchdog time interval. | ||
139 | */ | ||
140 | static int at32_wdt_settimeout(int time) | ||
141 | { | ||
142 | /* | ||
143 | * All counting occurs at 1 / SLOW_CLOCK (32 kHz) and max prescaler is | ||
144 | * 2 ^ 16 allowing up to 2 seconds timeout. | ||
145 | */ | ||
146 | if ((time < TIMEOUT_MIN) || (time > TIMEOUT_MAX)) | ||
147 | return -EINVAL; | ||
148 | |||
149 | /* | ||
150 | * Set new watchdog time. It will be used when at32_wdt_start() is | ||
151 | * called. | ||
152 | */ | ||
153 | wdt->timeout = time; | ||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | static struct watchdog_info at32_wdt_info = { | ||
158 | .identity = "at32ap700x watchdog", | ||
159 | .options = WDIOF_SETTIMEOUT | | ||
160 | WDIOF_KEEPALIVEPING | | ||
161 | WDIOF_MAGICCLOSE, | ||
162 | }; | ||
163 | |||
164 | /* | ||
165 | * Handle commands from user-space. | ||
166 | */ | ||
167 | static int at32_wdt_ioctl(struct inode *inode, struct file *file, | ||
168 | unsigned int cmd, unsigned long arg) | ||
169 | { | ||
170 | int ret = -ENOTTY; | ||
171 | int time; | ||
172 | void __user *argp = (void __user *)arg; | ||
173 | int __user *p = argp; | ||
174 | |||
175 | switch (cmd) { | ||
176 | case WDIOC_KEEPALIVE: | ||
177 | at32_wdt_pat(); | ||
178 | ret = 0; | ||
179 | break; | ||
180 | case WDIOC_GETSUPPORT: | ||
181 | ret = copy_to_user(argp, &at32_wdt_info, | ||
182 | sizeof(at32_wdt_info)) ? -EFAULT : 0; | ||
183 | break; | ||
184 | case WDIOC_SETTIMEOUT: | ||
185 | ret = get_user(time, p); | ||
186 | if (ret) | ||
187 | break; | ||
188 | ret = at32_wdt_settimeout(time); | ||
189 | if (ret) | ||
190 | break; | ||
191 | /* Enable new time value */ | ||
192 | at32_wdt_start(); | ||
193 | /* fall through */ | ||
194 | case WDIOC_GETTIMEOUT: | ||
195 | ret = put_user(wdt->timeout, p); | ||
196 | break; | ||
197 | case WDIOC_GETSTATUS: /* fall through */ | ||
198 | case WDIOC_GETBOOTSTATUS: | ||
199 | ret = put_user(0, p); | ||
200 | break; | ||
201 | case WDIOC_SETOPTIONS: | ||
202 | ret = get_user(time, p); | ||
203 | if (ret) | ||
204 | break; | ||
205 | if (time & WDIOS_DISABLECARD) | ||
206 | at32_wdt_stop(); | ||
207 | if (time & WDIOS_ENABLECARD) | ||
208 | at32_wdt_start(); | ||
209 | ret = 0; | ||
210 | break; | ||
211 | } | ||
212 | |||
213 | return ret; | ||
214 | } | ||
215 | |||
216 | static ssize_t at32_wdt_write(struct file *file, const char __user *data, | ||
217 | size_t len, loff_t *ppos) | ||
218 | { | ||
219 | /* See if we got the magic character 'V' and reload the timer */ | ||
220 | if (len) { | ||
221 | if (!nowayout) { | ||
222 | size_t i; | ||
223 | |||
224 | /* | ||
225 | * note: just in case someone wrote the magic | ||
226 | * character five months ago... | ||
227 | */ | ||
228 | expect_release = 0; | ||
229 | |||
230 | /* | ||
231 | * scan to see whether or not we got the magic | ||
232 | * character | ||
233 | */ | ||
234 | for (i = 0; i != len; i++) { | ||
235 | char c; | ||
236 | if (get_user(c, data+i)) | ||
237 | return -EFAULT; | ||
238 | if (c == 'V') | ||
239 | expect_release = 42; | ||
240 | } | ||
241 | } | ||
242 | /* someone wrote to us, we should pat the watchdog */ | ||
243 | at32_wdt_pat(); | ||
244 | } | ||
245 | return len; | ||
246 | } | ||
247 | |||
248 | static const struct file_operations at32_wdt_fops = { | ||
249 | .owner = THIS_MODULE, | ||
250 | .llseek = no_llseek, | ||
251 | .ioctl = at32_wdt_ioctl, | ||
252 | .open = at32_wdt_open, | ||
253 | .release = at32_wdt_close, | ||
254 | .write = at32_wdt_write, | ||
255 | }; | ||
256 | |||
257 | static int __init at32_wdt_probe(struct platform_device *pdev) | ||
258 | { | ||
259 | struct resource *regs; | ||
260 | int ret; | ||
261 | |||
262 | if (wdt) { | ||
263 | dev_dbg(&pdev->dev, "only 1 wdt instance supported.\n"); | ||
264 | return -EBUSY; | ||
265 | } | ||
266 | |||
267 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
268 | if (!regs) { | ||
269 | dev_dbg(&pdev->dev, "missing mmio resource\n"); | ||
270 | return -ENXIO; | ||
271 | } | ||
272 | |||
273 | wdt = kzalloc(sizeof(struct wdt_at32ap700x), GFP_KERNEL); | ||
274 | if (!wdt) { | ||
275 | dev_dbg(&pdev->dev, "no memory for wdt structure\n"); | ||
276 | return -ENOMEM; | ||
277 | } | ||
278 | |||
279 | wdt->regs = ioremap(regs->start, regs->end - regs->start + 1); | ||
280 | if (!wdt->regs) { | ||
281 | ret = -ENOMEM; | ||
282 | dev_dbg(&pdev->dev, "could not map I/O memory\n"); | ||
283 | goto err_free; | ||
284 | } | ||
285 | spin_lock_init(&wdt->io_lock); | ||
286 | wdt->users = 0; | ||
287 | wdt->miscdev.minor = WATCHDOG_MINOR; | ||
288 | wdt->miscdev.name = "watchdog"; | ||
289 | wdt->miscdev.fops = &at32_wdt_fops; | ||
290 | |||
291 | if (at32_wdt_settimeout(timeout)) { | ||
292 | at32_wdt_settimeout(TIMEOUT_DEFAULT); | ||
293 | dev_dbg(&pdev->dev, | ||
294 | "default timeout invalid, set to %d sec.\n", | ||
295 | TIMEOUT_DEFAULT); | ||
296 | } | ||
297 | |||
298 | ret = misc_register(&wdt->miscdev); | ||
299 | if (ret) { | ||
300 | dev_dbg(&pdev->dev, "failed to register wdt miscdev\n"); | ||
301 | goto err_iounmap; | ||
302 | } | ||
303 | |||
304 | platform_set_drvdata(pdev, wdt); | ||
305 | wdt->miscdev.parent = &pdev->dev; | ||
306 | dev_info(&pdev->dev, | ||
307 | "AT32AP700X WDT at 0x%p, timeout %d sec (nowayout=%d)\n", | ||
308 | wdt->regs, wdt->timeout, nowayout); | ||
309 | |||
310 | return 0; | ||
311 | |||
312 | err_iounmap: | ||
313 | iounmap(wdt->regs); | ||
314 | err_free: | ||
315 | kfree(wdt); | ||
316 | wdt = NULL; | ||
317 | return ret; | ||
318 | } | ||
319 | |||
320 | static int __exit at32_wdt_remove(struct platform_device *pdev) | ||
321 | { | ||
322 | if (wdt && platform_get_drvdata(pdev) == wdt) { | ||
323 | /* Stop the timer before we leave */ | ||
324 | if (!nowayout) | ||
325 | at32_wdt_stop(); | ||
326 | |||
327 | misc_deregister(&wdt->miscdev); | ||
328 | iounmap(wdt->regs); | ||
329 | kfree(wdt); | ||
330 | wdt = NULL; | ||
331 | platform_set_drvdata(pdev, NULL); | ||
332 | } | ||
333 | |||
334 | return 0; | ||
335 | } | ||
336 | |||
337 | static void at32_wdt_shutdown(struct platform_device *pdev) | ||
338 | { | ||
339 | at32_wdt_stop(); | ||
340 | } | ||
341 | |||
342 | #ifdef CONFIG_PM | ||
343 | static int at32_wdt_suspend(struct platform_device *pdev, pm_message_t message) | ||
344 | { | ||
345 | at32_wdt_stop(); | ||
346 | return 0; | ||
347 | } | ||
348 | |||
349 | static int at32_wdt_resume(struct platform_device *pdev) | ||
350 | { | ||
351 | if (wdt->users) | ||
352 | at32_wdt_start(); | ||
353 | return 0; | ||
354 | } | ||
355 | #else | ||
356 | #define at32_wdt_suspend NULL | ||
357 | #define at32_wdt_resume NULL | ||
358 | #endif | ||
359 | |||
360 | static struct platform_driver at32_wdt_driver = { | ||
361 | .remove = __exit_p(at32_wdt_remove), | ||
362 | .suspend = at32_wdt_suspend, | ||
363 | .resume = at32_wdt_resume, | ||
364 | .driver = { | ||
365 | .name = "at32_wdt", | ||
366 | .owner = THIS_MODULE, | ||
367 | }, | ||
368 | .shutdown = at32_wdt_shutdown, | ||
369 | }; | ||
370 | |||
371 | static int __init at32_wdt_init(void) | ||
372 | { | ||
373 | return platform_driver_probe(&at32_wdt_driver, at32_wdt_probe); | ||
374 | } | ||
375 | module_init(at32_wdt_init); | ||
376 | |||
377 | static void __exit at32_wdt_exit(void) | ||
378 | { | ||
379 | platform_driver_unregister(&at32_wdt_driver); | ||
380 | } | ||
381 | module_exit(at32_wdt_exit); | ||
382 | |||
383 | MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>"); | ||
384 | MODULE_DESCRIPTION("Watchdog driver for Atmel AT32AP700X"); | ||
385 | MODULE_LICENSE("GPL"); | ||
386 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/at91rm9200_wdt.c b/drivers/watchdog/at91rm9200_wdt.c new file mode 100644 index 000000000000..38bd37372599 --- /dev/null +++ b/drivers/watchdog/at91rm9200_wdt.c | |||
@@ -0,0 +1,288 @@ | |||
1 | /* | ||
2 | * Watchdog driver for Atmel AT91RM9200 (Thunder) | ||
3 | * | ||
4 | * Copyright (C) 2003 SAN People (Pty) Ltd | ||
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 | |||
12 | #include <linux/errno.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/moduleparam.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/types.h> | ||
21 | #include <linux/watchdog.h> | ||
22 | #include <asm/bitops.h> | ||
23 | #include <asm/uaccess.h> | ||
24 | #include <asm/arch/at91_st.h> | ||
25 | |||
26 | |||
27 | #define WDT_DEFAULT_TIME 5 /* seconds */ | ||
28 | #define WDT_MAX_TIME 256 /* seconds */ | ||
29 | |||
30 | static int wdt_time = WDT_DEFAULT_TIME; | ||
31 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
32 | |||
33 | module_param(wdt_time, int, 0); | ||
34 | MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default="__MODULE_STRING(WDT_DEFAULT_TIME) ")"); | ||
35 | |||
36 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
37 | module_param(nowayout, int, 0); | ||
38 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
39 | #endif | ||
40 | |||
41 | |||
42 | static unsigned long at91wdt_busy; | ||
43 | |||
44 | /* ......................................................................... */ | ||
45 | |||
46 | /* | ||
47 | * Disable the watchdog. | ||
48 | */ | ||
49 | static void inline at91_wdt_stop(void) | ||
50 | { | ||
51 | at91_sys_write(AT91_ST_WDMR, AT91_ST_EXTEN); | ||
52 | } | ||
53 | |||
54 | /* | ||
55 | * Enable and reset the watchdog. | ||
56 | */ | ||
57 | static void inline at91_wdt_start(void) | ||
58 | { | ||
59 | at91_sys_write(AT91_ST_WDMR, AT91_ST_EXTEN | AT91_ST_RSTEN | (((65536 * wdt_time) >> 8) & AT91_ST_WDV)); | ||
60 | at91_sys_write(AT91_ST_CR, AT91_ST_WDRST); | ||
61 | } | ||
62 | |||
63 | /* | ||
64 | * Reload the watchdog timer. (ie, pat the watchdog) | ||
65 | */ | ||
66 | static void inline at91_wdt_reload(void) | ||
67 | { | ||
68 | at91_sys_write(AT91_ST_CR, AT91_ST_WDRST); | ||
69 | } | ||
70 | |||
71 | /* ......................................................................... */ | ||
72 | |||
73 | /* | ||
74 | * Watchdog device is opened, and watchdog starts running. | ||
75 | */ | ||
76 | static int at91_wdt_open(struct inode *inode, struct file *file) | ||
77 | { | ||
78 | if (test_and_set_bit(0, &at91wdt_busy)) | ||
79 | return -EBUSY; | ||
80 | |||
81 | at91_wdt_start(); | ||
82 | return nonseekable_open(inode, file); | ||
83 | } | ||
84 | |||
85 | /* | ||
86 | * Close the watchdog device. | ||
87 | * If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also | ||
88 | * disabled. | ||
89 | */ | ||
90 | static int at91_wdt_close(struct inode *inode, struct file *file) | ||
91 | { | ||
92 | if (!nowayout) | ||
93 | at91_wdt_stop(); /* Disable the watchdog when file is closed */ | ||
94 | |||
95 | clear_bit(0, &at91wdt_busy); | ||
96 | return 0; | ||
97 | } | ||
98 | |||
99 | /* | ||
100 | * Change the watchdog time interval. | ||
101 | */ | ||
102 | static int at91_wdt_settimeout(int new_time) | ||
103 | { | ||
104 | /* | ||
105 | * All counting occurs at SLOW_CLOCK / 128 = 0.256 Hz | ||
106 | * | ||
107 | * Since WDV is a 16-bit counter, the maximum period is | ||
108 | * 65536 / 0.256 = 256 seconds. | ||
109 | */ | ||
110 | if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) | ||
111 | return -EINVAL; | ||
112 | |||
113 | /* Set new watchdog time. It will be used when at91_wdt_start() is called. */ | ||
114 | wdt_time = new_time; | ||
115 | return 0; | ||
116 | } | ||
117 | |||
118 | static struct watchdog_info at91_wdt_info = { | ||
119 | .identity = "at91 watchdog", | ||
120 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
121 | }; | ||
122 | |||
123 | /* | ||
124 | * Handle commands from user-space. | ||
125 | */ | ||
126 | static int at91_wdt_ioctl(struct inode *inode, struct file *file, | ||
127 | unsigned int cmd, unsigned long arg) | ||
128 | { | ||
129 | void __user *argp = (void __user *)arg; | ||
130 | int __user *p = argp; | ||
131 | int new_value; | ||
132 | |||
133 | switch(cmd) { | ||
134 | case WDIOC_KEEPALIVE: | ||
135 | at91_wdt_reload(); /* pat the watchdog */ | ||
136 | return 0; | ||
137 | |||
138 | case WDIOC_GETSUPPORT: | ||
139 | return copy_to_user(argp, &at91_wdt_info, sizeof(at91_wdt_info)) ? -EFAULT : 0; | ||
140 | |||
141 | case WDIOC_SETTIMEOUT: | ||
142 | if (get_user(new_value, p)) | ||
143 | return -EFAULT; | ||
144 | |||
145 | if (at91_wdt_settimeout(new_value)) | ||
146 | return -EINVAL; | ||
147 | |||
148 | /* Enable new time value */ | ||
149 | at91_wdt_start(); | ||
150 | |||
151 | /* Return current value */ | ||
152 | return put_user(wdt_time, p); | ||
153 | |||
154 | case WDIOC_GETTIMEOUT: | ||
155 | return put_user(wdt_time, p); | ||
156 | |||
157 | case WDIOC_GETSTATUS: | ||
158 | case WDIOC_GETBOOTSTATUS: | ||
159 | return put_user(0, p); | ||
160 | |||
161 | case WDIOC_SETOPTIONS: | ||
162 | if (get_user(new_value, p)) | ||
163 | return -EFAULT; | ||
164 | |||
165 | if (new_value & WDIOS_DISABLECARD) | ||
166 | at91_wdt_stop(); | ||
167 | if (new_value & WDIOS_ENABLECARD) | ||
168 | at91_wdt_start(); | ||
169 | return 0; | ||
170 | |||
171 | default: | ||
172 | return -ENOTTY; | ||
173 | } | ||
174 | } | ||
175 | |||
176 | /* | ||
177 | * Pat the watchdog whenever device is written to. | ||
178 | */ | ||
179 | static ssize_t at91_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) | ||
180 | { | ||
181 | at91_wdt_reload(); /* pat the watchdog */ | ||
182 | return len; | ||
183 | } | ||
184 | |||
185 | /* ......................................................................... */ | ||
186 | |||
187 | static const struct file_operations at91wdt_fops = { | ||
188 | .owner = THIS_MODULE, | ||
189 | .llseek = no_llseek, | ||
190 | .ioctl = at91_wdt_ioctl, | ||
191 | .open = at91_wdt_open, | ||
192 | .release = at91_wdt_close, | ||
193 | .write = at91_wdt_write, | ||
194 | }; | ||
195 | |||
196 | static struct miscdevice at91wdt_miscdev = { | ||
197 | .minor = WATCHDOG_MINOR, | ||
198 | .name = "watchdog", | ||
199 | .fops = &at91wdt_fops, | ||
200 | }; | ||
201 | |||
202 | static int __init at91wdt_probe(struct platform_device *pdev) | ||
203 | { | ||
204 | int res; | ||
205 | |||
206 | if (at91wdt_miscdev.parent) | ||
207 | return -EBUSY; | ||
208 | at91wdt_miscdev.parent = &pdev->dev; | ||
209 | |||
210 | res = misc_register(&at91wdt_miscdev); | ||
211 | if (res) | ||
212 | return res; | ||
213 | |||
214 | printk("AT91 Watchdog Timer enabled (%d seconds%s)\n", wdt_time, nowayout ? ", nowayout" : ""); | ||
215 | return 0; | ||
216 | } | ||
217 | |||
218 | static int __exit at91wdt_remove(struct platform_device *pdev) | ||
219 | { | ||
220 | int res; | ||
221 | |||
222 | res = misc_deregister(&at91wdt_miscdev); | ||
223 | if (!res) | ||
224 | at91wdt_miscdev.parent = NULL; | ||
225 | |||
226 | return res; | ||
227 | } | ||
228 | |||
229 | static void at91wdt_shutdown(struct platform_device *pdev) | ||
230 | { | ||
231 | at91_wdt_stop(); | ||
232 | } | ||
233 | |||
234 | #ifdef CONFIG_PM | ||
235 | |||
236 | static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message) | ||
237 | { | ||
238 | at91_wdt_stop(); | ||
239 | return 0; | ||
240 | } | ||
241 | |||
242 | static int at91wdt_resume(struct platform_device *pdev) | ||
243 | { | ||
244 | if (at91wdt_busy) | ||
245 | at91_wdt_start(); | ||
246 | return 0; | ||
247 | } | ||
248 | |||
249 | #else | ||
250 | #define at91wdt_suspend NULL | ||
251 | #define at91wdt_resume NULL | ||
252 | #endif | ||
253 | |||
254 | static struct platform_driver at91wdt_driver = { | ||
255 | .probe = at91wdt_probe, | ||
256 | .remove = __exit_p(at91wdt_remove), | ||
257 | .shutdown = at91wdt_shutdown, | ||
258 | .suspend = at91wdt_suspend, | ||
259 | .resume = at91wdt_resume, | ||
260 | .driver = { | ||
261 | .name = "at91_wdt", | ||
262 | .owner = THIS_MODULE, | ||
263 | }, | ||
264 | }; | ||
265 | |||
266 | static int __init at91_wdt_init(void) | ||
267 | { | ||
268 | /* Check that the heartbeat value is within range; if not reset to the default */ | ||
269 | if (at91_wdt_settimeout(wdt_time)) { | ||
270 | at91_wdt_settimeout(WDT_DEFAULT_TIME); | ||
271 | pr_info("at91_wdt: wdt_time value must be 1 <= wdt_time <= 256, using %d\n", wdt_time); | ||
272 | } | ||
273 | |||
274 | return platform_driver_register(&at91wdt_driver); | ||
275 | } | ||
276 | |||
277 | static void __exit at91_wdt_exit(void) | ||
278 | { | ||
279 | platform_driver_unregister(&at91wdt_driver); | ||
280 | } | ||
281 | |||
282 | module_init(at91_wdt_init); | ||
283 | module_exit(at91_wdt_exit); | ||
284 | |||
285 | MODULE_AUTHOR("Andrew Victor"); | ||
286 | MODULE_DESCRIPTION("Watchdog driver for Atmel AT91RM9200"); | ||
287 | MODULE_LICENSE("GPL"); | ||
288 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/bfin_wdt.c b/drivers/watchdog/bfin_wdt.c new file mode 100644 index 000000000000..309d27913fc1 --- /dev/null +++ b/drivers/watchdog/bfin_wdt.c | |||
@@ -0,0 +1,490 @@ | |||
1 | /* | ||
2 | * Blackfin On-Chip Watchdog Driver | ||
3 | * Supports BF53[123]/BF53[467]/BF54[2489]/BF561 | ||
4 | * | ||
5 | * Originally based on softdog.c | ||
6 | * Copyright 2006-2007 Analog Devices Inc. | ||
7 | * Copyright 2006-2007 Michele d'Amico | ||
8 | * Copyright 1996 Alan Cox <alan@redhat.com> | ||
9 | * | ||
10 | * Enter bugs at http://blackfin.uclinux.org/ | ||
11 | * | ||
12 | * Licensed under the GPL-2 or later. | ||
13 | */ | ||
14 | |||
15 | #include <linux/platform_device.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/moduleparam.h> | ||
18 | #include <linux/types.h> | ||
19 | #include <linux/timer.h> | ||
20 | #include <linux/miscdevice.h> | ||
21 | #include <linux/watchdog.h> | ||
22 | #include <linux/fs.h> | ||
23 | #include <linux/notifier.h> | ||
24 | #include <linux/reboot.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <linux/interrupt.h> | ||
27 | #include <asm/blackfin.h> | ||
28 | #include <asm/uaccess.h> | ||
29 | |||
30 | #define stamp(fmt, args...) pr_debug("%s:%i: " fmt "\n", __func__, __LINE__, ## args) | ||
31 | #define stampit() stamp("here i am") | ||
32 | |||
33 | #define WATCHDOG_NAME "bfin-wdt" | ||
34 | #define PFX WATCHDOG_NAME ": " | ||
35 | |||
36 | /* The BF561 has two watchdogs (one per core), but since Linux | ||
37 | * only runs on core A, we'll just work with that one. | ||
38 | */ | ||
39 | #ifdef BF561_FAMILY | ||
40 | # define bfin_read_WDOG_CTL() bfin_read_WDOGA_CTL() | ||
41 | # define bfin_read_WDOG_CNT() bfin_read_WDOGA_CNT() | ||
42 | # define bfin_read_WDOG_STAT() bfin_read_WDOGA_STAT() | ||
43 | # define bfin_write_WDOG_CTL(x) bfin_write_WDOGA_CTL(x) | ||
44 | # define bfin_write_WDOG_CNT(x) bfin_write_WDOGA_CNT(x) | ||
45 | # define bfin_write_WDOG_STAT(x) bfin_write_WDOGA_STAT(x) | ||
46 | #endif | ||
47 | |||
48 | /* Bit in SWRST that indicates boot caused by watchdog */ | ||
49 | #define SWRST_RESET_WDOG 0x4000 | ||
50 | |||
51 | /* Bit in WDOG_CTL that indicates watchdog has expired (WDR0) */ | ||
52 | #define WDOG_EXPIRED 0x8000 | ||
53 | |||
54 | /* Masks for WDEV field in WDOG_CTL register */ | ||
55 | #define ICTL_RESET 0x0 | ||
56 | #define ICTL_NMI 0x2 | ||
57 | #define ICTL_GPI 0x4 | ||
58 | #define ICTL_NONE 0x6 | ||
59 | #define ICTL_MASK 0x6 | ||
60 | |||
61 | /* Masks for WDEN field in WDOG_CTL register */ | ||
62 | #define WDEN_MASK 0x0FF0 | ||
63 | #define WDEN_ENABLE 0x0000 | ||
64 | #define WDEN_DISABLE 0x0AD0 | ||
65 | |||
66 | /* some defaults */ | ||
67 | #define WATCHDOG_TIMEOUT 20 | ||
68 | |||
69 | static unsigned int timeout = WATCHDOG_TIMEOUT; | ||
70 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
71 | static struct watchdog_info bfin_wdt_info; | ||
72 | static unsigned long open_check; | ||
73 | static char expect_close; | ||
74 | static spinlock_t bfin_wdt_spinlock = SPIN_LOCK_UNLOCKED; | ||
75 | |||
76 | /** | ||
77 | * bfin_wdt_keepalive - Keep the Userspace Watchdog Alive | ||
78 | * | ||
79 | * The Userspace watchdog got a KeepAlive: schedule the next timeout. | ||
80 | */ | ||
81 | static int bfin_wdt_keepalive(void) | ||
82 | { | ||
83 | stampit(); | ||
84 | bfin_write_WDOG_STAT(0); | ||
85 | return 0; | ||
86 | } | ||
87 | |||
88 | /** | ||
89 | * bfin_wdt_stop - Stop the Watchdog | ||
90 | * | ||
91 | * Stops the on-chip watchdog. | ||
92 | */ | ||
93 | static int bfin_wdt_stop(void) | ||
94 | { | ||
95 | stampit(); | ||
96 | bfin_write_WDOG_CTL(WDEN_DISABLE); | ||
97 | return 0; | ||
98 | } | ||
99 | |||
100 | /** | ||
101 | * bfin_wdt_start - Start the Watchdog | ||
102 | * | ||
103 | * Starts the on-chip watchdog. Automatically loads WDOG_CNT | ||
104 | * into WDOG_STAT for us. | ||
105 | */ | ||
106 | static int bfin_wdt_start(void) | ||
107 | { | ||
108 | stampit(); | ||
109 | bfin_write_WDOG_CTL(WDEN_ENABLE | ICTL_RESET); | ||
110 | return 0; | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * bfin_wdt_running - Check Watchdog status | ||
115 | * | ||
116 | * See if the watchdog is running. | ||
117 | */ | ||
118 | static int bfin_wdt_running(void) | ||
119 | { | ||
120 | stampit(); | ||
121 | return ((bfin_read_WDOG_CTL() & WDEN_MASK) != WDEN_DISABLE); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * bfin_wdt_set_timeout - Set the Userspace Watchdog timeout | ||
126 | * @t: new timeout value (in seconds) | ||
127 | * | ||
128 | * Translate the specified timeout in seconds into System Clock | ||
129 | * terms which is what the on-chip Watchdog requires. | ||
130 | */ | ||
131 | static int bfin_wdt_set_timeout(unsigned long t) | ||
132 | { | ||
133 | u32 cnt; | ||
134 | unsigned long flags; | ||
135 | |||
136 | stampit(); | ||
137 | |||
138 | cnt = t * get_sclk(); | ||
139 | if (cnt < get_sclk()) { | ||
140 | printk(KERN_WARNING PFX "timeout value is too large\n"); | ||
141 | return -EINVAL; | ||
142 | } | ||
143 | |||
144 | spin_lock_irqsave(&bfin_wdt_spinlock, flags); | ||
145 | { | ||
146 | int run = bfin_wdt_running(); | ||
147 | bfin_wdt_stop(); | ||
148 | bfin_write_WDOG_CNT(cnt); | ||
149 | if (run) bfin_wdt_start(); | ||
150 | } | ||
151 | spin_unlock_irqrestore(&bfin_wdt_spinlock, flags); | ||
152 | |||
153 | timeout = t; | ||
154 | |||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * bfin_wdt_open - Open the Device | ||
160 | * @inode: inode of device | ||
161 | * @file: file handle of device | ||
162 | * | ||
163 | * Watchdog device is opened and started. | ||
164 | */ | ||
165 | static int bfin_wdt_open(struct inode *inode, struct file *file) | ||
166 | { | ||
167 | stampit(); | ||
168 | |||
169 | if (test_and_set_bit(0, &open_check)) | ||
170 | return -EBUSY; | ||
171 | |||
172 | if (nowayout) | ||
173 | __module_get(THIS_MODULE); | ||
174 | |||
175 | bfin_wdt_keepalive(); | ||
176 | bfin_wdt_start(); | ||
177 | |||
178 | return nonseekable_open(inode, file); | ||
179 | } | ||
180 | |||
181 | /** | ||
182 | * bfin_wdt_close - Close the Device | ||
183 | * @inode: inode of device | ||
184 | * @file: file handle of device | ||
185 | * | ||
186 | * Watchdog device is closed and stopped. | ||
187 | */ | ||
188 | static int bfin_wdt_release(struct inode *inode, struct file *file) | ||
189 | { | ||
190 | stampit(); | ||
191 | |||
192 | if (expect_close == 42) { | ||
193 | bfin_wdt_stop(); | ||
194 | } else { | ||
195 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
196 | bfin_wdt_keepalive(); | ||
197 | } | ||
198 | |||
199 | expect_close = 0; | ||
200 | clear_bit(0, &open_check); | ||
201 | |||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | /** | ||
206 | * bfin_wdt_write - Write to Device | ||
207 | * @file: file handle of device | ||
208 | * @buf: buffer to write | ||
209 | * @count: length of buffer | ||
210 | * @ppos: offset | ||
211 | * | ||
212 | * Pings the watchdog on write. | ||
213 | */ | ||
214 | static ssize_t bfin_wdt_write(struct file *file, const char __user *data, | ||
215 | size_t len, loff_t *ppos) | ||
216 | { | ||
217 | stampit(); | ||
218 | |||
219 | if (len) { | ||
220 | if (!nowayout) { | ||
221 | size_t i; | ||
222 | |||
223 | /* In case it was set long ago */ | ||
224 | expect_close = 0; | ||
225 | |||
226 | for (i = 0; i != len; i++) { | ||
227 | char c; | ||
228 | if (get_user(c, data + i)) | ||
229 | return -EFAULT; | ||
230 | if (c == 'V') | ||
231 | expect_close = 42; | ||
232 | } | ||
233 | } | ||
234 | bfin_wdt_keepalive(); | ||
235 | } | ||
236 | |||
237 | return len; | ||
238 | } | ||
239 | |||
240 | /** | ||
241 | * bfin_wdt_ioctl - Query Device | ||
242 | * @inode: inode of device | ||
243 | * @file: file handle of device | ||
244 | * @cmd: watchdog command | ||
245 | * @arg: argument | ||
246 | * | ||
247 | * Query basic information from the device or ping it, as outlined by the | ||
248 | * watchdog API. | ||
249 | */ | ||
250 | static int bfin_wdt_ioctl(struct inode *inode, struct file *file, | ||
251 | unsigned int cmd, unsigned long arg) | ||
252 | { | ||
253 | void __user *argp = (void __user *)arg; | ||
254 | int __user *p = argp; | ||
255 | |||
256 | stampit(); | ||
257 | |||
258 | switch (cmd) { | ||
259 | default: | ||
260 | return -ENOTTY; | ||
261 | |||
262 | case WDIOC_GETSUPPORT: | ||
263 | if (copy_to_user(argp, &bfin_wdt_info, sizeof(bfin_wdt_info))) | ||
264 | return -EFAULT; | ||
265 | else | ||
266 | return 0; | ||
267 | |||
268 | case WDIOC_GETSTATUS: | ||
269 | case WDIOC_GETBOOTSTATUS: | ||
270 | return put_user(!!(_bfin_swrst & SWRST_RESET_WDOG), p); | ||
271 | |||
272 | case WDIOC_KEEPALIVE: | ||
273 | bfin_wdt_keepalive(); | ||
274 | return 0; | ||
275 | |||
276 | case WDIOC_SETTIMEOUT: { | ||
277 | int new_timeout; | ||
278 | |||
279 | if (get_user(new_timeout, p)) | ||
280 | return -EFAULT; | ||
281 | |||
282 | if (bfin_wdt_set_timeout(new_timeout)) | ||
283 | return -EINVAL; | ||
284 | } | ||
285 | /* Fall */ | ||
286 | case WDIOC_GETTIMEOUT: | ||
287 | return put_user(timeout, p); | ||
288 | |||
289 | case WDIOC_SETOPTIONS: { | ||
290 | unsigned long flags; | ||
291 | int options, ret = -EINVAL; | ||
292 | |||
293 | if (get_user(options, p)) | ||
294 | return -EFAULT; | ||
295 | |||
296 | spin_lock_irqsave(&bfin_wdt_spinlock, flags); | ||
297 | |||
298 | if (options & WDIOS_DISABLECARD) { | ||
299 | bfin_wdt_stop(); | ||
300 | ret = 0; | ||
301 | } | ||
302 | |||
303 | if (options & WDIOS_ENABLECARD) { | ||
304 | bfin_wdt_start(); | ||
305 | ret = 0; | ||
306 | } | ||
307 | |||
308 | spin_unlock_irqrestore(&bfin_wdt_spinlock, flags); | ||
309 | |||
310 | return ret; | ||
311 | } | ||
312 | } | ||
313 | } | ||
314 | |||
315 | /** | ||
316 | * bfin_wdt_notify_sys - Notifier Handler | ||
317 | * @this: notifier block | ||
318 | * @code: notifier event | ||
319 | * @unused: unused | ||
320 | * | ||
321 | * Handles specific events, such as turning off the watchdog during a | ||
322 | * shutdown event. | ||
323 | */ | ||
324 | static int bfin_wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
325 | void *unused) | ||
326 | { | ||
327 | stampit(); | ||
328 | |||
329 | if (code == SYS_DOWN || code == SYS_HALT) | ||
330 | bfin_wdt_stop(); | ||
331 | |||
332 | return NOTIFY_DONE; | ||
333 | } | ||
334 | |||
335 | #ifdef CONFIG_PM | ||
336 | static int state_before_suspend; | ||
337 | |||
338 | /** | ||
339 | * bfin_wdt_suspend - suspend the watchdog | ||
340 | * @pdev: device being suspended | ||
341 | * @state: requested suspend state | ||
342 | * | ||
343 | * Remember if the watchdog was running and stop it. | ||
344 | * TODO: is this even right? Doesn't seem to be any | ||
345 | * standard in the watchdog world ... | ||
346 | */ | ||
347 | static int bfin_wdt_suspend(struct platform_device *pdev, pm_message_t state) | ||
348 | { | ||
349 | stampit(); | ||
350 | |||
351 | state_before_suspend = bfin_wdt_running(); | ||
352 | bfin_wdt_stop(); | ||
353 | |||
354 | return 0; | ||
355 | } | ||
356 | |||
357 | /** | ||
358 | * bfin_wdt_resume - resume the watchdog | ||
359 | * @pdev: device being resumed | ||
360 | * | ||
361 | * If the watchdog was running, turn it back on. | ||
362 | */ | ||
363 | static int bfin_wdt_resume(struct platform_device *pdev) | ||
364 | { | ||
365 | stampit(); | ||
366 | |||
367 | if (state_before_suspend) { | ||
368 | bfin_wdt_set_timeout(timeout); | ||
369 | bfin_wdt_start(); | ||
370 | } | ||
371 | |||
372 | return 0; | ||
373 | } | ||
374 | #else | ||
375 | # define bfin_wdt_suspend NULL | ||
376 | # define bfin_wdt_resume NULL | ||
377 | #endif | ||
378 | |||
379 | static struct platform_device bfin_wdt_device = { | ||
380 | .name = WATCHDOG_NAME, | ||
381 | .id = -1, | ||
382 | }; | ||
383 | |||
384 | static struct platform_driver bfin_wdt_driver = { | ||
385 | .driver = { | ||
386 | .name = WATCHDOG_NAME, | ||
387 | .owner = THIS_MODULE, | ||
388 | }, | ||
389 | .suspend = bfin_wdt_suspend, | ||
390 | .resume = bfin_wdt_resume, | ||
391 | }; | ||
392 | |||
393 | static struct file_operations bfin_wdt_fops = { | ||
394 | .owner = THIS_MODULE, | ||
395 | .llseek = no_llseek, | ||
396 | .write = bfin_wdt_write, | ||
397 | .ioctl = bfin_wdt_ioctl, | ||
398 | .open = bfin_wdt_open, | ||
399 | .release = bfin_wdt_release, | ||
400 | }; | ||
401 | |||
402 | static struct miscdevice bfin_wdt_miscdev = { | ||
403 | .minor = WATCHDOG_MINOR, | ||
404 | .name = "watchdog", | ||
405 | .fops = &bfin_wdt_fops, | ||
406 | }; | ||
407 | |||
408 | static struct watchdog_info bfin_wdt_info = { | ||
409 | .identity = "Blackfin Watchdog", | ||
410 | .options = WDIOF_SETTIMEOUT | | ||
411 | WDIOF_KEEPALIVEPING | | ||
412 | WDIOF_MAGICCLOSE, | ||
413 | }; | ||
414 | |||
415 | static struct notifier_block bfin_wdt_notifier = { | ||
416 | .notifier_call = bfin_wdt_notify_sys, | ||
417 | }; | ||
418 | |||
419 | /** | ||
420 | * bfin_wdt_init - Initialize module | ||
421 | * | ||
422 | * Registers the device and notifier handler. Actual device | ||
423 | * initialization is handled by bfin_wdt_open(). | ||
424 | */ | ||
425 | static int __init bfin_wdt_init(void) | ||
426 | { | ||
427 | int ret; | ||
428 | |||
429 | stampit(); | ||
430 | |||
431 | /* Check that the timeout value is within range */ | ||
432 | if (bfin_wdt_set_timeout(timeout)) | ||
433 | return -EINVAL; | ||
434 | |||
435 | /* Since this is an on-chip device and needs no board-specific | ||
436 | * resources, we'll handle all the platform device stuff here. | ||
437 | */ | ||
438 | ret = platform_device_register(&bfin_wdt_device); | ||
439 | if (ret) | ||
440 | return ret; | ||
441 | |||
442 | ret = platform_driver_probe(&bfin_wdt_driver, NULL); | ||
443 | if (ret) | ||
444 | return ret; | ||
445 | |||
446 | ret = register_reboot_notifier(&bfin_wdt_notifier); | ||
447 | if (ret) { | ||
448 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret); | ||
449 | return ret; | ||
450 | } | ||
451 | |||
452 | ret = misc_register(&bfin_wdt_miscdev); | ||
453 | if (ret) { | ||
454 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
455 | WATCHDOG_MINOR, ret); | ||
456 | unregister_reboot_notifier(&bfin_wdt_notifier); | ||
457 | return ret; | ||
458 | } | ||
459 | |||
460 | printk(KERN_INFO PFX "initialized: timeout=%d sec (nowayout=%d)\n", | ||
461 | timeout, nowayout); | ||
462 | |||
463 | return 0; | ||
464 | } | ||
465 | |||
466 | /** | ||
467 | * bfin_wdt_exit - Deinitialize module | ||
468 | * | ||
469 | * Unregisters the device and notifier handler. Actual device | ||
470 | * deinitialization is handled by bfin_wdt_close(). | ||
471 | */ | ||
472 | static void __exit bfin_wdt_exit(void) | ||
473 | { | ||
474 | misc_deregister(&bfin_wdt_miscdev); | ||
475 | unregister_reboot_notifier(&bfin_wdt_notifier); | ||
476 | } | ||
477 | |||
478 | module_init(bfin_wdt_init); | ||
479 | module_exit(bfin_wdt_exit); | ||
480 | |||
481 | MODULE_AUTHOR("Michele d'Amico, Mike Frysinger <vapier@gentoo.org>"); | ||
482 | MODULE_DESCRIPTION("Blackfin Watchdog Device Driver"); | ||
483 | MODULE_LICENSE("GPL"); | ||
484 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
485 | |||
486 | module_param(timeout, uint, 0); | ||
487 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=((2^32)/SCLK), default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
488 | |||
489 | module_param(nowayout, int, 0); | ||
490 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
diff --git a/drivers/watchdog/booke_wdt.c b/drivers/watchdog/booke_wdt.c new file mode 100644 index 000000000000..d362f5bf658a --- /dev/null +++ b/drivers/watchdog/booke_wdt.c | |||
@@ -0,0 +1,194 @@ | |||
1 | /* | ||
2 | * drivers/char/watchdog/booke_wdt.c | ||
3 | * | ||
4 | * Watchdog timer for PowerPC Book-E systems | ||
5 | * | ||
6 | * Author: Matthew McClintock | ||
7 | * Maintainer: Kumar Gala <galak@kernel.crashing.org> | ||
8 | * | ||
9 | * Copyright 2005 Freescale Semiconductor Inc. | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify it | ||
12 | * under the terms of the GNU General Public License as published by the | ||
13 | * Free Software Foundation; either version 2 of the License, or (at your | ||
14 | * option) any later version. | ||
15 | */ | ||
16 | |||
17 | #include <linux/module.h> | ||
18 | #include <linux/fs.h> | ||
19 | #include <linux/miscdevice.h> | ||
20 | #include <linux/notifier.h> | ||
21 | #include <linux/watchdog.h> | ||
22 | |||
23 | #include <asm/reg_booke.h> | ||
24 | #include <asm/uaccess.h> | ||
25 | #include <asm/system.h> | ||
26 | |||
27 | /* If the kernel parameter wdt=1, the watchdog will be enabled at boot. | ||
28 | * Also, the wdt_period sets the watchdog timer period timeout. | ||
29 | * For E500 cpus the wdt_period sets which bit changing from 0->1 will | ||
30 | * trigger a watchog timeout. This watchdog timeout will occur 3 times, the | ||
31 | * first time nothing will happen, the second time a watchdog exception will | ||
32 | * occur, and the final time the board will reset. | ||
33 | */ | ||
34 | |||
35 | #ifdef CONFIG_FSL_BOOKE | ||
36 | #define WDT_PERIOD_DEFAULT 63 /* Ex. wdt_period=28 bus=333Mhz , reset=~40sec */ | ||
37 | #else | ||
38 | #define WDT_PERIOD_DEFAULT 3 /* Refer to the PPC40x and PPC4xx manuals */ | ||
39 | #endif /* for timing information */ | ||
40 | |||
41 | u32 booke_wdt_enabled = 0; | ||
42 | u32 booke_wdt_period = WDT_PERIOD_DEFAULT; | ||
43 | |||
44 | #ifdef CONFIG_FSL_BOOKE | ||
45 | #define WDTP(x) ((((63-x)&0x3)<<30)|(((63-x)&0x3c)<<15)) | ||
46 | #else | ||
47 | #define WDTP(x) (TCR_WP(x)) | ||
48 | #endif | ||
49 | |||
50 | /* | ||
51 | * booke_wdt_ping: | ||
52 | */ | ||
53 | static __inline__ void booke_wdt_ping(void) | ||
54 | { | ||
55 | mtspr(SPRN_TSR, TSR_ENW|TSR_WIS); | ||
56 | } | ||
57 | |||
58 | /* | ||
59 | * booke_wdt_enable: | ||
60 | */ | ||
61 | static __inline__ void booke_wdt_enable(void) | ||
62 | { | ||
63 | u32 val; | ||
64 | |||
65 | /* clear status before enabling watchdog */ | ||
66 | booke_wdt_ping(); | ||
67 | val = mfspr(SPRN_TCR); | ||
68 | val |= (TCR_WIE|TCR_WRC(WRC_CHIP)|WDTP(booke_wdt_period)); | ||
69 | |||
70 | mtspr(SPRN_TCR, val); | ||
71 | } | ||
72 | |||
73 | /* | ||
74 | * booke_wdt_write: | ||
75 | */ | ||
76 | static ssize_t booke_wdt_write (struct file *file, const char __user *buf, | ||
77 | size_t count, loff_t *ppos) | ||
78 | { | ||
79 | booke_wdt_ping(); | ||
80 | return count; | ||
81 | } | ||
82 | |||
83 | static struct watchdog_info ident = { | ||
84 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
85 | .firmware_version = 0, | ||
86 | .identity = "PowerPC Book-E Watchdog", | ||
87 | }; | ||
88 | |||
89 | /* | ||
90 | * booke_wdt_ioctl: | ||
91 | */ | ||
92 | static int booke_wdt_ioctl (struct inode *inode, struct file *file, | ||
93 | unsigned int cmd, unsigned long arg) | ||
94 | { | ||
95 | u32 tmp = 0; | ||
96 | u32 __user *p = (u32 __user *)arg; | ||
97 | |||
98 | switch (cmd) { | ||
99 | case WDIOC_GETSUPPORT: | ||
100 | if (copy_to_user ((struct watchdog_info __user *) arg, &ident, | ||
101 | sizeof(struct watchdog_info))) | ||
102 | return -EFAULT; | ||
103 | case WDIOC_GETSTATUS: | ||
104 | return put_user(ident.options, p); | ||
105 | case WDIOC_GETBOOTSTATUS: | ||
106 | /* XXX: something is clearing TSR */ | ||
107 | tmp = mfspr(SPRN_TSR) & TSR_WRS(3); | ||
108 | /* returns 1 if last reset was caused by the WDT */ | ||
109 | return (tmp ? 1 : 0); | ||
110 | case WDIOC_KEEPALIVE: | ||
111 | booke_wdt_ping(); | ||
112 | return 0; | ||
113 | case WDIOC_SETTIMEOUT: | ||
114 | if (get_user(booke_wdt_period, p)) | ||
115 | return -EFAULT; | ||
116 | mtspr(SPRN_TCR, (mfspr(SPRN_TCR)&~WDTP(0))|WDTP(booke_wdt_period)); | ||
117 | return 0; | ||
118 | case WDIOC_GETTIMEOUT: | ||
119 | return put_user(booke_wdt_period, p); | ||
120 | case WDIOC_SETOPTIONS: | ||
121 | if (get_user(tmp, p)) | ||
122 | return -EINVAL; | ||
123 | if (tmp == WDIOS_ENABLECARD) { | ||
124 | booke_wdt_ping(); | ||
125 | break; | ||
126 | } else | ||
127 | return -EINVAL; | ||
128 | return 0; | ||
129 | default: | ||
130 | return -ENOTTY; | ||
131 | } | ||
132 | |||
133 | return 0; | ||
134 | } | ||
135 | /* | ||
136 | * booke_wdt_open: | ||
137 | */ | ||
138 | static int booke_wdt_open (struct inode *inode, struct file *file) | ||
139 | { | ||
140 | if (booke_wdt_enabled == 0) { | ||
141 | booke_wdt_enabled = 1; | ||
142 | booke_wdt_enable(); | ||
143 | printk (KERN_INFO "PowerPC Book-E Watchdog Timer Enabled (wdt_period=%d)\n", | ||
144 | booke_wdt_period); | ||
145 | } | ||
146 | |||
147 | return nonseekable_open(inode, file); | ||
148 | } | ||
149 | |||
150 | static const struct file_operations booke_wdt_fops = { | ||
151 | .owner = THIS_MODULE, | ||
152 | .llseek = no_llseek, | ||
153 | .write = booke_wdt_write, | ||
154 | .ioctl = booke_wdt_ioctl, | ||
155 | .open = booke_wdt_open, | ||
156 | }; | ||
157 | |||
158 | static struct miscdevice booke_wdt_miscdev = { | ||
159 | .minor = WATCHDOG_MINOR, | ||
160 | .name = "watchdog", | ||
161 | .fops = &booke_wdt_fops, | ||
162 | }; | ||
163 | |||
164 | static void __exit booke_wdt_exit(void) | ||
165 | { | ||
166 | misc_deregister(&booke_wdt_miscdev); | ||
167 | } | ||
168 | |||
169 | /* | ||
170 | * booke_wdt_init: | ||
171 | */ | ||
172 | static int __init booke_wdt_init(void) | ||
173 | { | ||
174 | int ret = 0; | ||
175 | |||
176 | printk (KERN_INFO "PowerPC Book-E Watchdog Timer Loaded\n"); | ||
177 | ident.firmware_version = cur_cpu_spec->pvr_value; | ||
178 | |||
179 | ret = misc_register(&booke_wdt_miscdev); | ||
180 | if (ret) { | ||
181 | printk (KERN_CRIT "Cannot register miscdev on minor=%d (err=%d)\n", | ||
182 | WATCHDOG_MINOR, ret); | ||
183 | return ret; | ||
184 | } | ||
185 | |||
186 | if (booke_wdt_enabled == 1) { | ||
187 | printk (KERN_INFO "PowerPC Book-E Watchdog Timer Enabled (wdt_period=%d)\n", | ||
188 | booke_wdt_period); | ||
189 | booke_wdt_enable(); | ||
190 | } | ||
191 | |||
192 | return ret; | ||
193 | } | ||
194 | device_initcall(booke_wdt_init); | ||
diff --git a/drivers/watchdog/cpu5wdt.c b/drivers/watchdog/cpu5wdt.c new file mode 100644 index 000000000000..5941ca601a3a --- /dev/null +++ b/drivers/watchdog/cpu5wdt.c | |||
@@ -0,0 +1,304 @@ | |||
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 <linux/completion.h> | ||
32 | #include <linux/jiffies.h> | ||
33 | #include <asm/io.h> | ||
34 | #include <asm/uaccess.h> | ||
35 | |||
36 | #include <linux/watchdog.h> | ||
37 | |||
38 | /* adjustable parameters */ | ||
39 | |||
40 | static int verbose = 0; | ||
41 | static int port = 0x91; | ||
42 | static int ticks = 10000; | ||
43 | |||
44 | #define PFX "cpu5wdt: " | ||
45 | |||
46 | #define CPU5WDT_EXTENT 0x0A | ||
47 | |||
48 | #define CPU5WDT_STATUS_REG 0x00 | ||
49 | #define CPU5WDT_TIME_A_REG 0x02 | ||
50 | #define CPU5WDT_TIME_B_REG 0x03 | ||
51 | #define CPU5WDT_MODE_REG 0x04 | ||
52 | #define CPU5WDT_TRIGGER_REG 0x07 | ||
53 | #define CPU5WDT_ENABLE_REG 0x08 | ||
54 | #define CPU5WDT_RESET_REG 0x09 | ||
55 | |||
56 | #define CPU5WDT_INTERVAL (HZ/10+1) | ||
57 | |||
58 | /* some device data */ | ||
59 | |||
60 | static struct { | ||
61 | struct completion stop; | ||
62 | volatile int running; | ||
63 | struct timer_list timer; | ||
64 | volatile int queue; | ||
65 | int default_ticks; | ||
66 | unsigned long inuse; | ||
67 | } cpu5wdt_device; | ||
68 | |||
69 | /* generic helper functions */ | ||
70 | |||
71 | static void cpu5wdt_trigger(unsigned long unused) | ||
72 | { | ||
73 | if ( verbose > 2 ) | ||
74 | printk(KERN_DEBUG PFX "trigger at %i ticks\n", ticks); | ||
75 | |||
76 | if( cpu5wdt_device.running ) | ||
77 | ticks--; | ||
78 | |||
79 | /* keep watchdog alive */ | ||
80 | outb(1, port + CPU5WDT_TRIGGER_REG); | ||
81 | |||
82 | /* requeue?? */ | ||
83 | if (cpu5wdt_device.queue && ticks) | ||
84 | mod_timer(&cpu5wdt_device.timer, jiffies + CPU5WDT_INTERVAL); | ||
85 | else { | ||
86 | /* ticks doesn't matter anyway */ | ||
87 | complete(&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 | mod_timer(&cpu5wdt_device.timer, jiffies + CPU5WDT_INTERVAL); | ||
111 | } | ||
112 | /* if process dies, counter is not decremented */ | ||
113 | cpu5wdt_device.running++; | ||
114 | } | ||
115 | |||
116 | static int cpu5wdt_stop(void) | ||
117 | { | ||
118 | if ( cpu5wdt_device.running ) | ||
119 | cpu5wdt_device.running = 0; | ||
120 | |||
121 | ticks = cpu5wdt_device.default_ticks; | ||
122 | |||
123 | if ( verbose ) | ||
124 | printk(KERN_CRIT PFX "stop not possible\n"); | ||
125 | |||
126 | return -EIO; | ||
127 | } | ||
128 | |||
129 | /* filesystem operations */ | ||
130 | |||
131 | static int cpu5wdt_open(struct inode *inode, struct file *file) | ||
132 | { | ||
133 | if ( test_and_set_bit(0, &cpu5wdt_device.inuse) ) | ||
134 | return -EBUSY; | ||
135 | |||
136 | return nonseekable_open(inode, file); | ||
137 | } | ||
138 | |||
139 | static int cpu5wdt_release(struct inode *inode, struct file *file) | ||
140 | { | ||
141 | clear_bit(0, &cpu5wdt_device.inuse); | ||
142 | return 0; | ||
143 | } | ||
144 | |||
145 | static int cpu5wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) | ||
146 | { | ||
147 | void __user *argp = (void __user *)arg; | ||
148 | unsigned int value; | ||
149 | static struct watchdog_info ident = | ||
150 | { | ||
151 | .options = WDIOF_CARDRESET, | ||
152 | .identity = "CPU5 WDT", | ||
153 | }; | ||
154 | |||
155 | switch(cmd) { | ||
156 | case WDIOC_KEEPALIVE: | ||
157 | cpu5wdt_reset(); | ||
158 | break; | ||
159 | case WDIOC_GETSTATUS: | ||
160 | value = inb(port + CPU5WDT_STATUS_REG); | ||
161 | value = (value >> 2) & 1; | ||
162 | if ( copy_to_user(argp, &value, sizeof(int)) ) | ||
163 | return -EFAULT; | ||
164 | break; | ||
165 | case WDIOC_GETBOOTSTATUS: | ||
166 | if ( copy_to_user(argp, &value, sizeof(int)) ) | ||
167 | return -EFAULT; | ||
168 | break; | ||
169 | case WDIOC_GETSUPPORT: | ||
170 | if ( copy_to_user(argp, &ident, sizeof(ident)) ) | ||
171 | return -EFAULT; | ||
172 | break; | ||
173 | case WDIOC_SETOPTIONS: | ||
174 | if ( copy_from_user(&value, argp, sizeof(int)) ) | ||
175 | return -EFAULT; | ||
176 | switch(value) { | ||
177 | case WDIOS_ENABLECARD: | ||
178 | cpu5wdt_start(); | ||
179 | break; | ||
180 | case WDIOS_DISABLECARD: | ||
181 | return cpu5wdt_stop(); | ||
182 | default: | ||
183 | return -EINVAL; | ||
184 | } | ||
185 | break; | ||
186 | default: | ||
187 | return -ENOTTY; | ||
188 | } | ||
189 | return 0; | ||
190 | } | ||
191 | |||
192 | static ssize_t cpu5wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
193 | { | ||
194 | if ( !count ) | ||
195 | return -EIO; | ||
196 | |||
197 | cpu5wdt_reset(); | ||
198 | |||
199 | return count; | ||
200 | } | ||
201 | |||
202 | static const struct file_operations cpu5wdt_fops = { | ||
203 | .owner = THIS_MODULE, | ||
204 | .llseek = no_llseek, | ||
205 | .ioctl = cpu5wdt_ioctl, | ||
206 | .open = cpu5wdt_open, | ||
207 | .write = cpu5wdt_write, | ||
208 | .release = cpu5wdt_release, | ||
209 | }; | ||
210 | |||
211 | static struct miscdevice cpu5wdt_misc = { | ||
212 | .minor = WATCHDOG_MINOR, | ||
213 | .name = "watchdog", | ||
214 | .fops = &cpu5wdt_fops, | ||
215 | }; | ||
216 | |||
217 | /* init/exit function */ | ||
218 | |||
219 | static int __devinit cpu5wdt_init(void) | ||
220 | { | ||
221 | unsigned int val; | ||
222 | int err; | ||
223 | |||
224 | if ( verbose ) | ||
225 | printk(KERN_DEBUG PFX "port=0x%x, verbose=%i\n", port, verbose); | ||
226 | |||
227 | if ( !request_region(port, CPU5WDT_EXTENT, PFX) ) { | ||
228 | printk(KERN_ERR PFX "request_region failed\n"); | ||
229 | err = -EBUSY; | ||
230 | goto no_port; | ||
231 | } | ||
232 | |||
233 | if ( (err = misc_register(&cpu5wdt_misc)) < 0 ) { | ||
234 | printk(KERN_ERR PFX "misc_register failed\n"); | ||
235 | goto no_misc; | ||
236 | } | ||
237 | |||
238 | /* watchdog reboot? */ | ||
239 | val = inb(port + CPU5WDT_STATUS_REG); | ||
240 | val = (val >> 2) & 1; | ||
241 | if ( !val ) | ||
242 | printk(KERN_INFO PFX "sorry, was my fault\n"); | ||
243 | |||
244 | init_completion(&cpu5wdt_device.stop); | ||
245 | cpu5wdt_device.queue = 0; | ||
246 | |||
247 | clear_bit(0, &cpu5wdt_device.inuse); | ||
248 | |||
249 | setup_timer(&cpu5wdt_device.timer, cpu5wdt_trigger, 0); | ||
250 | |||
251 | cpu5wdt_device.default_ticks = ticks; | ||
252 | |||
253 | printk(KERN_INFO PFX "init success\n"); | ||
254 | |||
255 | return 0; | ||
256 | |||
257 | no_misc: | ||
258 | release_region(port, CPU5WDT_EXTENT); | ||
259 | no_port: | ||
260 | return err; | ||
261 | } | ||
262 | |||
263 | static int __devinit cpu5wdt_init_module(void) | ||
264 | { | ||
265 | return cpu5wdt_init(); | ||
266 | } | ||
267 | |||
268 | static void __devexit cpu5wdt_exit(void) | ||
269 | { | ||
270 | if ( cpu5wdt_device.queue ) { | ||
271 | cpu5wdt_device.queue = 0; | ||
272 | wait_for_completion(&cpu5wdt_device.stop); | ||
273 | } | ||
274 | |||
275 | misc_deregister(&cpu5wdt_misc); | ||
276 | |||
277 | release_region(port, CPU5WDT_EXTENT); | ||
278 | |||
279 | } | ||
280 | |||
281 | static void __devexit cpu5wdt_exit_module(void) | ||
282 | { | ||
283 | cpu5wdt_exit(); | ||
284 | } | ||
285 | |||
286 | /* module entry points */ | ||
287 | |||
288 | module_init(cpu5wdt_init_module); | ||
289 | module_exit(cpu5wdt_exit_module); | ||
290 | |||
291 | MODULE_AUTHOR("Heiko Ronsdorf <hero@ihg.uni-duisburg.de>"); | ||
292 | MODULE_DESCRIPTION("sma cpu5 watchdog driver"); | ||
293 | MODULE_SUPPORTED_DEVICE("sma cpu5 watchdog"); | ||
294 | MODULE_LICENSE("GPL"); | ||
295 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
296 | |||
297 | module_param(port, int, 0); | ||
298 | MODULE_PARM_DESC(port, "base address of watchdog card, default is 0x91"); | ||
299 | |||
300 | module_param(verbose, int, 0); | ||
301 | MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)"); | ||
302 | |||
303 | module_param(ticks, int, 0); | ||
304 | MODULE_PARM_DESC(ticks, "count down ticks, default is 10000"); | ||
diff --git a/drivers/watchdog/davinci_wdt.c b/drivers/watchdog/davinci_wdt.c new file mode 100644 index 000000000000..19db5302ba6e --- /dev/null +++ b/drivers/watchdog/davinci_wdt.c | |||
@@ -0,0 +1,281 @@ | |||
1 | /* | ||
2 | * drivers/char/watchdog/davinci_wdt.c | ||
3 | * | ||
4 | * Watchdog driver for DaVinci DM644x/DM646x processors | ||
5 | * | ||
6 | * Copyright (C) 2006 Texas Instruments. | ||
7 | * | ||
8 | * 2007 (c) MontaVista Software, Inc. This file is licensed under | ||
9 | * the terms of the GNU General Public License version 2. This program | ||
10 | * is licensed "as is" without any warranty of any kind, whether express | ||
11 | * or implied. | ||
12 | */ | ||
13 | |||
14 | #include <linux/module.h> | ||
15 | #include <linux/moduleparam.h> | ||
16 | #include <linux/types.h> | ||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/fs.h> | ||
19 | #include <linux/miscdevice.h> | ||
20 | #include <linux/watchdog.h> | ||
21 | #include <linux/init.h> | ||
22 | #include <linux/bitops.h> | ||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/spinlock.h> | ||
25 | |||
26 | #include <asm/hardware.h> | ||
27 | #include <asm/uaccess.h> | ||
28 | #include <asm/io.h> | ||
29 | |||
30 | #define MODULE_NAME "DAVINCI-WDT: " | ||
31 | |||
32 | #define DEFAULT_HEARTBEAT 60 | ||
33 | #define MAX_HEARTBEAT 600 /* really the max margin is 264/27MHz*/ | ||
34 | |||
35 | /* Timer register set definition */ | ||
36 | #define PID12 (0x0) | ||
37 | #define EMUMGT (0x4) | ||
38 | #define TIM12 (0x10) | ||
39 | #define TIM34 (0x14) | ||
40 | #define PRD12 (0x18) | ||
41 | #define PRD34 (0x1C) | ||
42 | #define TCR (0x20) | ||
43 | #define TGCR (0x24) | ||
44 | #define WDTCR (0x28) | ||
45 | |||
46 | /* TCR bit definitions */ | ||
47 | #define ENAMODE12_DISABLED (0 << 6) | ||
48 | #define ENAMODE12_ONESHOT (1 << 6) | ||
49 | #define ENAMODE12_PERIODIC (2 << 6) | ||
50 | |||
51 | /* TGCR bit definitions */ | ||
52 | #define TIM12RS_UNRESET (1 << 0) | ||
53 | #define TIM34RS_UNRESET (1 << 1) | ||
54 | #define TIMMODE_64BIT_WDOG (2 << 2) | ||
55 | |||
56 | /* WDTCR bit definitions */ | ||
57 | #define WDEN (1 << 14) | ||
58 | #define WDFLAG (1 << 15) | ||
59 | #define WDKEY_SEQ0 (0xa5c6 << 16) | ||
60 | #define WDKEY_SEQ1 (0xda7e << 16) | ||
61 | |||
62 | static int heartbeat = DEFAULT_HEARTBEAT; | ||
63 | |||
64 | static spinlock_t io_lock; | ||
65 | static unsigned long wdt_status; | ||
66 | #define WDT_IN_USE 0 | ||
67 | #define WDT_OK_TO_CLOSE 1 | ||
68 | #define WDT_REGION_INITED 2 | ||
69 | #define WDT_DEVICE_INITED 3 | ||
70 | |||
71 | static struct resource *wdt_mem; | ||
72 | static void __iomem *wdt_base; | ||
73 | |||
74 | static void wdt_service(void) | ||
75 | { | ||
76 | spin_lock(&io_lock); | ||
77 | |||
78 | /* put watchdog in service state */ | ||
79 | davinci_writel(WDKEY_SEQ0, wdt_base + WDTCR); | ||
80 | /* put watchdog in active state */ | ||
81 | davinci_writel(WDKEY_SEQ1, wdt_base + WDTCR); | ||
82 | |||
83 | spin_unlock(&io_lock); | ||
84 | } | ||
85 | |||
86 | static void wdt_enable(void) | ||
87 | { | ||
88 | u32 tgcr; | ||
89 | u32 timer_margin; | ||
90 | |||
91 | spin_lock(&io_lock); | ||
92 | |||
93 | /* disable, internal clock source */ | ||
94 | davinci_writel(0, wdt_base + TCR); | ||
95 | /* reset timer, set mode to 64-bit watchdog, and unreset */ | ||
96 | davinci_writel(0, wdt_base + TGCR); | ||
97 | tgcr = TIMMODE_64BIT_WDOG | TIM12RS_UNRESET | TIM34RS_UNRESET; | ||
98 | davinci_writel(tgcr, wdt_base + TGCR); | ||
99 | /* clear counter regs */ | ||
100 | davinci_writel(0, wdt_base + TIM12); | ||
101 | davinci_writel(0, wdt_base + TIM34); | ||
102 | /* set timeout period */ | ||
103 | timer_margin = (((u64)heartbeat * CLOCK_TICK_RATE) & 0xffffffff); | ||
104 | davinci_writel(timer_margin, wdt_base + PRD12); | ||
105 | timer_margin = (((u64)heartbeat * CLOCK_TICK_RATE) >> 32); | ||
106 | davinci_writel(timer_margin, wdt_base + PRD34); | ||
107 | /* enable run continuously */ | ||
108 | davinci_writel(ENAMODE12_PERIODIC, wdt_base + TCR); | ||
109 | /* Once the WDT is in pre-active state write to | ||
110 | * TIM12, TIM34, PRD12, PRD34, TCR, TGCR, WDTCR are | ||
111 | * write protected (except for the WDKEY field) | ||
112 | */ | ||
113 | /* put watchdog in pre-active state */ | ||
114 | davinci_writel(WDKEY_SEQ0 | WDEN, wdt_base + WDTCR); | ||
115 | /* put watchdog in active state */ | ||
116 | davinci_writel(WDKEY_SEQ1 | WDEN, wdt_base + WDTCR); | ||
117 | |||
118 | spin_unlock(&io_lock); | ||
119 | } | ||
120 | |||
121 | static int davinci_wdt_open(struct inode *inode, struct file *file) | ||
122 | { | ||
123 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | ||
124 | return -EBUSY; | ||
125 | |||
126 | wdt_enable(); | ||
127 | |||
128 | return nonseekable_open(inode, file); | ||
129 | } | ||
130 | |||
131 | static ssize_t | ||
132 | davinci_wdt_write(struct file *file, const char *data, size_t len, | ||
133 | loff_t *ppos) | ||
134 | { | ||
135 | if (len) | ||
136 | wdt_service(); | ||
137 | |||
138 | return len; | ||
139 | } | ||
140 | |||
141 | static struct watchdog_info ident = { | ||
142 | .options = WDIOF_KEEPALIVEPING, | ||
143 | .identity = "DaVinci Watchdog", | ||
144 | }; | ||
145 | |||
146 | static int | ||
147 | davinci_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
148 | unsigned long arg) | ||
149 | { | ||
150 | int ret = -ENOTTY; | ||
151 | |||
152 | switch (cmd) { | ||
153 | case WDIOC_GETSUPPORT: | ||
154 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | ||
155 | sizeof(ident)) ? -EFAULT : 0; | ||
156 | break; | ||
157 | |||
158 | case WDIOC_GETSTATUS: | ||
159 | case WDIOC_GETBOOTSTATUS: | ||
160 | ret = put_user(0, (int *)arg); | ||
161 | break; | ||
162 | |||
163 | case WDIOC_GETTIMEOUT: | ||
164 | ret = put_user(heartbeat, (int *)arg); | ||
165 | break; | ||
166 | |||
167 | case WDIOC_KEEPALIVE: | ||
168 | wdt_service(); | ||
169 | ret = 0; | ||
170 | break; | ||
171 | } | ||
172 | return ret; | ||
173 | } | ||
174 | |||
175 | static int davinci_wdt_release(struct inode *inode, struct file *file) | ||
176 | { | ||
177 | wdt_service(); | ||
178 | clear_bit(WDT_IN_USE, &wdt_status); | ||
179 | |||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | static const struct file_operations davinci_wdt_fops = { | ||
184 | .owner = THIS_MODULE, | ||
185 | .llseek = no_llseek, | ||
186 | .write = davinci_wdt_write, | ||
187 | .ioctl = davinci_wdt_ioctl, | ||
188 | .open = davinci_wdt_open, | ||
189 | .release = davinci_wdt_release, | ||
190 | }; | ||
191 | |||
192 | static struct miscdevice davinci_wdt_miscdev = { | ||
193 | .minor = WATCHDOG_MINOR, | ||
194 | .name = "watchdog", | ||
195 | .fops = &davinci_wdt_fops, | ||
196 | }; | ||
197 | |||
198 | static int davinci_wdt_probe(struct platform_device *pdev) | ||
199 | { | ||
200 | int ret = 0, size; | ||
201 | struct resource *res; | ||
202 | |||
203 | spin_lock_init(&io_lock); | ||
204 | |||
205 | if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) | ||
206 | heartbeat = DEFAULT_HEARTBEAT; | ||
207 | |||
208 | printk(KERN_INFO MODULE_NAME | ||
209 | "DaVinci Watchdog Timer: heartbeat %d sec\n", heartbeat); | ||
210 | |||
211 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
212 | if (res == NULL) { | ||
213 | printk(KERN_INFO MODULE_NAME | ||
214 | "failed to get memory region resource\n"); | ||
215 | return -ENOENT; | ||
216 | } | ||
217 | |||
218 | size = res->end - res->start + 1; | ||
219 | wdt_mem = request_mem_region(res->start, size, pdev->name); | ||
220 | |||
221 | if (wdt_mem == NULL) { | ||
222 | printk(KERN_INFO MODULE_NAME "failed to get memory region\n"); | ||
223 | return -ENOENT; | ||
224 | } | ||
225 | wdt_base = (void __iomem *)(res->start); | ||
226 | |||
227 | ret = misc_register(&davinci_wdt_miscdev); | ||
228 | if (ret < 0) { | ||
229 | printk(KERN_ERR MODULE_NAME "cannot register misc device\n"); | ||
230 | release_resource(wdt_mem); | ||
231 | kfree(wdt_mem); | ||
232 | } else { | ||
233 | set_bit(WDT_DEVICE_INITED, &wdt_status); | ||
234 | } | ||
235 | |||
236 | return ret; | ||
237 | } | ||
238 | |||
239 | static int davinci_wdt_remove(struct platform_device *pdev) | ||
240 | { | ||
241 | misc_deregister(&davinci_wdt_miscdev); | ||
242 | if (wdt_mem) { | ||
243 | release_resource(wdt_mem); | ||
244 | kfree(wdt_mem); | ||
245 | wdt_mem = NULL; | ||
246 | } | ||
247 | return 0; | ||
248 | } | ||
249 | |||
250 | static struct platform_driver platform_wdt_driver = { | ||
251 | .driver = { | ||
252 | .name = "watchdog", | ||
253 | }, | ||
254 | .probe = davinci_wdt_probe, | ||
255 | .remove = davinci_wdt_remove, | ||
256 | }; | ||
257 | |||
258 | static int __init davinci_wdt_init(void) | ||
259 | { | ||
260 | return platform_driver_register(&platform_wdt_driver); | ||
261 | } | ||
262 | |||
263 | static void __exit davinci_wdt_exit(void) | ||
264 | { | ||
265 | return platform_driver_unregister(&platform_wdt_driver); | ||
266 | } | ||
267 | |||
268 | module_init(davinci_wdt_init); | ||
269 | module_exit(davinci_wdt_exit); | ||
270 | |||
271 | MODULE_AUTHOR("Texas Instruments"); | ||
272 | MODULE_DESCRIPTION("DaVinci Watchdog Driver"); | ||
273 | |||
274 | module_param(heartbeat, int, 0); | ||
275 | MODULE_PARM_DESC(heartbeat, | ||
276 | "Watchdog heartbeat period in seconds from 1 to " | ||
277 | __MODULE_STRING(MAX_HEARTBEAT) ", default " | ||
278 | __MODULE_STRING(DEFAULT_HEARTBEAT)); | ||
279 | |||
280 | MODULE_LICENSE("GPL"); | ||
281 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/ep93xx_wdt.c b/drivers/watchdog/ep93xx_wdt.c new file mode 100644 index 000000000000..0e4787a0bb87 --- /dev/null +++ b/drivers/watchdog/ep93xx_wdt.c | |||
@@ -0,0 +1,253 @@ | |||
1 | /* | ||
2 | * Watchdog driver for Cirrus Logic EP93xx family of devices. | ||
3 | * | ||
4 | * Copyright (c) 2004 Ray Lehtiniemi | ||
5 | * Copyright (c) 2006 Tower Technologies | ||
6 | * Based on ep93xx driver, bits from alim7101_wdt.c | ||
7 | * | ||
8 | * Authors: Ray Lehtiniemi <rayl@mail.com>, | ||
9 | * Alessandro Zummo <a.zummo@towertech.it> | ||
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 | * This watchdog fires after 250msec, which is a too short interval | ||
16 | * for us to rely on the user space daemon alone. So we ping the | ||
17 | * wdt each ~200msec and eventually stop doing it if the user space | ||
18 | * daemon dies. | ||
19 | * | ||
20 | * TODO: | ||
21 | * | ||
22 | * - Test last reset from watchdog status | ||
23 | * - Add a few missing ioctls | ||
24 | */ | ||
25 | |||
26 | #include <linux/module.h> | ||
27 | #include <linux/fs.h> | ||
28 | #include <linux/miscdevice.h> | ||
29 | #include <linux/watchdog.h> | ||
30 | #include <linux/timer.h> | ||
31 | |||
32 | #include <asm/hardware.h> | ||
33 | #include <asm/uaccess.h> | ||
34 | |||
35 | #define WDT_VERSION "0.3" | ||
36 | #define PFX "ep93xx_wdt: " | ||
37 | |||
38 | /* default timeout (secs) */ | ||
39 | #define WDT_TIMEOUT 30 | ||
40 | |||
41 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
42 | static int timeout = WDT_TIMEOUT; | ||
43 | |||
44 | static struct timer_list timer; | ||
45 | static unsigned long next_heartbeat; | ||
46 | static unsigned long wdt_status; | ||
47 | static unsigned long boot_status; | ||
48 | |||
49 | #define WDT_IN_USE 0 | ||
50 | #define WDT_OK_TO_CLOSE 1 | ||
51 | |||
52 | #define EP93XX_WDT_REG(x) (EP93XX_WATCHDOG_BASE + (x)) | ||
53 | #define EP93XX_WDT_WATCHDOG EP93XX_WDT_REG(0x00) | ||
54 | #define EP93XX_WDT_WDSTATUS EP93XX_WDT_REG(0x04) | ||
55 | |||
56 | /* reset the wdt every ~200ms */ | ||
57 | #define WDT_INTERVAL (HZ/5) | ||
58 | |||
59 | static void wdt_enable(void) | ||
60 | { | ||
61 | __raw_writew(0xaaaa, EP93XX_WDT_WATCHDOG); | ||
62 | } | ||
63 | |||
64 | static void wdt_disable(void) | ||
65 | { | ||
66 | __raw_writew(0xaa55, EP93XX_WDT_WATCHDOG); | ||
67 | } | ||
68 | |||
69 | static inline void wdt_ping(void) | ||
70 | { | ||
71 | __raw_writew(0x5555, EP93XX_WDT_WATCHDOG); | ||
72 | } | ||
73 | |||
74 | static void wdt_startup(void) | ||
75 | { | ||
76 | next_heartbeat = jiffies + (timeout * HZ); | ||
77 | |||
78 | wdt_enable(); | ||
79 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
80 | } | ||
81 | |||
82 | static void wdt_shutdown(void) | ||
83 | { | ||
84 | del_timer_sync(&timer); | ||
85 | wdt_disable(); | ||
86 | } | ||
87 | |||
88 | static void wdt_keepalive(void) | ||
89 | { | ||
90 | /* user land ping */ | ||
91 | next_heartbeat = jiffies + (timeout * HZ); | ||
92 | } | ||
93 | |||
94 | static int ep93xx_wdt_open(struct inode *inode, struct file *file) | ||
95 | { | ||
96 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | ||
97 | return -EBUSY; | ||
98 | |||
99 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
100 | |||
101 | wdt_startup(); | ||
102 | |||
103 | return nonseekable_open(inode, file); | ||
104 | } | ||
105 | |||
106 | static ssize_t | ||
107 | ep93xx_wdt_write(struct file *file, const char __user *data, size_t len, | ||
108 | loff_t *ppos) | ||
109 | { | ||
110 | if (len) { | ||
111 | if (!nowayout) { | ||
112 | size_t i; | ||
113 | |||
114 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
115 | |||
116 | for (i = 0; i != len; i++) { | ||
117 | char c; | ||
118 | |||
119 | if (get_user(c, data + i)) | ||
120 | return -EFAULT; | ||
121 | |||
122 | if (c == 'V') | ||
123 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
124 | else | ||
125 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
126 | } | ||
127 | } | ||
128 | wdt_keepalive(); | ||
129 | } | ||
130 | |||
131 | return len; | ||
132 | } | ||
133 | |||
134 | static struct watchdog_info ident = { | ||
135 | .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE, | ||
136 | .identity = "EP93xx Watchdog", | ||
137 | }; | ||
138 | |||
139 | static int | ||
140 | ep93xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
141 | unsigned long arg) | ||
142 | { | ||
143 | int ret = -ENOTTY; | ||
144 | |||
145 | switch (cmd) { | ||
146 | case WDIOC_GETSUPPORT: | ||
147 | ret = copy_to_user((struct watchdog_info __user *)arg, &ident, | ||
148 | sizeof(ident)) ? -EFAULT : 0; | ||
149 | break; | ||
150 | |||
151 | case WDIOC_GETSTATUS: | ||
152 | ret = put_user(0, (int __user *)arg); | ||
153 | break; | ||
154 | |||
155 | case WDIOC_GETBOOTSTATUS: | ||
156 | ret = put_user(boot_status, (int __user *)arg); | ||
157 | break; | ||
158 | |||
159 | case WDIOC_GETTIMEOUT: | ||
160 | /* actually, it is 0.250 seconds.... */ | ||
161 | ret = put_user(1, (int __user *)arg); | ||
162 | break; | ||
163 | |||
164 | case WDIOC_KEEPALIVE: | ||
165 | wdt_keepalive(); | ||
166 | ret = 0; | ||
167 | break; | ||
168 | } | ||
169 | return ret; | ||
170 | } | ||
171 | |||
172 | static int ep93xx_wdt_release(struct inode *inode, struct file *file) | ||
173 | { | ||
174 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) | ||
175 | wdt_shutdown(); | ||
176 | else | ||
177 | printk(KERN_CRIT PFX "Device closed unexpectedly - " | ||
178 | "timer will not stop\n"); | ||
179 | |||
180 | clear_bit(WDT_IN_USE, &wdt_status); | ||
181 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
182 | |||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | static const struct file_operations ep93xx_wdt_fops = { | ||
187 | .owner = THIS_MODULE, | ||
188 | .write = ep93xx_wdt_write, | ||
189 | .ioctl = ep93xx_wdt_ioctl, | ||
190 | .open = ep93xx_wdt_open, | ||
191 | .release = ep93xx_wdt_release, | ||
192 | }; | ||
193 | |||
194 | static struct miscdevice ep93xx_wdt_miscdev = { | ||
195 | .minor = WATCHDOG_MINOR, | ||
196 | .name = "watchdog", | ||
197 | .fops = &ep93xx_wdt_fops, | ||
198 | }; | ||
199 | |||
200 | static void ep93xx_timer_ping(unsigned long data) | ||
201 | { | ||
202 | if (time_before(jiffies, next_heartbeat)) | ||
203 | wdt_ping(); | ||
204 | |||
205 | /* Re-set the timer interval */ | ||
206 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
207 | } | ||
208 | |||
209 | static int __init ep93xx_wdt_init(void) | ||
210 | { | ||
211 | int err; | ||
212 | |||
213 | err = misc_register(&ep93xx_wdt_miscdev); | ||
214 | |||
215 | boot_status = __raw_readl(EP93XX_WDT_WATCHDOG) & 0x01 ? 1 : 0; | ||
216 | |||
217 | printk(KERN_INFO PFX "EP93XX watchdog, driver version " | ||
218 | WDT_VERSION "%s\n", | ||
219 | (__raw_readl(EP93XX_WDT_WATCHDOG) & 0x08) | ||
220 | ? " (nCS1 disable detected)" : ""); | ||
221 | |||
222 | if (timeout < 1 || timeout > 3600) { | ||
223 | timeout = WDT_TIMEOUT; | ||
224 | printk(KERN_INFO PFX | ||
225 | "timeout value must be 1<=x<=3600, using %d\n", | ||
226 | timeout); | ||
227 | } | ||
228 | |||
229 | setup_timer(&timer, ep93xx_timer_ping, 1); | ||
230 | return err; | ||
231 | } | ||
232 | |||
233 | static void __exit ep93xx_wdt_exit(void) | ||
234 | { | ||
235 | wdt_shutdown(); | ||
236 | misc_deregister(&ep93xx_wdt_miscdev); | ||
237 | } | ||
238 | |||
239 | module_init(ep93xx_wdt_init); | ||
240 | module_exit(ep93xx_wdt_exit); | ||
241 | |||
242 | module_param(nowayout, int, 0); | ||
243 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); | ||
244 | |||
245 | module_param(timeout, int, 0); | ||
246 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
247 | |||
248 | MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>," | ||
249 | "Alessandro Zummo <a.zummo@towertech.it>"); | ||
250 | MODULE_DESCRIPTION("EP93xx Watchdog"); | ||
251 | MODULE_LICENSE("GPL"); | ||
252 | MODULE_VERSION(WDT_VERSION); | ||
253 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/eurotechwdt.c b/drivers/watchdog/eurotechwdt.c new file mode 100644 index 000000000000..b14e9d1f164d --- /dev/null +++ b/drivers/watchdog/eurotechwdt.c | |||
@@ -0,0 +1,473 @@ | |||
1 | /* | ||
2 | * Eurotech CPU-1220/1410/1420 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 | * 2001 - Rodolfo Giometti | ||
29 | * Initial release | ||
30 | * | ||
31 | * 2002/04/25 - Rob Radez | ||
32 | * clean up #includes | ||
33 | * clean up locking | ||
34 | * make __setup param unique | ||
35 | * proper options in watchdog_info | ||
36 | * add WDIOC_GETSTATUS and WDIOC_SETOPTIONS ioctls | ||
37 | * add expect_close support | ||
38 | * | ||
39 | * 2002.05.30 - Joel Becker <joel.becker@oracle.com> | ||
40 | * Added Matt Domsch's nowayout module option. | ||
41 | */ | ||
42 | |||
43 | /* | ||
44 | * The eurotech CPU-1220/1410/1420's watchdog is a part | ||
45 | * of the on-board SUPER I/O device SMSC FDC 37B782. | ||
46 | */ | ||
47 | |||
48 | #include <linux/interrupt.h> | ||
49 | #include <linux/module.h> | ||
50 | #include <linux/moduleparam.h> | ||
51 | #include <linux/types.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 | static unsigned long eurwdt_is_open; | ||
65 | static int eurwdt_timeout; | ||
66 | static char eur_expect_close; | ||
67 | |||
68 | /* | ||
69 | * You must set these - there is no sane way to probe for this board. | ||
70 | * You can use eurwdt=x,y to set these now. | ||
71 | */ | ||
72 | |||
73 | static int io = 0x3f0; | ||
74 | static int irq = 10; | ||
75 | static char *ev = "int"; | ||
76 | |||
77 | #define WDT_TIMEOUT 60 /* 1 minute */ | ||
78 | |||
79 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
80 | module_param(nowayout, int, 0); | ||
81 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
82 | |||
83 | /* | ||
84 | * Some symbolic names | ||
85 | */ | ||
86 | |||
87 | #define WDT_CTRL_REG 0x30 | ||
88 | #define WDT_OUTPIN_CFG 0xe2 | ||
89 | #define WDT_EVENT_INT 0x00 | ||
90 | #define WDT_EVENT_REBOOT 0x08 | ||
91 | #define WDT_UNIT_SEL 0xf1 | ||
92 | #define WDT_UNIT_SECS 0x80 | ||
93 | #define WDT_TIMEOUT_VAL 0xf2 | ||
94 | #define WDT_TIMER_CFG 0xf3 | ||
95 | |||
96 | |||
97 | module_param(io, int, 0); | ||
98 | MODULE_PARM_DESC(io, "Eurotech WDT io port (default=0x3f0)"); | ||
99 | module_param(irq, int, 0); | ||
100 | MODULE_PARM_DESC(irq, "Eurotech WDT irq (default=10)"); | ||
101 | module_param(ev, charp, 0); | ||
102 | MODULE_PARM_DESC(ev, "Eurotech WDT event type (default is `int')"); | ||
103 | |||
104 | |||
105 | /* | ||
106 | * Programming support | ||
107 | */ | ||
108 | |||
109 | static inline void eurwdt_write_reg(u8 index, u8 data) | ||
110 | { | ||
111 | outb(index, io); | ||
112 | outb(data, io+1); | ||
113 | } | ||
114 | |||
115 | static inline void eurwdt_lock_chip(void) | ||
116 | { | ||
117 | outb(0xaa, io); | ||
118 | } | ||
119 | |||
120 | static inline void eurwdt_unlock_chip(void) | ||
121 | { | ||
122 | outb(0x55, io); | ||
123 | eurwdt_write_reg(0x07, 0x08); /* set the logical device */ | ||
124 | } | ||
125 | |||
126 | static inline void eurwdt_set_timeout(int timeout) | ||
127 | { | ||
128 | eurwdt_write_reg(WDT_TIMEOUT_VAL, (u8) timeout); | ||
129 | } | ||
130 | |||
131 | static inline void eurwdt_disable_timer(void) | ||
132 | { | ||
133 | eurwdt_set_timeout(0); | ||
134 | } | ||
135 | |||
136 | static void eurwdt_activate_timer(void) | ||
137 | { | ||
138 | eurwdt_disable_timer(); | ||
139 | eurwdt_write_reg(WDT_CTRL_REG, 0x01); /* activate the WDT */ | ||
140 | eurwdt_write_reg(WDT_OUTPIN_CFG, !strcmp("int", ev) ? WDT_EVENT_INT : WDT_EVENT_REBOOT); | ||
141 | |||
142 | /* Setting interrupt line */ | ||
143 | if (irq == 2 || irq > 15 || irq < 0) { | ||
144 | printk(KERN_ERR ": invalid irq number\n"); | ||
145 | irq = 0; /* if invalid we disable interrupt */ | ||
146 | } | ||
147 | if (irq == 0) | ||
148 | printk(KERN_INFO ": interrupt disabled\n"); | ||
149 | |||
150 | eurwdt_write_reg(WDT_TIMER_CFG, irq<<4); | ||
151 | |||
152 | eurwdt_write_reg(WDT_UNIT_SEL, WDT_UNIT_SECS); /* we use seconds */ | ||
153 | eurwdt_set_timeout(0); /* the default timeout */ | ||
154 | } | ||
155 | |||
156 | |||
157 | /* | ||
158 | * Kernel methods. | ||
159 | */ | ||
160 | |||
161 | static irqreturn_t eurwdt_interrupt(int irq, void *dev_id) | ||
162 | { | ||
163 | printk(KERN_CRIT "timeout WDT timeout\n"); | ||
164 | |||
165 | #ifdef ONLY_TESTING | ||
166 | printk(KERN_CRIT "Would Reboot.\n"); | ||
167 | #else | ||
168 | printk(KERN_CRIT "Initiating system reboot.\n"); | ||
169 | emergency_restart(); | ||
170 | #endif | ||
171 | return IRQ_HANDLED; | ||
172 | } | ||
173 | |||
174 | |||
175 | /** | ||
176 | * eurwdt_ping: | ||
177 | * | ||
178 | * Reload counter one with the watchdog timeout. | ||
179 | */ | ||
180 | |||
181 | static void eurwdt_ping(void) | ||
182 | { | ||
183 | /* Write the watchdog default value */ | ||
184 | eurwdt_set_timeout(eurwdt_timeout); | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * eurwdt_write: | ||
189 | * @file: file handle to the watchdog | ||
190 | * @buf: buffer to write (unused as data does not matter here | ||
191 | * @count: count of bytes | ||
192 | * @ppos: pointer to the position to write. No seeks allowed | ||
193 | * | ||
194 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
195 | * write of data will do, as we we don't define content meaning. | ||
196 | */ | ||
197 | |||
198 | static ssize_t eurwdt_write(struct file *file, const char __user *buf, | ||
199 | size_t count, loff_t *ppos) | ||
200 | { | ||
201 | if (count) { | ||
202 | if (!nowayout) { | ||
203 | size_t i; | ||
204 | |||
205 | eur_expect_close = 0; | ||
206 | |||
207 | for (i = 0; i != count; i++) { | ||
208 | char c; | ||
209 | if(get_user(c, buf+i)) | ||
210 | return -EFAULT; | ||
211 | if (c == 'V') | ||
212 | eur_expect_close = 42; | ||
213 | } | ||
214 | } | ||
215 | eurwdt_ping(); /* the default timeout */ | ||
216 | } | ||
217 | |||
218 | return count; | ||
219 | } | ||
220 | |||
221 | /** | ||
222 | * eurwdt_ioctl: | ||
223 | * @inode: inode of the device | ||
224 | * @file: file handle to the device | ||
225 | * @cmd: watchdog command | ||
226 | * @arg: argument pointer | ||
227 | * | ||
228 | * The watchdog API defines a common set of functions for all watchdogs | ||
229 | * according to their available features. | ||
230 | */ | ||
231 | |||
232 | static int eurwdt_ioctl(struct inode *inode, struct file *file, | ||
233 | unsigned int cmd, unsigned long arg) | ||
234 | { | ||
235 | void __user *argp = (void __user *)arg; | ||
236 | int __user *p = argp; | ||
237 | static struct watchdog_info ident = { | ||
238 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
239 | .firmware_version = 1, | ||
240 | .identity = "WDT Eurotech CPU-1220/1410", | ||
241 | }; | ||
242 | |||
243 | int time; | ||
244 | int options, retval = -EINVAL; | ||
245 | |||
246 | switch(cmd) { | ||
247 | default: | ||
248 | return -ENOTTY; | ||
249 | |||
250 | case WDIOC_GETSUPPORT: | ||
251 | return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; | ||
252 | |||
253 | case WDIOC_GETSTATUS: | ||
254 | case WDIOC_GETBOOTSTATUS: | ||
255 | return put_user(0, p); | ||
256 | |||
257 | case WDIOC_KEEPALIVE: | ||
258 | eurwdt_ping(); | ||
259 | return 0; | ||
260 | |||
261 | case WDIOC_SETTIMEOUT: | ||
262 | if (copy_from_user(&time, p, sizeof(int))) | ||
263 | return -EFAULT; | ||
264 | |||
265 | /* Sanity check */ | ||
266 | if (time < 0 || time > 255) | ||
267 | return -EINVAL; | ||
268 | |||
269 | eurwdt_timeout = time; | ||
270 | eurwdt_set_timeout(time); | ||
271 | /* Fall */ | ||
272 | |||
273 | case WDIOC_GETTIMEOUT: | ||
274 | return put_user(eurwdt_timeout, p); | ||
275 | |||
276 | case WDIOC_SETOPTIONS: | ||
277 | if (get_user(options, p)) | ||
278 | return -EFAULT; | ||
279 | if (options & WDIOS_DISABLECARD) { | ||
280 | eurwdt_disable_timer(); | ||
281 | retval = 0; | ||
282 | } | ||
283 | if (options & WDIOS_ENABLECARD) { | ||
284 | eurwdt_activate_timer(); | ||
285 | eurwdt_ping(); | ||
286 | retval = 0; | ||
287 | } | ||
288 | return retval; | ||
289 | } | ||
290 | } | ||
291 | |||
292 | /** | ||
293 | * eurwdt_open: | ||
294 | * @inode: inode of device | ||
295 | * @file: file handle to device | ||
296 | * | ||
297 | * The misc device has been opened. The watchdog device is single | ||
298 | * open and on opening we load the counter. | ||
299 | */ | ||
300 | |||
301 | static int eurwdt_open(struct inode *inode, struct file *file) | ||
302 | { | ||
303 | if (test_and_set_bit(0, &eurwdt_is_open)) | ||
304 | return -EBUSY; | ||
305 | eurwdt_timeout = WDT_TIMEOUT; /* initial timeout */ | ||
306 | /* Activate the WDT */ | ||
307 | eurwdt_activate_timer(); | ||
308 | return nonseekable_open(inode, file); | ||
309 | } | ||
310 | |||
311 | /** | ||
312 | * eurwdt_release: | ||
313 | * @inode: inode to board | ||
314 | * @file: file handle to board | ||
315 | * | ||
316 | * The watchdog has a configurable API. There is a religious dispute | ||
317 | * between people who want their watchdog to be able to shut down and | ||
318 | * those who want to be sure if the watchdog manager dies the machine | ||
319 | * reboots. In the former case we disable the counters, in the latter | ||
320 | * case you have to open it again very soon. | ||
321 | */ | ||
322 | |||
323 | static int eurwdt_release(struct inode *inode, struct file *file) | ||
324 | { | ||
325 | if (eur_expect_close == 42) { | ||
326 | eurwdt_disable_timer(); | ||
327 | } else { | ||
328 | printk(KERN_CRIT "eurwdt: Unexpected close, not stopping watchdog!\n"); | ||
329 | eurwdt_ping(); | ||
330 | } | ||
331 | clear_bit(0, &eurwdt_is_open); | ||
332 | eur_expect_close = 0; | ||
333 | return 0; | ||
334 | } | ||
335 | |||
336 | /** | ||
337 | * eurwdt_notify_sys: | ||
338 | * @this: our notifier block | ||
339 | * @code: the event being reported | ||
340 | * @unused: unused | ||
341 | * | ||
342 | * Our notifier is called on system shutdowns. We want to turn the card | ||
343 | * off at reboot otherwise the machine will reboot again during memory | ||
344 | * test or worse yet during the following fsck. This would suck, in fact | ||
345 | * trust me - if it happens it does suck. | ||
346 | */ | ||
347 | |||
348 | static int eurwdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
349 | void *unused) | ||
350 | { | ||
351 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
352 | /* Turn the card off */ | ||
353 | eurwdt_disable_timer(); | ||
354 | } | ||
355 | |||
356 | return NOTIFY_DONE; | ||
357 | } | ||
358 | |||
359 | /* | ||
360 | * Kernel Interfaces | ||
361 | */ | ||
362 | |||
363 | |||
364 | static const struct file_operations eurwdt_fops = { | ||
365 | .owner = THIS_MODULE, | ||
366 | .llseek = no_llseek, | ||
367 | .write = eurwdt_write, | ||
368 | .ioctl = eurwdt_ioctl, | ||
369 | .open = eurwdt_open, | ||
370 | .release = eurwdt_release, | ||
371 | }; | ||
372 | |||
373 | static struct miscdevice eurwdt_miscdev = { | ||
374 | .minor = WATCHDOG_MINOR, | ||
375 | .name = "watchdog", | ||
376 | .fops = &eurwdt_fops, | ||
377 | }; | ||
378 | |||
379 | /* | ||
380 | * The WDT card needs to learn about soft shutdowns in order to | ||
381 | * turn the timebomb registers off. | ||
382 | */ | ||
383 | |||
384 | static struct notifier_block eurwdt_notifier = { | ||
385 | .notifier_call = eurwdt_notify_sys, | ||
386 | }; | ||
387 | |||
388 | /** | ||
389 | * cleanup_module: | ||
390 | * | ||
391 | * Unload the watchdog. You cannot do this with any file handles open. | ||
392 | * If your watchdog is set to continue ticking on close and you unload | ||
393 | * it, well it keeps ticking. We won't get the interrupt but the board | ||
394 | * will not touch PC memory so all is fine. You just have to load a new | ||
395 | * module in 60 seconds or reboot. | ||
396 | */ | ||
397 | |||
398 | static void __exit eurwdt_exit(void) | ||
399 | { | ||
400 | eurwdt_lock_chip(); | ||
401 | |||
402 | misc_deregister(&eurwdt_miscdev); | ||
403 | |||
404 | unregister_reboot_notifier(&eurwdt_notifier); | ||
405 | release_region(io, 2); | ||
406 | free_irq(irq, NULL); | ||
407 | } | ||
408 | |||
409 | /** | ||
410 | * eurwdt_init: | ||
411 | * | ||
412 | * Set up the WDT watchdog board. After grabbing the resources | ||
413 | * we require we need also to unlock the device. | ||
414 | * The open() function will actually kick the board off. | ||
415 | */ | ||
416 | |||
417 | static int __init eurwdt_init(void) | ||
418 | { | ||
419 | int ret; | ||
420 | |||
421 | ret = request_irq(irq, eurwdt_interrupt, IRQF_DISABLED, "eurwdt", NULL); | ||
422 | if(ret) { | ||
423 | printk(KERN_ERR "eurwdt: IRQ %d is not free.\n", irq); | ||
424 | goto out; | ||
425 | } | ||
426 | |||
427 | if (!request_region(io, 2, "eurwdt")) { | ||
428 | printk(KERN_ERR "eurwdt: IO %X is not free.\n", io); | ||
429 | ret = -EBUSY; | ||
430 | goto outirq; | ||
431 | } | ||
432 | |||
433 | ret = register_reboot_notifier(&eurwdt_notifier); | ||
434 | if (ret) { | ||
435 | printk(KERN_ERR "eurwdt: can't register reboot notifier (err=%d)\n", ret); | ||
436 | goto outreg; | ||
437 | } | ||
438 | |||
439 | ret = misc_register(&eurwdt_miscdev); | ||
440 | if (ret) { | ||
441 | printk(KERN_ERR "eurwdt: can't misc_register on minor=%d\n", | ||
442 | WATCHDOG_MINOR); | ||
443 | goto outreboot; | ||
444 | } | ||
445 | |||
446 | eurwdt_unlock_chip(); | ||
447 | |||
448 | ret = 0; | ||
449 | printk(KERN_INFO "Eurotech WDT driver 0.01 at %X (Interrupt %d)" | ||
450 | " - timeout event: %s\n", | ||
451 | io, irq, (!strcmp("int", ev) ? "int" : "reboot")); | ||
452 | |||
453 | out: | ||
454 | return ret; | ||
455 | |||
456 | outreboot: | ||
457 | unregister_reboot_notifier(&eurwdt_notifier); | ||
458 | |||
459 | outreg: | ||
460 | release_region(io, 2); | ||
461 | |||
462 | outirq: | ||
463 | free_irq(irq, NULL); | ||
464 | goto out; | ||
465 | } | ||
466 | |||
467 | module_init(eurwdt_init); | ||
468 | module_exit(eurwdt_exit); | ||
469 | |||
470 | MODULE_AUTHOR("Rodolfo Giometti"); | ||
471 | MODULE_DESCRIPTION("Driver for Eurotech CPU-1220/1410 on board watchdog"); | ||
472 | MODULE_LICENSE("GPL"); | ||
473 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/i6300esb.c b/drivers/watchdog/i6300esb.c new file mode 100644 index 000000000000..c5982502c03d --- /dev/null +++ b/drivers/watchdog/i6300esb.c | |||
@@ -0,0 +1,527 @@ | |||
1 | /* | ||
2 | * i6300esb: Watchdog timer driver for Intel 6300ESB chipset | ||
3 | * | ||
4 | * (c) Copyright 2004 Google Inc. | ||
5 | * (c) Copyright 2005 David Härdeman <david@2gen.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 | * based on i810-tco.c which is in turn based on softdog.c | ||
13 | * | ||
14 | * The timer is implemented in the following I/O controller hubs: | ||
15 | * (See the intel documentation on http://developer.intel.com.) | ||
16 | * 6300ESB chip : document number 300641-003 | ||
17 | * | ||
18 | * 2004YYZZ Ross Biro | ||
19 | * Initial version 0.01 | ||
20 | * 2004YYZZ Ross Biro | ||
21 | * Version 0.02 | ||
22 | * 20050210 David Härdeman <david@2gen.com> | ||
23 | * Ported driver to kernel 2.6 | ||
24 | */ | ||
25 | |||
26 | /* | ||
27 | * Includes, defines, variables, module parameters, ... | ||
28 | */ | ||
29 | |||
30 | #include <linux/module.h> | ||
31 | #include <linux/types.h> | ||
32 | #include <linux/kernel.h> | ||
33 | #include <linux/fs.h> | ||
34 | #include <linux/mm.h> | ||
35 | #include <linux/miscdevice.h> | ||
36 | #include <linux/watchdog.h> | ||
37 | #include <linux/reboot.h> | ||
38 | #include <linux/init.h> | ||
39 | #include <linux/pci.h> | ||
40 | #include <linux/ioport.h> | ||
41 | |||
42 | #include <asm/uaccess.h> | ||
43 | #include <asm/io.h> | ||
44 | |||
45 | /* Module and version information */ | ||
46 | #define ESB_VERSION "0.03" | ||
47 | #define ESB_MODULE_NAME "i6300ESB timer" | ||
48 | #define ESB_DRIVER_NAME ESB_MODULE_NAME ", v" ESB_VERSION | ||
49 | #define PFX ESB_MODULE_NAME ": " | ||
50 | |||
51 | /* PCI configuration registers */ | ||
52 | #define ESB_CONFIG_REG 0x60 /* Config register */ | ||
53 | #define ESB_LOCK_REG 0x68 /* WDT lock register */ | ||
54 | |||
55 | /* Memory mapped registers */ | ||
56 | #define ESB_TIMER1_REG BASEADDR + 0x00 /* Timer1 value after each reset */ | ||
57 | #define ESB_TIMER2_REG BASEADDR + 0x04 /* Timer2 value after each reset */ | ||
58 | #define ESB_GINTSR_REG BASEADDR + 0x08 /* General Interrupt Status Register */ | ||
59 | #define ESB_RELOAD_REG BASEADDR + 0x0c /* Reload register */ | ||
60 | |||
61 | /* Lock register bits */ | ||
62 | #define ESB_WDT_FUNC ( 0x01 << 2 ) /* Watchdog functionality */ | ||
63 | #define ESB_WDT_ENABLE ( 0x01 << 1 ) /* Enable WDT */ | ||
64 | #define ESB_WDT_LOCK ( 0x01 << 0 ) /* Lock (nowayout) */ | ||
65 | |||
66 | /* Config register bits */ | ||
67 | #define ESB_WDT_REBOOT ( 0x01 << 5 ) /* Enable reboot on timeout */ | ||
68 | #define ESB_WDT_FREQ ( 0x01 << 2 ) /* Decrement frequency */ | ||
69 | #define ESB_WDT_INTTYPE ( 0x11 << 0 ) /* Interrupt type on timer1 timeout */ | ||
70 | |||
71 | /* Reload register bits */ | ||
72 | #define ESB_WDT_RELOAD ( 0x01 << 8 ) /* prevent timeout */ | ||
73 | |||
74 | /* Magic constants */ | ||
75 | #define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */ | ||
76 | #define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */ | ||
77 | |||
78 | /* internal variables */ | ||
79 | static void __iomem *BASEADDR; | ||
80 | static spinlock_t esb_lock; /* Guards the hardware */ | ||
81 | static unsigned long timer_alive; | ||
82 | static struct pci_dev *esb_pci; | ||
83 | static unsigned short triggered; /* The status of the watchdog upon boot */ | ||
84 | static char esb_expect_close; | ||
85 | |||
86 | /* module parameters */ | ||
87 | #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (1<heartbeat<2*1023) */ | ||
88 | static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ | ||
89 | module_param(heartbeat, int, 0); | ||
90 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1<heartbeat<2046, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
91 | |||
92 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
93 | module_param(nowayout, int, 0); | ||
94 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
95 | |||
96 | /* | ||
97 | * Some i6300ESB specific functions | ||
98 | */ | ||
99 | |||
100 | /* | ||
101 | * Prepare for reloading the timer by unlocking the proper registers. | ||
102 | * This is performed by first writing 0x80 followed by 0x86 to the | ||
103 | * reload register. After this the appropriate registers can be written | ||
104 | * to once before they need to be unlocked again. | ||
105 | */ | ||
106 | static inline void esb_unlock_registers(void) { | ||
107 | writeb(ESB_UNLOCK1, ESB_RELOAD_REG); | ||
108 | writeb(ESB_UNLOCK2, ESB_RELOAD_REG); | ||
109 | } | ||
110 | |||
111 | static void esb_timer_start(void) | ||
112 | { | ||
113 | u8 val; | ||
114 | |||
115 | /* Enable or Enable + Lock? */ | ||
116 | val = 0x02 | (nowayout ? 0x01 : 0x00); | ||
117 | |||
118 | pci_write_config_byte(esb_pci, ESB_LOCK_REG, val); | ||
119 | } | ||
120 | |||
121 | static int esb_timer_stop(void) | ||
122 | { | ||
123 | u8 val; | ||
124 | |||
125 | spin_lock(&esb_lock); | ||
126 | /* First, reset timers as suggested by the docs */ | ||
127 | esb_unlock_registers(); | ||
128 | writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); | ||
129 | /* Then disable the WDT */ | ||
130 | pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x0); | ||
131 | pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val); | ||
132 | spin_unlock(&esb_lock); | ||
133 | |||
134 | /* Returns 0 if the timer was disabled, non-zero otherwise */ | ||
135 | return (val & 0x01); | ||
136 | } | ||
137 | |||
138 | static void esb_timer_keepalive(void) | ||
139 | { | ||
140 | spin_lock(&esb_lock); | ||
141 | esb_unlock_registers(); | ||
142 | writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); | ||
143 | /* FIXME: Do we need to flush anything here? */ | ||
144 | spin_unlock(&esb_lock); | ||
145 | } | ||
146 | |||
147 | static int esb_timer_set_heartbeat(int time) | ||
148 | { | ||
149 | u32 val; | ||
150 | |||
151 | if (time < 0x1 || time > (2 * 0x03ff)) | ||
152 | return -EINVAL; | ||
153 | |||
154 | spin_lock(&esb_lock); | ||
155 | |||
156 | /* We shift by 9, so if we are passed a value of 1 sec, | ||
157 | * val will be 1 << 9 = 512, then write that to two | ||
158 | * timers => 2 * 512 = 1024 (which is decremented at 1KHz) | ||
159 | */ | ||
160 | val = time << 9; | ||
161 | |||
162 | /* Write timer 1 */ | ||
163 | esb_unlock_registers(); | ||
164 | writel(val, ESB_TIMER1_REG); | ||
165 | |||
166 | /* Write timer 2 */ | ||
167 | esb_unlock_registers(); | ||
168 | writel(val, ESB_TIMER2_REG); | ||
169 | |||
170 | /* Reload */ | ||
171 | esb_unlock_registers(); | ||
172 | writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); | ||
173 | |||
174 | /* FIXME: Do we need to flush everything out? */ | ||
175 | |||
176 | /* Done */ | ||
177 | heartbeat = time; | ||
178 | spin_unlock(&esb_lock); | ||
179 | return 0; | ||
180 | } | ||
181 | |||
182 | static int esb_timer_read (void) | ||
183 | { | ||
184 | u32 count; | ||
185 | |||
186 | /* This isn't documented, and doesn't take into | ||
187 | * acount which stage is running, but it looks | ||
188 | * like a 20 bit count down, so we might as well report it. | ||
189 | */ | ||
190 | pci_read_config_dword(esb_pci, 0x64, &count); | ||
191 | return (int)count; | ||
192 | } | ||
193 | |||
194 | /* | ||
195 | * /dev/watchdog handling | ||
196 | */ | ||
197 | |||
198 | static int esb_open (struct inode *inode, struct file *file) | ||
199 | { | ||
200 | /* /dev/watchdog can only be opened once */ | ||
201 | if (test_and_set_bit(0, &timer_alive)) | ||
202 | return -EBUSY; | ||
203 | |||
204 | /* Reload and activate timer */ | ||
205 | esb_timer_keepalive (); | ||
206 | esb_timer_start (); | ||
207 | |||
208 | return nonseekable_open(inode, file); | ||
209 | } | ||
210 | |||
211 | static int esb_release (struct inode *inode, struct file *file) | ||
212 | { | ||
213 | /* Shut off the timer. */ | ||
214 | if (esb_expect_close == 42) { | ||
215 | esb_timer_stop (); | ||
216 | } else { | ||
217 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
218 | esb_timer_keepalive (); | ||
219 | } | ||
220 | clear_bit(0, &timer_alive); | ||
221 | esb_expect_close = 0; | ||
222 | return 0; | ||
223 | } | ||
224 | |||
225 | static ssize_t esb_write (struct file *file, const char __user *data, | ||
226 | size_t len, loff_t * ppos) | ||
227 | { | ||
228 | /* See if we got the magic character 'V' and reload the timer */ | ||
229 | if (len) { | ||
230 | if (!nowayout) { | ||
231 | size_t i; | ||
232 | |||
233 | /* note: just in case someone wrote the magic character | ||
234 | * five months ago... */ | ||
235 | esb_expect_close = 0; | ||
236 | |||
237 | /* scan to see whether or not we got the magic character */ | ||
238 | for (i = 0; i != len; i++) { | ||
239 | char c; | ||
240 | if(get_user(c, data+i)) | ||
241 | return -EFAULT; | ||
242 | if (c == 'V') | ||
243 | esb_expect_close = 42; | ||
244 | } | ||
245 | } | ||
246 | |||
247 | /* someone wrote to us, we should reload the timer */ | ||
248 | esb_timer_keepalive (); | ||
249 | } | ||
250 | return len; | ||
251 | } | ||
252 | |||
253 | static int esb_ioctl (struct inode *inode, struct file *file, | ||
254 | unsigned int cmd, unsigned long arg) | ||
255 | { | ||
256 | int new_options, retval = -EINVAL; | ||
257 | int new_heartbeat; | ||
258 | void __user *argp = (void __user *)arg; | ||
259 | int __user *p = argp; | ||
260 | static struct watchdog_info ident = { | ||
261 | .options = WDIOF_SETTIMEOUT | | ||
262 | WDIOF_KEEPALIVEPING | | ||
263 | WDIOF_MAGICCLOSE, | ||
264 | .firmware_version = 0, | ||
265 | .identity = ESB_MODULE_NAME, | ||
266 | }; | ||
267 | |||
268 | switch (cmd) { | ||
269 | case WDIOC_GETSUPPORT: | ||
270 | return copy_to_user(argp, &ident, | ||
271 | sizeof (ident)) ? -EFAULT : 0; | ||
272 | |||
273 | case WDIOC_GETSTATUS: | ||
274 | return put_user (esb_timer_read(), p); | ||
275 | |||
276 | case WDIOC_GETBOOTSTATUS: | ||
277 | return put_user (triggered, p); | ||
278 | |||
279 | case WDIOC_KEEPALIVE: | ||
280 | esb_timer_keepalive (); | ||
281 | return 0; | ||
282 | |||
283 | case WDIOC_SETOPTIONS: | ||
284 | { | ||
285 | if (get_user (new_options, p)) | ||
286 | return -EFAULT; | ||
287 | |||
288 | if (new_options & WDIOS_DISABLECARD) { | ||
289 | esb_timer_stop (); | ||
290 | retval = 0; | ||
291 | } | ||
292 | |||
293 | if (new_options & WDIOS_ENABLECARD) { | ||
294 | esb_timer_keepalive (); | ||
295 | esb_timer_start (); | ||
296 | retval = 0; | ||
297 | } | ||
298 | |||
299 | return retval; | ||
300 | } | ||
301 | |||
302 | case WDIOC_SETTIMEOUT: | ||
303 | { | ||
304 | if (get_user(new_heartbeat, p)) | ||
305 | return -EFAULT; | ||
306 | |||
307 | if (esb_timer_set_heartbeat(new_heartbeat)) | ||
308 | return -EINVAL; | ||
309 | |||
310 | esb_timer_keepalive (); | ||
311 | /* Fall */ | ||
312 | } | ||
313 | |||
314 | case WDIOC_GETTIMEOUT: | ||
315 | return put_user(heartbeat, p); | ||
316 | |||
317 | default: | ||
318 | return -ENOTTY; | ||
319 | } | ||
320 | } | ||
321 | |||
322 | /* | ||
323 | * Notify system | ||
324 | */ | ||
325 | |||
326 | static int esb_notify_sys (struct notifier_block *this, unsigned long code, void *unused) | ||
327 | { | ||
328 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
329 | /* Turn the WDT off */ | ||
330 | esb_timer_stop (); | ||
331 | } | ||
332 | |||
333 | return NOTIFY_DONE; | ||
334 | } | ||
335 | |||
336 | /* | ||
337 | * Kernel Interfaces | ||
338 | */ | ||
339 | |||
340 | static const struct file_operations esb_fops = { | ||
341 | .owner = THIS_MODULE, | ||
342 | .llseek = no_llseek, | ||
343 | .write = esb_write, | ||
344 | .ioctl = esb_ioctl, | ||
345 | .open = esb_open, | ||
346 | .release = esb_release, | ||
347 | }; | ||
348 | |||
349 | static struct miscdevice esb_miscdev = { | ||
350 | .minor = WATCHDOG_MINOR, | ||
351 | .name = "watchdog", | ||
352 | .fops = &esb_fops, | ||
353 | }; | ||
354 | |||
355 | static struct notifier_block esb_notifier = { | ||
356 | .notifier_call = esb_notify_sys, | ||
357 | }; | ||
358 | |||
359 | /* | ||
360 | * Data for PCI driver interface | ||
361 | * | ||
362 | * This data only exists for exporting the supported | ||
363 | * PCI ids via MODULE_DEVICE_TABLE. We do not actually | ||
364 | * register a pci_driver, because someone else might one day | ||
365 | * want to register another driver on the same PCI id. | ||
366 | */ | ||
367 | static struct pci_device_id esb_pci_tbl[] = { | ||
368 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_9), }, | ||
369 | { 0, }, /* End of list */ | ||
370 | }; | ||
371 | MODULE_DEVICE_TABLE (pci, esb_pci_tbl); | ||
372 | |||
373 | /* | ||
374 | * Init & exit routines | ||
375 | */ | ||
376 | |||
377 | static unsigned char __init esb_getdevice (void) | ||
378 | { | ||
379 | u8 val1; | ||
380 | unsigned short val2; | ||
381 | |||
382 | struct pci_dev *dev = NULL; | ||
383 | /* | ||
384 | * Find the PCI device | ||
385 | */ | ||
386 | |||
387 | for_each_pci_dev(dev) { | ||
388 | if (pci_match_id(esb_pci_tbl, dev)) { | ||
389 | esb_pci = dev; | ||
390 | break; | ||
391 | } | ||
392 | } | ||
393 | |||
394 | if (esb_pci) { | ||
395 | if (pci_enable_device(esb_pci)) { | ||
396 | printk (KERN_ERR PFX "failed to enable device\n"); | ||
397 | goto err_devput; | ||
398 | } | ||
399 | |||
400 | if (pci_request_region(esb_pci, 0, ESB_MODULE_NAME)) { | ||
401 | printk (KERN_ERR PFX "failed to request region\n"); | ||
402 | goto err_disable; | ||
403 | } | ||
404 | |||
405 | BASEADDR = ioremap(pci_resource_start(esb_pci, 0), | ||
406 | pci_resource_len(esb_pci, 0)); | ||
407 | if (BASEADDR == NULL) { | ||
408 | /* Something's wrong here, BASEADDR has to be set */ | ||
409 | printk (KERN_ERR PFX "failed to get BASEADDR\n"); | ||
410 | goto err_release; | ||
411 | } | ||
412 | |||
413 | /* | ||
414 | * The watchdog has two timers, it can be setup so that the | ||
415 | * expiry of timer1 results in an interrupt and the expiry of | ||
416 | * timer2 results in a reboot. We set it to not generate | ||
417 | * any interrupts as there is not much we can do with it | ||
418 | * right now. | ||
419 | * | ||
420 | * We also enable reboots and set the timer frequency to | ||
421 | * the PCI clock divided by 2^15 (approx 1KHz). | ||
422 | */ | ||
423 | pci_write_config_word(esb_pci, ESB_CONFIG_REG, 0x0003); | ||
424 | |||
425 | /* Check that the WDT isn't already locked */ | ||
426 | pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val1); | ||
427 | if (val1 & ESB_WDT_LOCK) | ||
428 | printk (KERN_WARNING PFX "nowayout already set\n"); | ||
429 | |||
430 | /* Set the timer to watchdog mode and disable it for now */ | ||
431 | pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x00); | ||
432 | |||
433 | /* Check if the watchdog was previously triggered */ | ||
434 | esb_unlock_registers(); | ||
435 | val2 = readw(ESB_RELOAD_REG); | ||
436 | triggered = (val2 & (0x01 << 9) >> 9); | ||
437 | |||
438 | /* Reset trigger flag and timers */ | ||
439 | esb_unlock_registers(); | ||
440 | writew((0x11 << 8), ESB_RELOAD_REG); | ||
441 | |||
442 | /* Done */ | ||
443 | return 1; | ||
444 | |||
445 | err_release: | ||
446 | pci_release_region(esb_pci, 0); | ||
447 | err_disable: | ||
448 | pci_disable_device(esb_pci); | ||
449 | err_devput: | ||
450 | pci_dev_put(esb_pci); | ||
451 | } | ||
452 | return 0; | ||
453 | } | ||
454 | |||
455 | static int __init watchdog_init (void) | ||
456 | { | ||
457 | int ret; | ||
458 | |||
459 | spin_lock_init(&esb_lock); | ||
460 | |||
461 | /* Check whether or not the hardware watchdog is there */ | ||
462 | if (!esb_getdevice () || esb_pci == NULL) | ||
463 | return -ENODEV; | ||
464 | |||
465 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
466 | if (esb_timer_set_heartbeat (heartbeat)) { | ||
467 | esb_timer_set_heartbeat (WATCHDOG_HEARTBEAT); | ||
468 | printk(KERN_INFO PFX "heartbeat value must be 1<heartbeat<2046, using %d\n", | ||
469 | heartbeat); | ||
470 | } | ||
471 | |||
472 | ret = register_reboot_notifier(&esb_notifier); | ||
473 | if (ret != 0) { | ||
474 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
475 | ret); | ||
476 | goto err_unmap; | ||
477 | } | ||
478 | |||
479 | ret = misc_register(&esb_miscdev); | ||
480 | if (ret != 0) { | ||
481 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
482 | WATCHDOG_MINOR, ret); | ||
483 | goto err_notifier; | ||
484 | } | ||
485 | |||
486 | esb_timer_stop (); | ||
487 | |||
488 | printk (KERN_INFO PFX "initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", | ||
489 | BASEADDR, heartbeat, nowayout); | ||
490 | |||
491 | return 0; | ||
492 | |||
493 | err_notifier: | ||
494 | unregister_reboot_notifier(&esb_notifier); | ||
495 | err_unmap: | ||
496 | iounmap(BASEADDR); | ||
497 | /* err_release: */ | ||
498 | pci_release_region(esb_pci, 0); | ||
499 | /* err_disable: */ | ||
500 | pci_disable_device(esb_pci); | ||
501 | /* err_devput: */ | ||
502 | pci_dev_put(esb_pci); | ||
503 | return ret; | ||
504 | } | ||
505 | |||
506 | static void __exit watchdog_cleanup (void) | ||
507 | { | ||
508 | /* Stop the timer before we leave */ | ||
509 | if (!nowayout) | ||
510 | esb_timer_stop (); | ||
511 | |||
512 | /* Deregister */ | ||
513 | misc_deregister(&esb_miscdev); | ||
514 | unregister_reboot_notifier(&esb_notifier); | ||
515 | iounmap(BASEADDR); | ||
516 | pci_release_region(esb_pci, 0); | ||
517 | pci_disable_device(esb_pci); | ||
518 | pci_dev_put(esb_pci); | ||
519 | } | ||
520 | |||
521 | module_init(watchdog_init); | ||
522 | module_exit(watchdog_cleanup); | ||
523 | |||
524 | MODULE_AUTHOR("Ross Biro and David Härdeman"); | ||
525 | MODULE_DESCRIPTION("Watchdog driver for Intel 6300ESB chipsets"); | ||
526 | MODULE_LICENSE("GPL"); | ||
527 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/iTCO_vendor_support.c b/drivers/watchdog/iTCO_vendor_support.c new file mode 100644 index 000000000000..415083990097 --- /dev/null +++ b/drivers/watchdog/iTCO_vendor_support.c | |||
@@ -0,0 +1,307 @@ | |||
1 | /* | ||
2 | * intel TCO vendor specific watchdog driver support | ||
3 | * | ||
4 | * (c) Copyright 2006 Wim Van Sebroeck <wim@iguana.be>. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License | ||
8 | * as published by the Free Software Foundation; either version | ||
9 | * 2 of the License, or (at your option) any later version. | ||
10 | * | ||
11 | * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor | ||
12 | * provide warranty for any of this software. This material is | ||
13 | * provided "AS-IS" and at no charge. | ||
14 | */ | ||
15 | |||
16 | /* | ||
17 | * Includes, defines, variables, module parameters, ... | ||
18 | */ | ||
19 | |||
20 | /* Module and version information */ | ||
21 | #define DRV_NAME "iTCO_vendor_support" | ||
22 | #define DRV_VERSION "1.01" | ||
23 | #define DRV_RELDATE "11-Nov-2006" | ||
24 | #define PFX DRV_NAME ": " | ||
25 | |||
26 | /* Includes */ | ||
27 | #include <linux/module.h> /* For module specific items */ | ||
28 | #include <linux/moduleparam.h> /* For new moduleparam's */ | ||
29 | #include <linux/types.h> /* For standard types (like size_t) */ | ||
30 | #include <linux/errno.h> /* For the -ENODEV/... values */ | ||
31 | #include <linux/kernel.h> /* For printk/panic/... */ | ||
32 | #include <linux/init.h> /* For __init/__exit/... */ | ||
33 | #include <linux/ioport.h> /* For io-port access */ | ||
34 | |||
35 | #include <asm/io.h> /* For inb/outb/... */ | ||
36 | |||
37 | /* iTCO defines */ | ||
38 | #define SMI_EN acpibase + 0x30 /* SMI Control and Enable Register */ | ||
39 | #define TCOBASE acpibase + 0x60 /* TCO base address */ | ||
40 | #define TCO1_STS TCOBASE + 0x04 /* TCO1 Status Register */ | ||
41 | |||
42 | /* List of vendor support modes */ | ||
43 | #define SUPERMICRO_OLD_BOARD 1 /* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */ | ||
44 | #define SUPERMICRO_NEW_BOARD 2 /* SuperMicro Pentium 4 / Xeon 4 / EMT64T Era Systems */ | ||
45 | |||
46 | static int vendorsupport = 0; | ||
47 | module_param(vendorsupport, int, 0); | ||
48 | MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default=0 (none), 1=SuperMicro Pent3, 2=SuperMicro Pent4+"); | ||
49 | |||
50 | /* | ||
51 | * Vendor Specific Support | ||
52 | */ | ||
53 | |||
54 | /* | ||
55 | * Vendor Support: 1 | ||
56 | * Board: Super Micro Computer Inc. 370SSE+-OEM1/P3TSSE | ||
57 | * iTCO chipset: ICH2 | ||
58 | * | ||
59 | * Code contributed by: R. Seretny <lkpatches@paypc.com> | ||
60 | * Documentation obtained by R. Seretny from SuperMicro Technical Support | ||
61 | * | ||
62 | * To enable Watchdog function: | ||
63 | * BIOS setup -> Power -> TCO Logic SMI Enable -> Within5Minutes | ||
64 | * This setting enables SMI to clear the watchdog expired flag. | ||
65 | * If BIOS or CPU fail which may cause SMI hang, then system will | ||
66 | * reboot. When application starts to use watchdog function, | ||
67 | * application has to take over the control from SMI. | ||
68 | * | ||
69 | * For P3TSSE, J36 jumper needs to be removed to enable the Watchdog | ||
70 | * function. | ||
71 | * | ||
72 | * Note: The system will reboot when Expire Flag is set TWICE. | ||
73 | * So, if the watchdog timer is 20 seconds, then the maximum hang | ||
74 | * time is about 40 seconds, and the minimum hang time is about | ||
75 | * 20.6 seconds. | ||
76 | */ | ||
77 | |||
78 | static void supermicro_old_pre_start(unsigned long acpibase) | ||
79 | { | ||
80 | unsigned long val32; | ||
81 | |||
82 | val32 = inl(SMI_EN); | ||
83 | val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */ | ||
84 | outl(val32, SMI_EN); /* Needed to activate watchdog */ | ||
85 | } | ||
86 | |||
87 | static void supermicro_old_pre_stop(unsigned long acpibase) | ||
88 | { | ||
89 | unsigned long val32; | ||
90 | |||
91 | val32 = inl(SMI_EN); | ||
92 | val32 &= 0x00002000; /* Turn on SMI clearing watchdog */ | ||
93 | outl(val32, SMI_EN); /* Needed to deactivate watchdog */ | ||
94 | } | ||
95 | |||
96 | static void supermicro_old_pre_keepalive(unsigned long acpibase) | ||
97 | { | ||
98 | /* Reload TCO Timer (done in iTCO_wdt_keepalive) + */ | ||
99 | /* Clear "Expire Flag" (Bit 3 of TC01_STS register) */ | ||
100 | outb(0x08, TCO1_STS); | ||
101 | } | ||
102 | |||
103 | /* | ||
104 | * Vendor Support: 2 | ||
105 | * Board: Super Micro Computer Inc. P4SBx, P4DPx | ||
106 | * iTCO chipset: ICH4 | ||
107 | * | ||
108 | * Code contributed by: R. Seretny <lkpatches@paypc.com> | ||
109 | * Documentation obtained by R. Seretny from SuperMicro Technical Support | ||
110 | * | ||
111 | * To enable Watchdog function: | ||
112 | * 1. BIOS | ||
113 | * For P4SBx: | ||
114 | * BIOS setup -> Advanced -> Integrated Peripherals -> Watch Dog Feature | ||
115 | * For P4DPx: | ||
116 | * BIOS setup -> Advanced -> I/O Device Configuration -> Watch Dog | ||
117 | * This setting enables or disables Watchdog function. When enabled, the | ||
118 | * default watchdog timer is set to be 5 minutes (about 4’35â€). It is | ||
119 | * enough to load and run the OS. The application (service or driver) has | ||
120 | * to take over the control once OS is running up and before watchdog | ||
121 | * expires. | ||
122 | * | ||
123 | * 2. JUMPER | ||
124 | * For P4SBx: JP39 | ||
125 | * For P4DPx: JP37 | ||
126 | * This jumper is used for safety. Closed is enabled. This jumper | ||
127 | * prevents user enables watchdog in BIOS by accident. | ||
128 | * | ||
129 | * To enable Watch Dog function, both BIOS and JUMPER must be enabled. | ||
130 | * | ||
131 | * The documentation lists motherboards P4SBx and P4DPx series as of | ||
132 | * 20-March-2002. However, this code works flawlessly with much newer | ||
133 | * motherboards, such as my X6DHR-8G2 (SuperServer 6014H-82). | ||
134 | * | ||
135 | * The original iTCO driver as written does not actually reset the | ||
136 | * watchdog timer on these machines, as a result they reboot after five | ||
137 | * minutes. | ||
138 | * | ||
139 | * NOTE: You may leave the Watchdog function disabled in the SuperMicro | ||
140 | * BIOS to avoid a "boot-race"... This driver will enable watchdog | ||
141 | * functionality even if it's disabled in the BIOS once the /dev/watchdog | ||
142 | * file is opened. | ||
143 | */ | ||
144 | |||
145 | /* I/O Port's */ | ||
146 | #define SM_REGINDEX 0x2e /* SuperMicro ICH4+ Register Index */ | ||
147 | #define SM_DATAIO 0x2f /* SuperMicro ICH4+ Register Data I/O */ | ||
148 | |||
149 | /* Control Register's */ | ||
150 | #define SM_CTLPAGESW 0x07 /* SuperMicro ICH4+ Control Page Switch */ | ||
151 | #define SM_CTLPAGE 0x08 /* SuperMicro ICH4+ Control Page Num */ | ||
152 | |||
153 | #define SM_WATCHENABLE 0x30 /* Watchdog enable: Bit 0: 0=off, 1=on */ | ||
154 | |||
155 | #define SM_WATCHPAGE 0x87 /* Watchdog unlock control page */ | ||
156 | |||
157 | #define SM_ENDWATCH 0xAA /* Watchdog lock control page */ | ||
158 | |||
159 | #define SM_COUNTMODE 0xf5 /* Watchdog count mode select */ | ||
160 | /* (Bit 3: 0 = seconds, 1 = minutes */ | ||
161 | |||
162 | #define SM_WATCHTIMER 0xf6 /* 8-bits, Watchdog timer counter (RW) */ | ||
163 | |||
164 | #define SM_RESETCONTROL 0xf7 /* Watchdog reset control */ | ||
165 | /* Bit 6: timer is reset by kbd interrupt */ | ||
166 | /* Bit 7: timer is reset by mouse interrupt */ | ||
167 | |||
168 | static void supermicro_new_unlock_watchdog(void) | ||
169 | { | ||
170 | outb(SM_WATCHPAGE, SM_REGINDEX); /* Write 0x87 to port 0x2e twice */ | ||
171 | outb(SM_WATCHPAGE, SM_REGINDEX); | ||
172 | |||
173 | outb(SM_CTLPAGESW, SM_REGINDEX); /* Switch to watchdog control page */ | ||
174 | outb(SM_CTLPAGE, SM_DATAIO); | ||
175 | } | ||
176 | |||
177 | static void supermicro_new_lock_watchdog(void) | ||
178 | { | ||
179 | outb(SM_ENDWATCH, SM_REGINDEX); | ||
180 | } | ||
181 | |||
182 | static void supermicro_new_pre_start(unsigned int heartbeat) | ||
183 | { | ||
184 | unsigned int val; | ||
185 | |||
186 | supermicro_new_unlock_watchdog(); | ||
187 | |||
188 | /* Watchdog timer setting needs to be in seconds*/ | ||
189 | outb(SM_COUNTMODE, SM_REGINDEX); | ||
190 | val = inb(SM_DATAIO); | ||
191 | val &= 0xF7; | ||
192 | outb(val, SM_DATAIO); | ||
193 | |||
194 | /* Write heartbeat interval to WDOG */ | ||
195 | outb (SM_WATCHTIMER, SM_REGINDEX); | ||
196 | outb((heartbeat & 255), SM_DATAIO); | ||
197 | |||
198 | /* Make sure keyboard/mouse interrupts don't interfere */ | ||
199 | outb(SM_RESETCONTROL, SM_REGINDEX); | ||
200 | val = inb(SM_DATAIO); | ||
201 | val &= 0x3f; | ||
202 | outb(val, SM_DATAIO); | ||
203 | |||
204 | /* enable watchdog by setting bit 0 of Watchdog Enable to 1 */ | ||
205 | outb(SM_WATCHENABLE, SM_REGINDEX); | ||
206 | val = inb(SM_DATAIO); | ||
207 | val |= 0x01; | ||
208 | outb(val, SM_DATAIO); | ||
209 | |||
210 | supermicro_new_lock_watchdog(); | ||
211 | } | ||
212 | |||
213 | static void supermicro_new_pre_stop(void) | ||
214 | { | ||
215 | unsigned int val; | ||
216 | |||
217 | supermicro_new_unlock_watchdog(); | ||
218 | |||
219 | /* disable watchdog by setting bit 0 of Watchdog Enable to 0 */ | ||
220 | outb(SM_WATCHENABLE, SM_REGINDEX); | ||
221 | val = inb(SM_DATAIO); | ||
222 | val &= 0xFE; | ||
223 | outb(val, SM_DATAIO); | ||
224 | |||
225 | supermicro_new_lock_watchdog(); | ||
226 | } | ||
227 | |||
228 | static void supermicro_new_pre_set_heartbeat(unsigned int heartbeat) | ||
229 | { | ||
230 | supermicro_new_unlock_watchdog(); | ||
231 | |||
232 | /* reset watchdog timeout to heartveat value */ | ||
233 | outb(SM_WATCHTIMER, SM_REGINDEX); | ||
234 | outb((heartbeat & 255), SM_DATAIO); | ||
235 | |||
236 | supermicro_new_lock_watchdog(); | ||
237 | } | ||
238 | |||
239 | /* | ||
240 | * Generic Support Functions | ||
241 | */ | ||
242 | |||
243 | void iTCO_vendor_pre_start(unsigned long acpibase, | ||
244 | unsigned int heartbeat) | ||
245 | { | ||
246 | if (vendorsupport == SUPERMICRO_OLD_BOARD) | ||
247 | supermicro_old_pre_start(acpibase); | ||
248 | else if (vendorsupport == SUPERMICRO_NEW_BOARD) | ||
249 | supermicro_new_pre_start(heartbeat); | ||
250 | } | ||
251 | EXPORT_SYMBOL(iTCO_vendor_pre_start); | ||
252 | |||
253 | void iTCO_vendor_pre_stop(unsigned long acpibase) | ||
254 | { | ||
255 | if (vendorsupport == SUPERMICRO_OLD_BOARD) | ||
256 | supermicro_old_pre_stop(acpibase); | ||
257 | else if (vendorsupport == SUPERMICRO_NEW_BOARD) | ||
258 | supermicro_new_pre_stop(); | ||
259 | } | ||
260 | EXPORT_SYMBOL(iTCO_vendor_pre_stop); | ||
261 | |||
262 | void iTCO_vendor_pre_keepalive(unsigned long acpibase, unsigned int heartbeat) | ||
263 | { | ||
264 | if (vendorsupport == SUPERMICRO_OLD_BOARD) | ||
265 | supermicro_old_pre_keepalive(acpibase); | ||
266 | else if (vendorsupport == SUPERMICRO_NEW_BOARD) | ||
267 | supermicro_new_pre_set_heartbeat(heartbeat); | ||
268 | } | ||
269 | EXPORT_SYMBOL(iTCO_vendor_pre_keepalive); | ||
270 | |||
271 | void iTCO_vendor_pre_set_heartbeat(unsigned int heartbeat) | ||
272 | { | ||
273 | if (vendorsupport == SUPERMICRO_NEW_BOARD) | ||
274 | supermicro_new_pre_set_heartbeat(heartbeat); | ||
275 | } | ||
276 | EXPORT_SYMBOL(iTCO_vendor_pre_set_heartbeat); | ||
277 | |||
278 | int iTCO_vendor_check_noreboot_on(void) | ||
279 | { | ||
280 | switch(vendorsupport) { | ||
281 | case SUPERMICRO_OLD_BOARD: | ||
282 | return 0; | ||
283 | default: | ||
284 | return 1; | ||
285 | } | ||
286 | } | ||
287 | EXPORT_SYMBOL(iTCO_vendor_check_noreboot_on); | ||
288 | |||
289 | static int __init iTCO_vendor_init_module(void) | ||
290 | { | ||
291 | printk (KERN_INFO PFX "vendor-support=%d\n", vendorsupport); | ||
292 | return 0; | ||
293 | } | ||
294 | |||
295 | static void __exit iTCO_vendor_exit_module(void) | ||
296 | { | ||
297 | printk (KERN_INFO PFX "Module Unloaded\n"); | ||
298 | } | ||
299 | |||
300 | module_init(iTCO_vendor_init_module); | ||
301 | module_exit(iTCO_vendor_exit_module); | ||
302 | |||
303 | MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>, R. Seretny <lkpatches@paypc.com>"); | ||
304 | MODULE_DESCRIPTION("Intel TCO Vendor Specific WatchDog Timer Driver Support"); | ||
305 | MODULE_VERSION(DRV_VERSION); | ||
306 | MODULE_LICENSE("GPL"); | ||
307 | |||
diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c new file mode 100644 index 000000000000..cd5a565bc3a0 --- /dev/null +++ b/drivers/watchdog/iTCO_wdt.c | |||
@@ -0,0 +1,804 @@ | |||
1 | /* | ||
2 | * intel TCO Watchdog Driver (Used in i82801 and i6300ESB chipsets) | ||
3 | * | ||
4 | * (c) Copyright 2006-2007 Wim Van Sebroeck <wim@iguana.be>. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License | ||
8 | * as published by the Free Software Foundation; either version | ||
9 | * 2 of the License, or (at your option) any later version. | ||
10 | * | ||
11 | * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor | ||
12 | * provide warranty for any of this software. This material is | ||
13 | * provided "AS-IS" and at no charge. | ||
14 | * | ||
15 | * The TCO watchdog is implemented in the following I/O controller hubs: | ||
16 | * (See the intel documentation on http://developer.intel.com.) | ||
17 | * 82801AA (ICH) : document number 290655-003, 290677-014, | ||
18 | * 82801AB (ICHO) : document number 290655-003, 290677-014, | ||
19 | * 82801BA (ICH2) : document number 290687-002, 298242-027, | ||
20 | * 82801BAM (ICH2-M) : document number 290687-002, 298242-027, | ||
21 | * 82801CA (ICH3-S) : document number 290733-003, 290739-013, | ||
22 | * 82801CAM (ICH3-M) : document number 290716-001, 290718-007, | ||
23 | * 82801DB (ICH4) : document number 290744-001, 290745-020, | ||
24 | * 82801DBM (ICH4-M) : document number 252337-001, 252663-005, | ||
25 | * 82801E (C-ICH) : document number 273599-001, 273645-002, | ||
26 | * 82801EB (ICH5) : document number 252516-001, 252517-003, | ||
27 | * 82801ER (ICH5R) : document number 252516-001, 252517-003, | ||
28 | * 82801FB (ICH6) : document number 301473-002, 301474-007, | ||
29 | * 82801FR (ICH6R) : document number 301473-002, 301474-007, | ||
30 | * 82801FBM (ICH6-M) : document number 301473-002, 301474-007, | ||
31 | * 82801FW (ICH6W) : document number 301473-001, 301474-007, | ||
32 | * 82801FRW (ICH6RW) : document number 301473-001, 301474-007, | ||
33 | * 82801GB (ICH7) : document number 307013-002, 307014-009, | ||
34 | * 82801GR (ICH7R) : document number 307013-002, 307014-009, | ||
35 | * 82801GDH (ICH7DH) : document number 307013-002, 307014-009, | ||
36 | * 82801GBM (ICH7-M) : document number 307013-002, 307014-009, | ||
37 | * 82801GHM (ICH7-M DH) : document number 307013-002, 307014-009, | ||
38 | * 82801HB (ICH8) : document number 313056-002, 313057-004, | ||
39 | * 82801HR (ICH8R) : document number 313056-002, 313057-004, | ||
40 | * 82801HH (ICH8DH) : document number 313056-002, 313057-004, | ||
41 | * 82801HO (ICH8DO) : document number 313056-002, 313057-004, | ||
42 | * 82801IB (ICH9) : document number 316972-001, 316973-001, | ||
43 | * 82801IR (ICH9R) : document number 316972-001, 316973-001, | ||
44 | * 82801IH (ICH9DH) : document number 316972-001, 316973-001, | ||
45 | * 6300ESB (6300ESB) : document number 300641-003, 300884-010, | ||
46 | * 631xESB (631xESB) : document number 313082-001, 313075-005, | ||
47 | * 632xESB (632xESB) : document number 313082-001, 313075-005 | ||
48 | */ | ||
49 | |||
50 | /* | ||
51 | * Includes, defines, variables, module parameters, ... | ||
52 | */ | ||
53 | |||
54 | /* Module and version information */ | ||
55 | #define DRV_NAME "iTCO_wdt" | ||
56 | #define DRV_VERSION "1.02" | ||
57 | #define DRV_RELDATE "26-Jul-2007" | ||
58 | #define PFX DRV_NAME ": " | ||
59 | |||
60 | /* Includes */ | ||
61 | #include <linux/module.h> /* For module specific items */ | ||
62 | #include <linux/moduleparam.h> /* For new moduleparam's */ | ||
63 | #include <linux/types.h> /* For standard types (like size_t) */ | ||
64 | #include <linux/errno.h> /* For the -ENODEV/... values */ | ||
65 | #include <linux/kernel.h> /* For printk/panic/... */ | ||
66 | #include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ | ||
67 | #include <linux/watchdog.h> /* For the watchdog specific items */ | ||
68 | #include <linux/init.h> /* For __init/__exit/... */ | ||
69 | #include <linux/fs.h> /* For file operations */ | ||
70 | #include <linux/platform_device.h> /* For platform_driver framework */ | ||
71 | #include <linux/pci.h> /* For pci functions */ | ||
72 | #include <linux/ioport.h> /* For io-port access */ | ||
73 | #include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ | ||
74 | |||
75 | #include <asm/uaccess.h> /* For copy_to_user/put_user/... */ | ||
76 | #include <asm/io.h> /* For inb/outb/... */ | ||
77 | |||
78 | /* TCO related info */ | ||
79 | enum iTCO_chipsets { | ||
80 | TCO_ICH = 0, /* ICH */ | ||
81 | TCO_ICH0, /* ICH0 */ | ||
82 | TCO_ICH2, /* ICH2 */ | ||
83 | TCO_ICH2M, /* ICH2-M */ | ||
84 | TCO_ICH3, /* ICH3-S */ | ||
85 | TCO_ICH3M, /* ICH3-M */ | ||
86 | TCO_ICH4, /* ICH4 */ | ||
87 | TCO_ICH4M, /* ICH4-M */ | ||
88 | TCO_CICH, /* C-ICH */ | ||
89 | TCO_ICH5, /* ICH5 & ICH5R */ | ||
90 | TCO_6300ESB, /* 6300ESB */ | ||
91 | TCO_ICH6, /* ICH6 & ICH6R */ | ||
92 | TCO_ICH6M, /* ICH6-M */ | ||
93 | TCO_ICH6W, /* ICH6W & ICH6RW */ | ||
94 | TCO_ICH7, /* ICH7 & ICH7R */ | ||
95 | TCO_ICH7M, /* ICH7-M */ | ||
96 | TCO_ICH7MDH, /* ICH7-M DH */ | ||
97 | TCO_ICH8, /* ICH8 & ICH8R */ | ||
98 | TCO_ICH8DH, /* ICH8DH */ | ||
99 | TCO_ICH8DO, /* ICH8DO */ | ||
100 | TCO_ICH9, /* ICH9 */ | ||
101 | TCO_ICH9R, /* ICH9R */ | ||
102 | TCO_ICH9DH, /* ICH9DH */ | ||
103 | TCO_631XESB, /* 631xESB/632xESB */ | ||
104 | }; | ||
105 | |||
106 | static struct { | ||
107 | char *name; | ||
108 | unsigned int iTCO_version; | ||
109 | } iTCO_chipset_info[] __devinitdata = { | ||
110 | {"ICH", 1}, | ||
111 | {"ICH0", 1}, | ||
112 | {"ICH2", 1}, | ||
113 | {"ICH2-M", 1}, | ||
114 | {"ICH3-S", 1}, | ||
115 | {"ICH3-M", 1}, | ||
116 | {"ICH4", 1}, | ||
117 | {"ICH4-M", 1}, | ||
118 | {"C-ICH", 1}, | ||
119 | {"ICH5 or ICH5R", 1}, | ||
120 | {"6300ESB", 1}, | ||
121 | {"ICH6 or ICH6R", 2}, | ||
122 | {"ICH6-M", 2}, | ||
123 | {"ICH6W or ICH6RW", 2}, | ||
124 | {"ICH7 or ICH7R", 2}, | ||
125 | {"ICH7-M", 2}, | ||
126 | {"ICH7-M DH", 2}, | ||
127 | {"ICH8 or ICH8R", 2}, | ||
128 | {"ICH8DH", 2}, | ||
129 | {"ICH8DO", 2}, | ||
130 | {"ICH9", 2}, | ||
131 | {"ICH9R", 2}, | ||
132 | {"ICH9DH", 2}, | ||
133 | {"631xESB/632xESB", 2}, | ||
134 | {NULL,0} | ||
135 | }; | ||
136 | |||
137 | /* | ||
138 | * This data only exists for exporting the supported PCI ids | ||
139 | * via MODULE_DEVICE_TABLE. We do not actually register a | ||
140 | * pci_driver, because the I/O Controller Hub has also other | ||
141 | * functions that probably will be registered by other drivers. | ||
142 | */ | ||
143 | static struct pci_device_id iTCO_wdt_pci_tbl[] = { | ||
144 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH }, | ||
145 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH0 }, | ||
146 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH2 }, | ||
147 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH2M }, | ||
148 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH3 }, | ||
149 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH3M }, | ||
150 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH4 }, | ||
151 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH4M }, | ||
152 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801E_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_CICH }, | ||
153 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH5 }, | ||
154 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_6300ESB }, | ||
155 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH6 }, | ||
156 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH6M }, | ||
157 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH6W }, | ||
158 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH7 }, | ||
159 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH7M }, | ||
160 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_31, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH7MDH }, | ||
161 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH8 }, | ||
162 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH8DH }, | ||
163 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH8DO }, | ||
164 | { PCI_VENDOR_ID_INTEL, 0x2918, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH9 }, | ||
165 | { PCI_VENDOR_ID_INTEL, 0x2916, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH9R }, | ||
166 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH9DH }, | ||
167 | { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB2_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
168 | { PCI_VENDOR_ID_INTEL, 0x2671, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
169 | { PCI_VENDOR_ID_INTEL, 0x2672, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
170 | { PCI_VENDOR_ID_INTEL, 0x2673, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
171 | { PCI_VENDOR_ID_INTEL, 0x2674, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
172 | { PCI_VENDOR_ID_INTEL, 0x2675, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
173 | { PCI_VENDOR_ID_INTEL, 0x2676, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
174 | { PCI_VENDOR_ID_INTEL, 0x2677, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
175 | { PCI_VENDOR_ID_INTEL, 0x2678, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
176 | { PCI_VENDOR_ID_INTEL, 0x2679, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
177 | { PCI_VENDOR_ID_INTEL, 0x267a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
178 | { PCI_VENDOR_ID_INTEL, 0x267b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
179 | { PCI_VENDOR_ID_INTEL, 0x267c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
180 | { PCI_VENDOR_ID_INTEL, 0x267d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
181 | { PCI_VENDOR_ID_INTEL, 0x267e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
182 | { PCI_VENDOR_ID_INTEL, 0x267f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB }, | ||
183 | { 0, }, /* End of list */ | ||
184 | }; | ||
185 | MODULE_DEVICE_TABLE (pci, iTCO_wdt_pci_tbl); | ||
186 | |||
187 | /* Address definitions for the TCO */ | ||
188 | #define TCOBASE iTCO_wdt_private.ACPIBASE + 0x60 /* TCO base address */ | ||
189 | #define SMI_EN iTCO_wdt_private.ACPIBASE + 0x30 /* SMI Control and Enable Register */ | ||
190 | |||
191 | #define TCO_RLD TCOBASE + 0x00 /* TCO Timer Reload and Current Value */ | ||
192 | #define TCOv1_TMR TCOBASE + 0x01 /* TCOv1 Timer Initial Value */ | ||
193 | #define TCO_DAT_IN TCOBASE + 0x02 /* TCO Data In Register */ | ||
194 | #define TCO_DAT_OUT TCOBASE + 0x03 /* TCO Data Out Register */ | ||
195 | #define TCO1_STS TCOBASE + 0x04 /* TCO1 Status Register */ | ||
196 | #define TCO2_STS TCOBASE + 0x06 /* TCO2 Status Register */ | ||
197 | #define TCO1_CNT TCOBASE + 0x08 /* TCO1 Control Register */ | ||
198 | #define TCO2_CNT TCOBASE + 0x0a /* TCO2 Control Register */ | ||
199 | #define TCOv2_TMR TCOBASE + 0x12 /* TCOv2 Timer Initial Value */ | ||
200 | |||
201 | /* internal variables */ | ||
202 | static unsigned long is_active; | ||
203 | static char expect_release; | ||
204 | static struct { /* this is private data for the iTCO_wdt device */ | ||
205 | unsigned int iTCO_version; /* TCO version/generation */ | ||
206 | unsigned long ACPIBASE; /* The cards ACPIBASE address (TCOBASE = ACPIBASE+0x60) */ | ||
207 | unsigned long __iomem *gcs; /* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2) */ | ||
208 | spinlock_t io_lock; /* the lock for io operations */ | ||
209 | struct pci_dev *pdev; /* the PCI-device */ | ||
210 | } iTCO_wdt_private; | ||
211 | |||
212 | static struct platform_device *iTCO_wdt_platform_device; /* the watchdog platform device */ | ||
213 | |||
214 | /* module parameters */ | ||
215 | #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ | ||
216 | static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ | ||
217 | module_param(heartbeat, int, 0); | ||
218 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39 (TCO v1) or 613 (TCO v2), default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
219 | |||
220 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
221 | module_param(nowayout, int, 0); | ||
222 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
223 | |||
224 | /* iTCO Vendor Specific Support hooks */ | ||
225 | #ifdef CONFIG_ITCO_VENDOR_SUPPORT | ||
226 | extern void iTCO_vendor_pre_start(unsigned long, unsigned int); | ||
227 | extern void iTCO_vendor_pre_stop(unsigned long); | ||
228 | extern void iTCO_vendor_pre_keepalive(unsigned long, unsigned int); | ||
229 | extern void iTCO_vendor_pre_set_heartbeat(unsigned int); | ||
230 | extern int iTCO_vendor_check_noreboot_on(void); | ||
231 | #else | ||
232 | #define iTCO_vendor_pre_start(acpibase, heartbeat) {} | ||
233 | #define iTCO_vendor_pre_stop(acpibase) {} | ||
234 | #define iTCO_vendor_pre_keepalive(acpibase,heartbeat) {} | ||
235 | #define iTCO_vendor_pre_set_heartbeat(heartbeat) {} | ||
236 | #define iTCO_vendor_check_noreboot_on() 1 /* 1=check noreboot; 0=don't check */ | ||
237 | #endif | ||
238 | |||
239 | /* | ||
240 | * Some TCO specific functions | ||
241 | */ | ||
242 | |||
243 | static inline unsigned int seconds_to_ticks(int seconds) | ||
244 | { | ||
245 | /* the internal timer is stored as ticks which decrement | ||
246 | * every 0.6 seconds */ | ||
247 | return (seconds * 10) / 6; | ||
248 | } | ||
249 | |||
250 | static void iTCO_wdt_set_NO_REBOOT_bit(void) | ||
251 | { | ||
252 | u32 val32; | ||
253 | |||
254 | /* Set the NO_REBOOT bit: this disables reboots */ | ||
255 | if (iTCO_wdt_private.iTCO_version == 2) { | ||
256 | val32 = readl(iTCO_wdt_private.gcs); | ||
257 | val32 |= 0x00000020; | ||
258 | writel(val32, iTCO_wdt_private.gcs); | ||
259 | } else if (iTCO_wdt_private.iTCO_version == 1) { | ||
260 | pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32); | ||
261 | val32 |= 0x00000002; | ||
262 | pci_write_config_dword(iTCO_wdt_private.pdev, 0xd4, val32); | ||
263 | } | ||
264 | } | ||
265 | |||
266 | static int iTCO_wdt_unset_NO_REBOOT_bit(void) | ||
267 | { | ||
268 | int ret = 0; | ||
269 | u32 val32; | ||
270 | |||
271 | /* Unset the NO_REBOOT bit: this enables reboots */ | ||
272 | if (iTCO_wdt_private.iTCO_version == 2) { | ||
273 | val32 = readl(iTCO_wdt_private.gcs); | ||
274 | val32 &= 0xffffffdf; | ||
275 | writel(val32, iTCO_wdt_private.gcs); | ||
276 | |||
277 | val32 = readl(iTCO_wdt_private.gcs); | ||
278 | if (val32 & 0x00000020) | ||
279 | ret = -EIO; | ||
280 | } else if (iTCO_wdt_private.iTCO_version == 1) { | ||
281 | pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32); | ||
282 | val32 &= 0xfffffffd; | ||
283 | pci_write_config_dword(iTCO_wdt_private.pdev, 0xd4, val32); | ||
284 | |||
285 | pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32); | ||
286 | if (val32 & 0x00000002) | ||
287 | ret = -EIO; | ||
288 | } | ||
289 | |||
290 | return ret; /* returns: 0 = OK, -EIO = Error */ | ||
291 | } | ||
292 | |||
293 | static int iTCO_wdt_start(void) | ||
294 | { | ||
295 | unsigned int val; | ||
296 | |||
297 | spin_lock(&iTCO_wdt_private.io_lock); | ||
298 | |||
299 | iTCO_vendor_pre_start(iTCO_wdt_private.ACPIBASE, heartbeat); | ||
300 | |||
301 | /* disable chipset's NO_REBOOT bit */ | ||
302 | if (iTCO_wdt_unset_NO_REBOOT_bit()) { | ||
303 | printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n"); | ||
304 | return -EIO; | ||
305 | } | ||
306 | |||
307 | /* Bit 11: TCO Timer Halt -> 0 = The TCO timer is enabled to count */ | ||
308 | val = inw(TCO1_CNT); | ||
309 | val &= 0xf7ff; | ||
310 | outw(val, TCO1_CNT); | ||
311 | val = inw(TCO1_CNT); | ||
312 | spin_unlock(&iTCO_wdt_private.io_lock); | ||
313 | |||
314 | if (val & 0x0800) | ||
315 | return -1; | ||
316 | return 0; | ||
317 | } | ||
318 | |||
319 | static int iTCO_wdt_stop(void) | ||
320 | { | ||
321 | unsigned int val; | ||
322 | |||
323 | spin_lock(&iTCO_wdt_private.io_lock); | ||
324 | |||
325 | iTCO_vendor_pre_stop(iTCO_wdt_private.ACPIBASE); | ||
326 | |||
327 | /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */ | ||
328 | val = inw(TCO1_CNT); | ||
329 | val |= 0x0800; | ||
330 | outw(val, TCO1_CNT); | ||
331 | val = inw(TCO1_CNT); | ||
332 | |||
333 | /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ | ||
334 | iTCO_wdt_set_NO_REBOOT_bit(); | ||
335 | |||
336 | spin_unlock(&iTCO_wdt_private.io_lock); | ||
337 | |||
338 | if ((val & 0x0800) == 0) | ||
339 | return -1; | ||
340 | return 0; | ||
341 | } | ||
342 | |||
343 | static int iTCO_wdt_keepalive(void) | ||
344 | { | ||
345 | spin_lock(&iTCO_wdt_private.io_lock); | ||
346 | |||
347 | iTCO_vendor_pre_keepalive(iTCO_wdt_private.ACPIBASE, heartbeat); | ||
348 | |||
349 | /* Reload the timer by writing to the TCO Timer Counter register */ | ||
350 | if (iTCO_wdt_private.iTCO_version == 2) { | ||
351 | outw(0x01, TCO_RLD); | ||
352 | } else if (iTCO_wdt_private.iTCO_version == 1) { | ||
353 | outb(0x01, TCO_RLD); | ||
354 | } | ||
355 | |||
356 | spin_unlock(&iTCO_wdt_private.io_lock); | ||
357 | return 0; | ||
358 | } | ||
359 | |||
360 | static int iTCO_wdt_set_heartbeat(int t) | ||
361 | { | ||
362 | unsigned int val16; | ||
363 | unsigned char val8; | ||
364 | unsigned int tmrval; | ||
365 | |||
366 | tmrval = seconds_to_ticks(t); | ||
367 | /* from the specs: */ | ||
368 | /* "Values of 0h-3h are ignored and should not be attempted" */ | ||
369 | if (tmrval < 0x04) | ||
370 | return -EINVAL; | ||
371 | if (((iTCO_wdt_private.iTCO_version == 2) && (tmrval > 0x3ff)) || | ||
372 | ((iTCO_wdt_private.iTCO_version == 1) && (tmrval > 0x03f))) | ||
373 | return -EINVAL; | ||
374 | |||
375 | iTCO_vendor_pre_set_heartbeat(tmrval); | ||
376 | |||
377 | /* Write new heartbeat to watchdog */ | ||
378 | if (iTCO_wdt_private.iTCO_version == 2) { | ||
379 | spin_lock(&iTCO_wdt_private.io_lock); | ||
380 | val16 = inw(TCOv2_TMR); | ||
381 | val16 &= 0xfc00; | ||
382 | val16 |= tmrval; | ||
383 | outw(val16, TCOv2_TMR); | ||
384 | val16 = inw(TCOv2_TMR); | ||
385 | spin_unlock(&iTCO_wdt_private.io_lock); | ||
386 | |||
387 | if ((val16 & 0x3ff) != tmrval) | ||
388 | return -EINVAL; | ||
389 | } else if (iTCO_wdt_private.iTCO_version == 1) { | ||
390 | spin_lock(&iTCO_wdt_private.io_lock); | ||
391 | val8 = inb(TCOv1_TMR); | ||
392 | val8 &= 0xc0; | ||
393 | val8 |= (tmrval & 0xff); | ||
394 | outb(val8, TCOv1_TMR); | ||
395 | val8 = inb(TCOv1_TMR); | ||
396 | spin_unlock(&iTCO_wdt_private.io_lock); | ||
397 | |||
398 | if ((val8 & 0x3f) != tmrval) | ||
399 | return -EINVAL; | ||
400 | } | ||
401 | |||
402 | heartbeat = t; | ||
403 | return 0; | ||
404 | } | ||
405 | |||
406 | static int iTCO_wdt_get_timeleft (int *time_left) | ||
407 | { | ||
408 | unsigned int val16; | ||
409 | unsigned char val8; | ||
410 | |||
411 | /* read the TCO Timer */ | ||
412 | if (iTCO_wdt_private.iTCO_version == 2) { | ||
413 | spin_lock(&iTCO_wdt_private.io_lock); | ||
414 | val16 = inw(TCO_RLD); | ||
415 | val16 &= 0x3ff; | ||
416 | spin_unlock(&iTCO_wdt_private.io_lock); | ||
417 | |||
418 | *time_left = (val16 * 6) / 10; | ||
419 | } else if (iTCO_wdt_private.iTCO_version == 1) { | ||
420 | spin_lock(&iTCO_wdt_private.io_lock); | ||
421 | val8 = inb(TCO_RLD); | ||
422 | val8 &= 0x3f; | ||
423 | spin_unlock(&iTCO_wdt_private.io_lock); | ||
424 | |||
425 | *time_left = (val8 * 6) / 10; | ||
426 | } else | ||
427 | return -EINVAL; | ||
428 | return 0; | ||
429 | } | ||
430 | |||
431 | /* | ||
432 | * /dev/watchdog handling | ||
433 | */ | ||
434 | |||
435 | static int iTCO_wdt_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 | /* | ||
442 | * Reload and activate timer | ||
443 | */ | ||
444 | iTCO_wdt_keepalive(); | ||
445 | iTCO_wdt_start(); | ||
446 | return nonseekable_open(inode, file); | ||
447 | } | ||
448 | |||
449 | static int iTCO_wdt_release (struct inode *inode, struct file *file) | ||
450 | { | ||
451 | /* | ||
452 | * Shut off the timer. | ||
453 | */ | ||
454 | if (expect_release == 42) { | ||
455 | iTCO_wdt_stop(); | ||
456 | } else { | ||
457 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
458 | iTCO_wdt_keepalive(); | ||
459 | } | ||
460 | clear_bit(0, &is_active); | ||
461 | expect_release = 0; | ||
462 | return 0; | ||
463 | } | ||
464 | |||
465 | static ssize_t iTCO_wdt_write (struct file *file, const char __user *data, | ||
466 | size_t len, loff_t * ppos) | ||
467 | { | ||
468 | /* See if we got the magic character 'V' and reload the timer */ | ||
469 | if (len) { | ||
470 | if (!nowayout) { | ||
471 | size_t i; | ||
472 | |||
473 | /* note: just in case someone wrote the magic character | ||
474 | * five months ago... */ | ||
475 | expect_release = 0; | ||
476 | |||
477 | /* scan to see whether or not we got the magic character */ | ||
478 | for (i = 0; i != len; i++) { | ||
479 | char c; | ||
480 | if (get_user(c, data+i)) | ||
481 | return -EFAULT; | ||
482 | if (c == 'V') | ||
483 | expect_release = 42; | ||
484 | } | ||
485 | } | ||
486 | |||
487 | /* someone wrote to us, we should reload the timer */ | ||
488 | iTCO_wdt_keepalive(); | ||
489 | } | ||
490 | return len; | ||
491 | } | ||
492 | |||
493 | static int iTCO_wdt_ioctl (struct inode *inode, struct file *file, | ||
494 | unsigned int cmd, unsigned long arg) | ||
495 | { | ||
496 | int new_options, retval = -EINVAL; | ||
497 | int new_heartbeat; | ||
498 | void __user *argp = (void __user *)arg; | ||
499 | int __user *p = argp; | ||
500 | static struct watchdog_info ident = { | ||
501 | .options = WDIOF_SETTIMEOUT | | ||
502 | WDIOF_KEEPALIVEPING | | ||
503 | WDIOF_MAGICCLOSE, | ||
504 | .firmware_version = 0, | ||
505 | .identity = DRV_NAME, | ||
506 | }; | ||
507 | |||
508 | switch (cmd) { | ||
509 | case WDIOC_GETSUPPORT: | ||
510 | return copy_to_user(argp, &ident, | ||
511 | sizeof (ident)) ? -EFAULT : 0; | ||
512 | |||
513 | case WDIOC_GETSTATUS: | ||
514 | case WDIOC_GETBOOTSTATUS: | ||
515 | return put_user(0, p); | ||
516 | |||
517 | case WDIOC_KEEPALIVE: | ||
518 | iTCO_wdt_keepalive(); | ||
519 | return 0; | ||
520 | |||
521 | case WDIOC_SETOPTIONS: | ||
522 | { | ||
523 | if (get_user(new_options, p)) | ||
524 | return -EFAULT; | ||
525 | |||
526 | if (new_options & WDIOS_DISABLECARD) { | ||
527 | iTCO_wdt_stop(); | ||
528 | retval = 0; | ||
529 | } | ||
530 | |||
531 | if (new_options & WDIOS_ENABLECARD) { | ||
532 | iTCO_wdt_keepalive(); | ||
533 | iTCO_wdt_start(); | ||
534 | retval = 0; | ||
535 | } | ||
536 | |||
537 | return retval; | ||
538 | } | ||
539 | |||
540 | case WDIOC_SETTIMEOUT: | ||
541 | { | ||
542 | if (get_user(new_heartbeat, p)) | ||
543 | return -EFAULT; | ||
544 | |||
545 | if (iTCO_wdt_set_heartbeat(new_heartbeat)) | ||
546 | return -EINVAL; | ||
547 | |||
548 | iTCO_wdt_keepalive(); | ||
549 | /* Fall */ | ||
550 | } | ||
551 | |||
552 | case WDIOC_GETTIMEOUT: | ||
553 | return put_user(heartbeat, p); | ||
554 | |||
555 | case WDIOC_GETTIMELEFT: | ||
556 | { | ||
557 | int time_left; | ||
558 | |||
559 | if (iTCO_wdt_get_timeleft(&time_left)) | ||
560 | return -EINVAL; | ||
561 | |||
562 | return put_user(time_left, p); | ||
563 | } | ||
564 | |||
565 | default: | ||
566 | return -ENOTTY; | ||
567 | } | ||
568 | } | ||
569 | |||
570 | /* | ||
571 | * Kernel Interfaces | ||
572 | */ | ||
573 | |||
574 | static const struct file_operations iTCO_wdt_fops = { | ||
575 | .owner = THIS_MODULE, | ||
576 | .llseek = no_llseek, | ||
577 | .write = iTCO_wdt_write, | ||
578 | .ioctl = iTCO_wdt_ioctl, | ||
579 | .open = iTCO_wdt_open, | ||
580 | .release = iTCO_wdt_release, | ||
581 | }; | ||
582 | |||
583 | static struct miscdevice iTCO_wdt_miscdev = { | ||
584 | .minor = WATCHDOG_MINOR, | ||
585 | .name = "watchdog", | ||
586 | .fops = &iTCO_wdt_fops, | ||
587 | }; | ||
588 | |||
589 | /* | ||
590 | * Init & exit routines | ||
591 | */ | ||
592 | |||
593 | static int iTCO_wdt_init(struct pci_dev *pdev, const struct pci_device_id *ent, struct platform_device *dev) | ||
594 | { | ||
595 | int ret; | ||
596 | u32 base_address; | ||
597 | unsigned long RCBA; | ||
598 | unsigned long val32; | ||
599 | |||
600 | /* | ||
601 | * Find the ACPI/PM base I/O address which is the base | ||
602 | * for the TCO registers (TCOBASE=ACPIBASE + 0x60) | ||
603 | * ACPIBASE is bits [15:7] from 0x40-0x43 | ||
604 | */ | ||
605 | pci_read_config_dword(pdev, 0x40, &base_address); | ||
606 | base_address &= 0x0000ff80; | ||
607 | if (base_address == 0x00000000) { | ||
608 | /* Something's wrong here, ACPIBASE has to be set */ | ||
609 | printk(KERN_ERR PFX "failed to get TCOBASE address\n"); | ||
610 | pci_dev_put(pdev); | ||
611 | return -ENODEV; | ||
612 | } | ||
613 | iTCO_wdt_private.iTCO_version = iTCO_chipset_info[ent->driver_data].iTCO_version; | ||
614 | iTCO_wdt_private.ACPIBASE = base_address; | ||
615 | iTCO_wdt_private.pdev = pdev; | ||
616 | |||
617 | /* Get the Memory-Mapped GCS register, we need it for the NO_REBOOT flag (TCO v2) */ | ||
618 | /* To get access to it you have to read RCBA from PCI Config space 0xf0 | ||
619 | and use it as base. GCS = RCBA + ICH6_GCS(0x3410). */ | ||
620 | if (iTCO_wdt_private.iTCO_version == 2) { | ||
621 | pci_read_config_dword(pdev, 0xf0, &base_address); | ||
622 | RCBA = base_address & 0xffffc000; | ||
623 | iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410),4); | ||
624 | } | ||
625 | |||
626 | /* Check chipset's NO_REBOOT bit */ | ||
627 | if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) { | ||
628 | printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n"); | ||
629 | ret = -ENODEV; /* Cannot reset NO_REBOOT bit */ | ||
630 | goto out; | ||
631 | } | ||
632 | |||
633 | /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ | ||
634 | iTCO_wdt_set_NO_REBOOT_bit(); | ||
635 | |||
636 | /* Set the TCO_EN bit in SMI_EN register */ | ||
637 | if (!request_region(SMI_EN, 4, "iTCO_wdt")) { | ||
638 | printk(KERN_ERR PFX "I/O address 0x%04lx already in use\n", | ||
639 | SMI_EN ); | ||
640 | ret = -EIO; | ||
641 | goto out; | ||
642 | } | ||
643 | val32 = inl(SMI_EN); | ||
644 | val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */ | ||
645 | outl(val32, SMI_EN); | ||
646 | release_region(SMI_EN, 4); | ||
647 | |||
648 | /* The TCO I/O registers reside in a 32-byte range pointed to by the TCOBASE value */ | ||
649 | if (!request_region (TCOBASE, 0x20, "iTCO_wdt")) { | ||
650 | printk (KERN_ERR PFX "I/O address 0x%04lx already in use\n", | ||
651 | TCOBASE); | ||
652 | ret = -EIO; | ||
653 | goto out; | ||
654 | } | ||
655 | |||
656 | printk(KERN_INFO PFX "Found a %s TCO device (Version=%d, TCOBASE=0x%04lx)\n", | ||
657 | iTCO_chipset_info[ent->driver_data].name, | ||
658 | iTCO_chipset_info[ent->driver_data].iTCO_version, | ||
659 | TCOBASE); | ||
660 | |||
661 | /* Clear out the (probably old) status */ | ||
662 | outb(0, TCO1_STS); | ||
663 | outb(3, TCO2_STS); | ||
664 | |||
665 | /* Make sure the watchdog is not running */ | ||
666 | iTCO_wdt_stop(); | ||
667 | |||
668 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
669 | if (iTCO_wdt_set_heartbeat(heartbeat)) { | ||
670 | iTCO_wdt_set_heartbeat(WATCHDOG_HEARTBEAT); | ||
671 | printk(KERN_INFO PFX "heartbeat value must be 2<heartbeat<39 (TCO v1) or 613 (TCO v2), using %d\n", | ||
672 | heartbeat); | ||
673 | } | ||
674 | |||
675 | ret = misc_register(&iTCO_wdt_miscdev); | ||
676 | if (ret != 0) { | ||
677 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
678 | WATCHDOG_MINOR, ret); | ||
679 | goto unreg_region; | ||
680 | } | ||
681 | |||
682 | printk (KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
683 | heartbeat, nowayout); | ||
684 | |||
685 | return 0; | ||
686 | |||
687 | unreg_region: | ||
688 | release_region (TCOBASE, 0x20); | ||
689 | out: | ||
690 | if (iTCO_wdt_private.iTCO_version == 2) | ||
691 | iounmap(iTCO_wdt_private.gcs); | ||
692 | pci_dev_put(iTCO_wdt_private.pdev); | ||
693 | iTCO_wdt_private.ACPIBASE = 0; | ||
694 | return ret; | ||
695 | } | ||
696 | |||
697 | static void iTCO_wdt_cleanup(void) | ||
698 | { | ||
699 | /* Stop the timer before we leave */ | ||
700 | if (!nowayout) | ||
701 | iTCO_wdt_stop(); | ||
702 | |||
703 | /* Deregister */ | ||
704 | misc_deregister(&iTCO_wdt_miscdev); | ||
705 | release_region(TCOBASE, 0x20); | ||
706 | if (iTCO_wdt_private.iTCO_version == 2) | ||
707 | iounmap(iTCO_wdt_private.gcs); | ||
708 | pci_dev_put(iTCO_wdt_private.pdev); | ||
709 | iTCO_wdt_private.ACPIBASE = 0; | ||
710 | } | ||
711 | |||
712 | static int iTCO_wdt_probe(struct platform_device *dev) | ||
713 | { | ||
714 | int found = 0; | ||
715 | struct pci_dev *pdev = NULL; | ||
716 | const struct pci_device_id *ent; | ||
717 | |||
718 | spin_lock_init(&iTCO_wdt_private.io_lock); | ||
719 | |||
720 | for_each_pci_dev(pdev) { | ||
721 | ent = pci_match_id(iTCO_wdt_pci_tbl, pdev); | ||
722 | if (ent) { | ||
723 | if (!(iTCO_wdt_init(pdev, ent, dev))) { | ||
724 | found++; | ||
725 | break; | ||
726 | } | ||
727 | } | ||
728 | } | ||
729 | |||
730 | if (!found) { | ||
731 | printk(KERN_INFO PFX "No card detected\n"); | ||
732 | return -ENODEV; | ||
733 | } | ||
734 | |||
735 | return 0; | ||
736 | } | ||
737 | |||
738 | static int iTCO_wdt_remove(struct platform_device *dev) | ||
739 | { | ||
740 | if (iTCO_wdt_private.ACPIBASE) | ||
741 | iTCO_wdt_cleanup(); | ||
742 | |||
743 | return 0; | ||
744 | } | ||
745 | |||
746 | static void iTCO_wdt_shutdown(struct platform_device *dev) | ||
747 | { | ||
748 | iTCO_wdt_stop(); | ||
749 | } | ||
750 | |||
751 | #define iTCO_wdt_suspend NULL | ||
752 | #define iTCO_wdt_resume NULL | ||
753 | |||
754 | static struct platform_driver iTCO_wdt_driver = { | ||
755 | .probe = iTCO_wdt_probe, | ||
756 | .remove = iTCO_wdt_remove, | ||
757 | .shutdown = iTCO_wdt_shutdown, | ||
758 | .suspend = iTCO_wdt_suspend, | ||
759 | .resume = iTCO_wdt_resume, | ||
760 | .driver = { | ||
761 | .owner = THIS_MODULE, | ||
762 | .name = DRV_NAME, | ||
763 | }, | ||
764 | }; | ||
765 | |||
766 | static int __init iTCO_wdt_init_module(void) | ||
767 | { | ||
768 | int err; | ||
769 | |||
770 | printk(KERN_INFO PFX "Intel TCO WatchDog Timer Driver v%s (%s)\n", | ||
771 | DRV_VERSION, DRV_RELDATE); | ||
772 | |||
773 | err = platform_driver_register(&iTCO_wdt_driver); | ||
774 | if (err) | ||
775 | return err; | ||
776 | |||
777 | iTCO_wdt_platform_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0); | ||
778 | if (IS_ERR(iTCO_wdt_platform_device)) { | ||
779 | err = PTR_ERR(iTCO_wdt_platform_device); | ||
780 | goto unreg_platform_driver; | ||
781 | } | ||
782 | |||
783 | return 0; | ||
784 | |||
785 | unreg_platform_driver: | ||
786 | platform_driver_unregister(&iTCO_wdt_driver); | ||
787 | return err; | ||
788 | } | ||
789 | |||
790 | static void __exit iTCO_wdt_cleanup_module(void) | ||
791 | { | ||
792 | platform_device_unregister(iTCO_wdt_platform_device); | ||
793 | platform_driver_unregister(&iTCO_wdt_driver); | ||
794 | printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); | ||
795 | } | ||
796 | |||
797 | module_init(iTCO_wdt_init_module); | ||
798 | module_exit(iTCO_wdt_cleanup_module); | ||
799 | |||
800 | MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>"); | ||
801 | MODULE_DESCRIPTION("Intel TCO WatchDog Timer Driver"); | ||
802 | MODULE_VERSION(DRV_VERSION); | ||
803 | MODULE_LICENSE("GPL"); | ||
804 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/ib700wdt.c b/drivers/watchdog/ib700wdt.c new file mode 100644 index 000000000000..c3a60f52ccb9 --- /dev/null +++ b/drivers/watchdog/ib700wdt.c | |||
@@ -0,0 +1,408 @@ | |||
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/module.h> | ||
35 | #include <linux/types.h> | ||
36 | #include <linux/miscdevice.h> | ||
37 | #include <linux/watchdog.h> | ||
38 | #include <linux/ioport.h> | ||
39 | #include <linux/fs.h> | ||
40 | #include <linux/init.h> | ||
41 | #include <linux/spinlock.h> | ||
42 | #include <linux/moduleparam.h> | ||
43 | #include <linux/platform_device.h> | ||
44 | |||
45 | #include <asm/io.h> | ||
46 | #include <asm/uaccess.h> | ||
47 | #include <asm/system.h> | ||
48 | |||
49 | static struct platform_device *ibwdt_platform_device; | ||
50 | static unsigned long ibwdt_is_open; | ||
51 | static spinlock_t ibwdt_lock; | ||
52 | static char expect_close; | ||
53 | |||
54 | /* Module information */ | ||
55 | #define DRV_NAME "ib700wdt" | ||
56 | #define PFX DRV_NAME ": " | ||
57 | |||
58 | /* | ||
59 | * | ||
60 | * Watchdog Timer Configuration | ||
61 | * | ||
62 | * The function of the watchdog timer is to reset the system | ||
63 | * automatically and is defined at I/O port 0443H. To enable the | ||
64 | * watchdog timer and allow the system to reset, write I/O port 0443H. | ||
65 | * To disable the timer, write I/O port 0441H for the system to stop the | ||
66 | * watchdog function. The timer has a tolerance of 20% for its | ||
67 | * intervals. | ||
68 | * | ||
69 | * The following describes how the timer should be programmed. | ||
70 | * | ||
71 | * Enabling Watchdog: | ||
72 | * MOV AX,000FH (Choose the values from 0 to F) | ||
73 | * MOV DX,0443H | ||
74 | * OUT DX,AX | ||
75 | * | ||
76 | * Disabling Watchdog: | ||
77 | * MOV AX,000FH (Any value is fine.) | ||
78 | * MOV DX,0441H | ||
79 | * OUT DX,AX | ||
80 | * | ||
81 | * Watchdog timer control table: | ||
82 | * Level Value Time/sec | Level Value Time/sec | ||
83 | * 1 F 0 | 9 7 16 | ||
84 | * 2 E 2 | 10 6 18 | ||
85 | * 3 D 4 | 11 5 20 | ||
86 | * 4 C 6 | 12 4 22 | ||
87 | * 5 B 8 | 13 3 24 | ||
88 | * 6 A 10 | 14 2 26 | ||
89 | * 7 9 12 | 15 1 28 | ||
90 | * 8 8 14 | 16 0 30 | ||
91 | * | ||
92 | */ | ||
93 | |||
94 | static int wd_times[] = { | ||
95 | 30, /* 0x0 */ | ||
96 | 28, /* 0x1 */ | ||
97 | 26, /* 0x2 */ | ||
98 | 24, /* 0x3 */ | ||
99 | 22, /* 0x4 */ | ||
100 | 20, /* 0x5 */ | ||
101 | 18, /* 0x6 */ | ||
102 | 16, /* 0x7 */ | ||
103 | 14, /* 0x8 */ | ||
104 | 12, /* 0x9 */ | ||
105 | 10, /* 0xA */ | ||
106 | 8, /* 0xB */ | ||
107 | 6, /* 0xC */ | ||
108 | 4, /* 0xD */ | ||
109 | 2, /* 0xE */ | ||
110 | 0, /* 0xF */ | ||
111 | }; | ||
112 | |||
113 | #define WDT_STOP 0x441 | ||
114 | #define WDT_START 0x443 | ||
115 | |||
116 | /* Default timeout */ | ||
117 | #define WD_TIMO 0 /* 30 seconds +/- 20%, from table */ | ||
118 | |||
119 | static int wd_margin = WD_TIMO; | ||
120 | |||
121 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
122 | module_param(nowayout, int, 0); | ||
123 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
124 | |||
125 | |||
126 | /* | ||
127 | * Watchdog Operations | ||
128 | */ | ||
129 | |||
130 | static void | ||
131 | ibwdt_ping(void) | ||
132 | { | ||
133 | spin_lock(&ibwdt_lock); | ||
134 | |||
135 | /* Write a watchdog value */ | ||
136 | outb_p(wd_margin, WDT_START); | ||
137 | |||
138 | spin_unlock(&ibwdt_lock); | ||
139 | } | ||
140 | |||
141 | static void | ||
142 | ibwdt_disable(void) | ||
143 | { | ||
144 | spin_lock(&ibwdt_lock); | ||
145 | outb_p(0, WDT_STOP); | ||
146 | spin_unlock(&ibwdt_lock); | ||
147 | } | ||
148 | |||
149 | static int | ||
150 | ibwdt_set_heartbeat(int t) | ||
151 | { | ||
152 | int i; | ||
153 | |||
154 | if ((t < 0) || (t > 30)) | ||
155 | return -EINVAL; | ||
156 | |||
157 | for (i = 0x0F; i > -1; i--) | ||
158 | if (wd_times[i] > t) | ||
159 | break; | ||
160 | wd_margin = i; | ||
161 | return 0; | ||
162 | } | ||
163 | |||
164 | /* | ||
165 | * /dev/watchdog handling | ||
166 | */ | ||
167 | |||
168 | static ssize_t | ||
169 | ibwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
170 | { | ||
171 | if (count) { | ||
172 | if (!nowayout) { | ||
173 | size_t i; | ||
174 | |||
175 | /* In case it was set long ago */ | ||
176 | expect_close = 0; | ||
177 | |||
178 | for (i = 0; i != count; i++) { | ||
179 | char c; | ||
180 | if (get_user(c, buf + i)) | ||
181 | return -EFAULT; | ||
182 | if (c == 'V') | ||
183 | expect_close = 42; | ||
184 | } | ||
185 | } | ||
186 | ibwdt_ping(); | ||
187 | } | ||
188 | return count; | ||
189 | } | ||
190 | |||
191 | static int | ||
192 | ibwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
193 | unsigned long arg) | ||
194 | { | ||
195 | int new_margin; | ||
196 | void __user *argp = (void __user *)arg; | ||
197 | int __user *p = argp; | ||
198 | |||
199 | static struct watchdog_info ident = { | ||
200 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
201 | .firmware_version = 1, | ||
202 | .identity = "IB700 WDT", | ||
203 | }; | ||
204 | |||
205 | switch (cmd) { | ||
206 | case WDIOC_GETSUPPORT: | ||
207 | if (copy_to_user(argp, &ident, sizeof(ident))) | ||
208 | return -EFAULT; | ||
209 | break; | ||
210 | |||
211 | case WDIOC_GETSTATUS: | ||
212 | case WDIOC_GETBOOTSTATUS: | ||
213 | return put_user(0, p); | ||
214 | |||
215 | case WDIOC_KEEPALIVE: | ||
216 | ibwdt_ping(); | ||
217 | break; | ||
218 | |||
219 | case WDIOC_SETTIMEOUT: | ||
220 | if (get_user(new_margin, p)) | ||
221 | return -EFAULT; | ||
222 | if (ibwdt_set_heartbeat(new_margin)) | ||
223 | return -EINVAL; | ||
224 | ibwdt_ping(); | ||
225 | /* Fall */ | ||
226 | |||
227 | case WDIOC_GETTIMEOUT: | ||
228 | return put_user(wd_times[wd_margin], p); | ||
229 | |||
230 | case WDIOC_SETOPTIONS: | ||
231 | { | ||
232 | int options, retval = -EINVAL; | ||
233 | |||
234 | if (get_user(options, p)) | ||
235 | return -EFAULT; | ||
236 | |||
237 | if (options & WDIOS_DISABLECARD) { | ||
238 | ibwdt_disable(); | ||
239 | retval = 0; | ||
240 | } | ||
241 | |||
242 | if (options & WDIOS_ENABLECARD) { | ||
243 | ibwdt_ping(); | ||
244 | retval = 0; | ||
245 | } | ||
246 | |||
247 | return retval; | ||
248 | } | ||
249 | |||
250 | default: | ||
251 | return -ENOTTY; | ||
252 | } | ||
253 | return 0; | ||
254 | } | ||
255 | |||
256 | static int | ||
257 | ibwdt_open(struct inode *inode, struct file *file) | ||
258 | { | ||
259 | if (test_and_set_bit(0, &ibwdt_is_open)) { | ||
260 | return -EBUSY; | ||
261 | } | ||
262 | if (nowayout) | ||
263 | __module_get(THIS_MODULE); | ||
264 | |||
265 | /* Activate */ | ||
266 | ibwdt_ping(); | ||
267 | return nonseekable_open(inode, file); | ||
268 | } | ||
269 | |||
270 | static int | ||
271 | ibwdt_close(struct inode *inode, struct file *file) | ||
272 | { | ||
273 | if (expect_close == 42) { | ||
274 | ibwdt_disable(); | ||
275 | } else { | ||
276 | printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n"); | ||
277 | ibwdt_ping(); | ||
278 | } | ||
279 | clear_bit(0, &ibwdt_is_open); | ||
280 | expect_close = 0; | ||
281 | return 0; | ||
282 | } | ||
283 | |||
284 | /* | ||
285 | * Kernel Interfaces | ||
286 | */ | ||
287 | |||
288 | static const struct file_operations ibwdt_fops = { | ||
289 | .owner = THIS_MODULE, | ||
290 | .llseek = no_llseek, | ||
291 | .write = ibwdt_write, | ||
292 | .ioctl = ibwdt_ioctl, | ||
293 | .open = ibwdt_open, | ||
294 | .release = ibwdt_close, | ||
295 | }; | ||
296 | |||
297 | static struct miscdevice ibwdt_miscdev = { | ||
298 | .minor = WATCHDOG_MINOR, | ||
299 | .name = "watchdog", | ||
300 | .fops = &ibwdt_fops, | ||
301 | }; | ||
302 | |||
303 | /* | ||
304 | * Init & exit routines | ||
305 | */ | ||
306 | |||
307 | static int __devinit ibwdt_probe(struct platform_device *dev) | ||
308 | { | ||
309 | int res; | ||
310 | |||
311 | spin_lock_init(&ibwdt_lock); | ||
312 | |||
313 | #if WDT_START != WDT_STOP | ||
314 | if (!request_region(WDT_STOP, 1, "IB700 WDT")) { | ||
315 | printk (KERN_ERR PFX "STOP method I/O %X is not available.\n", WDT_STOP); | ||
316 | res = -EIO; | ||
317 | goto out_nostopreg; | ||
318 | } | ||
319 | #endif | ||
320 | |||
321 | if (!request_region(WDT_START, 1, "IB700 WDT")) { | ||
322 | printk (KERN_ERR PFX "START method I/O %X is not available.\n", WDT_START); | ||
323 | res = -EIO; | ||
324 | goto out_nostartreg; | ||
325 | } | ||
326 | |||
327 | res = misc_register(&ibwdt_miscdev); | ||
328 | if (res) { | ||
329 | printk (KERN_ERR PFX "failed to register misc device\n"); | ||
330 | goto out_nomisc; | ||
331 | } | ||
332 | return 0; | ||
333 | |||
334 | out_nomisc: | ||
335 | release_region(WDT_START, 1); | ||
336 | out_nostartreg: | ||
337 | #if WDT_START != WDT_STOP | ||
338 | release_region(WDT_STOP, 1); | ||
339 | #endif | ||
340 | out_nostopreg: | ||
341 | return res; | ||
342 | } | ||
343 | |||
344 | static int __devexit ibwdt_remove(struct platform_device *dev) | ||
345 | { | ||
346 | misc_deregister(&ibwdt_miscdev); | ||
347 | release_region(WDT_START,1); | ||
348 | #if WDT_START != WDT_STOP | ||
349 | release_region(WDT_STOP,1); | ||
350 | #endif | ||
351 | return 0; | ||
352 | } | ||
353 | |||
354 | static void ibwdt_shutdown(struct platform_device *dev) | ||
355 | { | ||
356 | /* Turn the WDT off if we have a soft shutdown */ | ||
357 | ibwdt_disable(); | ||
358 | } | ||
359 | |||
360 | static struct platform_driver ibwdt_driver = { | ||
361 | .probe = ibwdt_probe, | ||
362 | .remove = __devexit_p(ibwdt_remove), | ||
363 | .shutdown = ibwdt_shutdown, | ||
364 | .driver = { | ||
365 | .owner = THIS_MODULE, | ||
366 | .name = DRV_NAME, | ||
367 | }, | ||
368 | }; | ||
369 | |||
370 | static int __init ibwdt_init(void) | ||
371 | { | ||
372 | int err; | ||
373 | |||
374 | printk(KERN_INFO PFX "WDT driver for IB700 single board computer initialising.\n"); | ||
375 | |||
376 | err = platform_driver_register(&ibwdt_driver); | ||
377 | if (err) | ||
378 | return err; | ||
379 | |||
380 | ibwdt_platform_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0); | ||
381 | if (IS_ERR(ibwdt_platform_device)) { | ||
382 | err = PTR_ERR(ibwdt_platform_device); | ||
383 | goto unreg_platform_driver; | ||
384 | } | ||
385 | |||
386 | return 0; | ||
387 | |||
388 | unreg_platform_driver: | ||
389 | platform_driver_unregister(&ibwdt_driver); | ||
390 | return err; | ||
391 | } | ||
392 | |||
393 | static void __exit ibwdt_exit(void) | ||
394 | { | ||
395 | platform_device_unregister(ibwdt_platform_device); | ||
396 | platform_driver_unregister(&ibwdt_driver); | ||
397 | printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); | ||
398 | } | ||
399 | |||
400 | module_init(ibwdt_init); | ||
401 | module_exit(ibwdt_exit); | ||
402 | |||
403 | MODULE_AUTHOR("Charles Howes <chowes@vsol.net>"); | ||
404 | MODULE_DESCRIPTION("IB700 SBC watchdog driver"); | ||
405 | MODULE_LICENSE("GPL"); | ||
406 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
407 | |||
408 | /* end of ib700wdt.c */ | ||
diff --git a/drivers/watchdog/ibmasr.c b/drivers/watchdog/ibmasr.c new file mode 100644 index 000000000000..94155f6136c2 --- /dev/null +++ b/drivers/watchdog/ibmasr.c | |||
@@ -0,0 +1,403 @@ | |||
1 | /* | ||
2 | * IBM Automatic Server Restart driver. | ||
3 | * | ||
4 | * Copyright (c) 2005 Andrey Panin <pazke@donpac.ru> | ||
5 | * | ||
6 | * Based on driver written by Pete Reynolds. | ||
7 | * Copyright (c) IBM Corporation, 1998-2004. | ||
8 | * | ||
9 | * This software may be used and distributed according to the terms | ||
10 | * of the GNU Public License, incorporated herein by reference. | ||
11 | */ | ||
12 | |||
13 | #include <linux/fs.h> | ||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/slab.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/pci.h> | ||
18 | #include <linux/timer.h> | ||
19 | #include <linux/miscdevice.h> | ||
20 | #include <linux/watchdog.h> | ||
21 | #include <linux/dmi.h> | ||
22 | |||
23 | #include <asm/io.h> | ||
24 | #include <asm/uaccess.h> | ||
25 | |||
26 | |||
27 | enum { | ||
28 | ASMTYPE_UNKNOWN, | ||
29 | ASMTYPE_TOPAZ, | ||
30 | ASMTYPE_JASPER, | ||
31 | ASMTYPE_PEARL, | ||
32 | ASMTYPE_JUNIPER, | ||
33 | ASMTYPE_SPRUCE, | ||
34 | }; | ||
35 | |||
36 | #define PFX "ibmasr: " | ||
37 | |||
38 | #define TOPAZ_ASR_REG_OFFSET 4 | ||
39 | #define TOPAZ_ASR_TOGGLE 0x40 | ||
40 | #define TOPAZ_ASR_DISABLE 0x80 | ||
41 | |||
42 | /* PEARL ASR S/W REGISTER SUPERIO PORT ADDRESSES */ | ||
43 | #define PEARL_BASE 0xe04 | ||
44 | #define PEARL_WRITE 0xe06 | ||
45 | #define PEARL_READ 0xe07 | ||
46 | |||
47 | #define PEARL_ASR_DISABLE_MASK 0x80 /* bit 7: disable = 1, enable = 0 */ | ||
48 | #define PEARL_ASR_TOGGLE_MASK 0x40 /* bit 6: 0, then 1, then 0 */ | ||
49 | |||
50 | /* JASPER OFFSET FROM SIO BASE ADDR TO ASR S/W REGISTERS. */ | ||
51 | #define JASPER_ASR_REG_OFFSET 0x38 | ||
52 | |||
53 | #define JASPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1, enable = 0 */ | ||
54 | #define JASPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */ | ||
55 | |||
56 | #define JUNIPER_BASE_ADDRESS 0x54b /* Base address of Juniper ASR */ | ||
57 | #define JUNIPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1 enable = 0 */ | ||
58 | #define JUNIPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */ | ||
59 | |||
60 | #define SPRUCE_BASE_ADDRESS 0x118e /* Base address of Spruce ASR */ | ||
61 | #define SPRUCE_ASR_DISABLE_MASK 0x01 /* bit 1: disable = 1 enable = 0 */ | ||
62 | #define SPRUCE_ASR_TOGGLE_MASK 0x02 /* bit 0: 0, then 1, then 0 */ | ||
63 | |||
64 | |||
65 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
66 | |||
67 | static unsigned long asr_is_open; | ||
68 | static char asr_expect_close; | ||
69 | |||
70 | static unsigned int asr_type, asr_base, asr_length; | ||
71 | static unsigned int asr_read_addr, asr_write_addr; | ||
72 | static unsigned char asr_toggle_mask, asr_disable_mask; | ||
73 | |||
74 | static void asr_toggle(void) | ||
75 | { | ||
76 | unsigned char reg = inb(asr_read_addr); | ||
77 | |||
78 | outb(reg & ~asr_toggle_mask, asr_write_addr); | ||
79 | reg = inb(asr_read_addr); | ||
80 | |||
81 | outb(reg | asr_toggle_mask, asr_write_addr); | ||
82 | reg = inb(asr_read_addr); | ||
83 | |||
84 | outb(reg & ~asr_toggle_mask, asr_write_addr); | ||
85 | reg = inb(asr_read_addr); | ||
86 | } | ||
87 | |||
88 | static void asr_enable(void) | ||
89 | { | ||
90 | unsigned char reg; | ||
91 | |||
92 | if (asr_type == ASMTYPE_TOPAZ) { | ||
93 | /* asr_write_addr == asr_read_addr */ | ||
94 | reg = inb(asr_read_addr); | ||
95 | outb(reg & ~(TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE), | ||
96 | asr_read_addr); | ||
97 | } else { | ||
98 | /* | ||
99 | * First make sure the hardware timer is reset by toggling | ||
100 | * ASR hardware timer line. | ||
101 | */ | ||
102 | asr_toggle(); | ||
103 | |||
104 | reg = inb(asr_read_addr); | ||
105 | outb(reg & ~asr_disable_mask, asr_write_addr); | ||
106 | } | ||
107 | reg = inb(asr_read_addr); | ||
108 | } | ||
109 | |||
110 | static void asr_disable(void) | ||
111 | { | ||
112 | unsigned char reg = inb(asr_read_addr); | ||
113 | |||
114 | if (asr_type == ASMTYPE_TOPAZ) | ||
115 | /* asr_write_addr == asr_read_addr */ | ||
116 | outb(reg | TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE, | ||
117 | asr_read_addr); | ||
118 | else { | ||
119 | outb(reg | asr_toggle_mask, asr_write_addr); | ||
120 | reg = inb(asr_read_addr); | ||
121 | |||
122 | outb(reg | asr_disable_mask, asr_write_addr); | ||
123 | } | ||
124 | reg = inb(asr_read_addr); | ||
125 | } | ||
126 | |||
127 | static int __init asr_get_base_address(void) | ||
128 | { | ||
129 | unsigned char low, high; | ||
130 | const char *type = ""; | ||
131 | |||
132 | asr_length = 1; | ||
133 | |||
134 | switch (asr_type) { | ||
135 | case ASMTYPE_TOPAZ: | ||
136 | /* SELECT SuperIO CHIP FOR QUERYING (WRITE 0x07 TO BOTH 0x2E and 0x2F) */ | ||
137 | outb(0x07, 0x2e); | ||
138 | outb(0x07, 0x2f); | ||
139 | |||
140 | /* SELECT AND READ THE HIGH-NIBBLE OF THE GPIO BASE ADDRESS */ | ||
141 | outb(0x60, 0x2e); | ||
142 | high = inb(0x2f); | ||
143 | |||
144 | /* SELECT AND READ THE LOW-NIBBLE OF THE GPIO BASE ADDRESS */ | ||
145 | outb(0x61, 0x2e); | ||
146 | low = inb(0x2f); | ||
147 | |||
148 | asr_base = (high << 16) | low; | ||
149 | asr_read_addr = asr_write_addr = | ||
150 | asr_base + TOPAZ_ASR_REG_OFFSET; | ||
151 | asr_length = 5; | ||
152 | |||
153 | break; | ||
154 | |||
155 | case ASMTYPE_JASPER: | ||
156 | type = "Jaspers "; | ||
157 | |||
158 | /* FIXME: need to use pci_config_lock here, but it's not exported */ | ||
159 | |||
160 | /* spin_lock_irqsave(&pci_config_lock, flags);*/ | ||
161 | |||
162 | /* Select the SuperIO chip in the PCI I/O port register */ | ||
163 | outl(0x8000f858, 0xcf8); | ||
164 | |||
165 | /* | ||
166 | * Read the base address for the SuperIO chip. | ||
167 | * Only the lower 16 bits are valid, but the address is word | ||
168 | * aligned so the last bit must be masked off. | ||
169 | */ | ||
170 | asr_base = inl(0xcfc) & 0xfffe; | ||
171 | |||
172 | /* spin_unlock_irqrestore(&pci_config_lock, flags);*/ | ||
173 | |||
174 | asr_read_addr = asr_write_addr = | ||
175 | asr_base + JASPER_ASR_REG_OFFSET; | ||
176 | asr_toggle_mask = JASPER_ASR_TOGGLE_MASK; | ||
177 | asr_disable_mask = JASPER_ASR_DISABLE_MASK; | ||
178 | asr_length = JASPER_ASR_REG_OFFSET + 1; | ||
179 | |||
180 | break; | ||
181 | |||
182 | case ASMTYPE_PEARL: | ||
183 | type = "Pearls "; | ||
184 | asr_base = PEARL_BASE; | ||
185 | asr_read_addr = PEARL_READ; | ||
186 | asr_write_addr = PEARL_WRITE; | ||
187 | asr_toggle_mask = PEARL_ASR_TOGGLE_MASK; | ||
188 | asr_disable_mask = PEARL_ASR_DISABLE_MASK; | ||
189 | asr_length = 4; | ||
190 | break; | ||
191 | |||
192 | case ASMTYPE_JUNIPER: | ||
193 | type = "Junipers "; | ||
194 | asr_base = JUNIPER_BASE_ADDRESS; | ||
195 | asr_read_addr = asr_write_addr = asr_base; | ||
196 | asr_toggle_mask = JUNIPER_ASR_TOGGLE_MASK; | ||
197 | asr_disable_mask = JUNIPER_ASR_DISABLE_MASK; | ||
198 | break; | ||
199 | |||
200 | case ASMTYPE_SPRUCE: | ||
201 | type = "Spruce's "; | ||
202 | asr_base = SPRUCE_BASE_ADDRESS; | ||
203 | asr_read_addr = asr_write_addr = asr_base; | ||
204 | asr_toggle_mask = SPRUCE_ASR_TOGGLE_MASK; | ||
205 | asr_disable_mask = SPRUCE_ASR_DISABLE_MASK; | ||
206 | break; | ||
207 | } | ||
208 | |||
209 | if (!request_region(asr_base, asr_length, "ibmasr")) { | ||
210 | printk(KERN_ERR PFX "address %#x already in use\n", | ||
211 | asr_base); | ||
212 | return -EBUSY; | ||
213 | } | ||
214 | |||
215 | printk(KERN_INFO PFX "found %sASR @ addr %#x\n", type, asr_base); | ||
216 | |||
217 | return 0; | ||
218 | } | ||
219 | |||
220 | |||
221 | static ssize_t asr_write(struct file *file, const char __user *buf, | ||
222 | size_t count, loff_t *ppos) | ||
223 | { | ||
224 | if (count) { | ||
225 | if (!nowayout) { | ||
226 | size_t i; | ||
227 | |||
228 | /* In case it was set long ago */ | ||
229 | asr_expect_close = 0; | ||
230 | |||
231 | for (i = 0; i != count; i++) { | ||
232 | char c; | ||
233 | if (get_user(c, buf + i)) | ||
234 | return -EFAULT; | ||
235 | if (c == 'V') | ||
236 | asr_expect_close = 42; | ||
237 | } | ||
238 | } | ||
239 | asr_toggle(); | ||
240 | } | ||
241 | return count; | ||
242 | } | ||
243 | |||
244 | static int asr_ioctl(struct inode *inode, struct file *file, | ||
245 | unsigned int cmd, unsigned long arg) | ||
246 | { | ||
247 | static const struct watchdog_info ident = { | ||
248 | .options = WDIOF_KEEPALIVEPING | | ||
249 | WDIOF_MAGICCLOSE, | ||
250 | .identity = "IBM ASR" | ||
251 | }; | ||
252 | void __user *argp = (void __user *)arg; | ||
253 | int __user *p = argp; | ||
254 | int heartbeat; | ||
255 | |||
256 | switch (cmd) { | ||
257 | case WDIOC_GETSUPPORT: | ||
258 | return copy_to_user(argp, &ident, sizeof(ident)) ? | ||
259 | -EFAULT : 0; | ||
260 | |||
261 | case WDIOC_GETSTATUS: | ||
262 | case WDIOC_GETBOOTSTATUS: | ||
263 | return put_user(0, p); | ||
264 | |||
265 | case WDIOC_KEEPALIVE: | ||
266 | asr_toggle(); | ||
267 | return 0; | ||
268 | |||
269 | /* | ||
270 | * The hardware has a fixed timeout value, so no WDIOC_SETTIMEOUT | ||
271 | * and WDIOC_GETTIMEOUT always returns 256. | ||
272 | */ | ||
273 | case WDIOC_GETTIMEOUT: | ||
274 | heartbeat = 256; | ||
275 | return put_user(heartbeat, p); | ||
276 | |||
277 | case WDIOC_SETOPTIONS: { | ||
278 | int new_options, retval = -EINVAL; | ||
279 | |||
280 | if (get_user(new_options, p)) | ||
281 | return -EFAULT; | ||
282 | |||
283 | if (new_options & WDIOS_DISABLECARD) { | ||
284 | asr_disable(); | ||
285 | retval = 0; | ||
286 | } | ||
287 | |||
288 | if (new_options & WDIOS_ENABLECARD) { | ||
289 | asr_enable(); | ||
290 | asr_toggle(); | ||
291 | retval = 0; | ||
292 | } | ||
293 | |||
294 | return retval; | ||
295 | } | ||
296 | } | ||
297 | |||
298 | return -ENOTTY; | ||
299 | } | ||
300 | |||
301 | static int asr_open(struct inode *inode, struct file *file) | ||
302 | { | ||
303 | if(test_and_set_bit(0, &asr_is_open)) | ||
304 | return -EBUSY; | ||
305 | |||
306 | asr_toggle(); | ||
307 | asr_enable(); | ||
308 | |||
309 | return nonseekable_open(inode, file); | ||
310 | } | ||
311 | |||
312 | static int asr_release(struct inode *inode, struct file *file) | ||
313 | { | ||
314 | if (asr_expect_close == 42) | ||
315 | asr_disable(); | ||
316 | else { | ||
317 | printk(KERN_CRIT PFX "unexpected close, not stopping watchdog!\n"); | ||
318 | asr_toggle(); | ||
319 | } | ||
320 | clear_bit(0, &asr_is_open); | ||
321 | asr_expect_close = 0; | ||
322 | return 0; | ||
323 | } | ||
324 | |||
325 | static const struct file_operations asr_fops = { | ||
326 | .owner = THIS_MODULE, | ||
327 | .llseek = no_llseek, | ||
328 | .write = asr_write, | ||
329 | .ioctl = asr_ioctl, | ||
330 | .open = asr_open, | ||
331 | .release = asr_release, | ||
332 | }; | ||
333 | |||
334 | static struct miscdevice asr_miscdev = { | ||
335 | .minor = WATCHDOG_MINOR, | ||
336 | .name = "watchdog", | ||
337 | .fops = &asr_fops, | ||
338 | }; | ||
339 | |||
340 | |||
341 | struct ibmasr_id { | ||
342 | const char *desc; | ||
343 | int type; | ||
344 | }; | ||
345 | |||
346 | static struct ibmasr_id __initdata ibmasr_id_table[] = { | ||
347 | { "IBM Automatic Server Restart - eserver xSeries 220", ASMTYPE_TOPAZ }, | ||
348 | { "IBM Automatic Server Restart - Machine Type 8673", ASMTYPE_PEARL }, | ||
349 | { "IBM Automatic Server Restart - Machine Type 8480", ASMTYPE_JASPER }, | ||
350 | { "IBM Automatic Server Restart - Machine Type 8482", ASMTYPE_JUNIPER }, | ||
351 | { "IBM Automatic Server Restart - Machine Type 8648", ASMTYPE_SPRUCE }, | ||
352 | { NULL } | ||
353 | }; | ||
354 | |||
355 | static int __init ibmasr_init(void) | ||
356 | { | ||
357 | struct ibmasr_id *id; | ||
358 | int rc; | ||
359 | |||
360 | for (id = ibmasr_id_table; id->desc; id++) { | ||
361 | if (dmi_find_device(DMI_DEV_TYPE_OTHER, id->desc, NULL)) { | ||
362 | asr_type = id->type; | ||
363 | break; | ||
364 | } | ||
365 | } | ||
366 | |||
367 | if (!asr_type) | ||
368 | return -ENODEV; | ||
369 | |||
370 | rc = asr_get_base_address(); | ||
371 | if (rc) | ||
372 | return rc; | ||
373 | |||
374 | rc = misc_register(&asr_miscdev); | ||
375 | if (rc < 0) { | ||
376 | release_region(asr_base, asr_length); | ||
377 | printk(KERN_ERR PFX "failed to register misc device\n"); | ||
378 | return rc; | ||
379 | } | ||
380 | |||
381 | return 0; | ||
382 | } | ||
383 | |||
384 | static void __exit ibmasr_exit(void) | ||
385 | { | ||
386 | if (!nowayout) | ||
387 | asr_disable(); | ||
388 | |||
389 | misc_deregister(&asr_miscdev); | ||
390 | |||
391 | release_region(asr_base, asr_length); | ||
392 | } | ||
393 | |||
394 | module_init(ibmasr_init); | ||
395 | module_exit(ibmasr_exit); | ||
396 | |||
397 | module_param(nowayout, int, 0); | ||
398 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
399 | |||
400 | MODULE_DESCRIPTION("IBM Automatic Server Restart driver"); | ||
401 | MODULE_AUTHOR("Andrey Panin"); | ||
402 | MODULE_LICENSE("GPL"); | ||
403 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/indydog.c b/drivers/watchdog/indydog.c new file mode 100644 index 000000000000..788245bdaa7f --- /dev/null +++ b/drivers/watchdog/indydog.c | |||
@@ -0,0 +1,215 @@ | |||
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/types.h> | ||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/fs.h> | ||
19 | #include <linux/mm.h> | ||
20 | #include <linux/miscdevice.h> | ||
21 | #include <linux/watchdog.h> | ||
22 | #include <linux/notifier.h> | ||
23 | #include <linux/reboot.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <asm/uaccess.h> | ||
26 | #include <asm/sgi/mc.h> | ||
27 | |||
28 | #define PFX "indydog: " | ||
29 | static int indydog_alive; | ||
30 | |||
31 | #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ | ||
32 | |||
33 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
34 | module_param(nowayout, int, 0); | ||
35 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
36 | |||
37 | static void indydog_start(void) | ||
38 | { | ||
39 | u32 mc_ctrl0 = sgimc->cpuctrl0; | ||
40 | |||
41 | mc_ctrl0 = sgimc->cpuctrl0 | SGIMC_CCTRL0_WDOG; | ||
42 | sgimc->cpuctrl0 = mc_ctrl0; | ||
43 | } | ||
44 | |||
45 | static void indydog_stop(void) | ||
46 | { | ||
47 | u32 mc_ctrl0 = sgimc->cpuctrl0; | ||
48 | |||
49 | mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG; | ||
50 | sgimc->cpuctrl0 = mc_ctrl0; | ||
51 | |||
52 | printk(KERN_INFO PFX "Stopped watchdog timer.\n"); | ||
53 | } | ||
54 | |||
55 | static void indydog_ping(void) | ||
56 | { | ||
57 | sgimc->watchdogt = 0; | ||
58 | } | ||
59 | |||
60 | /* | ||
61 | * Allow only one person to hold it open | ||
62 | */ | ||
63 | static int indydog_open(struct inode *inode, struct file *file) | ||
64 | { | ||
65 | if (indydog_alive) | ||
66 | return -EBUSY; | ||
67 | |||
68 | if (nowayout) | ||
69 | __module_get(THIS_MODULE); | ||
70 | |||
71 | /* Activate timer */ | ||
72 | indydog_start(); | ||
73 | indydog_ping(); | ||
74 | |||
75 | indydog_alive = 1; | ||
76 | printk(KERN_INFO "Started watchdog timer.\n"); | ||
77 | |||
78 | return nonseekable_open(inode, file); | ||
79 | } | ||
80 | |||
81 | static int indydog_release(struct inode *inode, struct file *file) | ||
82 | { | ||
83 | /* Shut off the timer. | ||
84 | * Lock it in if it's a module and we defined ...NOWAYOUT */ | ||
85 | if (!nowayout) | ||
86 | indydog_stop(); /* Turn the WDT off */ | ||
87 | |||
88 | indydog_alive = 0; | ||
89 | |||
90 | return 0; | ||
91 | } | ||
92 | |||
93 | static ssize_t indydog_write(struct file *file, const char *data, size_t len, loff_t *ppos) | ||
94 | { | ||
95 | /* Refresh the timer. */ | ||
96 | if (len) { | ||
97 | indydog_ping(); | ||
98 | } | ||
99 | return len; | ||
100 | } | ||
101 | |||
102 | static int indydog_ioctl(struct inode *inode, struct file *file, | ||
103 | unsigned int cmd, unsigned long arg) | ||
104 | { | ||
105 | int options, retval = -EINVAL; | ||
106 | static struct watchdog_info ident = { | ||
107 | .options = WDIOF_KEEPALIVEPING | | ||
108 | WDIOF_MAGICCLOSE, | ||
109 | .firmware_version = 0, | ||
110 | .identity = "Hardware Watchdog for SGI IP22", | ||
111 | }; | ||
112 | |||
113 | switch (cmd) { | ||
114 | default: | ||
115 | return -ENOTTY; | ||
116 | case WDIOC_GETSUPPORT: | ||
117 | if (copy_to_user((struct watchdog_info *)arg, | ||
118 | &ident, sizeof(ident))) | ||
119 | return -EFAULT; | ||
120 | return 0; | ||
121 | case WDIOC_GETSTATUS: | ||
122 | case WDIOC_GETBOOTSTATUS: | ||
123 | return put_user(0,(int *)arg); | ||
124 | case WDIOC_KEEPALIVE: | ||
125 | indydog_ping(); | ||
126 | return 0; | ||
127 | case WDIOC_GETTIMEOUT: | ||
128 | return put_user(WATCHDOG_TIMEOUT,(int *)arg); | ||
129 | case WDIOC_SETOPTIONS: | ||
130 | { | ||
131 | if (get_user(options, (int *)arg)) | ||
132 | return -EFAULT; | ||
133 | |||
134 | if (options & WDIOS_DISABLECARD) { | ||
135 | indydog_stop(); | ||
136 | retval = 0; | ||
137 | } | ||
138 | |||
139 | if (options & WDIOS_ENABLECARD) { | ||
140 | indydog_start(); | ||
141 | retval = 0; | ||
142 | } | ||
143 | |||
144 | return retval; | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | |||
149 | static int indydog_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
150 | { | ||
151 | if (code == SYS_DOWN || code == SYS_HALT) | ||
152 | indydog_stop(); /* Turn the WDT off */ | ||
153 | |||
154 | return NOTIFY_DONE; | ||
155 | } | ||
156 | |||
157 | static const struct file_operations indydog_fops = { | ||
158 | .owner = THIS_MODULE, | ||
159 | .llseek = no_llseek, | ||
160 | .write = indydog_write, | ||
161 | .ioctl = indydog_ioctl, | ||
162 | .open = indydog_open, | ||
163 | .release = indydog_release, | ||
164 | }; | ||
165 | |||
166 | static struct miscdevice indydog_miscdev = { | ||
167 | .minor = WATCHDOG_MINOR, | ||
168 | .name = "watchdog", | ||
169 | .fops = &indydog_fops, | ||
170 | }; | ||
171 | |||
172 | static struct notifier_block indydog_notifier = { | ||
173 | .notifier_call = indydog_notify_sys, | ||
174 | }; | ||
175 | |||
176 | static char banner[] __initdata = | ||
177 | KERN_INFO PFX "Hardware Watchdog Timer for SGI IP22: 0.3\n"; | ||
178 | |||
179 | static int __init watchdog_init(void) | ||
180 | { | ||
181 | int ret; | ||
182 | |||
183 | ret = register_reboot_notifier(&indydog_notifier); | ||
184 | if (ret) { | ||
185 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
186 | ret); | ||
187 | return ret; | ||
188 | } | ||
189 | |||
190 | ret = misc_register(&indydog_miscdev); | ||
191 | if (ret) { | ||
192 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
193 | WATCHDOG_MINOR, ret); | ||
194 | unregister_reboot_notifier(&indydog_notifier); | ||
195 | return ret; | ||
196 | } | ||
197 | |||
198 | printk(banner); | ||
199 | |||
200 | return 0; | ||
201 | } | ||
202 | |||
203 | static void __exit watchdog_exit(void) | ||
204 | { | ||
205 | misc_deregister(&indydog_miscdev); | ||
206 | unregister_reboot_notifier(&indydog_notifier); | ||
207 | } | ||
208 | |||
209 | module_init(watchdog_init); | ||
210 | module_exit(watchdog_exit); | ||
211 | |||
212 | MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>"); | ||
213 | MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22"); | ||
214 | MODULE_LICENSE("GPL"); | ||
215 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/iop_wdt.c b/drivers/watchdog/iop_wdt.c new file mode 100644 index 000000000000..bbbd91af754d --- /dev/null +++ b/drivers/watchdog/iop_wdt.c | |||
@@ -0,0 +1,262 @@ | |||
1 | /* | ||
2 | * drivers/char/watchdog/iop_wdt.c | ||
3 | * | ||
4 | * WDT driver for Intel I/O Processors | ||
5 | * Copyright (C) 2005, Intel Corporation. | ||
6 | * | ||
7 | * Based on ixp4xx driver, Copyright 2004 (c) MontaVista, Software, Inc. | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify it | ||
10 | * under the terms and conditions of the GNU General Public License, | ||
11 | * version 2, as published by the Free Software Foundation. | ||
12 | * | ||
13 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along with | ||
19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple | ||
20 | * Place - Suite 330, Boston, MA 02111-1307 USA. | ||
21 | * | ||
22 | * Curt E Bruns <curt.e.bruns@intel.com> | ||
23 | * Peter Milne <peter.milne@d-tacq.com> | ||
24 | * Dan Williams <dan.j.williams@intel.com> | ||
25 | */ | ||
26 | |||
27 | #include <linux/module.h> | ||
28 | #include <linux/kernel.h> | ||
29 | #include <linux/fs.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/device.h> | ||
32 | #include <linux/miscdevice.h> | ||
33 | #include <linux/watchdog.h> | ||
34 | #include <linux/uaccess.h> | ||
35 | #include <asm/hardware.h> | ||
36 | |||
37 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
38 | static unsigned long wdt_status; | ||
39 | static unsigned long boot_status; | ||
40 | |||
41 | #define WDT_IN_USE 0 | ||
42 | #define WDT_OK_TO_CLOSE 1 | ||
43 | #define WDT_ENABLED 2 | ||
44 | |||
45 | static unsigned long iop_watchdog_timeout(void) | ||
46 | { | ||
47 | return (0xffffffffUL / get_iop_tick_rate()); | ||
48 | } | ||
49 | |||
50 | /** | ||
51 | * wdt_supports_disable - determine if we are accessing a iop13xx watchdog | ||
52 | * or iop3xx by whether it has a disable command | ||
53 | */ | ||
54 | static int wdt_supports_disable(void) | ||
55 | { | ||
56 | int can_disable; | ||
57 | |||
58 | if (IOP_WDTCR_EN_ARM != IOP_WDTCR_DIS_ARM) | ||
59 | can_disable = 1; | ||
60 | else | ||
61 | can_disable = 0; | ||
62 | |||
63 | return can_disable; | ||
64 | } | ||
65 | |||
66 | static void wdt_enable(void) | ||
67 | { | ||
68 | /* Arm and enable the Timer to starting counting down from 0xFFFF.FFFF | ||
69 | * Takes approx. 10.7s to timeout | ||
70 | */ | ||
71 | write_wdtcr(IOP_WDTCR_EN_ARM); | ||
72 | write_wdtcr(IOP_WDTCR_EN); | ||
73 | } | ||
74 | |||
75 | /* returns 0 if the timer was successfully disabled */ | ||
76 | static int wdt_disable(void) | ||
77 | { | ||
78 | /* Stop Counting */ | ||
79 | if (wdt_supports_disable()) { | ||
80 | write_wdtcr(IOP_WDTCR_DIS_ARM); | ||
81 | write_wdtcr(IOP_WDTCR_DIS); | ||
82 | clear_bit(WDT_ENABLED, &wdt_status); | ||
83 | printk(KERN_INFO "WATCHDOG: Disabled\n"); | ||
84 | return 0; | ||
85 | } else | ||
86 | return 1; | ||
87 | } | ||
88 | |||
89 | static int iop_wdt_open(struct inode *inode, struct file *file) | ||
90 | { | ||
91 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | ||
92 | return -EBUSY; | ||
93 | |||
94 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
95 | |||
96 | wdt_enable(); | ||
97 | |||
98 | set_bit(WDT_ENABLED, &wdt_status); | ||
99 | |||
100 | return nonseekable_open(inode, file); | ||
101 | } | ||
102 | |||
103 | static ssize_t | ||
104 | iop_wdt_write(struct file *file, const char *data, size_t len, | ||
105 | loff_t *ppos) | ||
106 | { | ||
107 | if (len) { | ||
108 | if (!nowayout) { | ||
109 | size_t i; | ||
110 | |||
111 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
112 | |||
113 | for (i = 0; i != len; i++) { | ||
114 | char c; | ||
115 | |||
116 | if (get_user(c, data + i)) | ||
117 | return -EFAULT; | ||
118 | if (c == 'V') | ||
119 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
120 | } | ||
121 | } | ||
122 | wdt_enable(); | ||
123 | } | ||
124 | |||
125 | return len; | ||
126 | } | ||
127 | |||
128 | static struct watchdog_info ident = { | ||
129 | .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, | ||
130 | .identity = "iop watchdog", | ||
131 | }; | ||
132 | |||
133 | static int | ||
134 | iop_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
135 | unsigned long arg) | ||
136 | { | ||
137 | int options; | ||
138 | int ret = -ENOTTY; | ||
139 | |||
140 | switch (cmd) { | ||
141 | case WDIOC_GETSUPPORT: | ||
142 | if (copy_to_user | ||
143 | ((struct watchdog_info *)arg, &ident, sizeof ident)) | ||
144 | ret = -EFAULT; | ||
145 | else | ||
146 | ret = 0; | ||
147 | break; | ||
148 | |||
149 | case WDIOC_GETSTATUS: | ||
150 | ret = put_user(0, (int *)arg); | ||
151 | break; | ||
152 | |||
153 | case WDIOC_GETBOOTSTATUS: | ||
154 | ret = put_user(boot_status, (int *)arg); | ||
155 | break; | ||
156 | |||
157 | case WDIOC_GETTIMEOUT: | ||
158 | ret = put_user(iop_watchdog_timeout(), (int *)arg); | ||
159 | break; | ||
160 | |||
161 | case WDIOC_KEEPALIVE: | ||
162 | wdt_enable(); | ||
163 | ret = 0; | ||
164 | break; | ||
165 | |||
166 | case WDIOC_SETOPTIONS: | ||
167 | if (get_user(options, (int *)arg)) | ||
168 | return -EFAULT; | ||
169 | |||
170 | if (options & WDIOS_DISABLECARD) { | ||
171 | if (!nowayout) { | ||
172 | if (wdt_disable() == 0) { | ||
173 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
174 | ret = 0; | ||
175 | } else | ||
176 | ret = -ENXIO; | ||
177 | } else | ||
178 | ret = 0; | ||
179 | } | ||
180 | |||
181 | if (options & WDIOS_ENABLECARD) { | ||
182 | wdt_enable(); | ||
183 | ret = 0; | ||
184 | } | ||
185 | break; | ||
186 | } | ||
187 | |||
188 | return ret; | ||
189 | } | ||
190 | |||
191 | static int iop_wdt_release(struct inode *inode, struct file *file) | ||
192 | { | ||
193 | int state = 1; | ||
194 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) | ||
195 | if (test_bit(WDT_ENABLED, &wdt_status)) | ||
196 | state = wdt_disable(); | ||
197 | |||
198 | /* if the timer is not disbaled reload and notify that we are still | ||
199 | * going down | ||
200 | */ | ||
201 | if (state != 0) { | ||
202 | wdt_enable(); | ||
203 | printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " | ||
204 | "reset in %lu seconds\n", iop_watchdog_timeout()); | ||
205 | } | ||
206 | |||
207 | clear_bit(WDT_IN_USE, &wdt_status); | ||
208 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
209 | |||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | static const struct file_operations iop_wdt_fops = { | ||
214 | .owner = THIS_MODULE, | ||
215 | .llseek = no_llseek, | ||
216 | .write = iop_wdt_write, | ||
217 | .ioctl = iop_wdt_ioctl, | ||
218 | .open = iop_wdt_open, | ||
219 | .release = iop_wdt_release, | ||
220 | }; | ||
221 | |||
222 | static struct miscdevice iop_wdt_miscdev = { | ||
223 | .minor = WATCHDOG_MINOR, | ||
224 | .name = "watchdog", | ||
225 | .fops = &iop_wdt_fops, | ||
226 | }; | ||
227 | |||
228 | static int __init iop_wdt_init(void) | ||
229 | { | ||
230 | int ret; | ||
231 | |||
232 | ret = misc_register(&iop_wdt_miscdev); | ||
233 | if (ret == 0) | ||
234 | printk("iop watchdog timer: timeout %lu sec\n", | ||
235 | iop_watchdog_timeout()); | ||
236 | |||
237 | /* check if the reset was caused by the watchdog timer */ | ||
238 | boot_status = (read_rcsr() & IOP_RCSR_WDT) ? WDIOF_CARDRESET : 0; | ||
239 | |||
240 | /* Configure Watchdog Timeout to cause an Internal Bus (IB) Reset | ||
241 | * NOTE: An IB Reset will Reset both cores in the IOP342 | ||
242 | */ | ||
243 | write_wdtsr(IOP13XX_WDTCR_IB_RESET); | ||
244 | |||
245 | return ret; | ||
246 | } | ||
247 | |||
248 | static void __exit iop_wdt_exit(void) | ||
249 | { | ||
250 | misc_deregister(&iop_wdt_miscdev); | ||
251 | } | ||
252 | |||
253 | module_init(iop_wdt_init); | ||
254 | module_exit(iop_wdt_exit); | ||
255 | |||
256 | module_param(nowayout, int, 0); | ||
257 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); | ||
258 | |||
259 | MODULE_AUTHOR("Curt E Bruns <curt.e.bruns@intel.com>"); | ||
260 | MODULE_DESCRIPTION("iop watchdog timer driver"); | ||
261 | MODULE_LICENSE("GPL"); | ||
262 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/ixp2000_wdt.c b/drivers/watchdog/ixp2000_wdt.c new file mode 100644 index 000000000000..dc7548dcaf35 --- /dev/null +++ b/drivers/watchdog/ixp2000_wdt.c | |||
@@ -0,0 +1,219 @@ | |||
1 | /* | ||
2 | * drivers/char/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/module.h> | ||
20 | #include <linux/moduleparam.h> | ||
21 | #include <linux/types.h> | ||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/fs.h> | ||
24 | #include <linux/miscdevice.h> | ||
25 | #include <linux/watchdog.h> | ||
26 | #include <linux/init.h> | ||
27 | #include <linux/bitops.h> | ||
28 | |||
29 | #include <asm/hardware.h> | ||
30 | #include <asm/uaccess.h> | ||
31 | |||
32 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
33 | static unsigned int heartbeat = 60; /* (secs) Default is 1 minute */ | ||
34 | static unsigned long wdt_status; | ||
35 | |||
36 | #define WDT_IN_USE 0 | ||
37 | #define WDT_OK_TO_CLOSE 1 | ||
38 | |||
39 | static unsigned long wdt_tick_rate; | ||
40 | |||
41 | static void | ||
42 | wdt_enable(void) | ||
43 | { | ||
44 | ixp2000_reg_write(IXP2000_RESET0, *(IXP2000_RESET0) | WDT_RESET_ENABLE); | ||
45 | ixp2000_reg_write(IXP2000_TWDE, WDT_ENABLE); | ||
46 | ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); | ||
47 | ixp2000_reg_write(IXP2000_T4_CTL, TIMER_DIVIDER_256 | TIMER_ENABLE); | ||
48 | } | ||
49 | |||
50 | static void | ||
51 | wdt_disable(void) | ||
52 | { | ||
53 | ixp2000_reg_write(IXP2000_T4_CTL, 0); | ||
54 | } | ||
55 | |||
56 | static void | ||
57 | wdt_keepalive(void) | ||
58 | { | ||
59 | ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); | ||
60 | } | ||
61 | |||
62 | static int | ||
63 | ixp2000_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 | ixp2000_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_keepalive(); | ||
94 | } | ||
95 | |||
96 | return len; | ||
97 | } | ||
98 | |||
99 | |||
100 | static struct watchdog_info ident = { | ||
101 | .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | | ||
102 | WDIOF_KEEPALIVEPING, | ||
103 | .identity = "IXP2000 Watchdog", | ||
104 | }; | ||
105 | |||
106 | static int | ||
107 | ixp2000_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
108 | unsigned long arg) | ||
109 | { | ||
110 | int ret = -ENOTTY; | ||
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(0, (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_keepalive(); | ||
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 | |||
151 | return ret; | ||
152 | } | ||
153 | |||
154 | static int | ||
155 | ixp2000_wdt_release(struct inode *inode, struct file *file) | ||
156 | { | ||
157 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { | ||
158 | wdt_disable(); | ||
159 | } else { | ||
160 | printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " | ||
161 | "timer will not stop\n"); | ||
162 | } | ||
163 | |||
164 | clear_bit(WDT_IN_USE, &wdt_status); | ||
165 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
166 | |||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | |||
171 | static const struct file_operations ixp2000_wdt_fops = | ||
172 | { | ||
173 | .owner = THIS_MODULE, | ||
174 | .llseek = no_llseek, | ||
175 | .write = ixp2000_wdt_write, | ||
176 | .ioctl = ixp2000_wdt_ioctl, | ||
177 | .open = ixp2000_wdt_open, | ||
178 | .release = ixp2000_wdt_release, | ||
179 | }; | ||
180 | |||
181 | static struct miscdevice ixp2000_wdt_miscdev = | ||
182 | { | ||
183 | .minor = WATCHDOG_MINOR, | ||
184 | .name = "watchdog", | ||
185 | .fops = &ixp2000_wdt_fops, | ||
186 | }; | ||
187 | |||
188 | static int __init ixp2000_wdt_init(void) | ||
189 | { | ||
190 | if ((*IXP2000_PRODUCT_ID & 0x001ffef0) == 0x00000000) { | ||
191 | printk(KERN_INFO "Unable to use IXP2000 watchdog due to IXP2800 erratum #25.\n"); | ||
192 | return -EIO; | ||
193 | } | ||
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/watchdog/ixp4xx_wdt.c b/drivers/watchdog/ixp4xx_wdt.c new file mode 100644 index 000000000000..5864bb865cfe --- /dev/null +++ b/drivers/watchdog/ixp4xx_wdt.c | |||
@@ -0,0 +1,225 @@ | |||
1 | /* | ||
2 | * drivers/char/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/module.h> | ||
17 | #include <linux/moduleparam.h> | ||
18 | #include <linux/types.h> | ||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/fs.h> | ||
21 | #include <linux/miscdevice.h> | ||
22 | #include <linux/watchdog.h> | ||
23 | #include <linux/init.h> | ||
24 | #include <linux/bitops.h> | ||
25 | |||
26 | #include <asm/hardware.h> | ||
27 | #include <asm/uaccess.h> | ||
28 | |||
29 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
30 | static int heartbeat = 60; /* (secs) Default is 1 minute */ | ||
31 | static unsigned long wdt_status; | ||
32 | static unsigned long boot_status; | ||
33 | |||
34 | #define WDT_TICK_RATE (IXP4XX_PERIPHERAL_BUS_CLOCK * 1000000UL) | ||
35 | |||
36 | #define WDT_IN_USE 0 | ||
37 | #define WDT_OK_TO_CLOSE 1 | ||
38 | |||
39 | static void | ||
40 | wdt_enable(void) | ||
41 | { | ||
42 | *IXP4XX_OSWK = IXP4XX_WDT_KEY; | ||
43 | *IXP4XX_OSWE = 0; | ||
44 | *IXP4XX_OSWT = WDT_TICK_RATE * heartbeat; | ||
45 | *IXP4XX_OSWE = IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE; | ||
46 | *IXP4XX_OSWK = 0; | ||
47 | } | ||
48 | |||
49 | static void | ||
50 | wdt_disable(void) | ||
51 | { | ||
52 | *IXP4XX_OSWK = IXP4XX_WDT_KEY; | ||
53 | *IXP4XX_OSWE = 0; | ||
54 | *IXP4XX_OSWK = 0; | ||
55 | } | ||
56 | |||
57 | static int | ||
58 | ixp4xx_wdt_open(struct inode *inode, struct file *file) | ||
59 | { | ||
60 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | ||
61 | return -EBUSY; | ||
62 | |||
63 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
64 | |||
65 | wdt_enable(); | ||
66 | |||
67 | return nonseekable_open(inode, file); | ||
68 | } | ||
69 | |||
70 | static ssize_t | ||
71 | ixp4xx_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) | ||
72 | { | ||
73 | if (len) { | ||
74 | if (!nowayout) { | ||
75 | size_t i; | ||
76 | |||
77 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
78 | |||
79 | for (i = 0; i != len; i++) { | ||
80 | char c; | ||
81 | |||
82 | if (get_user(c, data + i)) | ||
83 | return -EFAULT; | ||
84 | if (c == 'V') | ||
85 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
86 | } | ||
87 | } | ||
88 | wdt_enable(); | ||
89 | } | ||
90 | |||
91 | return len; | ||
92 | } | ||
93 | |||
94 | static struct watchdog_info ident = { | ||
95 | .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | | ||
96 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
97 | .identity = "IXP4xx Watchdog", | ||
98 | }; | ||
99 | |||
100 | |||
101 | static int | ||
102 | ixp4xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
103 | unsigned long arg) | ||
104 | { | ||
105 | int ret = -ENOTTY; | ||
106 | int time; | ||
107 | |||
108 | switch (cmd) { | ||
109 | case WDIOC_GETSUPPORT: | ||
110 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | ||
111 | sizeof(ident)) ? -EFAULT : 0; | ||
112 | break; | ||
113 | |||
114 | case WDIOC_GETSTATUS: | ||
115 | ret = put_user(0, (int *)arg); | ||
116 | break; | ||
117 | |||
118 | case WDIOC_GETBOOTSTATUS: | ||
119 | ret = put_user(boot_status, (int *)arg); | ||
120 | break; | ||
121 | |||
122 | case WDIOC_SETTIMEOUT: | ||
123 | ret = get_user(time, (int *)arg); | ||
124 | if (ret) | ||
125 | break; | ||
126 | |||
127 | if (time <= 0 || time > 60) { | ||
128 | ret = -EINVAL; | ||
129 | break; | ||
130 | } | ||
131 | |||
132 | heartbeat = time; | ||
133 | wdt_enable(); | ||
134 | /* Fall through */ | ||
135 | |||
136 | case WDIOC_GETTIMEOUT: | ||
137 | ret = put_user(heartbeat, (int *)arg); | ||
138 | break; | ||
139 | |||
140 | case WDIOC_KEEPALIVE: | ||
141 | wdt_enable(); | ||
142 | ret = 0; | ||
143 | break; | ||
144 | } | ||
145 | return ret; | ||
146 | } | ||
147 | |||
148 | static int | ||
149 | ixp4xx_wdt_release(struct inode *inode, struct file *file) | ||
150 | { | ||
151 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { | ||
152 | wdt_disable(); | ||
153 | } else { | ||
154 | printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " | ||
155 | "timer will not stop\n"); | ||
156 | } | ||
157 | |||
158 | clear_bit(WDT_IN_USE, &wdt_status); | ||
159 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
160 | |||
161 | return 0; | ||
162 | } | ||
163 | |||
164 | |||
165 | static const struct file_operations ixp4xx_wdt_fops = | ||
166 | { | ||
167 | .owner = THIS_MODULE, | ||
168 | .llseek = no_llseek, | ||
169 | .write = ixp4xx_wdt_write, | ||
170 | .ioctl = ixp4xx_wdt_ioctl, | ||
171 | .open = ixp4xx_wdt_open, | ||
172 | .release = ixp4xx_wdt_release, | ||
173 | }; | ||
174 | |||
175 | static struct miscdevice ixp4xx_wdt_miscdev = | ||
176 | { | ||
177 | .minor = WATCHDOG_MINOR, | ||
178 | .name = "watchdog", | ||
179 | .fops = &ixp4xx_wdt_fops, | ||
180 | }; | ||
181 | |||
182 | static int __init ixp4xx_wdt_init(void) | ||
183 | { | ||
184 | int ret; | ||
185 | unsigned long processor_id; | ||
186 | |||
187 | asm("mrc p15, 0, %0, cr0, cr0, 0;" : "=r"(processor_id) :); | ||
188 | if (!(processor_id & 0xf) && !cpu_is_ixp46x()) { | ||
189 | printk("IXP4XXX Watchdog: Rev. A0 IXP42x CPU detected - " | ||
190 | "watchdog disabled\n"); | ||
191 | |||
192 | return -ENODEV; | ||
193 | } | ||
194 | |||
195 | ret = misc_register(&ixp4xx_wdt_miscdev); | ||
196 | if (ret == 0) | ||
197 | printk("IXP4xx Watchdog Timer: heartbeat %d sec\n", heartbeat); | ||
198 | |||
199 | boot_status = (*IXP4XX_OSST & IXP4XX_OSST_TIMER_WARM_RESET) ? | ||
200 | WDIOF_CARDRESET : 0; | ||
201 | |||
202 | return ret; | ||
203 | } | ||
204 | |||
205 | static void __exit ixp4xx_wdt_exit(void) | ||
206 | { | ||
207 | misc_deregister(&ixp4xx_wdt_miscdev); | ||
208 | } | ||
209 | |||
210 | |||
211 | module_init(ixp4xx_wdt_init); | ||
212 | module_exit(ixp4xx_wdt_exit); | ||
213 | |||
214 | MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); | ||
215 | MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog"); | ||
216 | |||
217 | module_param(heartbeat, int, 0); | ||
218 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); | ||
219 | |||
220 | module_param(nowayout, int, 0); | ||
221 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); | ||
222 | |||
223 | MODULE_LICENSE("GPL"); | ||
224 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
225 | |||
diff --git a/drivers/watchdog/ks8695_wdt.c b/drivers/watchdog/ks8695_wdt.c new file mode 100644 index 000000000000..7150fb945eaf --- /dev/null +++ b/drivers/watchdog/ks8695_wdt.c | |||
@@ -0,0 +1,308 @@ | |||
1 | /* | ||
2 | * Watchdog driver for Kendin/Micrel KS8695. | ||
3 | * | ||
4 | * (C) 2007 Andrew Victor | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #include <linux/errno.h> | ||
12 | #include <linux/fs.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/miscdevice.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/moduleparam.h> | ||
18 | #include <linux/platform_device.h> | ||
19 | #include <linux/types.h> | ||
20 | #include <linux/watchdog.h> | ||
21 | #include <asm/bitops.h> | ||
22 | #include <asm/io.h> | ||
23 | #include <asm/uaccess.h> | ||
24 | #include <asm/arch/regs-timer.h> | ||
25 | |||
26 | |||
27 | #define WDT_DEFAULT_TIME 5 /* seconds */ | ||
28 | #define WDT_MAX_TIME 171 /* seconds */ | ||
29 | |||
30 | static int wdt_time = WDT_DEFAULT_TIME; | ||
31 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
32 | |||
33 | module_param(wdt_time, int, 0); | ||
34 | MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default="__MODULE_STRING(WDT_DEFAULT_TIME) ")"); | ||
35 | |||
36 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
37 | module_param(nowayout, int, 0); | ||
38 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
39 | #endif | ||
40 | |||
41 | |||
42 | static unsigned long ks8695wdt_busy; | ||
43 | |||
44 | /* ......................................................................... */ | ||
45 | |||
46 | /* | ||
47 | * Disable the watchdog. | ||
48 | */ | ||
49 | static void inline ks8695_wdt_stop(void) | ||
50 | { | ||
51 | unsigned long tmcon; | ||
52 | |||
53 | /* disable timer0 */ | ||
54 | tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); | ||
55 | __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); | ||
56 | } | ||
57 | |||
58 | /* | ||
59 | * Enable and reset the watchdog. | ||
60 | */ | ||
61 | static void inline ks8695_wdt_start(void) | ||
62 | { | ||
63 | unsigned long tmcon; | ||
64 | unsigned long tval = wdt_time * CLOCK_TICK_RATE; | ||
65 | |||
66 | /* disable timer0 */ | ||
67 | tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); | ||
68 | __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); | ||
69 | |||
70 | /* program timer0 */ | ||
71 | __raw_writel(tval | T0TC_WATCHDOG, KS8695_TMR_VA + KS8695_T0TC); | ||
72 | |||
73 | /* re-enable timer0 */ | ||
74 | tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); | ||
75 | __raw_writel(tmcon | TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); | ||
76 | } | ||
77 | |||
78 | /* | ||
79 | * Reload the watchdog timer. (ie, pat the watchdog) | ||
80 | */ | ||
81 | static void inline ks8695_wdt_reload(void) | ||
82 | { | ||
83 | unsigned long tmcon; | ||
84 | |||
85 | /* disable, then re-enable timer0 */ | ||
86 | tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); | ||
87 | __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); | ||
88 | __raw_writel(tmcon | TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); | ||
89 | } | ||
90 | |||
91 | /* | ||
92 | * Change the watchdog time interval. | ||
93 | */ | ||
94 | static int ks8695_wdt_settimeout(int new_time) | ||
95 | { | ||
96 | /* | ||
97 | * All counting occurs at SLOW_CLOCK / 128 = 0.256 Hz | ||
98 | * | ||
99 | * Since WDV is a 16-bit counter, the maximum period is | ||
100 | * 65536 / 0.256 = 256 seconds. | ||
101 | */ | ||
102 | if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) | ||
103 | return -EINVAL; | ||
104 | |||
105 | /* Set new watchdog time. It will be used when ks8695_wdt_start() is called. */ | ||
106 | wdt_time = new_time; | ||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | /* ......................................................................... */ | ||
111 | |||
112 | /* | ||
113 | * Watchdog device is opened, and watchdog starts running. | ||
114 | */ | ||
115 | static int ks8695_wdt_open(struct inode *inode, struct file *file) | ||
116 | { | ||
117 | if (test_and_set_bit(0, &ks8695wdt_busy)) | ||
118 | return -EBUSY; | ||
119 | |||
120 | ks8695_wdt_start(); | ||
121 | return nonseekable_open(inode, file); | ||
122 | } | ||
123 | |||
124 | /* | ||
125 | * Close the watchdog device. | ||
126 | * If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also | ||
127 | * disabled. | ||
128 | */ | ||
129 | static int ks8695_wdt_close(struct inode *inode, struct file *file) | ||
130 | { | ||
131 | if (!nowayout) | ||
132 | ks8695_wdt_stop(); /* Disable the watchdog when file is closed */ | ||
133 | |||
134 | clear_bit(0, &ks8695wdt_busy); | ||
135 | return 0; | ||
136 | } | ||
137 | |||
138 | static struct watchdog_info ks8695_wdt_info = { | ||
139 | .identity = "ks8695 watchdog", | ||
140 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
141 | }; | ||
142 | |||
143 | /* | ||
144 | * Handle commands from user-space. | ||
145 | */ | ||
146 | static int ks8695_wdt_ioctl(struct inode *inode, struct file *file, | ||
147 | unsigned int cmd, unsigned long arg) | ||
148 | { | ||
149 | void __user *argp = (void __user *)arg; | ||
150 | int __user *p = argp; | ||
151 | int new_value; | ||
152 | |||
153 | switch(cmd) { | ||
154 | case WDIOC_KEEPALIVE: | ||
155 | ks8695_wdt_reload(); /* pat the watchdog */ | ||
156 | return 0; | ||
157 | |||
158 | case WDIOC_GETSUPPORT: | ||
159 | return copy_to_user(argp, &ks8695_wdt_info, sizeof(ks8695_wdt_info)) ? -EFAULT : 0; | ||
160 | |||
161 | case WDIOC_SETTIMEOUT: | ||
162 | if (get_user(new_value, p)) | ||
163 | return -EFAULT; | ||
164 | |||
165 | if (ks8695_wdt_settimeout(new_value)) | ||
166 | return -EINVAL; | ||
167 | |||
168 | /* Enable new time value */ | ||
169 | ks8695_wdt_start(); | ||
170 | |||
171 | /* Return current value */ | ||
172 | return put_user(wdt_time, p); | ||
173 | |||
174 | case WDIOC_GETTIMEOUT: | ||
175 | return put_user(wdt_time, p); | ||
176 | |||
177 | case WDIOC_GETSTATUS: | ||
178 | case WDIOC_GETBOOTSTATUS: | ||
179 | return put_user(0, p); | ||
180 | |||
181 | case WDIOC_SETOPTIONS: | ||
182 | if (get_user(new_value, p)) | ||
183 | return -EFAULT; | ||
184 | |||
185 | if (new_value & WDIOS_DISABLECARD) | ||
186 | ks8695_wdt_stop(); | ||
187 | if (new_value & WDIOS_ENABLECARD) | ||
188 | ks8695_wdt_start(); | ||
189 | return 0; | ||
190 | |||
191 | default: | ||
192 | return -ENOTTY; | ||
193 | } | ||
194 | } | ||
195 | |||
196 | /* | ||
197 | * Pat the watchdog whenever device is written to. | ||
198 | */ | ||
199 | static ssize_t ks8695_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) | ||
200 | { | ||
201 | ks8695_wdt_reload(); /* pat the watchdog */ | ||
202 | return len; | ||
203 | } | ||
204 | |||
205 | /* ......................................................................... */ | ||
206 | |||
207 | static const struct file_operations ks8695wdt_fops = { | ||
208 | .owner = THIS_MODULE, | ||
209 | .llseek = no_llseek, | ||
210 | .ioctl = ks8695_wdt_ioctl, | ||
211 | .open = ks8695_wdt_open, | ||
212 | .release = ks8695_wdt_close, | ||
213 | .write = ks8695_wdt_write, | ||
214 | }; | ||
215 | |||
216 | static struct miscdevice ks8695wdt_miscdev = { | ||
217 | .minor = WATCHDOG_MINOR, | ||
218 | .name = "watchdog", | ||
219 | .fops = &ks8695wdt_fops, | ||
220 | }; | ||
221 | |||
222 | static int __init ks8695wdt_probe(struct platform_device *pdev) | ||
223 | { | ||
224 | int res; | ||
225 | |||
226 | if (ks8695wdt_miscdev.parent) | ||
227 | return -EBUSY; | ||
228 | ks8695wdt_miscdev.parent = &pdev->dev; | ||
229 | |||
230 | res = misc_register(&ks8695wdt_miscdev); | ||
231 | if (res) | ||
232 | return res; | ||
233 | |||
234 | printk("KS8695 Watchdog Timer enabled (%d seconds%s)\n", wdt_time, nowayout ? ", nowayout" : ""); | ||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | static int __exit ks8695wdt_remove(struct platform_device *pdev) | ||
239 | { | ||
240 | int res; | ||
241 | |||
242 | res = misc_deregister(&ks8695wdt_miscdev); | ||
243 | if (!res) | ||
244 | ks8695wdt_miscdev.parent = NULL; | ||
245 | |||
246 | return res; | ||
247 | } | ||
248 | |||
249 | static void ks8695wdt_shutdown(struct platform_device *pdev) | ||
250 | { | ||
251 | ks8695_wdt_stop(); | ||
252 | } | ||
253 | |||
254 | #ifdef CONFIG_PM | ||
255 | |||
256 | static int ks8695wdt_suspend(struct platform_device *pdev, pm_message_t message) | ||
257 | { | ||
258 | ks8695_wdt_stop(); | ||
259 | return 0; | ||
260 | } | ||
261 | |||
262 | static int ks8695wdt_resume(struct platform_device *pdev) | ||
263 | { | ||
264 | if (ks8695wdt_busy) | ||
265 | ks8695_wdt_start(); | ||
266 | return 0; | ||
267 | } | ||
268 | |||
269 | #else | ||
270 | #define ks8695wdt_suspend NULL | ||
271 | #define ks8695wdt_resume NULL | ||
272 | #endif | ||
273 | |||
274 | static struct platform_driver ks8695wdt_driver = { | ||
275 | .probe = ks8695wdt_probe, | ||
276 | .remove = __exit_p(ks8695wdt_remove), | ||
277 | .shutdown = ks8695wdt_shutdown, | ||
278 | .suspend = ks8695wdt_suspend, | ||
279 | .resume = ks8695wdt_resume, | ||
280 | .driver = { | ||
281 | .name = "ks8695_wdt", | ||
282 | .owner = THIS_MODULE, | ||
283 | }, | ||
284 | }; | ||
285 | |||
286 | static int __init ks8695_wdt_init(void) | ||
287 | { | ||
288 | /* Check that the heartbeat value is within range; if not reset to the default */ | ||
289 | if (ks8695_wdt_settimeout(wdt_time)) { | ||
290 | ks8695_wdt_settimeout(WDT_DEFAULT_TIME); | ||
291 | pr_info("ks8695_wdt: wdt_time value must be 1 <= wdt_time <= %i, using %d\n", wdt_time, WDT_MAX_TIME); | ||
292 | } | ||
293 | |||
294 | return platform_driver_register(&ks8695wdt_driver); | ||
295 | } | ||
296 | |||
297 | static void __exit ks8695_wdt_exit(void) | ||
298 | { | ||
299 | platform_driver_unregister(&ks8695wdt_driver); | ||
300 | } | ||
301 | |||
302 | module_init(ks8695_wdt_init); | ||
303 | module_exit(ks8695_wdt_exit); | ||
304 | |||
305 | MODULE_AUTHOR("Andrew Victor"); | ||
306 | MODULE_DESCRIPTION("Watchdog driver for KS8695"); | ||
307 | MODULE_LICENSE("GPL"); | ||
308 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/machzwd.c b/drivers/watchdog/machzwd.c new file mode 100644 index 000000000000..6d35bb112a5f --- /dev/null +++ b/drivers/watchdog/machzwd.c | |||
@@ -0,0 +1,489 @@ | |||
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/module.h> | ||
32 | #include <linux/moduleparam.h> | ||
33 | #include <linux/types.h> | ||
34 | #include <linux/timer.h> | ||
35 | #include <linux/jiffies.h> | ||
36 | #include <linux/miscdevice.h> | ||
37 | #include <linux/watchdog.h> | ||
38 | #include <linux/fs.h> | ||
39 | #include <linux/ioport.h> | ||
40 | #include <linux/notifier.h> | ||
41 | #include <linux/reboot.h> | ||
42 | #include <linux/init.h> | ||
43 | |||
44 | #include <asm/io.h> | ||
45 | #include <asm/uaccess.h> | ||
46 | #include <asm/system.h> | ||
47 | |||
48 | /* ports */ | ||
49 | #define ZF_IOBASE 0x218 | ||
50 | #define INDEX 0x218 | ||
51 | #define DATA_B 0x219 | ||
52 | #define DATA_W 0x21A | ||
53 | #define DATA_D 0x21A | ||
54 | |||
55 | /* indexes */ /* size */ | ||
56 | #define ZFL_VERSION 0x02 /* 16 */ | ||
57 | #define CONTROL 0x10 /* 16 */ | ||
58 | #define STATUS 0x12 /* 8 */ | ||
59 | #define COUNTER_1 0x0C /* 16 */ | ||
60 | #define COUNTER_2 0x0E /* 8 */ | ||
61 | #define PULSE_LEN 0x0F /* 8 */ | ||
62 | |||
63 | /* controls */ | ||
64 | #define ENABLE_WD1 0x0001 | ||
65 | #define ENABLE_WD2 0x0002 | ||
66 | #define RESET_WD1 0x0010 | ||
67 | #define RESET_WD2 0x0020 | ||
68 | #define GEN_SCI 0x0100 | ||
69 | #define GEN_NMI 0x0200 | ||
70 | #define GEN_SMI 0x0400 | ||
71 | #define GEN_RESET 0x0800 | ||
72 | |||
73 | |||
74 | /* utilities */ | ||
75 | |||
76 | #define WD1 0 | ||
77 | #define WD2 1 | ||
78 | |||
79 | #define zf_writew(port, data) { outb(port, INDEX); outw(data, DATA_W); } | ||
80 | #define zf_writeb(port, data) { outb(port, INDEX); outb(data, DATA_B); } | ||
81 | #define zf_get_ZFL_version() zf_readw(ZFL_VERSION) | ||
82 | |||
83 | |||
84 | static unsigned short zf_readw(unsigned char port) | ||
85 | { | ||
86 | outb(port, INDEX); | ||
87 | return inw(DATA_W); | ||
88 | } | ||
89 | |||
90 | |||
91 | MODULE_AUTHOR("Fernando Fuganti <fuganti@conectiva.com.br>"); | ||
92 | MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver"); | ||
93 | MODULE_LICENSE("GPL"); | ||
94 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
95 | |||
96 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
97 | module_param(nowayout, int, 0); | ||
98 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
99 | |||
100 | #define PFX "machzwd" | ||
101 | |||
102 | static struct watchdog_info zf_info = { | ||
103 | .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | ||
104 | .firmware_version = 1, | ||
105 | .identity = "ZF-Logic watchdog", | ||
106 | }; | ||
107 | |||
108 | |||
109 | /* | ||
110 | * action refers to action taken when watchdog resets | ||
111 | * 0 = GEN_RESET | ||
112 | * 1 = GEN_SMI | ||
113 | * 2 = GEN_NMI | ||
114 | * 3 = GEN_SCI | ||
115 | * defaults to GEN_RESET (0) | ||
116 | */ | ||
117 | static int action = 0; | ||
118 | module_param(action, int, 0); | ||
119 | MODULE_PARM_DESC(action, "after watchdog resets, generate: 0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI"); | ||
120 | |||
121 | static void zf_ping(unsigned long data); | ||
122 | |||
123 | static int zf_action = GEN_RESET; | ||
124 | static unsigned long zf_is_open; | ||
125 | static char zf_expect_close; | ||
126 | static spinlock_t zf_lock; | ||
127 | static spinlock_t zf_port_lock; | ||
128 | static DEFINE_TIMER(zf_timer, zf_ping, 0, 0); | ||
129 | static unsigned long next_heartbeat = 0; | ||
130 | |||
131 | |||
132 | /* timeout for user land heart beat (10 seconds) */ | ||
133 | #define ZF_USER_TIMEO (HZ*10) | ||
134 | |||
135 | /* timeout for hardware watchdog (~500ms) */ | ||
136 | #define ZF_HW_TIMEO (HZ/2) | ||
137 | |||
138 | /* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */ | ||
139 | #define ZF_CTIMEOUT 0xffff | ||
140 | |||
141 | #ifndef ZF_DEBUG | ||
142 | # define dprintk(format, args...) | ||
143 | #else | ||
144 | # define dprintk(format, args...) printk(KERN_DEBUG PFX ":%s:%d: " format, __FUNCTION__, __LINE__ , ## args) | ||
145 | #endif | ||
146 | |||
147 | |||
148 | static inline void zf_set_status(unsigned char new) | ||
149 | { | ||
150 | zf_writeb(STATUS, new); | ||
151 | } | ||
152 | |||
153 | |||
154 | /* CONTROL register functions */ | ||
155 | |||
156 | static inline unsigned short zf_get_control(void) | ||
157 | { | ||
158 | return zf_readw(CONTROL); | ||
159 | } | ||
160 | |||
161 | static inline void zf_set_control(unsigned short new) | ||
162 | { | ||
163 | zf_writew(CONTROL, new); | ||
164 | } | ||
165 | |||
166 | |||
167 | /* WD#? counter functions */ | ||
168 | /* | ||
169 | * Just set counter value | ||
170 | */ | ||
171 | |||
172 | static inline void zf_set_timer(unsigned short new, unsigned char n) | ||
173 | { | ||
174 | switch(n){ | ||
175 | case WD1: | ||
176 | zf_writew(COUNTER_1, new); | ||
177 | case WD2: | ||
178 | zf_writeb(COUNTER_2, new > 0xff ? 0xff : new); | ||
179 | default: | ||
180 | return; | ||
181 | } | ||
182 | } | ||
183 | |||
184 | /* | ||
185 | * stop hardware timer | ||
186 | */ | ||
187 | static void zf_timer_off(void) | ||
188 | { | ||
189 | unsigned int ctrl_reg = 0; | ||
190 | unsigned long flags; | ||
191 | |||
192 | /* stop internal ping */ | ||
193 | del_timer_sync(&zf_timer); | ||
194 | |||
195 | spin_lock_irqsave(&zf_port_lock, flags); | ||
196 | /* stop watchdog timer */ | ||
197 | ctrl_reg = zf_get_control(); | ||
198 | ctrl_reg |= (ENABLE_WD1|ENABLE_WD2); /* disable wd1 and wd2 */ | ||
199 | ctrl_reg &= ~(ENABLE_WD1|ENABLE_WD2); | ||
200 | zf_set_control(ctrl_reg); | ||
201 | spin_unlock_irqrestore(&zf_port_lock, flags); | ||
202 | |||
203 | printk(KERN_INFO PFX ": Watchdog timer is now disabled\n"); | ||
204 | } | ||
205 | |||
206 | |||
207 | /* | ||
208 | * start hardware timer | ||
209 | */ | ||
210 | static void zf_timer_on(void) | ||
211 | { | ||
212 | unsigned int ctrl_reg = 0; | ||
213 | unsigned long flags; | ||
214 | |||
215 | spin_lock_irqsave(&zf_port_lock, flags); | ||
216 | |||
217 | zf_writeb(PULSE_LEN, 0xff); | ||
218 | |||
219 | zf_set_timer(ZF_CTIMEOUT, WD1); | ||
220 | |||
221 | /* user land ping */ | ||
222 | next_heartbeat = jiffies + ZF_USER_TIMEO; | ||
223 | |||
224 | /* start the timer for internal ping */ | ||
225 | mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO); | ||
226 | |||
227 | /* start watchdog timer */ | ||
228 | ctrl_reg = zf_get_control(); | ||
229 | ctrl_reg |= (ENABLE_WD1|zf_action); | ||
230 | zf_set_control(ctrl_reg); | ||
231 | spin_unlock_irqrestore(&zf_port_lock, flags); | ||
232 | |||
233 | printk(KERN_INFO PFX ": Watchdog timer is now enabled\n"); | ||
234 | } | ||
235 | |||
236 | |||
237 | static void zf_ping(unsigned long data) | ||
238 | { | ||
239 | unsigned int ctrl_reg = 0; | ||
240 | unsigned long flags; | ||
241 | |||
242 | zf_writeb(COUNTER_2, 0xff); | ||
243 | |||
244 | if(time_before(jiffies, next_heartbeat)){ | ||
245 | |||
246 | dprintk("time_before: %ld\n", next_heartbeat - jiffies); | ||
247 | |||
248 | /* | ||
249 | * reset event is activated by transition from 0 to 1 on | ||
250 | * RESET_WD1 bit and we assume that it is already zero... | ||
251 | */ | ||
252 | |||
253 | spin_lock_irqsave(&zf_port_lock, flags); | ||
254 | ctrl_reg = zf_get_control(); | ||
255 | ctrl_reg |= RESET_WD1; | ||
256 | zf_set_control(ctrl_reg); | ||
257 | |||
258 | /* ...and nothing changes until here */ | ||
259 | ctrl_reg &= ~(RESET_WD1); | ||
260 | zf_set_control(ctrl_reg); | ||
261 | spin_unlock_irqrestore(&zf_port_lock, flags); | ||
262 | |||
263 | mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO); | ||
264 | }else{ | ||
265 | printk(KERN_CRIT PFX ": I will reset your machine\n"); | ||
266 | } | ||
267 | } | ||
268 | |||
269 | static ssize_t zf_write(struct file *file, const char __user *buf, size_t count, | ||
270 | loff_t *ppos) | ||
271 | { | ||
272 | /* See if we got the magic character */ | ||
273 | if(count){ | ||
274 | |||
275 | /* | ||
276 | * no need to check for close confirmation | ||
277 | * no way to disable watchdog ;) | ||
278 | */ | ||
279 | if (!nowayout) { | ||
280 | size_t ofs; | ||
281 | |||
282 | /* | ||
283 | * note: just in case someone wrote the magic character | ||
284 | * five months ago... | ||
285 | */ | ||
286 | zf_expect_close = 0; | ||
287 | |||
288 | /* now scan */ | ||
289 | for (ofs = 0; ofs != count; ofs++){ | ||
290 | char c; | ||
291 | if (get_user(c, buf + ofs)) | ||
292 | return -EFAULT; | ||
293 | if (c == 'V'){ | ||
294 | zf_expect_close = 42; | ||
295 | dprintk("zf_expect_close = 42\n"); | ||
296 | } | ||
297 | } | ||
298 | } | ||
299 | |||
300 | /* | ||
301 | * Well, anyhow someone wrote to us, | ||
302 | * we should return that favour | ||
303 | */ | ||
304 | next_heartbeat = jiffies + ZF_USER_TIMEO; | ||
305 | dprintk("user ping at %ld\n", jiffies); | ||
306 | |||
307 | } | ||
308 | |||
309 | return count; | ||
310 | } | ||
311 | |||
312 | static int zf_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
313 | unsigned long arg) | ||
314 | { | ||
315 | void __user *argp = (void __user *)arg; | ||
316 | int __user *p = argp; | ||
317 | switch (cmd) { | ||
318 | case WDIOC_GETSUPPORT: | ||
319 | if (copy_to_user(argp, &zf_info, sizeof(zf_info))) | ||
320 | return -EFAULT; | ||
321 | break; | ||
322 | |||
323 | case WDIOC_GETSTATUS: | ||
324 | case WDIOC_GETBOOTSTATUS: | ||
325 | return put_user(0, p); | ||
326 | |||
327 | case WDIOC_KEEPALIVE: | ||
328 | zf_ping(0); | ||
329 | break; | ||
330 | |||
331 | default: | ||
332 | return -ENOTTY; | ||
333 | } | ||
334 | |||
335 | return 0; | ||
336 | } | ||
337 | |||
338 | static int zf_open(struct inode *inode, struct file *file) | ||
339 | { | ||
340 | spin_lock(&zf_lock); | ||
341 | if(test_and_set_bit(0, &zf_is_open)) { | ||
342 | spin_unlock(&zf_lock); | ||
343 | return -EBUSY; | ||
344 | } | ||
345 | |||
346 | if (nowayout) | ||
347 | __module_get(THIS_MODULE); | ||
348 | |||
349 | spin_unlock(&zf_lock); | ||
350 | |||
351 | zf_timer_on(); | ||
352 | |||
353 | return nonseekable_open(inode, file); | ||
354 | } | ||
355 | |||
356 | static int zf_close(struct inode *inode, struct file *file) | ||
357 | { | ||
358 | if(zf_expect_close == 42){ | ||
359 | zf_timer_off(); | ||
360 | } else { | ||
361 | del_timer(&zf_timer); | ||
362 | printk(KERN_ERR PFX ": device file closed unexpectedly. Will not stop the WDT!\n"); | ||
363 | } | ||
364 | |||
365 | spin_lock(&zf_lock); | ||
366 | clear_bit(0, &zf_is_open); | ||
367 | spin_unlock(&zf_lock); | ||
368 | |||
369 | zf_expect_close = 0; | ||
370 | |||
371 | return 0; | ||
372 | } | ||
373 | |||
374 | /* | ||
375 | * Notifier for system down | ||
376 | */ | ||
377 | |||
378 | static int zf_notify_sys(struct notifier_block *this, unsigned long code, | ||
379 | void *unused) | ||
380 | { | ||
381 | if(code == SYS_DOWN || code == SYS_HALT){ | ||
382 | zf_timer_off(); | ||
383 | } | ||
384 | |||
385 | return NOTIFY_DONE; | ||
386 | } | ||
387 | |||
388 | |||
389 | |||
390 | |||
391 | static const struct file_operations zf_fops = { | ||
392 | .owner = THIS_MODULE, | ||
393 | .llseek = no_llseek, | ||
394 | .write = zf_write, | ||
395 | .ioctl = zf_ioctl, | ||
396 | .open = zf_open, | ||
397 | .release = zf_close, | ||
398 | }; | ||
399 | |||
400 | static struct miscdevice zf_miscdev = { | ||
401 | .minor = WATCHDOG_MINOR, | ||
402 | .name = "watchdog", | ||
403 | .fops = &zf_fops, | ||
404 | }; | ||
405 | |||
406 | |||
407 | /* | ||
408 | * The device needs to learn about soft shutdowns in order to | ||
409 | * turn the timebomb registers off. | ||
410 | */ | ||
411 | static struct notifier_block zf_notifier = { | ||
412 | .notifier_call = zf_notify_sys, | ||
413 | }; | ||
414 | |||
415 | static void __init zf_show_action(int act) | ||
416 | { | ||
417 | char *str[] = { "RESET", "SMI", "NMI", "SCI" }; | ||
418 | |||
419 | printk(KERN_INFO PFX ": Watchdog using action = %s\n", str[act]); | ||
420 | } | ||
421 | |||
422 | static int __init zf_init(void) | ||
423 | { | ||
424 | int ret; | ||
425 | |||
426 | printk(KERN_INFO PFX ": MachZ ZF-Logic Watchdog driver initializing.\n"); | ||
427 | |||
428 | ret = zf_get_ZFL_version(); | ||
429 | if ((!ret) || (ret == 0xffff)) { | ||
430 | printk(KERN_WARNING PFX ": no ZF-Logic found\n"); | ||
431 | return -ENODEV; | ||
432 | } | ||
433 | |||
434 | if((action <= 3) && (action >= 0)){ | ||
435 | zf_action = zf_action>>action; | ||
436 | } else | ||
437 | action = 0; | ||
438 | |||
439 | zf_show_action(action); | ||
440 | |||
441 | spin_lock_init(&zf_lock); | ||
442 | spin_lock_init(&zf_port_lock); | ||
443 | |||
444 | if(!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")){ | ||
445 | printk(KERN_ERR "cannot reserve I/O ports at %d\n", | ||
446 | ZF_IOBASE); | ||
447 | ret = -EBUSY; | ||
448 | goto no_region; | ||
449 | } | ||
450 | |||
451 | ret = register_reboot_notifier(&zf_notifier); | ||
452 | if(ret){ | ||
453 | printk(KERN_ERR "can't register reboot notifier (err=%d)\n", | ||
454 | ret); | ||
455 | goto no_reboot; | ||
456 | } | ||
457 | |||
458 | ret = misc_register(&zf_miscdev); | ||
459 | if (ret){ | ||
460 | printk(KERN_ERR "can't misc_register on minor=%d\n", | ||
461 | WATCHDOG_MINOR); | ||
462 | goto no_misc; | ||
463 | } | ||
464 | |||
465 | zf_set_status(0); | ||
466 | zf_set_control(0); | ||
467 | |||
468 | return 0; | ||
469 | |||
470 | no_misc: | ||
471 | unregister_reboot_notifier(&zf_notifier); | ||
472 | no_reboot: | ||
473 | release_region(ZF_IOBASE, 3); | ||
474 | no_region: | ||
475 | return ret; | ||
476 | } | ||
477 | |||
478 | |||
479 | static void __exit zf_exit(void) | ||
480 | { | ||
481 | zf_timer_off(); | ||
482 | |||
483 | misc_deregister(&zf_miscdev); | ||
484 | unregister_reboot_notifier(&zf_notifier); | ||
485 | release_region(ZF_IOBASE, 3); | ||
486 | } | ||
487 | |||
488 | module_init(zf_init); | ||
489 | module_exit(zf_exit); | ||
diff --git a/drivers/watchdog/mixcomwd.c b/drivers/watchdog/mixcomwd.c new file mode 100644 index 000000000000..1adf1d56027d --- /dev/null +++ b/drivers/watchdog/mixcomwd.c | |||
@@ -0,0 +1,330 @@ | |||
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 | * Version 0.6 (2002/04/12): Rob Radez <rob@osinvestor.com> | ||
35 | * - make mixcomwd_opened unsigned, | ||
36 | * removed lock_kernel/unlock_kernel from mixcomwd_release, | ||
37 | * modified ioctl a bit to conform to API | ||
38 | * | ||
39 | */ | ||
40 | |||
41 | #define VERSION "0.6" | ||
42 | #define WATCHDOG_NAME "mixcomwd" | ||
43 | #define PFX WATCHDOG_NAME ": " | ||
44 | |||
45 | #include <linux/module.h> | ||
46 | #include <linux/moduleparam.h> | ||
47 | #include <linux/types.h> | ||
48 | #include <linux/miscdevice.h> | ||
49 | #include <linux/ioport.h> | ||
50 | #include <linux/watchdog.h> | ||
51 | #include <linux/fs.h> | ||
52 | #include <linux/reboot.h> | ||
53 | #include <linux/init.h> | ||
54 | #include <linux/jiffies.h> | ||
55 | #include <linux/timer.h> | ||
56 | #include <asm/uaccess.h> | ||
57 | #include <asm/io.h> | ||
58 | |||
59 | /* | ||
60 | * We have two types of cards that can be probed: | ||
61 | * 1) The Mixcom cards: these cards can be found at addresses | ||
62 | * 0x180, 0x280, 0x380 with an additional offset of 0xc10. | ||
63 | * (Or 0xd90, 0xe90, 0xf90). | ||
64 | * 2) The FlashCOM cards: these cards can be set up at | ||
65 | * 0x300 -> 0x378, in 0x8 jumps with an offset of 0x04. | ||
66 | * (Or 0x304 -> 0x37c in 0x8 jumps). | ||
67 | * Each card has it's own ID. | ||
68 | */ | ||
69 | #define MIXCOM_ID 0x11 | ||
70 | #define FLASHCOM_ID 0x18 | ||
71 | static struct { | ||
72 | int ioport; | ||
73 | int id; | ||
74 | } mixcomwd_io_info[] __devinitdata = { | ||
75 | /* The Mixcom cards */ | ||
76 | {0x0d90, MIXCOM_ID}, | ||
77 | {0x0e90, MIXCOM_ID}, | ||
78 | {0x0f90, MIXCOM_ID}, | ||
79 | /* The FlashCOM cards */ | ||
80 | {0x0304, FLASHCOM_ID}, | ||
81 | {0x030c, FLASHCOM_ID}, | ||
82 | {0x0314, FLASHCOM_ID}, | ||
83 | {0x031c, FLASHCOM_ID}, | ||
84 | {0x0324, FLASHCOM_ID}, | ||
85 | {0x032c, FLASHCOM_ID}, | ||
86 | {0x0334, FLASHCOM_ID}, | ||
87 | {0x033c, FLASHCOM_ID}, | ||
88 | {0x0344, FLASHCOM_ID}, | ||
89 | {0x034c, FLASHCOM_ID}, | ||
90 | {0x0354, FLASHCOM_ID}, | ||
91 | {0x035c, FLASHCOM_ID}, | ||
92 | {0x0364, FLASHCOM_ID}, | ||
93 | {0x036c, FLASHCOM_ID}, | ||
94 | {0x0374, FLASHCOM_ID}, | ||
95 | {0x037c, FLASHCOM_ID}, | ||
96 | /* The end of the list */ | ||
97 | {0x0000, 0}, | ||
98 | }; | ||
99 | |||
100 | static void mixcomwd_timerfun(unsigned long d); | ||
101 | |||
102 | static unsigned long mixcomwd_opened; /* long req'd for setbit --RR */ | ||
103 | |||
104 | static int watchdog_port; | ||
105 | static int mixcomwd_timer_alive; | ||
106 | static DEFINE_TIMER(mixcomwd_timer, mixcomwd_timerfun, 0, 0); | ||
107 | static char expect_close; | ||
108 | |||
109 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
110 | module_param(nowayout, int, 0); | ||
111 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
112 | |||
113 | static void mixcomwd_ping(void) | ||
114 | { | ||
115 | outb_p(55,watchdog_port); | ||
116 | return; | ||
117 | } | ||
118 | |||
119 | static void mixcomwd_timerfun(unsigned long d) | ||
120 | { | ||
121 | mixcomwd_ping(); | ||
122 | |||
123 | mod_timer(&mixcomwd_timer, jiffies + 5 * HZ); | ||
124 | } | ||
125 | |||
126 | /* | ||
127 | * Allow only one person to hold it open | ||
128 | */ | ||
129 | |||
130 | static int mixcomwd_open(struct inode *inode, struct file *file) | ||
131 | { | ||
132 | if(test_and_set_bit(0,&mixcomwd_opened)) { | ||
133 | return -EBUSY; | ||
134 | } | ||
135 | mixcomwd_ping(); | ||
136 | |||
137 | if (nowayout) { | ||
138 | /* | ||
139 | * fops_get() code via open() has already done | ||
140 | * a try_module_get() so it is safe to do the | ||
141 | * __module_get(). | ||
142 | */ | ||
143 | __module_get(THIS_MODULE); | ||
144 | } else { | ||
145 | if(mixcomwd_timer_alive) { | ||
146 | del_timer(&mixcomwd_timer); | ||
147 | mixcomwd_timer_alive=0; | ||
148 | } | ||
149 | } | ||
150 | return nonseekable_open(inode, file); | ||
151 | } | ||
152 | |||
153 | static int mixcomwd_release(struct inode *inode, struct file *file) | ||
154 | { | ||
155 | if (expect_close == 42) { | ||
156 | if(mixcomwd_timer_alive) { | ||
157 | printk(KERN_ERR PFX "release called while internal timer alive"); | ||
158 | return -EBUSY; | ||
159 | } | ||
160 | mixcomwd_timer_alive=1; | ||
161 | mod_timer(&mixcomwd_timer, jiffies + 5 * HZ); | ||
162 | } else { | ||
163 | printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n"); | ||
164 | } | ||
165 | |||
166 | clear_bit(0,&mixcomwd_opened); | ||
167 | expect_close=0; | ||
168 | return 0; | ||
169 | } | ||
170 | |||
171 | |||
172 | static ssize_t mixcomwd_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) | ||
173 | { | ||
174 | if(len) | ||
175 | { | ||
176 | if (!nowayout) { | ||
177 | size_t i; | ||
178 | |||
179 | /* In case it was set long ago */ | ||
180 | expect_close = 0; | ||
181 | |||
182 | for (i = 0; i != len; i++) { | ||
183 | char c; | ||
184 | if (get_user(c, data + i)) | ||
185 | return -EFAULT; | ||
186 | if (c == 'V') | ||
187 | expect_close = 42; | ||
188 | } | ||
189 | } | ||
190 | mixcomwd_ping(); | ||
191 | } | ||
192 | return len; | ||
193 | } | ||
194 | |||
195 | static int mixcomwd_ioctl(struct inode *inode, struct file *file, | ||
196 | unsigned int cmd, unsigned long arg) | ||
197 | { | ||
198 | void __user *argp = (void __user *)arg; | ||
199 | int __user *p = argp; | ||
200 | int status; | ||
201 | static struct watchdog_info ident = { | ||
202 | .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | ||
203 | .firmware_version = 1, | ||
204 | .identity = "MixCOM watchdog", | ||
205 | }; | ||
206 | |||
207 | switch(cmd) | ||
208 | { | ||
209 | case WDIOC_GETSTATUS: | ||
210 | status=mixcomwd_opened; | ||
211 | if (!nowayout) { | ||
212 | status|=mixcomwd_timer_alive; | ||
213 | } | ||
214 | if (copy_to_user(p, &status, sizeof(int))) { | ||
215 | return -EFAULT; | ||
216 | } | ||
217 | break; | ||
218 | case WDIOC_GETBOOTSTATUS: | ||
219 | if (copy_to_user(p, &status, sizeof(int))) { | ||
220 | return -EFAULT; | ||
221 | } | ||
222 | break; | ||
223 | case WDIOC_GETSUPPORT: | ||
224 | if (copy_to_user(argp, &ident, sizeof(ident))) { | ||
225 | return -EFAULT; | ||
226 | } | ||
227 | break; | ||
228 | case WDIOC_KEEPALIVE: | ||
229 | mixcomwd_ping(); | ||
230 | break; | ||
231 | default: | ||
232 | return -ENOTTY; | ||
233 | } | ||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | static const struct file_operations mixcomwd_fops = { | ||
238 | .owner = THIS_MODULE, | ||
239 | .llseek = no_llseek, | ||
240 | .write = mixcomwd_write, | ||
241 | .ioctl = mixcomwd_ioctl, | ||
242 | .open = mixcomwd_open, | ||
243 | .release = mixcomwd_release, | ||
244 | }; | ||
245 | |||
246 | static struct miscdevice mixcomwd_miscdev = { | ||
247 | .minor = WATCHDOG_MINOR, | ||
248 | .name = "watchdog", | ||
249 | .fops = &mixcomwd_fops, | ||
250 | }; | ||
251 | |||
252 | static int __init checkcard(int port, int card_id) | ||
253 | { | ||
254 | int id; | ||
255 | |||
256 | if (!request_region(port, 1, "MixCOM watchdog")) { | ||
257 | return 0; | ||
258 | } | ||
259 | |||
260 | id=inb_p(port); | ||
261 | if (card_id==MIXCOM_ID) | ||
262 | id &= 0x3f; | ||
263 | |||
264 | if (id!=card_id) { | ||
265 | release_region(port, 1); | ||
266 | return 0; | ||
267 | } | ||
268 | return 1; | ||
269 | } | ||
270 | |||
271 | static int __init mixcomwd_init(void) | ||
272 | { | ||
273 | int i; | ||
274 | int ret; | ||
275 | int found=0; | ||
276 | |||
277 | for (i = 0; !found && mixcomwd_io_info[i].ioport != 0; i++) { | ||
278 | if (checkcard(mixcomwd_io_info[i].ioport, | ||
279 | mixcomwd_io_info[i].id)) { | ||
280 | found = 1; | ||
281 | watchdog_port = mixcomwd_io_info[i].ioport; | ||
282 | } | ||
283 | } | ||
284 | |||
285 | if (!found) { | ||
286 | printk(KERN_ERR PFX "No card detected, or port not available.\n"); | ||
287 | return -ENODEV; | ||
288 | } | ||
289 | |||
290 | ret = misc_register(&mixcomwd_miscdev); | ||
291 | if (ret) | ||
292 | { | ||
293 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
294 | WATCHDOG_MINOR, ret); | ||
295 | goto error_misc_register_watchdog; | ||
296 | } | ||
297 | |||
298 | printk(KERN_INFO "MixCOM watchdog driver v%s, watchdog port at 0x%3x\n", | ||
299 | VERSION, watchdog_port); | ||
300 | |||
301 | return 0; | ||
302 | |||
303 | error_misc_register_watchdog: | ||
304 | release_region(watchdog_port, 1); | ||
305 | watchdog_port = 0x0000; | ||
306 | return ret; | ||
307 | } | ||
308 | |||
309 | static void __exit mixcomwd_exit(void) | ||
310 | { | ||
311 | if (!nowayout) { | ||
312 | if(mixcomwd_timer_alive) { | ||
313 | printk(KERN_WARNING PFX "I quit now, hardware will" | ||
314 | " probably reboot!\n"); | ||
315 | del_timer_sync(&mixcomwd_timer); | ||
316 | mixcomwd_timer_alive=0; | ||
317 | } | ||
318 | } | ||
319 | misc_deregister(&mixcomwd_miscdev); | ||
320 | release_region(watchdog_port,1); | ||
321 | } | ||
322 | |||
323 | module_init(mixcomwd_init); | ||
324 | module_exit(mixcomwd_exit); | ||
325 | |||
326 | MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>"); | ||
327 | MODULE_DESCRIPTION("MixCom Watchdog driver"); | ||
328 | MODULE_VERSION(VERSION); | ||
329 | MODULE_LICENSE("GPL"); | ||
330 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/mpc5200_wdt.c b/drivers/watchdog/mpc5200_wdt.c new file mode 100644 index 000000000000..9cfb97576623 --- /dev/null +++ b/drivers/watchdog/mpc5200_wdt.c | |||
@@ -0,0 +1,286 @@ | |||
1 | #include <linux/init.h> | ||
2 | #include <linux/module.h> | ||
3 | #include <linux/miscdevice.h> | ||
4 | #include <linux/watchdog.h> | ||
5 | #include <linux/io.h> | ||
6 | #include <linux/spinlock.h> | ||
7 | #include <asm/of_platform.h> | ||
8 | #include <asm/uaccess.h> | ||
9 | #include <asm/mpc52xx.h> | ||
10 | |||
11 | |||
12 | #define GPT_MODE_WDT (1<<15) | ||
13 | #define GPT_MODE_CE (1<<12) | ||
14 | #define GPT_MODE_MS_TIMER (0x4) | ||
15 | |||
16 | |||
17 | struct mpc5200_wdt { | ||
18 | unsigned count; /* timer ticks before watchdog kicks in */ | ||
19 | long ipb_freq; | ||
20 | struct miscdevice miscdev; | ||
21 | struct resource mem; | ||
22 | struct mpc52xx_gpt __iomem *regs; | ||
23 | spinlock_t io_lock; | ||
24 | }; | ||
25 | |||
26 | /* is_active stores wether or not the /dev/watchdog device is opened */ | ||
27 | static unsigned long is_active; | ||
28 | |||
29 | /* misc devices don't provide a way, to get back to 'dev' or 'miscdev' from | ||
30 | * file operations, which sucks. But there can be max 1 watchdog anyway, so... | ||
31 | */ | ||
32 | static struct mpc5200_wdt *wdt_global; | ||
33 | |||
34 | |||
35 | /* helper to calculate timeout in timer counts */ | ||
36 | static void mpc5200_wdt_set_timeout(struct mpc5200_wdt *wdt, int timeout) | ||
37 | { | ||
38 | /* use biggest prescaler of 64k */ | ||
39 | wdt->count = (wdt->ipb_freq + 0xffff) / 0x10000 * timeout; | ||
40 | |||
41 | if (wdt->count > 0xffff) | ||
42 | wdt->count = 0xffff; | ||
43 | } | ||
44 | /* return timeout in seconds (calculated from timer count) */ | ||
45 | static int mpc5200_wdt_get_timeout(struct mpc5200_wdt *wdt) | ||
46 | { | ||
47 | return wdt->count * 0x10000 / wdt->ipb_freq; | ||
48 | } | ||
49 | |||
50 | |||
51 | /* watchdog operations */ | ||
52 | static int mpc5200_wdt_start(struct mpc5200_wdt *wdt) | ||
53 | { | ||
54 | spin_lock(&wdt->io_lock); | ||
55 | /* disable */ | ||
56 | out_be32(&wdt->regs->mode, 0); | ||
57 | /* set timeout, with maximum prescaler */ | ||
58 | out_be32(&wdt->regs->count, 0x0 | wdt->count); | ||
59 | /* enable watchdog */ | ||
60 | out_be32(&wdt->regs->mode, GPT_MODE_CE | GPT_MODE_WDT | GPT_MODE_MS_TIMER); | ||
61 | spin_unlock(&wdt->io_lock); | ||
62 | |||
63 | return 0; | ||
64 | } | ||
65 | static int mpc5200_wdt_ping(struct mpc5200_wdt *wdt) | ||
66 | { | ||
67 | spin_lock(&wdt->io_lock); | ||
68 | /* writing A5 to OCPW resets the watchdog */ | ||
69 | out_be32(&wdt->regs->mode, 0xA5000000 | (0xffffff & in_be32(&wdt->regs->mode))); | ||
70 | spin_unlock(&wdt->io_lock); | ||
71 | return 0; | ||
72 | } | ||
73 | static int mpc5200_wdt_stop(struct mpc5200_wdt *wdt) | ||
74 | { | ||
75 | spin_lock(&wdt->io_lock); | ||
76 | /* disable */ | ||
77 | out_be32(&wdt->regs->mode, 0); | ||
78 | spin_unlock(&wdt->io_lock); | ||
79 | return 0; | ||
80 | } | ||
81 | |||
82 | |||
83 | /* file operations */ | ||
84 | static ssize_t mpc5200_wdt_write(struct file *file, const char __user *data, | ||
85 | size_t len, loff_t *ppos) | ||
86 | { | ||
87 | struct mpc5200_wdt *wdt = file->private_data; | ||
88 | mpc5200_wdt_ping(wdt); | ||
89 | return 0; | ||
90 | } | ||
91 | static struct watchdog_info mpc5200_wdt_info = { | ||
92 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
93 | .identity = "mpc5200 watchdog on GPT0", | ||
94 | }; | ||
95 | static int mpc5200_wdt_ioctl(struct inode *inode, struct file *file, | ||
96 | unsigned int cmd, unsigned long arg) | ||
97 | { | ||
98 | struct mpc5200_wdt *wdt = file->private_data; | ||
99 | int __user *data = (int __user *)arg; | ||
100 | int timeout; | ||
101 | int ret = 0; | ||
102 | |||
103 | switch (cmd) { | ||
104 | case WDIOC_GETSUPPORT: | ||
105 | ret = copy_to_user(data, &mpc5200_wdt_info, | ||
106 | sizeof(mpc5200_wdt_info)); | ||
107 | if (ret) | ||
108 | ret = -EFAULT; | ||
109 | break; | ||
110 | |||
111 | case WDIOC_GETSTATUS: | ||
112 | case WDIOC_GETBOOTSTATUS: | ||
113 | ret = put_user(0, data); | ||
114 | break; | ||
115 | |||
116 | case WDIOC_KEEPALIVE: | ||
117 | mpc5200_wdt_ping(wdt); | ||
118 | break; | ||
119 | |||
120 | case WDIOC_SETTIMEOUT: | ||
121 | ret = get_user(timeout, data); | ||
122 | if (ret) | ||
123 | break; | ||
124 | mpc5200_wdt_set_timeout(wdt, timeout); | ||
125 | mpc5200_wdt_start(wdt); | ||
126 | /* fall through and return the timeout */ | ||
127 | |||
128 | case WDIOC_GETTIMEOUT: | ||
129 | timeout = mpc5200_wdt_get_timeout(wdt); | ||
130 | ret = put_user(timeout, data); | ||
131 | break; | ||
132 | |||
133 | default: | ||
134 | ret = -ENOTTY; | ||
135 | } | ||
136 | return ret; | ||
137 | } | ||
138 | static int mpc5200_wdt_open(struct inode *inode, struct file *file) | ||
139 | { | ||
140 | /* /dev/watchdog can only be opened once */ | ||
141 | if (test_and_set_bit(0, &is_active)) | ||
142 | return -EBUSY; | ||
143 | |||
144 | /* Set and activate the watchdog */ | ||
145 | mpc5200_wdt_set_timeout(wdt_global, 30); | ||
146 | mpc5200_wdt_start(wdt_global); | ||
147 | file->private_data = wdt_global; | ||
148 | return nonseekable_open(inode, file); | ||
149 | } | ||
150 | static int mpc5200_wdt_release(struct inode *inode, struct file *file) | ||
151 | { | ||
152 | #if WATCHDOG_NOWAYOUT == 0 | ||
153 | struct mpc5200_wdt *wdt = file->private_data; | ||
154 | mpc5200_wdt_stop(wdt); | ||
155 | wdt->count = 0; /* == disabled */ | ||
156 | #endif | ||
157 | clear_bit(0, &is_active); | ||
158 | return 0; | ||
159 | } | ||
160 | |||
161 | static struct file_operations mpc5200_wdt_fops = { | ||
162 | .owner = THIS_MODULE, | ||
163 | .write = mpc5200_wdt_write, | ||
164 | .ioctl = mpc5200_wdt_ioctl, | ||
165 | .open = mpc5200_wdt_open, | ||
166 | .release = mpc5200_wdt_release, | ||
167 | }; | ||
168 | |||
169 | /* module operations */ | ||
170 | static int mpc5200_wdt_probe(struct of_device *op, const struct of_device_id *match) | ||
171 | { | ||
172 | struct mpc5200_wdt *wdt; | ||
173 | int err; | ||
174 | const void *has_wdt; | ||
175 | int size; | ||
176 | |||
177 | has_wdt = of_get_property(op->node, "has-wdt", NULL); | ||
178 | if (!has_wdt) | ||
179 | return -ENODEV; | ||
180 | |||
181 | wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); | ||
182 | if (!wdt) | ||
183 | return -ENOMEM; | ||
184 | |||
185 | wdt->ipb_freq = mpc52xx_find_ipb_freq(op->node); | ||
186 | |||
187 | err = of_address_to_resource(op->node, 0, &wdt->mem); | ||
188 | if (err) | ||
189 | goto out_free; | ||
190 | size = wdt->mem.end - wdt->mem.start + 1; | ||
191 | if (!request_mem_region(wdt->mem.start, size, "mpc5200_wdt")) { | ||
192 | err = -ENODEV; | ||
193 | goto out_free; | ||
194 | } | ||
195 | wdt->regs = ioremap(wdt->mem.start, size); | ||
196 | if (!wdt->regs) { | ||
197 | err = -ENODEV; | ||
198 | goto out_release; | ||
199 | } | ||
200 | |||
201 | dev_set_drvdata(&op->dev, wdt); | ||
202 | spin_lock_init(&wdt->io_lock); | ||
203 | |||
204 | wdt->miscdev = (struct miscdevice) { | ||
205 | .minor = WATCHDOG_MINOR, | ||
206 | .name = "watchdog", | ||
207 | .fops = &mpc5200_wdt_fops, | ||
208 | .parent = &op->dev, | ||
209 | }; | ||
210 | wdt_global = wdt; | ||
211 | err = misc_register(&wdt->miscdev); | ||
212 | if (!err) | ||
213 | return 0; | ||
214 | |||
215 | iounmap(wdt->regs); | ||
216 | out_release: | ||
217 | release_mem_region(wdt->mem.start, size); | ||
218 | out_free: | ||
219 | kfree(wdt); | ||
220 | return err; | ||
221 | } | ||
222 | |||
223 | static int mpc5200_wdt_remove(struct of_device *op) | ||
224 | { | ||
225 | struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev); | ||
226 | |||
227 | mpc5200_wdt_stop(wdt); | ||
228 | misc_deregister(&wdt->miscdev); | ||
229 | iounmap(wdt->regs); | ||
230 | release_mem_region(wdt->mem.start, wdt->mem.end - wdt->mem.start + 1); | ||
231 | kfree(wdt); | ||
232 | |||
233 | return 0; | ||
234 | } | ||
235 | static int mpc5200_wdt_suspend(struct of_device *op, pm_message_t state) | ||
236 | { | ||
237 | struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev); | ||
238 | mpc5200_wdt_stop(wdt); | ||
239 | return 0; | ||
240 | } | ||
241 | static int mpc5200_wdt_resume(struct of_device *op) | ||
242 | { | ||
243 | struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev); | ||
244 | if (wdt->count) | ||
245 | mpc5200_wdt_start(wdt); | ||
246 | return 0; | ||
247 | } | ||
248 | static int mpc5200_wdt_shutdown(struct of_device *op) | ||
249 | { | ||
250 | struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev); | ||
251 | mpc5200_wdt_stop(wdt); | ||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | static struct of_device_id mpc5200_wdt_match[] = { | ||
256 | { .compatible = "mpc5200-gpt", }, | ||
257 | {}, | ||
258 | }; | ||
259 | static struct of_platform_driver mpc5200_wdt_driver = { | ||
260 | .owner = THIS_MODULE, | ||
261 | .name = "mpc5200-gpt-wdt", | ||
262 | .match_table = mpc5200_wdt_match, | ||
263 | .probe = mpc5200_wdt_probe, | ||
264 | .remove = mpc5200_wdt_remove, | ||
265 | .suspend = mpc5200_wdt_suspend, | ||
266 | .resume = mpc5200_wdt_resume, | ||
267 | .shutdown = mpc5200_wdt_shutdown, | ||
268 | }; | ||
269 | |||
270 | |||
271 | static int __init mpc5200_wdt_init(void) | ||
272 | { | ||
273 | return of_register_platform_driver(&mpc5200_wdt_driver); | ||
274 | } | ||
275 | |||
276 | static void __exit mpc5200_wdt_exit(void) | ||
277 | { | ||
278 | of_unregister_platform_driver(&mpc5200_wdt_driver); | ||
279 | } | ||
280 | |||
281 | module_init(mpc5200_wdt_init); | ||
282 | module_exit(mpc5200_wdt_exit); | ||
283 | |||
284 | MODULE_AUTHOR("Domen Puncer <domen.puncer@telargo.com>"); | ||
285 | MODULE_LICENSE("Dual BSD/GPL"); | ||
286 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/mpc83xx_wdt.c b/drivers/watchdog/mpc83xx_wdt.c new file mode 100644 index 000000000000..a0bf95fb9763 --- /dev/null +++ b/drivers/watchdog/mpc83xx_wdt.c | |||
@@ -0,0 +1,231 @@ | |||
1 | /* | ||
2 | * mpc83xx_wdt.c - MPC83xx watchdog userspace interface | ||
3 | * | ||
4 | * Authors: Dave Updegraff <dave@cray.org> | ||
5 | * Kumar Gala <galak@kernel.crashing.org> | ||
6 | * Attribution: from 83xx_wst: Florian Schirmer <jolt@tuxbox.org> | ||
7 | * ..and from sc520_wdt | ||
8 | * | ||
9 | * Note: it appears that you can only actually ENABLE or DISABLE the thing | ||
10 | * once after POR. Once enabled, you cannot disable, and vice versa. | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify it | ||
13 | * under the terms of the GNU General Public License as published by the | ||
14 | * Free Software Foundation; either version 2 of the License, or (at your | ||
15 | * option) any later version. | ||
16 | */ | ||
17 | |||
18 | #include <linux/fs.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/miscdevice.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/watchdog.h> | ||
25 | #include <asm/io.h> | ||
26 | #include <asm/uaccess.h> | ||
27 | |||
28 | struct mpc83xx_wdt { | ||
29 | __be32 res0; | ||
30 | __be32 swcrr; /* System watchdog control register */ | ||
31 | #define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */ | ||
32 | #define SWCRR_SWEN 0x00000004 /* Watchdog Enable bit. */ | ||
33 | #define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/ | ||
34 | #define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */ | ||
35 | __be32 swcnr; /* System watchdog count register */ | ||
36 | u8 res1[2]; | ||
37 | __be16 swsrr; /* System watchdog service register */ | ||
38 | u8 res2[0xF0]; | ||
39 | }; | ||
40 | |||
41 | static struct mpc83xx_wdt __iomem *wd_base; | ||
42 | |||
43 | static u16 timeout = 0xffff; | ||
44 | module_param(timeout, ushort, 0); | ||
45 | MODULE_PARM_DESC(timeout, "Watchdog timeout in ticks. (0<timeout<65536, default=65535"); | ||
46 | |||
47 | static int reset = 1; | ||
48 | module_param(reset, bool, 0); | ||
49 | MODULE_PARM_DESC(reset, "Watchdog Interrupt/Reset Mode. 0 = interrupt, 1 = reset"); | ||
50 | |||
51 | /* | ||
52 | * We always prescale, but if someone really doesn't want to they can set this | ||
53 | * to 0 | ||
54 | */ | ||
55 | static int prescale = 1; | ||
56 | static unsigned int timeout_sec; | ||
57 | |||
58 | static unsigned long wdt_is_open; | ||
59 | static spinlock_t wdt_spinlock; | ||
60 | |||
61 | static void mpc83xx_wdt_keepalive(void) | ||
62 | { | ||
63 | /* Ping the WDT */ | ||
64 | spin_lock(&wdt_spinlock); | ||
65 | out_be16(&wd_base->swsrr, 0x556c); | ||
66 | out_be16(&wd_base->swsrr, 0xaa39); | ||
67 | spin_unlock(&wdt_spinlock); | ||
68 | } | ||
69 | |||
70 | static ssize_t mpc83xx_wdt_write(struct file *file, const char __user *buf, | ||
71 | size_t count, loff_t *ppos) | ||
72 | { | ||
73 | if (count) | ||
74 | mpc83xx_wdt_keepalive(); | ||
75 | return count; | ||
76 | } | ||
77 | |||
78 | static int mpc83xx_wdt_open(struct inode *inode, struct file *file) | ||
79 | { | ||
80 | u32 tmp = SWCRR_SWEN; | ||
81 | if (test_and_set_bit(0, &wdt_is_open)) | ||
82 | return -EBUSY; | ||
83 | |||
84 | /* Once we start the watchdog we can't stop it */ | ||
85 | __module_get(THIS_MODULE); | ||
86 | |||
87 | /* Good, fire up the show */ | ||
88 | if (prescale) | ||
89 | tmp |= SWCRR_SWPR; | ||
90 | if (reset) | ||
91 | tmp |= SWCRR_SWRI; | ||
92 | |||
93 | tmp |= timeout << 16; | ||
94 | |||
95 | out_be32(&wd_base->swcrr, tmp); | ||
96 | |||
97 | return nonseekable_open(inode, file); | ||
98 | } | ||
99 | |||
100 | static int mpc83xx_wdt_release(struct inode *inode, struct file *file) | ||
101 | { | ||
102 | printk(KERN_CRIT "Unexpected close, not stopping watchdog!\n"); | ||
103 | mpc83xx_wdt_keepalive(); | ||
104 | clear_bit(0, &wdt_is_open); | ||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | static int mpc83xx_wdt_ioctl(struct inode *inode, struct file *file, | ||
109 | unsigned int cmd, unsigned long arg) | ||
110 | { | ||
111 | void __user *argp = (void __user *)arg; | ||
112 | int __user *p = argp; | ||
113 | static struct watchdog_info ident = { | ||
114 | .options = WDIOF_KEEPALIVEPING, | ||
115 | .firmware_version = 1, | ||
116 | .identity = "MPC83xx", | ||
117 | }; | ||
118 | |||
119 | switch (cmd) { | ||
120 | case WDIOC_GETSUPPORT: | ||
121 | return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; | ||
122 | case WDIOC_GETSTATUS: | ||
123 | case WDIOC_GETBOOTSTATUS: | ||
124 | return put_user(0, p); | ||
125 | case WDIOC_KEEPALIVE: | ||
126 | mpc83xx_wdt_keepalive(); | ||
127 | return 0; | ||
128 | case WDIOC_GETTIMEOUT: | ||
129 | return put_user(timeout_sec, p); | ||
130 | default: | ||
131 | return -ENOTTY; | ||
132 | } | ||
133 | } | ||
134 | |||
135 | static const struct file_operations mpc83xx_wdt_fops = { | ||
136 | .owner = THIS_MODULE, | ||
137 | .llseek = no_llseek, | ||
138 | .write = mpc83xx_wdt_write, | ||
139 | .ioctl = mpc83xx_wdt_ioctl, | ||
140 | .open = mpc83xx_wdt_open, | ||
141 | .release = mpc83xx_wdt_release, | ||
142 | }; | ||
143 | |||
144 | static struct miscdevice mpc83xx_wdt_miscdev = { | ||
145 | .minor = WATCHDOG_MINOR, | ||
146 | .name = "watchdog", | ||
147 | .fops = &mpc83xx_wdt_fops, | ||
148 | }; | ||
149 | |||
150 | static int __devinit mpc83xx_wdt_probe(struct platform_device *dev) | ||
151 | { | ||
152 | struct resource *r; | ||
153 | int ret; | ||
154 | unsigned int *freq = dev->dev.platform_data; | ||
155 | |||
156 | /* get a pointer to the register memory */ | ||
157 | r = platform_get_resource(dev, IORESOURCE_MEM, 0); | ||
158 | |||
159 | if (!r) { | ||
160 | ret = -ENODEV; | ||
161 | goto err_out; | ||
162 | } | ||
163 | |||
164 | wd_base = ioremap(r->start, sizeof (struct mpc83xx_wdt)); | ||
165 | |||
166 | if (wd_base == NULL) { | ||
167 | ret = -ENOMEM; | ||
168 | goto err_out; | ||
169 | } | ||
170 | |||
171 | ret = misc_register(&mpc83xx_wdt_miscdev); | ||
172 | if (ret) { | ||
173 | printk(KERN_ERR "cannot register miscdev on minor=%d " | ||
174 | "(err=%d)\n", | ||
175 | WATCHDOG_MINOR, ret); | ||
176 | goto err_unmap; | ||
177 | } | ||
178 | |||
179 | /* Calculate the timeout in seconds */ | ||
180 | if (prescale) | ||
181 | timeout_sec = (timeout * 0x10000) / (*freq); | ||
182 | else | ||
183 | timeout_sec = timeout / (*freq); | ||
184 | |||
185 | printk(KERN_INFO "WDT driver for MPC83xx initialized. " | ||
186 | "mode:%s timeout=%d (%d seconds)\n", | ||
187 | reset ? "reset":"interrupt", timeout, timeout_sec); | ||
188 | |||
189 | spin_lock_init(&wdt_spinlock); | ||
190 | |||
191 | return 0; | ||
192 | |||
193 | err_unmap: | ||
194 | iounmap(wd_base); | ||
195 | err_out: | ||
196 | return ret; | ||
197 | } | ||
198 | |||
199 | static int __devexit mpc83xx_wdt_remove(struct platform_device *dev) | ||
200 | { | ||
201 | misc_deregister(&mpc83xx_wdt_miscdev); | ||
202 | iounmap(wd_base); | ||
203 | |||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | static struct platform_driver mpc83xx_wdt_driver = { | ||
208 | .probe = mpc83xx_wdt_probe, | ||
209 | .remove = __devexit_p(mpc83xx_wdt_remove), | ||
210 | .driver = { | ||
211 | .name = "mpc83xx_wdt", | ||
212 | }, | ||
213 | }; | ||
214 | |||
215 | static int __init mpc83xx_wdt_init(void) | ||
216 | { | ||
217 | return platform_driver_register(&mpc83xx_wdt_driver); | ||
218 | } | ||
219 | |||
220 | static void __exit mpc83xx_wdt_exit(void) | ||
221 | { | ||
222 | platform_driver_unregister(&mpc83xx_wdt_driver); | ||
223 | } | ||
224 | |||
225 | module_init(mpc83xx_wdt_init); | ||
226 | module_exit(mpc83xx_wdt_exit); | ||
227 | |||
228 | MODULE_AUTHOR("Dave Updegraff, Kumar Gala"); | ||
229 | MODULE_DESCRIPTION("Driver for watchdog timer in MPC83xx uProcessor"); | ||
230 | MODULE_LICENSE("GPL"); | ||
231 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/mpc8xx_wdt.c b/drivers/watchdog/mpc8xx_wdt.c new file mode 100644 index 000000000000..85b5734403a5 --- /dev/null +++ b/drivers/watchdog/mpc8xx_wdt.c | |||
@@ -0,0 +1,169 @@ | |||
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/fs.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/miscdevice.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/watchdog.h> | ||
18 | #include <asm/8xx_immap.h> | ||
19 | #include <asm/uaccess.h> | ||
20 | #include <asm/io.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 uint __iomem *piscr; | ||
29 | piscr = (uint *)&((immap_t*)IMAP_ADDR)->im_sit.sit_piscr; | ||
30 | |||
31 | if (!m8xx_has_internal_rtc) | ||
32 | m8xx_wdt_stop_timer(); | ||
33 | else | ||
34 | out_be32(piscr, in_be32(piscr) & ~(PISCR_PIE | PISCR_PTE)); | ||
35 | |||
36 | printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler deactivated\n"); | ||
37 | } | ||
38 | |||
39 | static void mpc8xx_wdt_handler_enable(void) | ||
40 | { | ||
41 | volatile uint __iomem *piscr; | ||
42 | piscr = (uint *)&((immap_t*)IMAP_ADDR)->im_sit.sit_piscr; | ||
43 | |||
44 | if (!m8xx_has_internal_rtc) | ||
45 | m8xx_wdt_install_timer(); | ||
46 | else | ||
47 | out_be32(piscr, in_be32(piscr) | PISCR_PIE | PISCR_PTE); | ||
48 | |||
49 | printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler activated\n"); | ||
50 | } | ||
51 | |||
52 | static int mpc8xx_wdt_open(struct inode *inode, struct file *file) | ||
53 | { | ||
54 | if (test_and_set_bit(0, &wdt_opened)) | ||
55 | return -EBUSY; | ||
56 | |||
57 | m8xx_wdt_reset(); | ||
58 | mpc8xx_wdt_handler_disable(); | ||
59 | |||
60 | return nonseekable_open(inode, file); | ||
61 | } | ||
62 | |||
63 | static int mpc8xx_wdt_release(struct inode *inode, struct file *file) | ||
64 | { | ||
65 | m8xx_wdt_reset(); | ||
66 | |||
67 | #if !defined(CONFIG_WATCHDOG_NOWAYOUT) | ||
68 | mpc8xx_wdt_handler_enable(); | ||
69 | #endif | ||
70 | |||
71 | clear_bit(0, &wdt_opened); | ||
72 | |||
73 | return 0; | ||
74 | } | ||
75 | |||
76 | static ssize_t mpc8xx_wdt_write(struct file *file, const char *data, size_t len, | ||
77 | loff_t * ppos) | ||
78 | { | ||
79 | if (len) | ||
80 | m8xx_wdt_reset(); | ||
81 | |||
82 | return len; | ||
83 | } | ||
84 | |||
85 | static int mpc8xx_wdt_ioctl(struct inode *inode, struct file *file, | ||
86 | unsigned int cmd, unsigned long arg) | ||
87 | { | ||
88 | int timeout; | ||
89 | static struct watchdog_info info = { | ||
90 | .options = WDIOF_KEEPALIVEPING, | ||
91 | .firmware_version = 0, | ||
92 | .identity = "MPC8xx watchdog", | ||
93 | }; | ||
94 | |||
95 | switch (cmd) { | ||
96 | case WDIOC_GETSUPPORT: | ||
97 | if (copy_to_user((void *)arg, &info, sizeof(info))) | ||
98 | return -EFAULT; | ||
99 | break; | ||
100 | |||
101 | case WDIOC_GETSTATUS: | ||
102 | case WDIOC_GETBOOTSTATUS: | ||
103 | if (put_user(wdt_status, (int *)arg)) | ||
104 | return -EFAULT; | ||
105 | wdt_status &= ~WDIOF_KEEPALIVEPING; | ||
106 | break; | ||
107 | |||
108 | case WDIOC_GETTEMP: | ||
109 | return -EOPNOTSUPP; | ||
110 | |||
111 | case WDIOC_SETOPTIONS: | ||
112 | return -EOPNOTSUPP; | ||
113 | |||
114 | case WDIOC_KEEPALIVE: | ||
115 | m8xx_wdt_reset(); | ||
116 | wdt_status |= WDIOF_KEEPALIVEPING; | ||
117 | break; | ||
118 | |||
119 | case WDIOC_SETTIMEOUT: | ||
120 | return -EOPNOTSUPP; | ||
121 | |||
122 | case WDIOC_GETTIMEOUT: | ||
123 | timeout = m8xx_wdt_get_timeout(); | ||
124 | if (put_user(timeout, (int *)arg)) | ||
125 | return -EFAULT; | ||
126 | break; | ||
127 | |||
128 | default: | ||
129 | return -ENOTTY; | ||
130 | } | ||
131 | |||
132 | return 0; | ||
133 | } | ||
134 | |||
135 | static const struct file_operations mpc8xx_wdt_fops = { | ||
136 | .owner = THIS_MODULE, | ||
137 | .llseek = no_llseek, | ||
138 | .write = mpc8xx_wdt_write, | ||
139 | .ioctl = mpc8xx_wdt_ioctl, | ||
140 | .open = mpc8xx_wdt_open, | ||
141 | .release = mpc8xx_wdt_release, | ||
142 | }; | ||
143 | |||
144 | static struct miscdevice mpc8xx_wdt_miscdev = { | ||
145 | .minor = WATCHDOG_MINOR, | ||
146 | .name = "watchdog", | ||
147 | .fops = &mpc8xx_wdt_fops, | ||
148 | }; | ||
149 | |||
150 | static int __init mpc8xx_wdt_init(void) | ||
151 | { | ||
152 | return misc_register(&mpc8xx_wdt_miscdev); | ||
153 | } | ||
154 | |||
155 | static void __exit mpc8xx_wdt_exit(void) | ||
156 | { | ||
157 | misc_deregister(&mpc8xx_wdt_miscdev); | ||
158 | |||
159 | m8xx_wdt_reset(); | ||
160 | mpc8xx_wdt_handler_enable(); | ||
161 | } | ||
162 | |||
163 | module_init(mpc8xx_wdt_init); | ||
164 | module_exit(mpc8xx_wdt_exit); | ||
165 | |||
166 | MODULE_AUTHOR("Florian Schirmer <jolt@tuxbox.org>"); | ||
167 | MODULE_DESCRIPTION("MPC8xx watchdog driver"); | ||
168 | MODULE_LICENSE("GPL"); | ||
169 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/mpcore_wdt.c b/drivers/watchdog/mpcore_wdt.c new file mode 100644 index 000000000000..0d2b27735419 --- /dev/null +++ b/drivers/watchdog/mpcore_wdt.c | |||
@@ -0,0 +1,435 @@ | |||
1 | /* | ||
2 | * Watchdog driver for the mpcore watchdog timer | ||
3 | * | ||
4 | * (c) Copyright 2004 ARM Limited | ||
5 | * | ||
6 | * Based on the SoftDog driver: | ||
7 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
8 | * http://www.redhat.com | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or | ||
11 | * modify it under the terms of the GNU General Public License | ||
12 | * as published by the Free Software Foundation; either version | ||
13 | * 2 of the License, or (at your option) any later version. | ||
14 | * | ||
15 | * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
16 | * warranty for any of this software. This material is provided | ||
17 | * "AS-IS" and at no charge. | ||
18 | * | ||
19 | * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> | ||
20 | * | ||
21 | */ | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/moduleparam.h> | ||
24 | #include <linux/types.h> | ||
25 | #include <linux/miscdevice.h> | ||
26 | #include <linux/watchdog.h> | ||
27 | #include <linux/fs.h> | ||
28 | #include <linux/reboot.h> | ||
29 | #include <linux/init.h> | ||
30 | #include <linux/interrupt.h> | ||
31 | #include <linux/platform_device.h> | ||
32 | |||
33 | #include <asm/hardware/arm_twd.h> | ||
34 | #include <asm/uaccess.h> | ||
35 | |||
36 | struct mpcore_wdt { | ||
37 | unsigned long timer_alive; | ||
38 | struct device *dev; | ||
39 | void __iomem *base; | ||
40 | int irq; | ||
41 | unsigned int perturb; | ||
42 | char expect_close; | ||
43 | }; | ||
44 | |||
45 | static struct platform_device *mpcore_wdt_dev; | ||
46 | |||
47 | extern unsigned int mpcore_timer_rate; | ||
48 | |||
49 | #define TIMER_MARGIN 60 | ||
50 | static int mpcore_margin = TIMER_MARGIN; | ||
51 | module_param(mpcore_margin, int, 0); | ||
52 | MODULE_PARM_DESC(mpcore_margin, "MPcore timer margin in seconds. (0<mpcore_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")"); | ||
53 | |||
54 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
55 | module_param(nowayout, int, 0); | ||
56 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
57 | |||
58 | #define ONLY_TESTING 0 | ||
59 | static int mpcore_noboot = ONLY_TESTING; | ||
60 | module_param(mpcore_noboot, int, 0); | ||
61 | MODULE_PARM_DESC(mpcore_noboot, "MPcore watchdog action, set to 1 to ignore reboots, 0 to reboot (default=" __MODULE_STRING(ONLY_TESTING) ")"); | ||
62 | |||
63 | /* | ||
64 | * This is the interrupt handler. Note that we only use this | ||
65 | * in testing mode, so don't actually do a reboot here. | ||
66 | */ | ||
67 | static irqreturn_t mpcore_wdt_fire(int irq, void *arg) | ||
68 | { | ||
69 | struct mpcore_wdt *wdt = arg; | ||
70 | |||
71 | /* Check it really was our interrupt */ | ||
72 | if (readl(wdt->base + TWD_WDOG_INTSTAT)) { | ||
73 | dev_printk(KERN_CRIT, wdt->dev, "Triggered - Reboot ignored.\n"); | ||
74 | |||
75 | /* Clear the interrupt on the watchdog */ | ||
76 | writel(1, wdt->base + TWD_WDOG_INTSTAT); | ||
77 | |||
78 | return IRQ_HANDLED; | ||
79 | } | ||
80 | |||
81 | return IRQ_NONE; | ||
82 | } | ||
83 | |||
84 | /* | ||
85 | * mpcore_wdt_keepalive - reload the timer | ||
86 | * | ||
87 | * Note that the spec says a DIFFERENT value must be written to the reload | ||
88 | * register each time. The "perturb" variable deals with this by adding 1 | ||
89 | * to the count every other time the function is called. | ||
90 | */ | ||
91 | static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt) | ||
92 | { | ||
93 | unsigned int count; | ||
94 | |||
95 | /* Assume prescale is set to 256 */ | ||
96 | count = (mpcore_timer_rate / 256) * mpcore_margin; | ||
97 | |||
98 | /* Reload the counter */ | ||
99 | writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD); | ||
100 | |||
101 | wdt->perturb = wdt->perturb ? 0 : 1; | ||
102 | } | ||
103 | |||
104 | static void mpcore_wdt_stop(struct mpcore_wdt *wdt) | ||
105 | { | ||
106 | writel(0x12345678, wdt->base + TWD_WDOG_DISABLE); | ||
107 | writel(0x87654321, wdt->base + TWD_WDOG_DISABLE); | ||
108 | writel(0x0, wdt->base + TWD_WDOG_CONTROL); | ||
109 | } | ||
110 | |||
111 | static void mpcore_wdt_start(struct mpcore_wdt *wdt) | ||
112 | { | ||
113 | dev_printk(KERN_INFO, wdt->dev, "enabling watchdog.\n"); | ||
114 | |||
115 | /* This loads the count register but does NOT start the count yet */ | ||
116 | mpcore_wdt_keepalive(wdt); | ||
117 | |||
118 | if (mpcore_noboot) { | ||
119 | /* Enable watchdog - prescale=256, watchdog mode=0, enable=1 */ | ||
120 | writel(0x0000FF01, wdt->base + TWD_WDOG_CONTROL); | ||
121 | } else { | ||
122 | /* Enable watchdog - prescale=256, watchdog mode=1, enable=1 */ | ||
123 | writel(0x0000FF09, wdt->base + TWD_WDOG_CONTROL); | ||
124 | } | ||
125 | } | ||
126 | |||
127 | static int mpcore_wdt_set_heartbeat(int t) | ||
128 | { | ||
129 | if (t < 0x0001 || t > 0xFFFF) | ||
130 | return -EINVAL; | ||
131 | |||
132 | mpcore_margin = t; | ||
133 | return 0; | ||
134 | } | ||
135 | |||
136 | /* | ||
137 | * /dev/watchdog handling | ||
138 | */ | ||
139 | static int mpcore_wdt_open(struct inode *inode, struct file *file) | ||
140 | { | ||
141 | struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); | ||
142 | |||
143 | if (test_and_set_bit(0, &wdt->timer_alive)) | ||
144 | return -EBUSY; | ||
145 | |||
146 | if (nowayout) | ||
147 | __module_get(THIS_MODULE); | ||
148 | |||
149 | file->private_data = wdt; | ||
150 | |||
151 | /* | ||
152 | * Activate timer | ||
153 | */ | ||
154 | mpcore_wdt_start(wdt); | ||
155 | |||
156 | return nonseekable_open(inode, file); | ||
157 | } | ||
158 | |||
159 | static int mpcore_wdt_release(struct inode *inode, struct file *file) | ||
160 | { | ||
161 | struct mpcore_wdt *wdt = file->private_data; | ||
162 | |||
163 | /* | ||
164 | * Shut off the timer. | ||
165 | * Lock it in if it's a module and we set nowayout | ||
166 | */ | ||
167 | if (wdt->expect_close == 42) { | ||
168 | mpcore_wdt_stop(wdt); | ||
169 | } else { | ||
170 | dev_printk(KERN_CRIT, wdt->dev, "unexpected close, not stopping watchdog!\n"); | ||
171 | mpcore_wdt_keepalive(wdt); | ||
172 | } | ||
173 | clear_bit(0, &wdt->timer_alive); | ||
174 | wdt->expect_close = 0; | ||
175 | return 0; | ||
176 | } | ||
177 | |||
178 | static ssize_t mpcore_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) | ||
179 | { | ||
180 | struct mpcore_wdt *wdt = file->private_data; | ||
181 | |||
182 | /* | ||
183 | * Refresh the timer. | ||
184 | */ | ||
185 | if (len) { | ||
186 | if (!nowayout) { | ||
187 | size_t i; | ||
188 | |||
189 | /* In case it was set long ago */ | ||
190 | wdt->expect_close = 0; | ||
191 | |||
192 | for (i = 0; i != len; i++) { | ||
193 | char c; | ||
194 | |||
195 | if (get_user(c, data + i)) | ||
196 | return -EFAULT; | ||
197 | if (c == 'V') | ||
198 | wdt->expect_close = 42; | ||
199 | } | ||
200 | } | ||
201 | mpcore_wdt_keepalive(wdt); | ||
202 | } | ||
203 | return len; | ||
204 | } | ||
205 | |||
206 | static struct watchdog_info ident = { | ||
207 | .options = WDIOF_SETTIMEOUT | | ||
208 | WDIOF_KEEPALIVEPING | | ||
209 | WDIOF_MAGICCLOSE, | ||
210 | .identity = "MPcore Watchdog", | ||
211 | }; | ||
212 | |||
213 | static int mpcore_wdt_ioctl(struct inode *inode, struct file *file, | ||
214 | unsigned int cmd, unsigned long arg) | ||
215 | { | ||
216 | struct mpcore_wdt *wdt = file->private_data; | ||
217 | int ret; | ||
218 | union { | ||
219 | struct watchdog_info ident; | ||
220 | int i; | ||
221 | } uarg; | ||
222 | |||
223 | if (_IOC_DIR(cmd) && _IOC_SIZE(cmd) > sizeof(uarg)) | ||
224 | return -ENOTTY; | ||
225 | |||
226 | if (_IOC_DIR(cmd) & _IOC_WRITE) { | ||
227 | ret = copy_from_user(&uarg, (void __user *)arg, _IOC_SIZE(cmd)); | ||
228 | if (ret) | ||
229 | return -EFAULT; | ||
230 | } | ||
231 | |||
232 | switch (cmd) { | ||
233 | case WDIOC_GETSUPPORT: | ||
234 | uarg.ident = ident; | ||
235 | ret = 0; | ||
236 | break; | ||
237 | |||
238 | case WDIOC_SETOPTIONS: | ||
239 | ret = -EINVAL; | ||
240 | if (uarg.i & WDIOS_DISABLECARD) { | ||
241 | mpcore_wdt_stop(wdt); | ||
242 | ret = 0; | ||
243 | } | ||
244 | if (uarg.i & WDIOS_ENABLECARD) { | ||
245 | mpcore_wdt_start(wdt); | ||
246 | ret = 0; | ||
247 | } | ||
248 | break; | ||
249 | |||
250 | case WDIOC_GETSTATUS: | ||
251 | case WDIOC_GETBOOTSTATUS: | ||
252 | uarg.i = 0; | ||
253 | ret = 0; | ||
254 | break; | ||
255 | |||
256 | case WDIOC_KEEPALIVE: | ||
257 | mpcore_wdt_keepalive(wdt); | ||
258 | ret = 0; | ||
259 | break; | ||
260 | |||
261 | case WDIOC_SETTIMEOUT: | ||
262 | ret = mpcore_wdt_set_heartbeat(uarg.i); | ||
263 | if (ret) | ||
264 | break; | ||
265 | |||
266 | mpcore_wdt_keepalive(wdt); | ||
267 | /* Fall */ | ||
268 | case WDIOC_GETTIMEOUT: | ||
269 | uarg.i = mpcore_margin; | ||
270 | ret = 0; | ||
271 | break; | ||
272 | |||
273 | default: | ||
274 | return -ENOTTY; | ||
275 | } | ||
276 | |||
277 | if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) { | ||
278 | ret = copy_to_user((void __user *)arg, &uarg, _IOC_SIZE(cmd)); | ||
279 | if (ret) | ||
280 | ret = -EFAULT; | ||
281 | } | ||
282 | return ret; | ||
283 | } | ||
284 | |||
285 | /* | ||
286 | * System shutdown handler. Turn off the watchdog if we're | ||
287 | * restarting or halting the system. | ||
288 | */ | ||
289 | static void mpcore_wdt_shutdown(struct platform_device *dev) | ||
290 | { | ||
291 | struct mpcore_wdt *wdt = platform_get_drvdata(dev); | ||
292 | |||
293 | if (system_state == SYSTEM_RESTART || system_state == SYSTEM_HALT) | ||
294 | mpcore_wdt_stop(wdt); | ||
295 | } | ||
296 | |||
297 | /* | ||
298 | * Kernel Interfaces | ||
299 | */ | ||
300 | static const struct file_operations mpcore_wdt_fops = { | ||
301 | .owner = THIS_MODULE, | ||
302 | .llseek = no_llseek, | ||
303 | .write = mpcore_wdt_write, | ||
304 | .ioctl = mpcore_wdt_ioctl, | ||
305 | .open = mpcore_wdt_open, | ||
306 | .release = mpcore_wdt_release, | ||
307 | }; | ||
308 | |||
309 | static struct miscdevice mpcore_wdt_miscdev = { | ||
310 | .minor = WATCHDOG_MINOR, | ||
311 | .name = "watchdog", | ||
312 | .fops = &mpcore_wdt_fops, | ||
313 | }; | ||
314 | |||
315 | static int __devinit mpcore_wdt_probe(struct platform_device *dev) | ||
316 | { | ||
317 | struct mpcore_wdt *wdt; | ||
318 | struct resource *res; | ||
319 | int ret; | ||
320 | |||
321 | /* We only accept one device, and it must have an id of -1 */ | ||
322 | if (dev->id != -1) | ||
323 | return -ENODEV; | ||
324 | |||
325 | res = platform_get_resource(dev, IORESOURCE_MEM, 0); | ||
326 | if (!res) { | ||
327 | ret = -ENODEV; | ||
328 | goto err_out; | ||
329 | } | ||
330 | |||
331 | wdt = kzalloc(sizeof(struct mpcore_wdt), GFP_KERNEL); | ||
332 | if (!wdt) { | ||
333 | ret = -ENOMEM; | ||
334 | goto err_out; | ||
335 | } | ||
336 | |||
337 | wdt->dev = &dev->dev; | ||
338 | wdt->irq = platform_get_irq(dev, 0); | ||
339 | if (wdt->irq < 0) { | ||
340 | ret = -ENXIO; | ||
341 | goto err_free; | ||
342 | } | ||
343 | wdt->base = ioremap(res->start, res->end - res->start + 1); | ||
344 | if (!wdt->base) { | ||
345 | ret = -ENOMEM; | ||
346 | goto err_free; | ||
347 | } | ||
348 | |||
349 | mpcore_wdt_miscdev.parent = &dev->dev; | ||
350 | ret = misc_register(&mpcore_wdt_miscdev); | ||
351 | if (ret) { | ||
352 | dev_printk(KERN_ERR, _dev, "cannot register miscdev on minor=%d (err=%d)\n", | ||
353 | WATCHDOG_MINOR, ret); | ||
354 | goto err_misc; | ||
355 | } | ||
356 | |||
357 | ret = request_irq(wdt->irq, mpcore_wdt_fire, IRQF_DISABLED, "mpcore_wdt", wdt); | ||
358 | if (ret) { | ||
359 | dev_printk(KERN_ERR, _dev, "cannot register IRQ%d for watchdog\n", wdt->irq); | ||
360 | goto err_irq; | ||
361 | } | ||
362 | |||
363 | mpcore_wdt_stop(wdt); | ||
364 | platform_set_drvdata(&dev->dev, wdt); | ||
365 | mpcore_wdt_dev = dev; | ||
366 | |||
367 | return 0; | ||
368 | |||
369 | err_irq: | ||
370 | misc_deregister(&mpcore_wdt_miscdev); | ||
371 | err_misc: | ||
372 | iounmap(wdt->base); | ||
373 | err_free: | ||
374 | kfree(wdt); | ||
375 | err_out: | ||
376 | return ret; | ||
377 | } | ||
378 | |||
379 | static int __devexit mpcore_wdt_remove(struct platform_device *dev) | ||
380 | { | ||
381 | struct mpcore_wdt *wdt = platform_get_drvdata(dev); | ||
382 | |||
383 | platform_set_drvdata(dev, NULL); | ||
384 | |||
385 | misc_deregister(&mpcore_wdt_miscdev); | ||
386 | |||
387 | mpcore_wdt_dev = NULL; | ||
388 | |||
389 | free_irq(wdt->irq, wdt); | ||
390 | iounmap(wdt->base); | ||
391 | kfree(wdt); | ||
392 | return 0; | ||
393 | } | ||
394 | |||
395 | static struct platform_driver mpcore_wdt_driver = { | ||
396 | .probe = mpcore_wdt_probe, | ||
397 | .remove = __devexit_p(mpcore_wdt_remove), | ||
398 | .shutdown = mpcore_wdt_shutdown, | ||
399 | .driver = { | ||
400 | .owner = THIS_MODULE, | ||
401 | .name = "mpcore_wdt", | ||
402 | }, | ||
403 | }; | ||
404 | |||
405 | static char banner[] __initdata = KERN_INFO "MPcore Watchdog Timer: 0.1. mpcore_noboot=%d mpcore_margin=%d sec (nowayout= %d)\n"; | ||
406 | |||
407 | static int __init mpcore_wdt_init(void) | ||
408 | { | ||
409 | /* | ||
410 | * Check that the margin value is within it's range; | ||
411 | * if not reset to the default | ||
412 | */ | ||
413 | if (mpcore_wdt_set_heartbeat(mpcore_margin)) { | ||
414 | mpcore_wdt_set_heartbeat(TIMER_MARGIN); | ||
415 | printk(KERN_INFO "mpcore_margin value must be 0<mpcore_margin<65536, using %d\n", | ||
416 | TIMER_MARGIN); | ||
417 | } | ||
418 | |||
419 | printk(banner, mpcore_noboot, mpcore_margin, nowayout); | ||
420 | |||
421 | return platform_driver_register(&mpcore_wdt_driver); | ||
422 | } | ||
423 | |||
424 | static void __exit mpcore_wdt_exit(void) | ||
425 | { | ||
426 | platform_driver_unregister(&mpcore_wdt_driver); | ||
427 | } | ||
428 | |||
429 | module_init(mpcore_wdt_init); | ||
430 | module_exit(mpcore_wdt_exit); | ||
431 | |||
432 | MODULE_AUTHOR("ARM Limited"); | ||
433 | MODULE_DESCRIPTION("MPcore Watchdog Device Driver"); | ||
434 | MODULE_LICENSE("GPL"); | ||
435 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/mtx-1_wdt.c b/drivers/watchdog/mtx-1_wdt.c new file mode 100644 index 000000000000..dcfd401a7ad7 --- /dev/null +++ b/drivers/watchdog/mtx-1_wdt.c | |||
@@ -0,0 +1,239 @@ | |||
1 | /* | ||
2 | * Driver for the MTX-1 Watchdog. | ||
3 | * | ||
4 | * (C) Copyright 2005 4G Systems <info@4g-systems.biz>, All Rights Reserved. | ||
5 | * http://www.4g-systems.biz | ||
6 | * | ||
7 | * (C) Copyright 2007 OpenWrt.org, Florian Fainelli <florian@openwrt.org> | ||
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 Michael Stickel nor 4G Systems 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 2005 4G Systems <info@4g-systems.biz> | ||
19 | * | ||
20 | * Release 0.01. | ||
21 | * Author: Michael Stickel michael.stickel@4g-systems.biz | ||
22 | * | ||
23 | * Release 0.02. | ||
24 | * Author: Florian Fainelli florian@openwrt.org | ||
25 | * use the Linux watchdog/timer APIs | ||
26 | * | ||
27 | * The Watchdog is configured to reset the MTX-1 | ||
28 | * if it is not triggered for 100 seconds. | ||
29 | * It should not be triggered more often than 1.6 seconds. | ||
30 | * | ||
31 | * A timer triggers the watchdog every 5 seconds, until | ||
32 | * it is opened for the first time. After the first open | ||
33 | * it MUST be triggered every 2..95 seconds. | ||
34 | */ | ||
35 | |||
36 | #include <linux/module.h> | ||
37 | #include <linux/moduleparam.h> | ||
38 | #include <linux/types.h> | ||
39 | #include <linux/errno.h> | ||
40 | #include <linux/miscdevice.h> | ||
41 | #include <linux/fs.h> | ||
42 | #include <linux/init.h> | ||
43 | #include <linux/ioport.h> | ||
44 | #include <linux/timer.h> | ||
45 | #include <linux/completion.h> | ||
46 | #include <linux/jiffies.h> | ||
47 | #include <linux/watchdog.h> | ||
48 | #include <asm/io.h> | ||
49 | #include <asm/uaccess.h> | ||
50 | |||
51 | #include <asm/mach-au1x00/au1000.h> | ||
52 | |||
53 | #define MTX1_WDT_INTERVAL (5 * HZ) | ||
54 | |||
55 | static int ticks = 100 * HZ; | ||
56 | |||
57 | static struct { | ||
58 | struct completion stop; | ||
59 | volatile int running; | ||
60 | struct timer_list timer; | ||
61 | volatile int queue; | ||
62 | int default_ticks; | ||
63 | unsigned long inuse; | ||
64 | } mtx1_wdt_device; | ||
65 | |||
66 | static void mtx1_wdt_trigger(unsigned long unused) | ||
67 | { | ||
68 | u32 tmp; | ||
69 | |||
70 | if (mtx1_wdt_device.running) | ||
71 | ticks--; | ||
72 | /* | ||
73 | * toggle GPIO2_15 | ||
74 | */ | ||
75 | tmp = au_readl(GPIO2_DIR); | ||
76 | tmp = (tmp & ~(1<<15)) | ((~tmp) & (1<<15)); | ||
77 | au_writel (tmp, GPIO2_DIR); | ||
78 | |||
79 | if (mtx1_wdt_device.queue && ticks) | ||
80 | mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); | ||
81 | else { | ||
82 | complete(&mtx1_wdt_device.stop); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | static void mtx1_wdt_reset(void) | ||
87 | { | ||
88 | ticks = mtx1_wdt_device.default_ticks; | ||
89 | } | ||
90 | |||
91 | |||
92 | static void mtx1_wdt_start(void) | ||
93 | { | ||
94 | if (!mtx1_wdt_device.queue) { | ||
95 | mtx1_wdt_device.queue = 1; | ||
96 | au_writel (au_readl(GPIO2_DIR) | (u32)(1<<15), GPIO2_DIR); | ||
97 | mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); | ||
98 | } | ||
99 | mtx1_wdt_device.running++; | ||
100 | } | ||
101 | |||
102 | static int mtx1_wdt_stop(void) | ||
103 | { | ||
104 | if (mtx1_wdt_device.queue) { | ||
105 | mtx1_wdt_device.queue = 0; | ||
106 | au_writel (au_readl(GPIO2_DIR) & ~((u32)(1<<15)), GPIO2_DIR); | ||
107 | } | ||
108 | |||
109 | ticks = mtx1_wdt_device.default_ticks; | ||
110 | |||
111 | return 0; | ||
112 | } | ||
113 | |||
114 | /* Filesystem functions */ | ||
115 | |||
116 | static int mtx1_wdt_open(struct inode *inode, struct file *file) | ||
117 | { | ||
118 | if (test_and_set_bit(0, &mtx1_wdt_device.inuse)) | ||
119 | return -EBUSY; | ||
120 | |||
121 | return nonseekable_open(inode, file); | ||
122 | } | ||
123 | |||
124 | |||
125 | static int mtx1_wdt_release(struct inode *inode, struct file *file) | ||
126 | { | ||
127 | clear_bit(0, &mtx1_wdt_device.inuse); | ||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int mtx1_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) | ||
132 | { | ||
133 | void __user *argp = (void __user *)arg; | ||
134 | unsigned int value; | ||
135 | static struct watchdog_info ident = | ||
136 | { | ||
137 | .options = WDIOF_CARDRESET, | ||
138 | .identity = "MTX-1 WDT", | ||
139 | }; | ||
140 | |||
141 | switch(cmd) { | ||
142 | case WDIOC_KEEPALIVE: | ||
143 | mtx1_wdt_reset(); | ||
144 | break; | ||
145 | case WDIOC_GETSTATUS: | ||
146 | case WDIOC_GETBOOTSTATUS: | ||
147 | if ( copy_to_user(argp, &value, sizeof(int)) ) | ||
148 | return -EFAULT; | ||
149 | break; | ||
150 | case WDIOC_GETSUPPORT: | ||
151 | if ( copy_to_user(argp, &ident, sizeof(ident)) ) | ||
152 | return -EFAULT; | ||
153 | break; | ||
154 | case WDIOC_SETOPTIONS: | ||
155 | if ( copy_from_user(&value, argp, sizeof(int)) ) | ||
156 | return -EFAULT; | ||
157 | switch(value) { | ||
158 | case WDIOS_ENABLECARD: | ||
159 | mtx1_wdt_start(); | ||
160 | break; | ||
161 | case WDIOS_DISABLECARD: | ||
162 | return mtx1_wdt_stop(); | ||
163 | default: | ||
164 | return -EINVAL; | ||
165 | } | ||
166 | break; | ||
167 | default: | ||
168 | return -ENOTTY; | ||
169 | } | ||
170 | return 0; | ||
171 | } | ||
172 | |||
173 | |||
174 | static ssize_t mtx1_wdt_write(struct file *file, const char *buf, size_t count, loff_t *ppos) | ||
175 | { | ||
176 | if (!count) | ||
177 | return -EIO; | ||
178 | |||
179 | mtx1_wdt_reset(); | ||
180 | return count; | ||
181 | } | ||
182 | |||
183 | static struct file_operations mtx1_wdt_fops = { | ||
184 | .owner = THIS_MODULE, | ||
185 | .llseek = no_llseek, | ||
186 | .ioctl = mtx1_wdt_ioctl, | ||
187 | .open = mtx1_wdt_open, | ||
188 | .write = mtx1_wdt_write, | ||
189 | .release = mtx1_wdt_release | ||
190 | }; | ||
191 | |||
192 | |||
193 | static struct miscdevice mtx1_wdt_misc = { | ||
194 | .minor = WATCHDOG_MINOR, | ||
195 | .name = "watchdog", | ||
196 | .fops = &mtx1_wdt_fops | ||
197 | }; | ||
198 | |||
199 | |||
200 | static int __init mtx1_wdt_init(void) | ||
201 | { | ||
202 | int ret; | ||
203 | |||
204 | if ((ret = misc_register(&mtx1_wdt_misc)) < 0) { | ||
205 | printk(KERN_ERR " mtx-1_wdt : failed to register\n"); | ||
206 | return ret; | ||
207 | } | ||
208 | |||
209 | init_completion(&mtx1_wdt_device.stop); | ||
210 | mtx1_wdt_device.queue = 0; | ||
211 | |||
212 | clear_bit(0, &mtx1_wdt_device.inuse); | ||
213 | |||
214 | setup_timer(&mtx1_wdt_device.timer, mtx1_wdt_trigger, 0L); | ||
215 | |||
216 | mtx1_wdt_device.default_ticks = ticks; | ||
217 | |||
218 | mtx1_wdt_start(); | ||
219 | |||
220 | printk(KERN_INFO "MTX-1 Watchdog driver\n"); | ||
221 | |||
222 | return 0; | ||
223 | } | ||
224 | |||
225 | static void __exit mtx1_wdt_exit(void) | ||
226 | { | ||
227 | if (mtx1_wdt_device.queue) { | ||
228 | mtx1_wdt_device.queue = 0; | ||
229 | wait_for_completion(&mtx1_wdt_device.stop); | ||
230 | } | ||
231 | misc_deregister(&mtx1_wdt_misc); | ||
232 | } | ||
233 | |||
234 | module_init(mtx1_wdt_init); | ||
235 | module_exit(mtx1_wdt_exit); | ||
236 | |||
237 | MODULE_AUTHOR("Michael Stickel, Florian Fainelli"); | ||
238 | MODULE_DESCRIPTION("Driver for the MTX-1 watchdog"); | ||
239 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/watchdog/mv64x60_wdt.c b/drivers/watchdog/mv64x60_wdt.c new file mode 100644 index 000000000000..0365c317f7e1 --- /dev/null +++ b/drivers/watchdog/mv64x60_wdt.c | |||
@@ -0,0 +1,326 @@ | |||
1 | /* | ||
2 | * mv64x60_wdt.c - MV64X60 (Marvell Discovery) watchdog userspace interface | ||
3 | * | ||
4 | * Author: James Chapman <jchapman@katalix.com> | ||
5 | * | ||
6 | * Platform-specific setup code should configure the dog to generate | ||
7 | * interrupt or reset as required. This code only enables/disables | ||
8 | * and services the watchdog. | ||
9 | * | ||
10 | * Derived from mpc8xx_wdt.c, with the following copyright. | ||
11 | * | ||
12 | * 2002 (c) Florian Schirmer <jolt@tuxbox.org> This file is licensed under | ||
13 | * the terms of the GNU General Public License version 2. This program | ||
14 | * is licensed "as is" without any warranty of any kind, whether express | ||
15 | * or implied. | ||
16 | */ | ||
17 | |||
18 | #include <linux/fs.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/miscdevice.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/watchdog.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | |||
26 | #include <linux/mv643xx.h> | ||
27 | #include <asm/uaccess.h> | ||
28 | #include <asm/io.h> | ||
29 | |||
30 | #define MV64x60_WDT_WDC_OFFSET 0 | ||
31 | |||
32 | /* | ||
33 | * The watchdog configuration register contains a pair of 2-bit fields, | ||
34 | * 1. a reload field, bits 27-26, which triggers a reload of | ||
35 | * the countdown register, and | ||
36 | * 2. an enable field, bits 25-24, which toggles between | ||
37 | * enabling and disabling the watchdog timer. | ||
38 | * Bit 31 is a read-only field which indicates whether the | ||
39 | * watchdog timer is currently enabled. | ||
40 | * | ||
41 | * The low 24 bits contain the timer reload value. | ||
42 | */ | ||
43 | #define MV64x60_WDC_ENABLE_SHIFT 24 | ||
44 | #define MV64x60_WDC_SERVICE_SHIFT 26 | ||
45 | #define MV64x60_WDC_ENABLED_SHIFT 31 | ||
46 | |||
47 | #define MV64x60_WDC_ENABLED_TRUE 1 | ||
48 | #define MV64x60_WDC_ENABLED_FALSE 0 | ||
49 | |||
50 | /* Flags bits */ | ||
51 | #define MV64x60_WDOG_FLAG_OPENED 0 | ||
52 | |||
53 | static unsigned long wdt_flags; | ||
54 | static int wdt_status; | ||
55 | static void __iomem *mv64x60_wdt_regs; | ||
56 | static int mv64x60_wdt_timeout; | ||
57 | static int mv64x60_wdt_count; | ||
58 | static unsigned int bus_clk; | ||
59 | static char expect_close; | ||
60 | static DEFINE_SPINLOCK(mv64x60_wdt_spinlock); | ||
61 | |||
62 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
63 | module_param(nowayout, int, 0); | ||
64 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
65 | |||
66 | static int mv64x60_wdt_toggle_wdc(int enabled_predicate, int field_shift) | ||
67 | { | ||
68 | u32 data; | ||
69 | u32 enabled; | ||
70 | int ret = 0; | ||
71 | |||
72 | spin_lock(&mv64x60_wdt_spinlock); | ||
73 | data = readl(mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); | ||
74 | enabled = (data >> MV64x60_WDC_ENABLED_SHIFT) & 1; | ||
75 | |||
76 | /* only toggle the requested field if enabled state matches predicate */ | ||
77 | if ((enabled ^ enabled_predicate) == 0) { | ||
78 | /* We write a 1, then a 2 -- to the appropriate field */ | ||
79 | data = (1 << field_shift) | mv64x60_wdt_count; | ||
80 | writel(data, mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); | ||
81 | |||
82 | data = (2 << field_shift) | mv64x60_wdt_count; | ||
83 | writel(data, mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); | ||
84 | ret = 1; | ||
85 | } | ||
86 | spin_unlock(&mv64x60_wdt_spinlock); | ||
87 | |||
88 | return ret; | ||
89 | } | ||
90 | |||
91 | static void mv64x60_wdt_service(void) | ||
92 | { | ||
93 | mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE, | ||
94 | MV64x60_WDC_SERVICE_SHIFT); | ||
95 | } | ||
96 | |||
97 | static void mv64x60_wdt_handler_enable(void) | ||
98 | { | ||
99 | if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_FALSE, | ||
100 | MV64x60_WDC_ENABLE_SHIFT)) { | ||
101 | mv64x60_wdt_service(); | ||
102 | printk(KERN_NOTICE "mv64x60_wdt: watchdog activated\n"); | ||
103 | } | ||
104 | } | ||
105 | |||
106 | static void mv64x60_wdt_handler_disable(void) | ||
107 | { | ||
108 | if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE, | ||
109 | MV64x60_WDC_ENABLE_SHIFT)) | ||
110 | printk(KERN_NOTICE "mv64x60_wdt: watchdog deactivated\n"); | ||
111 | } | ||
112 | |||
113 | static void mv64x60_wdt_set_timeout(unsigned int timeout) | ||
114 | { | ||
115 | /* maximum bus cycle count is 0xFFFFFFFF */ | ||
116 | if (timeout > 0xFFFFFFFF / bus_clk) | ||
117 | timeout = 0xFFFFFFFF / bus_clk; | ||
118 | |||
119 | mv64x60_wdt_count = timeout * bus_clk >> 8; | ||
120 | mv64x60_wdt_timeout = timeout; | ||
121 | } | ||
122 | |||
123 | static int mv64x60_wdt_open(struct inode *inode, struct file *file) | ||
124 | { | ||
125 | if (test_and_set_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags)) | ||
126 | return -EBUSY; | ||
127 | |||
128 | if (nowayout) | ||
129 | __module_get(THIS_MODULE); | ||
130 | |||
131 | mv64x60_wdt_handler_enable(); | ||
132 | |||
133 | return nonseekable_open(inode, file); | ||
134 | } | ||
135 | |||
136 | static int mv64x60_wdt_release(struct inode *inode, struct file *file) | ||
137 | { | ||
138 | if (expect_close == 42) | ||
139 | mv64x60_wdt_handler_disable(); | ||
140 | else { | ||
141 | printk(KERN_CRIT | ||
142 | "mv64x60_wdt: unexpected close, not stopping timer!\n"); | ||
143 | mv64x60_wdt_service(); | ||
144 | } | ||
145 | expect_close = 0; | ||
146 | |||
147 | clear_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags); | ||
148 | |||
149 | return 0; | ||
150 | } | ||
151 | |||
152 | static ssize_t mv64x60_wdt_write(struct file *file, const char __user *data, | ||
153 | size_t len, loff_t * ppos) | ||
154 | { | ||
155 | if (len) { | ||
156 | if (!nowayout) { | ||
157 | size_t i; | ||
158 | |||
159 | expect_close = 0; | ||
160 | |||
161 | for (i = 0; i != len; i++) { | ||
162 | char c; | ||
163 | if(get_user(c, data + i)) | ||
164 | return -EFAULT; | ||
165 | if (c == 'V') | ||
166 | expect_close = 42; | ||
167 | } | ||
168 | } | ||
169 | mv64x60_wdt_service(); | ||
170 | } | ||
171 | |||
172 | return len; | ||
173 | } | ||
174 | |||
175 | static int mv64x60_wdt_ioctl(struct inode *inode, struct file *file, | ||
176 | unsigned int cmd, unsigned long arg) | ||
177 | { | ||
178 | int timeout; | ||
179 | int options; | ||
180 | void __user *argp = (void __user *)arg; | ||
181 | static struct watchdog_info info = { | ||
182 | .options = WDIOF_SETTIMEOUT | | ||
183 | WDIOF_MAGICCLOSE | | ||
184 | WDIOF_KEEPALIVEPING, | ||
185 | .firmware_version = 0, | ||
186 | .identity = "MV64x60 watchdog", | ||
187 | }; | ||
188 | |||
189 | switch (cmd) { | ||
190 | case WDIOC_GETSUPPORT: | ||
191 | if (copy_to_user(argp, &info, sizeof(info))) | ||
192 | return -EFAULT; | ||
193 | break; | ||
194 | |||
195 | case WDIOC_GETSTATUS: | ||
196 | case WDIOC_GETBOOTSTATUS: | ||
197 | if (put_user(wdt_status, (int __user *)argp)) | ||
198 | return -EFAULT; | ||
199 | wdt_status &= ~WDIOF_KEEPALIVEPING; | ||
200 | break; | ||
201 | |||
202 | case WDIOC_GETTEMP: | ||
203 | return -EOPNOTSUPP; | ||
204 | |||
205 | case WDIOC_SETOPTIONS: | ||
206 | if (get_user(options, (int __user *)argp)) | ||
207 | return -EFAULT; | ||
208 | |||
209 | if (options & WDIOS_DISABLECARD) | ||
210 | mv64x60_wdt_handler_disable(); | ||
211 | |||
212 | if (options & WDIOS_ENABLECARD) | ||
213 | mv64x60_wdt_handler_enable(); | ||
214 | break; | ||
215 | |||
216 | case WDIOC_KEEPALIVE: | ||
217 | mv64x60_wdt_service(); | ||
218 | wdt_status |= WDIOF_KEEPALIVEPING; | ||
219 | break; | ||
220 | |||
221 | case WDIOC_SETTIMEOUT: | ||
222 | if (get_user(timeout, (int __user *)argp)) | ||
223 | return -EFAULT; | ||
224 | mv64x60_wdt_set_timeout(timeout); | ||
225 | /* Fall through */ | ||
226 | |||
227 | case WDIOC_GETTIMEOUT: | ||
228 | if (put_user(mv64x60_wdt_timeout, (int __user *)argp)) | ||
229 | return -EFAULT; | ||
230 | break; | ||
231 | |||
232 | default: | ||
233 | return -ENOTTY; | ||
234 | } | ||
235 | |||
236 | return 0; | ||
237 | } | ||
238 | |||
239 | static const struct file_operations mv64x60_wdt_fops = { | ||
240 | .owner = THIS_MODULE, | ||
241 | .llseek = no_llseek, | ||
242 | .write = mv64x60_wdt_write, | ||
243 | .ioctl = mv64x60_wdt_ioctl, | ||
244 | .open = mv64x60_wdt_open, | ||
245 | .release = mv64x60_wdt_release, | ||
246 | }; | ||
247 | |||
248 | static struct miscdevice mv64x60_wdt_miscdev = { | ||
249 | .minor = WATCHDOG_MINOR, | ||
250 | .name = "watchdog", | ||
251 | .fops = &mv64x60_wdt_fops, | ||
252 | }; | ||
253 | |||
254 | static int __devinit mv64x60_wdt_probe(struct platform_device *dev) | ||
255 | { | ||
256 | struct mv64x60_wdt_pdata *pdata = dev->dev.platform_data; | ||
257 | struct resource *r; | ||
258 | int timeout = 10; | ||
259 | |||
260 | bus_clk = 133; /* in MHz */ | ||
261 | if (pdata) { | ||
262 | timeout = pdata->timeout; | ||
263 | bus_clk = pdata->bus_clk; | ||
264 | } | ||
265 | |||
266 | /* Since bus_clk is truncated MHz, actual frequency could be | ||
267 | * up to 1MHz higher. Round up, since it's better to time out | ||
268 | * too late than too soon. | ||
269 | */ | ||
270 | bus_clk++; | ||
271 | bus_clk *= 1000000; /* convert to Hz */ | ||
272 | |||
273 | r = platform_get_resource(dev, IORESOURCE_MEM, 0); | ||
274 | if (!r) | ||
275 | return -ENODEV; | ||
276 | |||
277 | mv64x60_wdt_regs = ioremap(r->start, r->end - r->start + 1); | ||
278 | if (mv64x60_wdt_regs == NULL) | ||
279 | return -ENOMEM; | ||
280 | |||
281 | mv64x60_wdt_set_timeout(timeout); | ||
282 | |||
283 | mv64x60_wdt_handler_disable(); /* in case timer was already running */ | ||
284 | |||
285 | return misc_register(&mv64x60_wdt_miscdev); | ||
286 | } | ||
287 | |||
288 | static int __devexit mv64x60_wdt_remove(struct platform_device *dev) | ||
289 | { | ||
290 | misc_deregister(&mv64x60_wdt_miscdev); | ||
291 | |||
292 | mv64x60_wdt_handler_disable(); | ||
293 | |||
294 | iounmap(mv64x60_wdt_regs); | ||
295 | |||
296 | return 0; | ||
297 | } | ||
298 | |||
299 | static struct platform_driver mv64x60_wdt_driver = { | ||
300 | .probe = mv64x60_wdt_probe, | ||
301 | .remove = __devexit_p(mv64x60_wdt_remove), | ||
302 | .driver = { | ||
303 | .owner = THIS_MODULE, | ||
304 | .name = MV64x60_WDT_NAME, | ||
305 | }, | ||
306 | }; | ||
307 | |||
308 | static int __init mv64x60_wdt_init(void) | ||
309 | { | ||
310 | printk(KERN_INFO "MV64x60 watchdog driver\n"); | ||
311 | |||
312 | return platform_driver_register(&mv64x60_wdt_driver); | ||
313 | } | ||
314 | |||
315 | static void __exit mv64x60_wdt_exit(void) | ||
316 | { | ||
317 | platform_driver_unregister(&mv64x60_wdt_driver); | ||
318 | } | ||
319 | |||
320 | module_init(mv64x60_wdt_init); | ||
321 | module_exit(mv64x60_wdt_exit); | ||
322 | |||
323 | MODULE_AUTHOR("James Chapman <jchapman@katalix.com>"); | ||
324 | MODULE_DESCRIPTION("MV64x60 watchdog driver"); | ||
325 | MODULE_LICENSE("GPL"); | ||
326 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c new file mode 100644 index 000000000000..719b066f73c4 --- /dev/null +++ b/drivers/watchdog/omap_wdt.c | |||
@@ -0,0 +1,389 @@ | |||
1 | /* | ||
2 | * linux/drivers/char/watchdog/omap_wdt.c | ||
3 | * | ||
4 | * Watchdog driver for the TI OMAP 16xx & 24xx 32KHz (non-secure) watchdog | ||
5 | * | ||
6 | * Author: MontaVista Software, Inc. | ||
7 | * <gdavis@mvista.com> or <source@mvista.com> | ||
8 | * | ||
9 | * 2003 (c) MontaVista Software, Inc. This file is licensed under the | ||
10 | * terms of the GNU General Public License version 2. This program is | ||
11 | * licensed "as is" without any warranty of any kind, whether express | ||
12 | * or implied. | ||
13 | * | ||
14 | * History: | ||
15 | * | ||
16 | * 20030527: George G. Davis <gdavis@mvista.com> | ||
17 | * Initially based on linux-2.4.19-rmk7-pxa1/drivers/char/sa1100_wdt.c | ||
18 | * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> | ||
19 | * Based on SoftDog driver by Alan Cox <alan@redhat.com> | ||
20 | * | ||
21 | * Copyright (c) 2004 Texas Instruments. | ||
22 | * 1. Modified to support OMAP1610 32-KHz watchdog timer | ||
23 | * 2. Ported to 2.6 kernel | ||
24 | * | ||
25 | * Copyright (c) 2005 David Brownell | ||
26 | * Use the driver model and standard identifiers; handle bigger timeouts. | ||
27 | */ | ||
28 | |||
29 | #include <linux/module.h> | ||
30 | #include <linux/types.h> | ||
31 | #include <linux/kernel.h> | ||
32 | #include <linux/fs.h> | ||
33 | #include <linux/mm.h> | ||
34 | #include <linux/miscdevice.h> | ||
35 | #include <linux/watchdog.h> | ||
36 | #include <linux/reboot.h> | ||
37 | #include <linux/init.h> | ||
38 | #include <linux/err.h> | ||
39 | #include <linux/platform_device.h> | ||
40 | #include <linux/moduleparam.h> | ||
41 | #include <linux/clk.h> | ||
42 | |||
43 | #include <asm/io.h> | ||
44 | #include <asm/uaccess.h> | ||
45 | #include <asm/hardware.h> | ||
46 | #include <asm/bitops.h> | ||
47 | |||
48 | #include <asm/arch/prcm.h> | ||
49 | |||
50 | #include "omap_wdt.h" | ||
51 | |||
52 | static unsigned timer_margin; | ||
53 | module_param(timer_margin, uint, 0); | ||
54 | MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)"); | ||
55 | |||
56 | static int omap_wdt_users; | ||
57 | static struct clk *armwdt_ck = NULL; | ||
58 | static struct clk *mpu_wdt_ick = NULL; | ||
59 | static struct clk *mpu_wdt_fck = NULL; | ||
60 | |||
61 | static unsigned int wdt_trgr_pattern = 0x1234; | ||
62 | |||
63 | static void omap_wdt_ping(void) | ||
64 | { | ||
65 | /* wait for posted write to complete */ | ||
66 | while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08) | ||
67 | cpu_relax(); | ||
68 | wdt_trgr_pattern = ~wdt_trgr_pattern; | ||
69 | omap_writel(wdt_trgr_pattern, (OMAP_WATCHDOG_TGR)); | ||
70 | /* wait for posted write to complete */ | ||
71 | while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08) | ||
72 | cpu_relax(); | ||
73 | /* reloaded WCRR from WLDR */ | ||
74 | } | ||
75 | |||
76 | static void omap_wdt_enable(void) | ||
77 | { | ||
78 | /* Sequence to enable the watchdog */ | ||
79 | omap_writel(0xBBBB, OMAP_WATCHDOG_SPR); | ||
80 | while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10) | ||
81 | cpu_relax(); | ||
82 | omap_writel(0x4444, OMAP_WATCHDOG_SPR); | ||
83 | while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10) | ||
84 | cpu_relax(); | ||
85 | } | ||
86 | |||
87 | static void omap_wdt_disable(void) | ||
88 | { | ||
89 | /* sequence required to disable watchdog */ | ||
90 | omap_writel(0xAAAA, OMAP_WATCHDOG_SPR); /* TIMER_MODE */ | ||
91 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10) | ||
92 | cpu_relax(); | ||
93 | omap_writel(0x5555, OMAP_WATCHDOG_SPR); /* TIMER_MODE */ | ||
94 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10) | ||
95 | cpu_relax(); | ||
96 | } | ||
97 | |||
98 | static void omap_wdt_adjust_timeout(unsigned new_timeout) | ||
99 | { | ||
100 | if (new_timeout < TIMER_MARGIN_MIN) | ||
101 | new_timeout = TIMER_MARGIN_DEFAULT; | ||
102 | if (new_timeout > TIMER_MARGIN_MAX) | ||
103 | new_timeout = TIMER_MARGIN_MAX; | ||
104 | timer_margin = new_timeout; | ||
105 | } | ||
106 | |||
107 | static void omap_wdt_set_timeout(void) | ||
108 | { | ||
109 | u32 pre_margin = GET_WLDR_VAL(timer_margin); | ||
110 | |||
111 | /* just count up at 32 KHz */ | ||
112 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04) | ||
113 | cpu_relax(); | ||
114 | omap_writel(pre_margin, OMAP_WATCHDOG_LDR); | ||
115 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04) | ||
116 | cpu_relax(); | ||
117 | } | ||
118 | |||
119 | /* | ||
120 | * Allow only one task to hold it open | ||
121 | */ | ||
122 | |||
123 | static int omap_wdt_open(struct inode *inode, struct file *file) | ||
124 | { | ||
125 | if (test_and_set_bit(1, (unsigned long *)&omap_wdt_users)) | ||
126 | return -EBUSY; | ||
127 | |||
128 | if (cpu_is_omap16xx()) | ||
129 | clk_enable(armwdt_ck); /* Enable the clock */ | ||
130 | |||
131 | if (cpu_is_omap24xx()) { | ||
132 | clk_enable(mpu_wdt_ick); /* Enable the interface clock */ | ||
133 | clk_enable(mpu_wdt_fck); /* Enable the functional clock */ | ||
134 | } | ||
135 | |||
136 | /* initialize prescaler */ | ||
137 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01) | ||
138 | cpu_relax(); | ||
139 | omap_writel((1 << 5) | (PTV << 2), OMAP_WATCHDOG_CNTRL); | ||
140 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01) | ||
141 | cpu_relax(); | ||
142 | |||
143 | omap_wdt_set_timeout(); | ||
144 | omap_wdt_enable(); | ||
145 | return nonseekable_open(inode, file); | ||
146 | } | ||
147 | |||
148 | static int omap_wdt_release(struct inode *inode, struct file *file) | ||
149 | { | ||
150 | /* | ||
151 | * Shut off the timer unless NOWAYOUT is defined. | ||
152 | */ | ||
153 | #ifndef CONFIG_WATCHDOG_NOWAYOUT | ||
154 | omap_wdt_disable(); | ||
155 | |||
156 | if (cpu_is_omap16xx()) { | ||
157 | clk_disable(armwdt_ck); /* Disable the clock */ | ||
158 | clk_put(armwdt_ck); | ||
159 | armwdt_ck = NULL; | ||
160 | } | ||
161 | |||
162 | if (cpu_is_omap24xx()) { | ||
163 | clk_disable(mpu_wdt_ick); /* Disable the clock */ | ||
164 | clk_disable(mpu_wdt_fck); /* Disable the clock */ | ||
165 | clk_put(mpu_wdt_ick); | ||
166 | clk_put(mpu_wdt_fck); | ||
167 | mpu_wdt_ick = NULL; | ||
168 | mpu_wdt_fck = NULL; | ||
169 | } | ||
170 | #else | ||
171 | printk(KERN_CRIT "omap_wdt: Unexpected close, not stopping!\n"); | ||
172 | #endif | ||
173 | omap_wdt_users = 0; | ||
174 | return 0; | ||
175 | } | ||
176 | |||
177 | static ssize_t | ||
178 | omap_wdt_write(struct file *file, const char __user *data, | ||
179 | size_t len, loff_t *ppos) | ||
180 | { | ||
181 | /* Refresh LOAD_TIME. */ | ||
182 | if (len) | ||
183 | omap_wdt_ping(); | ||
184 | return len; | ||
185 | } | ||
186 | |||
187 | static int | ||
188 | omap_wdt_ioctl(struct inode *inode, struct file *file, | ||
189 | unsigned int cmd, unsigned long arg) | ||
190 | { | ||
191 | int new_margin; | ||
192 | static struct watchdog_info ident = { | ||
193 | .identity = "OMAP Watchdog", | ||
194 | .options = WDIOF_SETTIMEOUT, | ||
195 | .firmware_version = 0, | ||
196 | }; | ||
197 | |||
198 | switch (cmd) { | ||
199 | default: | ||
200 | return -ENOTTY; | ||
201 | case WDIOC_GETSUPPORT: | ||
202 | return copy_to_user((struct watchdog_info __user *)arg, &ident, | ||
203 | sizeof(ident)); | ||
204 | case WDIOC_GETSTATUS: | ||
205 | return put_user(0, (int __user *)arg); | ||
206 | case WDIOC_GETBOOTSTATUS: | ||
207 | if (cpu_is_omap16xx()) | ||
208 | return put_user(omap_readw(ARM_SYSST), | ||
209 | (int __user *)arg); | ||
210 | if (cpu_is_omap24xx()) | ||
211 | return put_user(omap_prcm_get_reset_sources(), | ||
212 | (int __user *)arg); | ||
213 | case WDIOC_KEEPALIVE: | ||
214 | omap_wdt_ping(); | ||
215 | return 0; | ||
216 | case WDIOC_SETTIMEOUT: | ||
217 | if (get_user(new_margin, (int __user *)arg)) | ||
218 | return -EFAULT; | ||
219 | omap_wdt_adjust_timeout(new_margin); | ||
220 | |||
221 | omap_wdt_disable(); | ||
222 | omap_wdt_set_timeout(); | ||
223 | omap_wdt_enable(); | ||
224 | |||
225 | omap_wdt_ping(); | ||
226 | /* Fall */ | ||
227 | case WDIOC_GETTIMEOUT: | ||
228 | return put_user(timer_margin, (int __user *)arg); | ||
229 | } | ||
230 | } | ||
231 | |||
232 | static const struct file_operations omap_wdt_fops = { | ||
233 | .owner = THIS_MODULE, | ||
234 | .write = omap_wdt_write, | ||
235 | .ioctl = omap_wdt_ioctl, | ||
236 | .open = omap_wdt_open, | ||
237 | .release = omap_wdt_release, | ||
238 | }; | ||
239 | |||
240 | static struct miscdevice omap_wdt_miscdev = { | ||
241 | .minor = WATCHDOG_MINOR, | ||
242 | .name = "watchdog", | ||
243 | .fops = &omap_wdt_fops | ||
244 | }; | ||
245 | |||
246 | static int __init omap_wdt_probe(struct platform_device *pdev) | ||
247 | { | ||
248 | struct resource *res, *mem; | ||
249 | int ret; | ||
250 | |||
251 | /* reserve static register mappings */ | ||
252 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
253 | if (!res) | ||
254 | return -ENOENT; | ||
255 | |||
256 | mem = request_mem_region(res->start, res->end - res->start + 1, | ||
257 | pdev->name); | ||
258 | if (mem == NULL) | ||
259 | return -EBUSY; | ||
260 | |||
261 | platform_set_drvdata(pdev, mem); | ||
262 | |||
263 | omap_wdt_users = 0; | ||
264 | |||
265 | if (cpu_is_omap16xx()) { | ||
266 | armwdt_ck = clk_get(&pdev->dev, "armwdt_ck"); | ||
267 | if (IS_ERR(armwdt_ck)) { | ||
268 | ret = PTR_ERR(armwdt_ck); | ||
269 | armwdt_ck = NULL; | ||
270 | goto fail; | ||
271 | } | ||
272 | } | ||
273 | |||
274 | if (cpu_is_omap24xx()) { | ||
275 | mpu_wdt_ick = clk_get(&pdev->dev, "mpu_wdt_ick"); | ||
276 | if (IS_ERR(mpu_wdt_ick)) { | ||
277 | ret = PTR_ERR(mpu_wdt_ick); | ||
278 | mpu_wdt_ick = NULL; | ||
279 | goto fail; | ||
280 | } | ||
281 | mpu_wdt_fck = clk_get(&pdev->dev, "mpu_wdt_fck"); | ||
282 | if (IS_ERR(mpu_wdt_fck)) { | ||
283 | ret = PTR_ERR(mpu_wdt_fck); | ||
284 | mpu_wdt_fck = NULL; | ||
285 | goto fail; | ||
286 | } | ||
287 | } | ||
288 | |||
289 | omap_wdt_disable(); | ||
290 | omap_wdt_adjust_timeout(timer_margin); | ||
291 | |||
292 | omap_wdt_miscdev.parent = &pdev->dev; | ||
293 | ret = misc_register(&omap_wdt_miscdev); | ||
294 | if (ret) | ||
295 | goto fail; | ||
296 | |||
297 | pr_info("OMAP Watchdog Timer: initial timeout %d sec\n", timer_margin); | ||
298 | |||
299 | /* autogate OCP interface clock */ | ||
300 | omap_writel(0x01, OMAP_WATCHDOG_SYS_CONFIG); | ||
301 | return 0; | ||
302 | |||
303 | fail: | ||
304 | if (armwdt_ck) | ||
305 | clk_put(armwdt_ck); | ||
306 | if (mpu_wdt_ick) | ||
307 | clk_put(mpu_wdt_ick); | ||
308 | if (mpu_wdt_fck) | ||
309 | clk_put(mpu_wdt_fck); | ||
310 | release_resource(mem); | ||
311 | return ret; | ||
312 | } | ||
313 | |||
314 | static void omap_wdt_shutdown(struct platform_device *pdev) | ||
315 | { | ||
316 | omap_wdt_disable(); | ||
317 | } | ||
318 | |||
319 | static int omap_wdt_remove(struct platform_device *pdev) | ||
320 | { | ||
321 | struct resource *mem = platform_get_drvdata(pdev); | ||
322 | misc_deregister(&omap_wdt_miscdev); | ||
323 | release_resource(mem); | ||
324 | if (armwdt_ck) | ||
325 | clk_put(armwdt_ck); | ||
326 | if (mpu_wdt_ick) | ||
327 | clk_put(mpu_wdt_ick); | ||
328 | if (mpu_wdt_fck) | ||
329 | clk_put(mpu_wdt_fck); | ||
330 | return 0; | ||
331 | } | ||
332 | |||
333 | #ifdef CONFIG_PM | ||
334 | |||
335 | /* REVISIT ... not clear this is the best way to handle system suspend; and | ||
336 | * it's very inappropriate for selective device suspend (e.g. suspending this | ||
337 | * through sysfs rather than by stopping the watchdog daemon). Also, this | ||
338 | * may not play well enough with NOWAYOUT... | ||
339 | */ | ||
340 | |||
341 | static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state) | ||
342 | { | ||
343 | if (omap_wdt_users) | ||
344 | omap_wdt_disable(); | ||
345 | return 0; | ||
346 | } | ||
347 | |||
348 | static int omap_wdt_resume(struct platform_device *pdev) | ||
349 | { | ||
350 | if (omap_wdt_users) { | ||
351 | omap_wdt_enable(); | ||
352 | omap_wdt_ping(); | ||
353 | } | ||
354 | return 0; | ||
355 | } | ||
356 | |||
357 | #else | ||
358 | #define omap_wdt_suspend NULL | ||
359 | #define omap_wdt_resume NULL | ||
360 | #endif | ||
361 | |||
362 | static struct platform_driver omap_wdt_driver = { | ||
363 | .probe = omap_wdt_probe, | ||
364 | .remove = omap_wdt_remove, | ||
365 | .shutdown = omap_wdt_shutdown, | ||
366 | .suspend = omap_wdt_suspend, | ||
367 | .resume = omap_wdt_resume, | ||
368 | .driver = { | ||
369 | .owner = THIS_MODULE, | ||
370 | .name = "omap_wdt", | ||
371 | }, | ||
372 | }; | ||
373 | |||
374 | static int __init omap_wdt_init(void) | ||
375 | { | ||
376 | return platform_driver_register(&omap_wdt_driver); | ||
377 | } | ||
378 | |||
379 | static void __exit omap_wdt_exit(void) | ||
380 | { | ||
381 | platform_driver_unregister(&omap_wdt_driver); | ||
382 | } | ||
383 | |||
384 | module_init(omap_wdt_init); | ||
385 | module_exit(omap_wdt_exit); | ||
386 | |||
387 | MODULE_AUTHOR("George G. Davis"); | ||
388 | MODULE_LICENSE("GPL"); | ||
389 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/omap_wdt.h b/drivers/watchdog/omap_wdt.h new file mode 100644 index 000000000000..52a532a5114a --- /dev/null +++ b/drivers/watchdog/omap_wdt.h | |||
@@ -0,0 +1,64 @@ | |||
1 | /* | ||
2 | * linux/drivers/char/watchdog/omap_wdt.h | ||
3 | * | ||
4 | * BRIEF MODULE DESCRIPTION | ||
5 | * OMAP Watchdog timer register definitions | ||
6 | * | ||
7 | * Copyright (C) 2004 Texas Instruments. | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify it | ||
10 | * under the terms of the GNU General Public License as published by the | ||
11 | * Free Software Foundation; either version 2 of the License, or (at your | ||
12 | * option) any later version. | ||
13 | * | ||
14 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED | ||
15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||
16 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN | ||
17 | * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | ||
19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | ||
20 | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
21 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||
23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
24 | * | ||
25 | * You should have received a copy of the GNU General Public License along | ||
26 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
27 | * 675 Mass Ave, Cambridge, MA 02139, USA. | ||
28 | */ | ||
29 | |||
30 | #ifndef _OMAP_WATCHDOG_H | ||
31 | #define _OMAP_WATCHDOG_H | ||
32 | |||
33 | #define OMAP1610_WATCHDOG_BASE 0xfffeb000 | ||
34 | #define OMAP2420_WATCHDOG_BASE 0x48022000 /*WDT Timer 2 */ | ||
35 | |||
36 | #ifdef CONFIG_ARCH_OMAP24XX | ||
37 | #define OMAP_WATCHDOG_BASE OMAP2420_WATCHDOG_BASE | ||
38 | #else | ||
39 | #define OMAP_WATCHDOG_BASE OMAP1610_WATCHDOG_BASE | ||
40 | #define RM_RSTST_WKUP 0 | ||
41 | #endif | ||
42 | |||
43 | #define OMAP_WATCHDOG_REV (OMAP_WATCHDOG_BASE + 0x00) | ||
44 | #define OMAP_WATCHDOG_SYS_CONFIG (OMAP_WATCHDOG_BASE + 0x10) | ||
45 | #define OMAP_WATCHDOG_STATUS (OMAP_WATCHDOG_BASE + 0x14) | ||
46 | #define OMAP_WATCHDOG_CNTRL (OMAP_WATCHDOG_BASE + 0x24) | ||
47 | #define OMAP_WATCHDOG_CRR (OMAP_WATCHDOG_BASE + 0x28) | ||
48 | #define OMAP_WATCHDOG_LDR (OMAP_WATCHDOG_BASE + 0x2c) | ||
49 | #define OMAP_WATCHDOG_TGR (OMAP_WATCHDOG_BASE + 0x30) | ||
50 | #define OMAP_WATCHDOG_WPS (OMAP_WATCHDOG_BASE + 0x34) | ||
51 | #define OMAP_WATCHDOG_SPR (OMAP_WATCHDOG_BASE + 0x48) | ||
52 | |||
53 | /* Using the prescaler, the OMAP watchdog could go for many | ||
54 | * months before firing. These limits work without scaling, | ||
55 | * with the 60 second default assumed by most tools and docs. | ||
56 | */ | ||
57 | #define TIMER_MARGIN_MAX (24 * 60 * 60) /* 1 day */ | ||
58 | #define TIMER_MARGIN_DEFAULT 60 /* 60 secs */ | ||
59 | #define TIMER_MARGIN_MIN 1 | ||
60 | |||
61 | #define PTV 0 /* prescale */ | ||
62 | #define GET_WLDR_VAL(secs) (0xffffffff - ((secs) * (32768/(1<<PTV))) + 1) | ||
63 | |||
64 | #endif /* _OMAP_WATCHDOG_H */ | ||
diff --git a/drivers/watchdog/pc87413_wdt.c b/drivers/watchdog/pc87413_wdt.c new file mode 100644 index 000000000000..3d3deae0d64b --- /dev/null +++ b/drivers/watchdog/pc87413_wdt.c | |||
@@ -0,0 +1,635 @@ | |||
1 | /* | ||
2 | * NS pc87413-wdt Watchdog Timer driver for Linux 2.6.x.x | ||
3 | * | ||
4 | * This code is based on wdt.c with original copyright. | ||
5 | * | ||
6 | * (C) Copyright 2006 Sven Anders, <anders@anduras.de> | ||
7 | * and Marcus Junker, <junker@anduras.de> | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or | ||
10 | * modify it under the terms of the GNU General Public License | ||
11 | * as published by the Free Software Foundation; either version | ||
12 | * 2 of the License, or (at your option) any later version. | ||
13 | * | ||
14 | * Neither Sven Anders, Marcus Junker nor ANDURAS AG | ||
15 | * admit liability nor provide warranty for any of this software. | ||
16 | * This material is provided "AS-IS" and at no charge. | ||
17 | * | ||
18 | * Release 1.1 | ||
19 | */ | ||
20 | |||
21 | #include <linux/module.h> | ||
22 | #include <linux/types.h> | ||
23 | #include <linux/miscdevice.h> | ||
24 | #include <linux/watchdog.h> | ||
25 | #include <linux/ioport.h> | ||
26 | #include <linux/delay.h> | ||
27 | #include <linux/notifier.h> | ||
28 | #include <linux/fs.h> | ||
29 | #include <linux/reboot.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/spinlock.h> | ||
32 | #include <linux/moduleparam.h> | ||
33 | #include <linux/version.h> | ||
34 | |||
35 | #include <asm/io.h> | ||
36 | #include <asm/uaccess.h> | ||
37 | #include <asm/system.h> | ||
38 | |||
39 | /* #define DEBUG 1 */ | ||
40 | |||
41 | #define DEFAULT_TIMEOUT 1 /* 1 minute */ | ||
42 | #define MAX_TIMEOUT 255 | ||
43 | |||
44 | #define VERSION "1.1" | ||
45 | #define MODNAME "pc87413 WDT" | ||
46 | #define PFX MODNAME ": " | ||
47 | #define DPFX MODNAME " - DEBUG: " | ||
48 | |||
49 | #define WDT_INDEX_IO_PORT (io+0) /* I/O port base (index register) */ | ||
50 | #define WDT_DATA_IO_PORT (WDT_INDEX_IO_PORT+1) | ||
51 | #define SWC_LDN 0x04 | ||
52 | #define SIOCFG2 0x22 /* Serial IO register */ | ||
53 | #define WDCTL 0x10 /* Watchdog-Timer-Controll-Register */ | ||
54 | #define WDTO 0x11 /* Watchdog timeout register */ | ||
55 | #define WDCFG 0x12 /* Watchdog config register */ | ||
56 | |||
57 | static int io = 0x2E; /* Address used on Portwell Boards */ | ||
58 | |||
59 | static int timeout = DEFAULT_TIMEOUT; /* timeout value */ | ||
60 | static unsigned long timer_enabled = 0; /* is the timer enabled? */ | ||
61 | |||
62 | static char expect_close; /* is the close expected? */ | ||
63 | |||
64 | static spinlock_t io_lock; /* to guard the watchdog from io races */ | ||
65 | |||
66 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
67 | |||
68 | /* -- Low level function ----------------------------------------*/ | ||
69 | |||
70 | /* Select pins for Watchdog output */ | ||
71 | |||
72 | static inline void pc87413_select_wdt_out (void) | ||
73 | { | ||
74 | unsigned int cr_data = 0; | ||
75 | |||
76 | /* Step 1: Select multiple pin,pin55,as WDT output */ | ||
77 | |||
78 | outb_p(SIOCFG2, WDT_INDEX_IO_PORT); | ||
79 | |||
80 | cr_data = inb (WDT_DATA_IO_PORT); | ||
81 | |||
82 | cr_data |= 0x80; /* Set Bit7 to 1*/ | ||
83 | outb_p(SIOCFG2, WDT_INDEX_IO_PORT); | ||
84 | |||
85 | outb_p(cr_data, WDT_DATA_IO_PORT); | ||
86 | |||
87 | #ifdef DEBUG | ||
88 | printk(KERN_INFO DPFX "Select multiple pin,pin55,as WDT output:" | ||
89 | " Bit7 to 1: %d\n", cr_data); | ||
90 | #endif | ||
91 | } | ||
92 | |||
93 | /* Enable SWC functions */ | ||
94 | |||
95 | static inline void pc87413_enable_swc(void) | ||
96 | { | ||
97 | unsigned int cr_data=0; | ||
98 | |||
99 | /* Step 2: Enable SWC functions */ | ||
100 | |||
101 | outb_p(0x07, WDT_INDEX_IO_PORT); /* Point SWC_LDN (LDN=4) */ | ||
102 | outb_p(SWC_LDN, WDT_DATA_IO_PORT); | ||
103 | |||
104 | outb_p(0x30, WDT_INDEX_IO_PORT); /* Read Index 0x30 First */ | ||
105 | cr_data = inb(WDT_DATA_IO_PORT); | ||
106 | cr_data |= 0x01; /* Set Bit0 to 1 */ | ||
107 | outb_p(0x30, WDT_INDEX_IO_PORT); | ||
108 | outb_p(cr_data, WDT_DATA_IO_PORT); /* Index0x30_bit0P1 */ | ||
109 | |||
110 | #ifdef DEBUG | ||
111 | printk(KERN_INFO DPFX "pc87413 - Enable SWC functions\n"); | ||
112 | #endif | ||
113 | } | ||
114 | |||
115 | /* Read SWC I/O base address */ | ||
116 | |||
117 | static inline unsigned int pc87413_get_swc_base(void) | ||
118 | { | ||
119 | unsigned int swc_base_addr = 0; | ||
120 | unsigned char addr_l, addr_h = 0; | ||
121 | |||
122 | /* Step 3: Read SWC I/O Base Address */ | ||
123 | |||
124 | outb_p(0x60, WDT_INDEX_IO_PORT); /* Read Index 0x60 */ | ||
125 | addr_h = inb(WDT_DATA_IO_PORT); | ||
126 | |||
127 | outb_p(0x61, WDT_INDEX_IO_PORT); /* Read Index 0x61 */ | ||
128 | |||
129 | addr_l = inb(WDT_DATA_IO_PORT); | ||
130 | |||
131 | swc_base_addr = (addr_h << 8) + addr_l; | ||
132 | |||
133 | #ifdef DEBUG | ||
134 | printk(KERN_INFO DPFX "Read SWC I/O Base Address: low %d, high %d," | ||
135 | " res %d\n", addr_l, addr_h, swc_base_addr); | ||
136 | #endif | ||
137 | |||
138 | return swc_base_addr; | ||
139 | } | ||
140 | |||
141 | /* Select Bank 3 of SWC */ | ||
142 | |||
143 | static inline void pc87413_swc_bank3(unsigned int swc_base_addr) | ||
144 | { | ||
145 | /* Step 4: Select Bank3 of SWC */ | ||
146 | |||
147 | outb_p(inb(swc_base_addr + 0x0f) | 0x03, swc_base_addr + 0x0f); | ||
148 | |||
149 | #ifdef DEBUG | ||
150 | printk(KERN_INFO DPFX "Select Bank3 of SWC\n"); | ||
151 | #endif | ||
152 | } | ||
153 | |||
154 | /* Set watchdog timeout to x minutes */ | ||
155 | |||
156 | static inline void pc87413_programm_wdto(unsigned int swc_base_addr, | ||
157 | char pc87413_time) | ||
158 | { | ||
159 | /* Step 5: Programm WDTO, Twd. */ | ||
160 | |||
161 | outb_p(pc87413_time, swc_base_addr + WDTO); | ||
162 | |||
163 | #ifdef DEBUG | ||
164 | printk(KERN_INFO DPFX "Set WDTO to %d minutes\n", pc87413_time); | ||
165 | #endif | ||
166 | } | ||
167 | |||
168 | /* Enable WDEN */ | ||
169 | |||
170 | static inline void pc87413_enable_wden(unsigned int swc_base_addr) | ||
171 | { | ||
172 | /* Step 6: Enable WDEN */ | ||
173 | |||
174 | outb_p(inb (swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL); | ||
175 | |||
176 | #ifdef DEBUG | ||
177 | printk(KERN_INFO DPFX "Enable WDEN\n"); | ||
178 | #endif | ||
179 | } | ||
180 | |||
181 | /* Enable SW_WD_TREN */ | ||
182 | static inline void pc87413_enable_sw_wd_tren(unsigned int swc_base_addr) | ||
183 | { | ||
184 | /* Enable SW_WD_TREN */ | ||
185 | |||
186 | outb_p(inb (swc_base_addr + WDCFG) | 0x80, swc_base_addr + WDCFG); | ||
187 | |||
188 | #ifdef DEBUG | ||
189 | printk(KERN_INFO DPFX "Enable SW_WD_TREN\n"); | ||
190 | #endif | ||
191 | } | ||
192 | |||
193 | /* Disable SW_WD_TREN */ | ||
194 | |||
195 | static inline void pc87413_disable_sw_wd_tren(unsigned int swc_base_addr) | ||
196 | { | ||
197 | /* Disable SW_WD_TREN */ | ||
198 | |||
199 | outb_p(inb (swc_base_addr + WDCFG) & 0x7f, swc_base_addr + WDCFG); | ||
200 | |||
201 | #ifdef DEBUG | ||
202 | printk(KERN_INFO DPFX "pc87413 - Disable SW_WD_TREN\n"); | ||
203 | #endif | ||
204 | } | ||
205 | |||
206 | /* Enable SW_WD_TRG */ | ||
207 | |||
208 | static inline void pc87413_enable_sw_wd_trg(unsigned int swc_base_addr) | ||
209 | { | ||
210 | /* Enable SW_WD_TRG */ | ||
211 | |||
212 | outb_p(inb (swc_base_addr + WDCTL) | 0x80, swc_base_addr + WDCTL); | ||
213 | |||
214 | #ifdef DEBUG | ||
215 | printk(KERN_INFO DPFX "pc87413 - Enable SW_WD_TRG\n"); | ||
216 | #endif | ||
217 | } | ||
218 | |||
219 | /* Disable SW_WD_TRG */ | ||
220 | |||
221 | static inline void pc87413_disable_sw_wd_trg(unsigned int swc_base_addr) | ||
222 | { | ||
223 | /* Disable SW_WD_TRG */ | ||
224 | |||
225 | outb_p(inb (swc_base_addr + WDCTL) & 0x7f, swc_base_addr + WDCTL); | ||
226 | |||
227 | #ifdef DEBUG | ||
228 | printk(KERN_INFO DPFX "Disable SW_WD_TRG\n"); | ||
229 | #endif | ||
230 | } | ||
231 | |||
232 | /* -- Higher level functions ------------------------------------*/ | ||
233 | |||
234 | /* Enable the watchdog */ | ||
235 | |||
236 | static void pc87413_enable(void) | ||
237 | { | ||
238 | unsigned int swc_base_addr; | ||
239 | |||
240 | spin_lock(&io_lock); | ||
241 | |||
242 | pc87413_select_wdt_out(); | ||
243 | pc87413_enable_swc(); | ||
244 | swc_base_addr = pc87413_get_swc_base(); | ||
245 | pc87413_swc_bank3(swc_base_addr); | ||
246 | pc87413_programm_wdto(swc_base_addr, timeout); | ||
247 | pc87413_enable_wden(swc_base_addr); | ||
248 | pc87413_enable_sw_wd_tren(swc_base_addr); | ||
249 | pc87413_enable_sw_wd_trg(swc_base_addr); | ||
250 | |||
251 | spin_unlock(&io_lock); | ||
252 | } | ||
253 | |||
254 | /* Disable the watchdog */ | ||
255 | |||
256 | static void pc87413_disable(void) | ||
257 | { | ||
258 | unsigned int swc_base_addr; | ||
259 | |||
260 | spin_lock(&io_lock); | ||
261 | |||
262 | pc87413_select_wdt_out(); | ||
263 | pc87413_enable_swc(); | ||
264 | swc_base_addr = pc87413_get_swc_base(); | ||
265 | pc87413_swc_bank3(swc_base_addr); | ||
266 | pc87413_disable_sw_wd_tren(swc_base_addr); | ||
267 | pc87413_disable_sw_wd_trg(swc_base_addr); | ||
268 | pc87413_programm_wdto(swc_base_addr, 0); | ||
269 | |||
270 | spin_unlock(&io_lock); | ||
271 | } | ||
272 | |||
273 | /* Refresh the watchdog */ | ||
274 | |||
275 | static void pc87413_refresh(void) | ||
276 | { | ||
277 | unsigned int swc_base_addr; | ||
278 | |||
279 | spin_lock(&io_lock); | ||
280 | |||
281 | pc87413_select_wdt_out(); | ||
282 | pc87413_enable_swc(); | ||
283 | swc_base_addr = pc87413_get_swc_base(); | ||
284 | pc87413_swc_bank3(swc_base_addr); | ||
285 | pc87413_disable_sw_wd_tren(swc_base_addr); | ||
286 | pc87413_disable_sw_wd_trg(swc_base_addr); | ||
287 | pc87413_programm_wdto(swc_base_addr, timeout); | ||
288 | pc87413_enable_wden(swc_base_addr); | ||
289 | pc87413_enable_sw_wd_tren(swc_base_addr); | ||
290 | pc87413_enable_sw_wd_trg(swc_base_addr); | ||
291 | |||
292 | spin_unlock(&io_lock); | ||
293 | } | ||
294 | |||
295 | /* -- File operations -------------------------------------------*/ | ||
296 | |||
297 | /** | ||
298 | * pc87413_open: | ||
299 | * @inode: inode of device | ||
300 | * @file: file handle to device | ||
301 | * | ||
302 | */ | ||
303 | |||
304 | static int pc87413_open(struct inode *inode, struct file *file) | ||
305 | { | ||
306 | /* /dev/watchdog can only be opened once */ | ||
307 | |||
308 | if (test_and_set_bit(0, &timer_enabled)) | ||
309 | return -EBUSY; | ||
310 | |||
311 | if (nowayout) | ||
312 | __module_get(THIS_MODULE); | ||
313 | |||
314 | /* Reload and activate timer */ | ||
315 | pc87413_refresh(); | ||
316 | |||
317 | printk(KERN_INFO MODNAME "Watchdog enabled. Timeout set to" | ||
318 | " %d minute(s).\n", timeout); | ||
319 | |||
320 | return nonseekable_open(inode, file); | ||
321 | } | ||
322 | |||
323 | /** | ||
324 | * pc87413_release: | ||
325 | * @inode: inode to board | ||
326 | * @file: file handle to board | ||
327 | * | ||
328 | * The watchdog has a configurable API. There is a religious dispute | ||
329 | * between people who want their watchdog to be able to shut down and | ||
330 | * those who want to be sure if the watchdog manager dies the machine | ||
331 | * reboots. In the former case we disable the counters, in the latter | ||
332 | * case you have to open it again very soon. | ||
333 | */ | ||
334 | |||
335 | static int pc87413_release(struct inode *inode, struct file *file) | ||
336 | { | ||
337 | /* Shut off the timer. */ | ||
338 | |||
339 | if (expect_close == 42) { | ||
340 | pc87413_disable(); | ||
341 | printk(KERN_INFO MODNAME "Watchdog disabled," | ||
342 | " sleeping again...\n"); | ||
343 | } else { | ||
344 | printk(KERN_CRIT MODNAME "Unexpected close, not stopping" | ||
345 | " watchdog!\n"); | ||
346 | pc87413_refresh(); | ||
347 | } | ||
348 | |||
349 | clear_bit(0, &timer_enabled); | ||
350 | expect_close = 0; | ||
351 | |||
352 | return 0; | ||
353 | } | ||
354 | |||
355 | /** | ||
356 | * pc87413_status: | ||
357 | * | ||
358 | * return, if the watchdog is enabled (timeout is set...) | ||
359 | */ | ||
360 | |||
361 | |||
362 | static int pc87413_status(void) | ||
363 | { | ||
364 | return 0; /* currently not supported */ | ||
365 | } | ||
366 | |||
367 | /** | ||
368 | * pc87413_write: | ||
369 | * @file: file handle to the watchdog | ||
370 | * @data: data buffer to write | ||
371 | * @len: length in bytes | ||
372 | * @ppos: pointer to the position to write. No seeks allowed | ||
373 | * | ||
374 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
375 | * write of data will do, as we we don't define content meaning. | ||
376 | */ | ||
377 | |||
378 | static ssize_t pc87413_write(struct file *file, const char __user *data, | ||
379 | size_t len, loff_t *ppos) | ||
380 | { | ||
381 | /* See if we got the magic character 'V' and reload the timer */ | ||
382 | if (len) { | ||
383 | if (!nowayout) { | ||
384 | size_t i; | ||
385 | |||
386 | /* reset expect flag */ | ||
387 | expect_close = 0; | ||
388 | |||
389 | /* scan to see whether or not we got the magic character */ | ||
390 | for (i = 0; i != len; i++) { | ||
391 | char c; | ||
392 | if (get_user(c, data+i)) | ||
393 | return -EFAULT; | ||
394 | if (c == 'V') | ||
395 | expect_close = 42; | ||
396 | } | ||
397 | } | ||
398 | |||
399 | /* someone wrote to us, we should reload the timer */ | ||
400 | pc87413_refresh(); | ||
401 | } | ||
402 | return len; | ||
403 | } | ||
404 | |||
405 | /** | ||
406 | * pc87413_ioctl: | ||
407 | * @inode: inode of the device | ||
408 | * @file: file handle to the device | ||
409 | * @cmd: watchdog command | ||
410 | * @arg: argument pointer | ||
411 | * | ||
412 | * The watchdog API defines a common set of functions for all watchdogs | ||
413 | * according to their available features. We only actually usefully support | ||
414 | * querying capabilities and current status. | ||
415 | */ | ||
416 | |||
417 | static int pc87413_ioctl(struct inode *inode, struct file *file, | ||
418 | unsigned int cmd, unsigned long arg) | ||
419 | { | ||
420 | int new_timeout; | ||
421 | |||
422 | union { | ||
423 | struct watchdog_info __user *ident; | ||
424 | int __user *i; | ||
425 | } uarg; | ||
426 | |||
427 | static struct watchdog_info ident = { | ||
428 | .options = WDIOF_KEEPALIVEPING | | ||
429 | WDIOF_SETTIMEOUT | | ||
430 | WDIOF_MAGICCLOSE, | ||
431 | .firmware_version = 1, | ||
432 | .identity = "PC87413(HF/F) watchdog" | ||
433 | }; | ||
434 | |||
435 | uarg.i = (int __user *)arg; | ||
436 | |||
437 | switch(cmd) { | ||
438 | default: | ||
439 | return -ENOTTY; | ||
440 | |||
441 | case WDIOC_GETSUPPORT: | ||
442 | return copy_to_user(uarg.ident, &ident, | ||
443 | sizeof(ident)) ? -EFAULT : 0; | ||
444 | |||
445 | case WDIOC_GETSTATUS: | ||
446 | return put_user(pc87413_status(), uarg.i); | ||
447 | |||
448 | case WDIOC_GETBOOTSTATUS: | ||
449 | return put_user(0, uarg.i); | ||
450 | |||
451 | case WDIOC_KEEPALIVE: | ||
452 | pc87413_refresh(); | ||
453 | #ifdef DEBUG | ||
454 | printk(KERN_INFO DPFX "keepalive\n"); | ||
455 | #endif | ||
456 | return 0; | ||
457 | |||
458 | case WDIOC_SETTIMEOUT: | ||
459 | if (get_user(new_timeout, uarg.i)) | ||
460 | return -EFAULT; | ||
461 | |||
462 | // the API states this is given in secs | ||
463 | new_timeout /= 60; | ||
464 | |||
465 | if (new_timeout < 0 || new_timeout > MAX_TIMEOUT) | ||
466 | return -EINVAL; | ||
467 | |||
468 | timeout = new_timeout; | ||
469 | pc87413_refresh(); | ||
470 | |||
471 | // fall through and return the new timeout... | ||
472 | |||
473 | case WDIOC_GETTIMEOUT: | ||
474 | |||
475 | new_timeout = timeout * 60; | ||
476 | |||
477 | return put_user(new_timeout, uarg.i); | ||
478 | |||
479 | case WDIOC_SETOPTIONS: | ||
480 | { | ||
481 | int options, retval = -EINVAL; | ||
482 | |||
483 | if (get_user(options, uarg.i)) | ||
484 | return -EFAULT; | ||
485 | |||
486 | if (options & WDIOS_DISABLECARD) { | ||
487 | pc87413_disable(); | ||
488 | retval = 0; | ||
489 | } | ||
490 | |||
491 | if (options & WDIOS_ENABLECARD) { | ||
492 | pc87413_enable(); | ||
493 | retval = 0; | ||
494 | } | ||
495 | |||
496 | return retval; | ||
497 | } | ||
498 | } | ||
499 | } | ||
500 | |||
501 | /* -- Notifier funtions -----------------------------------------*/ | ||
502 | |||
503 | /** | ||
504 | * notify_sys: | ||
505 | * @this: our notifier block | ||
506 | * @code: the event being reported | ||
507 | * @unused: unused | ||
508 | * | ||
509 | * Our notifier is called on system shutdowns. We want to turn the card | ||
510 | * off at reboot otherwise the machine will reboot again during memory | ||
511 | * test or worse yet during the following fsck. This would suck, in fact | ||
512 | * trust me - if it happens it does suck. | ||
513 | */ | ||
514 | |||
515 | static int pc87413_notify_sys(struct notifier_block *this, | ||
516 | unsigned long code, | ||
517 | void *unused) | ||
518 | { | ||
519 | if (code == SYS_DOWN || code == SYS_HALT) | ||
520 | { | ||
521 | /* Turn the card off */ | ||
522 | pc87413_disable(); | ||
523 | } | ||
524 | return NOTIFY_DONE; | ||
525 | } | ||
526 | |||
527 | /* -- Module's structures ---------------------------------------*/ | ||
528 | |||
529 | static const struct file_operations pc87413_fops = { | ||
530 | .owner = THIS_MODULE, | ||
531 | .llseek = no_llseek, | ||
532 | .write = pc87413_write, | ||
533 | .ioctl = pc87413_ioctl, | ||
534 | .open = pc87413_open, | ||
535 | .release = pc87413_release, | ||
536 | }; | ||
537 | |||
538 | static struct notifier_block pc87413_notifier = | ||
539 | { | ||
540 | .notifier_call = pc87413_notify_sys, | ||
541 | }; | ||
542 | |||
543 | static struct miscdevice pc87413_miscdev= | ||
544 | { | ||
545 | .minor = WATCHDOG_MINOR, | ||
546 | .name = "watchdog", | ||
547 | .fops = &pc87413_fops | ||
548 | }; | ||
549 | |||
550 | /* -- Module init functions -------------------------------------*/ | ||
551 | |||
552 | /** | ||
553 | * pc87413_init: module's "constructor" | ||
554 | * | ||
555 | * Set up the WDT watchdog board. All we have to do is grab the | ||
556 | * resources we require and bitch if anyone beat us to them. | ||
557 | * The open() function will actually kick the board off. | ||
558 | */ | ||
559 | |||
560 | static int __init pc87413_init(void) | ||
561 | { | ||
562 | int ret; | ||
563 | |||
564 | spin_lock_init(&io_lock); | ||
565 | |||
566 | printk(KERN_INFO PFX "Version " VERSION " at io 0x%X\n", WDT_INDEX_IO_PORT); | ||
567 | |||
568 | /* request_region(io, 2, "pc87413"); */ | ||
569 | |||
570 | ret = register_reboot_notifier(&pc87413_notifier); | ||
571 | if (ret != 0) { | ||
572 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
573 | ret); | ||
574 | } | ||
575 | |||
576 | ret = misc_register(&pc87413_miscdev); | ||
577 | |||
578 | if (ret != 0) { | ||
579 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
580 | WATCHDOG_MINOR, ret); | ||
581 | unregister_reboot_notifier(&pc87413_notifier); | ||
582 | return ret; | ||
583 | } | ||
584 | |||
585 | printk(KERN_INFO PFX "initialized. timeout=%d min \n", timeout); | ||
586 | |||
587 | pc87413_enable(); | ||
588 | |||
589 | return 0; | ||
590 | } | ||
591 | |||
592 | /** | ||
593 | * pc87413_exit: module's "destructor" | ||
594 | * | ||
595 | * Unload the watchdog. You cannot do this with any file handles open. | ||
596 | * If your watchdog is set to continue ticking on close and you unload | ||
597 | * it, well it keeps ticking. We won't get the interrupt but the board | ||
598 | * will not touch PC memory so all is fine. You just have to load a new | ||
599 | * module in 60 seconds or reboot. | ||
600 | */ | ||
601 | |||
602 | static void __exit pc87413_exit(void) | ||
603 | { | ||
604 | /* Stop the timer before we leave */ | ||
605 | if (!nowayout) | ||
606 | { | ||
607 | pc87413_disable(); | ||
608 | printk(KERN_INFO MODNAME "Watchdog disabled.\n"); | ||
609 | } | ||
610 | |||
611 | misc_deregister(&pc87413_miscdev); | ||
612 | unregister_reboot_notifier(&pc87413_notifier); | ||
613 | /* release_region(io,2); */ | ||
614 | |||
615 | printk(MODNAME " watchdog component driver removed.\n"); | ||
616 | } | ||
617 | |||
618 | module_init(pc87413_init); | ||
619 | module_exit(pc87413_exit); | ||
620 | |||
621 | MODULE_AUTHOR("Sven Anders <anders@anduras.de>, Marcus Junker <junker@anduras.de>,"); | ||
622 | MODULE_DESCRIPTION("PC87413 WDT driver"); | ||
623 | MODULE_LICENSE("GPL"); | ||
624 | |||
625 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
626 | |||
627 | module_param(io, int, 0); | ||
628 | MODULE_PARM_DESC(io, MODNAME " I/O port (default: " __MODULE_STRING(io) ")."); | ||
629 | |||
630 | module_param(timeout, int, 0); | ||
631 | MODULE_PARM_DESC(timeout, "Watchdog timeout in minutes (default=" __MODULE_STRING(timeout) ")."); | ||
632 | |||
633 | module_param(nowayout, int, 0); | ||
634 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
635 | |||
diff --git a/drivers/watchdog/pcwd.c b/drivers/watchdog/pcwd.c new file mode 100644 index 000000000000..7b41434fac8c --- /dev/null +++ b/drivers/watchdog/pcwd.c | |||
@@ -0,0 +1,1013 @@ | |||
1 | /* | ||
2 | * PC Watchdog Driver | ||
3 | * by Ken Hollis (khollis@bitgate.com) | ||
4 | * | ||
5 | * Permission granted from Simon Machell (smachell@berkprod.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> /* For module specific items */ | ||
53 | #include <linux/moduleparam.h> /* For new moduleparam's */ | ||
54 | #include <linux/types.h> /* For standard types (like size_t) */ | ||
55 | #include <linux/errno.h> /* For the -ENODEV/... values */ | ||
56 | #include <linux/kernel.h> /* For printk/panic/... */ | ||
57 | #include <linux/delay.h> /* For mdelay function */ | ||
58 | #include <linux/timer.h> /* For timer related operations */ | ||
59 | #include <linux/jiffies.h> /* For jiffies stuff */ | ||
60 | #include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ | ||
61 | #include <linux/watchdog.h> /* For the watchdog specific items */ | ||
62 | #include <linux/reboot.h> /* For kernel_power_off() */ | ||
63 | #include <linux/init.h> /* For __init/__exit/... */ | ||
64 | #include <linux/fs.h> /* For file operations */ | ||
65 | #include <linux/isa.h> /* For isa devices */ | ||
66 | #include <linux/ioport.h> /* For io-port access */ | ||
67 | #include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ | ||
68 | |||
69 | #include <asm/uaccess.h> /* For copy_to_user/put_user/... */ | ||
70 | #include <asm/io.h> /* For inb/outb/... */ | ||
71 | |||
72 | /* Module and version information */ | ||
73 | #define WATCHDOG_VERSION "1.20" | ||
74 | #define WATCHDOG_DATE "18 Feb 2007" | ||
75 | #define WATCHDOG_DRIVER_NAME "ISA-PC Watchdog" | ||
76 | #define WATCHDOG_NAME "pcwd" | ||
77 | #define PFX WATCHDOG_NAME ": " | ||
78 | #define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION " (" WATCHDOG_DATE ")\n" | ||
79 | #define WD_VER WATCHDOG_VERSION " (" WATCHDOG_DATE ")" | ||
80 | |||
81 | /* | ||
82 | * It should be noted that PCWD_REVISION_B was removed because A and B | ||
83 | * are essentially the same types of card, with the exception that B | ||
84 | * has temperature reporting. Since I didn't receive a Rev.B card, | ||
85 | * the Rev.B card is not supported. (It's a good thing too, as they | ||
86 | * are no longer in production.) | ||
87 | */ | ||
88 | #define PCWD_REVISION_A 1 | ||
89 | #define PCWD_REVISION_C 2 | ||
90 | |||
91 | /* | ||
92 | * These are the auto-probe addresses available. | ||
93 | * | ||
94 | * Revision A only uses ports 0x270 and 0x370. Revision C introduced 0x350. | ||
95 | * Revision A has an address range of 2 addresses, while Revision C has 4. | ||
96 | */ | ||
97 | #define PCWD_ISA_NR_CARDS 3 | ||
98 | static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 }; | ||
99 | |||
100 | /* | ||
101 | * These are the defines that describe the control status bits for the | ||
102 | * PCI-PC Watchdog card. | ||
103 | */ | ||
104 | /* Port 1 : Control Status #1 for the PC Watchdog card, revision A. */ | ||
105 | #define WD_WDRST 0x01 /* Previously reset state */ | ||
106 | #define WD_T110 0x02 /* Temperature overheat sense */ | ||
107 | #define WD_HRTBT 0x04 /* Heartbeat sense */ | ||
108 | #define WD_RLY2 0x08 /* External relay triggered */ | ||
109 | #define WD_SRLY2 0x80 /* Software external relay triggered */ | ||
110 | /* Port 1 : Control Status #1 for the PC Watchdog card, revision C. */ | ||
111 | #define WD_REVC_WTRP 0x01 /* Watchdog Trip status */ | ||
112 | #define WD_REVC_HRBT 0x02 /* Watchdog Heartbeat */ | ||
113 | #define WD_REVC_TTRP 0x04 /* Temperature Trip status */ | ||
114 | #define WD_REVC_RL2A 0x08 /* Relay 2 activated by on-board processor */ | ||
115 | #define WD_REVC_RL1A 0x10 /* Relay 1 active */ | ||
116 | #define WD_REVC_R2DS 0x40 /* Relay 2 disable */ | ||
117 | #define WD_REVC_RLY2 0x80 /* Relay 2 activated? */ | ||
118 | /* Port 2 : Control Status #2 */ | ||
119 | #define WD_WDIS 0x10 /* Watchdog Disabled */ | ||
120 | #define WD_ENTP 0x20 /* Watchdog Enable Temperature Trip */ | ||
121 | #define WD_SSEL 0x40 /* Watchdog Switch Select (1:SW1 <-> 0:SW2) */ | ||
122 | #define WD_WCMD 0x80 /* Watchdog Command Mode */ | ||
123 | |||
124 | /* max. time we give an ISA watchdog card to process a command */ | ||
125 | /* 500ms for each 4 bit response (according to spec.) */ | ||
126 | #define ISA_COMMAND_TIMEOUT 1000 | ||
127 | |||
128 | /* Watchdog's internal commands */ | ||
129 | #define CMD_ISA_IDLE 0x00 | ||
130 | #define CMD_ISA_VERSION_INTEGER 0x01 | ||
131 | #define CMD_ISA_VERSION_TENTH 0x02 | ||
132 | #define CMD_ISA_VERSION_HUNDRETH 0x03 | ||
133 | #define CMD_ISA_VERSION_MINOR 0x04 | ||
134 | #define CMD_ISA_SWITCH_SETTINGS 0x05 | ||
135 | #define CMD_ISA_RESET_PC 0x06 | ||
136 | #define CMD_ISA_ARM_0 0x07 | ||
137 | #define CMD_ISA_ARM_30 0x08 | ||
138 | #define CMD_ISA_ARM_60 0x09 | ||
139 | #define CMD_ISA_DELAY_TIME_2SECS 0x0A | ||
140 | #define CMD_ISA_DELAY_TIME_4SECS 0x0B | ||
141 | #define CMD_ISA_DELAY_TIME_8SECS 0x0C | ||
142 | #define CMD_ISA_RESET_RELAYS 0x0D | ||
143 | |||
144 | /* Watchdog's Dip Switch heartbeat values */ | ||
145 | static const int heartbeat_tbl [] = { | ||
146 | 20, /* OFF-OFF-OFF = 20 Sec */ | ||
147 | 40, /* OFF-OFF-ON = 40 Sec */ | ||
148 | 60, /* OFF-ON-OFF = 1 Min */ | ||
149 | 300, /* OFF-ON-ON = 5 Min */ | ||
150 | 600, /* ON-OFF-OFF = 10 Min */ | ||
151 | 1800, /* ON-OFF-ON = 30 Min */ | ||
152 | 3600, /* ON-ON-OFF = 1 Hour */ | ||
153 | 7200, /* ON-ON-ON = 2 hour */ | ||
154 | }; | ||
155 | |||
156 | /* | ||
157 | * We are using an kernel timer to do the pinging of the watchdog | ||
158 | * every ~500ms. We try to set the internal heartbeat of the | ||
159 | * watchdog to 2 ms. | ||
160 | */ | ||
161 | |||
162 | #define WDT_INTERVAL (HZ/2+1) | ||
163 | |||
164 | /* We can only use 1 card due to the /dev/watchdog restriction */ | ||
165 | static int cards_found; | ||
166 | |||
167 | /* internal variables */ | ||
168 | static atomic_t open_allowed = ATOMIC_INIT(1); | ||
169 | static char expect_close; | ||
170 | static int temp_panic; | ||
171 | static struct { /* this is private data for each ISA-PC watchdog card */ | ||
172 | char fw_ver_str[6]; /* The cards firmware version */ | ||
173 | int revision; /* The card's revision */ | ||
174 | int supports_temp; /* Wether or not the card has a temperature device */ | ||
175 | int command_mode; /* Wether or not the card is in command mode */ | ||
176 | int boot_status; /* The card's boot status */ | ||
177 | int io_addr; /* The cards I/O address */ | ||
178 | spinlock_t io_lock; /* the lock for io operations */ | ||
179 | struct timer_list timer; /* The timer that pings the watchdog */ | ||
180 | unsigned long next_heartbeat; /* the next_heartbeat for the timer */ | ||
181 | } pcwd_private; | ||
182 | |||
183 | /* module parameters */ | ||
184 | #define QUIET 0 /* Default */ | ||
185 | #define VERBOSE 1 /* Verbose */ | ||
186 | #define DEBUG 2 /* print fancy stuff too */ | ||
187 | static int debug = QUIET; | ||
188 | module_param(debug, int, 0); | ||
189 | MODULE_PARM_DESC(debug, "Debug level: 0=Quiet, 1=Verbose, 2=Debug (default=0)"); | ||
190 | |||
191 | #define WATCHDOG_HEARTBEAT 0 /* default heartbeat = delay-time from dip-switches */ | ||
192 | static int heartbeat = WATCHDOG_HEARTBEAT; | ||
193 | module_param(heartbeat, int, 0); | ||
194 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<=heartbeat<=7200 or 0=delay-time from dip-switches, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
195 | |||
196 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
197 | module_param(nowayout, int, 0); | ||
198 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
199 | |||
200 | /* | ||
201 | * Internal functions | ||
202 | */ | ||
203 | |||
204 | static int send_isa_command(int cmd) | ||
205 | { | ||
206 | int i; | ||
207 | int control_status; | ||
208 | int port0, last_port0; /* Double read for stabilising */ | ||
209 | |||
210 | if (debug >= DEBUG) | ||
211 | printk(KERN_DEBUG PFX "sending following data cmd=0x%02x\n", | ||
212 | cmd); | ||
213 | |||
214 | /* The WCMD bit must be 1 and the command is only 4 bits in size */ | ||
215 | control_status = (cmd & 0x0F) | WD_WCMD; | ||
216 | outb_p(control_status, pcwd_private.io_addr + 2); | ||
217 | udelay(ISA_COMMAND_TIMEOUT); | ||
218 | |||
219 | port0 = inb_p(pcwd_private.io_addr); | ||
220 | for (i = 0; i < 25; ++i) { | ||
221 | last_port0 = port0; | ||
222 | port0 = inb_p(pcwd_private.io_addr); | ||
223 | |||
224 | if (port0 == last_port0) | ||
225 | break; /* Data is stable */ | ||
226 | |||
227 | udelay (250); | ||
228 | } | ||
229 | |||
230 | if (debug >= DEBUG) | ||
231 | printk(KERN_DEBUG PFX "received following data for cmd=0x%02x: port0=0x%02x last_port0=0x%02x\n", | ||
232 | cmd, port0, last_port0); | ||
233 | |||
234 | return port0; | ||
235 | } | ||
236 | |||
237 | static int set_command_mode(void) | ||
238 | { | ||
239 | int i, found=0, count=0; | ||
240 | |||
241 | /* Set the card into command mode */ | ||
242 | spin_lock(&pcwd_private.io_lock); | ||
243 | while ((!found) && (count < 3)) { | ||
244 | i = send_isa_command(CMD_ISA_IDLE); | ||
245 | |||
246 | if (i == 0x00) | ||
247 | found = 1; | ||
248 | else if (i == 0xF3) { | ||
249 | /* Card does not like what we've done to it */ | ||
250 | outb_p(0x00, pcwd_private.io_addr + 2); | ||
251 | udelay(1200); /* Spec says wait 1ms */ | ||
252 | outb_p(0x00, pcwd_private.io_addr + 2); | ||
253 | udelay(ISA_COMMAND_TIMEOUT); | ||
254 | } | ||
255 | count++; | ||
256 | } | ||
257 | spin_unlock(&pcwd_private.io_lock); | ||
258 | pcwd_private.command_mode = found; | ||
259 | |||
260 | if (debug >= DEBUG) | ||
261 | printk(KERN_DEBUG PFX "command_mode=%d\n", | ||
262 | pcwd_private.command_mode); | ||
263 | |||
264 | return(found); | ||
265 | } | ||
266 | |||
267 | static void unset_command_mode(void) | ||
268 | { | ||
269 | /* Set the card into normal mode */ | ||
270 | spin_lock(&pcwd_private.io_lock); | ||
271 | outb_p(0x00, pcwd_private.io_addr + 2); | ||
272 | udelay(ISA_COMMAND_TIMEOUT); | ||
273 | spin_unlock(&pcwd_private.io_lock); | ||
274 | |||
275 | pcwd_private.command_mode = 0; | ||
276 | |||
277 | if (debug >= DEBUG) | ||
278 | printk(KERN_DEBUG PFX "command_mode=%d\n", | ||
279 | pcwd_private.command_mode); | ||
280 | } | ||
281 | |||
282 | static inline void pcwd_check_temperature_support(void) | ||
283 | { | ||
284 | if (inb(pcwd_private.io_addr) != 0xF0) | ||
285 | pcwd_private.supports_temp = 1; | ||
286 | } | ||
287 | |||
288 | static inline void pcwd_get_firmware(void) | ||
289 | { | ||
290 | int one, ten, hund, minor; | ||
291 | |||
292 | strcpy(pcwd_private.fw_ver_str, "ERROR"); | ||
293 | |||
294 | if (set_command_mode()) { | ||
295 | one = send_isa_command(CMD_ISA_VERSION_INTEGER); | ||
296 | ten = send_isa_command(CMD_ISA_VERSION_TENTH); | ||
297 | hund = send_isa_command(CMD_ISA_VERSION_HUNDRETH); | ||
298 | minor = send_isa_command(CMD_ISA_VERSION_MINOR); | ||
299 | sprintf(pcwd_private.fw_ver_str, "%c.%c%c%c", one, ten, hund, minor); | ||
300 | } | ||
301 | unset_command_mode(); | ||
302 | |||
303 | return; | ||
304 | } | ||
305 | |||
306 | static inline int pcwd_get_option_switches(void) | ||
307 | { | ||
308 | int option_switches=0; | ||
309 | |||
310 | if (set_command_mode()) { | ||
311 | /* Get switch settings */ | ||
312 | option_switches = send_isa_command(CMD_ISA_SWITCH_SETTINGS); | ||
313 | } | ||
314 | |||
315 | unset_command_mode(); | ||
316 | return(option_switches); | ||
317 | } | ||
318 | |||
319 | static void pcwd_show_card_info(void) | ||
320 | { | ||
321 | int option_switches; | ||
322 | |||
323 | /* Get some extra info from the hardware (in command/debug/diag mode) */ | ||
324 | if (pcwd_private.revision == PCWD_REVISION_A) | ||
325 | printk(KERN_INFO PFX "ISA-PC Watchdog (REV.A) detected at port 0x%04x\n", pcwd_private.io_addr); | ||
326 | else if (pcwd_private.revision == PCWD_REVISION_C) { | ||
327 | pcwd_get_firmware(); | ||
328 | printk(KERN_INFO PFX "ISA-PC Watchdog (REV.C) detected at port 0x%04x (Firmware version: %s)\n", | ||
329 | pcwd_private.io_addr, pcwd_private.fw_ver_str); | ||
330 | option_switches = pcwd_get_option_switches(); | ||
331 | printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", | ||
332 | option_switches, | ||
333 | ((option_switches & 0x10) ? "ON" : "OFF"), | ||
334 | ((option_switches & 0x08) ? "ON" : "OFF")); | ||
335 | |||
336 | /* Reprogram internal heartbeat to 2 seconds */ | ||
337 | if (set_command_mode()) { | ||
338 | send_isa_command(CMD_ISA_DELAY_TIME_2SECS); | ||
339 | unset_command_mode(); | ||
340 | } | ||
341 | } | ||
342 | |||
343 | if (pcwd_private.supports_temp) | ||
344 | printk(KERN_INFO PFX "Temperature Option Detected\n"); | ||
345 | |||
346 | if (pcwd_private.boot_status & WDIOF_CARDRESET) | ||
347 | printk(KERN_INFO PFX "Previous reboot was caused by the card\n"); | ||
348 | |||
349 | if (pcwd_private.boot_status & WDIOF_OVERHEAT) { | ||
350 | printk(KERN_EMERG PFX "Card senses a CPU Overheat. Panicking!\n"); | ||
351 | printk(KERN_EMERG PFX "CPU Overheat\n"); | ||
352 | } | ||
353 | |||
354 | if (pcwd_private.boot_status == 0) | ||
355 | printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n"); | ||
356 | } | ||
357 | |||
358 | static void pcwd_timer_ping(unsigned long data) | ||
359 | { | ||
360 | int wdrst_stat; | ||
361 | |||
362 | /* If we got a heartbeat pulse within the WDT_INTERVAL | ||
363 | * we agree to ping the WDT */ | ||
364 | if(time_before(jiffies, pcwd_private.next_heartbeat)) { | ||
365 | /* Ping the watchdog */ | ||
366 | spin_lock(&pcwd_private.io_lock); | ||
367 | if (pcwd_private.revision == PCWD_REVISION_A) { | ||
368 | /* Rev A cards are reset by setting the WD_WDRST bit in register 1 */ | ||
369 | wdrst_stat = inb_p(pcwd_private.io_addr); | ||
370 | wdrst_stat &= 0x0F; | ||
371 | wdrst_stat |= WD_WDRST; | ||
372 | |||
373 | outb_p(wdrst_stat, pcwd_private.io_addr + 1); | ||
374 | } else { | ||
375 | /* Re-trigger watchdog by writing to port 0 */ | ||
376 | outb_p(0x00, pcwd_private.io_addr); | ||
377 | } | ||
378 | |||
379 | /* Re-set the timer interval */ | ||
380 | mod_timer(&pcwd_private.timer, jiffies + WDT_INTERVAL); | ||
381 | |||
382 | spin_unlock(&pcwd_private.io_lock); | ||
383 | } else { | ||
384 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
385 | } | ||
386 | } | ||
387 | |||
388 | static int pcwd_start(void) | ||
389 | { | ||
390 | int stat_reg; | ||
391 | |||
392 | pcwd_private.next_heartbeat = jiffies + (heartbeat * HZ); | ||
393 | |||
394 | /* Start the timer */ | ||
395 | mod_timer(&pcwd_private.timer, jiffies + WDT_INTERVAL); | ||
396 | |||
397 | /* Enable the port */ | ||
398 | if (pcwd_private.revision == PCWD_REVISION_C) { | ||
399 | spin_lock(&pcwd_private.io_lock); | ||
400 | outb_p(0x00, pcwd_private.io_addr + 3); | ||
401 | udelay(ISA_COMMAND_TIMEOUT); | ||
402 | stat_reg = inb_p(pcwd_private.io_addr + 2); | ||
403 | spin_unlock(&pcwd_private.io_lock); | ||
404 | if (stat_reg & WD_WDIS) { | ||
405 | printk(KERN_INFO PFX "Could not start watchdog\n"); | ||
406 | return -EIO; | ||
407 | } | ||
408 | } | ||
409 | |||
410 | if (debug >= VERBOSE) | ||
411 | printk(KERN_DEBUG PFX "Watchdog started\n"); | ||
412 | |||
413 | return 0; | ||
414 | } | ||
415 | |||
416 | static int pcwd_stop(void) | ||
417 | { | ||
418 | int stat_reg; | ||
419 | |||
420 | /* Stop the timer */ | ||
421 | del_timer(&pcwd_private.timer); | ||
422 | |||
423 | /* Disable the board */ | ||
424 | if (pcwd_private.revision == PCWD_REVISION_C) { | ||
425 | spin_lock(&pcwd_private.io_lock); | ||
426 | outb_p(0xA5, pcwd_private.io_addr + 3); | ||
427 | udelay(ISA_COMMAND_TIMEOUT); | ||
428 | outb_p(0xA5, pcwd_private.io_addr + 3); | ||
429 | udelay(ISA_COMMAND_TIMEOUT); | ||
430 | stat_reg = inb_p(pcwd_private.io_addr + 2); | ||
431 | spin_unlock(&pcwd_private.io_lock); | ||
432 | if ((stat_reg & WD_WDIS) == 0) { | ||
433 | printk(KERN_INFO PFX "Could not stop watchdog\n"); | ||
434 | return -EIO; | ||
435 | } | ||
436 | } | ||
437 | |||
438 | if (debug >= VERBOSE) | ||
439 | printk(KERN_DEBUG PFX "Watchdog stopped\n"); | ||
440 | |||
441 | return 0; | ||
442 | } | ||
443 | |||
444 | static int pcwd_keepalive(void) | ||
445 | { | ||
446 | /* user land ping */ | ||
447 | pcwd_private.next_heartbeat = jiffies + (heartbeat * HZ); | ||
448 | |||
449 | if (debug >= DEBUG) | ||
450 | printk(KERN_DEBUG PFX "Watchdog keepalive signal send\n"); | ||
451 | |||
452 | return 0; | ||
453 | } | ||
454 | |||
455 | static int pcwd_set_heartbeat(int t) | ||
456 | { | ||
457 | if ((t < 2) || (t > 7200)) /* arbitrary upper limit */ | ||
458 | return -EINVAL; | ||
459 | |||
460 | heartbeat = t; | ||
461 | |||
462 | if (debug >= VERBOSE) | ||
463 | printk(KERN_DEBUG PFX "New heartbeat: %d\n", | ||
464 | heartbeat); | ||
465 | |||
466 | return 0; | ||
467 | } | ||
468 | |||
469 | static int pcwd_get_status(int *status) | ||
470 | { | ||
471 | int control_status; | ||
472 | |||
473 | *status=0; | ||
474 | spin_lock(&pcwd_private.io_lock); | ||
475 | if (pcwd_private.revision == PCWD_REVISION_A) | ||
476 | /* Rev A cards return status information from | ||
477 | * the base register, which is used for the | ||
478 | * temperature in other cards. */ | ||
479 | control_status = inb(pcwd_private.io_addr); | ||
480 | else { | ||
481 | /* Rev C cards return card status in the base | ||
482 | * address + 1 register. And use different bits | ||
483 | * to indicate a card initiated reset, and an | ||
484 | * over-temperature condition. And the reboot | ||
485 | * status can be reset. */ | ||
486 | control_status = inb(pcwd_private.io_addr + 1); | ||
487 | } | ||
488 | spin_unlock(&pcwd_private.io_lock); | ||
489 | |||
490 | if (pcwd_private.revision == PCWD_REVISION_A) { | ||
491 | if (control_status & WD_WDRST) | ||
492 | *status |= WDIOF_CARDRESET; | ||
493 | |||
494 | if (control_status & WD_T110) { | ||
495 | *status |= WDIOF_OVERHEAT; | ||
496 | if (temp_panic) { | ||
497 | printk(KERN_INFO PFX "Temperature overheat trip!\n"); | ||
498 | kernel_power_off(); | ||
499 | /* or should we just do a: panic(PFX "Temperature overheat trip!\n"); */ | ||
500 | } | ||
501 | } | ||
502 | } else { | ||
503 | if (control_status & WD_REVC_WTRP) | ||
504 | *status |= WDIOF_CARDRESET; | ||
505 | |||
506 | if (control_status & WD_REVC_TTRP) { | ||
507 | *status |= WDIOF_OVERHEAT; | ||
508 | if (temp_panic) { | ||
509 | printk(KERN_INFO PFX "Temperature overheat trip!\n"); | ||
510 | kernel_power_off(); | ||
511 | /* or should we just do a: panic(PFX "Temperature overheat trip!\n"); */ | ||
512 | } | ||
513 | } | ||
514 | } | ||
515 | |||
516 | return 0; | ||
517 | } | ||
518 | |||
519 | static int pcwd_clear_status(void) | ||
520 | { | ||
521 | int control_status; | ||
522 | |||
523 | if (pcwd_private.revision == PCWD_REVISION_C) { | ||
524 | spin_lock(&pcwd_private.io_lock); | ||
525 | |||
526 | if (debug >= VERBOSE) | ||
527 | printk(KERN_INFO PFX "clearing watchdog trip status\n"); | ||
528 | |||
529 | control_status = inb_p(pcwd_private.io_addr + 1); | ||
530 | |||
531 | if (debug >= DEBUG) { | ||
532 | printk(KERN_DEBUG PFX "status was: 0x%02x\n", control_status); | ||
533 | printk(KERN_DEBUG PFX "sending: 0x%02x\n", | ||
534 | (control_status & WD_REVC_R2DS)); | ||
535 | } | ||
536 | |||
537 | /* clear reset status & Keep Relay 2 disable state as it is */ | ||
538 | outb_p((control_status & WD_REVC_R2DS), pcwd_private.io_addr + 1); | ||
539 | |||
540 | spin_unlock(&pcwd_private.io_lock); | ||
541 | } | ||
542 | return 0; | ||
543 | } | ||
544 | |||
545 | static int pcwd_get_temperature(int *temperature) | ||
546 | { | ||
547 | /* check that port 0 gives temperature info and no command results */ | ||
548 | if (pcwd_private.command_mode) | ||
549 | return -1; | ||
550 | |||
551 | *temperature = 0; | ||
552 | if (!pcwd_private.supports_temp) | ||
553 | return -ENODEV; | ||
554 | |||
555 | /* | ||
556 | * Convert celsius to fahrenheit, since this was | ||
557 | * the decided 'standard' for this return value. | ||
558 | */ | ||
559 | spin_lock(&pcwd_private.io_lock); | ||
560 | *temperature = ((inb(pcwd_private.io_addr)) * 9 / 5) + 32; | ||
561 | spin_unlock(&pcwd_private.io_lock); | ||
562 | |||
563 | if (debug >= DEBUG) { | ||
564 | printk(KERN_DEBUG PFX "temperature is: %d F\n", | ||
565 | *temperature); | ||
566 | } | ||
567 | |||
568 | return 0; | ||
569 | } | ||
570 | |||
571 | /* | ||
572 | * /dev/watchdog handling | ||
573 | */ | ||
574 | |||
575 | static int pcwd_ioctl(struct inode *inode, struct file *file, | ||
576 | unsigned int cmd, unsigned long arg) | ||
577 | { | ||
578 | int rv; | ||
579 | int status; | ||
580 | int temperature; | ||
581 | int new_heartbeat; | ||
582 | int __user *argp = (int __user *)arg; | ||
583 | static struct watchdog_info ident = { | ||
584 | .options = WDIOF_OVERHEAT | | ||
585 | WDIOF_CARDRESET | | ||
586 | WDIOF_KEEPALIVEPING | | ||
587 | WDIOF_SETTIMEOUT | | ||
588 | WDIOF_MAGICCLOSE, | ||
589 | .firmware_version = 1, | ||
590 | .identity = "PCWD", | ||
591 | }; | ||
592 | |||
593 | switch(cmd) { | ||
594 | default: | ||
595 | return -ENOTTY; | ||
596 | |||
597 | case WDIOC_GETSUPPORT: | ||
598 | if(copy_to_user(argp, &ident, sizeof(ident))) | ||
599 | return -EFAULT; | ||
600 | return 0; | ||
601 | |||
602 | case WDIOC_GETSTATUS: | ||
603 | pcwd_get_status(&status); | ||
604 | return put_user(status, argp); | ||
605 | |||
606 | case WDIOC_GETBOOTSTATUS: | ||
607 | return put_user(pcwd_private.boot_status, argp); | ||
608 | |||
609 | case WDIOC_GETTEMP: | ||
610 | if (pcwd_get_temperature(&temperature)) | ||
611 | return -EFAULT; | ||
612 | |||
613 | return put_user(temperature, argp); | ||
614 | |||
615 | case WDIOC_SETOPTIONS: | ||
616 | if (pcwd_private.revision == PCWD_REVISION_C) | ||
617 | { | ||
618 | if(copy_from_user(&rv, argp, sizeof(int))) | ||
619 | return -EFAULT; | ||
620 | |||
621 | if (rv & WDIOS_DISABLECARD) | ||
622 | { | ||
623 | return pcwd_stop(); | ||
624 | } | ||
625 | |||
626 | if (rv & WDIOS_ENABLECARD) | ||
627 | { | ||
628 | return pcwd_start(); | ||
629 | } | ||
630 | |||
631 | if (rv & WDIOS_TEMPPANIC) | ||
632 | { | ||
633 | temp_panic = 1; | ||
634 | } | ||
635 | } | ||
636 | return -EINVAL; | ||
637 | |||
638 | case WDIOC_KEEPALIVE: | ||
639 | pcwd_keepalive(); | ||
640 | return 0; | ||
641 | |||
642 | case WDIOC_SETTIMEOUT: | ||
643 | if (get_user(new_heartbeat, argp)) | ||
644 | return -EFAULT; | ||
645 | |||
646 | if (pcwd_set_heartbeat(new_heartbeat)) | ||
647 | return -EINVAL; | ||
648 | |||
649 | pcwd_keepalive(); | ||
650 | /* Fall */ | ||
651 | |||
652 | case WDIOC_GETTIMEOUT: | ||
653 | return put_user(heartbeat, argp); | ||
654 | } | ||
655 | |||
656 | return 0; | ||
657 | } | ||
658 | |||
659 | static ssize_t pcwd_write(struct file *file, const char __user *buf, size_t len, | ||
660 | loff_t *ppos) | ||
661 | { | ||
662 | if (len) { | ||
663 | if (!nowayout) { | ||
664 | size_t i; | ||
665 | |||
666 | /* In case it was set long ago */ | ||
667 | expect_close = 0; | ||
668 | |||
669 | for (i = 0; i != len; i++) { | ||
670 | char c; | ||
671 | |||
672 | if (get_user(c, buf + i)) | ||
673 | return -EFAULT; | ||
674 | if (c == 'V') | ||
675 | expect_close = 42; | ||
676 | } | ||
677 | } | ||
678 | pcwd_keepalive(); | ||
679 | } | ||
680 | return len; | ||
681 | } | ||
682 | |||
683 | static int pcwd_open(struct inode *inode, struct file *file) | ||
684 | { | ||
685 | if (!atomic_dec_and_test(&open_allowed) ) { | ||
686 | if (debug >= VERBOSE) | ||
687 | printk(KERN_ERR PFX "Attempt to open already opened device.\n"); | ||
688 | atomic_inc( &open_allowed ); | ||
689 | return -EBUSY; | ||
690 | } | ||
691 | |||
692 | if (nowayout) | ||
693 | __module_get(THIS_MODULE); | ||
694 | |||
695 | /* Activate */ | ||
696 | pcwd_start(); | ||
697 | pcwd_keepalive(); | ||
698 | return nonseekable_open(inode, file); | ||
699 | } | ||
700 | |||
701 | static int pcwd_close(struct inode *inode, struct file *file) | ||
702 | { | ||
703 | if (expect_close == 42) { | ||
704 | pcwd_stop(); | ||
705 | } else { | ||
706 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
707 | pcwd_keepalive(); | ||
708 | } | ||
709 | expect_close = 0; | ||
710 | atomic_inc( &open_allowed ); | ||
711 | return 0; | ||
712 | } | ||
713 | |||
714 | /* | ||
715 | * /dev/temperature handling | ||
716 | */ | ||
717 | |||
718 | static ssize_t pcwd_temp_read(struct file *file, char __user *buf, size_t count, | ||
719 | loff_t *ppos) | ||
720 | { | ||
721 | int temperature; | ||
722 | |||
723 | if (pcwd_get_temperature(&temperature)) | ||
724 | return -EFAULT; | ||
725 | |||
726 | if (copy_to_user(buf, &temperature, 1)) | ||
727 | return -EFAULT; | ||
728 | |||
729 | return 1; | ||
730 | } | ||
731 | |||
732 | static int pcwd_temp_open(struct inode *inode, struct file *file) | ||
733 | { | ||
734 | if (!pcwd_private.supports_temp) | ||
735 | return -ENODEV; | ||
736 | |||
737 | return nonseekable_open(inode, file); | ||
738 | } | ||
739 | |||
740 | static int pcwd_temp_close(struct inode *inode, struct file *file) | ||
741 | { | ||
742 | return 0; | ||
743 | } | ||
744 | |||
745 | /* | ||
746 | * Kernel Interfaces | ||
747 | */ | ||
748 | |||
749 | static const struct file_operations pcwd_fops = { | ||
750 | .owner = THIS_MODULE, | ||
751 | .llseek = no_llseek, | ||
752 | .write = pcwd_write, | ||
753 | .ioctl = pcwd_ioctl, | ||
754 | .open = pcwd_open, | ||
755 | .release = pcwd_close, | ||
756 | }; | ||
757 | |||
758 | static struct miscdevice pcwd_miscdev = { | ||
759 | .minor = WATCHDOG_MINOR, | ||
760 | .name = "watchdog", | ||
761 | .fops = &pcwd_fops, | ||
762 | }; | ||
763 | |||
764 | static const struct file_operations pcwd_temp_fops = { | ||
765 | .owner = THIS_MODULE, | ||
766 | .llseek = no_llseek, | ||
767 | .read = pcwd_temp_read, | ||
768 | .open = pcwd_temp_open, | ||
769 | .release = pcwd_temp_close, | ||
770 | }; | ||
771 | |||
772 | static struct miscdevice temp_miscdev = { | ||
773 | .minor = TEMP_MINOR, | ||
774 | .name = "temperature", | ||
775 | .fops = &pcwd_temp_fops, | ||
776 | }; | ||
777 | |||
778 | /* | ||
779 | * Init & exit routines | ||
780 | */ | ||
781 | |||
782 | static inline int get_revision(void) | ||
783 | { | ||
784 | int r = PCWD_REVISION_C; | ||
785 | |||
786 | spin_lock(&pcwd_private.io_lock); | ||
787 | /* REV A cards use only 2 io ports; test | ||
788 | * presumes a floating bus reads as 0xff. */ | ||
789 | if ((inb(pcwd_private.io_addr + 2) == 0xFF) || | ||
790 | (inb(pcwd_private.io_addr + 3) == 0xFF)) | ||
791 | r=PCWD_REVISION_A; | ||
792 | spin_unlock(&pcwd_private.io_lock); | ||
793 | |||
794 | return r; | ||
795 | } | ||
796 | |||
797 | /* | ||
798 | * The ISA cards have a heartbeat bit in one of the registers, which | ||
799 | * register is card dependent. The heartbeat bit is monitored, and if | ||
800 | * found, is considered proof that a Berkshire card has been found. | ||
801 | * The initial rate is once per second at board start up, then twice | ||
802 | * per second for normal operation. | ||
803 | */ | ||
804 | static int __devinit pcwd_isa_match(struct device *dev, unsigned int id) | ||
805 | { | ||
806 | int base_addr=pcwd_ioports[id]; | ||
807 | int port0, last_port0; /* Reg 0, in case it's REV A */ | ||
808 | int port1, last_port1; /* Register 1 for REV C cards */ | ||
809 | int i; | ||
810 | int retval; | ||
811 | |||
812 | if (debug >= DEBUG) | ||
813 | printk(KERN_DEBUG PFX "pcwd_isa_match id=%d\n", | ||
814 | id); | ||
815 | |||
816 | if (!request_region (base_addr, 4, "PCWD")) { | ||
817 | printk(KERN_INFO PFX "Port 0x%04x unavailable\n", base_addr); | ||
818 | return 0; | ||
819 | } | ||
820 | |||
821 | retval = 0; | ||
822 | |||
823 | port0 = inb_p(base_addr); /* For REV A boards */ | ||
824 | port1 = inb_p(base_addr + 1); /* For REV C boards */ | ||
825 | if (port0 != 0xff || port1 != 0xff) { | ||
826 | /* Not an 'ff' from a floating bus, so must be a card! */ | ||
827 | for (i = 0; i < 4; ++i) { | ||
828 | |||
829 | msleep(500); | ||
830 | |||
831 | last_port0 = port0; | ||
832 | last_port1 = port1; | ||
833 | |||
834 | port0 = inb_p(base_addr); | ||
835 | port1 = inb_p(base_addr + 1); | ||
836 | |||
837 | /* Has either hearbeat bit changed? */ | ||
838 | if ((port0 ^ last_port0) & WD_HRTBT || | ||
839 | (port1 ^ last_port1) & WD_REVC_HRBT) { | ||
840 | retval = 1; | ||
841 | break; | ||
842 | } | ||
843 | } | ||
844 | } | ||
845 | release_region (base_addr, 4); | ||
846 | |||
847 | return retval; | ||
848 | } | ||
849 | |||
850 | static int __devinit pcwd_isa_probe(struct device *dev, unsigned int id) | ||
851 | { | ||
852 | int ret; | ||
853 | |||
854 | if (debug >= DEBUG) | ||
855 | printk(KERN_DEBUG PFX "pcwd_isa_probe id=%d\n", | ||
856 | id); | ||
857 | |||
858 | cards_found++; | ||
859 | if (cards_found == 1) | ||
860 | printk(KERN_INFO PFX "v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER); | ||
861 | |||
862 | if (cards_found > 1) { | ||
863 | printk(KERN_ERR PFX "This driver only supports 1 device\n"); | ||
864 | return -ENODEV; | ||
865 | } | ||
866 | |||
867 | if (pcwd_ioports[id] == 0x0000) { | ||
868 | printk(KERN_ERR PFX "No I/O-Address for card detected\n"); | ||
869 | return -ENODEV; | ||
870 | } | ||
871 | pcwd_private.io_addr = pcwd_ioports[id]; | ||
872 | |||
873 | spin_lock_init(&pcwd_private.io_lock); | ||
874 | |||
875 | /* Check card's revision */ | ||
876 | pcwd_private.revision = get_revision(); | ||
877 | |||
878 | if (!request_region(pcwd_private.io_addr, (pcwd_private.revision == PCWD_REVISION_A) ? 2 : 4, "PCWD")) { | ||
879 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
880 | pcwd_private.io_addr); | ||
881 | ret=-EIO; | ||
882 | goto error_request_region; | ||
883 | } | ||
884 | |||
885 | /* Initial variables */ | ||
886 | pcwd_private.supports_temp = 0; | ||
887 | temp_panic = 0; | ||
888 | pcwd_private.boot_status = 0x0000; | ||
889 | |||
890 | /* get the boot_status */ | ||
891 | pcwd_get_status(&pcwd_private.boot_status); | ||
892 | |||
893 | /* clear the "card caused reboot" flag */ | ||
894 | pcwd_clear_status(); | ||
895 | |||
896 | setup_timer(&pcwd_private.timer, pcwd_timer_ping, 0); | ||
897 | |||
898 | /* Disable the board */ | ||
899 | pcwd_stop(); | ||
900 | |||
901 | /* Check whether or not the card supports the temperature device */ | ||
902 | pcwd_check_temperature_support(); | ||
903 | |||
904 | /* Show info about the card itself */ | ||
905 | pcwd_show_card_info(); | ||
906 | |||
907 | /* If heartbeat = 0 then we use the heartbeat from the dip-switches */ | ||
908 | if (heartbeat == 0) | ||
909 | heartbeat = heartbeat_tbl[(pcwd_get_option_switches() & 0x07)]; | ||
910 | |||
911 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
912 | if (pcwd_set_heartbeat(heartbeat)) { | ||
913 | pcwd_set_heartbeat(WATCHDOG_HEARTBEAT); | ||
914 | printk(KERN_INFO PFX "heartbeat value must be 2<=heartbeat<=7200, using %d\n", | ||
915 | WATCHDOG_HEARTBEAT); | ||
916 | } | ||
917 | |||
918 | if (pcwd_private.supports_temp) { | ||
919 | ret = misc_register(&temp_miscdev); | ||
920 | if (ret) { | ||
921 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
922 | TEMP_MINOR, ret); | ||
923 | goto error_misc_register_temp; | ||
924 | } | ||
925 | } | ||
926 | |||
927 | ret = misc_register(&pcwd_miscdev); | ||
928 | if (ret) { | ||
929 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
930 | WATCHDOG_MINOR, ret); | ||
931 | goto error_misc_register_watchdog; | ||
932 | } | ||
933 | |||
934 | printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
935 | heartbeat, nowayout); | ||
936 | |||
937 | return 0; | ||
938 | |||
939 | error_misc_register_watchdog: | ||
940 | if (pcwd_private.supports_temp) | ||
941 | misc_deregister(&temp_miscdev); | ||
942 | error_misc_register_temp: | ||
943 | release_region(pcwd_private.io_addr, (pcwd_private.revision == PCWD_REVISION_A) ? 2 : 4); | ||
944 | error_request_region: | ||
945 | pcwd_private.io_addr = 0x0000; | ||
946 | cards_found--; | ||
947 | return ret; | ||
948 | } | ||
949 | |||
950 | static int __devexit pcwd_isa_remove(struct device *dev, unsigned int id) | ||
951 | { | ||
952 | if (debug >= DEBUG) | ||
953 | printk(KERN_DEBUG PFX "pcwd_isa_remove id=%d\n", | ||
954 | id); | ||
955 | |||
956 | if (!pcwd_private.io_addr) | ||
957 | return 1; | ||
958 | |||
959 | /* Disable the board */ | ||
960 | if (!nowayout) | ||
961 | pcwd_stop(); | ||
962 | |||
963 | /* Deregister */ | ||
964 | misc_deregister(&pcwd_miscdev); | ||
965 | if (pcwd_private.supports_temp) | ||
966 | misc_deregister(&temp_miscdev); | ||
967 | release_region(pcwd_private.io_addr, (pcwd_private.revision == PCWD_REVISION_A) ? 2 : 4); | ||
968 | pcwd_private.io_addr = 0x0000; | ||
969 | cards_found--; | ||
970 | |||
971 | return 0; | ||
972 | } | ||
973 | |||
974 | static void pcwd_isa_shutdown(struct device *dev, unsigned int id) | ||
975 | { | ||
976 | if (debug >= DEBUG) | ||
977 | printk(KERN_DEBUG PFX "pcwd_isa_shutdown id=%d\n", | ||
978 | id); | ||
979 | |||
980 | pcwd_stop(); | ||
981 | } | ||
982 | |||
983 | static struct isa_driver pcwd_isa_driver = { | ||
984 | .match = pcwd_isa_match, | ||
985 | .probe = pcwd_isa_probe, | ||
986 | .remove = __devexit_p(pcwd_isa_remove), | ||
987 | .shutdown = pcwd_isa_shutdown, | ||
988 | .driver = { | ||
989 | .owner = THIS_MODULE, | ||
990 | .name = WATCHDOG_NAME, | ||
991 | }, | ||
992 | }; | ||
993 | |||
994 | static int __init pcwd_init_module(void) | ||
995 | { | ||
996 | return isa_register_driver(&pcwd_isa_driver, PCWD_ISA_NR_CARDS); | ||
997 | } | ||
998 | |||
999 | static void __exit pcwd_cleanup_module(void) | ||
1000 | { | ||
1001 | isa_unregister_driver(&pcwd_isa_driver); | ||
1002 | printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); | ||
1003 | } | ||
1004 | |||
1005 | module_init(pcwd_init_module); | ||
1006 | module_exit(pcwd_cleanup_module); | ||
1007 | |||
1008 | MODULE_AUTHOR("Ken Hollis <kenji@bitgate.com>, Wim Van Sebroeck <wim@iguana.be>"); | ||
1009 | MODULE_DESCRIPTION("Berkshire ISA-PC Watchdog driver"); | ||
1010 | MODULE_VERSION(WATCHDOG_VERSION); | ||
1011 | MODULE_LICENSE("GPL"); | ||
1012 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
1013 | MODULE_ALIAS_MISCDEV(TEMP_MINOR); | ||
diff --git a/drivers/watchdog/pcwd_pci.c b/drivers/watchdog/pcwd_pci.c new file mode 100644 index 000000000000..61a89e959642 --- /dev/null +++ b/drivers/watchdog/pcwd_pci.c | |||
@@ -0,0 +1,832 @@ | |||
1 | /* | ||
2 | * Berkshire PCI-PC Watchdog Card Driver | ||
3 | * | ||
4 | * (c) Copyright 2003-2007 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: | ||
25 | * http://www.kernel.org/pub/linux/kernel/people/wim/pcwd/pcwd_pci/ | ||
26 | * | ||
27 | * More info available at http://www.berkprod.com/ or http://www.pcwatchdog.com/ | ||
28 | */ | ||
29 | |||
30 | /* | ||
31 | * Includes, defines, variables, module parameters, ... | ||
32 | */ | ||
33 | |||
34 | #include <linux/module.h> /* For module specific items */ | ||
35 | #include <linux/moduleparam.h> /* For new moduleparam's */ | ||
36 | #include <linux/types.h> /* For standard types (like size_t) */ | ||
37 | #include <linux/errno.h> /* For the -ENODEV/... values */ | ||
38 | #include <linux/kernel.h> /* For printk/panic/... */ | ||
39 | #include <linux/delay.h> /* For mdelay function */ | ||
40 | #include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ | ||
41 | #include <linux/watchdog.h> /* For the watchdog specific items */ | ||
42 | #include <linux/notifier.h> /* For notifier support */ | ||
43 | #include <linux/reboot.h> /* For reboot_notifier stuff */ | ||
44 | #include <linux/init.h> /* For __init/__exit/... */ | ||
45 | #include <linux/fs.h> /* For file operations */ | ||
46 | #include <linux/pci.h> /* For pci functions */ | ||
47 | #include <linux/ioport.h> /* For io-port access */ | ||
48 | #include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ | ||
49 | |||
50 | #include <asm/uaccess.h> /* For copy_to_user/put_user/... */ | ||
51 | #include <asm/io.h> /* For inb/outb/... */ | ||
52 | |||
53 | /* Module and version information */ | ||
54 | #define WATCHDOG_VERSION "1.03" | ||
55 | #define WATCHDOG_DATE "21 Jan 2007" | ||
56 | #define WATCHDOG_DRIVER_NAME "PCI-PC Watchdog" | ||
57 | #define WATCHDOG_NAME "pcwd_pci" | ||
58 | #define PFX WATCHDOG_NAME ": " | ||
59 | #define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION " (" WATCHDOG_DATE ")\n" | ||
60 | |||
61 | /* Stuff for the PCI ID's */ | ||
62 | #ifndef PCI_VENDOR_ID_QUICKLOGIC | ||
63 | #define PCI_VENDOR_ID_QUICKLOGIC 0x11e3 | ||
64 | #endif | ||
65 | |||
66 | #ifndef PCI_DEVICE_ID_WATCHDOG_PCIPCWD | ||
67 | #define PCI_DEVICE_ID_WATCHDOG_PCIPCWD 0x5030 | ||
68 | #endif | ||
69 | |||
70 | /* | ||
71 | * These are the defines that describe the control status bits for the | ||
72 | * PCI-PC Watchdog card. | ||
73 | */ | ||
74 | /* Port 1 : Control Status #1 */ | ||
75 | #define WD_PCI_WTRP 0x01 /* Watchdog Trip status */ | ||
76 | #define WD_PCI_HRBT 0x02 /* Watchdog Heartbeat */ | ||
77 | #define WD_PCI_TTRP 0x04 /* Temperature Trip status */ | ||
78 | #define WD_PCI_RL2A 0x08 /* Relay 2 Active */ | ||
79 | #define WD_PCI_RL1A 0x10 /* Relay 1 Active */ | ||
80 | #define WD_PCI_R2DS 0x40 /* Relay 2 Disable Temperature-trip/reset */ | ||
81 | #define WD_PCI_RLY2 0x80 /* Activate Relay 2 on the board */ | ||
82 | /* Port 2 : Control Status #2 */ | ||
83 | #define WD_PCI_WDIS 0x10 /* Watchdog Disable */ | ||
84 | #define WD_PCI_ENTP 0x20 /* Enable Temperature Trip Reset */ | ||
85 | #define WD_PCI_WRSP 0x40 /* Watchdog wrote response */ | ||
86 | #define WD_PCI_PCMD 0x80 /* PC has sent command */ | ||
87 | |||
88 | /* according to documentation max. time to process a command for the pci | ||
89 | * watchdog card is 100 ms, so we give it 150 ms to do it's job */ | ||
90 | #define PCI_COMMAND_TIMEOUT 150 | ||
91 | |||
92 | /* Watchdog's internal commands */ | ||
93 | #define CMD_GET_STATUS 0x04 | ||
94 | #define CMD_GET_FIRMWARE_VERSION 0x08 | ||
95 | #define CMD_READ_WATCHDOG_TIMEOUT 0x18 | ||
96 | #define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 | ||
97 | #define CMD_GET_CLEAR_RESET_COUNT 0x84 | ||
98 | |||
99 | /* Watchdog's Dip Switch heartbeat values */ | ||
100 | static const int heartbeat_tbl [] = { | ||
101 | 5, /* OFF-OFF-OFF = 5 Sec */ | ||
102 | 10, /* OFF-OFF-ON = 10 Sec */ | ||
103 | 30, /* OFF-ON-OFF = 30 Sec */ | ||
104 | 60, /* OFF-ON-ON = 1 Min */ | ||
105 | 300, /* ON-OFF-OFF = 5 Min */ | ||
106 | 600, /* ON-OFF-ON = 10 Min */ | ||
107 | 1800, /* ON-ON-OFF = 30 Min */ | ||
108 | 3600, /* ON-ON-ON = 1 hour */ | ||
109 | }; | ||
110 | |||
111 | /* We can only use 1 card due to the /dev/watchdog restriction */ | ||
112 | static int cards_found; | ||
113 | |||
114 | /* internal variables */ | ||
115 | static int temp_panic; | ||
116 | static unsigned long is_active; | ||
117 | static char expect_release; | ||
118 | static struct { /* this is private data for each PCI-PC watchdog card */ | ||
119 | int supports_temp; /* Wether or not the card has a temperature device */ | ||
120 | int boot_status; /* The card's boot status */ | ||
121 | unsigned long io_addr; /* The cards I/O address */ | ||
122 | spinlock_t io_lock; /* the lock for io operations */ | ||
123 | struct pci_dev *pdev; /* the PCI-device */ | ||
124 | } pcipcwd_private; | ||
125 | |||
126 | /* module parameters */ | ||
127 | #define QUIET 0 /* Default */ | ||
128 | #define VERBOSE 1 /* Verbose */ | ||
129 | #define DEBUG 2 /* print fancy stuff too */ | ||
130 | static int debug = QUIET; | ||
131 | module_param(debug, int, 0); | ||
132 | MODULE_PARM_DESC(debug, "Debug level: 0=Quiet, 1=Verbose, 2=Debug (default=0)"); | ||
133 | |||
134 | #define WATCHDOG_HEARTBEAT 0 /* default heartbeat = delay-time from dip-switches */ | ||
135 | static int heartbeat = WATCHDOG_HEARTBEAT; | ||
136 | module_param(heartbeat, int, 0); | ||
137 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536 or 0=delay-time from dip-switches, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
138 | |||
139 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
140 | module_param(nowayout, int, 0); | ||
141 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
142 | |||
143 | /* | ||
144 | * Internal functions | ||
145 | */ | ||
146 | |||
147 | static int send_command(int cmd, int *msb, int *lsb) | ||
148 | { | ||
149 | int got_response, count; | ||
150 | |||
151 | if (debug >= DEBUG) | ||
152 | printk(KERN_DEBUG PFX "sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x\n", | ||
153 | cmd, *msb, *lsb); | ||
154 | |||
155 | spin_lock(&pcipcwd_private.io_lock); | ||
156 | /* If a command requires data it should be written first. | ||
157 | * Data for commands with 8 bits of data should be written to port 4. | ||
158 | * Commands with 16 bits of data, should be written as LSB to port 4 | ||
159 | * and MSB to port 5. | ||
160 | * After the required data has been written then write the command to | ||
161 | * port 6. */ | ||
162 | outb_p(*lsb, pcipcwd_private.io_addr + 4); | ||
163 | outb_p(*msb, pcipcwd_private.io_addr + 5); | ||
164 | outb_p(cmd, pcipcwd_private.io_addr + 6); | ||
165 | |||
166 | /* wait till the pci card processed the command, signaled by | ||
167 | * the WRSP bit in port 2 and give it a max. timeout of | ||
168 | * PCI_COMMAND_TIMEOUT to process */ | ||
169 | got_response = inb_p(pcipcwd_private.io_addr + 2) & WD_PCI_WRSP; | ||
170 | for (count = 0; (count < PCI_COMMAND_TIMEOUT) && (!got_response); count++) { | ||
171 | mdelay(1); | ||
172 | got_response = inb_p(pcipcwd_private.io_addr + 2) & WD_PCI_WRSP; | ||
173 | } | ||
174 | |||
175 | if (debug >= DEBUG) { | ||
176 | if (got_response) { | ||
177 | printk(KERN_DEBUG PFX "time to process command was: %d ms\n", | ||
178 | count); | ||
179 | } else { | ||
180 | printk(KERN_DEBUG PFX "card did not respond on command!\n"); | ||
181 | } | ||
182 | } | ||
183 | |||
184 | if (got_response) { | ||
185 | /* read back response */ | ||
186 | *lsb = inb_p(pcipcwd_private.io_addr + 4); | ||
187 | *msb = inb_p(pcipcwd_private.io_addr + 5); | ||
188 | |||
189 | /* clear WRSP bit */ | ||
190 | inb_p(pcipcwd_private.io_addr + 6); | ||
191 | |||
192 | if (debug >= DEBUG) | ||
193 | printk(KERN_DEBUG PFX "received following data for cmd=0x%02x: msb=0x%02x lsb=0x%02x\n", | ||
194 | cmd, *msb, *lsb); | ||
195 | } | ||
196 | |||
197 | spin_unlock(&pcipcwd_private.io_lock); | ||
198 | |||
199 | return got_response; | ||
200 | } | ||
201 | |||
202 | static inline void pcipcwd_check_temperature_support(void) | ||
203 | { | ||
204 | if (inb_p(pcipcwd_private.io_addr) != 0xF0) | ||
205 | pcipcwd_private.supports_temp = 1; | ||
206 | } | ||
207 | |||
208 | static int pcipcwd_get_option_switches(void) | ||
209 | { | ||
210 | int option_switches; | ||
211 | |||
212 | option_switches = inb_p(pcipcwd_private.io_addr + 3); | ||
213 | return option_switches; | ||
214 | } | ||
215 | |||
216 | static void pcipcwd_show_card_info(void) | ||
217 | { | ||
218 | int got_fw_rev, fw_rev_major, fw_rev_minor; | ||
219 | char fw_ver_str[20]; /* The cards firmware version */ | ||
220 | int option_switches; | ||
221 | |||
222 | got_fw_rev = send_command(CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor); | ||
223 | if (got_fw_rev) { | ||
224 | sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor); | ||
225 | } else { | ||
226 | sprintf(fw_ver_str, "<card no answer>"); | ||
227 | } | ||
228 | |||
229 | /* Get switch settings */ | ||
230 | option_switches = pcipcwd_get_option_switches(); | ||
231 | |||
232 | printk(KERN_INFO PFX "Found card at port 0x%04x (Firmware: %s) %s temp option\n", | ||
233 | (int) pcipcwd_private.io_addr, fw_ver_str, | ||
234 | (pcipcwd_private.supports_temp ? "with" : "without")); | ||
235 | |||
236 | printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", | ||
237 | option_switches, | ||
238 | ((option_switches & 0x10) ? "ON" : "OFF"), | ||
239 | ((option_switches & 0x08) ? "ON" : "OFF")); | ||
240 | |||
241 | if (pcipcwd_private.boot_status & WDIOF_CARDRESET) | ||
242 | printk(KERN_INFO PFX "Previous reset was caused by the Watchdog card\n"); | ||
243 | |||
244 | if (pcipcwd_private.boot_status & WDIOF_OVERHEAT) | ||
245 | printk(KERN_INFO PFX "Card sensed a CPU Overheat\n"); | ||
246 | |||
247 | if (pcipcwd_private.boot_status == 0) | ||
248 | printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n"); | ||
249 | } | ||
250 | |||
251 | static int pcipcwd_start(void) | ||
252 | { | ||
253 | int stat_reg; | ||
254 | |||
255 | spin_lock(&pcipcwd_private.io_lock); | ||
256 | outb_p(0x00, pcipcwd_private.io_addr + 3); | ||
257 | udelay(1000); | ||
258 | |||
259 | stat_reg = inb_p(pcipcwd_private.io_addr + 2); | ||
260 | spin_unlock(&pcipcwd_private.io_lock); | ||
261 | |||
262 | if (stat_reg & WD_PCI_WDIS) { | ||
263 | printk(KERN_ERR PFX "Card timer not enabled\n"); | ||
264 | return -1; | ||
265 | } | ||
266 | |||
267 | if (debug >= VERBOSE) | ||
268 | printk(KERN_DEBUG PFX "Watchdog started\n"); | ||
269 | |||
270 | return 0; | ||
271 | } | ||
272 | |||
273 | static int pcipcwd_stop(void) | ||
274 | { | ||
275 | int stat_reg; | ||
276 | |||
277 | spin_lock(&pcipcwd_private.io_lock); | ||
278 | outb_p(0xA5, pcipcwd_private.io_addr + 3); | ||
279 | udelay(1000); | ||
280 | |||
281 | outb_p(0xA5, pcipcwd_private.io_addr + 3); | ||
282 | udelay(1000); | ||
283 | |||
284 | stat_reg = inb_p(pcipcwd_private.io_addr + 2); | ||
285 | spin_unlock(&pcipcwd_private.io_lock); | ||
286 | |||
287 | if (!(stat_reg & WD_PCI_WDIS)) { | ||
288 | printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n"); | ||
289 | return -1; | ||
290 | } | ||
291 | |||
292 | if (debug >= VERBOSE) | ||
293 | printk(KERN_DEBUG PFX "Watchdog stopped\n"); | ||
294 | |||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | static int pcipcwd_keepalive(void) | ||
299 | { | ||
300 | /* Re-trigger watchdog by writing to port 0 */ | ||
301 | spin_lock(&pcipcwd_private.io_lock); | ||
302 | outb_p(0x42, pcipcwd_private.io_addr); /* send out any data */ | ||
303 | spin_unlock(&pcipcwd_private.io_lock); | ||
304 | |||
305 | if (debug >= DEBUG) | ||
306 | printk(KERN_DEBUG PFX "Watchdog keepalive signal send\n"); | ||
307 | |||
308 | return 0; | ||
309 | } | ||
310 | |||
311 | static int pcipcwd_set_heartbeat(int t) | ||
312 | { | ||
313 | int t_msb = t / 256; | ||
314 | int t_lsb = t % 256; | ||
315 | |||
316 | if ((t < 0x0001) || (t > 0xFFFF)) | ||
317 | return -EINVAL; | ||
318 | |||
319 | /* Write new heartbeat to watchdog */ | ||
320 | send_command(CMD_WRITE_WATCHDOG_TIMEOUT, &t_msb, &t_lsb); | ||
321 | |||
322 | heartbeat = t; | ||
323 | if (debug >= VERBOSE) | ||
324 | printk(KERN_DEBUG PFX "New heartbeat: %d\n", | ||
325 | heartbeat); | ||
326 | |||
327 | return 0; | ||
328 | } | ||
329 | |||
330 | static int pcipcwd_get_status(int *status) | ||
331 | { | ||
332 | int control_status; | ||
333 | |||
334 | *status=0; | ||
335 | control_status = inb_p(pcipcwd_private.io_addr + 1); | ||
336 | if (control_status & WD_PCI_WTRP) | ||
337 | *status |= WDIOF_CARDRESET; | ||
338 | if (control_status & WD_PCI_TTRP) { | ||
339 | *status |= WDIOF_OVERHEAT; | ||
340 | if (temp_panic) | ||
341 | panic(PFX "Temperature overheat trip!\n"); | ||
342 | } | ||
343 | |||
344 | if (debug >= DEBUG) | ||
345 | printk(KERN_DEBUG PFX "Control Status #1: 0x%02x\n", | ||
346 | control_status); | ||
347 | |||
348 | return 0; | ||
349 | } | ||
350 | |||
351 | static int pcipcwd_clear_status(void) | ||
352 | { | ||
353 | int control_status; | ||
354 | int msb; | ||
355 | int reset_counter; | ||
356 | |||
357 | if (debug >= VERBOSE) | ||
358 | printk(KERN_INFO PFX "clearing watchdog trip status & LED\n"); | ||
359 | |||
360 | control_status = inb_p(pcipcwd_private.io_addr + 1); | ||
361 | |||
362 | if (debug >= DEBUG) { | ||
363 | printk(KERN_DEBUG PFX "status was: 0x%02x\n", control_status); | ||
364 | printk(KERN_DEBUG PFX "sending: 0x%02x\n", | ||
365 | (control_status & WD_PCI_R2DS) | WD_PCI_WTRP); | ||
366 | } | ||
367 | |||
368 | /* clear trip status & LED and keep mode of relay 2 */ | ||
369 | outb_p((control_status & WD_PCI_R2DS) | WD_PCI_WTRP, pcipcwd_private.io_addr + 1); | ||
370 | |||
371 | /* clear reset counter */ | ||
372 | msb=0; | ||
373 | reset_counter=0xff; | ||
374 | send_command(CMD_GET_CLEAR_RESET_COUNT, &msb, &reset_counter); | ||
375 | |||
376 | if (debug >= DEBUG) { | ||
377 | printk(KERN_DEBUG PFX "reset count was: 0x%02x\n", | ||
378 | reset_counter); | ||
379 | } | ||
380 | |||
381 | return 0; | ||
382 | } | ||
383 | |||
384 | static int pcipcwd_get_temperature(int *temperature) | ||
385 | { | ||
386 | *temperature = 0; | ||
387 | if (!pcipcwd_private.supports_temp) | ||
388 | return -ENODEV; | ||
389 | |||
390 | spin_lock(&pcipcwd_private.io_lock); | ||
391 | *temperature = inb_p(pcipcwd_private.io_addr); | ||
392 | spin_unlock(&pcipcwd_private.io_lock); | ||
393 | |||
394 | /* | ||
395 | * Convert celsius to fahrenheit, since this was | ||
396 | * the decided 'standard' for this return value. | ||
397 | */ | ||
398 | *temperature = (*temperature * 9 / 5) + 32; | ||
399 | |||
400 | if (debug >= DEBUG) { | ||
401 | printk(KERN_DEBUG PFX "temperature is: %d F\n", | ||
402 | *temperature); | ||
403 | } | ||
404 | |||
405 | return 0; | ||
406 | } | ||
407 | |||
408 | static int pcipcwd_get_timeleft(int *time_left) | ||
409 | { | ||
410 | int msb; | ||
411 | int lsb; | ||
412 | |||
413 | /* Read the time that's left before rebooting */ | ||
414 | /* Note: if the board is not yet armed then we will read 0xFFFF */ | ||
415 | send_command(CMD_READ_WATCHDOG_TIMEOUT, &msb, &lsb); | ||
416 | |||
417 | *time_left = (msb << 8) + lsb; | ||
418 | |||
419 | if (debug >= VERBOSE) | ||
420 | printk(KERN_DEBUG PFX "Time left before next reboot: %d\n", | ||
421 | *time_left); | ||
422 | |||
423 | return 0; | ||
424 | } | ||
425 | |||
426 | /* | ||
427 | * /dev/watchdog handling | ||
428 | */ | ||
429 | |||
430 | static ssize_t pcipcwd_write(struct file *file, const char __user *data, | ||
431 | size_t len, loff_t *ppos) | ||
432 | { | ||
433 | /* See if we got the magic character 'V' and reload the timer */ | ||
434 | if (len) { | ||
435 | if (!nowayout) { | ||
436 | size_t i; | ||
437 | |||
438 | /* note: just in case someone wrote the magic character | ||
439 | * five months ago... */ | ||
440 | expect_release = 0; | ||
441 | |||
442 | /* scan to see whether or not we got the magic character */ | ||
443 | for (i = 0; i != len; i++) { | ||
444 | char c; | ||
445 | if(get_user(c, data+i)) | ||
446 | return -EFAULT; | ||
447 | if (c == 'V') | ||
448 | expect_release = 42; | ||
449 | } | ||
450 | } | ||
451 | |||
452 | /* someone wrote to us, we should reload the timer */ | ||
453 | pcipcwd_keepalive(); | ||
454 | } | ||
455 | return len; | ||
456 | } | ||
457 | |||
458 | static int pcipcwd_ioctl(struct inode *inode, struct file *file, | ||
459 | unsigned int cmd, unsigned long arg) | ||
460 | { | ||
461 | void __user *argp = (void __user *)arg; | ||
462 | int __user *p = argp; | ||
463 | static struct watchdog_info ident = { | ||
464 | .options = WDIOF_OVERHEAT | | ||
465 | WDIOF_CARDRESET | | ||
466 | WDIOF_KEEPALIVEPING | | ||
467 | WDIOF_SETTIMEOUT | | ||
468 | WDIOF_MAGICCLOSE, | ||
469 | .firmware_version = 1, | ||
470 | .identity = WATCHDOG_DRIVER_NAME, | ||
471 | }; | ||
472 | |||
473 | switch (cmd) { | ||
474 | case WDIOC_GETSUPPORT: | ||
475 | return copy_to_user(argp, &ident, | ||
476 | sizeof (ident)) ? -EFAULT : 0; | ||
477 | |||
478 | case WDIOC_GETSTATUS: | ||
479 | { | ||
480 | int status; | ||
481 | |||
482 | pcipcwd_get_status(&status); | ||
483 | |||
484 | return put_user(status, p); | ||
485 | } | ||
486 | |||
487 | case WDIOC_GETBOOTSTATUS: | ||
488 | return put_user(pcipcwd_private.boot_status, p); | ||
489 | |||
490 | case WDIOC_GETTEMP: | ||
491 | { | ||
492 | int temperature; | ||
493 | |||
494 | if (pcipcwd_get_temperature(&temperature)) | ||
495 | return -EFAULT; | ||
496 | |||
497 | return put_user(temperature, p); | ||
498 | } | ||
499 | |||
500 | case WDIOC_KEEPALIVE: | ||
501 | pcipcwd_keepalive(); | ||
502 | return 0; | ||
503 | |||
504 | case WDIOC_SETOPTIONS: | ||
505 | { | ||
506 | int new_options, retval = -EINVAL; | ||
507 | |||
508 | if (get_user (new_options, p)) | ||
509 | return -EFAULT; | ||
510 | |||
511 | if (new_options & WDIOS_DISABLECARD) { | ||
512 | if (pcipcwd_stop()) | ||
513 | return -EIO; | ||
514 | retval = 0; | ||
515 | } | ||
516 | |||
517 | if (new_options & WDIOS_ENABLECARD) { | ||
518 | if (pcipcwd_start()) | ||
519 | return -EIO; | ||
520 | retval = 0; | ||
521 | } | ||
522 | |||
523 | if (new_options & WDIOS_TEMPPANIC) { | ||
524 | temp_panic = 1; | ||
525 | retval = 0; | ||
526 | } | ||
527 | |||
528 | return retval; | ||
529 | } | ||
530 | |||
531 | case WDIOC_SETTIMEOUT: | ||
532 | { | ||
533 | int new_heartbeat; | ||
534 | |||
535 | if (get_user(new_heartbeat, p)) | ||
536 | return -EFAULT; | ||
537 | |||
538 | if (pcipcwd_set_heartbeat(new_heartbeat)) | ||
539 | return -EINVAL; | ||
540 | |||
541 | pcipcwd_keepalive(); | ||
542 | /* Fall */ | ||
543 | } | ||
544 | |||
545 | case WDIOC_GETTIMEOUT: | ||
546 | return put_user(heartbeat, p); | ||
547 | |||
548 | case WDIOC_GETTIMELEFT: | ||
549 | { | ||
550 | int time_left; | ||
551 | |||
552 | if (pcipcwd_get_timeleft(&time_left)) | ||
553 | return -EFAULT; | ||
554 | |||
555 | return put_user(time_left, p); | ||
556 | } | ||
557 | |||
558 | default: | ||
559 | return -ENOTTY; | ||
560 | } | ||
561 | } | ||
562 | |||
563 | static int pcipcwd_open(struct inode *inode, struct file *file) | ||
564 | { | ||
565 | /* /dev/watchdog can only be opened once */ | ||
566 | if (test_and_set_bit(0, &is_active)) { | ||
567 | if (debug >= VERBOSE) | ||
568 | printk(KERN_ERR PFX "Attempt to open already opened device.\n"); | ||
569 | return -EBUSY; | ||
570 | } | ||
571 | |||
572 | /* Activate */ | ||
573 | pcipcwd_start(); | ||
574 | pcipcwd_keepalive(); | ||
575 | return nonseekable_open(inode, file); | ||
576 | } | ||
577 | |||
578 | static int pcipcwd_release(struct inode *inode, struct file *file) | ||
579 | { | ||
580 | /* | ||
581 | * Shut off the timer. | ||
582 | */ | ||
583 | if (expect_release == 42) { | ||
584 | pcipcwd_stop(); | ||
585 | } else { | ||
586 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
587 | pcipcwd_keepalive(); | ||
588 | } | ||
589 | expect_release = 0; | ||
590 | clear_bit(0, &is_active); | ||
591 | return 0; | ||
592 | } | ||
593 | |||
594 | /* | ||
595 | * /dev/temperature handling | ||
596 | */ | ||
597 | |||
598 | static ssize_t pcipcwd_temp_read(struct file *file, char __user *data, | ||
599 | size_t len, loff_t *ppos) | ||
600 | { | ||
601 | int temperature; | ||
602 | |||
603 | if (pcipcwd_get_temperature(&temperature)) | ||
604 | return -EFAULT; | ||
605 | |||
606 | if (copy_to_user (data, &temperature, 1)) | ||
607 | return -EFAULT; | ||
608 | |||
609 | return 1; | ||
610 | } | ||
611 | |||
612 | static int pcipcwd_temp_open(struct inode *inode, struct file *file) | ||
613 | { | ||
614 | if (!pcipcwd_private.supports_temp) | ||
615 | return -ENODEV; | ||
616 | |||
617 | return nonseekable_open(inode, file); | ||
618 | } | ||
619 | |||
620 | static int pcipcwd_temp_release(struct inode *inode, struct file *file) | ||
621 | { | ||
622 | return 0; | ||
623 | } | ||
624 | |||
625 | /* | ||
626 | * Notify system | ||
627 | */ | ||
628 | |||
629 | static int pcipcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
630 | { | ||
631 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
632 | /* Turn the WDT off */ | ||
633 | pcipcwd_stop(); | ||
634 | } | ||
635 | |||
636 | return NOTIFY_DONE; | ||
637 | } | ||
638 | |||
639 | /* | ||
640 | * Kernel Interfaces | ||
641 | */ | ||
642 | |||
643 | static const struct file_operations pcipcwd_fops = { | ||
644 | .owner = THIS_MODULE, | ||
645 | .llseek = no_llseek, | ||
646 | .write = pcipcwd_write, | ||
647 | .ioctl = pcipcwd_ioctl, | ||
648 | .open = pcipcwd_open, | ||
649 | .release = pcipcwd_release, | ||
650 | }; | ||
651 | |||
652 | static struct miscdevice pcipcwd_miscdev = { | ||
653 | .minor = WATCHDOG_MINOR, | ||
654 | .name = "watchdog", | ||
655 | .fops = &pcipcwd_fops, | ||
656 | }; | ||
657 | |||
658 | static const struct file_operations pcipcwd_temp_fops = { | ||
659 | .owner = THIS_MODULE, | ||
660 | .llseek = no_llseek, | ||
661 | .read = pcipcwd_temp_read, | ||
662 | .open = pcipcwd_temp_open, | ||
663 | .release = pcipcwd_temp_release, | ||
664 | }; | ||
665 | |||
666 | static struct miscdevice pcipcwd_temp_miscdev = { | ||
667 | .minor = TEMP_MINOR, | ||
668 | .name = "temperature", | ||
669 | .fops = &pcipcwd_temp_fops, | ||
670 | }; | ||
671 | |||
672 | static struct notifier_block pcipcwd_notifier = { | ||
673 | .notifier_call = pcipcwd_notify_sys, | ||
674 | }; | ||
675 | |||
676 | /* | ||
677 | * Init & exit routines | ||
678 | */ | ||
679 | |||
680 | static int __devinit pcipcwd_card_init(struct pci_dev *pdev, | ||
681 | const struct pci_device_id *ent) | ||
682 | { | ||
683 | int ret = -EIO; | ||
684 | |||
685 | cards_found++; | ||
686 | if (cards_found == 1) | ||
687 | printk(KERN_INFO PFX DRIVER_VERSION); | ||
688 | |||
689 | if (cards_found > 1) { | ||
690 | printk(KERN_ERR PFX "This driver only supports 1 device\n"); | ||
691 | return -ENODEV; | ||
692 | } | ||
693 | |||
694 | if (pci_enable_device(pdev)) { | ||
695 | printk(KERN_ERR PFX "Not possible to enable PCI Device\n"); | ||
696 | return -ENODEV; | ||
697 | } | ||
698 | |||
699 | if (pci_resource_start(pdev, 0) == 0x0000) { | ||
700 | printk(KERN_ERR PFX "No I/O-Address for card detected\n"); | ||
701 | ret = -ENODEV; | ||
702 | goto err_out_disable_device; | ||
703 | } | ||
704 | |||
705 | pcipcwd_private.pdev = pdev; | ||
706 | pcipcwd_private.io_addr = pci_resource_start(pdev, 0); | ||
707 | |||
708 | if (pci_request_regions(pdev, WATCHDOG_NAME)) { | ||
709 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
710 | (int) pcipcwd_private.io_addr); | ||
711 | ret = -EIO; | ||
712 | goto err_out_disable_device; | ||
713 | } | ||
714 | |||
715 | /* get the boot_status */ | ||
716 | pcipcwd_get_status(&pcipcwd_private.boot_status); | ||
717 | |||
718 | /* clear the "card caused reboot" flag */ | ||
719 | pcipcwd_clear_status(); | ||
720 | |||
721 | /* disable card */ | ||
722 | pcipcwd_stop(); | ||
723 | |||
724 | /* Check whether or not the card supports the temperature device */ | ||
725 | pcipcwd_check_temperature_support(); | ||
726 | |||
727 | /* Show info about the card itself */ | ||
728 | pcipcwd_show_card_info(); | ||
729 | |||
730 | /* If heartbeat = 0 then we use the heartbeat from the dip-switches */ | ||
731 | if (heartbeat == 0) | ||
732 | heartbeat = heartbeat_tbl[(pcipcwd_get_option_switches() & 0x07)]; | ||
733 | |||
734 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
735 | if (pcipcwd_set_heartbeat(heartbeat)) { | ||
736 | pcipcwd_set_heartbeat(WATCHDOG_HEARTBEAT); | ||
737 | printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", | ||
738 | WATCHDOG_HEARTBEAT); | ||
739 | } | ||
740 | |||
741 | ret = register_reboot_notifier(&pcipcwd_notifier); | ||
742 | if (ret != 0) { | ||
743 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
744 | ret); | ||
745 | goto err_out_release_region; | ||
746 | } | ||
747 | |||
748 | if (pcipcwd_private.supports_temp) { | ||
749 | ret = misc_register(&pcipcwd_temp_miscdev); | ||
750 | if (ret != 0) { | ||
751 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
752 | TEMP_MINOR, ret); | ||
753 | goto err_out_unregister_reboot; | ||
754 | } | ||
755 | } | ||
756 | |||
757 | ret = misc_register(&pcipcwd_miscdev); | ||
758 | if (ret != 0) { | ||
759 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
760 | WATCHDOG_MINOR, ret); | ||
761 | goto err_out_misc_deregister; | ||
762 | } | ||
763 | |||
764 | printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
765 | heartbeat, nowayout); | ||
766 | |||
767 | return 0; | ||
768 | |||
769 | err_out_misc_deregister: | ||
770 | if (pcipcwd_private.supports_temp) | ||
771 | misc_deregister(&pcipcwd_temp_miscdev); | ||
772 | err_out_unregister_reboot: | ||
773 | unregister_reboot_notifier(&pcipcwd_notifier); | ||
774 | err_out_release_region: | ||
775 | pci_release_regions(pdev); | ||
776 | err_out_disable_device: | ||
777 | pci_disable_device(pdev); | ||
778 | return ret; | ||
779 | } | ||
780 | |||
781 | static void __devexit pcipcwd_card_exit(struct pci_dev *pdev) | ||
782 | { | ||
783 | /* Stop the timer before we leave */ | ||
784 | if (!nowayout) | ||
785 | pcipcwd_stop(); | ||
786 | |||
787 | /* Deregister */ | ||
788 | misc_deregister(&pcipcwd_miscdev); | ||
789 | if (pcipcwd_private.supports_temp) | ||
790 | misc_deregister(&pcipcwd_temp_miscdev); | ||
791 | unregister_reboot_notifier(&pcipcwd_notifier); | ||
792 | pci_release_regions(pdev); | ||
793 | pci_disable_device(pdev); | ||
794 | cards_found--; | ||
795 | } | ||
796 | |||
797 | static struct pci_device_id pcipcwd_pci_tbl[] = { | ||
798 | { PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_WATCHDOG_PCIPCWD, | ||
799 | PCI_ANY_ID, PCI_ANY_ID, }, | ||
800 | { 0 }, /* End of list */ | ||
801 | }; | ||
802 | MODULE_DEVICE_TABLE(pci, pcipcwd_pci_tbl); | ||
803 | |||
804 | static struct pci_driver pcipcwd_driver = { | ||
805 | .name = WATCHDOG_NAME, | ||
806 | .id_table = pcipcwd_pci_tbl, | ||
807 | .probe = pcipcwd_card_init, | ||
808 | .remove = __devexit_p(pcipcwd_card_exit), | ||
809 | }; | ||
810 | |||
811 | static int __init pcipcwd_init_module(void) | ||
812 | { | ||
813 | spin_lock_init(&pcipcwd_private.io_lock); | ||
814 | |||
815 | return pci_register_driver(&pcipcwd_driver); | ||
816 | } | ||
817 | |||
818 | static void __exit pcipcwd_cleanup_module(void) | ||
819 | { | ||
820 | pci_unregister_driver(&pcipcwd_driver); | ||
821 | |||
822 | printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); | ||
823 | } | ||
824 | |||
825 | module_init(pcipcwd_init_module); | ||
826 | module_exit(pcipcwd_cleanup_module); | ||
827 | |||
828 | MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>"); | ||
829 | MODULE_DESCRIPTION("Berkshire PCI-PC Watchdog driver"); | ||
830 | MODULE_LICENSE("GPL"); | ||
831 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
832 | MODULE_ALIAS_MISCDEV(TEMP_MINOR); | ||
diff --git a/drivers/watchdog/pcwd_usb.c b/drivers/watchdog/pcwd_usb.c new file mode 100644 index 000000000000..0f3fd6c9c354 --- /dev/null +++ b/drivers/watchdog/pcwd_usb.c | |||
@@ -0,0 +1,824 @@ | |||
1 | /* | ||
2 | * Berkshire USB-PC Watchdog Card Driver | ||
3 | * | ||
4 | * (c) Copyright 2004-2007 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/module.h> /* For module specific items */ | ||
28 | #include <linux/moduleparam.h> /* For new moduleparam's */ | ||
29 | #include <linux/types.h> /* For standard types (like size_t) */ | ||
30 | #include <linux/errno.h> /* For the -ENODEV/... values */ | ||
31 | #include <linux/kernel.h> /* For printk/panic/... */ | ||
32 | #include <linux/delay.h> /* For mdelay function */ | ||
33 | #include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ | ||
34 | #include <linux/watchdog.h> /* For the watchdog specific items */ | ||
35 | #include <linux/notifier.h> /* For notifier support */ | ||
36 | #include <linux/reboot.h> /* For reboot_notifier stuff */ | ||
37 | #include <linux/init.h> /* For __init/__exit/... */ | ||
38 | #include <linux/fs.h> /* For file operations */ | ||
39 | #include <linux/usb.h> /* For USB functions */ | ||
40 | #include <linux/slab.h> /* For kmalloc, ... */ | ||
41 | #include <linux/mutex.h> /* For mutex locking */ | ||
42 | #include <linux/hid.h> /* For HID_REQ_SET_REPORT & HID_DT_REPORT */ | ||
43 | |||
44 | #include <asm/uaccess.h> /* For copy_to_user/put_user/... */ | ||
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.02" | ||
60 | #define DRIVER_DATE "21 Jan 2007" | ||
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 0 /* default heartbeat = delay-time from dip-switches */ | ||
78 | static int heartbeat = WATCHDOG_HEARTBEAT; | ||
79 | module_param(heartbeat, int, 0); | ||
80 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536 or 0=delay-time from dip-switches, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
81 | |||
82 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
83 | module_param(nowayout, int, 0); | ||
84 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
85 | |||
86 | /* The vendor and product id's for the USB-PC Watchdog card */ | ||
87 | #define USB_PCWD_VENDOR_ID 0x0c98 | ||
88 | #define USB_PCWD_PRODUCT_ID 0x1140 | ||
89 | |||
90 | /* table of devices that work with this driver */ | ||
91 | static struct usb_device_id usb_pcwd_table [] = { | ||
92 | { USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) }, | ||
93 | { } /* Terminating entry */ | ||
94 | }; | ||
95 | MODULE_DEVICE_TABLE (usb, usb_pcwd_table); | ||
96 | |||
97 | /* according to documentation max. time to process a command for the USB | ||
98 | * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */ | ||
99 | #define USB_COMMAND_TIMEOUT 250 | ||
100 | |||
101 | /* Watchdog's internal commands */ | ||
102 | #define CMD_READ_TEMP 0x02 /* Read Temperature; Re-trigger Watchdog */ | ||
103 | #define CMD_TRIGGER CMD_READ_TEMP | ||
104 | #define CMD_GET_STATUS 0x04 /* Get Status Information */ | ||
105 | #define CMD_GET_FIRMWARE_VERSION 0x08 /* Get Firmware Version */ | ||
106 | #define CMD_GET_DIP_SWITCH_SETTINGS 0x0c /* Get Dip Switch Settings */ | ||
107 | #define CMD_READ_WATCHDOG_TIMEOUT 0x18 /* Read Current Watchdog Time */ | ||
108 | #define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 /* Write Current Watchdog Time */ | ||
109 | #define CMD_ENABLE_WATCHDOG 0x30 /* Enable / Disable Watchdog */ | ||
110 | #define CMD_DISABLE_WATCHDOG CMD_ENABLE_WATCHDOG | ||
111 | |||
112 | /* Watchdog's Dip Switch heartbeat values */ | ||
113 | static const int heartbeat_tbl [] = { | ||
114 | 5, /* OFF-OFF-OFF = 5 Sec */ | ||
115 | 10, /* OFF-OFF-ON = 10 Sec */ | ||
116 | 30, /* OFF-ON-OFF = 30 Sec */ | ||
117 | 60, /* OFF-ON-ON = 1 Min */ | ||
118 | 300, /* ON-OFF-OFF = 5 Min */ | ||
119 | 600, /* ON-OFF-ON = 10 Min */ | ||
120 | 1800, /* ON-ON-OFF = 30 Min */ | ||
121 | 3600, /* ON-ON-ON = 1 hour */ | ||
122 | }; | ||
123 | |||
124 | /* We can only use 1 card due to the /dev/watchdog restriction */ | ||
125 | static int cards_found; | ||
126 | |||
127 | /* some internal variables */ | ||
128 | static unsigned long is_active; | ||
129 | static char expect_release; | ||
130 | |||
131 | /* Structure to hold all of our device specific stuff */ | ||
132 | struct usb_pcwd_private { | ||
133 | struct usb_device * udev; /* save off the usb device pointer */ | ||
134 | struct usb_interface * interface; /* the interface for this device */ | ||
135 | |||
136 | unsigned int interface_number; /* the interface number used for cmd's */ | ||
137 | |||
138 | unsigned char * intr_buffer; /* the buffer to intr data */ | ||
139 | dma_addr_t intr_dma; /* the dma address for the intr buffer */ | ||
140 | size_t intr_size; /* the size of the intr buffer */ | ||
141 | struct urb * intr_urb; /* the urb used for the intr pipe */ | ||
142 | |||
143 | unsigned char cmd_command; /* The command that is reported back */ | ||
144 | unsigned char cmd_data_msb; /* The data MSB that is reported back */ | ||
145 | unsigned char cmd_data_lsb; /* The data LSB that is reported back */ | ||
146 | atomic_t cmd_received; /* true if we received a report after a command */ | ||
147 | |||
148 | int exists; /* Wether or not the device exists */ | ||
149 | struct mutex mtx; /* locks this structure */ | ||
150 | }; | ||
151 | static struct usb_pcwd_private *usb_pcwd_device; | ||
152 | |||
153 | /* prevent races between open() and disconnect() */ | ||
154 | static DEFINE_MUTEX(disconnect_mutex); | ||
155 | |||
156 | /* local function prototypes */ | ||
157 | static int usb_pcwd_probe (struct usb_interface *interface, const struct usb_device_id *id); | ||
158 | static void usb_pcwd_disconnect (struct usb_interface *interface); | ||
159 | |||
160 | /* usb specific object needed to register this driver with the usb subsystem */ | ||
161 | static struct usb_driver usb_pcwd_driver = { | ||
162 | .name = DRIVER_NAME, | ||
163 | .probe = usb_pcwd_probe, | ||
164 | .disconnect = usb_pcwd_disconnect, | ||
165 | .id_table = usb_pcwd_table, | ||
166 | }; | ||
167 | |||
168 | |||
169 | static void usb_pcwd_intr_done(struct urb *urb) | ||
170 | { | ||
171 | struct usb_pcwd_private *usb_pcwd = (struct usb_pcwd_private *)urb->context; | ||
172 | unsigned char *data = usb_pcwd->intr_buffer; | ||
173 | int retval; | ||
174 | |||
175 | switch (urb->status) { | ||
176 | case 0: /* success */ | ||
177 | break; | ||
178 | case -ECONNRESET: /* unlink */ | ||
179 | case -ENOENT: | ||
180 | case -ESHUTDOWN: | ||
181 | /* this urb is terminated, clean up */ | ||
182 | dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); | ||
183 | return; | ||
184 | /* -EPIPE: should clear the halt */ | ||
185 | default: /* error */ | ||
186 | dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); | ||
187 | goto resubmit; | ||
188 | } | ||
189 | |||
190 | dbg("received following data cmd=0x%02x msb=0x%02x lsb=0x%02x", | ||
191 | data[0], data[1], data[2]); | ||
192 | |||
193 | usb_pcwd->cmd_command = data[0]; | ||
194 | usb_pcwd->cmd_data_msb = data[1]; | ||
195 | usb_pcwd->cmd_data_lsb = data[2]; | ||
196 | |||
197 | /* notify anyone waiting that the cmd has finished */ | ||
198 | atomic_set (&usb_pcwd->cmd_received, 1); | ||
199 | |||
200 | resubmit: | ||
201 | retval = usb_submit_urb (urb, GFP_ATOMIC); | ||
202 | if (retval) | ||
203 | printk(KERN_ERR PFX "can't resubmit intr, usb_submit_urb failed with result %d\n", | ||
204 | retval); | ||
205 | } | ||
206 | |||
207 | static int usb_pcwd_send_command(struct usb_pcwd_private *usb_pcwd, unsigned char cmd, | ||
208 | unsigned char *msb, unsigned char *lsb) | ||
209 | { | ||
210 | int got_response, count; | ||
211 | unsigned char buf[6]; | ||
212 | |||
213 | /* We will not send any commands if the USB PCWD device does not exist */ | ||
214 | if ((!usb_pcwd) || (!usb_pcwd->exists)) | ||
215 | return -1; | ||
216 | |||
217 | /* The USB PC Watchdog uses a 6 byte report format. The board currently uses | ||
218 | * only 3 of the six bytes of the report. */ | ||
219 | buf[0] = cmd; /* Byte 0 = CMD */ | ||
220 | buf[1] = *msb; /* Byte 1 = Data MSB */ | ||
221 | buf[2] = *lsb; /* Byte 2 = Data LSB */ | ||
222 | buf[3] = buf[4] = buf[5] = 0; /* All other bytes not used */ | ||
223 | |||
224 | dbg("sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x", | ||
225 | buf[0], buf[1], buf[2]); | ||
226 | |||
227 | atomic_set (&usb_pcwd->cmd_received, 0); | ||
228 | |||
229 | if (usb_control_msg(usb_pcwd->udev, usb_sndctrlpipe(usb_pcwd->udev, 0), | ||
230 | HID_REQ_SET_REPORT, HID_DT_REPORT, | ||
231 | 0x0200, usb_pcwd->interface_number, buf, sizeof(buf), | ||
232 | USB_COMMAND_TIMEOUT) != sizeof(buf)) { | ||
233 | dbg("usb_pcwd_send_command: error in usb_control_msg for cmd 0x%x 0x%x 0x%x\n", cmd, *msb, *lsb); | ||
234 | } | ||
235 | /* wait till the usb card processed the command, | ||
236 | * with a max. timeout of USB_COMMAND_TIMEOUT */ | ||
237 | got_response = 0; | ||
238 | for (count = 0; (count < USB_COMMAND_TIMEOUT) && (!got_response); count++) { | ||
239 | mdelay(1); | ||
240 | if (atomic_read (&usb_pcwd->cmd_received)) | ||
241 | got_response = 1; | ||
242 | } | ||
243 | |||
244 | if ((got_response) && (cmd == usb_pcwd->cmd_command)) { | ||
245 | /* read back response */ | ||
246 | *msb = usb_pcwd->cmd_data_msb; | ||
247 | *lsb = usb_pcwd->cmd_data_lsb; | ||
248 | } | ||
249 | |||
250 | return got_response; | ||
251 | } | ||
252 | |||
253 | static int usb_pcwd_start(struct usb_pcwd_private *usb_pcwd) | ||
254 | { | ||
255 | unsigned char msb = 0x00; | ||
256 | unsigned char lsb = 0x00; | ||
257 | int retval; | ||
258 | |||
259 | /* Enable Watchdog */ | ||
260 | retval = usb_pcwd_send_command(usb_pcwd, CMD_ENABLE_WATCHDOG, &msb, &lsb); | ||
261 | |||
262 | if ((retval == 0) || (lsb == 0)) { | ||
263 | printk(KERN_ERR PFX "Card did not acknowledge enable attempt\n"); | ||
264 | return -1; | ||
265 | } | ||
266 | |||
267 | return 0; | ||
268 | } | ||
269 | |||
270 | static int usb_pcwd_stop(struct usb_pcwd_private *usb_pcwd) | ||
271 | { | ||
272 | unsigned char msb = 0xA5; | ||
273 | unsigned char lsb = 0xC3; | ||
274 | int retval; | ||
275 | |||
276 | /* Disable Watchdog */ | ||
277 | retval = usb_pcwd_send_command(usb_pcwd, CMD_DISABLE_WATCHDOG, &msb, &lsb); | ||
278 | |||
279 | if ((retval == 0) || (lsb != 0)) { | ||
280 | printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n"); | ||
281 | return -1; | ||
282 | } | ||
283 | |||
284 | return 0; | ||
285 | } | ||
286 | |||
287 | static int usb_pcwd_keepalive(struct usb_pcwd_private *usb_pcwd) | ||
288 | { | ||
289 | unsigned char dummy; | ||
290 | |||
291 | /* Re-trigger Watchdog */ | ||
292 | usb_pcwd_send_command(usb_pcwd, CMD_TRIGGER, &dummy, &dummy); | ||
293 | |||
294 | return 0; | ||
295 | } | ||
296 | |||
297 | static int usb_pcwd_set_heartbeat(struct usb_pcwd_private *usb_pcwd, int t) | ||
298 | { | ||
299 | unsigned char msb = t / 256; | ||
300 | unsigned char lsb = t % 256; | ||
301 | |||
302 | if ((t < 0x0001) || (t > 0xFFFF)) | ||
303 | return -EINVAL; | ||
304 | |||
305 | /* Write new heartbeat to watchdog */ | ||
306 | usb_pcwd_send_command(usb_pcwd, CMD_WRITE_WATCHDOG_TIMEOUT, &msb, &lsb); | ||
307 | |||
308 | heartbeat = t; | ||
309 | return 0; | ||
310 | } | ||
311 | |||
312 | static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd, int *temperature) | ||
313 | { | ||
314 | unsigned char msb, lsb; | ||
315 | |||
316 | usb_pcwd_send_command(usb_pcwd, CMD_READ_TEMP, &msb, &lsb); | ||
317 | |||
318 | /* | ||
319 | * Convert celsius to fahrenheit, since this was | ||
320 | * the decided 'standard' for this return value. | ||
321 | */ | ||
322 | *temperature = (lsb * 9 / 5) + 32; | ||
323 | |||
324 | return 0; | ||
325 | } | ||
326 | |||
327 | static int usb_pcwd_get_timeleft(struct usb_pcwd_private *usb_pcwd, int *time_left) | ||
328 | { | ||
329 | unsigned char msb, lsb; | ||
330 | |||
331 | /* Read the time that's left before rebooting */ | ||
332 | /* Note: if the board is not yet armed then we will read 0xFFFF */ | ||
333 | usb_pcwd_send_command(usb_pcwd, CMD_READ_WATCHDOG_TIMEOUT, &msb, &lsb); | ||
334 | |||
335 | *time_left = (msb << 8) + lsb; | ||
336 | |||
337 | return 0; | ||
338 | } | ||
339 | |||
340 | /* | ||
341 | * /dev/watchdog handling | ||
342 | */ | ||
343 | |||
344 | static ssize_t usb_pcwd_write(struct file *file, const char __user *data, | ||
345 | size_t len, loff_t *ppos) | ||
346 | { | ||
347 | /* See if we got the magic character 'V' and reload the timer */ | ||
348 | if (len) { | ||
349 | if (!nowayout) { | ||
350 | size_t i; | ||
351 | |||
352 | /* note: just in case someone wrote the magic character | ||
353 | * five months ago... */ | ||
354 | expect_release = 0; | ||
355 | |||
356 | /* scan to see whether or not we got the magic character */ | ||
357 | for (i = 0; i != len; i++) { | ||
358 | char c; | ||
359 | if(get_user(c, data+i)) | ||
360 | return -EFAULT; | ||
361 | if (c == 'V') | ||
362 | expect_release = 42; | ||
363 | } | ||
364 | } | ||
365 | |||
366 | /* someone wrote to us, we should reload the timer */ | ||
367 | usb_pcwd_keepalive(usb_pcwd_device); | ||
368 | } | ||
369 | return len; | ||
370 | } | ||
371 | |||
372 | static int usb_pcwd_ioctl(struct inode *inode, struct file *file, | ||
373 | unsigned int cmd, unsigned long arg) | ||
374 | { | ||
375 | void __user *argp = (void __user *)arg; | ||
376 | int __user *p = argp; | ||
377 | static struct watchdog_info ident = { | ||
378 | .options = WDIOF_KEEPALIVEPING | | ||
379 | WDIOF_SETTIMEOUT | | ||
380 | WDIOF_MAGICCLOSE, | ||
381 | .firmware_version = 1, | ||
382 | .identity = DRIVER_NAME, | ||
383 | }; | ||
384 | |||
385 | switch (cmd) { | ||
386 | case WDIOC_GETSUPPORT: | ||
387 | return copy_to_user(argp, &ident, | ||
388 | sizeof (ident)) ? -EFAULT : 0; | ||
389 | |||
390 | case WDIOC_GETSTATUS: | ||
391 | case WDIOC_GETBOOTSTATUS: | ||
392 | return put_user(0, p); | ||
393 | |||
394 | case WDIOC_GETTEMP: | ||
395 | { | ||
396 | int temperature; | ||
397 | |||
398 | if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature)) | ||
399 | return -EFAULT; | ||
400 | |||
401 | return put_user(temperature, p); | ||
402 | } | ||
403 | |||
404 | case WDIOC_KEEPALIVE: | ||
405 | usb_pcwd_keepalive(usb_pcwd_device); | ||
406 | return 0; | ||
407 | |||
408 | case WDIOC_SETOPTIONS: | ||
409 | { | ||
410 | int new_options, retval = -EINVAL; | ||
411 | |||
412 | if (get_user (new_options, p)) | ||
413 | return -EFAULT; | ||
414 | |||
415 | if (new_options & WDIOS_DISABLECARD) { | ||
416 | usb_pcwd_stop(usb_pcwd_device); | ||
417 | retval = 0; | ||
418 | } | ||
419 | |||
420 | if (new_options & WDIOS_ENABLECARD) { | ||
421 | usb_pcwd_start(usb_pcwd_device); | ||
422 | retval = 0; | ||
423 | } | ||
424 | |||
425 | return retval; | ||
426 | } | ||
427 | |||
428 | case WDIOC_SETTIMEOUT: | ||
429 | { | ||
430 | int new_heartbeat; | ||
431 | |||
432 | if (get_user(new_heartbeat, p)) | ||
433 | return -EFAULT; | ||
434 | |||
435 | if (usb_pcwd_set_heartbeat(usb_pcwd_device, new_heartbeat)) | ||
436 | return -EINVAL; | ||
437 | |||
438 | usb_pcwd_keepalive(usb_pcwd_device); | ||
439 | /* Fall */ | ||
440 | } | ||
441 | |||
442 | case WDIOC_GETTIMEOUT: | ||
443 | return put_user(heartbeat, p); | ||
444 | |||
445 | case WDIOC_GETTIMELEFT: | ||
446 | { | ||
447 | int time_left; | ||
448 | |||
449 | if (usb_pcwd_get_timeleft(usb_pcwd_device, &time_left)) | ||
450 | return -EFAULT; | ||
451 | |||
452 | return put_user(time_left, p); | ||
453 | } | ||
454 | |||
455 | default: | ||
456 | return -ENOTTY; | ||
457 | } | ||
458 | } | ||
459 | |||
460 | static int usb_pcwd_open(struct inode *inode, struct file *file) | ||
461 | { | ||
462 | /* /dev/watchdog can only be opened once */ | ||
463 | if (test_and_set_bit(0, &is_active)) | ||
464 | return -EBUSY; | ||
465 | |||
466 | /* Activate */ | ||
467 | usb_pcwd_start(usb_pcwd_device); | ||
468 | usb_pcwd_keepalive(usb_pcwd_device); | ||
469 | return nonseekable_open(inode, file); | ||
470 | } | ||
471 | |||
472 | static int usb_pcwd_release(struct inode *inode, struct file *file) | ||
473 | { | ||
474 | /* | ||
475 | * Shut off the timer. | ||
476 | */ | ||
477 | if (expect_release == 42) { | ||
478 | usb_pcwd_stop(usb_pcwd_device); | ||
479 | } else { | ||
480 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
481 | usb_pcwd_keepalive(usb_pcwd_device); | ||
482 | } | ||
483 | expect_release = 0; | ||
484 | clear_bit(0, &is_active); | ||
485 | return 0; | ||
486 | } | ||
487 | |||
488 | /* | ||
489 | * /dev/temperature handling | ||
490 | */ | ||
491 | |||
492 | static ssize_t usb_pcwd_temperature_read(struct file *file, char __user *data, | ||
493 | size_t len, loff_t *ppos) | ||
494 | { | ||
495 | int temperature; | ||
496 | |||
497 | if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature)) | ||
498 | return -EFAULT; | ||
499 | |||
500 | if (copy_to_user(data, &temperature, 1)) | ||
501 | return -EFAULT; | ||
502 | |||
503 | return 1; | ||
504 | } | ||
505 | |||
506 | static int usb_pcwd_temperature_open(struct inode *inode, struct file *file) | ||
507 | { | ||
508 | return nonseekable_open(inode, file); | ||
509 | } | ||
510 | |||
511 | static int usb_pcwd_temperature_release(struct inode *inode, struct file *file) | ||
512 | { | ||
513 | return 0; | ||
514 | } | ||
515 | |||
516 | /* | ||
517 | * Notify system | ||
518 | */ | ||
519 | |||
520 | static int usb_pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
521 | { | ||
522 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
523 | /* Turn the WDT off */ | ||
524 | usb_pcwd_stop(usb_pcwd_device); | ||
525 | } | ||
526 | |||
527 | return NOTIFY_DONE; | ||
528 | } | ||
529 | |||
530 | /* | ||
531 | * Kernel Interfaces | ||
532 | */ | ||
533 | |||
534 | static const struct file_operations usb_pcwd_fops = { | ||
535 | .owner = THIS_MODULE, | ||
536 | .llseek = no_llseek, | ||
537 | .write = usb_pcwd_write, | ||
538 | .ioctl = usb_pcwd_ioctl, | ||
539 | .open = usb_pcwd_open, | ||
540 | .release = usb_pcwd_release, | ||
541 | }; | ||
542 | |||
543 | static struct miscdevice usb_pcwd_miscdev = { | ||
544 | .minor = WATCHDOG_MINOR, | ||
545 | .name = "watchdog", | ||
546 | .fops = &usb_pcwd_fops, | ||
547 | }; | ||
548 | |||
549 | static const struct file_operations usb_pcwd_temperature_fops = { | ||
550 | .owner = THIS_MODULE, | ||
551 | .llseek = no_llseek, | ||
552 | .read = usb_pcwd_temperature_read, | ||
553 | .open = usb_pcwd_temperature_open, | ||
554 | .release = usb_pcwd_temperature_release, | ||
555 | }; | ||
556 | |||
557 | static struct miscdevice usb_pcwd_temperature_miscdev = { | ||
558 | .minor = TEMP_MINOR, | ||
559 | .name = "temperature", | ||
560 | .fops = &usb_pcwd_temperature_fops, | ||
561 | }; | ||
562 | |||
563 | static struct notifier_block usb_pcwd_notifier = { | ||
564 | .notifier_call = usb_pcwd_notify_sys, | ||
565 | }; | ||
566 | |||
567 | /** | ||
568 | * usb_pcwd_delete | ||
569 | */ | ||
570 | static inline void usb_pcwd_delete (struct usb_pcwd_private *usb_pcwd) | ||
571 | { | ||
572 | usb_free_urb(usb_pcwd->intr_urb); | ||
573 | if (usb_pcwd->intr_buffer != NULL) | ||
574 | usb_buffer_free(usb_pcwd->udev, usb_pcwd->intr_size, | ||
575 | usb_pcwd->intr_buffer, usb_pcwd->intr_dma); | ||
576 | kfree (usb_pcwd); | ||
577 | } | ||
578 | |||
579 | /** | ||
580 | * usb_pcwd_probe | ||
581 | * | ||
582 | * Called by the usb core when a new device is connected that it thinks | ||
583 | * this driver might be interested in. | ||
584 | */ | ||
585 | static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_device_id *id) | ||
586 | { | ||
587 | struct usb_device *udev = interface_to_usbdev(interface); | ||
588 | struct usb_host_interface *iface_desc; | ||
589 | struct usb_endpoint_descriptor *endpoint; | ||
590 | struct usb_pcwd_private *usb_pcwd = NULL; | ||
591 | int pipe, maxp; | ||
592 | int retval = -ENOMEM; | ||
593 | int got_fw_rev; | ||
594 | unsigned char fw_rev_major, fw_rev_minor; | ||
595 | char fw_ver_str[20]; | ||
596 | unsigned char option_switches, dummy; | ||
597 | |||
598 | cards_found++; | ||
599 | if (cards_found > 1) { | ||
600 | printk(KERN_ERR PFX "This driver only supports 1 device\n"); | ||
601 | return -ENODEV; | ||
602 | } | ||
603 | |||
604 | /* get the active interface descriptor */ | ||
605 | iface_desc = interface->cur_altsetting; | ||
606 | |||
607 | /* check out that we have a HID device */ | ||
608 | if (!(iface_desc->desc.bInterfaceClass == USB_CLASS_HID)) { | ||
609 | printk(KERN_ERR PFX "The device isn't a Human Interface Device\n"); | ||
610 | return -ENODEV; | ||
611 | } | ||
612 | |||
613 | /* check out the endpoint: it has to be Interrupt & IN */ | ||
614 | endpoint = &iface_desc->endpoint[0].desc; | ||
615 | |||
616 | if (!((endpoint->bEndpointAddress & USB_DIR_IN) && | ||
617 | ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) | ||
618 | == USB_ENDPOINT_XFER_INT))) { | ||
619 | /* we didn't find a Interrupt endpoint with direction IN */ | ||
620 | printk(KERN_ERR PFX "Couldn't find an INTR & IN endpoint\n"); | ||
621 | return -ENODEV; | ||
622 | } | ||
623 | |||
624 | /* get a handle to the interrupt data pipe */ | ||
625 | pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); | ||
626 | maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); | ||
627 | |||
628 | /* allocate memory for our device and initialize it */ | ||
629 | usb_pcwd = kzalloc (sizeof(struct usb_pcwd_private), GFP_KERNEL); | ||
630 | if (usb_pcwd == NULL) { | ||
631 | printk(KERN_ERR PFX "Out of memory\n"); | ||
632 | goto error; | ||
633 | } | ||
634 | |||
635 | usb_pcwd_device = usb_pcwd; | ||
636 | |||
637 | mutex_init(&usb_pcwd->mtx); | ||
638 | usb_pcwd->udev = udev; | ||
639 | usb_pcwd->interface = interface; | ||
640 | usb_pcwd->interface_number = iface_desc->desc.bInterfaceNumber; | ||
641 | usb_pcwd->intr_size = (le16_to_cpu(endpoint->wMaxPacketSize) > 8 ? le16_to_cpu(endpoint->wMaxPacketSize) : 8); | ||
642 | |||
643 | /* set up the memory buffer's */ | ||
644 | if (!(usb_pcwd->intr_buffer = usb_buffer_alloc(udev, usb_pcwd->intr_size, GFP_ATOMIC, &usb_pcwd->intr_dma))) { | ||
645 | printk(KERN_ERR PFX "Out of memory\n"); | ||
646 | goto error; | ||
647 | } | ||
648 | |||
649 | /* allocate the urb's */ | ||
650 | usb_pcwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL); | ||
651 | if (!usb_pcwd->intr_urb) { | ||
652 | printk(KERN_ERR PFX "Out of memory\n"); | ||
653 | goto error; | ||
654 | } | ||
655 | |||
656 | /* initialise the intr urb's */ | ||
657 | usb_fill_int_urb(usb_pcwd->intr_urb, udev, pipe, | ||
658 | usb_pcwd->intr_buffer, usb_pcwd->intr_size, | ||
659 | usb_pcwd_intr_done, usb_pcwd, endpoint->bInterval); | ||
660 | usb_pcwd->intr_urb->transfer_dma = usb_pcwd->intr_dma; | ||
661 | usb_pcwd->intr_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; | ||
662 | |||
663 | /* register our interrupt URB with the USB system */ | ||
664 | if (usb_submit_urb(usb_pcwd->intr_urb, GFP_KERNEL)) { | ||
665 | printk(KERN_ERR PFX "Problem registering interrupt URB\n"); | ||
666 | retval = -EIO; /* failure */ | ||
667 | goto error; | ||
668 | } | ||
669 | |||
670 | /* The device exists and can be communicated with */ | ||
671 | usb_pcwd->exists = 1; | ||
672 | |||
673 | /* disable card */ | ||
674 | usb_pcwd_stop(usb_pcwd); | ||
675 | |||
676 | /* Get the Firmware Version */ | ||
677 | got_fw_rev = usb_pcwd_send_command(usb_pcwd, CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor); | ||
678 | if (got_fw_rev) { | ||
679 | sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor); | ||
680 | } else { | ||
681 | sprintf(fw_ver_str, "<card no answer>"); | ||
682 | } | ||
683 | |||
684 | printk(KERN_INFO PFX "Found card (Firmware: %s) with temp option\n", | ||
685 | fw_ver_str); | ||
686 | |||
687 | /* Get switch settings */ | ||
688 | usb_pcwd_send_command(usb_pcwd, CMD_GET_DIP_SWITCH_SETTINGS, &dummy, &option_switches); | ||
689 | |||
690 | printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", | ||
691 | option_switches, | ||
692 | ((option_switches & 0x10) ? "ON" : "OFF"), | ||
693 | ((option_switches & 0x08) ? "ON" : "OFF")); | ||
694 | |||
695 | /* If heartbeat = 0 then we use the heartbeat from the dip-switches */ | ||
696 | if (heartbeat == 0) | ||
697 | heartbeat = heartbeat_tbl[(option_switches & 0x07)]; | ||
698 | |||
699 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
700 | if (usb_pcwd_set_heartbeat(usb_pcwd, heartbeat)) { | ||
701 | usb_pcwd_set_heartbeat(usb_pcwd, WATCHDOG_HEARTBEAT); | ||
702 | printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", | ||
703 | WATCHDOG_HEARTBEAT); | ||
704 | } | ||
705 | |||
706 | retval = register_reboot_notifier(&usb_pcwd_notifier); | ||
707 | if (retval != 0) { | ||
708 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
709 | retval); | ||
710 | goto error; | ||
711 | } | ||
712 | |||
713 | retval = misc_register(&usb_pcwd_temperature_miscdev); | ||
714 | if (retval != 0) { | ||
715 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
716 | TEMP_MINOR, retval); | ||
717 | goto err_out_unregister_reboot; | ||
718 | } | ||
719 | |||
720 | retval = misc_register(&usb_pcwd_miscdev); | ||
721 | if (retval != 0) { | ||
722 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
723 | WATCHDOG_MINOR, retval); | ||
724 | goto err_out_misc_deregister; | ||
725 | } | ||
726 | |||
727 | /* we can register the device now, as it is ready */ | ||
728 | usb_set_intfdata (interface, usb_pcwd); | ||
729 | |||
730 | printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
731 | heartbeat, nowayout); | ||
732 | |||
733 | return 0; | ||
734 | |||
735 | err_out_misc_deregister: | ||
736 | misc_deregister(&usb_pcwd_temperature_miscdev); | ||
737 | err_out_unregister_reboot: | ||
738 | unregister_reboot_notifier(&usb_pcwd_notifier); | ||
739 | error: | ||
740 | if (usb_pcwd) | ||
741 | usb_pcwd_delete(usb_pcwd); | ||
742 | usb_pcwd_device = NULL; | ||
743 | return retval; | ||
744 | } | ||
745 | |||
746 | |||
747 | /** | ||
748 | * usb_pcwd_disconnect | ||
749 | * | ||
750 | * Called by the usb core when the device is removed from the system. | ||
751 | * | ||
752 | * This routine guarantees that the driver will not submit any more urbs | ||
753 | * by clearing dev->udev. | ||
754 | */ | ||
755 | static void usb_pcwd_disconnect(struct usb_interface *interface) | ||
756 | { | ||
757 | struct usb_pcwd_private *usb_pcwd; | ||
758 | |||
759 | /* prevent races with open() */ | ||
760 | mutex_lock(&disconnect_mutex); | ||
761 | |||
762 | usb_pcwd = usb_get_intfdata (interface); | ||
763 | usb_set_intfdata (interface, NULL); | ||
764 | |||
765 | mutex_lock(&usb_pcwd->mtx); | ||
766 | |||
767 | /* Stop the timer before we leave */ | ||
768 | if (!nowayout) | ||
769 | usb_pcwd_stop(usb_pcwd); | ||
770 | |||
771 | /* We should now stop communicating with the USB PCWD device */ | ||
772 | usb_pcwd->exists = 0; | ||
773 | |||
774 | /* Deregister */ | ||
775 | misc_deregister(&usb_pcwd_miscdev); | ||
776 | misc_deregister(&usb_pcwd_temperature_miscdev); | ||
777 | unregister_reboot_notifier(&usb_pcwd_notifier); | ||
778 | |||
779 | mutex_unlock(&usb_pcwd->mtx); | ||
780 | |||
781 | /* Delete the USB PCWD device */ | ||
782 | usb_pcwd_delete(usb_pcwd); | ||
783 | |||
784 | cards_found--; | ||
785 | |||
786 | mutex_unlock(&disconnect_mutex); | ||
787 | |||
788 | printk(KERN_INFO PFX "USB PC Watchdog disconnected\n"); | ||
789 | } | ||
790 | |||
791 | |||
792 | |||
793 | /** | ||
794 | * usb_pcwd_init | ||
795 | */ | ||
796 | static int __init usb_pcwd_init(void) | ||
797 | { | ||
798 | int result; | ||
799 | |||
800 | /* register this driver with the USB subsystem */ | ||
801 | result = usb_register(&usb_pcwd_driver); | ||
802 | if (result) { | ||
803 | printk(KERN_ERR PFX "usb_register failed. Error number %d\n", | ||
804 | result); | ||
805 | return result; | ||
806 | } | ||
807 | |||
808 | printk(KERN_INFO PFX DRIVER_DESC " v" DRIVER_VERSION " (" DRIVER_DATE ")\n"); | ||
809 | return 0; | ||
810 | } | ||
811 | |||
812 | |||
813 | /** | ||
814 | * usb_pcwd_exit | ||
815 | */ | ||
816 | static void __exit usb_pcwd_exit(void) | ||
817 | { | ||
818 | /* deregister this driver with the USB subsystem */ | ||
819 | usb_deregister(&usb_pcwd_driver); | ||
820 | } | ||
821 | |||
822 | |||
823 | module_init (usb_pcwd_init); | ||
824 | module_exit (usb_pcwd_exit); | ||
diff --git a/drivers/watchdog/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c new file mode 100644 index 000000000000..22f8873dd092 --- /dev/null +++ b/drivers/watchdog/pnx4008_wdt.c | |||
@@ -0,0 +1,358 @@ | |||
1 | /* | ||
2 | * drivers/char/watchdog/pnx4008_wdt.c | ||
3 | * | ||
4 | * Watchdog driver for PNX4008 board | ||
5 | * | ||
6 | * Authors: Dmitry Chigirev <source@mvista.com>, | ||
7 | * Vitaly Wool <vitalywool@gmail.com> | ||
8 | * Based on sa1100 driver, | ||
9 | * Copyright (C) 2000 Oleg Drokin <green@crimea.edu> | ||
10 | * | ||
11 | * 2005-2006 (c) MontaVista Software, Inc. This file is licensed under | ||
12 | * the terms of the GNU General Public License version 2. This program | ||
13 | * is licensed "as is" without any warranty of any kind, whether express | ||
14 | * or implied. | ||
15 | */ | ||
16 | |||
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 | #include <linux/ioport.h> | ||
27 | #include <linux/device.h> | ||
28 | #include <linux/platform_device.h> | ||
29 | #include <linux/clk.h> | ||
30 | #include <linux/spinlock.h> | ||
31 | |||
32 | #include <asm/hardware.h> | ||
33 | #include <asm/uaccess.h> | ||
34 | #include <asm/io.h> | ||
35 | |||
36 | #define MODULE_NAME "PNX4008-WDT: " | ||
37 | |||
38 | /* WatchDog Timer - Chapter 23 Page 207 */ | ||
39 | |||
40 | #define DEFAULT_HEARTBEAT 19 | ||
41 | #define MAX_HEARTBEAT 60 | ||
42 | |||
43 | /* Watchdog timer register set definition */ | ||
44 | #define WDTIM_INT(p) ((p) + 0x0) | ||
45 | #define WDTIM_CTRL(p) ((p) + 0x4) | ||
46 | #define WDTIM_COUNTER(p) ((p) + 0x8) | ||
47 | #define WDTIM_MCTRL(p) ((p) + 0xC) | ||
48 | #define WDTIM_MATCH0(p) ((p) + 0x10) | ||
49 | #define WDTIM_EMR(p) ((p) + 0x14) | ||
50 | #define WDTIM_PULSE(p) ((p) + 0x18) | ||
51 | #define WDTIM_RES(p) ((p) + 0x1C) | ||
52 | |||
53 | /* WDTIM_INT bit definitions */ | ||
54 | #define MATCH_INT 1 | ||
55 | |||
56 | /* WDTIM_CTRL bit definitions */ | ||
57 | #define COUNT_ENAB 1 | ||
58 | #define RESET_COUNT (1<<1) | ||
59 | #define DEBUG_EN (1<<2) | ||
60 | |||
61 | /* WDTIM_MCTRL bit definitions */ | ||
62 | #define MR0_INT 1 | ||
63 | #undef RESET_COUNT0 | ||
64 | #define RESET_COUNT0 (1<<2) | ||
65 | #define STOP_COUNT0 (1<<2) | ||
66 | #define M_RES1 (1<<3) | ||
67 | #define M_RES2 (1<<4) | ||
68 | #define RESFRC1 (1<<5) | ||
69 | #define RESFRC2 (1<<6) | ||
70 | |||
71 | /* WDTIM_EMR bit definitions */ | ||
72 | #define EXT_MATCH0 1 | ||
73 | #define MATCH_OUTPUT_HIGH (2<<4) /*a MATCH_CTRL setting */ | ||
74 | |||
75 | /* WDTIM_RES bit definitions */ | ||
76 | #define WDOG_RESET 1 /* read only */ | ||
77 | |||
78 | #define WDOG_COUNTER_RATE 13000000 /*the counter clock is 13 MHz fixed */ | ||
79 | |||
80 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
81 | static int heartbeat = DEFAULT_HEARTBEAT; | ||
82 | |||
83 | static spinlock_t io_lock; | ||
84 | static unsigned long wdt_status; | ||
85 | #define WDT_IN_USE 0 | ||
86 | #define WDT_OK_TO_CLOSE 1 | ||
87 | #define WDT_REGION_INITED 2 | ||
88 | #define WDT_DEVICE_INITED 3 | ||
89 | |||
90 | static unsigned long boot_status; | ||
91 | |||
92 | static struct resource *wdt_mem; | ||
93 | static void __iomem *wdt_base; | ||
94 | struct clk *wdt_clk; | ||
95 | |||
96 | static void wdt_enable(void) | ||
97 | { | ||
98 | spin_lock(&io_lock); | ||
99 | |||
100 | if (wdt_clk) | ||
101 | clk_set_rate(wdt_clk, 1); | ||
102 | |||
103 | /* stop counter, initiate counter reset */ | ||
104 | __raw_writel(RESET_COUNT, WDTIM_CTRL(wdt_base)); | ||
105 | /*wait for reset to complete. 100% guarantee event */ | ||
106 | while (__raw_readl(WDTIM_COUNTER(wdt_base))) | ||
107 | cpu_relax(); | ||
108 | /* internal and external reset, stop after that */ | ||
109 | __raw_writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, | ||
110 | WDTIM_MCTRL(wdt_base)); | ||
111 | /* configure match output */ | ||
112 | __raw_writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base)); | ||
113 | /* clear interrupt, just in case */ | ||
114 | __raw_writel(MATCH_INT, WDTIM_INT(wdt_base)); | ||
115 | /* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */ | ||
116 | __raw_writel(0xFFFF, WDTIM_PULSE(wdt_base)); | ||
117 | __raw_writel(heartbeat * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base)); | ||
118 | /*enable counter, stop when debugger active */ | ||
119 | __raw_writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base)); | ||
120 | |||
121 | spin_unlock(&io_lock); | ||
122 | } | ||
123 | |||
124 | static void wdt_disable(void) | ||
125 | { | ||
126 | spin_lock(&io_lock); | ||
127 | |||
128 | __raw_writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */ | ||
129 | if (wdt_clk) | ||
130 | clk_set_rate(wdt_clk, 0); | ||
131 | |||
132 | spin_unlock(&io_lock); | ||
133 | } | ||
134 | |||
135 | static int pnx4008_wdt_open(struct inode *inode, struct file *file) | ||
136 | { | ||
137 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | ||
138 | return -EBUSY; | ||
139 | |||
140 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
141 | |||
142 | wdt_enable(); | ||
143 | |||
144 | return nonseekable_open(inode, file); | ||
145 | } | ||
146 | |||
147 | static ssize_t | ||
148 | pnx4008_wdt_write(struct file *file, const char *data, size_t len, | ||
149 | loff_t * ppos) | ||
150 | { | ||
151 | if (len) { | ||
152 | if (!nowayout) { | ||
153 | size_t i; | ||
154 | |||
155 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
156 | |||
157 | for (i = 0; i != len; i++) { | ||
158 | char c; | ||
159 | |||
160 | if (get_user(c, data + i)) | ||
161 | return -EFAULT; | ||
162 | if (c == 'V') | ||
163 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
164 | } | ||
165 | } | ||
166 | wdt_enable(); | ||
167 | } | ||
168 | |||
169 | return len; | ||
170 | } | ||
171 | |||
172 | static struct watchdog_info ident = { | ||
173 | .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | | ||
174 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
175 | .identity = "PNX4008 Watchdog", | ||
176 | }; | ||
177 | |||
178 | static int | ||
179 | pnx4008_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
180 | unsigned long arg) | ||
181 | { | ||
182 | int ret = -ENOTTY; | ||
183 | int time; | ||
184 | |||
185 | switch (cmd) { | ||
186 | case WDIOC_GETSUPPORT: | ||
187 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | ||
188 | sizeof(ident)) ? -EFAULT : 0; | ||
189 | break; | ||
190 | |||
191 | case WDIOC_GETSTATUS: | ||
192 | ret = put_user(0, (int *)arg); | ||
193 | break; | ||
194 | |||
195 | case WDIOC_GETBOOTSTATUS: | ||
196 | ret = put_user(boot_status, (int *)arg); | ||
197 | break; | ||
198 | |||
199 | case WDIOC_SETTIMEOUT: | ||
200 | ret = get_user(time, (int *)arg); | ||
201 | if (ret) | ||
202 | break; | ||
203 | |||
204 | if (time <= 0 || time > MAX_HEARTBEAT) { | ||
205 | ret = -EINVAL; | ||
206 | break; | ||
207 | } | ||
208 | |||
209 | heartbeat = time; | ||
210 | wdt_enable(); | ||
211 | /* Fall through */ | ||
212 | |||
213 | case WDIOC_GETTIMEOUT: | ||
214 | ret = put_user(heartbeat, (int *)arg); | ||
215 | break; | ||
216 | |||
217 | case WDIOC_KEEPALIVE: | ||
218 | wdt_enable(); | ||
219 | ret = 0; | ||
220 | break; | ||
221 | } | ||
222 | return ret; | ||
223 | } | ||
224 | |||
225 | static int pnx4008_wdt_release(struct inode *inode, struct file *file) | ||
226 | { | ||
227 | if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) | ||
228 | printk(KERN_WARNING "WATCHDOG: Device closed unexpectdly\n"); | ||
229 | |||
230 | wdt_disable(); | ||
231 | clear_bit(WDT_IN_USE, &wdt_status); | ||
232 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
233 | |||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | static const struct file_operations pnx4008_wdt_fops = { | ||
238 | .owner = THIS_MODULE, | ||
239 | .llseek = no_llseek, | ||
240 | .write = pnx4008_wdt_write, | ||
241 | .ioctl = pnx4008_wdt_ioctl, | ||
242 | .open = pnx4008_wdt_open, | ||
243 | .release = pnx4008_wdt_release, | ||
244 | }; | ||
245 | |||
246 | static struct miscdevice pnx4008_wdt_miscdev = { | ||
247 | .minor = WATCHDOG_MINOR, | ||
248 | .name = "watchdog", | ||
249 | .fops = &pnx4008_wdt_fops, | ||
250 | }; | ||
251 | |||
252 | static int pnx4008_wdt_probe(struct platform_device *pdev) | ||
253 | { | ||
254 | int ret = 0, size; | ||
255 | struct resource *res; | ||
256 | |||
257 | spin_lock_init(&io_lock); | ||
258 | |||
259 | if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) | ||
260 | heartbeat = DEFAULT_HEARTBEAT; | ||
261 | |||
262 | printk(KERN_INFO MODULE_NAME | ||
263 | "PNX4008 Watchdog Timer: heartbeat %d sec\n", heartbeat); | ||
264 | |||
265 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
266 | if (res == NULL) { | ||
267 | printk(KERN_INFO MODULE_NAME | ||
268 | "failed to get memory region resouce\n"); | ||
269 | return -ENOENT; | ||
270 | } | ||
271 | |||
272 | size = res->end - res->start + 1; | ||
273 | wdt_mem = request_mem_region(res->start, size, pdev->name); | ||
274 | |||
275 | if (wdt_mem == NULL) { | ||
276 | printk(KERN_INFO MODULE_NAME "failed to get memory region\n"); | ||
277 | return -ENOENT; | ||
278 | } | ||
279 | wdt_base = (void __iomem *)IO_ADDRESS(res->start); | ||
280 | |||
281 | wdt_clk = clk_get(&pdev->dev, "wdt_ck"); | ||
282 | if (IS_ERR(wdt_clk)) { | ||
283 | ret = PTR_ERR(wdt_clk); | ||
284 | release_resource(wdt_mem); | ||
285 | kfree(wdt_mem); | ||
286 | goto out; | ||
287 | } else | ||
288 | clk_set_rate(wdt_clk, 1); | ||
289 | |||
290 | ret = misc_register(&pnx4008_wdt_miscdev); | ||
291 | if (ret < 0) { | ||
292 | printk(KERN_ERR MODULE_NAME "cannot register misc device\n"); | ||
293 | release_resource(wdt_mem); | ||
294 | kfree(wdt_mem); | ||
295 | clk_set_rate(wdt_clk, 0); | ||
296 | } else { | ||
297 | boot_status = (__raw_readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? | ||
298 | WDIOF_CARDRESET : 0; | ||
299 | wdt_disable(); /*disable for now */ | ||
300 | set_bit(WDT_DEVICE_INITED, &wdt_status); | ||
301 | } | ||
302 | |||
303 | out: | ||
304 | return ret; | ||
305 | } | ||
306 | |||
307 | static int pnx4008_wdt_remove(struct platform_device *pdev) | ||
308 | { | ||
309 | misc_deregister(&pnx4008_wdt_miscdev); | ||
310 | if (wdt_clk) { | ||
311 | clk_set_rate(wdt_clk, 0); | ||
312 | clk_put(wdt_clk); | ||
313 | wdt_clk = NULL; | ||
314 | } | ||
315 | if (wdt_mem) { | ||
316 | release_resource(wdt_mem); | ||
317 | kfree(wdt_mem); | ||
318 | wdt_mem = NULL; | ||
319 | } | ||
320 | return 0; | ||
321 | } | ||
322 | |||
323 | static struct platform_driver platform_wdt_driver = { | ||
324 | .driver = { | ||
325 | .name = "watchdog", | ||
326 | }, | ||
327 | .probe = pnx4008_wdt_probe, | ||
328 | .remove = pnx4008_wdt_remove, | ||
329 | }; | ||
330 | |||
331 | static int __init pnx4008_wdt_init(void) | ||
332 | { | ||
333 | return platform_driver_register(&platform_wdt_driver); | ||
334 | } | ||
335 | |||
336 | static void __exit pnx4008_wdt_exit(void) | ||
337 | { | ||
338 | return platform_driver_unregister(&platform_wdt_driver); | ||
339 | } | ||
340 | |||
341 | module_init(pnx4008_wdt_init); | ||
342 | module_exit(pnx4008_wdt_exit); | ||
343 | |||
344 | MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); | ||
345 | MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); | ||
346 | |||
347 | module_param(heartbeat, int, 0); | ||
348 | MODULE_PARM_DESC(heartbeat, | ||
349 | "Watchdog heartbeat period in seconds from 1 to " | ||
350 | __MODULE_STRING(MAX_HEARTBEAT) ", default " | ||
351 | __MODULE_STRING(DEFAULT_HEARTBEAT)); | ||
352 | |||
353 | module_param(nowayout, int, 0); | ||
354 | MODULE_PARM_DESC(nowayout, | ||
355 | "Set to 1 to keep watchdog running after device release"); | ||
356 | |||
357 | MODULE_LICENSE("GPL"); | ||
358 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/rm9k_wdt.c b/drivers/watchdog/rm9k_wdt.c new file mode 100644 index 000000000000..5c921e471564 --- /dev/null +++ b/drivers/watchdog/rm9k_wdt.c | |||
@@ -0,0 +1,420 @@ | |||
1 | /* | ||
2 | * Watchdog implementation for GPI h/w found on PMC-Sierra RM9xxx | ||
3 | * chips. | ||
4 | * | ||
5 | * Copyright (C) 2004 by Basler Vision Technologies AG | ||
6 | * Author: Thomas Koeller <thomas.koeller@baslerweb.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
21 | */ | ||
22 | |||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/moduleparam.h> | ||
26 | #include <linux/interrupt.h> | ||
27 | #include <linux/fs.h> | ||
28 | #include <linux/reboot.h> | ||
29 | #include <linux/notifier.h> | ||
30 | #include <linux/miscdevice.h> | ||
31 | #include <linux/watchdog.h> | ||
32 | #include <asm/io.h> | ||
33 | #include <asm/atomic.h> | ||
34 | #include <asm/processor.h> | ||
35 | #include <asm/uaccess.h> | ||
36 | #include <asm/system.h> | ||
37 | #include <asm/rm9k-ocd.h> | ||
38 | |||
39 | #include <rm9k_wdt.h> | ||
40 | |||
41 | |||
42 | #define CLOCK 125000000 | ||
43 | #define MAX_TIMEOUT_SECONDS 32 | ||
44 | #define CPCCR 0x0080 | ||
45 | #define CPGIG1SR 0x0044 | ||
46 | #define CPGIG1ER 0x0054 | ||
47 | |||
48 | |||
49 | /* Function prototypes */ | ||
50 | static irqreturn_t wdt_gpi_irqhdl(int, void *); | ||
51 | static void wdt_gpi_start(void); | ||
52 | static void wdt_gpi_stop(void); | ||
53 | static void wdt_gpi_set_timeout(unsigned int); | ||
54 | static int wdt_gpi_open(struct inode *, struct file *); | ||
55 | static int wdt_gpi_release(struct inode *, struct file *); | ||
56 | static ssize_t wdt_gpi_write(struct file *, const char __user *, size_t, loff_t *); | ||
57 | static long wdt_gpi_ioctl(struct file *, unsigned int, unsigned long); | ||
58 | static int wdt_gpi_notify(struct notifier_block *, unsigned long, void *); | ||
59 | static const struct resource *wdt_gpi_get_resource(struct platform_device *, const char *, unsigned int); | ||
60 | static int __init wdt_gpi_probe(struct device *); | ||
61 | static int __exit wdt_gpi_remove(struct device *); | ||
62 | |||
63 | |||
64 | static const char wdt_gpi_name[] = "wdt_gpi"; | ||
65 | static atomic_t opencnt; | ||
66 | static int expect_close; | ||
67 | static int locked; | ||
68 | |||
69 | |||
70 | /* These are set from device resources */ | ||
71 | static void __iomem * wd_regs; | ||
72 | static unsigned int wd_irq, wd_ctr; | ||
73 | |||
74 | |||
75 | /* Module arguments */ | ||
76 | static int timeout = MAX_TIMEOUT_SECONDS; | ||
77 | module_param(timeout, int, 0444); | ||
78 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); | ||
79 | |||
80 | static unsigned long resetaddr = 0xbffdc200; | ||
81 | module_param(resetaddr, ulong, 0444); | ||
82 | MODULE_PARM_DESC(resetaddr, "Address to write to to force a reset"); | ||
83 | |||
84 | static unsigned long flagaddr = 0xbffdc104; | ||
85 | module_param(flagaddr, ulong, 0444); | ||
86 | MODULE_PARM_DESC(flagaddr, "Address to write to boot flags to"); | ||
87 | |||
88 | static int powercycle; | ||
89 | module_param(powercycle, bool, 0444); | ||
90 | MODULE_PARM_DESC(powercycle, "Cycle power if watchdog expires"); | ||
91 | |||
92 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
93 | module_param(nowayout, bool, 0444); | ||
94 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be disabled once started"); | ||
95 | |||
96 | |||
97 | /* Kernel interfaces */ | ||
98 | static const struct file_operations fops = { | ||
99 | .owner = THIS_MODULE, | ||
100 | .open = wdt_gpi_open, | ||
101 | .release = wdt_gpi_release, | ||
102 | .write = wdt_gpi_write, | ||
103 | .unlocked_ioctl = wdt_gpi_ioctl, | ||
104 | }; | ||
105 | |||
106 | static struct miscdevice miscdev = { | ||
107 | .minor = WATCHDOG_MINOR, | ||
108 | .name = wdt_gpi_name, | ||
109 | .fops = &fops, | ||
110 | }; | ||
111 | |||
112 | static struct notifier_block wdt_gpi_shutdown = { | ||
113 | .notifier_call = wdt_gpi_notify, | ||
114 | }; | ||
115 | |||
116 | |||
117 | /* Interrupt handler */ | ||
118 | static irqreturn_t wdt_gpi_irqhdl(int irq, void *ctxt) | ||
119 | { | ||
120 | if (!unlikely(__raw_readl(wd_regs + 0x0008) & 0x1)) | ||
121 | return IRQ_NONE; | ||
122 | __raw_writel(0x1, wd_regs + 0x0008); | ||
123 | |||
124 | |||
125 | printk(KERN_CRIT "%s: watchdog expired - resetting system\n", | ||
126 | wdt_gpi_name); | ||
127 | |||
128 | *(volatile char *) flagaddr |= 0x01; | ||
129 | *(volatile char *) resetaddr = powercycle ? 0x01 : 0x2; | ||
130 | iob(); | ||
131 | while (1) | ||
132 | cpu_relax(); | ||
133 | } | ||
134 | |||
135 | |||
136 | /* Watchdog functions */ | ||
137 | static void wdt_gpi_start(void) | ||
138 | { | ||
139 | u32 reg; | ||
140 | |||
141 | lock_titan_regs(); | ||
142 | reg = titan_readl(CPGIG1ER); | ||
143 | titan_writel(reg | (0x100 << wd_ctr), CPGIG1ER); | ||
144 | iob(); | ||
145 | unlock_titan_regs(); | ||
146 | } | ||
147 | |||
148 | static void wdt_gpi_stop(void) | ||
149 | { | ||
150 | u32 reg; | ||
151 | |||
152 | lock_titan_regs(); | ||
153 | reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); | ||
154 | titan_writel(reg, CPCCR); | ||
155 | reg = titan_readl(CPGIG1ER); | ||
156 | titan_writel(reg & ~(0x100 << wd_ctr), CPGIG1ER); | ||
157 | iob(); | ||
158 | unlock_titan_regs(); | ||
159 | } | ||
160 | |||
161 | static void wdt_gpi_set_timeout(unsigned int to) | ||
162 | { | ||
163 | u32 reg; | ||
164 | const u32 wdval = (to * CLOCK) & ~0x0000000f; | ||
165 | |||
166 | lock_titan_regs(); | ||
167 | reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); | ||
168 | titan_writel(reg, CPCCR); | ||
169 | wmb(); | ||
170 | __raw_writel(wdval, wd_regs + 0x0000); | ||
171 | wmb(); | ||
172 | titan_writel(reg | (0x2 << (wd_ctr * 4)), CPCCR); | ||
173 | wmb(); | ||
174 | titan_writel(reg | (0x5 << (wd_ctr * 4)), CPCCR); | ||
175 | iob(); | ||
176 | unlock_titan_regs(); | ||
177 | } | ||
178 | |||
179 | |||
180 | /* /dev/watchdog operations */ | ||
181 | static int wdt_gpi_open(struct inode *inode, struct file *file) | ||
182 | { | ||
183 | int res; | ||
184 | |||
185 | if (unlikely(atomic_dec_if_positive(&opencnt) < 0)) | ||
186 | return -EBUSY; | ||
187 | |||
188 | expect_close = 0; | ||
189 | if (locked) { | ||
190 | module_put(THIS_MODULE); | ||
191 | free_irq(wd_irq, &miscdev); | ||
192 | locked = 0; | ||
193 | } | ||
194 | |||
195 | res = request_irq(wd_irq, wdt_gpi_irqhdl, IRQF_SHARED | IRQF_DISABLED, | ||
196 | wdt_gpi_name, &miscdev); | ||
197 | if (unlikely(res)) | ||
198 | return res; | ||
199 | |||
200 | wdt_gpi_set_timeout(timeout); | ||
201 | wdt_gpi_start(); | ||
202 | |||
203 | printk(KERN_INFO "%s: watchdog started, timeout = %u seconds\n", | ||
204 | wdt_gpi_name, timeout); | ||
205 | return nonseekable_open(inode, file); | ||
206 | } | ||
207 | |||
208 | static int wdt_gpi_release(struct inode *inode, struct file *file) | ||
209 | { | ||
210 | if (nowayout) { | ||
211 | printk(KERN_INFO "%s: no way out - watchdog left running\n", | ||
212 | wdt_gpi_name); | ||
213 | __module_get(THIS_MODULE); | ||
214 | locked = 1; | ||
215 | } else { | ||
216 | if (expect_close) { | ||
217 | wdt_gpi_stop(); | ||
218 | free_irq(wd_irq, &miscdev); | ||
219 | printk(KERN_INFO "%s: watchdog stopped\n", wdt_gpi_name); | ||
220 | } else { | ||
221 | printk(KERN_CRIT "%s: unexpected close() -" | ||
222 | " watchdog left running\n", | ||
223 | wdt_gpi_name); | ||
224 | wdt_gpi_set_timeout(timeout); | ||
225 | __module_get(THIS_MODULE); | ||
226 | locked = 1; | ||
227 | } | ||
228 | } | ||
229 | |||
230 | atomic_inc(&opencnt); | ||
231 | return 0; | ||
232 | } | ||
233 | |||
234 | static ssize_t | ||
235 | wdt_gpi_write(struct file *f, const char __user *d, size_t s, loff_t *o) | ||
236 | { | ||
237 | char val; | ||
238 | |||
239 | wdt_gpi_set_timeout(timeout); | ||
240 | expect_close = (s > 0) && !get_user(val, d) && (val == 'V'); | ||
241 | return s ? 1 : 0; | ||
242 | } | ||
243 | |||
244 | static long | ||
245 | wdt_gpi_ioctl(struct file *f, unsigned int cmd, unsigned long arg) | ||
246 | { | ||
247 | long res = -ENOTTY; | ||
248 | const long size = _IOC_SIZE(cmd); | ||
249 | int stat; | ||
250 | void __user *argp = (void __user *)arg; | ||
251 | static struct watchdog_info wdinfo = { | ||
252 | .identity = "RM9xxx/GPI watchdog", | ||
253 | .firmware_version = 0, | ||
254 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | ||
255 | }; | ||
256 | |||
257 | if (unlikely(_IOC_TYPE(cmd) != WATCHDOG_IOCTL_BASE)) | ||
258 | return -ENOTTY; | ||
259 | |||
260 | if ((_IOC_DIR(cmd) & _IOC_READ) | ||
261 | && !access_ok(VERIFY_WRITE, arg, size)) | ||
262 | return -EFAULT; | ||
263 | |||
264 | if ((_IOC_DIR(cmd) & _IOC_WRITE) | ||
265 | && !access_ok(VERIFY_READ, arg, size)) | ||
266 | return -EFAULT; | ||
267 | |||
268 | expect_close = 0; | ||
269 | |||
270 | switch (cmd) { | ||
271 | case WDIOC_GETSUPPORT: | ||
272 | wdinfo.options = nowayout ? | ||
273 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING : | ||
274 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE; | ||
275 | res = __copy_to_user(argp, &wdinfo, size) ? -EFAULT : size; | ||
276 | break; | ||
277 | |||
278 | case WDIOC_GETSTATUS: | ||
279 | break; | ||
280 | |||
281 | case WDIOC_GETBOOTSTATUS: | ||
282 | stat = (*(volatile char *) flagaddr & 0x01) | ||
283 | ? WDIOF_CARDRESET : 0; | ||
284 | res = __copy_to_user(argp, &stat, size) ? | ||
285 | -EFAULT : size; | ||
286 | break; | ||
287 | |||
288 | case WDIOC_SETOPTIONS: | ||
289 | break; | ||
290 | |||
291 | case WDIOC_KEEPALIVE: | ||
292 | wdt_gpi_set_timeout(timeout); | ||
293 | res = size; | ||
294 | break; | ||
295 | |||
296 | case WDIOC_SETTIMEOUT: | ||
297 | { | ||
298 | int val; | ||
299 | if (unlikely(__copy_from_user(&val, argp, size))) { | ||
300 | res = -EFAULT; | ||
301 | break; | ||
302 | } | ||
303 | |||
304 | if (val > MAX_TIMEOUT_SECONDS) | ||
305 | val = MAX_TIMEOUT_SECONDS; | ||
306 | timeout = val; | ||
307 | wdt_gpi_set_timeout(val); | ||
308 | res = size; | ||
309 | printk(KERN_INFO "%s: timeout set to %u seconds\n", | ||
310 | wdt_gpi_name, timeout); | ||
311 | } | ||
312 | break; | ||
313 | |||
314 | case WDIOC_GETTIMEOUT: | ||
315 | res = __copy_to_user(argp, &timeout, size) ? | ||
316 | -EFAULT : size; | ||
317 | break; | ||
318 | } | ||
319 | |||
320 | return res; | ||
321 | } | ||
322 | |||
323 | |||
324 | /* Shutdown notifier */ | ||
325 | static int | ||
326 | wdt_gpi_notify(struct notifier_block *this, unsigned long code, void *unused) | ||
327 | { | ||
328 | if (code == SYS_DOWN || code == SYS_HALT) | ||
329 | wdt_gpi_stop(); | ||
330 | |||
331 | return NOTIFY_DONE; | ||
332 | } | ||
333 | |||
334 | |||
335 | /* Init & exit procedures */ | ||
336 | static const struct resource * | ||
337 | wdt_gpi_get_resource(struct platform_device *pdv, const char *name, | ||
338 | unsigned int type) | ||
339 | { | ||
340 | char buf[80]; | ||
341 | if (snprintf(buf, sizeof buf, "%s_0", name) >= sizeof buf) | ||
342 | return NULL; | ||
343 | return platform_get_resource_byname(pdv, type, buf); | ||
344 | } | ||
345 | |||
346 | /* No hotplugging on the platform bus - use __init */ | ||
347 | static int __init wdt_gpi_probe(struct device *dev) | ||
348 | { | ||
349 | int res; | ||
350 | struct platform_device * const pdv = to_platform_device(dev); | ||
351 | const struct resource | ||
352 | * const rr = wdt_gpi_get_resource(pdv, WDT_RESOURCE_REGS, | ||
353 | IORESOURCE_MEM), | ||
354 | * const ri = wdt_gpi_get_resource(pdv, WDT_RESOURCE_IRQ, | ||
355 | IORESOURCE_IRQ), | ||
356 | * const rc = wdt_gpi_get_resource(pdv, WDT_RESOURCE_COUNTER, | ||
357 | 0); | ||
358 | |||
359 | if (unlikely(!rr || !ri || !rc)) | ||
360 | return -ENXIO; | ||
361 | |||
362 | wd_regs = ioremap_nocache(rr->start, rr->end + 1 - rr->start); | ||
363 | if (unlikely(!wd_regs)) | ||
364 | return -ENOMEM; | ||
365 | wd_irq = ri->start; | ||
366 | wd_ctr = rc->start; | ||
367 | res = misc_register(&miscdev); | ||
368 | if (res) | ||
369 | iounmap(wd_regs); | ||
370 | else | ||
371 | register_reboot_notifier(&wdt_gpi_shutdown); | ||
372 | return res; | ||
373 | } | ||
374 | |||
375 | static int __exit wdt_gpi_remove(struct device *dev) | ||
376 | { | ||
377 | int res; | ||
378 | |||
379 | unregister_reboot_notifier(&wdt_gpi_shutdown); | ||
380 | res = misc_deregister(&miscdev); | ||
381 | iounmap(wd_regs); | ||
382 | wd_regs = NULL; | ||
383 | return res; | ||
384 | } | ||
385 | |||
386 | |||
387 | /* Device driver init & exit */ | ||
388 | static struct device_driver wdt_gpi_driver = { | ||
389 | .name = (char *) wdt_gpi_name, | ||
390 | .bus = &platform_bus_type, | ||
391 | .owner = THIS_MODULE, | ||
392 | .probe = wdt_gpi_probe, | ||
393 | .remove = __exit_p(wdt_gpi_remove), | ||
394 | .shutdown = NULL, | ||
395 | .suspend = NULL, | ||
396 | .resume = NULL, | ||
397 | }; | ||
398 | |||
399 | static int __init wdt_gpi_init_module(void) | ||
400 | { | ||
401 | atomic_set(&opencnt, 1); | ||
402 | if (timeout > MAX_TIMEOUT_SECONDS) | ||
403 | timeout = MAX_TIMEOUT_SECONDS; | ||
404 | return driver_register(&wdt_gpi_driver); | ||
405 | } | ||
406 | |||
407 | static void __exit wdt_gpi_cleanup_module(void) | ||
408 | { | ||
409 | driver_unregister(&wdt_gpi_driver); | ||
410 | } | ||
411 | |||
412 | module_init(wdt_gpi_init_module); | ||
413 | module_exit(wdt_gpi_cleanup_module); | ||
414 | |||
415 | MODULE_AUTHOR("Thomas Koeller <thomas.koeller@baslerweb.com>"); | ||
416 | MODULE_DESCRIPTION("Basler eXcite watchdog driver for gpi devices"); | ||
417 | MODULE_VERSION("0.1"); | ||
418 | MODULE_LICENSE("GPL"); | ||
419 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
420 | |||
diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c new file mode 100644 index 000000000000..5d1c15f83d23 --- /dev/null +++ b/drivers/watchdog/s3c2410_wdt.c | |||
@@ -0,0 +1,563 @@ | |||
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-2005 BJD Fixed divide-by-2 in timeout code | ||
31 | * | ||
32 | * 25-Jan-2005 DA Added suspend/resume support | ||
33 | * Replaced reboot notifier with .shutdown method | ||
34 | * | ||
35 | * 10-Mar-2005 LCVR Changed S3C2410_VA to S3C24XX_VA | ||
36 | */ | ||
37 | |||
38 | #include <linux/module.h> | ||
39 | #include <linux/moduleparam.h> | ||
40 | #include <linux/types.h> | ||
41 | #include <linux/timer.h> | ||
42 | #include <linux/miscdevice.h> | ||
43 | #include <linux/watchdog.h> | ||
44 | #include <linux/fs.h> | ||
45 | #include <linux/init.h> | ||
46 | #include <linux/platform_device.h> | ||
47 | #include <linux/interrupt.h> | ||
48 | #include <linux/clk.h> | ||
49 | |||
50 | #include <asm/uaccess.h> | ||
51 | #include <asm/io.h> | ||
52 | |||
53 | #include <asm/arch/map.h> | ||
54 | |||
55 | #undef S3C_VA_WATCHDOG | ||
56 | #define S3C_VA_WATCHDOG (0) | ||
57 | |||
58 | #include <asm/plat-s3c/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 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
66 | static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; | ||
67 | static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; | ||
68 | static int soft_noboot = 0; | ||
69 | static int debug = 0; | ||
70 | |||
71 | module_param(tmr_margin, int, 0); | ||
72 | module_param(tmr_atboot, int, 0); | ||
73 | module_param(nowayout, int, 0); | ||
74 | module_param(soft_noboot, int, 0); | ||
75 | module_param(debug, int, 0); | ||
76 | |||
77 | MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); | ||
78 | |||
79 | MODULE_PARM_DESC(tmr_atboot, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT)); | ||
80 | |||
81 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
82 | |||
83 | MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); | ||
84 | |||
85 | MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)"); | ||
86 | |||
87 | |||
88 | typedef enum close_state { | ||
89 | CLOSE_STATE_NOT, | ||
90 | CLOSE_STATE_ALLOW=0x4021 | ||
91 | } close_state_t; | ||
92 | |||
93 | static DECLARE_MUTEX(open_lock); | ||
94 | |||
95 | static struct device *wdt_dev; /* platform device attached to */ | ||
96 | static struct resource *wdt_mem; | ||
97 | static struct resource *wdt_irq; | ||
98 | static struct clk *wdt_clock; | ||
99 | static void __iomem *wdt_base; | ||
100 | static unsigned int wdt_count; | ||
101 | static close_state_t allow_close; | ||
102 | |||
103 | /* watchdog control routines */ | ||
104 | |||
105 | #define DBG(msg...) do { \ | ||
106 | if (debug) \ | ||
107 | printk(KERN_INFO msg); \ | ||
108 | } while(0) | ||
109 | |||
110 | /* functions */ | ||
111 | |||
112 | static int s3c2410wdt_keepalive(void) | ||
113 | { | ||
114 | writel(wdt_count, wdt_base + S3C2410_WTCNT); | ||
115 | return 0; | ||
116 | } | ||
117 | |||
118 | static int s3c2410wdt_stop(void) | ||
119 | { | ||
120 | unsigned long wtcon; | ||
121 | |||
122 | wtcon = readl(wdt_base + S3C2410_WTCON); | ||
123 | wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); | ||
124 | writel(wtcon, wdt_base + S3C2410_WTCON); | ||
125 | |||
126 | return 0; | ||
127 | } | ||
128 | |||
129 | static int s3c2410wdt_start(void) | ||
130 | { | ||
131 | unsigned long wtcon; | ||
132 | |||
133 | s3c2410wdt_stop(); | ||
134 | |||
135 | wtcon = readl(wdt_base + S3C2410_WTCON); | ||
136 | wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; | ||
137 | |||
138 | if (soft_noboot) { | ||
139 | wtcon |= S3C2410_WTCON_INTEN; | ||
140 | wtcon &= ~S3C2410_WTCON_RSTEN; | ||
141 | } else { | ||
142 | wtcon &= ~S3C2410_WTCON_INTEN; | ||
143 | wtcon |= S3C2410_WTCON_RSTEN; | ||
144 | } | ||
145 | |||
146 | DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n", | ||
147 | __FUNCTION__, wdt_count, wtcon); | ||
148 | |||
149 | writel(wdt_count, wdt_base + S3C2410_WTDAT); | ||
150 | writel(wdt_count, wdt_base + S3C2410_WTCNT); | ||
151 | writel(wtcon, wdt_base + S3C2410_WTCON); | ||
152 | |||
153 | return 0; | ||
154 | } | ||
155 | |||
156 | static int s3c2410wdt_set_heartbeat(int timeout) | ||
157 | { | ||
158 | unsigned int freq = clk_get_rate(wdt_clock); | ||
159 | unsigned int count; | ||
160 | unsigned int divisor = 1; | ||
161 | unsigned long wtcon; | ||
162 | |||
163 | if (timeout < 1) | ||
164 | return -EINVAL; | ||
165 | |||
166 | freq /= 128; | ||
167 | count = timeout * freq; | ||
168 | |||
169 | DBG("%s: count=%d, timeout=%d, freq=%d\n", | ||
170 | __FUNCTION__, count, timeout, freq); | ||
171 | |||
172 | /* if the count is bigger than the watchdog register, | ||
173 | then work out what we need to do (and if) we can | ||
174 | actually make this value | ||
175 | */ | ||
176 | |||
177 | if (count >= 0x10000) { | ||
178 | for (divisor = 1; divisor <= 0x100; divisor++) { | ||
179 | if ((count / divisor) < 0x10000) | ||
180 | break; | ||
181 | } | ||
182 | |||
183 | if ((count / divisor) >= 0x10000) { | ||
184 | dev_err(wdt_dev, "timeout %d too big\n", timeout); | ||
185 | return -EINVAL; | ||
186 | } | ||
187 | } | ||
188 | |||
189 | tmr_margin = timeout; | ||
190 | |||
191 | DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", | ||
192 | __FUNCTION__, timeout, divisor, count, count/divisor); | ||
193 | |||
194 | count /= divisor; | ||
195 | wdt_count = count; | ||
196 | |||
197 | /* update the pre-scaler */ | ||
198 | wtcon = readl(wdt_base + S3C2410_WTCON); | ||
199 | wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; | ||
200 | wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); | ||
201 | |||
202 | writel(count, wdt_base + S3C2410_WTDAT); | ||
203 | writel(wtcon, wdt_base + S3C2410_WTCON); | ||
204 | |||
205 | return 0; | ||
206 | } | ||
207 | |||
208 | /* | ||
209 | * /dev/watchdog handling | ||
210 | */ | ||
211 | |||
212 | static int s3c2410wdt_open(struct inode *inode, struct file *file) | ||
213 | { | ||
214 | if(down_trylock(&open_lock)) | ||
215 | return -EBUSY; | ||
216 | |||
217 | if (nowayout) | ||
218 | __module_get(THIS_MODULE); | ||
219 | |||
220 | allow_close = CLOSE_STATE_NOT; | ||
221 | |||
222 | /* start the timer */ | ||
223 | s3c2410wdt_start(); | ||
224 | return nonseekable_open(inode, file); | ||
225 | } | ||
226 | |||
227 | static int s3c2410wdt_release(struct inode *inode, struct file *file) | ||
228 | { | ||
229 | /* | ||
230 | * Shut off the timer. | ||
231 | * Lock it in if it's a module and we set nowayout | ||
232 | */ | ||
233 | |||
234 | if (allow_close == CLOSE_STATE_ALLOW) { | ||
235 | s3c2410wdt_stop(); | ||
236 | } else { | ||
237 | dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n"); | ||
238 | s3c2410wdt_keepalive(); | ||
239 | } | ||
240 | |||
241 | allow_close = CLOSE_STATE_NOT; | ||
242 | up(&open_lock); | ||
243 | return 0; | ||
244 | } | ||
245 | |||
246 | static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, | ||
247 | size_t len, loff_t *ppos) | ||
248 | { | ||
249 | /* | ||
250 | * Refresh the timer. | ||
251 | */ | ||
252 | if(len) { | ||
253 | if (!nowayout) { | ||
254 | size_t i; | ||
255 | |||
256 | /* In case it was set long ago */ | ||
257 | allow_close = CLOSE_STATE_NOT; | ||
258 | |||
259 | for (i = 0; i != len; i++) { | ||
260 | char c; | ||
261 | |||
262 | if (get_user(c, data + i)) | ||
263 | return -EFAULT; | ||
264 | if (c == 'V') | ||
265 | allow_close = CLOSE_STATE_ALLOW; | ||
266 | } | ||
267 | } | ||
268 | |||
269 | s3c2410wdt_keepalive(); | ||
270 | } | ||
271 | return len; | ||
272 | } | ||
273 | |||
274 | #define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | ||
275 | |||
276 | static struct watchdog_info s3c2410_wdt_ident = { | ||
277 | .options = OPTIONS, | ||
278 | .firmware_version = 0, | ||
279 | .identity = "S3C2410 Watchdog", | ||
280 | }; | ||
281 | |||
282 | |||
283 | static int s3c2410wdt_ioctl(struct inode *inode, struct file *file, | ||
284 | unsigned int cmd, unsigned long arg) | ||
285 | { | ||
286 | void __user *argp = (void __user *)arg; | ||
287 | int __user *p = argp; | ||
288 | int new_margin; | ||
289 | |||
290 | switch (cmd) { | ||
291 | default: | ||
292 | return -ENOTTY; | ||
293 | |||
294 | case WDIOC_GETSUPPORT: | ||
295 | return copy_to_user(argp, &s3c2410_wdt_ident, | ||
296 | sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0; | ||
297 | |||
298 | case WDIOC_GETSTATUS: | ||
299 | case WDIOC_GETBOOTSTATUS: | ||
300 | return put_user(0, p); | ||
301 | |||
302 | case WDIOC_KEEPALIVE: | ||
303 | s3c2410wdt_keepalive(); | ||
304 | return 0; | ||
305 | |||
306 | case WDIOC_SETTIMEOUT: | ||
307 | if (get_user(new_margin, p)) | ||
308 | return -EFAULT; | ||
309 | |||
310 | if (s3c2410wdt_set_heartbeat(new_margin)) | ||
311 | return -EINVAL; | ||
312 | |||
313 | s3c2410wdt_keepalive(); | ||
314 | return put_user(tmr_margin, p); | ||
315 | |||
316 | case WDIOC_GETTIMEOUT: | ||
317 | return put_user(tmr_margin, p); | ||
318 | } | ||
319 | } | ||
320 | |||
321 | /* kernel interface */ | ||
322 | |||
323 | static const struct file_operations s3c2410wdt_fops = { | ||
324 | .owner = THIS_MODULE, | ||
325 | .llseek = no_llseek, | ||
326 | .write = s3c2410wdt_write, | ||
327 | .ioctl = s3c2410wdt_ioctl, | ||
328 | .open = s3c2410wdt_open, | ||
329 | .release = s3c2410wdt_release, | ||
330 | }; | ||
331 | |||
332 | static struct miscdevice s3c2410wdt_miscdev = { | ||
333 | .minor = WATCHDOG_MINOR, | ||
334 | .name = "watchdog", | ||
335 | .fops = &s3c2410wdt_fops, | ||
336 | }; | ||
337 | |||
338 | /* interrupt handler code */ | ||
339 | |||
340 | static irqreturn_t s3c2410wdt_irq(int irqno, void *param) | ||
341 | { | ||
342 | dev_info(wdt_dev, "watchdog timer expired (irq)\n"); | ||
343 | |||
344 | s3c2410wdt_keepalive(); | ||
345 | return IRQ_HANDLED; | ||
346 | } | ||
347 | /* device interface */ | ||
348 | |||
349 | static int s3c2410wdt_probe(struct platform_device *pdev) | ||
350 | { | ||
351 | struct resource *res; | ||
352 | struct device *dev; | ||
353 | unsigned int wtcon; | ||
354 | int started = 0; | ||
355 | int ret; | ||
356 | int size; | ||
357 | |||
358 | DBG("%s: probe=%p\n", __FUNCTION__, pdev); | ||
359 | |||
360 | dev = &pdev->dev; | ||
361 | wdt_dev = &pdev->dev; | ||
362 | |||
363 | /* get the memory region for the watchdog timer */ | ||
364 | |||
365 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
366 | if (res == NULL) { | ||
367 | dev_err(dev, "no memory resource specified\n"); | ||
368 | return -ENOENT; | ||
369 | } | ||
370 | |||
371 | size = (res->end-res->start)+1; | ||
372 | wdt_mem = request_mem_region(res->start, size, pdev->name); | ||
373 | if (wdt_mem == NULL) { | ||
374 | dev_err(dev, "failed to get memory region\n"); | ||
375 | ret = -ENOENT; | ||
376 | goto err_req; | ||
377 | } | ||
378 | |||
379 | wdt_base = ioremap(res->start, size); | ||
380 | if (wdt_base == 0) { | ||
381 | dev_err(dev, "failed to ioremap() region\n"); | ||
382 | ret = -EINVAL; | ||
383 | goto err_req; | ||
384 | } | ||
385 | |||
386 | DBG("probe: mapped wdt_base=%p\n", wdt_base); | ||
387 | |||
388 | wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
389 | if (wdt_irq == NULL) { | ||
390 | dev_err(dev, "no irq resource specified\n"); | ||
391 | ret = -ENOENT; | ||
392 | goto err_map; | ||
393 | } | ||
394 | |||
395 | ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev); | ||
396 | if (ret != 0) { | ||
397 | dev_err(dev, "failed to install irq (%d)\n", ret); | ||
398 | goto err_map; | ||
399 | } | ||
400 | |||
401 | wdt_clock = clk_get(&pdev->dev, "watchdog"); | ||
402 | if (IS_ERR(wdt_clock)) { | ||
403 | dev_err(dev, "failed to find watchdog clock source\n"); | ||
404 | ret = PTR_ERR(wdt_clock); | ||
405 | goto err_irq; | ||
406 | } | ||
407 | |||
408 | clk_enable(wdt_clock); | ||
409 | |||
410 | /* see if we can actually set the requested timer margin, and if | ||
411 | * not, try the default value */ | ||
412 | |||
413 | if (s3c2410wdt_set_heartbeat(tmr_margin)) { | ||
414 | started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); | ||
415 | |||
416 | if (started == 0) { | ||
417 | dev_info(dev,"tmr_margin value out of range, default %d used\n", | ||
418 | CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); | ||
419 | } else { | ||
420 | dev_info(dev, "default timer value is out of range, cannot start\n"); | ||
421 | } | ||
422 | } | ||
423 | |||
424 | ret = misc_register(&s3c2410wdt_miscdev); | ||
425 | if (ret) { | ||
426 | dev_err(dev, "cannot register miscdev on minor=%d (%d)\n", | ||
427 | WATCHDOG_MINOR, ret); | ||
428 | goto err_clk; | ||
429 | } | ||
430 | |||
431 | if (tmr_atboot && started == 0) { | ||
432 | dev_info(dev, "starting watchdog timer\n"); | ||
433 | s3c2410wdt_start(); | ||
434 | } else if (!tmr_atboot) { | ||
435 | /* if we're not enabling the watchdog, then ensure it is | ||
436 | * disabled if it has been left running from the bootloader | ||
437 | * or other source */ | ||
438 | |||
439 | s3c2410wdt_stop(); | ||
440 | } | ||
441 | |||
442 | /* print out a statement of readiness */ | ||
443 | |||
444 | wtcon = readl(wdt_base + S3C2410_WTCON); | ||
445 | |||
446 | dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n", | ||
447 | (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in", | ||
448 | (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis", | ||
449 | (wtcon & S3C2410_WTCON_INTEN) ? "" : "en"); | ||
450 | |||
451 | return 0; | ||
452 | |||
453 | err_clk: | ||
454 | clk_disable(wdt_clock); | ||
455 | clk_put(wdt_clock); | ||
456 | |||
457 | err_irq: | ||
458 | free_irq(wdt_irq->start, pdev); | ||
459 | |||
460 | err_map: | ||
461 | iounmap(wdt_base); | ||
462 | |||
463 | err_req: | ||
464 | release_resource(wdt_mem); | ||
465 | kfree(wdt_mem); | ||
466 | |||
467 | return ret; | ||
468 | } | ||
469 | |||
470 | static int s3c2410wdt_remove(struct platform_device *dev) | ||
471 | { | ||
472 | release_resource(wdt_mem); | ||
473 | kfree(wdt_mem); | ||
474 | wdt_mem = NULL; | ||
475 | |||
476 | free_irq(wdt_irq->start, dev); | ||
477 | wdt_irq = NULL; | ||
478 | |||
479 | clk_disable(wdt_clock); | ||
480 | clk_put(wdt_clock); | ||
481 | wdt_clock = NULL; | ||
482 | |||
483 | iounmap(wdt_base); | ||
484 | misc_deregister(&s3c2410wdt_miscdev); | ||
485 | return 0; | ||
486 | } | ||
487 | |||
488 | static void s3c2410wdt_shutdown(struct platform_device *dev) | ||
489 | { | ||
490 | s3c2410wdt_stop(); | ||
491 | } | ||
492 | |||
493 | #ifdef CONFIG_PM | ||
494 | |||
495 | static unsigned long wtcon_save; | ||
496 | static unsigned long wtdat_save; | ||
497 | |||
498 | static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state) | ||
499 | { | ||
500 | /* Save watchdog state, and turn it off. */ | ||
501 | wtcon_save = readl(wdt_base + S3C2410_WTCON); | ||
502 | wtdat_save = readl(wdt_base + S3C2410_WTDAT); | ||
503 | |||
504 | /* Note that WTCNT doesn't need to be saved. */ | ||
505 | s3c2410wdt_stop(); | ||
506 | |||
507 | return 0; | ||
508 | } | ||
509 | |||
510 | static int s3c2410wdt_resume(struct platform_device *dev) | ||
511 | { | ||
512 | /* Restore watchdog state. */ | ||
513 | |||
514 | writel(wtdat_save, wdt_base + S3C2410_WTDAT); | ||
515 | writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */ | ||
516 | writel(wtcon_save, wdt_base + S3C2410_WTCON); | ||
517 | |||
518 | printk(KERN_INFO PFX "watchdog %sabled\n", | ||
519 | (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); | ||
520 | |||
521 | return 0; | ||
522 | } | ||
523 | |||
524 | #else | ||
525 | #define s3c2410wdt_suspend NULL | ||
526 | #define s3c2410wdt_resume NULL | ||
527 | #endif /* CONFIG_PM */ | ||
528 | |||
529 | |||
530 | static struct platform_driver s3c2410wdt_driver = { | ||
531 | .probe = s3c2410wdt_probe, | ||
532 | .remove = s3c2410wdt_remove, | ||
533 | .shutdown = s3c2410wdt_shutdown, | ||
534 | .suspend = s3c2410wdt_suspend, | ||
535 | .resume = s3c2410wdt_resume, | ||
536 | .driver = { | ||
537 | .owner = THIS_MODULE, | ||
538 | .name = "s3c2410-wdt", | ||
539 | }, | ||
540 | }; | ||
541 | |||
542 | |||
543 | static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; | ||
544 | |||
545 | static int __init watchdog_init(void) | ||
546 | { | ||
547 | printk(banner); | ||
548 | return platform_driver_register(&s3c2410wdt_driver); | ||
549 | } | ||
550 | |||
551 | static void __exit watchdog_exit(void) | ||
552 | { | ||
553 | platform_driver_unregister(&s3c2410wdt_driver); | ||
554 | } | ||
555 | |||
556 | module_init(watchdog_init); | ||
557 | module_exit(watchdog_exit); | ||
558 | |||
559 | MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, " | ||
560 | "Dimitry Andric <dimitry.andric@tomtom.com>"); | ||
561 | MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); | ||
562 | MODULE_LICENSE("GPL"); | ||
563 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/sa1100_wdt.c b/drivers/watchdog/sa1100_wdt.c new file mode 100644 index 000000000000..3475f47aaa45 --- /dev/null +++ b/drivers/watchdog/sa1100_wdt.c | |||
@@ -0,0 +1,190 @@ | |||
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/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 | |||
29 | #ifdef CONFIG_ARCH_PXA | ||
30 | #include <asm/arch/pxa-regs.h> | ||
31 | #endif | ||
32 | |||
33 | #include <asm/hardware.h> | ||
34 | #include <asm/bitops.h> | ||
35 | #include <asm/uaccess.h> | ||
36 | |||
37 | #define OSCR_FREQ CLOCK_TICK_RATE | ||
38 | |||
39 | static unsigned long sa1100wdt_users; | ||
40 | static int pre_margin; | ||
41 | static int boot_status; | ||
42 | |||
43 | /* | ||
44 | * Allow only one person to hold it open | ||
45 | */ | ||
46 | static int sa1100dog_open(struct inode *inode, struct file *file) | ||
47 | { | ||
48 | if (test_and_set_bit(1,&sa1100wdt_users)) | ||
49 | return -EBUSY; | ||
50 | |||
51 | /* Activate SA1100 Watchdog timer */ | ||
52 | OSMR3 = OSCR + pre_margin; | ||
53 | OSSR = OSSR_M3; | ||
54 | OWER = OWER_WME; | ||
55 | OIER |= OIER_E3; | ||
56 | return nonseekable_open(inode, file); | ||
57 | } | ||
58 | |||
59 | /* | ||
60 | * The watchdog cannot be disabled. | ||
61 | * | ||
62 | * Previous comments suggested that turning off the interrupt by | ||
63 | * clearing OIER[E3] would prevent the watchdog timing out but this | ||
64 | * does not appear to be true (at least on the PXA255). | ||
65 | */ | ||
66 | static int sa1100dog_release(struct inode *inode, struct file *file) | ||
67 | { | ||
68 | printk(KERN_CRIT "WATCHDOG: Device closed - timer will not stop\n"); | ||
69 | |||
70 | clear_bit(1, &sa1100wdt_users); | ||
71 | |||
72 | return 0; | ||
73 | } | ||
74 | |||
75 | static ssize_t sa1100dog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) | ||
76 | { | ||
77 | if (len) | ||
78 | /* Refresh OSMR3 timer. */ | ||
79 | OSMR3 = OSCR + pre_margin; | ||
80 | |||
81 | return len; | ||
82 | } | ||
83 | |||
84 | static struct watchdog_info ident = { | ||
85 | .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
86 | .identity = "SA1100/PXA255 Watchdog", | ||
87 | }; | ||
88 | |||
89 | static int sa1100dog_ioctl(struct inode *inode, struct file *file, | ||
90 | unsigned int cmd, unsigned long arg) | ||
91 | { | ||
92 | int ret = -ENOTTY; | ||
93 | int time; | ||
94 | void __user *argp = (void __user *)arg; | ||
95 | int __user *p = argp; | ||
96 | |||
97 | switch (cmd) { | ||
98 | case WDIOC_GETSUPPORT: | ||
99 | ret = copy_to_user(argp, &ident, | ||
100 | sizeof(ident)) ? -EFAULT : 0; | ||
101 | break; | ||
102 | |||
103 | case WDIOC_GETSTATUS: | ||
104 | ret = put_user(0, p); | ||
105 | break; | ||
106 | |||
107 | case WDIOC_GETBOOTSTATUS: | ||
108 | ret = put_user(boot_status, p); | ||
109 | break; | ||
110 | |||
111 | case WDIOC_SETTIMEOUT: | ||
112 | ret = get_user(time, p); | ||
113 | if (ret) | ||
114 | break; | ||
115 | |||
116 | if (time <= 0 || time > 255) { | ||
117 | ret = -EINVAL; | ||
118 | break; | ||
119 | } | ||
120 | |||
121 | pre_margin = OSCR_FREQ * time; | ||
122 | OSMR3 = OSCR + pre_margin; | ||
123 | /*fall through*/ | ||
124 | |||
125 | case WDIOC_GETTIMEOUT: | ||
126 | ret = put_user(pre_margin / OSCR_FREQ, p); | ||
127 | break; | ||
128 | |||
129 | case WDIOC_KEEPALIVE: | ||
130 | OSMR3 = OSCR + pre_margin; | ||
131 | ret = 0; | ||
132 | break; | ||
133 | } | ||
134 | return ret; | ||
135 | } | ||
136 | |||
137 | static const struct file_operations sa1100dog_fops = | ||
138 | { | ||
139 | .owner = THIS_MODULE, | ||
140 | .llseek = no_llseek, | ||
141 | .write = sa1100dog_write, | ||
142 | .ioctl = sa1100dog_ioctl, | ||
143 | .open = sa1100dog_open, | ||
144 | .release = sa1100dog_release, | ||
145 | }; | ||
146 | |||
147 | static struct miscdevice sa1100dog_miscdev = | ||
148 | { | ||
149 | .minor = WATCHDOG_MINOR, | ||
150 | .name = "watchdog", | ||
151 | .fops = &sa1100dog_fops, | ||
152 | }; | ||
153 | |||
154 | static int margin __initdata = 60; /* (secs) Default is 1 minute */ | ||
155 | |||
156 | static int __init sa1100dog_init(void) | ||
157 | { | ||
158 | int ret; | ||
159 | |||
160 | /* | ||
161 | * Read the reset status, and save it for later. If | ||
162 | * we suspend, RCSR will be cleared, and the watchdog | ||
163 | * reset reason will be lost. | ||
164 | */ | ||
165 | boot_status = (RCSR & RCSR_WDR) ? WDIOF_CARDRESET : 0; | ||
166 | pre_margin = OSCR_FREQ * margin; | ||
167 | |||
168 | ret = misc_register(&sa1100dog_miscdev); | ||
169 | if (ret == 0) | ||
170 | printk("SA1100/PXA2xx Watchdog Timer: timer margin %d sec\n", | ||
171 | margin); | ||
172 | return ret; | ||
173 | } | ||
174 | |||
175 | static void __exit sa1100dog_exit(void) | ||
176 | { | ||
177 | misc_deregister(&sa1100dog_miscdev); | ||
178 | } | ||
179 | |||
180 | module_init(sa1100dog_init); | ||
181 | module_exit(sa1100dog_exit); | ||
182 | |||
183 | MODULE_AUTHOR("Oleg Drokin <green@crimea.edu>"); | ||
184 | MODULE_DESCRIPTION("SA1100/PXA2xx Watchdog"); | ||
185 | |||
186 | module_param(margin, int, 0); | ||
187 | MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); | ||
188 | |||
189 | MODULE_LICENSE("GPL"); | ||
190 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/sbc60xxwdt.c b/drivers/watchdog/sbc60xxwdt.c new file mode 100644 index 000000000000..e4f3cb6090bc --- /dev/null +++ b/drivers/watchdog/sbc60xxwdt.c | |||
@@ -0,0 +1,400 @@ | |||
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 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
102 | module_param(nowayout, int, 0); | ||
103 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
104 | |||
105 | static void wdt_timer_ping(unsigned long); | ||
106 | static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0); | ||
107 | static unsigned long next_heartbeat; | ||
108 | static unsigned long wdt_is_open; | ||
109 | static char wdt_expect_close; | ||
110 | |||
111 | /* | ||
112 | * Whack the dog | ||
113 | */ | ||
114 | |||
115 | static void wdt_timer_ping(unsigned long data) | ||
116 | { | ||
117 | /* If we got a heartbeat pulse within the WDT_US_INTERVAL | ||
118 | * we agree to ping the WDT | ||
119 | */ | ||
120 | if(time_before(jiffies, next_heartbeat)) | ||
121 | { | ||
122 | /* Ping the WDT by reading from wdt_start */ | ||
123 | inb_p(wdt_start); | ||
124 | /* Re-set the timer interval */ | ||
125 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
126 | } else { | ||
127 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
128 | } | ||
129 | } | ||
130 | |||
131 | /* | ||
132 | * Utility routines | ||
133 | */ | ||
134 | |||
135 | static void wdt_startup(void) | ||
136 | { | ||
137 | next_heartbeat = jiffies + (timeout * HZ); | ||
138 | |||
139 | /* Start the timer */ | ||
140 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
141 | printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); | ||
142 | } | ||
143 | |||
144 | static void wdt_turnoff(void) | ||
145 | { | ||
146 | /* Stop the timer */ | ||
147 | del_timer(&timer); | ||
148 | inb_p(wdt_stop); | ||
149 | printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); | ||
150 | } | ||
151 | |||
152 | static void wdt_keepalive(void) | ||
153 | { | ||
154 | /* user land ping */ | ||
155 | next_heartbeat = jiffies + (timeout * HZ); | ||
156 | } | ||
157 | |||
158 | /* | ||
159 | * /dev/watchdog handling | ||
160 | */ | ||
161 | |||
162 | static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) | ||
163 | { | ||
164 | /* See if we got the magic character 'V' and reload the timer */ | ||
165 | if(count) | ||
166 | { | ||
167 | if (!nowayout) | ||
168 | { | ||
169 | size_t ofs; | ||
170 | |||
171 | /* note: just in case someone wrote the magic character | ||
172 | * five months ago... */ | ||
173 | wdt_expect_close = 0; | ||
174 | |||
175 | /* scan to see whether or not we got the magic character */ | ||
176 | for(ofs = 0; ofs != count; ofs++) | ||
177 | { | ||
178 | char c; | ||
179 | if(get_user(c, buf+ofs)) | ||
180 | return -EFAULT; | ||
181 | if(c == 'V') | ||
182 | wdt_expect_close = 42; | ||
183 | } | ||
184 | } | ||
185 | |||
186 | /* Well, anyhow someone wrote to us, we should return that favour */ | ||
187 | wdt_keepalive(); | ||
188 | } | ||
189 | return count; | ||
190 | } | ||
191 | |||
192 | static int fop_open(struct inode * inode, struct file * file) | ||
193 | { | ||
194 | /* Just in case we're already talking to someone... */ | ||
195 | if(test_and_set_bit(0, &wdt_is_open)) | ||
196 | return -EBUSY; | ||
197 | |||
198 | if (nowayout) | ||
199 | __module_get(THIS_MODULE); | ||
200 | |||
201 | /* Good, fire up the show */ | ||
202 | wdt_startup(); | ||
203 | return nonseekable_open(inode, file); | ||
204 | } | ||
205 | |||
206 | static int fop_close(struct inode * inode, struct file * file) | ||
207 | { | ||
208 | if(wdt_expect_close == 42) | ||
209 | wdt_turnoff(); | ||
210 | else { | ||
211 | del_timer(&timer); | ||
212 | printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); | ||
213 | } | ||
214 | clear_bit(0, &wdt_is_open); | ||
215 | wdt_expect_close = 0; | ||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
220 | unsigned long arg) | ||
221 | { | ||
222 | void __user *argp = (void __user *)arg; | ||
223 | int __user *p = argp; | ||
224 | static struct watchdog_info ident= | ||
225 | { | ||
226 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
227 | .firmware_version = 1, | ||
228 | .identity = "SBC60xx", | ||
229 | }; | ||
230 | |||
231 | switch(cmd) | ||
232 | { | ||
233 | default: | ||
234 | return -ENOTTY; | ||
235 | case WDIOC_GETSUPPORT: | ||
236 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
237 | case WDIOC_GETSTATUS: | ||
238 | case WDIOC_GETBOOTSTATUS: | ||
239 | return put_user(0, p); | ||
240 | case WDIOC_KEEPALIVE: | ||
241 | wdt_keepalive(); | ||
242 | return 0; | ||
243 | case WDIOC_SETOPTIONS: | ||
244 | { | ||
245 | int new_options, retval = -EINVAL; | ||
246 | |||
247 | if(get_user(new_options, p)) | ||
248 | return -EFAULT; | ||
249 | |||
250 | if(new_options & WDIOS_DISABLECARD) { | ||
251 | wdt_turnoff(); | ||
252 | retval = 0; | ||
253 | } | ||
254 | |||
255 | if(new_options & WDIOS_ENABLECARD) { | ||
256 | wdt_startup(); | ||
257 | retval = 0; | ||
258 | } | ||
259 | |||
260 | return retval; | ||
261 | } | ||
262 | case WDIOC_SETTIMEOUT: | ||
263 | { | ||
264 | int new_timeout; | ||
265 | |||
266 | if(get_user(new_timeout, p)) | ||
267 | return -EFAULT; | ||
268 | |||
269 | if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ | ||
270 | return -EINVAL; | ||
271 | |||
272 | timeout = new_timeout; | ||
273 | wdt_keepalive(); | ||
274 | /* Fall through */ | ||
275 | } | ||
276 | case WDIOC_GETTIMEOUT: | ||
277 | return put_user(timeout, p); | ||
278 | } | ||
279 | } | ||
280 | |||
281 | static const struct file_operations wdt_fops = { | ||
282 | .owner = THIS_MODULE, | ||
283 | .llseek = no_llseek, | ||
284 | .write = fop_write, | ||
285 | .open = fop_open, | ||
286 | .release = fop_close, | ||
287 | .ioctl = fop_ioctl, | ||
288 | }; | ||
289 | |||
290 | static struct miscdevice wdt_miscdev = { | ||
291 | .minor = WATCHDOG_MINOR, | ||
292 | .name = "watchdog", | ||
293 | .fops = &wdt_fops, | ||
294 | }; | ||
295 | |||
296 | /* | ||
297 | * Notifier for system down | ||
298 | */ | ||
299 | |||
300 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
301 | void *unused) | ||
302 | { | ||
303 | if(code==SYS_DOWN || code==SYS_HALT) | ||
304 | wdt_turnoff(); | ||
305 | return NOTIFY_DONE; | ||
306 | } | ||
307 | |||
308 | /* | ||
309 | * The WDT needs to learn about soft shutdowns in order to | ||
310 | * turn the timebomb registers off. | ||
311 | */ | ||
312 | |||
313 | static struct notifier_block wdt_notifier= | ||
314 | { | ||
315 | .notifier_call = wdt_notify_sys, | ||
316 | }; | ||
317 | |||
318 | static void __exit sbc60xxwdt_unload(void) | ||
319 | { | ||
320 | wdt_turnoff(); | ||
321 | |||
322 | /* Deregister */ | ||
323 | misc_deregister(&wdt_miscdev); | ||
324 | |||
325 | unregister_reboot_notifier(&wdt_notifier); | ||
326 | if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) | ||
327 | release_region(wdt_stop,1); | ||
328 | release_region(wdt_start,1); | ||
329 | } | ||
330 | |||
331 | static int __init sbc60xxwdt_init(void) | ||
332 | { | ||
333 | int rc = -EBUSY; | ||
334 | |||
335 | if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */ | ||
336 | { | ||
337 | timeout = WATCHDOG_TIMEOUT; | ||
338 | printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", | ||
339 | timeout); | ||
340 | } | ||
341 | |||
342 | if (!request_region(wdt_start, 1, "SBC 60XX WDT")) | ||
343 | { | ||
344 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
345 | wdt_start); | ||
346 | rc = -EIO; | ||
347 | goto err_out; | ||
348 | } | ||
349 | |||
350 | /* We cannot reserve 0x45 - the kernel already has! */ | ||
351 | if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) | ||
352 | { | ||
353 | if (!request_region(wdt_stop, 1, "SBC 60XX WDT")) | ||
354 | { | ||
355 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
356 | wdt_stop); | ||
357 | rc = -EIO; | ||
358 | goto err_out_region1; | ||
359 | } | ||
360 | } | ||
361 | |||
362 | rc = misc_register(&wdt_miscdev); | ||
363 | if (rc) | ||
364 | { | ||
365 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
366 | wdt_miscdev.minor, rc); | ||
367 | goto err_out_region2; | ||
368 | } | ||
369 | |||
370 | rc = register_reboot_notifier(&wdt_notifier); | ||
371 | if (rc) | ||
372 | { | ||
373 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
374 | rc); | ||
375 | goto err_out_miscdev; | ||
376 | } | ||
377 | |||
378 | printk(KERN_INFO PFX "WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n", | ||
379 | timeout, nowayout); | ||
380 | |||
381 | return 0; | ||
382 | |||
383 | err_out_miscdev: | ||
384 | misc_deregister(&wdt_miscdev); | ||
385 | err_out_region2: | ||
386 | if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) | ||
387 | release_region(wdt_stop,1); | ||
388 | err_out_region1: | ||
389 | release_region(wdt_start,1); | ||
390 | err_out: | ||
391 | return rc; | ||
392 | } | ||
393 | |||
394 | module_init(sbc60xxwdt_init); | ||
395 | module_exit(sbc60xxwdt_unload); | ||
396 | |||
397 | MODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>"); | ||
398 | MODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver"); | ||
399 | MODULE_LICENSE("GPL"); | ||
400 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/sbc8360.c b/drivers/watchdog/sbc8360.c new file mode 100644 index 000000000000..285d85289532 --- /dev/null +++ b/drivers/watchdog/sbc8360.c | |||
@@ -0,0 +1,413 @@ | |||
1 | /* | ||
2 | * SBC8360 Watchdog driver | ||
3 | * | ||
4 | * (c) Copyright 2005 Webcon, Inc. | ||
5 | * | ||
6 | * Based on ib700wdt.c, which is based on advantechwdt.c which is based | ||
7 | * on acquirewdt.c which is based on wdt.c. | ||
8 | * | ||
9 | * (c) Copyright 2001 Charles Howes <chowes@vsol.net> | ||
10 | * | ||
11 | * Based on advantechwdt.c which is based on acquirewdt.c which | ||
12 | * is based on wdt.c. | ||
13 | * | ||
14 | * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> | ||
15 | * | ||
16 | * Based on acquirewdt.c which is based on wdt.c. | ||
17 | * Original copyright messages: | ||
18 | * | ||
19 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
20 | * http://www.redhat.com | ||
21 | * | ||
22 | * This program is free software; you can redistribute it and/or | ||
23 | * modify it under the terms of the GNU General Public License | ||
24 | * as published by the Free Software Foundation; either version | ||
25 | * 2 of the License, or (at your option) any later version. | ||
26 | * | ||
27 | * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
28 | * warranty for any of this software. This material is provided | ||
29 | * "AS-IS" and at no charge. | ||
30 | * | ||
31 | * (c) Copyright 1995 Alan Cox <alan@redhat.com> | ||
32 | * | ||
33 | * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> | ||
34 | * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT | ||
35 | * Added timeout module option to override default | ||
36 | * | ||
37 | */ | ||
38 | |||
39 | #include <linux/module.h> | ||
40 | #include <linux/types.h> | ||
41 | #include <linux/miscdevice.h> | ||
42 | #include <linux/watchdog.h> | ||
43 | #include <linux/ioport.h> | ||
44 | #include <linux/delay.h> | ||
45 | #include <linux/notifier.h> | ||
46 | #include <linux/fs.h> | ||
47 | #include <linux/reboot.h> | ||
48 | #include <linux/init.h> | ||
49 | #include <linux/spinlock.h> | ||
50 | #include <linux/moduleparam.h> | ||
51 | |||
52 | #include <asm/io.h> | ||
53 | #include <asm/uaccess.h> | ||
54 | #include <asm/system.h> | ||
55 | |||
56 | static unsigned long sbc8360_is_open; | ||
57 | static spinlock_t sbc8360_lock; | ||
58 | static char expect_close; | ||
59 | |||
60 | #define PFX "sbc8360: " | ||
61 | |||
62 | /* | ||
63 | * | ||
64 | * Watchdog Timer Configuration | ||
65 | * | ||
66 | * The function of the watchdog timer is to reset the system automatically | ||
67 | * and is defined at I/O port 0120H and 0121H. To enable the watchdog timer | ||
68 | * and allow the system to reset, write appropriate values from the table | ||
69 | * below to I/O port 0120H and 0121H. To disable the timer, write a zero | ||
70 | * value to I/O port 0121H for the system to stop the watchdog function. | ||
71 | * | ||
72 | * The following describes how the timer should be programmed (according to | ||
73 | * the vendor documentation) | ||
74 | * | ||
75 | * Enabling Watchdog: | ||
76 | * MOV AX,000AH (enable, phase I) | ||
77 | * MOV DX,0120H | ||
78 | * OUT DX,AX | ||
79 | * MOV AX,000BH (enable, phase II) | ||
80 | * MOV DX,0120H | ||
81 | * OUT DX,AX | ||
82 | * MOV AX,000nH (set multiplier n, from 1-4) | ||
83 | * MOV DX,0120H | ||
84 | * OUT DX,AX | ||
85 | * MOV AX,000mH (set base timer m, from 0-F) | ||
86 | * MOV DX,0121H | ||
87 | * OUT DX,AX | ||
88 | * | ||
89 | * Reset timer: | ||
90 | * MOV AX,000mH (same as set base timer, above) | ||
91 | * MOV DX,0121H | ||
92 | * OUT DX,AX | ||
93 | * | ||
94 | * Disabling Watchdog: | ||
95 | * MOV AX,0000H (a zero value) | ||
96 | * MOV DX,0120H | ||
97 | * OUT DX,AX | ||
98 | * | ||
99 | * Watchdog timeout configuration values: | ||
100 | * N | ||
101 | * M | 1 2 3 4 | ||
102 | * --|---------------------------------- | ||
103 | * 0 | 0.5s 5s 50s 100s | ||
104 | * 1 | 1s 10s 100s 200s | ||
105 | * 2 | 1.5s 15s 150s 300s | ||
106 | * 3 | 2s 20s 200s 400s | ||
107 | * 4 | 2.5s 25s 250s 500s | ||
108 | * 5 | 3s 30s 300s 600s | ||
109 | * 6 | 3.5s 35s 350s 700s | ||
110 | * 7 | 4s 40s 400s 800s | ||
111 | * 8 | 4.5s 45s 450s 900s | ||
112 | * 9 | 5s 50s 500s 1000s | ||
113 | * A | 5.5s 55s 550s 1100s | ||
114 | * B | 6s 60s 600s 1200s | ||
115 | * C | 6.5s 65s 650s 1300s | ||
116 | * D | 7s 70s 700s 1400s | ||
117 | * E | 7.5s 75s 750s 1500s | ||
118 | * F | 8s 80s 800s 1600s | ||
119 | * | ||
120 | * Another way to say the same things is: | ||
121 | * For N=1, Timeout = (M+1) * 0.5s | ||
122 | * For N=2, Timeout = (M+1) * 5s | ||
123 | * For N=3, Timeout = (M+1) * 50s | ||
124 | * For N=4, Timeout = (M+1) * 100s | ||
125 | * | ||
126 | */ | ||
127 | |||
128 | static int wd_times[64][2] = { | ||
129 | {0, 1}, /* 0 = 0.5s */ | ||
130 | {1, 1}, /* 1 = 1s */ | ||
131 | {2, 1}, /* 2 = 1.5s */ | ||
132 | {3, 1}, /* 3 = 2s */ | ||
133 | {4, 1}, /* 4 = 2.5s */ | ||
134 | {5, 1}, /* 5 = 3s */ | ||
135 | {6, 1}, /* 6 = 3.5s */ | ||
136 | {7, 1}, /* 7 = 4s */ | ||
137 | {8, 1}, /* 8 = 4.5s */ | ||
138 | {9, 1}, /* 9 = 5s */ | ||
139 | {0xA, 1}, /* 10 = 5.5s */ | ||
140 | {0xB, 1}, /* 11 = 6s */ | ||
141 | {0xC, 1}, /* 12 = 6.5s */ | ||
142 | {0xD, 1}, /* 13 = 7s */ | ||
143 | {0xE, 1}, /* 14 = 7.5s */ | ||
144 | {0xF, 1}, /* 15 = 8s */ | ||
145 | {0, 2}, /* 16 = 5s */ | ||
146 | {1, 2}, /* 17 = 10s */ | ||
147 | {2, 2}, /* 18 = 15s */ | ||
148 | {3, 2}, /* 19 = 20s */ | ||
149 | {4, 2}, /* 20 = 25s */ | ||
150 | {5, 2}, /* 21 = 30s */ | ||
151 | {6, 2}, /* 22 = 35s */ | ||
152 | {7, 2}, /* 23 = 40s */ | ||
153 | {8, 2}, /* 24 = 45s */ | ||
154 | {9, 2}, /* 25 = 50s */ | ||
155 | {0xA, 2}, /* 26 = 55s */ | ||
156 | {0xB, 2}, /* 27 = 60s */ | ||
157 | {0xC, 2}, /* 28 = 65s */ | ||
158 | {0xD, 2}, /* 29 = 70s */ | ||
159 | {0xE, 2}, /* 30 = 75s */ | ||
160 | {0xF, 2}, /* 31 = 80s */ | ||
161 | {0, 3}, /* 32 = 50s */ | ||
162 | {1, 3}, /* 33 = 100s */ | ||
163 | {2, 3}, /* 34 = 150s */ | ||
164 | {3, 3}, /* 35 = 200s */ | ||
165 | {4, 3}, /* 36 = 250s */ | ||
166 | {5, 3}, /* 37 = 300s */ | ||
167 | {6, 3}, /* 38 = 350s */ | ||
168 | {7, 3}, /* 39 = 400s */ | ||
169 | {8, 3}, /* 40 = 450s */ | ||
170 | {9, 3}, /* 41 = 500s */ | ||
171 | {0xA, 3}, /* 42 = 550s */ | ||
172 | {0xB, 3}, /* 43 = 600s */ | ||
173 | {0xC, 3}, /* 44 = 650s */ | ||
174 | {0xD, 3}, /* 45 = 700s */ | ||
175 | {0xE, 3}, /* 46 = 750s */ | ||
176 | {0xF, 3}, /* 47 = 800s */ | ||
177 | {0, 4}, /* 48 = 100s */ | ||
178 | {1, 4}, /* 49 = 200s */ | ||
179 | {2, 4}, /* 50 = 300s */ | ||
180 | {3, 4}, /* 51 = 400s */ | ||
181 | {4, 4}, /* 52 = 500s */ | ||
182 | {5, 4}, /* 53 = 600s */ | ||
183 | {6, 4}, /* 54 = 700s */ | ||
184 | {7, 4}, /* 55 = 800s */ | ||
185 | {8, 4}, /* 56 = 900s */ | ||
186 | {9, 4}, /* 57 = 1000s */ | ||
187 | {0xA, 4}, /* 58 = 1100s */ | ||
188 | {0xB, 4}, /* 59 = 1200s */ | ||
189 | {0xC, 4}, /* 60 = 1300s */ | ||
190 | {0xD, 4}, /* 61 = 1400s */ | ||
191 | {0xE, 4}, /* 62 = 1500s */ | ||
192 | {0xF, 4} /* 63 = 1600s */ | ||
193 | }; | ||
194 | |||
195 | #define SBC8360_ENABLE 0x120 | ||
196 | #define SBC8360_BASETIME 0x121 | ||
197 | |||
198 | static int timeout = 27; | ||
199 | static int wd_margin = 0xB; | ||
200 | static int wd_multiplier = 2; | ||
201 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
202 | |||
203 | module_param(timeout, int, 0); | ||
204 | MODULE_PARM_DESC(timeout, "Index into timeout table (0-63) (default=27 (60s))"); | ||
205 | module_param(nowayout, int, 0); | ||
206 | MODULE_PARM_DESC(nowayout, | ||
207 | "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
208 | |||
209 | /* | ||
210 | * Kernel methods. | ||
211 | */ | ||
212 | |||
213 | /* Activate and pre-configure watchdog */ | ||
214 | static void sbc8360_activate(void) | ||
215 | { | ||
216 | /* Enable the watchdog */ | ||
217 | outb(0x0A, SBC8360_ENABLE); | ||
218 | msleep_interruptible(100); | ||
219 | outb(0x0B, SBC8360_ENABLE); | ||
220 | msleep_interruptible(100); | ||
221 | /* Set timeout multiplier */ | ||
222 | outb(wd_multiplier, SBC8360_ENABLE); | ||
223 | msleep_interruptible(100); | ||
224 | /* Nothing happens until first sbc8360_ping() */ | ||
225 | } | ||
226 | |||
227 | /* Kernel pings watchdog */ | ||
228 | static void sbc8360_ping(void) | ||
229 | { | ||
230 | /* Write the base timer register */ | ||
231 | outb(wd_margin, SBC8360_BASETIME); | ||
232 | } | ||
233 | |||
234 | /* Userspace pings kernel driver, or requests clean close */ | ||
235 | static ssize_t sbc8360_write(struct file *file, const char __user * buf, | ||
236 | size_t count, loff_t * ppos) | ||
237 | { | ||
238 | if (count) { | ||
239 | if (!nowayout) { | ||
240 | size_t i; | ||
241 | |||
242 | /* In case it was set long ago */ | ||
243 | expect_close = 0; | ||
244 | |||
245 | for (i = 0; i != count; i++) { | ||
246 | char c; | ||
247 | if (get_user(c, buf + i)) | ||
248 | return -EFAULT; | ||
249 | if (c == 'V') | ||
250 | expect_close = 42; | ||
251 | } | ||
252 | } | ||
253 | sbc8360_ping(); | ||
254 | } | ||
255 | return count; | ||
256 | } | ||
257 | |||
258 | static int sbc8360_open(struct inode *inode, struct file *file) | ||
259 | { | ||
260 | spin_lock(&sbc8360_lock); | ||
261 | if (test_and_set_bit(0, &sbc8360_is_open)) { | ||
262 | spin_unlock(&sbc8360_lock); | ||
263 | return -EBUSY; | ||
264 | } | ||
265 | if (nowayout) | ||
266 | __module_get(THIS_MODULE); | ||
267 | |||
268 | /* Activate and ping once to start the countdown */ | ||
269 | spin_unlock(&sbc8360_lock); | ||
270 | sbc8360_activate(); | ||
271 | sbc8360_ping(); | ||
272 | return nonseekable_open(inode, file); | ||
273 | } | ||
274 | |||
275 | static int sbc8360_close(struct inode *inode, struct file *file) | ||
276 | { | ||
277 | spin_lock(&sbc8360_lock); | ||
278 | if (expect_close == 42) | ||
279 | outb(0, SBC8360_ENABLE); | ||
280 | else | ||
281 | printk(KERN_CRIT PFX | ||
282 | "SBC8360 device closed unexpectedly. SBC8360 will not stop!\n"); | ||
283 | |||
284 | clear_bit(0, &sbc8360_is_open); | ||
285 | expect_close = 0; | ||
286 | spin_unlock(&sbc8360_lock); | ||
287 | return 0; | ||
288 | } | ||
289 | |||
290 | /* | ||
291 | * Notifier for system down | ||
292 | */ | ||
293 | |||
294 | static int sbc8360_notify_sys(struct notifier_block *this, unsigned long code, | ||
295 | void *unused) | ||
296 | { | ||
297 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
298 | /* Disable the SBC8360 Watchdog */ | ||
299 | outb(0, SBC8360_ENABLE); | ||
300 | } | ||
301 | return NOTIFY_DONE; | ||
302 | } | ||
303 | |||
304 | /* | ||
305 | * Kernel Interfaces | ||
306 | */ | ||
307 | |||
308 | static const struct file_operations sbc8360_fops = { | ||
309 | .owner = THIS_MODULE, | ||
310 | .llseek = no_llseek, | ||
311 | .write = sbc8360_write, | ||
312 | .open = sbc8360_open, | ||
313 | .release = sbc8360_close, | ||
314 | }; | ||
315 | |||
316 | static struct miscdevice sbc8360_miscdev = { | ||
317 | .minor = WATCHDOG_MINOR, | ||
318 | .name = "watchdog", | ||
319 | .fops = &sbc8360_fops, | ||
320 | }; | ||
321 | |||
322 | /* | ||
323 | * The SBC8360 needs to learn about soft shutdowns in order to | ||
324 | * turn the timebomb registers off. | ||
325 | */ | ||
326 | |||
327 | static struct notifier_block sbc8360_notifier = { | ||
328 | .notifier_call = sbc8360_notify_sys, | ||
329 | }; | ||
330 | |||
331 | static int __init sbc8360_init(void) | ||
332 | { | ||
333 | int res; | ||
334 | unsigned long int mseconds = 60000; | ||
335 | |||
336 | if (timeout < 0 || timeout > 63) { | ||
337 | printk(KERN_ERR PFX "Invalid timeout index (must be 0-63).\n"); | ||
338 | res = -EINVAL; | ||
339 | goto out; | ||
340 | } | ||
341 | |||
342 | if (!request_region(SBC8360_ENABLE, 1, "SBC8360")) { | ||
343 | printk(KERN_ERR PFX "ENABLE method I/O %X is not available.\n", | ||
344 | SBC8360_ENABLE); | ||
345 | res = -EIO; | ||
346 | goto out; | ||
347 | } | ||
348 | if (!request_region(SBC8360_BASETIME, 1, "SBC8360")) { | ||
349 | printk(KERN_ERR PFX | ||
350 | "BASETIME method I/O %X is not available.\n", | ||
351 | SBC8360_BASETIME); | ||
352 | res = -EIO; | ||
353 | goto out_nobasetimereg; | ||
354 | } | ||
355 | |||
356 | res = register_reboot_notifier(&sbc8360_notifier); | ||
357 | if (res) { | ||
358 | printk(KERN_ERR PFX "Failed to register reboot notifier.\n"); | ||
359 | goto out_noreboot; | ||
360 | } | ||
361 | |||
362 | spin_lock_init(&sbc8360_lock); | ||
363 | res = misc_register(&sbc8360_miscdev); | ||
364 | if (res) { | ||
365 | printk(KERN_ERR PFX "failed to register misc device\n"); | ||
366 | goto out_nomisc; | ||
367 | } | ||
368 | |||
369 | wd_margin = wd_times[timeout][0]; | ||
370 | wd_multiplier = wd_times[timeout][1]; | ||
371 | |||
372 | if (wd_multiplier == 1) | ||
373 | mseconds = (wd_margin + 1) * 500; | ||
374 | else if (wd_multiplier == 2) | ||
375 | mseconds = (wd_margin + 1) * 5000; | ||
376 | else if (wd_multiplier == 3) | ||
377 | mseconds = (wd_margin + 1) * 50000; | ||
378 | else if (wd_multiplier == 4) | ||
379 | mseconds = (wd_margin + 1) * 100000; | ||
380 | |||
381 | /* My kingdom for the ability to print "0.5 seconds" in the kernel! */ | ||
382 | printk(KERN_INFO PFX "Timeout set at %ld ms.\n", mseconds); | ||
383 | |||
384 | return 0; | ||
385 | |||
386 | out_nomisc: | ||
387 | unregister_reboot_notifier(&sbc8360_notifier); | ||
388 | out_noreboot: | ||
389 | release_region(SBC8360_BASETIME, 1); | ||
390 | out_nobasetimereg: | ||
391 | release_region(SBC8360_ENABLE, 1); | ||
392 | out: | ||
393 | return res; | ||
394 | } | ||
395 | |||
396 | static void __exit sbc8360_exit(void) | ||
397 | { | ||
398 | misc_deregister(&sbc8360_miscdev); | ||
399 | unregister_reboot_notifier(&sbc8360_notifier); | ||
400 | release_region(SBC8360_ENABLE, 1); | ||
401 | release_region(SBC8360_BASETIME, 1); | ||
402 | } | ||
403 | |||
404 | module_init(sbc8360_init); | ||
405 | module_exit(sbc8360_exit); | ||
406 | |||
407 | MODULE_AUTHOR("Ian E. Morgan <imorgan@webcon.ca>"); | ||
408 | MODULE_DESCRIPTION("SBC8360 watchdog driver"); | ||
409 | MODULE_LICENSE("GPL"); | ||
410 | MODULE_VERSION("1.01"); | ||
411 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
412 | |||
413 | /* end of sbc8360.c */ | ||
diff --git a/drivers/watchdog/sbc_epx_c3.c b/drivers/watchdog/sbc_epx_c3.c new file mode 100644 index 000000000000..82cbd8809a69 --- /dev/null +++ b/drivers/watchdog/sbc_epx_c3.c | |||
@@ -0,0 +1,223 @@ | |||
1 | /* | ||
2 | * SBC EPX C3 0.1 A Hardware Watchdog Device for the Winsystems EPX-C3 | ||
3 | * single board computer | ||
4 | * | ||
5 | * (c) Copyright 2006 Calin A. Culianu <calin@ajvar.org>, All Rights | ||
6 | * Reserved. | ||
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 | * based on softdog.c by Alan Cox <alan@redhat.com> | ||
14 | */ | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/moduleparam.h> | ||
18 | #include <linux/types.h> | ||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/fs.h> | ||
21 | #include <linux/mm.h> | ||
22 | #include <linux/miscdevice.h> | ||
23 | #include <linux/watchdog.h> | ||
24 | #include <linux/notifier.h> | ||
25 | #include <linux/reboot.h> | ||
26 | #include <linux/init.h> | ||
27 | #include <linux/ioport.h> | ||
28 | #include <asm/uaccess.h> | ||
29 | #include <asm/io.h> | ||
30 | |||
31 | #define PFX "epx_c3: " | ||
32 | static int epx_c3_alive; | ||
33 | |||
34 | #define WATCHDOG_TIMEOUT 1 /* 1 sec default timeout */ | ||
35 | |||
36 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
37 | module_param(nowayout, int, 0); | ||
38 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
39 | |||
40 | #define EPXC3_WATCHDOG_CTL_REG 0x1ee /* write 1 to enable, 0 to disable */ | ||
41 | #define EPXC3_WATCHDOG_PET_REG 0x1ef /* write anything to pet once enabled */ | ||
42 | |||
43 | static void epx_c3_start(void) | ||
44 | { | ||
45 | outb(1, EPXC3_WATCHDOG_CTL_REG); | ||
46 | } | ||
47 | |||
48 | static void epx_c3_stop(void) | ||
49 | { | ||
50 | |||
51 | outb(0, EPXC3_WATCHDOG_CTL_REG); | ||
52 | |||
53 | printk(KERN_INFO PFX "Stopped watchdog timer.\n"); | ||
54 | } | ||
55 | |||
56 | static void epx_c3_pet(void) | ||
57 | { | ||
58 | outb(1, EPXC3_WATCHDOG_PET_REG); | ||
59 | } | ||
60 | |||
61 | /* | ||
62 | * Allow only one person to hold it open | ||
63 | */ | ||
64 | static int epx_c3_open(struct inode *inode, struct file *file) | ||
65 | { | ||
66 | if (epx_c3_alive) | ||
67 | return -EBUSY; | ||
68 | |||
69 | if (nowayout) | ||
70 | __module_get(THIS_MODULE); | ||
71 | |||
72 | /* Activate timer */ | ||
73 | epx_c3_start(); | ||
74 | epx_c3_pet(); | ||
75 | |||
76 | epx_c3_alive = 1; | ||
77 | printk(KERN_INFO "Started watchdog timer.\n"); | ||
78 | |||
79 | return nonseekable_open(inode, file); | ||
80 | } | ||
81 | |||
82 | static int epx_c3_release(struct inode *inode, struct file *file) | ||
83 | { | ||
84 | /* Shut off the timer. | ||
85 | * Lock it in if it's a module and we defined ...NOWAYOUT */ | ||
86 | if (!nowayout) | ||
87 | epx_c3_stop(); /* Turn the WDT off */ | ||
88 | |||
89 | epx_c3_alive = 0; | ||
90 | |||
91 | return 0; | ||
92 | } | ||
93 | |||
94 | static ssize_t epx_c3_write(struct file *file, const char __user *data, | ||
95 | size_t len, loff_t *ppos) | ||
96 | { | ||
97 | /* Refresh the timer. */ | ||
98 | if (len) | ||
99 | epx_c3_pet(); | ||
100 | return len; | ||
101 | } | ||
102 | |||
103 | static int epx_c3_ioctl(struct inode *inode, struct file *file, | ||
104 | unsigned int cmd, unsigned long arg) | ||
105 | { | ||
106 | int options, retval = -EINVAL; | ||
107 | int __user *argp = (void __user *)arg; | ||
108 | static struct watchdog_info ident = { | ||
109 | .options = WDIOF_KEEPALIVEPING | | ||
110 | WDIOF_MAGICCLOSE, | ||
111 | .firmware_version = 0, | ||
112 | .identity = "Winsystems EPX-C3 H/W Watchdog", | ||
113 | }; | ||
114 | |||
115 | switch (cmd) { | ||
116 | case WDIOC_GETSUPPORT: | ||
117 | if (copy_to_user(argp, &ident, sizeof(ident))) | ||
118 | return -EFAULT; | ||
119 | return 0; | ||
120 | case WDIOC_GETSTATUS: | ||
121 | case WDIOC_GETBOOTSTATUS: | ||
122 | return put_user(0, argp); | ||
123 | case WDIOC_KEEPALIVE: | ||
124 | epx_c3_pet(); | ||
125 | return 0; | ||
126 | case WDIOC_GETTIMEOUT: | ||
127 | return put_user(WATCHDOG_TIMEOUT, argp); | ||
128 | case WDIOC_SETOPTIONS: | ||
129 | if (get_user(options, argp)) | ||
130 | return -EFAULT; | ||
131 | |||
132 | if (options & WDIOS_DISABLECARD) { | ||
133 | epx_c3_stop(); | ||
134 | retval = 0; | ||
135 | } | ||
136 | |||
137 | if (options & WDIOS_ENABLECARD) { | ||
138 | epx_c3_start(); | ||
139 | retval = 0; | ||
140 | } | ||
141 | |||
142 | return retval; | ||
143 | default: | ||
144 | return -ENOTTY; | ||
145 | } | ||
146 | } | ||
147 | |||
148 | static int epx_c3_notify_sys(struct notifier_block *this, unsigned long code, | ||
149 | void *unused) | ||
150 | { | ||
151 | if (code == SYS_DOWN || code == SYS_HALT) | ||
152 | epx_c3_stop(); /* Turn the WDT off */ | ||
153 | |||
154 | return NOTIFY_DONE; | ||
155 | } | ||
156 | |||
157 | static const struct file_operations epx_c3_fops = { | ||
158 | .owner = THIS_MODULE, | ||
159 | .llseek = no_llseek, | ||
160 | .write = epx_c3_write, | ||
161 | .ioctl = epx_c3_ioctl, | ||
162 | .open = epx_c3_open, | ||
163 | .release = epx_c3_release, | ||
164 | }; | ||
165 | |||
166 | static struct miscdevice epx_c3_miscdev = { | ||
167 | .minor = WATCHDOG_MINOR, | ||
168 | .name = "watchdog", | ||
169 | .fops = &epx_c3_fops, | ||
170 | }; | ||
171 | |||
172 | static struct notifier_block epx_c3_notifier = { | ||
173 | .notifier_call = epx_c3_notify_sys, | ||
174 | }; | ||
175 | |||
176 | static const char banner[] __initdata = | ||
177 | KERN_INFO PFX "Hardware Watchdog Timer for Winsystems EPX-C3 SBC: 0.1\n"; | ||
178 | |||
179 | static int __init watchdog_init(void) | ||
180 | { | ||
181 | int ret; | ||
182 | |||
183 | if (!request_region(EPXC3_WATCHDOG_CTL_REG, 2, "epxc3_watchdog")) | ||
184 | return -EBUSY; | ||
185 | |||
186 | ret = register_reboot_notifier(&epx_c3_notifier); | ||
187 | if (ret) { | ||
188 | printk(KERN_ERR PFX "cannot register reboot notifier " | ||
189 | "(err=%d)\n", ret); | ||
190 | goto out; | ||
191 | } | ||
192 | |||
193 | ret = misc_register(&epx_c3_miscdev); | ||
194 | if (ret) { | ||
195 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d " | ||
196 | "(err=%d)\n", WATCHDOG_MINOR, ret); | ||
197 | unregister_reboot_notifier(&epx_c3_notifier); | ||
198 | goto out; | ||
199 | } | ||
200 | |||
201 | printk(banner); | ||
202 | |||
203 | return 0; | ||
204 | |||
205 | out: | ||
206 | release_region(EPXC3_WATCHDOG_CTL_REG, 2); | ||
207 | return ret; | ||
208 | } | ||
209 | |||
210 | static void __exit watchdog_exit(void) | ||
211 | { | ||
212 | misc_deregister(&epx_c3_miscdev); | ||
213 | unregister_reboot_notifier(&epx_c3_notifier); | ||
214 | release_region(EPXC3_WATCHDOG_CTL_REG, 2); | ||
215 | } | ||
216 | |||
217 | module_init(watchdog_init); | ||
218 | module_exit(watchdog_exit); | ||
219 | |||
220 | MODULE_AUTHOR("Calin A. Culianu <calin@ajvar.org>"); | ||
221 | MODULE_DESCRIPTION("Hardware Watchdog Device for Winsystems EPX-C3 SBC. Note that there is no way to probe for this device -- so only use it if you are *sure* you are runnning on this specific SBC system from Winsystems! It writes to IO ports 0x1ee and 0x1ef!"); | ||
222 | MODULE_LICENSE("GPL"); | ||
223 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/sc1200wdt.c b/drivers/watchdog/sc1200wdt.c new file mode 100644 index 000000000000..9670d47190d0 --- /dev/null +++ b/drivers/watchdog/sc1200wdt.c | |||
@@ -0,0 +1,463 @@ | |||
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/module.h> | ||
31 | #include <linux/moduleparam.h> | ||
32 | #include <linux/miscdevice.h> | ||
33 | #include <linux/watchdog.h> | ||
34 | #include <linux/ioport.h> | ||
35 | #include <linux/spinlock.h> | ||
36 | #include <linux/notifier.h> | ||
37 | #include <linux/reboot.h> | ||
38 | #include <linux/init.h> | ||
39 | #include <linux/pnp.h> | ||
40 | #include <linux/fs.h> | ||
41 | |||
42 | #include <asm/semaphore.h> | ||
43 | #include <asm/io.h> | ||
44 | #include <asm/uaccess.h> | ||
45 | |||
46 | #define SC1200_MODULE_VER "build 20020303" | ||
47 | #define SC1200_MODULE_NAME "sc1200wdt" | ||
48 | #define PFX SC1200_MODULE_NAME ": " | ||
49 | |||
50 | #define MAX_TIMEOUT 255 /* 255 minutes */ | ||
51 | #define PMIR (io) /* Power Management Index Register */ | ||
52 | #define PMDR (io+1) /* Power Management Data Register */ | ||
53 | |||
54 | /* Data Register indexes */ | ||
55 | #define FER1 0x00 /* Function enable register 1 */ | ||
56 | #define FER2 0x01 /* Function enable register 2 */ | ||
57 | #define PMC1 0x02 /* Power Management Ctrl 1 */ | ||
58 | #define PMC2 0x03 /* Power Management Ctrl 2 */ | ||
59 | #define PMC3 0x04 /* Power Management Ctrl 3 */ | ||
60 | #define WDTO 0x05 /* Watchdog timeout register */ | ||
61 | #define WDCF 0x06 /* Watchdog config register */ | ||
62 | #define WDST 0x07 /* Watchdog status register */ | ||
63 | |||
64 | /* WDCF bitfields - which devices assert WDO */ | ||
65 | #define KBC_IRQ 0x01 /* Keyboard Controller */ | ||
66 | #define MSE_IRQ 0x02 /* Mouse */ | ||
67 | #define UART1_IRQ 0x03 /* Serial0 */ | ||
68 | #define UART2_IRQ 0x04 /* Serial1 */ | ||
69 | /* 5 -7 are reserved */ | ||
70 | |||
71 | static char banner[] __initdata = KERN_INFO PFX SC1200_MODULE_VER; | ||
72 | static int timeout = 1; | ||
73 | static int io = -1; | ||
74 | static int io_len = 2; /* for non plug and play */ | ||
75 | static struct semaphore open_sem; | ||
76 | static char expect_close; | ||
77 | static spinlock_t sc1200wdt_lock; /* io port access serialisation */ | ||
78 | |||
79 | #if defined CONFIG_PNP | ||
80 | static int isapnp = 1; | ||
81 | static struct pnp_dev *wdt_dev; | ||
82 | |||
83 | module_param(isapnp, int, 0); | ||
84 | MODULE_PARM_DESC(isapnp, "When set to 0 driver ISA PnP support will be disabled"); | ||
85 | #endif | ||
86 | |||
87 | module_param(io, int, 0); | ||
88 | MODULE_PARM_DESC(io, "io port"); | ||
89 | module_param(timeout, int, 0); | ||
90 | MODULE_PARM_DESC(timeout, "range is 0-255 minutes, default is 1"); | ||
91 | |||
92 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
93 | module_param(nowayout, int, 0); | ||
94 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
95 | |||
96 | |||
97 | |||
98 | /* Read from Data Register */ | ||
99 | static inline void sc1200wdt_read_data(unsigned char index, unsigned char *data) | ||
100 | { | ||
101 | spin_lock(&sc1200wdt_lock); | ||
102 | outb_p(index, PMIR); | ||
103 | *data = inb(PMDR); | ||
104 | spin_unlock(&sc1200wdt_lock); | ||
105 | } | ||
106 | |||
107 | |||
108 | /* Write to Data Register */ | ||
109 | static inline void sc1200wdt_write_data(unsigned char index, unsigned char data) | ||
110 | { | ||
111 | spin_lock(&sc1200wdt_lock); | ||
112 | outb_p(index, PMIR); | ||
113 | outb(data, PMDR); | ||
114 | spin_unlock(&sc1200wdt_lock); | ||
115 | } | ||
116 | |||
117 | |||
118 | static void sc1200wdt_start(void) | ||
119 | { | ||
120 | unsigned char reg; | ||
121 | |||
122 | sc1200wdt_read_data(WDCF, ®); | ||
123 | /* assert WDO when any of the following interrupts are triggered too */ | ||
124 | reg |= (KBC_IRQ | MSE_IRQ | UART1_IRQ | UART2_IRQ); | ||
125 | sc1200wdt_write_data(WDCF, reg); | ||
126 | /* set the timeout and get the ball rolling */ | ||
127 | sc1200wdt_write_data(WDTO, timeout); | ||
128 | } | ||
129 | |||
130 | |||
131 | static void sc1200wdt_stop(void) | ||
132 | { | ||
133 | sc1200wdt_write_data(WDTO, 0); | ||
134 | } | ||
135 | |||
136 | |||
137 | /* This returns the status of the WDO signal, inactive high. */ | ||
138 | static inline int sc1200wdt_status(void) | ||
139 | { | ||
140 | unsigned char ret; | ||
141 | |||
142 | sc1200wdt_read_data(WDST, &ret); | ||
143 | /* If the bit is inactive, the watchdog is enabled, so return | ||
144 | * KEEPALIVEPING which is a bit of a kludge because there's nothing | ||
145 | * else for enabled/disabled status | ||
146 | */ | ||
147 | return (ret & 0x01) ? 0 : WDIOF_KEEPALIVEPING; /* bits 1 - 7 are undefined */ | ||
148 | } | ||
149 | |||
150 | |||
151 | static int sc1200wdt_open(struct inode *inode, struct file *file) | ||
152 | { | ||
153 | /* allow one at a time */ | ||
154 | if (down_trylock(&open_sem)) | ||
155 | return -EBUSY; | ||
156 | |||
157 | if (timeout > MAX_TIMEOUT) | ||
158 | timeout = MAX_TIMEOUT; | ||
159 | |||
160 | sc1200wdt_start(); | ||
161 | printk(KERN_INFO PFX "Watchdog enabled, timeout = %d min(s)", timeout); | ||
162 | |||
163 | return nonseekable_open(inode, file); | ||
164 | } | ||
165 | |||
166 | |||
167 | static int sc1200wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) | ||
168 | { | ||
169 | int new_timeout; | ||
170 | void __user *argp = (void __user *)arg; | ||
171 | int __user *p = argp; | ||
172 | static struct watchdog_info ident = { | ||
173 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
174 | .firmware_version = 0, | ||
175 | .identity = "PC87307/PC97307", | ||
176 | }; | ||
177 | |||
178 | switch (cmd) { | ||
179 | default: | ||
180 | return -ENOTTY; | ||
181 | |||
182 | case WDIOC_GETSUPPORT: | ||
183 | if (copy_to_user(argp, &ident, sizeof ident)) | ||
184 | return -EFAULT; | ||
185 | return 0; | ||
186 | |||
187 | case WDIOC_GETSTATUS: | ||
188 | return put_user(sc1200wdt_status(), p); | ||
189 | |||
190 | case WDIOC_GETBOOTSTATUS: | ||
191 | return put_user(0, p); | ||
192 | |||
193 | case WDIOC_KEEPALIVE: | ||
194 | sc1200wdt_write_data(WDTO, timeout); | ||
195 | return 0; | ||
196 | |||
197 | case WDIOC_SETTIMEOUT: | ||
198 | if (get_user(new_timeout, p)) | ||
199 | return -EFAULT; | ||
200 | |||
201 | /* the API states this is given in secs */ | ||
202 | new_timeout /= 60; | ||
203 | if (new_timeout < 0 || new_timeout > MAX_TIMEOUT) | ||
204 | return -EINVAL; | ||
205 | |||
206 | timeout = new_timeout; | ||
207 | sc1200wdt_write_data(WDTO, timeout); | ||
208 | /* fall through and return the new timeout */ | ||
209 | |||
210 | case WDIOC_GETTIMEOUT: | ||
211 | return put_user(timeout * 60, p); | ||
212 | |||
213 | case WDIOC_SETOPTIONS: | ||
214 | { | ||
215 | int options, retval = -EINVAL; | ||
216 | |||
217 | if (get_user(options, p)) | ||
218 | return -EFAULT; | ||
219 | |||
220 | if (options & WDIOS_DISABLECARD) { | ||
221 | sc1200wdt_stop(); | ||
222 | retval = 0; | ||
223 | } | ||
224 | |||
225 | if (options & WDIOS_ENABLECARD) { | ||
226 | sc1200wdt_start(); | ||
227 | retval = 0; | ||
228 | } | ||
229 | |||
230 | return retval; | ||
231 | } | ||
232 | } | ||
233 | } | ||
234 | |||
235 | |||
236 | static int sc1200wdt_release(struct inode *inode, struct file *file) | ||
237 | { | ||
238 | if (expect_close == 42) { | ||
239 | sc1200wdt_stop(); | ||
240 | printk(KERN_INFO PFX "Watchdog disabled\n"); | ||
241 | } else { | ||
242 | sc1200wdt_write_data(WDTO, timeout); | ||
243 | printk(KERN_CRIT PFX "Unexpected close!, timeout = %d min(s)\n", timeout); | ||
244 | } | ||
245 | up(&open_sem); | ||
246 | expect_close = 0; | ||
247 | |||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | |||
252 | static ssize_t sc1200wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) | ||
253 | { | ||
254 | if (len) { | ||
255 | if (!nowayout) { | ||
256 | size_t i; | ||
257 | |||
258 | expect_close = 0; | ||
259 | |||
260 | for (i = 0; i != len; i++) { | ||
261 | char c; | ||
262 | |||
263 | if (get_user(c, data+i)) | ||
264 | return -EFAULT; | ||
265 | if (c == 'V') | ||
266 | expect_close = 42; | ||
267 | } | ||
268 | } | ||
269 | |||
270 | sc1200wdt_write_data(WDTO, timeout); | ||
271 | return len; | ||
272 | } | ||
273 | |||
274 | return 0; | ||
275 | } | ||
276 | |||
277 | |||
278 | static int sc1200wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
279 | { | ||
280 | if (code == SYS_DOWN || code == SYS_HALT) | ||
281 | sc1200wdt_stop(); | ||
282 | |||
283 | return NOTIFY_DONE; | ||
284 | } | ||
285 | |||
286 | |||
287 | static struct notifier_block sc1200wdt_notifier = | ||
288 | { | ||
289 | .notifier_call = sc1200wdt_notify_sys, | ||
290 | }; | ||
291 | |||
292 | static const struct file_operations sc1200wdt_fops = | ||
293 | { | ||
294 | .owner = THIS_MODULE, | ||
295 | .llseek = no_llseek, | ||
296 | .write = sc1200wdt_write, | ||
297 | .ioctl = sc1200wdt_ioctl, | ||
298 | .open = sc1200wdt_open, | ||
299 | .release = sc1200wdt_release, | ||
300 | }; | ||
301 | |||
302 | static struct miscdevice sc1200wdt_miscdev = | ||
303 | { | ||
304 | .minor = WATCHDOG_MINOR, | ||
305 | .name = "watchdog", | ||
306 | .fops = &sc1200wdt_fops, | ||
307 | }; | ||
308 | |||
309 | |||
310 | static int __init sc1200wdt_probe(void) | ||
311 | { | ||
312 | /* The probe works by reading the PMC3 register's default value of 0x0e | ||
313 | * there is one caveat, if the device disables the parallel port or any | ||
314 | * of the UARTs we won't be able to detect it. | ||
315 | * Nb. This could be done with accuracy by reading the SID registers, but | ||
316 | * we don't have access to those io regions. | ||
317 | */ | ||
318 | |||
319 | unsigned char reg; | ||
320 | |||
321 | sc1200wdt_read_data(PMC3, ®); | ||
322 | reg &= 0x0f; /* we don't want the UART busy bits */ | ||
323 | return (reg == 0x0e) ? 0 : -ENODEV; | ||
324 | } | ||
325 | |||
326 | |||
327 | #if defined CONFIG_PNP | ||
328 | |||
329 | static struct pnp_device_id scl200wdt_pnp_devices[] = { | ||
330 | /* National Semiconductor PC87307/PC97307 watchdog component */ | ||
331 | {.id = "NSC0800", .driver_data = 0}, | ||
332 | {.id = ""}, | ||
333 | }; | ||
334 | |||
335 | static int scl200wdt_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id) | ||
336 | { | ||
337 | /* this driver only supports one card at a time */ | ||
338 | if (wdt_dev || !isapnp) | ||
339 | return -EBUSY; | ||
340 | |||
341 | wdt_dev = dev; | ||
342 | io = pnp_port_start(wdt_dev, 0); | ||
343 | io_len = pnp_port_len(wdt_dev, 0); | ||
344 | |||
345 | if (!request_region(io, io_len, SC1200_MODULE_NAME)) { | ||
346 | printk(KERN_ERR PFX "Unable to register IO port %#x\n", io); | ||
347 | return -EBUSY; | ||
348 | } | ||
349 | |||
350 | printk(KERN_INFO "scl200wdt: PnP device found at io port %#x/%d\n", io, io_len); | ||
351 | return 0; | ||
352 | } | ||
353 | |||
354 | static void scl200wdt_pnp_remove(struct pnp_dev * dev) | ||
355 | { | ||
356 | if (wdt_dev){ | ||
357 | release_region(io, io_len); | ||
358 | wdt_dev = NULL; | ||
359 | } | ||
360 | } | ||
361 | |||
362 | static struct pnp_driver scl200wdt_pnp_driver = { | ||
363 | .name = "scl200wdt", | ||
364 | .id_table = scl200wdt_pnp_devices, | ||
365 | .probe = scl200wdt_pnp_probe, | ||
366 | .remove = scl200wdt_pnp_remove, | ||
367 | }; | ||
368 | |||
369 | #endif /* CONFIG_PNP */ | ||
370 | |||
371 | |||
372 | static int __init sc1200wdt_init(void) | ||
373 | { | ||
374 | int ret; | ||
375 | |||
376 | printk("%s\n", banner); | ||
377 | |||
378 | spin_lock_init(&sc1200wdt_lock); | ||
379 | sema_init(&open_sem, 1); | ||
380 | |||
381 | #if defined CONFIG_PNP | ||
382 | if (isapnp) { | ||
383 | ret = pnp_register_driver(&scl200wdt_pnp_driver); | ||
384 | if (ret) | ||
385 | goto out_clean; | ||
386 | } | ||
387 | #endif | ||
388 | |||
389 | if (io == -1) { | ||
390 | printk(KERN_ERR PFX "io parameter must be specified\n"); | ||
391 | ret = -EINVAL; | ||
392 | goto out_pnp; | ||
393 | } | ||
394 | |||
395 | #if defined CONFIG_PNP | ||
396 | /* now that the user has specified an IO port and we haven't detected | ||
397 | * any devices, disable pnp support */ | ||
398 | isapnp = 0; | ||
399 | pnp_unregister_driver(&scl200wdt_pnp_driver); | ||
400 | #endif | ||
401 | |||
402 | if (!request_region(io, io_len, SC1200_MODULE_NAME)) { | ||
403 | printk(KERN_ERR PFX "Unable to register IO port %#x\n", io); | ||
404 | ret = -EBUSY; | ||
405 | goto out_pnp; | ||
406 | } | ||
407 | |||
408 | ret = sc1200wdt_probe(); | ||
409 | if (ret) | ||
410 | goto out_io; | ||
411 | |||
412 | ret = register_reboot_notifier(&sc1200wdt_notifier); | ||
413 | if (ret) { | ||
414 | printk(KERN_ERR PFX "Unable to register reboot notifier err = %d\n", ret); | ||
415 | goto out_io; | ||
416 | } | ||
417 | |||
418 | ret = misc_register(&sc1200wdt_miscdev); | ||
419 | if (ret) { | ||
420 | printk(KERN_ERR PFX "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR); | ||
421 | goto out_rbt; | ||
422 | } | ||
423 | |||
424 | /* ret = 0 */ | ||
425 | |||
426 | out_clean: | ||
427 | return ret; | ||
428 | |||
429 | out_rbt: | ||
430 | unregister_reboot_notifier(&sc1200wdt_notifier); | ||
431 | |||
432 | out_io: | ||
433 | release_region(io, io_len); | ||
434 | |||
435 | out_pnp: | ||
436 | #if defined CONFIG_PNP | ||
437 | if (isapnp) | ||
438 | pnp_unregister_driver(&scl200wdt_pnp_driver); | ||
439 | #endif | ||
440 | goto out_clean; | ||
441 | } | ||
442 | |||
443 | |||
444 | static void __exit sc1200wdt_exit(void) | ||
445 | { | ||
446 | misc_deregister(&sc1200wdt_miscdev); | ||
447 | unregister_reboot_notifier(&sc1200wdt_notifier); | ||
448 | |||
449 | #if defined CONFIG_PNP | ||
450 | if(isapnp) | ||
451 | pnp_unregister_driver(&scl200wdt_pnp_driver); | ||
452 | else | ||
453 | #endif | ||
454 | release_region(io, io_len); | ||
455 | } | ||
456 | |||
457 | module_init(sc1200wdt_init); | ||
458 | module_exit(sc1200wdt_exit); | ||
459 | |||
460 | MODULE_AUTHOR("Zwane Mwaikambo <zwane@commfireservices.com>"); | ||
461 | MODULE_DESCRIPTION("Driver for National Semiconductor PC87307/PC97307 watchdog component"); | ||
462 | MODULE_LICENSE("GPL"); | ||
463 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/sc520_wdt.c b/drivers/watchdog/sc520_wdt.c new file mode 100644 index 000000000000..e8594c64d1e6 --- /dev/null +++ b/drivers/watchdog/sc520_wdt.c | |||
@@ -0,0 +1,435 @@ | |||
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 | #include <linux/jiffies.h> | ||
67 | |||
68 | #include <asm/io.h> | ||
69 | #include <asm/uaccess.h> | ||
70 | #include <asm/system.h> | ||
71 | |||
72 | #define OUR_NAME "sc520_wdt" | ||
73 | #define PFX OUR_NAME ": " | ||
74 | |||
75 | /* | ||
76 | * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7) | ||
77 | * | ||
78 | * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s | ||
79 | * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s | ||
80 | * | ||
81 | * We will program the SC520 watchdog for a timeout of 2.01s. | ||
82 | * If we reset 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 | */ | ||
92 | |||
93 | #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ | ||
94 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ | ||
95 | module_param(timeout, int, 0); | ||
96 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
97 | |||
98 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
99 | module_param(nowayout, int, 0); | ||
100 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
101 | |||
102 | /* | ||
103 | * AMD Elan SC520 - Watchdog Timer Registers | ||
104 | */ | ||
105 | #define MMCR_BASE 0xfffef000 /* The default base address */ | ||
106 | #define OFFS_WDTMRCTL 0xCB0 /* Watchdog Timer Control Register */ | ||
107 | |||
108 | /* WDT Control Register bit definitions */ | ||
109 | #define WDT_EXP_SEL_01 0x0001 /* [01] Time-out = 496 us (with 33 Mhz clk). */ | ||
110 | #define WDT_EXP_SEL_02 0x0002 /* [02] Time-out = 508 ms (with 33 Mhz clk). */ | ||
111 | #define WDT_EXP_SEL_03 0x0004 /* [03] Time-out = 1.02 s (with 33 Mhz clk). */ | ||
112 | #define WDT_EXP_SEL_04 0x0008 /* [04] Time-out = 2.03 s (with 33 Mhz clk). */ | ||
113 | #define WDT_EXP_SEL_05 0x0010 /* [05] Time-out = 4.07 s (with 33 Mhz clk). */ | ||
114 | #define WDT_EXP_SEL_06 0x0020 /* [06] Time-out = 8.13 s (with 33 Mhz clk). */ | ||
115 | #define WDT_EXP_SEL_07 0x0040 /* [07] Time-out = 16.27s (with 33 Mhz clk). */ | ||
116 | #define WDT_EXP_SEL_08 0x0080 /* [08] Time-out = 32.54s (with 33 Mhz clk). */ | ||
117 | #define WDT_IRQ_FLG 0x1000 /* [12] Interrupt Request Flag */ | ||
118 | #define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */ | ||
119 | #define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */ | ||
120 | |||
121 | static __u16 __iomem *wdtmrctl; | ||
122 | |||
123 | static void wdt_timer_ping(unsigned long); | ||
124 | static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0); | ||
125 | static unsigned long next_heartbeat; | ||
126 | static unsigned long wdt_is_open; | ||
127 | static char wdt_expect_close; | ||
128 | static spinlock_t wdt_spinlock; | ||
129 | |||
130 | /* | ||
131 | * Whack the dog | ||
132 | */ | ||
133 | |||
134 | static void wdt_timer_ping(unsigned long data) | ||
135 | { | ||
136 | /* If we got a heartbeat pulse within the WDT_US_INTERVAL | ||
137 | * we agree to ping the WDT | ||
138 | */ | ||
139 | if(time_before(jiffies, next_heartbeat)) | ||
140 | { | ||
141 | /* Ping the WDT */ | ||
142 | spin_lock(&wdt_spinlock); | ||
143 | writew(0xAAAA, wdtmrctl); | ||
144 | writew(0x5555, wdtmrctl); | ||
145 | spin_unlock(&wdt_spinlock); | ||
146 | |||
147 | /* Re-set the timer interval */ | ||
148 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
149 | } else { | ||
150 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
151 | } | ||
152 | } | ||
153 | |||
154 | /* | ||
155 | * Utility routines | ||
156 | */ | ||
157 | |||
158 | static void wdt_config(int writeval) | ||
159 | { | ||
160 | __u16 dummy; | ||
161 | unsigned long flags; | ||
162 | |||
163 | /* buy some time (ping) */ | ||
164 | spin_lock_irqsave(&wdt_spinlock, flags); | ||
165 | dummy=readw(wdtmrctl); /* ensure write synchronization */ | ||
166 | writew(0xAAAA, wdtmrctl); | ||
167 | writew(0x5555, wdtmrctl); | ||
168 | /* unlock WDT = make WDT configuration register writable one time */ | ||
169 | writew(0x3333, wdtmrctl); | ||
170 | writew(0xCCCC, wdtmrctl); | ||
171 | /* write WDT configuration register */ | ||
172 | writew(writeval, wdtmrctl); | ||
173 | spin_unlock_irqrestore(&wdt_spinlock, flags); | ||
174 | } | ||
175 | |||
176 | static int wdt_startup(void) | ||
177 | { | ||
178 | next_heartbeat = jiffies + (timeout * HZ); | ||
179 | |||
180 | /* Start the timer */ | ||
181 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
182 | |||
183 | /* Start the watchdog */ | ||
184 | wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04); | ||
185 | |||
186 | printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); | ||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | static int wdt_turnoff(void) | ||
191 | { | ||
192 | /* Stop the timer */ | ||
193 | del_timer(&timer); | ||
194 | |||
195 | /* Stop the watchdog */ | ||
196 | wdt_config(0); | ||
197 | |||
198 | printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); | ||
199 | return 0; | ||
200 | } | ||
201 | |||
202 | static int wdt_keepalive(void) | ||
203 | { | ||
204 | /* user land ping */ | ||
205 | next_heartbeat = jiffies + (timeout * HZ); | ||
206 | return 0; | ||
207 | } | ||
208 | |||
209 | static int wdt_set_heartbeat(int t) | ||
210 | { | ||
211 | if ((t < 1) || (t > 3600)) /* arbitrary upper limit */ | ||
212 | return -EINVAL; | ||
213 | |||
214 | timeout = t; | ||
215 | return 0; | ||
216 | } | ||
217 | |||
218 | /* | ||
219 | * /dev/watchdog handling | ||
220 | */ | ||
221 | |||
222 | static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) | ||
223 | { | ||
224 | /* See if we got the magic character 'V' and reload the timer */ | ||
225 | if(count) { | ||
226 | if (!nowayout) { | ||
227 | size_t ofs; | ||
228 | |||
229 | /* note: just in case someone wrote the magic character | ||
230 | * five months ago... */ | ||
231 | wdt_expect_close = 0; | ||
232 | |||
233 | /* now scan */ | ||
234 | for(ofs = 0; ofs != count; ofs++) { | ||
235 | char c; | ||
236 | if (get_user(c, buf + ofs)) | ||
237 | return -EFAULT; | ||
238 | if(c == 'V') | ||
239 | wdt_expect_close = 42; | ||
240 | } | ||
241 | } | ||
242 | |||
243 | /* Well, anyhow someone wrote to us, we should return that favour */ | ||
244 | wdt_keepalive(); | ||
245 | } | ||
246 | return count; | ||
247 | } | ||
248 | |||
249 | static int fop_open(struct inode * inode, struct file * file) | ||
250 | { | ||
251 | /* Just in case we're already talking to someone... */ | ||
252 | if(test_and_set_bit(0, &wdt_is_open)) | ||
253 | return -EBUSY; | ||
254 | if (nowayout) | ||
255 | __module_get(THIS_MODULE); | ||
256 | |||
257 | /* Good, fire up the show */ | ||
258 | wdt_startup(); | ||
259 | return nonseekable_open(inode, file); | ||
260 | } | ||
261 | |||
262 | static int fop_close(struct inode * inode, struct file * file) | ||
263 | { | ||
264 | if(wdt_expect_close == 42) { | ||
265 | wdt_turnoff(); | ||
266 | } else { | ||
267 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
268 | wdt_keepalive(); | ||
269 | } | ||
270 | clear_bit(0, &wdt_is_open); | ||
271 | wdt_expect_close = 0; | ||
272 | return 0; | ||
273 | } | ||
274 | |||
275 | static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
276 | unsigned long arg) | ||
277 | { | ||
278 | void __user *argp = (void __user *)arg; | ||
279 | int __user *p = argp; | ||
280 | static struct watchdog_info ident = { | ||
281 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
282 | .firmware_version = 1, | ||
283 | .identity = "SC520", | ||
284 | }; | ||
285 | |||
286 | switch(cmd) | ||
287 | { | ||
288 | default: | ||
289 | return -ENOTTY; | ||
290 | case WDIOC_GETSUPPORT: | ||
291 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
292 | case WDIOC_GETSTATUS: | ||
293 | case WDIOC_GETBOOTSTATUS: | ||
294 | return put_user(0, p); | ||
295 | case WDIOC_KEEPALIVE: | ||
296 | wdt_keepalive(); | ||
297 | return 0; | ||
298 | case WDIOC_SETOPTIONS: | ||
299 | { | ||
300 | int new_options, retval = -EINVAL; | ||
301 | |||
302 | if(get_user(new_options, p)) | ||
303 | return -EFAULT; | ||
304 | |||
305 | if(new_options & WDIOS_DISABLECARD) { | ||
306 | wdt_turnoff(); | ||
307 | retval = 0; | ||
308 | } | ||
309 | |||
310 | if(new_options & WDIOS_ENABLECARD) { | ||
311 | wdt_startup(); | ||
312 | retval = 0; | ||
313 | } | ||
314 | |||
315 | return retval; | ||
316 | } | ||
317 | case WDIOC_SETTIMEOUT: | ||
318 | { | ||
319 | int new_timeout; | ||
320 | |||
321 | if(get_user(new_timeout, p)) | ||
322 | return -EFAULT; | ||
323 | |||
324 | if(wdt_set_heartbeat(new_timeout)) | ||
325 | return -EINVAL; | ||
326 | |||
327 | wdt_keepalive(); | ||
328 | /* Fall through */ | ||
329 | } | ||
330 | case WDIOC_GETTIMEOUT: | ||
331 | return put_user(timeout, p); | ||
332 | } | ||
333 | } | ||
334 | |||
335 | static const struct file_operations wdt_fops = { | ||
336 | .owner = THIS_MODULE, | ||
337 | .llseek = no_llseek, | ||
338 | .write = fop_write, | ||
339 | .open = fop_open, | ||
340 | .release = fop_close, | ||
341 | .ioctl = fop_ioctl, | ||
342 | }; | ||
343 | |||
344 | static struct miscdevice wdt_miscdev = { | ||
345 | .minor = WATCHDOG_MINOR, | ||
346 | .name = "watchdog", | ||
347 | .fops = &wdt_fops, | ||
348 | }; | ||
349 | |||
350 | /* | ||
351 | * Notifier for system down | ||
352 | */ | ||
353 | |||
354 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
355 | void *unused) | ||
356 | { | ||
357 | if(code==SYS_DOWN || code==SYS_HALT) | ||
358 | wdt_turnoff(); | ||
359 | return NOTIFY_DONE; | ||
360 | } | ||
361 | |||
362 | /* | ||
363 | * The WDT needs to learn about soft shutdowns in order to | ||
364 | * turn the timebomb registers off. | ||
365 | */ | ||
366 | |||
367 | static struct notifier_block wdt_notifier = { | ||
368 | .notifier_call = wdt_notify_sys, | ||
369 | }; | ||
370 | |||
371 | static void __exit sc520_wdt_unload(void) | ||
372 | { | ||
373 | if (!nowayout) | ||
374 | wdt_turnoff(); | ||
375 | |||
376 | /* Deregister */ | ||
377 | misc_deregister(&wdt_miscdev); | ||
378 | unregister_reboot_notifier(&wdt_notifier); | ||
379 | iounmap(wdtmrctl); | ||
380 | } | ||
381 | |||
382 | static int __init sc520_wdt_init(void) | ||
383 | { | ||
384 | int rc = -EBUSY; | ||
385 | |||
386 | spin_lock_init(&wdt_spinlock); | ||
387 | |||
388 | /* Check that the timeout value is within it's range ; if not reset to the default */ | ||
389 | if (wdt_set_heartbeat(timeout)) { | ||
390 | wdt_set_heartbeat(WATCHDOG_TIMEOUT); | ||
391 | printk(KERN_INFO PFX "timeout value must be 1<=timeout<=3600, using %d\n", | ||
392 | WATCHDOG_TIMEOUT); | ||
393 | } | ||
394 | |||
395 | wdtmrctl = ioremap((unsigned long)(MMCR_BASE + OFFS_WDTMRCTL), 2); | ||
396 | if (!wdtmrctl) { | ||
397 | printk(KERN_ERR PFX "Unable to remap memory\n"); | ||
398 | rc = -ENOMEM; | ||
399 | goto err_out_region2; | ||
400 | } | ||
401 | |||
402 | rc = register_reboot_notifier(&wdt_notifier); | ||
403 | if (rc) { | ||
404 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
405 | rc); | ||
406 | goto err_out_ioremap; | ||
407 | } | ||
408 | |||
409 | rc = misc_register(&wdt_miscdev); | ||
410 | if (rc) { | ||
411 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
412 | WATCHDOG_MINOR, rc); | ||
413 | goto err_out_notifier; | ||
414 | } | ||
415 | |||
416 | printk(KERN_INFO PFX "WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n", | ||
417 | timeout,nowayout); | ||
418 | |||
419 | return 0; | ||
420 | |||
421 | err_out_notifier: | ||
422 | unregister_reboot_notifier(&wdt_notifier); | ||
423 | err_out_ioremap: | ||
424 | iounmap(wdtmrctl); | ||
425 | err_out_region2: | ||
426 | return rc; | ||
427 | } | ||
428 | |||
429 | module_init(sc520_wdt_init); | ||
430 | module_exit(sc520_wdt_unload); | ||
431 | |||
432 | MODULE_AUTHOR("Scott and Bill Jennings"); | ||
433 | MODULE_DESCRIPTION("Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor"); | ||
434 | MODULE_LICENSE("GPL"); | ||
435 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/scx200_wdt.c b/drivers/watchdog/scx200_wdt.c new file mode 100644 index 000000000000..d4fd0fa2f176 --- /dev/null +++ b/drivers/watchdog/scx200_wdt.c | |||
@@ -0,0 +1,269 @@ | |||
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/module.h> | ||
21 | #include <linux/moduleparam.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/miscdevice.h> | ||
24 | #include <linux/watchdog.h> | ||
25 | #include <linux/notifier.h> | ||
26 | #include <linux/reboot.h> | ||
27 | #include <linux/fs.h> | ||
28 | #include <linux/ioport.h> | ||
29 | #include <linux/scx200.h> | ||
30 | |||
31 | #include <asm/uaccess.h> | ||
32 | #include <asm/io.h> | ||
33 | |||
34 | #define NAME "scx200_wdt" | ||
35 | |||
36 | MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>"); | ||
37 | MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver"); | ||
38 | MODULE_LICENSE("GPL"); | ||
39 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
40 | |||
41 | static int margin = 60; /* in seconds */ | ||
42 | module_param(margin, int, 0); | ||
43 | MODULE_PARM_DESC(margin, "Watchdog margin in seconds"); | ||
44 | |||
45 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
46 | module_param(nowayout, int, 0); | ||
47 | MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); | ||
48 | |||
49 | static u16 wdto_restart; | ||
50 | static struct semaphore open_semaphore; | ||
51 | static char expect_close; | ||
52 | |||
53 | /* Bits of the WDCNFG register */ | ||
54 | #define W_ENABLE 0x00fa /* Enable watchdog */ | ||
55 | #define W_DISABLE 0x0000 /* Disable watchdog */ | ||
56 | |||
57 | /* The scaling factor for the timer, this depends on the value of W_ENABLE */ | ||
58 | #define W_SCALE (32768/1024) | ||
59 | |||
60 | static void scx200_wdt_ping(void) | ||
61 | { | ||
62 | outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO); | ||
63 | } | ||
64 | |||
65 | static void scx200_wdt_update_margin(void) | ||
66 | { | ||
67 | printk(KERN_INFO NAME ": timer margin %d seconds\n", margin); | ||
68 | wdto_restart = margin * W_SCALE; | ||
69 | } | ||
70 | |||
71 | static void scx200_wdt_enable(void) | ||
72 | { | ||
73 | printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d\n", | ||
74 | wdto_restart); | ||
75 | |||
76 | outw(0, scx200_cb_base + SCx200_WDT_WDTO); | ||
77 | outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS); | ||
78 | outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG); | ||
79 | |||
80 | scx200_wdt_ping(); | ||
81 | } | ||
82 | |||
83 | static void scx200_wdt_disable(void) | ||
84 | { | ||
85 | printk(KERN_DEBUG NAME ": disabling watchdog timer\n"); | ||
86 | |||
87 | outw(0, scx200_cb_base + SCx200_WDT_WDTO); | ||
88 | outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS); | ||
89 | outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG); | ||
90 | } | ||
91 | |||
92 | static int scx200_wdt_open(struct inode *inode, struct file *file) | ||
93 | { | ||
94 | /* only allow one at a time */ | ||
95 | if (down_trylock(&open_semaphore)) | ||
96 | return -EBUSY; | ||
97 | scx200_wdt_enable(); | ||
98 | |||
99 | return nonseekable_open(inode, file); | ||
100 | } | ||
101 | |||
102 | static int scx200_wdt_release(struct inode *inode, struct file *file) | ||
103 | { | ||
104 | if (expect_close != 42) { | ||
105 | printk(KERN_WARNING NAME ": watchdog device closed unexpectedly, will not disable the watchdog timer\n"); | ||
106 | } else if (!nowayout) { | ||
107 | scx200_wdt_disable(); | ||
108 | } | ||
109 | expect_close = 0; | ||
110 | up(&open_semaphore); | ||
111 | |||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | static int scx200_wdt_notify_sys(struct notifier_block *this, | ||
116 | unsigned long code, void *unused) | ||
117 | { | ||
118 | if (code == SYS_HALT || code == SYS_POWER_OFF) | ||
119 | if (!nowayout) | ||
120 | scx200_wdt_disable(); | ||
121 | |||
122 | return NOTIFY_DONE; | ||
123 | } | ||
124 | |||
125 | static struct notifier_block scx200_wdt_notifier = | ||
126 | { | ||
127 | .notifier_call = scx200_wdt_notify_sys, | ||
128 | }; | ||
129 | |||
130 | static ssize_t scx200_wdt_write(struct file *file, const char __user *data, | ||
131 | size_t len, loff_t *ppos) | ||
132 | { | ||
133 | /* check for a magic close character */ | ||
134 | if (len) | ||
135 | { | ||
136 | size_t i; | ||
137 | |||
138 | scx200_wdt_ping(); | ||
139 | |||
140 | expect_close = 0; | ||
141 | for (i = 0; i < len; ++i) { | ||
142 | char c; | ||
143 | if (get_user(c, data+i)) | ||
144 | return -EFAULT; | ||
145 | if (c == 'V') | ||
146 | expect_close = 42; | ||
147 | } | ||
148 | |||
149 | return len; | ||
150 | } | ||
151 | |||
152 | return 0; | ||
153 | } | ||
154 | |||
155 | static int scx200_wdt_ioctl(struct inode *inode, struct file *file, | ||
156 | unsigned int cmd, unsigned long arg) | ||
157 | { | ||
158 | void __user *argp = (void __user *)arg; | ||
159 | int __user *p = argp; | ||
160 | static struct watchdog_info ident = { | ||
161 | .identity = "NatSemi SCx200 Watchdog", | ||
162 | .firmware_version = 1, | ||
163 | .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING), | ||
164 | }; | ||
165 | int new_margin; | ||
166 | |||
167 | switch (cmd) { | ||
168 | default: | ||
169 | return -ENOTTY; | ||
170 | case WDIOC_GETSUPPORT: | ||
171 | if(copy_to_user(argp, &ident, sizeof(ident))) | ||
172 | return -EFAULT; | ||
173 | return 0; | ||
174 | case WDIOC_GETSTATUS: | ||
175 | case WDIOC_GETBOOTSTATUS: | ||
176 | if (put_user(0, p)) | ||
177 | return -EFAULT; | ||
178 | return 0; | ||
179 | case WDIOC_KEEPALIVE: | ||
180 | scx200_wdt_ping(); | ||
181 | return 0; | ||
182 | case WDIOC_SETTIMEOUT: | ||
183 | if (get_user(new_margin, p)) | ||
184 | return -EFAULT; | ||
185 | if (new_margin < 1) | ||
186 | return -EINVAL; | ||
187 | margin = new_margin; | ||
188 | scx200_wdt_update_margin(); | ||
189 | scx200_wdt_ping(); | ||
190 | case WDIOC_GETTIMEOUT: | ||
191 | if (put_user(margin, p)) | ||
192 | return -EFAULT; | ||
193 | return 0; | ||
194 | } | ||
195 | } | ||
196 | |||
197 | static const struct file_operations scx200_wdt_fops = { | ||
198 | .owner = THIS_MODULE, | ||
199 | .llseek = no_llseek, | ||
200 | .write = scx200_wdt_write, | ||
201 | .ioctl = scx200_wdt_ioctl, | ||
202 | .open = scx200_wdt_open, | ||
203 | .release = scx200_wdt_release, | ||
204 | }; | ||
205 | |||
206 | static struct miscdevice scx200_wdt_miscdev = { | ||
207 | .minor = WATCHDOG_MINOR, | ||
208 | .name = "watchdog", | ||
209 | .fops = &scx200_wdt_fops, | ||
210 | }; | ||
211 | |||
212 | static int __init scx200_wdt_init(void) | ||
213 | { | ||
214 | int r; | ||
215 | |||
216 | printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver\n"); | ||
217 | |||
218 | /* check that we have found the configuration block */ | ||
219 | if (!scx200_cb_present()) | ||
220 | return -ENODEV; | ||
221 | |||
222 | if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET, | ||
223 | SCx200_WDT_SIZE, | ||
224 | "NatSemi SCx200 Watchdog")) { | ||
225 | printk(KERN_WARNING NAME ": watchdog I/O region busy\n"); | ||
226 | return -EBUSY; | ||
227 | } | ||
228 | |||
229 | scx200_wdt_update_margin(); | ||
230 | scx200_wdt_disable(); | ||
231 | |||
232 | sema_init(&open_semaphore, 1); | ||
233 | |||
234 | r = misc_register(&scx200_wdt_miscdev); | ||
235 | if (r) { | ||
236 | release_region(scx200_cb_base + SCx200_WDT_OFFSET, | ||
237 | SCx200_WDT_SIZE); | ||
238 | return r; | ||
239 | } | ||
240 | |||
241 | r = register_reboot_notifier(&scx200_wdt_notifier); | ||
242 | if (r) { | ||
243 | printk(KERN_ERR NAME ": unable to register reboot notifier"); | ||
244 | misc_deregister(&scx200_wdt_miscdev); | ||
245 | release_region(scx200_cb_base + SCx200_WDT_OFFSET, | ||
246 | SCx200_WDT_SIZE); | ||
247 | return r; | ||
248 | } | ||
249 | |||
250 | return 0; | ||
251 | } | ||
252 | |||
253 | static void __exit scx200_wdt_cleanup(void) | ||
254 | { | ||
255 | unregister_reboot_notifier(&scx200_wdt_notifier); | ||
256 | misc_deregister(&scx200_wdt_miscdev); | ||
257 | release_region(scx200_cb_base + SCx200_WDT_OFFSET, | ||
258 | SCx200_WDT_SIZE); | ||
259 | } | ||
260 | |||
261 | module_init(scx200_wdt_init); | ||
262 | module_exit(scx200_wdt_cleanup); | ||
263 | |||
264 | /* | ||
265 | Local variables: | ||
266 | compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules" | ||
267 | c-basic-offset: 8 | ||
268 | End: | ||
269 | */ | ||
diff --git a/drivers/watchdog/shwdt.c b/drivers/watchdog/shwdt.c new file mode 100644 index 000000000000..cecbedd473a4 --- /dev/null +++ b/drivers/watchdog/shwdt.c | |||
@@ -0,0 +1,485 @@ | |||
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/module.h> | ||
21 | #include <linux/moduleparam.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/types.h> | ||
24 | #include <linux/miscdevice.h> | ||
25 | #include <linux/watchdog.h> | ||
26 | #include <linux/reboot.h> | ||
27 | #include <linux/notifier.h> | ||
28 | #include <linux/ioport.h> | ||
29 | #include <linux/fs.h> | ||
30 | #include <linux/mm.h> | ||
31 | #include <asm/io.h> | ||
32 | #include <asm/uaccess.h> | ||
33 | #include <asm/watchdog.h> | ||
34 | |||
35 | #define PFX "shwdt: " | ||
36 | |||
37 | /* | ||
38 | * Default clock division ratio is 5.25 msecs. For an additional table of | ||
39 | * values, consult the asm-sh/watchdog.h. Overload this at module load | ||
40 | * time. | ||
41 | * | ||
42 | * In order for this to work reliably we need to have HZ set to 1000 or | ||
43 | * something quite higher than 100 (or we need a proper high-res timer | ||
44 | * implementation that will deal with this properly), otherwise the 10ms | ||
45 | * resolution of a jiffy is enough to trigger the overflow. For things like | ||
46 | * the SH-4 and SH-5, this isn't necessarily that big of a problem, though | ||
47 | * for the SH-2 and SH-3, this isn't recommended unless the WDT is absolutely | ||
48 | * necssary. | ||
49 | * | ||
50 | * As a result of this timing problem, the only modes that are particularly | ||
51 | * feasible are the 4096 and the 2048 divisors, which yeild 5.25 and 2.62ms | ||
52 | * overflow periods respectively. | ||
53 | * | ||
54 | * Also, since we can't really expect userspace to be responsive enough | ||
55 | * before the overflow happens, we maintain two seperate timers .. One in | ||
56 | * the kernel for clearing out WOVF every 2ms or so (again, this depends on | ||
57 | * HZ == 1000), and another for monitoring userspace writes to the WDT device. | ||
58 | * | ||
59 | * As such, we currently use a configurable heartbeat interval which defaults | ||
60 | * to 30s. In this case, the userspace daemon is only responsible for periodic | ||
61 | * writes to the device before the next heartbeat is scheduled. If the daemon | ||
62 | * misses its deadline, the kernel timer will allow the WDT to overflow. | ||
63 | */ | ||
64 | static int clock_division_ratio = WTCSR_CKS_4096; | ||
65 | |||
66 | #define next_ping_period(cks) msecs_to_jiffies(cks - 4) | ||
67 | |||
68 | static void sh_wdt_ping(unsigned long data); | ||
69 | |||
70 | static unsigned long shwdt_is_open; | ||
71 | static struct watchdog_info sh_wdt_info; | ||
72 | static char shwdt_expect_close; | ||
73 | static DEFINE_TIMER(timer, sh_wdt_ping, 0, 0); | ||
74 | static unsigned long next_heartbeat; | ||
75 | |||
76 | #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ | ||
77 | static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ | ||
78 | |||
79 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
80 | |||
81 | /** | ||
82 | * sh_wdt_start - Start the Watchdog | ||
83 | * | ||
84 | * Starts the watchdog. | ||
85 | */ | ||
86 | static void sh_wdt_start(void) | ||
87 | { | ||
88 | __u8 csr; | ||
89 | |||
90 | next_heartbeat = jiffies + (heartbeat * HZ); | ||
91 | mod_timer(&timer, next_ping_period(clock_division_ratio)); | ||
92 | |||
93 | csr = sh_wdt_read_csr(); | ||
94 | csr |= WTCSR_WT | clock_division_ratio; | ||
95 | sh_wdt_write_csr(csr); | ||
96 | |||
97 | sh_wdt_write_cnt(0); | ||
98 | |||
99 | /* | ||
100 | * These processors have a bit of an inconsistent initialization | ||
101 | * process.. starting with SH-3, RSTS was moved to WTCSR, and the | ||
102 | * RSTCSR register was removed. | ||
103 | * | ||
104 | * On the SH-2 however, in addition with bits being in different | ||
105 | * locations, we must deal with RSTCSR outright.. | ||
106 | */ | ||
107 | csr = sh_wdt_read_csr(); | ||
108 | csr |= WTCSR_TME; | ||
109 | csr &= ~WTCSR_RSTS; | ||
110 | sh_wdt_write_csr(csr); | ||
111 | |||
112 | #ifdef CONFIG_CPU_SH2 | ||
113 | /* | ||
114 | * Whoever came up with the RSTCSR semantics must've been smoking | ||
115 | * some of the good stuff, since in addition to the WTCSR/WTCNT write | ||
116 | * brain-damage, it's managed to fuck things up one step further.. | ||
117 | * | ||
118 | * If we need to clear the WOVF bit, the upper byte has to be 0xa5.. | ||
119 | * but if we want to touch RSTE or RSTS, the upper byte has to be | ||
120 | * 0x5a.. | ||
121 | */ | ||
122 | csr = sh_wdt_read_rstcsr(); | ||
123 | csr &= ~RSTCSR_RSTS; | ||
124 | sh_wdt_write_rstcsr(csr); | ||
125 | #endif | ||
126 | } | ||
127 | |||
128 | /** | ||
129 | * sh_wdt_stop - Stop the Watchdog | ||
130 | * Stops the watchdog. | ||
131 | */ | ||
132 | static void sh_wdt_stop(void) | ||
133 | { | ||
134 | __u8 csr; | ||
135 | |||
136 | del_timer(&timer); | ||
137 | |||
138 | csr = sh_wdt_read_csr(); | ||
139 | csr &= ~WTCSR_TME; | ||
140 | sh_wdt_write_csr(csr); | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * sh_wdt_keepalive - Keep the Userspace Watchdog Alive | ||
145 | * The Userspace watchdog got a KeepAlive: schedule the next heartbeat. | ||
146 | */ | ||
147 | static inline void sh_wdt_keepalive(void) | ||
148 | { | ||
149 | next_heartbeat = jiffies + (heartbeat * HZ); | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * sh_wdt_set_heartbeat - Set the Userspace Watchdog heartbeat | ||
154 | * Set the Userspace Watchdog heartbeat | ||
155 | */ | ||
156 | static int sh_wdt_set_heartbeat(int t) | ||
157 | { | ||
158 | if (unlikely((t < 1) || (t > 3600))) /* arbitrary upper limit */ | ||
159 | return -EINVAL; | ||
160 | |||
161 | heartbeat = t; | ||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | /** | ||
166 | * sh_wdt_ping - Ping the Watchdog | ||
167 | * @data: Unused | ||
168 | * | ||
169 | * Clears overflow bit, resets timer counter. | ||
170 | */ | ||
171 | static void sh_wdt_ping(unsigned long data) | ||
172 | { | ||
173 | if (time_before(jiffies, next_heartbeat)) { | ||
174 | __u8 csr; | ||
175 | |||
176 | csr = sh_wdt_read_csr(); | ||
177 | csr &= ~WTCSR_IOVF; | ||
178 | sh_wdt_write_csr(csr); | ||
179 | |||
180 | sh_wdt_write_cnt(0); | ||
181 | |||
182 | mod_timer(&timer, next_ping_period(clock_division_ratio)); | ||
183 | } else | ||
184 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping " | ||
185 | "the watchdog\n"); | ||
186 | } | ||
187 | |||
188 | /** | ||
189 | * sh_wdt_open - Open the Device | ||
190 | * @inode: inode of device | ||
191 | * @file: file handle of device | ||
192 | * | ||
193 | * Watchdog device is opened and started. | ||
194 | */ | ||
195 | static int sh_wdt_open(struct inode *inode, struct file *file) | ||
196 | { | ||
197 | if (test_and_set_bit(0, &shwdt_is_open)) | ||
198 | return -EBUSY; | ||
199 | if (nowayout) | ||
200 | __module_get(THIS_MODULE); | ||
201 | |||
202 | sh_wdt_start(); | ||
203 | |||
204 | return nonseekable_open(inode, file); | ||
205 | } | ||
206 | |||
207 | /** | ||
208 | * sh_wdt_close - Close the Device | ||
209 | * @inode: inode of device | ||
210 | * @file: file handle of device | ||
211 | * | ||
212 | * Watchdog device is closed and stopped. | ||
213 | */ | ||
214 | static int sh_wdt_close(struct inode *inode, struct file *file) | ||
215 | { | ||
216 | if (shwdt_expect_close == 42) { | ||
217 | sh_wdt_stop(); | ||
218 | } else { | ||
219 | printk(KERN_CRIT PFX "Unexpected close, not " | ||
220 | "stopping watchdog!\n"); | ||
221 | sh_wdt_keepalive(); | ||
222 | } | ||
223 | |||
224 | clear_bit(0, &shwdt_is_open); | ||
225 | shwdt_expect_close = 0; | ||
226 | |||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | /** | ||
231 | * sh_wdt_write - Write to Device | ||
232 | * @file: file handle of device | ||
233 | * @buf: buffer to write | ||
234 | * @count: length of buffer | ||
235 | * @ppos: offset | ||
236 | * | ||
237 | * Pings the watchdog on write. | ||
238 | */ | ||
239 | static ssize_t sh_wdt_write(struct file *file, const char *buf, | ||
240 | size_t count, loff_t *ppos) | ||
241 | { | ||
242 | if (count) { | ||
243 | if (!nowayout) { | ||
244 | size_t i; | ||
245 | |||
246 | shwdt_expect_close = 0; | ||
247 | |||
248 | for (i = 0; i != count; i++) { | ||
249 | char c; | ||
250 | if (get_user(c, buf + i)) | ||
251 | return -EFAULT; | ||
252 | if (c == 'V') | ||
253 | shwdt_expect_close = 42; | ||
254 | } | ||
255 | } | ||
256 | sh_wdt_keepalive(); | ||
257 | } | ||
258 | |||
259 | return count; | ||
260 | } | ||
261 | |||
262 | /** | ||
263 | * sh_wdt_mmap - map WDT/CPG registers into userspace | ||
264 | * @file: file structure for the device | ||
265 | * @vma: VMA to map the registers into | ||
266 | * | ||
267 | * A simple mmap() implementation for the corner cases where the counter | ||
268 | * needs to be mapped in userspace directly. Due to the relatively small | ||
269 | * size of the area, neighbouring registers not necessarily tied to the | ||
270 | * CPG will also be accessible through the register page, so this remains | ||
271 | * configurable for users that really know what they're doing. | ||
272 | * | ||
273 | * Additionaly, the register page maps in the CPG register base relative | ||
274 | * to the nearest page-aligned boundary, which requires that userspace do | ||
275 | * the appropriate CPU subtype math for calculating the page offset for | ||
276 | * the counter value. | ||
277 | */ | ||
278 | static int sh_wdt_mmap(struct file *file, struct vm_area_struct *vma) | ||
279 | { | ||
280 | int ret = -ENOSYS; | ||
281 | |||
282 | #ifdef CONFIG_SH_WDT_MMAP | ||
283 | unsigned long addr; | ||
284 | |||
285 | /* Only support the simple cases where we map in a register page. */ | ||
286 | if (((vma->vm_end - vma->vm_start) != PAGE_SIZE) || vma->vm_pgoff) | ||
287 | return -EINVAL; | ||
288 | |||
289 | /* | ||
290 | * Pick WTCNT as the start, it's usually the first register after the | ||
291 | * FRQCR, and neither one are generally page-aligned out of the box. | ||
292 | */ | ||
293 | addr = WTCNT & ~(PAGE_SIZE - 1); | ||
294 | |||
295 | vma->vm_flags |= VM_IO; | ||
296 | vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); | ||
297 | |||
298 | if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT, | ||
299 | PAGE_SIZE, vma->vm_page_prot)) { | ||
300 | printk(KERN_ERR PFX "%s: io_remap_pfn_range failed\n", | ||
301 | __FUNCTION__); | ||
302 | return -EAGAIN; | ||
303 | } | ||
304 | |||
305 | ret = 0; | ||
306 | #endif | ||
307 | |||
308 | return ret; | ||
309 | } | ||
310 | |||
311 | /** | ||
312 | * sh_wdt_ioctl - Query Device | ||
313 | * @inode: inode of device | ||
314 | * @file: file handle of device | ||
315 | * @cmd: watchdog command | ||
316 | * @arg: argument | ||
317 | * | ||
318 | * Query basic information from the device or ping it, as outlined by the | ||
319 | * watchdog API. | ||
320 | */ | ||
321 | static int sh_wdt_ioctl(struct inode *inode, struct file *file, | ||
322 | unsigned int cmd, unsigned long arg) | ||
323 | { | ||
324 | int new_heartbeat; | ||
325 | int options, retval = -EINVAL; | ||
326 | |||
327 | switch (cmd) { | ||
328 | case WDIOC_GETSUPPORT: | ||
329 | return copy_to_user((struct watchdog_info *)arg, | ||
330 | &sh_wdt_info, | ||
331 | sizeof(sh_wdt_info)) ? -EFAULT : 0; | ||
332 | case WDIOC_GETSTATUS: | ||
333 | case WDIOC_GETBOOTSTATUS: | ||
334 | return put_user(0, (int *)arg); | ||
335 | case WDIOC_KEEPALIVE: | ||
336 | sh_wdt_keepalive(); | ||
337 | return 0; | ||
338 | case WDIOC_SETTIMEOUT: | ||
339 | if (get_user(new_heartbeat, (int *)arg)) | ||
340 | return -EFAULT; | ||
341 | |||
342 | if (sh_wdt_set_heartbeat(new_heartbeat)) | ||
343 | return -EINVAL; | ||
344 | |||
345 | sh_wdt_keepalive(); | ||
346 | /* Fall */ | ||
347 | case WDIOC_GETTIMEOUT: | ||
348 | return put_user(heartbeat, (int *)arg); | ||
349 | case WDIOC_SETOPTIONS: | ||
350 | if (get_user(options, (int *)arg)) | ||
351 | return -EFAULT; | ||
352 | |||
353 | if (options & WDIOS_DISABLECARD) { | ||
354 | sh_wdt_stop(); | ||
355 | retval = 0; | ||
356 | } | ||
357 | |||
358 | if (options & WDIOS_ENABLECARD) { | ||
359 | sh_wdt_start(); | ||
360 | retval = 0; | ||
361 | } | ||
362 | |||
363 | return retval; | ||
364 | default: | ||
365 | return -ENOTTY; | ||
366 | } | ||
367 | |||
368 | return 0; | ||
369 | } | ||
370 | |||
371 | /** | ||
372 | * sh_wdt_notify_sys - Notifier Handler | ||
373 | * @this: notifier block | ||
374 | * @code: notifier event | ||
375 | * @unused: unused | ||
376 | * | ||
377 | * Handles specific events, such as turning off the watchdog during a | ||
378 | * shutdown event. | ||
379 | */ | ||
380 | static int sh_wdt_notify_sys(struct notifier_block *this, | ||
381 | unsigned long code, void *unused) | ||
382 | { | ||
383 | if (code == SYS_DOWN || code == SYS_HALT) | ||
384 | sh_wdt_stop(); | ||
385 | |||
386 | return NOTIFY_DONE; | ||
387 | } | ||
388 | |||
389 | static const struct file_operations sh_wdt_fops = { | ||
390 | .owner = THIS_MODULE, | ||
391 | .llseek = no_llseek, | ||
392 | .write = sh_wdt_write, | ||
393 | .ioctl = sh_wdt_ioctl, | ||
394 | .open = sh_wdt_open, | ||
395 | .release = sh_wdt_close, | ||
396 | .mmap = sh_wdt_mmap, | ||
397 | }; | ||
398 | |||
399 | static struct watchdog_info sh_wdt_info = { | ||
400 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | | ||
401 | WDIOF_MAGICCLOSE, | ||
402 | .firmware_version = 1, | ||
403 | .identity = "SH WDT", | ||
404 | }; | ||
405 | |||
406 | static struct notifier_block sh_wdt_notifier = { | ||
407 | .notifier_call = sh_wdt_notify_sys, | ||
408 | }; | ||
409 | |||
410 | static struct miscdevice sh_wdt_miscdev = { | ||
411 | .minor = WATCHDOG_MINOR, | ||
412 | .name = "watchdog", | ||
413 | .fops = &sh_wdt_fops, | ||
414 | }; | ||
415 | |||
416 | /** | ||
417 | * sh_wdt_init - Initialize module | ||
418 | * Registers the device and notifier handler. Actual device | ||
419 | * initialization is handled by sh_wdt_open(). | ||
420 | */ | ||
421 | static int __init sh_wdt_init(void) | ||
422 | { | ||
423 | int rc; | ||
424 | |||
425 | if ((clock_division_ratio < 0x5) || (clock_division_ratio > 0x7)) { | ||
426 | clock_division_ratio = WTCSR_CKS_4096; | ||
427 | printk(KERN_INFO PFX "clock_division_ratio value must " | ||
428 | "be 0x5<=x<=0x7, using %d\n", clock_division_ratio); | ||
429 | } | ||
430 | |||
431 | rc = sh_wdt_set_heartbeat(heartbeat); | ||
432 | if (unlikely(rc)) { | ||
433 | heartbeat = WATCHDOG_HEARTBEAT; | ||
434 | printk(KERN_INFO PFX "heartbeat value must " | ||
435 | "be 1<=x<=3600, using %d\n", heartbeat); | ||
436 | } | ||
437 | |||
438 | rc = register_reboot_notifier(&sh_wdt_notifier); | ||
439 | if (unlikely(rc)) { | ||
440 | printk(KERN_ERR PFX "Can't register reboot notifier (err=%d)\n", | ||
441 | rc); | ||
442 | return rc; | ||
443 | } | ||
444 | |||
445 | rc = misc_register(&sh_wdt_miscdev); | ||
446 | if (unlikely(rc)) { | ||
447 | printk(KERN_ERR PFX "Can't register miscdev on " | ||
448 | "minor=%d (err=%d)\n", sh_wdt_miscdev.minor, rc); | ||
449 | unregister_reboot_notifier(&sh_wdt_notifier); | ||
450 | return rc; | ||
451 | } | ||
452 | |||
453 | printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
454 | heartbeat, nowayout); | ||
455 | |||
456 | return 0; | ||
457 | } | ||
458 | |||
459 | /** | ||
460 | * sh_wdt_exit - Deinitialize module | ||
461 | * Unregisters the device and notifier handler. Actual device | ||
462 | * deinitialization is handled by sh_wdt_close(). | ||
463 | */ | ||
464 | static void __exit sh_wdt_exit(void) | ||
465 | { | ||
466 | misc_deregister(&sh_wdt_miscdev); | ||
467 | unregister_reboot_notifier(&sh_wdt_notifier); | ||
468 | } | ||
469 | |||
470 | MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); | ||
471 | MODULE_DESCRIPTION("SuperH watchdog driver"); | ||
472 | MODULE_LICENSE("GPL"); | ||
473 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
474 | |||
475 | module_param(clock_division_ratio, int, 0); | ||
476 | MODULE_PARM_DESC(clock_division_ratio, "Clock division ratio. Valid ranges are from 0x5 (1.31ms) to 0x7 (5.25ms). (default=" __MODULE_STRING(clock_division_ratio) ")"); | ||
477 | |||
478 | module_param(heartbeat, int, 0); | ||
479 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1<=heartbeat<=3600, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | ||
480 | |||
481 | module_param(nowayout, int, 0); | ||
482 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
483 | |||
484 | module_init(sh_wdt_init); | ||
485 | module_exit(sh_wdt_exit); | ||
diff --git a/drivers/watchdog/smsc37b787_wdt.c b/drivers/watchdog/smsc37b787_wdt.c new file mode 100644 index 000000000000..d3cb0a766020 --- /dev/null +++ b/drivers/watchdog/smsc37b787_wdt.c | |||
@@ -0,0 +1,627 @@ | |||
1 | /* | ||
2 | * SMsC 37B787 Watchdog Timer driver for Linux 2.6.x.x | ||
3 | * | ||
4 | * Based on acquirewdt.c by Alan Cox <alan@redhat.com> | ||
5 | * and some other existing drivers | ||
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 2003-2006 Sven Anders <anders@anduras.de> | ||
17 | * | ||
18 | * History: | ||
19 | * 2003 - Created version 1.0 for Linux 2.4.x. | ||
20 | * 2006 - Ported to Linux 2.6, added nowayout and MAGICCLOSE | ||
21 | * features. Released version 1.1 | ||
22 | * | ||
23 | * Theory of operation: | ||
24 | * | ||
25 | * A Watchdog Timer (WDT) is a hardware circuit that can | ||
26 | * reset the computer system in case of a software fault. | ||
27 | * You probably knew that already. | ||
28 | * | ||
29 | * Usually a userspace daemon will notify the kernel WDT driver | ||
30 | * via the /dev/watchdog special device file that userspace is | ||
31 | * still alive, at regular intervals. When such a notification | ||
32 | * occurs, the driver will usually tell the hardware watchdog | ||
33 | * that everything is in order, and that the watchdog should wait | ||
34 | * for yet another little while to reset the system. | ||
35 | * If userspace fails (RAM error, kernel bug, whatever), the | ||
36 | * notifications cease to occur, and the hardware watchdog will | ||
37 | * reset the system (causing a reboot) after the timeout occurs. | ||
38 | * | ||
39 | * Create device with: | ||
40 | * mknod /dev/watchdog c 10 130 | ||
41 | * | ||
42 | * For an example userspace keep-alive daemon, see: | ||
43 | * Documentation/watchdog/watchdog.txt | ||
44 | */ | ||
45 | |||
46 | #include <linux/module.h> | ||
47 | #include <linux/moduleparam.h> | ||
48 | #include <linux/types.h> | ||
49 | #include <linux/miscdevice.h> | ||
50 | #include <linux/watchdog.h> | ||
51 | #include <linux/delay.h> | ||
52 | #include <linux/fs.h> | ||
53 | #include <linux/ioport.h> | ||
54 | #include <linux/notifier.h> | ||
55 | #include <linux/reboot.h> | ||
56 | #include <linux/init.h> | ||
57 | #include <linux/spinlock.h> | ||
58 | |||
59 | #include <asm/io.h> | ||
60 | #include <asm/uaccess.h> | ||
61 | #include <asm/system.h> | ||
62 | |||
63 | /* enable support for minutes as units? */ | ||
64 | /* (does not always work correctly, so disabled by default!) */ | ||
65 | #define SMSC_SUPPORT_MINUTES | ||
66 | #undef SMSC_SUPPORT_MINUTES | ||
67 | |||
68 | #define MAX_TIMEOUT 255 | ||
69 | |||
70 | #define UNIT_SECOND 0 | ||
71 | #define UNIT_MINUTE 1 | ||
72 | |||
73 | #define MODNAME "smsc37b787_wdt: " | ||
74 | #define VERSION "1.1" | ||
75 | |||
76 | #define IOPORT 0x3F0 | ||
77 | #define IOPORT_SIZE 2 | ||
78 | #define IODEV_NO 8 | ||
79 | |||
80 | static int unit = UNIT_SECOND; /* timer's unit */ | ||
81 | static int timeout = 60; /* timeout value: default is 60 "units" */ | ||
82 | static unsigned long timer_enabled = 0; /* is the timer enabled? */ | ||
83 | |||
84 | static char expect_close; /* is the close expected? */ | ||
85 | |||
86 | static spinlock_t io_lock; /* to guard the watchdog from io races */ | ||
87 | |||
88 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
89 | |||
90 | /* -- Low level function ----------------------------------------*/ | ||
91 | |||
92 | /* unlock the IO chip */ | ||
93 | |||
94 | static inline void open_io_config(void) | ||
95 | { | ||
96 | outb(0x55, IOPORT); | ||
97 | mdelay(1); | ||
98 | outb(0x55, IOPORT); | ||
99 | } | ||
100 | |||
101 | /* lock the IO chip */ | ||
102 | static inline void close_io_config(void) | ||
103 | { | ||
104 | outb(0xAA, IOPORT); | ||
105 | } | ||
106 | |||
107 | /* select the IO device */ | ||
108 | static inline void select_io_device(unsigned char devno) | ||
109 | { | ||
110 | outb(0x07, IOPORT); | ||
111 | outb(devno, IOPORT+1); | ||
112 | } | ||
113 | |||
114 | /* write to the control register */ | ||
115 | static inline void write_io_cr(unsigned char reg, unsigned char data) | ||
116 | { | ||
117 | outb(reg, IOPORT); | ||
118 | outb(data, IOPORT+1); | ||
119 | } | ||
120 | |||
121 | /* read from the control register */ | ||
122 | static inline char read_io_cr(unsigned char reg) | ||
123 | { | ||
124 | outb(reg, IOPORT); | ||
125 | return inb(IOPORT+1); | ||
126 | } | ||
127 | |||
128 | /* -- Medium level functions ------------------------------------*/ | ||
129 | |||
130 | static inline void gpio_bit12(unsigned char reg) | ||
131 | { | ||
132 | // -- General Purpose I/O Bit 1.2 -- | ||
133 | // Bit 0, In/Out: 0 = Output, 1 = Input | ||
134 | // Bit 1, Polarity: 0 = No Invert, 1 = Invert | ||
135 | // Bit 2, Group Enable Intr.: 0 = Disable, 1 = Enable | ||
136 | // Bit 3/4, Function select: 00 = GPI/O, 01 = WDT, 10 = P17, | ||
137 | // 11 = Either Edge Triggered Intr. 2 | ||
138 | // Bit 5/6 (Reserved) | ||
139 | // Bit 7, Output Type: 0 = Push Pull Bit, 1 = Open Drain | ||
140 | write_io_cr(0xE2, reg); | ||
141 | } | ||
142 | |||
143 | static inline void gpio_bit13(unsigned char reg) | ||
144 | { | ||
145 | // -- General Purpose I/O Bit 1.3 -- | ||
146 | // Bit 0, In/Out: 0 = Output, 1 = Input | ||
147 | // Bit 1, Polarity: 0 = No Invert, 1 = Invert | ||
148 | // Bit 2, Group Enable Intr.: 0 = Disable, 1 = Enable | ||
149 | // Bit 3, Function select: 0 = GPI/O, 1 = LED | ||
150 | // Bit 4-6 (Reserved) | ||
151 | // Bit 7, Output Type: 0 = Push Pull Bit, 1 = Open Drain | ||
152 | write_io_cr(0xE3, reg); | ||
153 | } | ||
154 | |||
155 | static inline void wdt_timer_units(unsigned char new_units) | ||
156 | { | ||
157 | // -- Watchdog timer units -- | ||
158 | // Bit 0-6 (Reserved) | ||
159 | // Bit 7, WDT Time-out Value Units Select | ||
160 | // (0 = Minutes, 1 = Seconds) | ||
161 | write_io_cr(0xF1, new_units); | ||
162 | } | ||
163 | |||
164 | static inline void wdt_timeout_value(unsigned char new_timeout) | ||
165 | { | ||
166 | // -- Watchdog Timer Time-out Value -- | ||
167 | // Bit 0-7 Binary coded units (0=Disabled, 1..255) | ||
168 | write_io_cr(0xF2, new_timeout); | ||
169 | } | ||
170 | |||
171 | static inline void wdt_timer_conf(unsigned char conf) | ||
172 | { | ||
173 | // -- Watchdog timer configuration -- | ||
174 | // Bit 0 Joystick enable: 0* = No Reset, 1 = Reset WDT upon Gameport I/O | ||
175 | // Bit 1 Keyboard enable: 0* = No Reset, 1 = Reset WDT upon KBD Intr. | ||
176 | // Bit 2 Mouse enable: 0* = No Reset, 1 = Reset WDT upon Mouse Intr. | ||
177 | // Bit 3 Reset the timer | ||
178 | // (Wrong in SMsC documentation? Given as: PowerLED Timout Enabled) | ||
179 | // Bit 4-7 WDT Interrupt Mapping: (0000* = Disabled, | ||
180 | // 0001=IRQ1, 0010=(Invalid), 0011=IRQ3 to 1111=IRQ15) | ||
181 | write_io_cr(0xF3, conf); | ||
182 | } | ||
183 | |||
184 | static inline void wdt_timer_ctrl(unsigned char reg) | ||
185 | { | ||
186 | // -- Watchdog timer control -- | ||
187 | // Bit 0 Status Bit: 0 = Timer counting, 1 = Timeout occured | ||
188 | // Bit 1 Power LED Toggle: 0 = Disable Toggle, 1 = Toggle at 1 Hz | ||
189 | // Bit 2 Force Timeout: 1 = Forces WD timeout event (self-cleaning) | ||
190 | // Bit 3 P20 Force Timeout enabled: | ||
191 | // 0 = P20 activity does not generate the WD timeout event | ||
192 | // 1 = P20 Allows rising edge of P20, from the keyboard | ||
193 | // controller, to force the WD timeout event. | ||
194 | // Bit 4 (Reserved) | ||
195 | // -- Soft power management -- | ||
196 | // Bit 5 Stop Counter: 1 = Stop software power down counter | ||
197 | // set via register 0xB8, (self-cleaning) | ||
198 | // (Upon read: 0 = Counter running, 1 = Counter stopped) | ||
199 | // Bit 6 Restart Counter: 1 = Restart software power down counter | ||
200 | // set via register 0xB8, (self-cleaning) | ||
201 | // Bit 7 SPOFF: 1 = Force software power down (self-cleaning) | ||
202 | |||
203 | write_io_cr(0xF4, reg); | ||
204 | } | ||
205 | |||
206 | /* -- Higher level functions ------------------------------------*/ | ||
207 | |||
208 | /* initialize watchdog */ | ||
209 | |||
210 | static void wb_smsc_wdt_initialize(void) | ||
211 | { | ||
212 | unsigned char old; | ||
213 | |||
214 | spin_lock(&io_lock); | ||
215 | open_io_config(); | ||
216 | select_io_device(IODEV_NO); | ||
217 | |||
218 | // enable the watchdog | ||
219 | gpio_bit13(0x08); // Select pin 80 = LED not GPIO | ||
220 | gpio_bit12(0x0A); // Set pin 79 = WDT not GPIO/Output/Polarity=Invert | ||
221 | |||
222 | // disable the timeout | ||
223 | wdt_timeout_value(0); | ||
224 | |||
225 | // reset control register | ||
226 | wdt_timer_ctrl(0x00); | ||
227 | |||
228 | // reset configuration register | ||
229 | wdt_timer_conf(0x00); | ||
230 | |||
231 | // read old (timer units) register | ||
232 | old = read_io_cr(0xF1) & 0x7F; | ||
233 | if (unit == UNIT_SECOND) old |= 0x80; // set to seconds | ||
234 | |||
235 | // set the watchdog timer units | ||
236 | wdt_timer_units(old); | ||
237 | |||
238 | close_io_config(); | ||
239 | spin_unlock(&io_lock); | ||
240 | } | ||
241 | |||
242 | /* shutdown the watchdog */ | ||
243 | |||
244 | static void wb_smsc_wdt_shutdown(void) | ||
245 | { | ||
246 | spin_lock(&io_lock); | ||
247 | open_io_config(); | ||
248 | select_io_device(IODEV_NO); | ||
249 | |||
250 | // disable the watchdog | ||
251 | gpio_bit13(0x09); | ||
252 | gpio_bit12(0x09); | ||
253 | |||
254 | // reset watchdog config register | ||
255 | wdt_timer_conf(0x00); | ||
256 | |||
257 | // reset watchdog control register | ||
258 | wdt_timer_ctrl(0x00); | ||
259 | |||
260 | // disable timeout | ||
261 | wdt_timeout_value(0x00); | ||
262 | |||
263 | close_io_config(); | ||
264 | spin_unlock(&io_lock); | ||
265 | } | ||
266 | |||
267 | /* set timeout => enable watchdog */ | ||
268 | |||
269 | static void wb_smsc_wdt_set_timeout(unsigned char new_timeout) | ||
270 | { | ||
271 | spin_lock(&io_lock); | ||
272 | open_io_config(); | ||
273 | select_io_device(IODEV_NO); | ||
274 | |||
275 | // set Power LED to blink, if we enable the timeout | ||
276 | wdt_timer_ctrl((new_timeout == 0) ? 0x00 : 0x02); | ||
277 | |||
278 | // set timeout value | ||
279 | wdt_timeout_value(new_timeout); | ||
280 | |||
281 | close_io_config(); | ||
282 | spin_unlock(&io_lock); | ||
283 | } | ||
284 | |||
285 | /* get timeout */ | ||
286 | |||
287 | static unsigned char wb_smsc_wdt_get_timeout(void) | ||
288 | { | ||
289 | unsigned char set_timeout; | ||
290 | |||
291 | spin_lock(&io_lock); | ||
292 | open_io_config(); | ||
293 | select_io_device(IODEV_NO); | ||
294 | set_timeout = read_io_cr(0xF2); | ||
295 | close_io_config(); | ||
296 | spin_unlock(&io_lock); | ||
297 | |||
298 | return set_timeout; | ||
299 | } | ||
300 | |||
301 | /* disable watchdog */ | ||
302 | |||
303 | static void wb_smsc_wdt_disable(void) | ||
304 | { | ||
305 | // set the timeout to 0 to disable the watchdog | ||
306 | wb_smsc_wdt_set_timeout(0); | ||
307 | } | ||
308 | |||
309 | /* enable watchdog by setting the current timeout */ | ||
310 | |||
311 | static void wb_smsc_wdt_enable(void) | ||
312 | { | ||
313 | // set the current timeout... | ||
314 | wb_smsc_wdt_set_timeout(timeout); | ||
315 | } | ||
316 | |||
317 | /* reset the timer */ | ||
318 | |||
319 | static void wb_smsc_wdt_reset_timer(void) | ||
320 | { | ||
321 | spin_lock(&io_lock); | ||
322 | open_io_config(); | ||
323 | select_io_device(IODEV_NO); | ||
324 | |||
325 | // reset the timer | ||
326 | wdt_timeout_value(timeout); | ||
327 | wdt_timer_conf(0x08); | ||
328 | |||
329 | close_io_config(); | ||
330 | spin_unlock(&io_lock); | ||
331 | } | ||
332 | |||
333 | /* return, if the watchdog is enabled (timeout is set...) */ | ||
334 | |||
335 | static int wb_smsc_wdt_status(void) | ||
336 | { | ||
337 | return (wb_smsc_wdt_get_timeout() == 0) ? 0 : WDIOF_KEEPALIVEPING; | ||
338 | } | ||
339 | |||
340 | |||
341 | /* -- File operations -------------------------------------------*/ | ||
342 | |||
343 | /* open => enable watchdog and set initial timeout */ | ||
344 | |||
345 | static int wb_smsc_wdt_open(struct inode *inode, struct file *file) | ||
346 | { | ||
347 | /* /dev/watchdog can only be opened once */ | ||
348 | |||
349 | if (test_and_set_bit(0, &timer_enabled)) | ||
350 | return -EBUSY; | ||
351 | |||
352 | if (nowayout) | ||
353 | __module_get(THIS_MODULE); | ||
354 | |||
355 | /* Reload and activate timer */ | ||
356 | wb_smsc_wdt_enable(); | ||
357 | |||
358 | printk(KERN_INFO MODNAME "Watchdog enabled. Timeout set to %d %s.\n", timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)"); | ||
359 | |||
360 | return nonseekable_open(inode, file); | ||
361 | } | ||
362 | |||
363 | /* close => shut off the timer */ | ||
364 | |||
365 | static int wb_smsc_wdt_release(struct inode *inode, struct file *file) | ||
366 | { | ||
367 | /* Shut off the timer. */ | ||
368 | |||
369 | if (expect_close == 42) { | ||
370 | wb_smsc_wdt_disable(); | ||
371 | printk(KERN_INFO MODNAME "Watchdog disabled, sleeping again...\n"); | ||
372 | } else { | ||
373 | printk(KERN_CRIT MODNAME "Unexpected close, not stopping watchdog!\n"); | ||
374 | wb_smsc_wdt_reset_timer(); | ||
375 | } | ||
376 | |||
377 | clear_bit(0, &timer_enabled); | ||
378 | expect_close = 0; | ||
379 | return 0; | ||
380 | } | ||
381 | |||
382 | /* write => update the timer to keep the machine alive */ | ||
383 | |||
384 | static ssize_t wb_smsc_wdt_write(struct file *file, const char __user *data, | ||
385 | size_t len, loff_t *ppos) | ||
386 | { | ||
387 | /* See if we got the magic character 'V' and reload the timer */ | ||
388 | if (len) { | ||
389 | if (!nowayout) { | ||
390 | size_t i; | ||
391 | |||
392 | /* reset expect flag */ | ||
393 | expect_close = 0; | ||
394 | |||
395 | /* scan to see whether or not we got the magic character */ | ||
396 | for (i = 0; i != len; i++) { | ||
397 | char c; | ||
398 | if (get_user(c, data+i)) | ||
399 | return -EFAULT; | ||
400 | if (c == 'V') | ||
401 | expect_close = 42; | ||
402 | } | ||
403 | } | ||
404 | |||
405 | /* someone wrote to us, we should reload the timer */ | ||
406 | wb_smsc_wdt_reset_timer(); | ||
407 | } | ||
408 | return len; | ||
409 | } | ||
410 | |||
411 | /* ioctl => control interface */ | ||
412 | |||
413 | static int wb_smsc_wdt_ioctl(struct inode *inode, struct file *file, | ||
414 | unsigned int cmd, unsigned long arg) | ||
415 | { | ||
416 | int new_timeout; | ||
417 | |||
418 | union { | ||
419 | struct watchdog_info __user *ident; | ||
420 | int __user *i; | ||
421 | } uarg; | ||
422 | |||
423 | static struct watchdog_info ident = { | ||
424 | .options = WDIOF_KEEPALIVEPING | | ||
425 | WDIOF_SETTIMEOUT | | ||
426 | WDIOF_MAGICCLOSE, | ||
427 | .firmware_version = 0, | ||
428 | .identity = "SMsC 37B787 Watchdog" | ||
429 | }; | ||
430 | |||
431 | uarg.i = (int __user *)arg; | ||
432 | |||
433 | switch (cmd) { | ||
434 | default: | ||
435 | return -ENOTTY; | ||
436 | |||
437 | case WDIOC_GETSUPPORT: | ||
438 | return copy_to_user(uarg.ident, &ident, | ||
439 | sizeof(ident)) ? -EFAULT : 0; | ||
440 | |||
441 | case WDIOC_GETSTATUS: | ||
442 | return put_user(wb_smsc_wdt_status(), uarg.i); | ||
443 | |||
444 | case WDIOC_GETBOOTSTATUS: | ||
445 | return put_user(0, uarg.i); | ||
446 | |||
447 | case WDIOC_KEEPALIVE: | ||
448 | wb_smsc_wdt_reset_timer(); | ||
449 | return 0; | ||
450 | |||
451 | case WDIOC_SETTIMEOUT: | ||
452 | if (get_user(new_timeout, uarg.i)) | ||
453 | return -EFAULT; | ||
454 | |||
455 | // the API states this is given in secs | ||
456 | if (unit == UNIT_MINUTE) | ||
457 | new_timeout /= 60; | ||
458 | |||
459 | if (new_timeout < 0 || new_timeout > MAX_TIMEOUT) | ||
460 | return -EINVAL; | ||
461 | |||
462 | timeout = new_timeout; | ||
463 | wb_smsc_wdt_set_timeout(timeout); | ||
464 | |||
465 | // fall through and return the new timeout... | ||
466 | |||
467 | case WDIOC_GETTIMEOUT: | ||
468 | |||
469 | new_timeout = timeout; | ||
470 | |||
471 | if (unit == UNIT_MINUTE) | ||
472 | new_timeout *= 60; | ||
473 | |||
474 | return put_user(new_timeout, uarg.i); | ||
475 | |||
476 | case WDIOC_SETOPTIONS: | ||
477 | { | ||
478 | int options, retval = -EINVAL; | ||
479 | |||
480 | if (get_user(options, uarg.i)) | ||
481 | return -EFAULT; | ||
482 | |||
483 | if (options & WDIOS_DISABLECARD) { | ||
484 | wb_smsc_wdt_disable(); | ||
485 | retval = 0; | ||
486 | } | ||
487 | |||
488 | if (options & WDIOS_ENABLECARD) { | ||
489 | wb_smsc_wdt_enable(); | ||
490 | retval = 0; | ||
491 | } | ||
492 | |||
493 | return retval; | ||
494 | } | ||
495 | } | ||
496 | } | ||
497 | |||
498 | /* -- Notifier funtions -----------------------------------------*/ | ||
499 | |||
500 | static int wb_smsc_wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
501 | { | ||
502 | if (code == SYS_DOWN || code == SYS_HALT) | ||
503 | { | ||
504 | // set timeout to 0, to avoid possible race-condition | ||
505 | timeout = 0; | ||
506 | wb_smsc_wdt_disable(); | ||
507 | } | ||
508 | return NOTIFY_DONE; | ||
509 | } | ||
510 | |||
511 | /* -- Module's structures ---------------------------------------*/ | ||
512 | |||
513 | static const struct file_operations wb_smsc_wdt_fops = | ||
514 | { | ||
515 | .owner = THIS_MODULE, | ||
516 | .llseek = no_llseek, | ||
517 | .write = wb_smsc_wdt_write, | ||
518 | .ioctl = wb_smsc_wdt_ioctl, | ||
519 | .open = wb_smsc_wdt_open, | ||
520 | .release = wb_smsc_wdt_release, | ||
521 | }; | ||
522 | |||
523 | static struct notifier_block wb_smsc_wdt_notifier = | ||
524 | { | ||
525 | .notifier_call = wb_smsc_wdt_notify_sys, | ||
526 | }; | ||
527 | |||
528 | static struct miscdevice wb_smsc_wdt_miscdev = | ||
529 | { | ||
530 | .minor = WATCHDOG_MINOR, | ||
531 | .name = "watchdog", | ||
532 | .fops = &wb_smsc_wdt_fops, | ||
533 | }; | ||
534 | |||
535 | /* -- Module init functions -------------------------------------*/ | ||
536 | |||
537 | /* module's "constructor" */ | ||
538 | |||
539 | static int __init wb_smsc_wdt_init(void) | ||
540 | { | ||
541 | int ret; | ||
542 | |||
543 | spin_lock_init(&io_lock); | ||
544 | |||
545 | printk("SMsC 37B787 watchdog component driver " VERSION " initialising...\n"); | ||
546 | |||
547 | if (!request_region(IOPORT, IOPORT_SIZE, "SMsC 37B787 watchdog")) { | ||
548 | printk(KERN_ERR MODNAME "Unable to register IO port %#x\n", IOPORT); | ||
549 | ret = -EBUSY; | ||
550 | goto out_pnp; | ||
551 | } | ||
552 | |||
553 | // set new maximum, if it's too big | ||
554 | if (timeout > MAX_TIMEOUT) | ||
555 | timeout = MAX_TIMEOUT; | ||
556 | |||
557 | // init the watchdog timer | ||
558 | wb_smsc_wdt_initialize(); | ||
559 | |||
560 | ret = register_reboot_notifier(&wb_smsc_wdt_notifier); | ||
561 | if (ret) { | ||
562 | printk(KERN_ERR MODNAME "Unable to register reboot notifier err = %d\n", ret); | ||
563 | goto out_io; | ||
564 | } | ||
565 | |||
566 | ret = misc_register(&wb_smsc_wdt_miscdev); | ||
567 | if (ret) { | ||
568 | printk(KERN_ERR MODNAME "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR); | ||
569 | goto out_rbt; | ||
570 | } | ||
571 | |||
572 | // output info | ||
573 | printk(KERN_INFO MODNAME "Timeout set to %d %s.\n", timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)"); | ||
574 | printk(KERN_INFO MODNAME "Watchdog initialized and sleeping (nowayout=%d)...\n", nowayout); | ||
575 | |||
576 | // ret = 0 | ||
577 | |||
578 | out_clean: | ||
579 | return ret; | ||
580 | |||
581 | out_rbt: | ||
582 | unregister_reboot_notifier(&wb_smsc_wdt_notifier); | ||
583 | |||
584 | out_io: | ||
585 | release_region(IOPORT, IOPORT_SIZE); | ||
586 | |||
587 | out_pnp: | ||
588 | goto out_clean; | ||
589 | } | ||
590 | |||
591 | /* module's "destructor" */ | ||
592 | |||
593 | static void __exit wb_smsc_wdt_exit(void) | ||
594 | { | ||
595 | /* Stop the timer before we leave */ | ||
596 | if (!nowayout) | ||
597 | { | ||
598 | wb_smsc_wdt_shutdown(); | ||
599 | printk(KERN_INFO MODNAME "Watchdog disabled.\n"); | ||
600 | } | ||
601 | |||
602 | misc_deregister(&wb_smsc_wdt_miscdev); | ||
603 | unregister_reboot_notifier(&wb_smsc_wdt_notifier); | ||
604 | release_region(IOPORT, IOPORT_SIZE); | ||
605 | |||
606 | printk("SMsC 37B787 watchdog component driver removed.\n"); | ||
607 | } | ||
608 | |||
609 | module_init(wb_smsc_wdt_init); | ||
610 | module_exit(wb_smsc_wdt_exit); | ||
611 | |||
612 | MODULE_AUTHOR("Sven Anders <anders@anduras.de>"); | ||
613 | MODULE_DESCRIPTION("Driver for SMsC 37B787 watchdog component (Version " VERSION ")"); | ||
614 | MODULE_LICENSE("GPL"); | ||
615 | |||
616 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
617 | |||
618 | #ifdef SMSC_SUPPORT_MINUTES | ||
619 | module_param(unit, int, 0); | ||
620 | MODULE_PARM_DESC(unit, "set unit to use, 0=seconds or 1=minutes, default is 0"); | ||
621 | #endif | ||
622 | |||
623 | module_param(timeout, int, 0); | ||
624 | MODULE_PARM_DESC(timeout, "range is 1-255 units, default is 60"); | ||
625 | |||
626 | module_param(nowayout, int, 0); | ||
627 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
diff --git a/drivers/watchdog/softdog.c b/drivers/watchdog/softdog.c new file mode 100644 index 000000000000..9c3694909243 --- /dev/null +++ b/drivers/watchdog/softdog.c | |||
@@ -0,0 +1,310 @@ | |||
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/types.h> | ||
42 | #include <linux/timer.h> | ||
43 | #include <linux/miscdevice.h> | ||
44 | #include <linux/watchdog.h> | ||
45 | #include <linux/fs.h> | ||
46 | #include <linux/notifier.h> | ||
47 | #include <linux/reboot.h> | ||
48 | #include <linux/init.h> | ||
49 | #include <linux/jiffies.h> | ||
50 | |||
51 | #include <asm/uaccess.h> | ||
52 | |||
53 | #define PFX "SoftDog: " | ||
54 | |||
55 | #define TIMER_MARGIN 60 /* Default is 60 seconds */ | ||
56 | static int soft_margin = TIMER_MARGIN; /* in seconds */ | ||
57 | module_param(soft_margin, int, 0); | ||
58 | MODULE_PARM_DESC(soft_margin, "Watchdog soft_margin in seconds. (0<soft_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")"); | ||
59 | |||
60 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
61 | module_param(nowayout, int, 0); | ||
62 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
63 | |||
64 | #ifdef ONLY_TESTING | ||
65 | static int soft_noboot = 1; | ||
66 | #else | ||
67 | static int soft_noboot = 0; | ||
68 | #endif /* ONLY_TESTING */ | ||
69 | |||
70 | module_param(soft_noboot, int, 0); | ||
71 | MODULE_PARM_DESC(soft_noboot, "Softdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); | ||
72 | |||
73 | /* | ||
74 | * Our timer | ||
75 | */ | ||
76 | |||
77 | static void watchdog_fire(unsigned long); | ||
78 | |||
79 | static struct timer_list watchdog_ticktock = | ||
80 | TIMER_INITIALIZER(watchdog_fire, 0, 0); | ||
81 | static unsigned long driver_open, orphan_timer; | ||
82 | static char expect_close; | ||
83 | |||
84 | |||
85 | /* | ||
86 | * If the timer expires.. | ||
87 | */ | ||
88 | |||
89 | static void watchdog_fire(unsigned long data) | ||
90 | { | ||
91 | if (test_and_clear_bit(0, &orphan_timer)) | ||
92 | module_put(THIS_MODULE); | ||
93 | |||
94 | if (soft_noboot) | ||
95 | printk(KERN_CRIT PFX "Triggered - Reboot ignored.\n"); | ||
96 | else | ||
97 | { | ||
98 | printk(KERN_CRIT PFX "Initiating system reboot.\n"); | ||
99 | emergency_restart(); | ||
100 | printk(KERN_CRIT PFX "Reboot didn't ?????\n"); | ||
101 | } | ||
102 | } | ||
103 | |||
104 | /* | ||
105 | * Softdog operations | ||
106 | */ | ||
107 | |||
108 | static int softdog_keepalive(void) | ||
109 | { | ||
110 | mod_timer(&watchdog_ticktock, jiffies+(soft_margin*HZ)); | ||
111 | return 0; | ||
112 | } | ||
113 | |||
114 | static int softdog_stop(void) | ||
115 | { | ||
116 | del_timer(&watchdog_ticktock); | ||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | static int softdog_set_heartbeat(int t) | ||
121 | { | ||
122 | if ((t < 0x0001) || (t > 0xFFFF)) | ||
123 | return -EINVAL; | ||
124 | |||
125 | soft_margin = t; | ||
126 | return 0; | ||
127 | } | ||
128 | |||
129 | /* | ||
130 | * /dev/watchdog handling | ||
131 | */ | ||
132 | |||
133 | static int softdog_open(struct inode *inode, struct file *file) | ||
134 | { | ||
135 | if (test_and_set_bit(0, &driver_open)) | ||
136 | return -EBUSY; | ||
137 | if (!test_and_clear_bit(0, &orphan_timer)) | ||
138 | __module_get(THIS_MODULE); | ||
139 | /* | ||
140 | * Activate timer | ||
141 | */ | ||
142 | softdog_keepalive(); | ||
143 | return nonseekable_open(inode, file); | ||
144 | } | ||
145 | |||
146 | static int softdog_release(struct inode *inode, struct file *file) | ||
147 | { | ||
148 | /* | ||
149 | * Shut off the timer. | ||
150 | * Lock it in if it's a module and we set nowayout | ||
151 | */ | ||
152 | if (expect_close == 42) { | ||
153 | softdog_stop(); | ||
154 | module_put(THIS_MODULE); | ||
155 | } else { | ||
156 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
157 | set_bit(0, &orphan_timer); | ||
158 | softdog_keepalive(); | ||
159 | } | ||
160 | clear_bit(0, &driver_open); | ||
161 | expect_close = 0; | ||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | static ssize_t softdog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) | ||
166 | { | ||
167 | /* | ||
168 | * Refresh the timer. | ||
169 | */ | ||
170 | if(len) { | ||
171 | if (!nowayout) { | ||
172 | size_t i; | ||
173 | |||
174 | /* In case it was set long ago */ | ||
175 | expect_close = 0; | ||
176 | |||
177 | for (i = 0; i != len; i++) { | ||
178 | char c; | ||
179 | |||
180 | if (get_user(c, data + i)) | ||
181 | return -EFAULT; | ||
182 | if (c == 'V') | ||
183 | expect_close = 42; | ||
184 | } | ||
185 | } | ||
186 | softdog_keepalive(); | ||
187 | } | ||
188 | return len; | ||
189 | } | ||
190 | |||
191 | static int softdog_ioctl(struct inode *inode, struct file *file, | ||
192 | unsigned int cmd, unsigned long arg) | ||
193 | { | ||
194 | void __user *argp = (void __user *)arg; | ||
195 | int __user *p = argp; | ||
196 | int new_margin; | ||
197 | static struct watchdog_info ident = { | ||
198 | .options = WDIOF_SETTIMEOUT | | ||
199 | WDIOF_KEEPALIVEPING | | ||
200 | WDIOF_MAGICCLOSE, | ||
201 | .firmware_version = 0, | ||
202 | .identity = "Software Watchdog", | ||
203 | }; | ||
204 | switch (cmd) { | ||
205 | default: | ||
206 | return -ENOTTY; | ||
207 | case WDIOC_GETSUPPORT: | ||
208 | return copy_to_user(argp, &ident, | ||
209 | sizeof(ident)) ? -EFAULT : 0; | ||
210 | case WDIOC_GETSTATUS: | ||
211 | case WDIOC_GETBOOTSTATUS: | ||
212 | return put_user(0, p); | ||
213 | case WDIOC_KEEPALIVE: | ||
214 | softdog_keepalive(); | ||
215 | return 0; | ||
216 | case WDIOC_SETTIMEOUT: | ||
217 | if (get_user(new_margin, p)) | ||
218 | return -EFAULT; | ||
219 | if (softdog_set_heartbeat(new_margin)) | ||
220 | return -EINVAL; | ||
221 | softdog_keepalive(); | ||
222 | /* Fall */ | ||
223 | case WDIOC_GETTIMEOUT: | ||
224 | return put_user(soft_margin, p); | ||
225 | } | ||
226 | } | ||
227 | |||
228 | /* | ||
229 | * Notifier for system down | ||
230 | */ | ||
231 | |||
232 | static int softdog_notify_sys(struct notifier_block *this, unsigned long code, | ||
233 | void *unused) | ||
234 | { | ||
235 | if(code==SYS_DOWN || code==SYS_HALT) { | ||
236 | /* Turn the WDT off */ | ||
237 | softdog_stop(); | ||
238 | } | ||
239 | return NOTIFY_DONE; | ||
240 | } | ||
241 | |||
242 | /* | ||
243 | * Kernel Interfaces | ||
244 | */ | ||
245 | |||
246 | static const struct file_operations softdog_fops = { | ||
247 | .owner = THIS_MODULE, | ||
248 | .llseek = no_llseek, | ||
249 | .write = softdog_write, | ||
250 | .ioctl = softdog_ioctl, | ||
251 | .open = softdog_open, | ||
252 | .release = softdog_release, | ||
253 | }; | ||
254 | |||
255 | static struct miscdevice softdog_miscdev = { | ||
256 | .minor = WATCHDOG_MINOR, | ||
257 | .name = "watchdog", | ||
258 | .fops = &softdog_fops, | ||
259 | }; | ||
260 | |||
261 | static struct notifier_block softdog_notifier = { | ||
262 | .notifier_call = softdog_notify_sys, | ||
263 | }; | ||
264 | |||
265 | static char banner[] __initdata = KERN_INFO "Software Watchdog Timer: 0.07 initialized. soft_noboot=%d soft_margin=%d sec (nowayout= %d)\n"; | ||
266 | |||
267 | static int __init watchdog_init(void) | ||
268 | { | ||
269 | int ret; | ||
270 | |||
271 | /* Check that the soft_margin value is within it's range ; if not reset to the default */ | ||
272 | if (softdog_set_heartbeat(soft_margin)) { | ||
273 | softdog_set_heartbeat(TIMER_MARGIN); | ||
274 | printk(KERN_INFO PFX "soft_margin value must be 0<soft_margin<65536, using %d\n", | ||
275 | TIMER_MARGIN); | ||
276 | } | ||
277 | |||
278 | ret = register_reboot_notifier(&softdog_notifier); | ||
279 | if (ret) { | ||
280 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
281 | ret); | ||
282 | return ret; | ||
283 | } | ||
284 | |||
285 | ret = misc_register(&softdog_miscdev); | ||
286 | if (ret) { | ||
287 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
288 | WATCHDOG_MINOR, ret); | ||
289 | unregister_reboot_notifier(&softdog_notifier); | ||
290 | return ret; | ||
291 | } | ||
292 | |||
293 | printk(banner, soft_noboot, soft_margin, nowayout); | ||
294 | |||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | static void __exit watchdog_exit(void) | ||
299 | { | ||
300 | misc_deregister(&softdog_miscdev); | ||
301 | unregister_reboot_notifier(&softdog_notifier); | ||
302 | } | ||
303 | |||
304 | module_init(watchdog_init); | ||
305 | module_exit(watchdog_exit); | ||
306 | |||
307 | MODULE_AUTHOR("Alan Cox"); | ||
308 | MODULE_DESCRIPTION("Software Watchdog Device Driver"); | ||
309 | MODULE_LICENSE("GPL"); | ||
310 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/w83627hf_wdt.c b/drivers/watchdog/w83627hf_wdt.c new file mode 100644 index 000000000000..df33b3b5a53c --- /dev/null +++ b/drivers/watchdog/w83627hf_wdt.c | |||
@@ -0,0 +1,390 @@ | |||
1 | /* | ||
2 | * w83627hf/thf WDT driver | ||
3 | * | ||
4 | * (c) Copyright 2007 Vlad Drukker <vlad@storewiz.com> | ||
5 | * added support for W83627THF. | ||
6 | * | ||
7 | * (c) Copyright 2003,2007 Pádraig Brady <P@draigBrady.com> | ||
8 | * | ||
9 | * Based on advantechwdt.c which is based on wdt.c. | ||
10 | * Original copyright messages: | ||
11 | * | ||
12 | * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> | ||
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 | |||
29 | #include <linux/module.h> | ||
30 | #include <linux/moduleparam.h> | ||
31 | #include <linux/types.h> | ||
32 | #include <linux/miscdevice.h> | ||
33 | #include <linux/watchdog.h> | ||
34 | #include <linux/fs.h> | ||
35 | #include <linux/ioport.h> | ||
36 | #include <linux/notifier.h> | ||
37 | #include <linux/reboot.h> | ||
38 | #include <linux/init.h> | ||
39 | #include <linux/spinlock.h> | ||
40 | |||
41 | #include <asm/io.h> | ||
42 | #include <asm/uaccess.h> | ||
43 | #include <asm/system.h> | ||
44 | |||
45 | #define WATCHDOG_NAME "w83627hf/thf/hg WDT" | ||
46 | #define PFX WATCHDOG_NAME ": " | ||
47 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ | ||
48 | |||
49 | static unsigned long wdt_is_open; | ||
50 | static char expect_close; | ||
51 | static spinlock_t io_lock; | ||
52 | |||
53 | /* You must set this - there is no sane way to probe for this board. */ | ||
54 | static int wdt_io = 0x2E; | ||
55 | module_param(wdt_io, int, 0); | ||
56 | MODULE_PARM_DESC(wdt_io, "w83627hf/thf WDT io port (default 0x2E)"); | ||
57 | |||
58 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ | ||
59 | module_param(timeout, int, 0); | ||
60 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=255, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) "."); | ||
61 | |||
62 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
63 | module_param(nowayout, int, 0); | ||
64 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(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 | unsigned char c; | ||
78 | outb_p(0x87, WDT_EFER); /* Enter extended function mode */ | ||
79 | outb_p(0x87, WDT_EFER); /* Again according to manual */ | ||
80 | |||
81 | outb(0x20, WDT_EFER); /* check chip version */ | ||
82 | c = inb(WDT_EFDR); | ||
83 | if (c == 0x82) { /* W83627THF */ | ||
84 | outb_p(0x2b, WDT_EFER); /* select GPIO3 */ | ||
85 | c = ((inb_p(WDT_EFDR) & 0xf7) | 0x04); /* select WDT0 */ | ||
86 | outb_p(0x2b, WDT_EFER); | ||
87 | outb_p(c, WDT_EFDR); /* set GPIO3 to WDT0 */ | ||
88 | } | ||
89 | |||
90 | outb_p(0x07, WDT_EFER); /* point to logical device number reg */ | ||
91 | outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */ | ||
92 | outb_p(0x30, WDT_EFER); /* select CR30 */ | ||
93 | outb_p(0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */ | ||
94 | } | ||
95 | |||
96 | static void | ||
97 | w83627hf_unselect_wd_register(void) | ||
98 | { | ||
99 | outb_p(0xAA, WDT_EFER); /* Leave extended function mode */ | ||
100 | } | ||
101 | |||
102 | /* tyan motherboards seem to set F5 to 0x4C ? | ||
103 | * So explicitly init to appropriate value. */ | ||
104 | static void | ||
105 | w83627hf_init(void) | ||
106 | { | ||
107 | unsigned char t; | ||
108 | |||
109 | w83627hf_select_wd_register(); | ||
110 | |||
111 | outb_p(0xF6, WDT_EFER); /* Select CRF6 */ | ||
112 | t=inb_p(WDT_EFDR); /* read CRF6 */ | ||
113 | if (t != 0) { | ||
114 | printk (KERN_INFO PFX "Watchdog already running. Resetting timeout to %d sec\n", timeout); | ||
115 | outb_p(timeout, WDT_EFDR); /* Write back to CRF6 */ | ||
116 | } | ||
117 | |||
118 | outb_p(0xF5, WDT_EFER); /* Select CRF5 */ | ||
119 | t=inb_p(WDT_EFDR); /* read CRF5 */ | ||
120 | t&=~0x0C; /* set second mode & disable keyboard turning off watchdog */ | ||
121 | outb_p(t, WDT_EFDR); /* Write back to CRF5 */ | ||
122 | |||
123 | outb_p(0xF7, WDT_EFER); /* Select CRF7 */ | ||
124 | t=inb_p(WDT_EFDR); /* read CRF7 */ | ||
125 | t&=~0xC0; /* disable keyboard & mouse turning off watchdog */ | ||
126 | outb_p(t, WDT_EFDR); /* Write back to CRF7 */ | ||
127 | |||
128 | w83627hf_unselect_wd_register(); | ||
129 | } | ||
130 | |||
131 | static void | ||
132 | wdt_ctrl(int timeout) | ||
133 | { | ||
134 | spin_lock(&io_lock); | ||
135 | |||
136 | w83627hf_select_wd_register(); | ||
137 | |||
138 | outb_p(0xF6, WDT_EFER); /* Select CRF6 */ | ||
139 | outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF6 */ | ||
140 | |||
141 | w83627hf_unselect_wd_register(); | ||
142 | |||
143 | spin_unlock(&io_lock); | ||
144 | } | ||
145 | |||
146 | static int | ||
147 | wdt_ping(void) | ||
148 | { | ||
149 | wdt_ctrl(timeout); | ||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static int | ||
154 | wdt_disable(void) | ||
155 | { | ||
156 | wdt_ctrl(0); | ||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static int | ||
161 | wdt_set_heartbeat(int t) | ||
162 | { | ||
163 | if ((t < 1) || (t > 255)) | ||
164 | return -EINVAL; | ||
165 | |||
166 | timeout = t; | ||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | static ssize_t | ||
171 | wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
172 | { | ||
173 | if (count) { | ||
174 | if (!nowayout) { | ||
175 | size_t i; | ||
176 | |||
177 | expect_close = 0; | ||
178 | |||
179 | for (i = 0; i != count; i++) { | ||
180 | char c; | ||
181 | if (get_user(c, buf+i)) | ||
182 | return -EFAULT; | ||
183 | if (c == 'V') | ||
184 | expect_close = 42; | ||
185 | } | ||
186 | } | ||
187 | wdt_ping(); | ||
188 | } | ||
189 | return count; | ||
190 | } | ||
191 | |||
192 | static int | ||
193 | wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
194 | unsigned long arg) | ||
195 | { | ||
196 | void __user *argp = (void __user *)arg; | ||
197 | int __user *p = argp; | ||
198 | int new_timeout; | ||
199 | static struct watchdog_info ident = { | ||
200 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
201 | .firmware_version = 1, | ||
202 | .identity = "W83627HF WDT", | ||
203 | }; | ||
204 | |||
205 | switch (cmd) { | ||
206 | case WDIOC_GETSUPPORT: | ||
207 | if (copy_to_user(argp, &ident, sizeof(ident))) | ||
208 | return -EFAULT; | ||
209 | break; | ||
210 | |||
211 | case WDIOC_GETSTATUS: | ||
212 | case WDIOC_GETBOOTSTATUS: | ||
213 | return put_user(0, p); | ||
214 | |||
215 | case WDIOC_KEEPALIVE: | ||
216 | wdt_ping(); | ||
217 | break; | ||
218 | |||
219 | case WDIOC_SETTIMEOUT: | ||
220 | if (get_user(new_timeout, p)) | ||
221 | return -EFAULT; | ||
222 | if (wdt_set_heartbeat(new_timeout)) | ||
223 | return -EINVAL; | ||
224 | wdt_ping(); | ||
225 | /* Fall */ | ||
226 | |||
227 | case WDIOC_GETTIMEOUT: | ||
228 | return put_user(timeout, p); | ||
229 | |||
230 | case WDIOC_SETOPTIONS: | ||
231 | { | ||
232 | int options, retval = -EINVAL; | ||
233 | |||
234 | if (get_user(options, p)) | ||
235 | return -EFAULT; | ||
236 | |||
237 | if (options & WDIOS_DISABLECARD) { | ||
238 | wdt_disable(); | ||
239 | retval = 0; | ||
240 | } | ||
241 | |||
242 | if (options & WDIOS_ENABLECARD) { | ||
243 | wdt_ping(); | ||
244 | retval = 0; | ||
245 | } | ||
246 | |||
247 | return retval; | ||
248 | } | ||
249 | |||
250 | default: | ||
251 | return -ENOTTY; | ||
252 | } | ||
253 | return 0; | ||
254 | } | ||
255 | |||
256 | static int | ||
257 | wdt_open(struct inode *inode, struct file *file) | ||
258 | { | ||
259 | if (test_and_set_bit(0, &wdt_is_open)) | ||
260 | return -EBUSY; | ||
261 | /* | ||
262 | * Activate | ||
263 | */ | ||
264 | |||
265 | wdt_ping(); | ||
266 | return nonseekable_open(inode, file); | ||
267 | } | ||
268 | |||
269 | static int | ||
270 | wdt_close(struct inode *inode, struct file *file) | ||
271 | { | ||
272 | if (expect_close == 42) { | ||
273 | wdt_disable(); | ||
274 | } else { | ||
275 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
276 | wdt_ping(); | ||
277 | } | ||
278 | expect_close = 0; | ||
279 | clear_bit(0, &wdt_is_open); | ||
280 | return 0; | ||
281 | } | ||
282 | |||
283 | /* | ||
284 | * Notifier for system down | ||
285 | */ | ||
286 | |||
287 | static int | ||
288 | wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
289 | void *unused) | ||
290 | { | ||
291 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
292 | /* Turn the WDT off */ | ||
293 | wdt_disable(); | ||
294 | } | ||
295 | return NOTIFY_DONE; | ||
296 | } | ||
297 | |||
298 | /* | ||
299 | * Kernel Interfaces | ||
300 | */ | ||
301 | |||
302 | static const struct file_operations wdt_fops = { | ||
303 | .owner = THIS_MODULE, | ||
304 | .llseek = no_llseek, | ||
305 | .write = wdt_write, | ||
306 | .ioctl = wdt_ioctl, | ||
307 | .open = wdt_open, | ||
308 | .release = wdt_close, | ||
309 | }; | ||
310 | |||
311 | static struct miscdevice wdt_miscdev = { | ||
312 | .minor = WATCHDOG_MINOR, | ||
313 | .name = "watchdog", | ||
314 | .fops = &wdt_fops, | ||
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 | .notifier_call = wdt_notify_sys, | ||
324 | }; | ||
325 | |||
326 | static int __init | ||
327 | wdt_init(void) | ||
328 | { | ||
329 | int ret; | ||
330 | |||
331 | spin_lock_init(&io_lock); | ||
332 | |||
333 | printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF/THF/HG Super I/O chip initialising.\n"); | ||
334 | |||
335 | if (wdt_set_heartbeat(timeout)) { | ||
336 | wdt_set_heartbeat(WATCHDOG_TIMEOUT); | ||
337 | printk (KERN_INFO PFX "timeout value must be 1<=timeout<=255, using %d\n", | ||
338 | WATCHDOG_TIMEOUT); | ||
339 | } | ||
340 | |||
341 | if (!request_region(wdt_io, 1, WATCHDOG_NAME)) { | ||
342 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
343 | wdt_io); | ||
344 | ret = -EIO; | ||
345 | goto out; | ||
346 | } | ||
347 | |||
348 | w83627hf_init(); | ||
349 | |||
350 | ret = register_reboot_notifier(&wdt_notifier); | ||
351 | if (ret != 0) { | ||
352 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
353 | ret); | ||
354 | goto unreg_regions; | ||
355 | } | ||
356 | |||
357 | ret = misc_register(&wdt_miscdev); | ||
358 | if (ret != 0) { | ||
359 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
360 | WATCHDOG_MINOR, ret); | ||
361 | goto unreg_reboot; | ||
362 | } | ||
363 | |||
364 | printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", | ||
365 | timeout, nowayout); | ||
366 | |||
367 | out: | ||
368 | return ret; | ||
369 | unreg_reboot: | ||
370 | unregister_reboot_notifier(&wdt_notifier); | ||
371 | unreg_regions: | ||
372 | release_region(wdt_io, 1); | ||
373 | goto out; | ||
374 | } | ||
375 | |||
376 | static void __exit | ||
377 | wdt_exit(void) | ||
378 | { | ||
379 | misc_deregister(&wdt_miscdev); | ||
380 | unregister_reboot_notifier(&wdt_notifier); | ||
381 | release_region(wdt_io,1); | ||
382 | } | ||
383 | |||
384 | module_init(wdt_init); | ||
385 | module_exit(wdt_exit); | ||
386 | |||
387 | MODULE_LICENSE("GPL"); | ||
388 | MODULE_AUTHOR("Pádraig Brady <P@draigBrady.com>"); | ||
389 | MODULE_DESCRIPTION("w83627hf/thf WDT driver"); | ||
390 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/w83697hf_wdt.c b/drivers/watchdog/w83697hf_wdt.c new file mode 100644 index 000000000000..d9e821d08deb --- /dev/null +++ b/drivers/watchdog/w83697hf_wdt.c | |||
@@ -0,0 +1,450 @@ | |||
1 | /* | ||
2 | * w83697hf/hg WDT driver | ||
3 | * | ||
4 | * (c) Copyright 2006 Samuel Tardieu <sam@rfc1149.net> | ||
5 | * (c) Copyright 2006 Marcus Junker <junker@anduras.de> | ||
6 | * | ||
7 | * Based on w83627hf_wdt.c which is based on advantechwdt.c | ||
8 | * which is based on wdt.c. | ||
9 | * Original copyright messages: | ||
10 | * | ||
11 | * (c) Copyright 2003 Pádraig Brady <P@draigBrady.com> | ||
12 | * | ||
13 | * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> | ||
14 | * | ||
15 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
16 | * http://www.redhat.com | ||
17 | * | ||
18 | * This program is free software; you can redistribute it and/or | ||
19 | * modify it under the terms of the GNU General Public License | ||
20 | * as published by the Free Software Foundation; either version | ||
21 | * 2 of the License, or (at your option) any later version. | ||
22 | * | ||
23 | * Neither Marcus Junker nor ANDURAS AG admit liability nor provide | ||
24 | * warranty for any of this software. This material is provided | ||
25 | * "AS-IS" and at no charge. | ||
26 | */ | ||
27 | |||
28 | #include <linux/module.h> | ||
29 | #include <linux/moduleparam.h> | ||
30 | #include <linux/types.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 | |||
40 | #include <asm/io.h> | ||
41 | #include <asm/uaccess.h> | ||
42 | #include <asm/system.h> | ||
43 | |||
44 | #define WATCHDOG_NAME "w83697hf/hg WDT" | ||
45 | #define PFX WATCHDOG_NAME ": " | ||
46 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ | ||
47 | |||
48 | static unsigned long wdt_is_open; | ||
49 | static char expect_close; | ||
50 | static spinlock_t io_lock; | ||
51 | |||
52 | /* You must set this - there is no sane way to probe for this board. */ | ||
53 | static int wdt_io = 0x2e; | ||
54 | module_param(wdt_io, int, 0); | ||
55 | MODULE_PARM_DESC(wdt_io, "w83697hf/hg WDT io port (default 0x2e, 0 = autodetect)"); | ||
56 | |||
57 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ | ||
58 | module_param(timeout, int, 0); | ||
59 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=255, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) "."); | ||
60 | |||
61 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
62 | module_param(nowayout, int, 0); | ||
63 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
64 | |||
65 | /* | ||
66 | * Kernel methods. | ||
67 | */ | ||
68 | |||
69 | #define W83697HF_EFER (wdt_io+0) /* Extended Function Enable Register */ | ||
70 | #define W83697HF_EFIR (wdt_io+0) /* Extended Function Index Register (same as EFER) */ | ||
71 | #define W83697HF_EFDR (wdt_io+1) /* Extended Function Data Register */ | ||
72 | |||
73 | static inline void | ||
74 | w83697hf_unlock(void) | ||
75 | { | ||
76 | outb_p(0x87, W83697HF_EFER); /* Enter extended function mode */ | ||
77 | outb_p(0x87, W83697HF_EFER); /* Again according to manual */ | ||
78 | } | ||
79 | |||
80 | static inline void | ||
81 | w83697hf_lock(void) | ||
82 | { | ||
83 | outb_p(0xAA, W83697HF_EFER); /* Leave extended function mode */ | ||
84 | } | ||
85 | |||
86 | /* | ||
87 | * The three functions w83697hf_get_reg(), w83697hf_set_reg() and | ||
88 | * w83697hf_write_timeout() must be called with the device unlocked. | ||
89 | */ | ||
90 | |||
91 | static unsigned char | ||
92 | w83697hf_get_reg(unsigned char reg) | ||
93 | { | ||
94 | outb_p(reg, W83697HF_EFIR); | ||
95 | return inb_p(W83697HF_EFDR); | ||
96 | } | ||
97 | |||
98 | static void | ||
99 | w83697hf_set_reg(unsigned char reg, unsigned char data) | ||
100 | { | ||
101 | outb_p(reg, W83697HF_EFIR); | ||
102 | outb_p(data, W83697HF_EFDR); | ||
103 | } | ||
104 | |||
105 | static void | ||
106 | w83697hf_write_timeout(int timeout) | ||
107 | { | ||
108 | w83697hf_set_reg(0xF4, timeout); /* Write Timeout counter to CRF4 */ | ||
109 | } | ||
110 | |||
111 | static void | ||
112 | w83697hf_select_wdt(void) | ||
113 | { | ||
114 | w83697hf_unlock(); | ||
115 | w83697hf_set_reg(0x07, 0x08); /* Switch to logic device 8 (GPIO2) */ | ||
116 | } | ||
117 | |||
118 | static inline void | ||
119 | w83697hf_deselect_wdt(void) | ||
120 | { | ||
121 | w83697hf_lock(); | ||
122 | } | ||
123 | |||
124 | static void | ||
125 | w83697hf_init(void) | ||
126 | { | ||
127 | unsigned char bbuf; | ||
128 | |||
129 | w83697hf_select_wdt(); | ||
130 | |||
131 | bbuf = w83697hf_get_reg(0x29); | ||
132 | bbuf &= ~0x60; | ||
133 | bbuf |= 0x20; | ||
134 | w83697hf_set_reg(0x29, bbuf); /* Set pin 119 to WDTO# mode (= CR29, WDT0) */ | ||
135 | |||
136 | bbuf = w83697hf_get_reg(0xF3); | ||
137 | bbuf &= ~0x04; | ||
138 | w83697hf_set_reg(0xF3, bbuf); /* Count mode is seconds */ | ||
139 | |||
140 | w83697hf_deselect_wdt(); | ||
141 | } | ||
142 | |||
143 | static int | ||
144 | wdt_ping(void) | ||
145 | { | ||
146 | spin_lock(&io_lock); | ||
147 | w83697hf_select_wdt(); | ||
148 | |||
149 | w83697hf_write_timeout(timeout); | ||
150 | |||
151 | w83697hf_deselect_wdt(); | ||
152 | spin_unlock(&io_lock); | ||
153 | return 0; | ||
154 | } | ||
155 | |||
156 | static int | ||
157 | wdt_enable(void) | ||
158 | { | ||
159 | spin_lock(&io_lock); | ||
160 | w83697hf_select_wdt(); | ||
161 | |||
162 | w83697hf_write_timeout(timeout); | ||
163 | w83697hf_set_reg(0x30, 1); /* Enable timer */ | ||
164 | |||
165 | w83697hf_deselect_wdt(); | ||
166 | spin_unlock(&io_lock); | ||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | static int | ||
171 | wdt_disable(void) | ||
172 | { | ||
173 | spin_lock(&io_lock); | ||
174 | w83697hf_select_wdt(); | ||
175 | |||
176 | w83697hf_set_reg(0x30, 0); /* Disable timer */ | ||
177 | w83697hf_write_timeout(0); | ||
178 | |||
179 | w83697hf_deselect_wdt(); | ||
180 | spin_unlock(&io_lock); | ||
181 | return 0; | ||
182 | } | ||
183 | |||
184 | static int | ||
185 | wdt_set_heartbeat(int t) | ||
186 | { | ||
187 | if ((t < 1) || (t > 255)) | ||
188 | return -EINVAL; | ||
189 | |||
190 | timeout = t; | ||
191 | return 0; | ||
192 | } | ||
193 | |||
194 | static ssize_t | ||
195 | wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
196 | { | ||
197 | if (count) { | ||
198 | if (!nowayout) { | ||
199 | size_t i; | ||
200 | |||
201 | expect_close = 0; | ||
202 | |||
203 | for (i = 0; i != count; i++) { | ||
204 | char c; | ||
205 | if (get_user(c, buf+i)) | ||
206 | return -EFAULT; | ||
207 | if (c == 'V') | ||
208 | expect_close = 42; | ||
209 | } | ||
210 | } | ||
211 | wdt_ping(); | ||
212 | } | ||
213 | return count; | ||
214 | } | ||
215 | |||
216 | static int | ||
217 | wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
218 | unsigned long arg) | ||
219 | { | ||
220 | void __user *argp = (void __user *)arg; | ||
221 | int __user *p = argp; | ||
222 | int new_timeout; | ||
223 | static struct watchdog_info ident = { | ||
224 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
225 | .firmware_version = 1, | ||
226 | .identity = "W83697HF WDT", | ||
227 | }; | ||
228 | |||
229 | switch (cmd) { | ||
230 | case WDIOC_GETSUPPORT: | ||
231 | if (copy_to_user(argp, &ident, sizeof(ident))) | ||
232 | return -EFAULT; | ||
233 | break; | ||
234 | |||
235 | case WDIOC_GETSTATUS: | ||
236 | case WDIOC_GETBOOTSTATUS: | ||
237 | return put_user(0, p); | ||
238 | |||
239 | case WDIOC_KEEPALIVE: | ||
240 | wdt_ping(); | ||
241 | break; | ||
242 | |||
243 | case WDIOC_SETTIMEOUT: | ||
244 | if (get_user(new_timeout, p)) | ||
245 | return -EFAULT; | ||
246 | if (wdt_set_heartbeat(new_timeout)) | ||
247 | return -EINVAL; | ||
248 | wdt_ping(); | ||
249 | /* Fall */ | ||
250 | |||
251 | case WDIOC_GETTIMEOUT: | ||
252 | return put_user(timeout, p); | ||
253 | |||
254 | case WDIOC_SETOPTIONS: | ||
255 | { | ||
256 | int options, retval = -EINVAL; | ||
257 | |||
258 | if (get_user(options, p)) | ||
259 | return -EFAULT; | ||
260 | |||
261 | if (options & WDIOS_DISABLECARD) { | ||
262 | wdt_disable(); | ||
263 | retval = 0; | ||
264 | } | ||
265 | |||
266 | if (options & WDIOS_ENABLECARD) { | ||
267 | wdt_enable(); | ||
268 | retval = 0; | ||
269 | } | ||
270 | |||
271 | return retval; | ||
272 | } | ||
273 | |||
274 | default: | ||
275 | return -ENOTTY; | ||
276 | } | ||
277 | return 0; | ||
278 | } | ||
279 | |||
280 | static int | ||
281 | wdt_open(struct inode *inode, struct file *file) | ||
282 | { | ||
283 | if (test_and_set_bit(0, &wdt_is_open)) | ||
284 | return -EBUSY; | ||
285 | /* | ||
286 | * Activate | ||
287 | */ | ||
288 | |||
289 | wdt_enable(); | ||
290 | return nonseekable_open(inode, file); | ||
291 | } | ||
292 | |||
293 | static int | ||
294 | wdt_close(struct inode *inode, struct file *file) | ||
295 | { | ||
296 | if (expect_close == 42) { | ||
297 | wdt_disable(); | ||
298 | } else { | ||
299 | printk (KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
300 | wdt_ping(); | ||
301 | } | ||
302 | expect_close = 0; | ||
303 | clear_bit(0, &wdt_is_open); | ||
304 | return 0; | ||
305 | } | ||
306 | |||
307 | /* | ||
308 | * Notifier for system down | ||
309 | */ | ||
310 | |||
311 | static int | ||
312 | wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
313 | void *unused) | ||
314 | { | ||
315 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
316 | /* Turn the WDT off */ | ||
317 | wdt_disable(); | ||
318 | } | ||
319 | return NOTIFY_DONE; | ||
320 | } | ||
321 | |||
322 | /* | ||
323 | * Kernel Interfaces | ||
324 | */ | ||
325 | |||
326 | static const struct file_operations wdt_fops = { | ||
327 | .owner = THIS_MODULE, | ||
328 | .llseek = no_llseek, | ||
329 | .write = wdt_write, | ||
330 | .ioctl = wdt_ioctl, | ||
331 | .open = wdt_open, | ||
332 | .release = wdt_close, | ||
333 | }; | ||
334 | |||
335 | static struct miscdevice wdt_miscdev = { | ||
336 | .minor = WATCHDOG_MINOR, | ||
337 | .name = "watchdog", | ||
338 | .fops = &wdt_fops, | ||
339 | }; | ||
340 | |||
341 | /* | ||
342 | * The WDT needs to learn about soft shutdowns in order to | ||
343 | * turn the timebomb registers off. | ||
344 | */ | ||
345 | |||
346 | static struct notifier_block wdt_notifier = { | ||
347 | .notifier_call = wdt_notify_sys, | ||
348 | }; | ||
349 | |||
350 | static int | ||
351 | w83697hf_check_wdt(void) | ||
352 | { | ||
353 | if (!request_region(wdt_io, 2, WATCHDOG_NAME)) { | ||
354 | printk (KERN_ERR PFX "I/O address 0x%x already in use\n", wdt_io); | ||
355 | return -EIO; | ||
356 | } | ||
357 | |||
358 | printk (KERN_DEBUG PFX "Looking for watchdog at address 0x%x\n", wdt_io); | ||
359 | w83697hf_unlock(); | ||
360 | if (w83697hf_get_reg(0x20) == 0x60) { | ||
361 | printk (KERN_INFO PFX "watchdog found at address 0x%x\n", wdt_io); | ||
362 | w83697hf_lock(); | ||
363 | return 0; | ||
364 | } | ||
365 | w83697hf_lock(); /* Reprotect in case it was a compatible device */ | ||
366 | |||
367 | printk (KERN_INFO PFX "watchdog not found at address 0x%x\n", wdt_io); | ||
368 | release_region(wdt_io, 2); | ||
369 | return -EIO; | ||
370 | } | ||
371 | |||
372 | static int w83697hf_ioports[] = { 0x2e, 0x4e, 0x00 }; | ||
373 | |||
374 | static int __init | ||
375 | wdt_init(void) | ||
376 | { | ||
377 | int ret, i, found = 0; | ||
378 | |||
379 | spin_lock_init(&io_lock); | ||
380 | |||
381 | printk (KERN_INFO PFX "WDT driver for W83697HF/HG initializing\n"); | ||
382 | |||
383 | if (wdt_io == 0) { | ||
384 | /* we will autodetect the W83697HF/HG watchdog */ | ||
385 | for (i = 0; ((!found) && (w83697hf_ioports[i] != 0)); i++) { | ||
386 | wdt_io = w83697hf_ioports[i]; | ||
387 | if (!w83697hf_check_wdt()) | ||
388 | found++; | ||
389 | } | ||
390 | } else { | ||
391 | if (!w83697hf_check_wdt()) | ||
392 | found++; | ||
393 | } | ||
394 | |||
395 | if (!found) { | ||
396 | printk (KERN_ERR PFX "No W83697HF/HG could be found\n"); | ||
397 | ret = -EIO; | ||
398 | goto out; | ||
399 | } | ||
400 | |||
401 | w83697hf_init(); | ||
402 | wdt_disable(); /* Disable watchdog until first use */ | ||
403 | |||
404 | if (wdt_set_heartbeat(timeout)) { | ||
405 | wdt_set_heartbeat(WATCHDOG_TIMEOUT); | ||
406 | printk (KERN_INFO PFX "timeout value must be 1<=timeout<=255, using %d\n", | ||
407 | WATCHDOG_TIMEOUT); | ||
408 | } | ||
409 | |||
410 | ret = register_reboot_notifier(&wdt_notifier); | ||
411 | if (ret != 0) { | ||
412 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
413 | ret); | ||
414 | goto unreg_regions; | ||
415 | } | ||
416 | |||
417 | ret = misc_register(&wdt_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 unreg_reboot; | ||
422 | } | ||
423 | |||
424 | printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", | ||
425 | timeout, nowayout); | ||
426 | |||
427 | out: | ||
428 | return ret; | ||
429 | unreg_reboot: | ||
430 | unregister_reboot_notifier(&wdt_notifier); | ||
431 | unreg_regions: | ||
432 | release_region(wdt_io, 2); | ||
433 | goto out; | ||
434 | } | ||
435 | |||
436 | static void __exit | ||
437 | wdt_exit(void) | ||
438 | { | ||
439 | misc_deregister(&wdt_miscdev); | ||
440 | unregister_reboot_notifier(&wdt_notifier); | ||
441 | release_region(wdt_io, 2); | ||
442 | } | ||
443 | |||
444 | module_init(wdt_init); | ||
445 | module_exit(wdt_exit); | ||
446 | |||
447 | MODULE_LICENSE("GPL"); | ||
448 | MODULE_AUTHOR("Marcus Junker <junker@anduras.de>, Samuel Tardieu <sam@rfc1149.net>"); | ||
449 | MODULE_DESCRIPTION("w83697hf/hg WDT driver"); | ||
450 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/w83877f_wdt.c b/drivers/watchdog/w83877f_wdt.c new file mode 100644 index 000000000000..3c88fe18f4f4 --- /dev/null +++ b/drivers/watchdog/w83877f_wdt.c | |||
@@ -0,0 +1,415 @@ | |||
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 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
89 | module_param(nowayout, int, 0); | ||
90 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
91 | |||
92 | static void wdt_timer_ping(unsigned long); | ||
93 | static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0); | ||
94 | static unsigned long next_heartbeat; | ||
95 | static unsigned long wdt_is_open; | ||
96 | static char wdt_expect_close; | ||
97 | static spinlock_t wdt_spinlock; | ||
98 | |||
99 | /* | ||
100 | * Whack the dog | ||
101 | */ | ||
102 | |||
103 | static void wdt_timer_ping(unsigned long data) | ||
104 | { | ||
105 | /* If we got a heartbeat pulse within the WDT_US_INTERVAL | ||
106 | * we agree to ping the WDT | ||
107 | */ | ||
108 | if(time_before(jiffies, next_heartbeat)) | ||
109 | { | ||
110 | /* Ping the WDT */ | ||
111 | spin_lock(&wdt_spinlock); | ||
112 | |||
113 | /* Ping the WDT by reading from WDT_PING */ | ||
114 | inb_p(WDT_PING); | ||
115 | |||
116 | /* Re-set the timer interval */ | ||
117 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
118 | |||
119 | spin_unlock(&wdt_spinlock); | ||
120 | |||
121 | } else { | ||
122 | printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); | ||
123 | } | ||
124 | } | ||
125 | |||
126 | /* | ||
127 | * Utility routines | ||
128 | */ | ||
129 | |||
130 | static void wdt_change(int writeval) | ||
131 | { | ||
132 | unsigned long flags; | ||
133 | spin_lock_irqsave(&wdt_spinlock, flags); | ||
134 | |||
135 | /* buy some time */ | ||
136 | inb_p(WDT_PING); | ||
137 | |||
138 | /* make W83877F available */ | ||
139 | outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT); | ||
140 | outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT); | ||
141 | |||
142 | /* enable watchdog */ | ||
143 | outb_p(WDT_REGISTER, ENABLE_W83877F_PORT); | ||
144 | outb_p(writeval, ENABLE_W83877F_PORT+1); | ||
145 | |||
146 | /* lock the W8387FF away */ | ||
147 | outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT); | ||
148 | |||
149 | spin_unlock_irqrestore(&wdt_spinlock, flags); | ||
150 | } | ||
151 | |||
152 | static void wdt_startup(void) | ||
153 | { | ||
154 | next_heartbeat = jiffies + (timeout * HZ); | ||
155 | |||
156 | /* Start the timer */ | ||
157 | mod_timer(&timer, jiffies + WDT_INTERVAL); | ||
158 | |||
159 | wdt_change(WDT_ENABLE); | ||
160 | |||
161 | printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); | ||
162 | } | ||
163 | |||
164 | static void wdt_turnoff(void) | ||
165 | { | ||
166 | /* Stop the timer */ | ||
167 | del_timer(&timer); | ||
168 | |||
169 | wdt_change(WDT_DISABLE); | ||
170 | |||
171 | printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); | ||
172 | } | ||
173 | |||
174 | static void wdt_keepalive(void) | ||
175 | { | ||
176 | /* user land ping */ | ||
177 | next_heartbeat = jiffies + (timeout * HZ); | ||
178 | } | ||
179 | |||
180 | /* | ||
181 | * /dev/watchdog handling | ||
182 | */ | ||
183 | |||
184 | static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) | ||
185 | { | ||
186 | /* See if we got the magic character 'V' and reload the timer */ | ||
187 | if(count) | ||
188 | { | ||
189 | if (!nowayout) | ||
190 | { | ||
191 | size_t ofs; | ||
192 | |||
193 | /* note: just in case someone wrote the magic character | ||
194 | * five months ago... */ | ||
195 | wdt_expect_close = 0; | ||
196 | |||
197 | /* scan to see whether or not we got the magic character */ | ||
198 | for(ofs = 0; ofs != count; ofs++) | ||
199 | { | ||
200 | char c; | ||
201 | if (get_user(c, buf + ofs)) | ||
202 | return -EFAULT; | ||
203 | if (c == 'V') | ||
204 | wdt_expect_close = 42; | ||
205 | } | ||
206 | } | ||
207 | |||
208 | /* someone wrote to us, we should restart timer */ | ||
209 | wdt_keepalive(); | ||
210 | } | ||
211 | return count; | ||
212 | } | ||
213 | |||
214 | static int fop_open(struct inode * inode, struct file * file) | ||
215 | { | ||
216 | /* Just in case we're already talking to someone... */ | ||
217 | if(test_and_set_bit(0, &wdt_is_open)) | ||
218 | return -EBUSY; | ||
219 | |||
220 | /* Good, fire up the show */ | ||
221 | wdt_startup(); | ||
222 | return nonseekable_open(inode, file); | ||
223 | } | ||
224 | |||
225 | static int fop_close(struct inode * inode, struct file * file) | ||
226 | { | ||
227 | if(wdt_expect_close == 42) | ||
228 | wdt_turnoff(); | ||
229 | else { | ||
230 | del_timer(&timer); | ||
231 | printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); | ||
232 | } | ||
233 | clear_bit(0, &wdt_is_open); | ||
234 | wdt_expect_close = 0; | ||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
239 | unsigned long arg) | ||
240 | { | ||
241 | void __user *argp = (void __user *)arg; | ||
242 | int __user *p = argp; | ||
243 | static struct watchdog_info ident= | ||
244 | { | ||
245 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
246 | .firmware_version = 1, | ||
247 | .identity = "W83877F", | ||
248 | }; | ||
249 | |||
250 | switch(cmd) | ||
251 | { | ||
252 | default: | ||
253 | return -ENOTTY; | ||
254 | case WDIOC_GETSUPPORT: | ||
255 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
256 | case WDIOC_GETSTATUS: | ||
257 | case WDIOC_GETBOOTSTATUS: | ||
258 | return put_user(0, p); | ||
259 | case WDIOC_KEEPALIVE: | ||
260 | wdt_keepalive(); | ||
261 | return 0; | ||
262 | case WDIOC_SETOPTIONS: | ||
263 | { | ||
264 | int new_options, retval = -EINVAL; | ||
265 | |||
266 | if(get_user(new_options, p)) | ||
267 | return -EFAULT; | ||
268 | |||
269 | if(new_options & WDIOS_DISABLECARD) { | ||
270 | wdt_turnoff(); | ||
271 | retval = 0; | ||
272 | } | ||
273 | |||
274 | if(new_options & WDIOS_ENABLECARD) { | ||
275 | wdt_startup(); | ||
276 | retval = 0; | ||
277 | } | ||
278 | |||
279 | return retval; | ||
280 | } | ||
281 | case WDIOC_SETTIMEOUT: | ||
282 | { | ||
283 | int new_timeout; | ||
284 | |||
285 | if(get_user(new_timeout, p)) | ||
286 | return -EFAULT; | ||
287 | |||
288 | if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ | ||
289 | return -EINVAL; | ||
290 | |||
291 | timeout = new_timeout; | ||
292 | wdt_keepalive(); | ||
293 | /* Fall through */ | ||
294 | } | ||
295 | case WDIOC_GETTIMEOUT: | ||
296 | return put_user(timeout, p); | ||
297 | } | ||
298 | } | ||
299 | |||
300 | static const struct file_operations wdt_fops = { | ||
301 | .owner = THIS_MODULE, | ||
302 | .llseek = no_llseek, | ||
303 | .write = fop_write, | ||
304 | .open = fop_open, | ||
305 | .release = fop_close, | ||
306 | .ioctl = fop_ioctl, | ||
307 | }; | ||
308 | |||
309 | static struct miscdevice wdt_miscdev = { | ||
310 | .minor = WATCHDOG_MINOR, | ||
311 | .name = "watchdog", | ||
312 | .fops = &wdt_fops, | ||
313 | }; | ||
314 | |||
315 | /* | ||
316 | * Notifier for system down | ||
317 | */ | ||
318 | |||
319 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
320 | void *unused) | ||
321 | { | ||
322 | if(code==SYS_DOWN || code==SYS_HALT) | ||
323 | wdt_turnoff(); | ||
324 | return NOTIFY_DONE; | ||
325 | } | ||
326 | |||
327 | /* | ||
328 | * The WDT needs to learn about soft shutdowns in order to | ||
329 | * turn the timebomb registers off. | ||
330 | */ | ||
331 | |||
332 | static struct notifier_block wdt_notifier= | ||
333 | { | ||
334 | .notifier_call = wdt_notify_sys, | ||
335 | }; | ||
336 | |||
337 | static void __exit w83877f_wdt_unload(void) | ||
338 | { | ||
339 | wdt_turnoff(); | ||
340 | |||
341 | /* Deregister */ | ||
342 | misc_deregister(&wdt_miscdev); | ||
343 | |||
344 | unregister_reboot_notifier(&wdt_notifier); | ||
345 | release_region(WDT_PING,1); | ||
346 | release_region(ENABLE_W83877F_PORT,2); | ||
347 | } | ||
348 | |||
349 | static int __init w83877f_wdt_init(void) | ||
350 | { | ||
351 | int rc = -EBUSY; | ||
352 | |||
353 | spin_lock_init(&wdt_spinlock); | ||
354 | |||
355 | if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */ | ||
356 | { | ||
357 | timeout = WATCHDOG_TIMEOUT; | ||
358 | printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", | ||
359 | timeout); | ||
360 | } | ||
361 | |||
362 | if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) | ||
363 | { | ||
364 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
365 | ENABLE_W83877F_PORT); | ||
366 | rc = -EIO; | ||
367 | goto err_out; | ||
368 | } | ||
369 | |||
370 | if (!request_region(WDT_PING, 1, "W8387FF WDT")) | ||
371 | { | ||
372 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
373 | WDT_PING); | ||
374 | rc = -EIO; | ||
375 | goto err_out_region1; | ||
376 | } | ||
377 | |||
378 | rc = misc_register(&wdt_miscdev); | ||
379 | if (rc) | ||
380 | { | ||
381 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
382 | wdt_miscdev.minor, rc); | ||
383 | goto err_out_region2; | ||
384 | } | ||
385 | |||
386 | rc = register_reboot_notifier(&wdt_notifier); | ||
387 | if (rc) | ||
388 | { | ||
389 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
390 | rc); | ||
391 | goto err_out_miscdev; | ||
392 | } | ||
393 | |||
394 | printk(KERN_INFO PFX "WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n", | ||
395 | timeout, nowayout); | ||
396 | |||
397 | return 0; | ||
398 | |||
399 | err_out_miscdev: | ||
400 | misc_deregister(&wdt_miscdev); | ||
401 | err_out_region2: | ||
402 | release_region(WDT_PING,1); | ||
403 | err_out_region1: | ||
404 | release_region(ENABLE_W83877F_PORT,2); | ||
405 | err_out: | ||
406 | return rc; | ||
407 | } | ||
408 | |||
409 | module_init(w83877f_wdt_init); | ||
410 | module_exit(w83877f_wdt_unload); | ||
411 | |||
412 | MODULE_AUTHOR("Scott and Bill Jennings"); | ||
413 | MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip"); | ||
414 | MODULE_LICENSE("GPL"); | ||
415 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/w83977f_wdt.c b/drivers/watchdog/w83977f_wdt.c new file mode 100644 index 000000000000..157968442891 --- /dev/null +++ b/drivers/watchdog/w83977f_wdt.c | |||
@@ -0,0 +1,542 @@ | |||
1 | /* | ||
2 | * W83977F Watchdog Timer Driver for Winbond W83977F I/O Chip | ||
3 | * | ||
4 | * (c) Copyright 2005 Jose Goncalves <jose.goncalves@inov.pt> | ||
5 | * | ||
6 | * Based on w83877f_wdt.c by Scott Jennings, | ||
7 | * and wdt977.c by Woody Suwalski | ||
8 | * | ||
9 | * ----------------------- | ||
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/miscdevice.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/ioport.h> | ||
26 | #include <linux/watchdog.h> | ||
27 | #include <linux/notifier.h> | ||
28 | #include <linux/reboot.h> | ||
29 | |||
30 | #include <asm/io.h> | ||
31 | #include <asm/system.h> | ||
32 | #include <asm/uaccess.h> | ||
33 | |||
34 | #define WATCHDOG_VERSION "1.00" | ||
35 | #define WATCHDOG_NAME "W83977F WDT" | ||
36 | #define PFX WATCHDOG_NAME ": " | ||
37 | #define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n" | ||
38 | |||
39 | #define IO_INDEX_PORT 0x3F0 | ||
40 | #define IO_DATA_PORT (IO_INDEX_PORT+1) | ||
41 | |||
42 | #define UNLOCK_DATA 0x87 | ||
43 | #define LOCK_DATA 0xAA | ||
44 | #define DEVICE_REGISTER 0x07 | ||
45 | |||
46 | #define DEFAULT_TIMEOUT 45 /* default timeout in seconds */ | ||
47 | |||
48 | static int timeout = DEFAULT_TIMEOUT; | ||
49 | static int timeoutW; /* timeout in watchdog counter units */ | ||
50 | static unsigned long timer_alive; | ||
51 | static int testmode; | ||
52 | static char expect_close; | ||
53 | static spinlock_t spinlock; | ||
54 | |||
55 | module_param(timeout, int, 0); | ||
56 | MODULE_PARM_DESC(timeout,"Watchdog timeout in seconds (15..7635), default=" __MODULE_STRING(DEFAULT_TIMEOUT) ")"); | ||
57 | module_param(testmode, int, 0); | ||
58 | MODULE_PARM_DESC(testmode,"Watchdog testmode (1 = no reboot), default=0"); | ||
59 | |||
60 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
61 | module_param(nowayout, int, 0); | ||
62 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
63 | |||
64 | /* | ||
65 | * Start the watchdog | ||
66 | */ | ||
67 | |||
68 | static int wdt_start(void) | ||
69 | { | ||
70 | unsigned long flags; | ||
71 | |||
72 | spin_lock_irqsave(&spinlock, flags); | ||
73 | |||
74 | /* Unlock the SuperIO chip */ | ||
75 | outb_p(UNLOCK_DATA,IO_INDEX_PORT); | ||
76 | outb_p(UNLOCK_DATA,IO_INDEX_PORT); | ||
77 | |||
78 | /* | ||
79 | * Select device Aux2 (device=8) to set watchdog regs F2, F3 and F4. | ||
80 | * F2 has the timeout in watchdog counter units. | ||
81 | * F3 is set to enable watchdog LED blink at timeout. | ||
82 | * F4 is used to just clear the TIMEOUT'ed state (bit 0). | ||
83 | */ | ||
84 | outb_p(DEVICE_REGISTER,IO_INDEX_PORT); | ||
85 | outb_p(0x08,IO_DATA_PORT); | ||
86 | outb_p(0xF2,IO_INDEX_PORT); | ||
87 | outb_p(timeoutW,IO_DATA_PORT); | ||
88 | outb_p(0xF3,IO_INDEX_PORT); | ||
89 | outb_p(0x08,IO_DATA_PORT); | ||
90 | outb_p(0xF4,IO_INDEX_PORT); | ||
91 | outb_p(0x00,IO_DATA_PORT); | ||
92 | |||
93 | /* Set device Aux2 active */ | ||
94 | outb_p(0x30,IO_INDEX_PORT); | ||
95 | outb_p(0x01,IO_DATA_PORT); | ||
96 | |||
97 | /* | ||
98 | * Select device Aux1 (dev=7) to set GP16 as the watchdog output | ||
99 | * (in reg E6) and GP13 as the watchdog LED output (in reg E3). | ||
100 | * Map GP16 at pin 119. | ||
101 | * In test mode watch the bit 0 on F4 to indicate "triggered" or | ||
102 | * check watchdog LED on SBC. | ||
103 | */ | ||
104 | outb_p(DEVICE_REGISTER,IO_INDEX_PORT); | ||
105 | outb_p(0x07,IO_DATA_PORT); | ||
106 | if (!testmode) | ||
107 | { | ||
108 | unsigned pin_map; | ||
109 | |||
110 | outb_p(0xE6,IO_INDEX_PORT); | ||
111 | outb_p(0x0A,IO_DATA_PORT); | ||
112 | outb_p(0x2C,IO_INDEX_PORT); | ||
113 | pin_map = inb_p(IO_DATA_PORT); | ||
114 | pin_map |= 0x10; | ||
115 | pin_map &= ~(0x20); | ||
116 | outb_p(0x2C,IO_INDEX_PORT); | ||
117 | outb_p(pin_map,IO_DATA_PORT); | ||
118 | } | ||
119 | outb_p(0xE3,IO_INDEX_PORT); | ||
120 | outb_p(0x08,IO_DATA_PORT); | ||
121 | |||
122 | /* Set device Aux1 active */ | ||
123 | outb_p(0x30,IO_INDEX_PORT); | ||
124 | outb_p(0x01,IO_DATA_PORT); | ||
125 | |||
126 | /* Lock the SuperIO chip */ | ||
127 | outb_p(LOCK_DATA,IO_INDEX_PORT); | ||
128 | |||
129 | spin_unlock_irqrestore(&spinlock, flags); | ||
130 | |||
131 | printk(KERN_INFO PFX "activated.\n"); | ||
132 | |||
133 | return 0; | ||
134 | } | ||
135 | |||
136 | /* | ||
137 | * Stop the watchdog | ||
138 | */ | ||
139 | |||
140 | static int wdt_stop(void) | ||
141 | { | ||
142 | unsigned long flags; | ||
143 | |||
144 | spin_lock_irqsave(&spinlock, flags); | ||
145 | |||
146 | /* Unlock the SuperIO chip */ | ||
147 | outb_p(UNLOCK_DATA,IO_INDEX_PORT); | ||
148 | outb_p(UNLOCK_DATA,IO_INDEX_PORT); | ||
149 | |||
150 | /* | ||
151 | * Select device Aux2 (device=8) to set watchdog regs F2, F3 and F4. | ||
152 | * F2 is reset to its default value (watchdog timer disabled). | ||
153 | * F3 is reset to its default state. | ||
154 | * F4 clears the TIMEOUT'ed state (bit 0) - back to default. | ||
155 | */ | ||
156 | outb_p(DEVICE_REGISTER,IO_INDEX_PORT); | ||
157 | outb_p(0x08,IO_DATA_PORT); | ||
158 | outb_p(0xF2,IO_INDEX_PORT); | ||
159 | outb_p(0xFF,IO_DATA_PORT); | ||
160 | outb_p(0xF3,IO_INDEX_PORT); | ||
161 | outb_p(0x00,IO_DATA_PORT); | ||
162 | outb_p(0xF4,IO_INDEX_PORT); | ||
163 | outb_p(0x00,IO_DATA_PORT); | ||
164 | outb_p(0xF2,IO_INDEX_PORT); | ||
165 | outb_p(0x00,IO_DATA_PORT); | ||
166 | |||
167 | /* | ||
168 | * Select device Aux1 (dev=7) to set GP16 (in reg E6) and | ||
169 | * Gp13 (in reg E3) as inputs. | ||
170 | */ | ||
171 | outb_p(DEVICE_REGISTER,IO_INDEX_PORT); | ||
172 | outb_p(0x07,IO_DATA_PORT); | ||
173 | if (!testmode) | ||
174 | { | ||
175 | outb_p(0xE6,IO_INDEX_PORT); | ||
176 | outb_p(0x01,IO_DATA_PORT); | ||
177 | } | ||
178 | outb_p(0xE3,IO_INDEX_PORT); | ||
179 | outb_p(0x01,IO_DATA_PORT); | ||
180 | |||
181 | /* Lock the SuperIO chip */ | ||
182 | outb_p(LOCK_DATA,IO_INDEX_PORT); | ||
183 | |||
184 | spin_unlock_irqrestore(&spinlock, flags); | ||
185 | |||
186 | printk(KERN_INFO PFX "shutdown.\n"); | ||
187 | |||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | /* | ||
192 | * Send a keepalive ping to the watchdog | ||
193 | * This is done by simply re-writing the timeout to reg. 0xF2 | ||
194 | */ | ||
195 | |||
196 | static int wdt_keepalive(void) | ||
197 | { | ||
198 | unsigned long flags; | ||
199 | |||
200 | spin_lock_irqsave(&spinlock, flags); | ||
201 | |||
202 | /* Unlock the SuperIO chip */ | ||
203 | outb_p(UNLOCK_DATA,IO_INDEX_PORT); | ||
204 | outb_p(UNLOCK_DATA,IO_INDEX_PORT); | ||
205 | |||
206 | /* Select device Aux2 (device=8) to kick watchdog reg F2 */ | ||
207 | outb_p(DEVICE_REGISTER,IO_INDEX_PORT); | ||
208 | outb_p(0x08,IO_DATA_PORT); | ||
209 | outb_p(0xF2,IO_INDEX_PORT); | ||
210 | outb_p(timeoutW,IO_DATA_PORT); | ||
211 | |||
212 | /* Lock the SuperIO chip */ | ||
213 | outb_p(LOCK_DATA,IO_INDEX_PORT); | ||
214 | |||
215 | spin_unlock_irqrestore(&spinlock, flags); | ||
216 | |||
217 | return 0; | ||
218 | } | ||
219 | |||
220 | /* | ||
221 | * Set the watchdog timeout value | ||
222 | */ | ||
223 | |||
224 | static int wdt_set_timeout(int t) | ||
225 | { | ||
226 | int tmrval; | ||
227 | |||
228 | /* | ||
229 | * Convert seconds to watchdog counter time units, rounding up. | ||
230 | * On PCM-5335 watchdog units are 30 seconds/step with 15 sec startup | ||
231 | * value. This information is supplied in the PCM-5335 manual and was | ||
232 | * checked by me on a real board. This is a bit strange because W83977f | ||
233 | * datasheet says counter unit is in minutes! | ||
234 | */ | ||
235 | if (t < 15) | ||
236 | return -EINVAL; | ||
237 | |||
238 | tmrval = ((t + 15) + 29) / 30; | ||
239 | |||
240 | if (tmrval > 255) | ||
241 | return -EINVAL; | ||
242 | |||
243 | /* | ||
244 | * timeout is the timeout in seconds, | ||
245 | * timeoutW is the timeout in watchdog counter units. | ||
246 | */ | ||
247 | timeoutW = tmrval; | ||
248 | timeout = (timeoutW * 30) - 15; | ||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | /* | ||
253 | * Get the watchdog status | ||
254 | */ | ||
255 | |||
256 | static int wdt_get_status(int *status) | ||
257 | { | ||
258 | int new_status; | ||
259 | unsigned long flags; | ||
260 | |||
261 | spin_lock_irqsave(&spinlock, flags); | ||
262 | |||
263 | /* Unlock the SuperIO chip */ | ||
264 | outb_p(UNLOCK_DATA,IO_INDEX_PORT); | ||
265 | outb_p(UNLOCK_DATA,IO_INDEX_PORT); | ||
266 | |||
267 | /* Select device Aux2 (device=8) to read watchdog reg F4 */ | ||
268 | outb_p(DEVICE_REGISTER,IO_INDEX_PORT); | ||
269 | outb_p(0x08,IO_DATA_PORT); | ||
270 | outb_p(0xF4,IO_INDEX_PORT); | ||
271 | new_status = inb_p(IO_DATA_PORT); | ||
272 | |||
273 | /* Lock the SuperIO chip */ | ||
274 | outb_p(LOCK_DATA,IO_INDEX_PORT); | ||
275 | |||
276 | spin_unlock_irqrestore(&spinlock, flags); | ||
277 | |||
278 | *status = 0; | ||
279 | if (new_status & 1) | ||
280 | *status |= WDIOF_CARDRESET; | ||
281 | |||
282 | return 0; | ||
283 | } | ||
284 | |||
285 | |||
286 | /* | ||
287 | * /dev/watchdog handling | ||
288 | */ | ||
289 | |||
290 | static int wdt_open(struct inode *inode, struct file *file) | ||
291 | { | ||
292 | /* If the watchdog is alive we don't need to start it again */ | ||
293 | if( test_and_set_bit(0, &timer_alive) ) | ||
294 | return -EBUSY; | ||
295 | |||
296 | if (nowayout) | ||
297 | __module_get(THIS_MODULE); | ||
298 | |||
299 | wdt_start(); | ||
300 | return nonseekable_open(inode, file); | ||
301 | } | ||
302 | |||
303 | static int wdt_release(struct inode *inode, struct file *file) | ||
304 | { | ||
305 | /* | ||
306 | * Shut off the timer. | ||
307 | * Lock it in if it's a module and we set nowayout | ||
308 | */ | ||
309 | if (expect_close == 42) | ||
310 | { | ||
311 | wdt_stop(); | ||
312 | clear_bit(0, &timer_alive); | ||
313 | } else { | ||
314 | wdt_keepalive(); | ||
315 | printk(KERN_CRIT PFX "unexpected close, not stopping watchdog!\n"); | ||
316 | } | ||
317 | expect_close = 0; | ||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | /* | ||
322 | * wdt_write: | ||
323 | * @file: file handle to the watchdog | ||
324 | * @buf: buffer to write (unused as data does not matter here | ||
325 | * @count: count of bytes | ||
326 | * @ppos: pointer to the position to write. No seeks allowed | ||
327 | * | ||
328 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
329 | * write of data will do, as we we don't define content meaning. | ||
330 | */ | ||
331 | |||
332 | static ssize_t wdt_write(struct file *file, const char __user *buf, | ||
333 | size_t count, loff_t *ppos) | ||
334 | { | ||
335 | /* See if we got the magic character 'V' and reload the timer */ | ||
336 | if(count) | ||
337 | { | ||
338 | if (!nowayout) | ||
339 | { | ||
340 | size_t ofs; | ||
341 | |||
342 | /* note: just in case someone wrote the magic character long ago */ | ||
343 | expect_close = 0; | ||
344 | |||
345 | /* scan to see whether or not we got the magic character */ | ||
346 | for(ofs = 0; ofs != count; ofs++) | ||
347 | { | ||
348 | char c; | ||
349 | if (get_user(c, buf + ofs)) | ||
350 | return -EFAULT; | ||
351 | if (c == 'V') { | ||
352 | expect_close = 42; | ||
353 | } | ||
354 | } | ||
355 | } | ||
356 | |||
357 | /* someone wrote to us, we should restart timer */ | ||
358 | wdt_keepalive(); | ||
359 | } | ||
360 | return count; | ||
361 | } | ||
362 | |||
363 | /* | ||
364 | * wdt_ioctl: | ||
365 | * @inode: inode of the device | ||
366 | * @file: file handle to the device | ||
367 | * @cmd: watchdog command | ||
368 | * @arg: argument pointer | ||
369 | * | ||
370 | * The watchdog API defines a common set of functions for all watchdogs | ||
371 | * according to their available features. | ||
372 | */ | ||
373 | |||
374 | static struct watchdog_info ident = { | ||
375 | .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, | ||
376 | .firmware_version = 1, | ||
377 | .identity = WATCHDOG_NAME, | ||
378 | }; | ||
379 | |||
380 | static int wdt_ioctl(struct inode *inode, struct file *file, | ||
381 | unsigned int cmd, unsigned long arg) | ||
382 | { | ||
383 | int status; | ||
384 | int new_options, retval = -EINVAL; | ||
385 | int new_timeout; | ||
386 | union { | ||
387 | struct watchdog_info __user *ident; | ||
388 | int __user *i; | ||
389 | } uarg; | ||
390 | |||
391 | uarg.i = (int __user *)arg; | ||
392 | |||
393 | switch(cmd) | ||
394 | { | ||
395 | default: | ||
396 | return -ENOTTY; | ||
397 | |||
398 | case WDIOC_GETSUPPORT: | ||
399 | return copy_to_user(uarg.ident, &ident, sizeof(ident)) ? -EFAULT : 0; | ||
400 | |||
401 | case WDIOC_GETSTATUS: | ||
402 | wdt_get_status(&status); | ||
403 | return put_user(status, uarg.i); | ||
404 | |||
405 | case WDIOC_GETBOOTSTATUS: | ||
406 | return put_user(0, uarg.i); | ||
407 | |||
408 | case WDIOC_KEEPALIVE: | ||
409 | wdt_keepalive(); | ||
410 | return 0; | ||
411 | |||
412 | case WDIOC_SETOPTIONS: | ||
413 | if (get_user (new_options, uarg.i)) | ||
414 | return -EFAULT; | ||
415 | |||
416 | if (new_options & WDIOS_DISABLECARD) { | ||
417 | wdt_stop(); | ||
418 | retval = 0; | ||
419 | } | ||
420 | |||
421 | if (new_options & WDIOS_ENABLECARD) { | ||
422 | wdt_start(); | ||
423 | retval = 0; | ||
424 | } | ||
425 | |||
426 | return retval; | ||
427 | |||
428 | case WDIOC_SETTIMEOUT: | ||
429 | if (get_user(new_timeout, uarg.i)) | ||
430 | return -EFAULT; | ||
431 | |||
432 | if (wdt_set_timeout(new_timeout)) | ||
433 | return -EINVAL; | ||
434 | |||
435 | wdt_keepalive(); | ||
436 | /* Fall */ | ||
437 | |||
438 | case WDIOC_GETTIMEOUT: | ||
439 | return put_user(timeout, uarg.i); | ||
440 | |||
441 | } | ||
442 | } | ||
443 | |||
444 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
445 | void *unused) | ||
446 | { | ||
447 | if (code==SYS_DOWN || code==SYS_HALT) | ||
448 | wdt_stop(); | ||
449 | return NOTIFY_DONE; | ||
450 | } | ||
451 | |||
452 | static const struct file_operations wdt_fops= | ||
453 | { | ||
454 | .owner = THIS_MODULE, | ||
455 | .llseek = no_llseek, | ||
456 | .write = wdt_write, | ||
457 | .ioctl = wdt_ioctl, | ||
458 | .open = wdt_open, | ||
459 | .release = wdt_release, | ||
460 | }; | ||
461 | |||
462 | static struct miscdevice wdt_miscdev= | ||
463 | { | ||
464 | .minor = WATCHDOG_MINOR, | ||
465 | .name = "watchdog", | ||
466 | .fops = &wdt_fops, | ||
467 | }; | ||
468 | |||
469 | static struct notifier_block wdt_notifier = { | ||
470 | .notifier_call = wdt_notify_sys, | ||
471 | }; | ||
472 | |||
473 | static int __init w83977f_wdt_init(void) | ||
474 | { | ||
475 | int rc; | ||
476 | |||
477 | printk(KERN_INFO PFX DRIVER_VERSION); | ||
478 | |||
479 | spin_lock_init(&spinlock); | ||
480 | |||
481 | /* | ||
482 | * Check that the timeout value is within it's range ; | ||
483 | * if not reset to the default | ||
484 | */ | ||
485 | if (wdt_set_timeout(timeout)) { | ||
486 | wdt_set_timeout(DEFAULT_TIMEOUT); | ||
487 | printk(KERN_INFO PFX "timeout value must be 15<=timeout<=7635, using %d\n", | ||
488 | DEFAULT_TIMEOUT); | ||
489 | } | ||
490 | |||
491 | if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME)) | ||
492 | { | ||
493 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
494 | IO_INDEX_PORT); | ||
495 | rc = -EIO; | ||
496 | goto err_out; | ||
497 | } | ||
498 | |||
499 | rc = misc_register(&wdt_miscdev); | ||
500 | if (rc) | ||
501 | { | ||
502 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
503 | wdt_miscdev.minor, rc); | ||
504 | goto err_out_region; | ||
505 | } | ||
506 | |||
507 | rc = register_reboot_notifier(&wdt_notifier); | ||
508 | if (rc) | ||
509 | { | ||
510 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
511 | rc); | ||
512 | goto err_out_miscdev; | ||
513 | } | ||
514 | |||
515 | printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d testmode=%d)\n", | ||
516 | timeout, nowayout, testmode); | ||
517 | |||
518 | return 0; | ||
519 | |||
520 | err_out_miscdev: | ||
521 | misc_deregister(&wdt_miscdev); | ||
522 | err_out_region: | ||
523 | release_region(IO_INDEX_PORT,2); | ||
524 | err_out: | ||
525 | return rc; | ||
526 | } | ||
527 | |||
528 | static void __exit w83977f_wdt_exit(void) | ||
529 | { | ||
530 | wdt_stop(); | ||
531 | misc_deregister(&wdt_miscdev); | ||
532 | unregister_reboot_notifier(&wdt_notifier); | ||
533 | release_region(IO_INDEX_PORT,2); | ||
534 | } | ||
535 | |||
536 | module_init(w83977f_wdt_init); | ||
537 | module_exit(w83977f_wdt_exit); | ||
538 | |||
539 | MODULE_AUTHOR("Jose Goncalves <jose.goncalves@inov.pt>"); | ||
540 | MODULE_DESCRIPTION("Driver for watchdog timer in W83977F I/O chip"); | ||
541 | MODULE_LICENSE("GPL"); | ||
542 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/wafer5823wdt.c b/drivers/watchdog/wafer5823wdt.c new file mode 100644 index 000000000000..950905d3c39f --- /dev/null +++ b/drivers/watchdog/wafer5823wdt.c | |||
@@ -0,0 +1,325 @@ | |||
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 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
67 | module_param(nowayout, int, 0); | ||
68 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
69 | |||
70 | static void wafwdt_ping(void) | ||
71 | { | ||
72 | /* pat watchdog */ | ||
73 | spin_lock(&wafwdt_lock); | ||
74 | inb_p(wdt_stop); | ||
75 | inb_p(wdt_start); | ||
76 | spin_unlock(&wafwdt_lock); | ||
77 | } | ||
78 | |||
79 | static void wafwdt_start(void) | ||
80 | { | ||
81 | /* start up watchdog */ | ||
82 | outb_p(timeout, wdt_start); | ||
83 | inb_p(wdt_start); | ||
84 | } | ||
85 | |||
86 | static void | ||
87 | wafwdt_stop(void) | ||
88 | { | ||
89 | /* stop watchdog */ | ||
90 | inb_p(wdt_stop); | ||
91 | } | ||
92 | |||
93 | static ssize_t wafwdt_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) | ||
94 | { | ||
95 | /* See if we got the magic character 'V' and reload the timer */ | ||
96 | if (count) { | ||
97 | if (!nowayout) { | ||
98 | size_t i; | ||
99 | |||
100 | /* In case it was set long ago */ | ||
101 | expect_close = 0; | ||
102 | |||
103 | /* scan to see whether or not we got the magic character */ | ||
104 | for (i = 0; i != count; i++) { | ||
105 | char c; | ||
106 | if (get_user(c, buf + i)) | ||
107 | return -EFAULT; | ||
108 | if (c == 'V') | ||
109 | expect_close = 42; | ||
110 | } | ||
111 | } | ||
112 | /* Well, anyhow someone wrote to us, we should return that favour */ | ||
113 | wafwdt_ping(); | ||
114 | } | ||
115 | return count; | ||
116 | } | ||
117 | |||
118 | static int wafwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
119 | unsigned long arg) | ||
120 | { | ||
121 | int new_timeout; | ||
122 | void __user *argp = (void __user *)arg; | ||
123 | int __user *p = argp; | ||
124 | static struct watchdog_info ident = { | ||
125 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | ||
126 | .firmware_version = 1, | ||
127 | .identity = "Wafer 5823 WDT", | ||
128 | }; | ||
129 | |||
130 | switch (cmd) { | ||
131 | case WDIOC_GETSUPPORT: | ||
132 | if (copy_to_user(argp, &ident, sizeof (ident))) | ||
133 | return -EFAULT; | ||
134 | break; | ||
135 | |||
136 | case WDIOC_GETSTATUS: | ||
137 | case WDIOC_GETBOOTSTATUS: | ||
138 | return put_user(0, p); | ||
139 | |||
140 | case WDIOC_KEEPALIVE: | ||
141 | wafwdt_ping(); | ||
142 | break; | ||
143 | |||
144 | case WDIOC_SETTIMEOUT: | ||
145 | if (get_user(new_timeout, p)) | ||
146 | return -EFAULT; | ||
147 | if ((new_timeout < 1) || (new_timeout > 255)) | ||
148 | return -EINVAL; | ||
149 | timeout = new_timeout; | ||
150 | wafwdt_stop(); | ||
151 | wafwdt_start(); | ||
152 | /* Fall */ | ||
153 | case WDIOC_GETTIMEOUT: | ||
154 | return put_user(timeout, p); | ||
155 | |||
156 | case WDIOC_SETOPTIONS: | ||
157 | { | ||
158 | int options, retval = -EINVAL; | ||
159 | |||
160 | if (get_user(options, p)) | ||
161 | return -EFAULT; | ||
162 | |||
163 | if (options & WDIOS_DISABLECARD) { | ||
164 | wafwdt_start(); | ||
165 | retval = 0; | ||
166 | } | ||
167 | |||
168 | if (options & WDIOS_ENABLECARD) { | ||
169 | wafwdt_stop(); | ||
170 | retval = 0; | ||
171 | } | ||
172 | |||
173 | return retval; | ||
174 | } | ||
175 | |||
176 | default: | ||
177 | return -ENOTTY; | ||
178 | } | ||
179 | return 0; | ||
180 | } | ||
181 | |||
182 | static int wafwdt_open(struct inode *inode, struct file *file) | ||
183 | { | ||
184 | if (test_and_set_bit(0, &wafwdt_is_open)) | ||
185 | return -EBUSY; | ||
186 | |||
187 | /* | ||
188 | * Activate | ||
189 | */ | ||
190 | wafwdt_start(); | ||
191 | return nonseekable_open(inode, file); | ||
192 | } | ||
193 | |||
194 | static int | ||
195 | wafwdt_close(struct inode *inode, struct file *file) | ||
196 | { | ||
197 | if (expect_close == 42) { | ||
198 | wafwdt_stop(); | ||
199 | } else { | ||
200 | printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n"); | ||
201 | wafwdt_ping(); | ||
202 | } | ||
203 | clear_bit(0, &wafwdt_is_open); | ||
204 | expect_close = 0; | ||
205 | return 0; | ||
206 | } | ||
207 | |||
208 | /* | ||
209 | * Notifier for system down | ||
210 | */ | ||
211 | |||
212 | static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) | ||
213 | { | ||
214 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
215 | /* Turn the WDT off */ | ||
216 | wafwdt_stop(); | ||
217 | } | ||
218 | return NOTIFY_DONE; | ||
219 | } | ||
220 | |||
221 | /* | ||
222 | * Kernel Interfaces | ||
223 | */ | ||
224 | |||
225 | static const struct file_operations wafwdt_fops = { | ||
226 | .owner = THIS_MODULE, | ||
227 | .llseek = no_llseek, | ||
228 | .write = wafwdt_write, | ||
229 | .ioctl = wafwdt_ioctl, | ||
230 | .open = wafwdt_open, | ||
231 | .release = wafwdt_close, | ||
232 | }; | ||
233 | |||
234 | static struct miscdevice wafwdt_miscdev = { | ||
235 | .minor = WATCHDOG_MINOR, | ||
236 | .name = "watchdog", | ||
237 | .fops = &wafwdt_fops, | ||
238 | }; | ||
239 | |||
240 | /* | ||
241 | * The WDT needs to learn about soft shutdowns in order to | ||
242 | * turn the timebomb registers off. | ||
243 | */ | ||
244 | |||
245 | static struct notifier_block wafwdt_notifier = { | ||
246 | .notifier_call = wafwdt_notify_sys, | ||
247 | }; | ||
248 | |||
249 | static int __init wafwdt_init(void) | ||
250 | { | ||
251 | int ret; | ||
252 | |||
253 | printk(KERN_INFO "WDT driver for Wafer 5823 single board computer initialising.\n"); | ||
254 | |||
255 | spin_lock_init(&wafwdt_lock); | ||
256 | |||
257 | if (timeout < 1 || timeout > 255) { | ||
258 | timeout = WD_TIMO; | ||
259 | printk (KERN_INFO PFX "timeout value must be 1<=x<=255, using %d\n", | ||
260 | timeout); | ||
261 | } | ||
262 | |||
263 | if (wdt_stop != wdt_start) { | ||
264 | if(!request_region(wdt_stop, 1, "Wafer 5823 WDT")) { | ||
265 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
266 | wdt_stop); | ||
267 | ret = -EIO; | ||
268 | goto error; | ||
269 | } | ||
270 | } | ||
271 | |||
272 | if(!request_region(wdt_start, 1, "Wafer 5823 WDT")) { | ||
273 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
274 | wdt_start); | ||
275 | ret = -EIO; | ||
276 | goto error2; | ||
277 | } | ||
278 | |||
279 | ret = register_reboot_notifier(&wafwdt_notifier); | ||
280 | if (ret != 0) { | ||
281 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
282 | ret); | ||
283 | goto error3; | ||
284 | } | ||
285 | |||
286 | ret = misc_register(&wafwdt_miscdev); | ||
287 | if (ret != 0) { | ||
288 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
289 | WATCHDOG_MINOR, ret); | ||
290 | goto error4; | ||
291 | } | ||
292 | |||
293 | printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", | ||
294 | timeout, nowayout); | ||
295 | |||
296 | return ret; | ||
297 | error4: | ||
298 | unregister_reboot_notifier(&wafwdt_notifier); | ||
299 | error3: | ||
300 | release_region(wdt_start, 1); | ||
301 | error2: | ||
302 | if (wdt_stop != wdt_start) | ||
303 | release_region(wdt_stop, 1); | ||
304 | error: | ||
305 | return ret; | ||
306 | } | ||
307 | |||
308 | static void __exit wafwdt_exit(void) | ||
309 | { | ||
310 | misc_deregister(&wafwdt_miscdev); | ||
311 | unregister_reboot_notifier(&wafwdt_notifier); | ||
312 | if(wdt_stop != wdt_start) | ||
313 | release_region(wdt_stop, 1); | ||
314 | release_region(wdt_start, 1); | ||
315 | } | ||
316 | |||
317 | module_init(wafwdt_init); | ||
318 | module_exit(wafwdt_exit); | ||
319 | |||
320 | MODULE_AUTHOR("Justin Cormack"); | ||
321 | MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver"); | ||
322 | MODULE_LICENSE("GPL"); | ||
323 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
324 | |||
325 | /* end of wafer5823wdt.c */ | ||
diff --git a/drivers/watchdog/wd501p.h b/drivers/watchdog/wd501p.h new file mode 100644 index 000000000000..a4504f40394d --- /dev/null +++ b/drivers/watchdog/wd501p.h | |||
@@ -0,0 +1,51 @@ | |||
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 | |||
23 | #define WDT_COUNT0 (io+0) | ||
24 | #define WDT_COUNT1 (io+1) | ||
25 | #define WDT_COUNT2 (io+2) | ||
26 | #define WDT_CR (io+3) | ||
27 | #define WDT_SR (io+4) /* Start buzzer on PCI write */ | ||
28 | #define WDT_RT (io+5) /* Stop buzzer on PCI write */ | ||
29 | #define WDT_BUZZER (io+6) /* PCI only: rd=disable, wr=enable */ | ||
30 | #define WDT_DC (io+7) | ||
31 | |||
32 | /* The following are only on the PCI card, they're outside of I/O space on | ||
33 | * the ISA card: */ | ||
34 | #define WDT_CLOCK (io+12) /* COUNT2: rd=16.67MHz, wr=2.0833MHz */ | ||
35 | /* inverted opto isolated reset output: */ | ||
36 | #define WDT_OPTONOTRST (io+13) /* wr=enable, rd=disable */ | ||
37 | /* opto isolated reset output: */ | ||
38 | #define WDT_OPTORST (io+14) /* wr=enable, rd=disable */ | ||
39 | /* programmable outputs: */ | ||
40 | #define WDT_PROGOUT (io+15) /* wr=enable, rd=disable */ | ||
41 | |||
42 | /* FAN 501 500 */ | ||
43 | #define WDC_SR_WCCR 1 /* Active low */ /* X X X */ | ||
44 | #define WDC_SR_TGOOD 2 /* X X - */ | ||
45 | #define WDC_SR_ISOI0 4 /* X X X */ | ||
46 | #define WDC_SR_ISII1 8 /* X X X */ | ||
47 | #define WDC_SR_FANGOOD 16 /* X - - */ | ||
48 | #define WDC_SR_PSUOVER 32 /* Active low */ /* X X - */ | ||
49 | #define WDC_SR_PSUUNDR 64 /* Active low */ /* X X - */ | ||
50 | #define WDC_SR_IRQ 128 /* Active low */ /* X X X */ | ||
51 | |||
diff --git a/drivers/watchdog/wdrtas.c b/drivers/watchdog/wdrtas.c new file mode 100644 index 000000000000..1d64e277567d --- /dev/null +++ b/drivers/watchdog/wdrtas.c | |||
@@ -0,0 +1,695 @@ | |||
1 | /* | ||
2 | * FIXME: add wdrtas_get_status and wdrtas_get_boot_status as soon as | ||
3 | * RTAS calls are available | ||
4 | */ | ||
5 | |||
6 | /* | ||
7 | * RTAS watchdog driver | ||
8 | * | ||
9 | * (C) Copyright IBM Corp. 2005 | ||
10 | * device driver to exploit watchdog RTAS functions | ||
11 | * | ||
12 | * Authors : Utz Bacher <utz.bacher@de.ibm.com> | ||
13 | * | ||
14 | * This program is free software; you can redistribute it and/or modify | ||
15 | * it under the terms of the GNU General Public License as published by | ||
16 | * the Free Software Foundation; either version 2, or (at your option) | ||
17 | * any later version. | ||
18 | * | ||
19 | * This program is distributed in the hope that it will be useful, | ||
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
22 | * GNU General Public License for more details. | ||
23 | * | ||
24 | * You should have received a copy of the GNU General Public License | ||
25 | * along with this program; if not, write to the Free Software | ||
26 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
27 | */ | ||
28 | |||
29 | #include <linux/fs.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/kernel.h> | ||
32 | #include <linux/miscdevice.h> | ||
33 | #include <linux/module.h> | ||
34 | #include <linux/notifier.h> | ||
35 | #include <linux/reboot.h> | ||
36 | #include <linux/types.h> | ||
37 | #include <linux/watchdog.h> | ||
38 | |||
39 | #include <asm/rtas.h> | ||
40 | #include <asm/uaccess.h> | ||
41 | |||
42 | #define WDRTAS_MAGIC_CHAR 42 | ||
43 | #define WDRTAS_SUPPORTED_MASK (WDIOF_SETTIMEOUT | \ | ||
44 | WDIOF_MAGICCLOSE) | ||
45 | |||
46 | MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>"); | ||
47 | MODULE_DESCRIPTION("RTAS watchdog driver"); | ||
48 | MODULE_LICENSE("GPL"); | ||
49 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
50 | MODULE_ALIAS_MISCDEV(TEMP_MINOR); | ||
51 | |||
52 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
53 | static int wdrtas_nowayout = 1; | ||
54 | #else | ||
55 | static int wdrtas_nowayout = 0; | ||
56 | #endif | ||
57 | |||
58 | static atomic_t wdrtas_miscdev_open = ATOMIC_INIT(0); | ||
59 | static char wdrtas_expect_close = 0; | ||
60 | |||
61 | static int wdrtas_interval; | ||
62 | |||
63 | #define WDRTAS_THERMAL_SENSOR 3 | ||
64 | static int wdrtas_token_get_sensor_state; | ||
65 | #define WDRTAS_SURVEILLANCE_IND 9000 | ||
66 | static int wdrtas_token_set_indicator; | ||
67 | #define WDRTAS_SP_SPI 28 | ||
68 | static int wdrtas_token_get_sp; | ||
69 | static int wdrtas_token_event_scan; | ||
70 | |||
71 | #define WDRTAS_DEFAULT_INTERVAL 300 | ||
72 | |||
73 | #define WDRTAS_LOGBUFFER_LEN 128 | ||
74 | static char wdrtas_logbuffer[WDRTAS_LOGBUFFER_LEN]; | ||
75 | |||
76 | |||
77 | /*** watchdog access functions */ | ||
78 | |||
79 | /** | ||
80 | * wdrtas_set_interval - sets the watchdog interval | ||
81 | * @interval: new interval | ||
82 | * | ||
83 | * returns 0 on success, <0 on failures | ||
84 | * | ||
85 | * wdrtas_set_interval sets the watchdog keepalive interval by calling the | ||
86 | * RTAS function set-indicator (surveillance). The unit of interval is | ||
87 | * seconds. | ||
88 | */ | ||
89 | static int | ||
90 | wdrtas_set_interval(int interval) | ||
91 | { | ||
92 | long result; | ||
93 | static int print_msg = 10; | ||
94 | |||
95 | /* rtas uses minutes */ | ||
96 | interval = (interval + 59) / 60; | ||
97 | |||
98 | result = rtas_call(wdrtas_token_set_indicator, 3, 1, NULL, | ||
99 | WDRTAS_SURVEILLANCE_IND, 0, interval); | ||
100 | if ( (result < 0) && (print_msg) ) { | ||
101 | printk(KERN_ERR "wdrtas: setting the watchdog to %i " | ||
102 | "timeout failed: %li\n", interval, result); | ||
103 | print_msg--; | ||
104 | } | ||
105 | |||
106 | return result; | ||
107 | } | ||
108 | |||
109 | /** | ||
110 | * wdrtas_get_interval - returns the current watchdog interval | ||
111 | * @fallback_value: value (in seconds) to use, if the RTAS call fails | ||
112 | * | ||
113 | * returns the interval | ||
114 | * | ||
115 | * wdrtas_get_interval returns the current watchdog keepalive interval | ||
116 | * as reported by the RTAS function ibm,get-system-parameter. The unit | ||
117 | * of the return value is seconds. | ||
118 | */ | ||
119 | static int | ||
120 | wdrtas_get_interval(int fallback_value) | ||
121 | { | ||
122 | long result; | ||
123 | char value[4]; | ||
124 | |||
125 | result = rtas_call(wdrtas_token_get_sp, 3, 1, NULL, | ||
126 | WDRTAS_SP_SPI, (void *)__pa(&value), 4); | ||
127 | if ( (value[0] != 0) || (value[1] != 2) || (value[3] != 0) || | ||
128 | (result < 0) ) { | ||
129 | printk(KERN_WARNING "wdrtas: could not get sp_spi watchdog " | ||
130 | "timeout (%li). Continuing\n", result); | ||
131 | return fallback_value; | ||
132 | } | ||
133 | |||
134 | /* rtas uses minutes */ | ||
135 | return ((int)value[2]) * 60; | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * wdrtas_timer_start - starts watchdog | ||
140 | * | ||
141 | * wdrtas_timer_start starts the watchdog by calling the RTAS function | ||
142 | * set-interval (surveillance) | ||
143 | */ | ||
144 | static void | ||
145 | wdrtas_timer_start(void) | ||
146 | { | ||
147 | wdrtas_set_interval(wdrtas_interval); | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * wdrtas_timer_stop - stops watchdog | ||
152 | * | ||
153 | * wdrtas_timer_stop stops the watchdog timer by calling the RTAS function | ||
154 | * set-interval (surveillance) | ||
155 | */ | ||
156 | static void | ||
157 | wdrtas_timer_stop(void) | ||
158 | { | ||
159 | wdrtas_set_interval(0); | ||
160 | } | ||
161 | |||
162 | /** | ||
163 | * wdrtas_log_scanned_event - logs an event we received during keepalive | ||
164 | * | ||
165 | * wdrtas_log_scanned_event prints a message to the log buffer dumping | ||
166 | * the results of the last event-scan call | ||
167 | */ | ||
168 | static void | ||
169 | wdrtas_log_scanned_event(void) | ||
170 | { | ||
171 | int i; | ||
172 | |||
173 | for (i = 0; i < WDRTAS_LOGBUFFER_LEN; i += 16) | ||
174 | printk(KERN_INFO "wdrtas: dumping event (line %i/%i), data = " | ||
175 | "%02x %02x %02x %02x %02x %02x %02x %02x " | ||
176 | "%02x %02x %02x %02x %02x %02x %02x %02x\n", | ||
177 | (i / 16) + 1, (WDRTAS_LOGBUFFER_LEN / 16), | ||
178 | wdrtas_logbuffer[i + 0], wdrtas_logbuffer[i + 1], | ||
179 | wdrtas_logbuffer[i + 2], wdrtas_logbuffer[i + 3], | ||
180 | wdrtas_logbuffer[i + 4], wdrtas_logbuffer[i + 5], | ||
181 | wdrtas_logbuffer[i + 6], wdrtas_logbuffer[i + 7], | ||
182 | wdrtas_logbuffer[i + 8], wdrtas_logbuffer[i + 9], | ||
183 | wdrtas_logbuffer[i + 10], wdrtas_logbuffer[i + 11], | ||
184 | wdrtas_logbuffer[i + 12], wdrtas_logbuffer[i + 13], | ||
185 | wdrtas_logbuffer[i + 14], wdrtas_logbuffer[i + 15]); | ||
186 | } | ||
187 | |||
188 | /** | ||
189 | * wdrtas_timer_keepalive - resets watchdog timer to keep system alive | ||
190 | * | ||
191 | * wdrtas_timer_keepalive restarts the watchdog timer by calling the | ||
192 | * RTAS function event-scan and repeats these calls as long as there are | ||
193 | * events available. All events will be dumped. | ||
194 | */ | ||
195 | static void | ||
196 | wdrtas_timer_keepalive(void) | ||
197 | { | ||
198 | long result; | ||
199 | |||
200 | do { | ||
201 | result = rtas_call(wdrtas_token_event_scan, 4, 1, NULL, | ||
202 | RTAS_EVENT_SCAN_ALL_EVENTS, 0, | ||
203 | (void *)__pa(wdrtas_logbuffer), | ||
204 | WDRTAS_LOGBUFFER_LEN); | ||
205 | if (result < 0) | ||
206 | printk(KERN_ERR "wdrtas: event-scan failed: %li\n", | ||
207 | result); | ||
208 | if (result == 0) | ||
209 | wdrtas_log_scanned_event(); | ||
210 | } while (result == 0); | ||
211 | } | ||
212 | |||
213 | /** | ||
214 | * wdrtas_get_temperature - returns current temperature | ||
215 | * | ||
216 | * returns temperature or <0 on failures | ||
217 | * | ||
218 | * wdrtas_get_temperature returns the current temperature in Fahrenheit. It | ||
219 | * uses the RTAS call get-sensor-state, token 3 to do so | ||
220 | */ | ||
221 | static int | ||
222 | wdrtas_get_temperature(void) | ||
223 | { | ||
224 | long result; | ||
225 | int temperature = 0; | ||
226 | |||
227 | result = rtas_call(wdrtas_token_get_sensor_state, 2, 2, | ||
228 | (void *)__pa(&temperature), | ||
229 | WDRTAS_THERMAL_SENSOR, 0); | ||
230 | |||
231 | if (result < 0) | ||
232 | printk(KERN_WARNING "wdrtas: reading the thermal sensor " | ||
233 | "faild: %li\n", result); | ||
234 | else | ||
235 | temperature = ((temperature * 9) / 5) + 32; /* fahrenheit */ | ||
236 | |||
237 | return temperature; | ||
238 | } | ||
239 | |||
240 | /** | ||
241 | * wdrtas_get_status - returns the status of the watchdog | ||
242 | * | ||
243 | * returns a bitmask of defines WDIOF_... as defined in | ||
244 | * include/linux/watchdog.h | ||
245 | */ | ||
246 | static int | ||
247 | wdrtas_get_status(void) | ||
248 | { | ||
249 | return 0; /* TODO */ | ||
250 | } | ||
251 | |||
252 | /** | ||
253 | * wdrtas_get_boot_status - returns the reason for the last boot | ||
254 | * | ||
255 | * returns a bitmask of defines WDIOF_... as defined in | ||
256 | * include/linux/watchdog.h, indicating why the watchdog rebooted the system | ||
257 | */ | ||
258 | static int | ||
259 | wdrtas_get_boot_status(void) | ||
260 | { | ||
261 | return 0; /* TODO */ | ||
262 | } | ||
263 | |||
264 | /*** watchdog API and operations stuff */ | ||
265 | |||
266 | /* wdrtas_write - called when watchdog device is written to | ||
267 | * @file: file structure | ||
268 | * @buf: user buffer with data | ||
269 | * @len: amount to data written | ||
270 | * @ppos: position in file | ||
271 | * | ||
272 | * returns the number of successfully processed characters, which is always | ||
273 | * the number of bytes passed to this function | ||
274 | * | ||
275 | * wdrtas_write processes all the data given to it and looks for the magic | ||
276 | * character 'V'. This character allows the watchdog device to be closed | ||
277 | * properly. | ||
278 | */ | ||
279 | static ssize_t | ||
280 | wdrtas_write(struct file *file, const char __user *buf, | ||
281 | size_t len, loff_t *ppos) | ||
282 | { | ||
283 | int i; | ||
284 | char c; | ||
285 | |||
286 | if (!len) | ||
287 | goto out; | ||
288 | |||
289 | if (!wdrtas_nowayout) { | ||
290 | wdrtas_expect_close = 0; | ||
291 | /* look for 'V' */ | ||
292 | for (i = 0; i < len; i++) { | ||
293 | if (get_user(c, buf + i)) | ||
294 | return -EFAULT; | ||
295 | /* allow to close device */ | ||
296 | if (c == 'V') | ||
297 | wdrtas_expect_close = WDRTAS_MAGIC_CHAR; | ||
298 | } | ||
299 | } | ||
300 | |||
301 | wdrtas_timer_keepalive(); | ||
302 | |||
303 | out: | ||
304 | return len; | ||
305 | } | ||
306 | |||
307 | /** | ||
308 | * wdrtas_ioctl - ioctl function for the watchdog device | ||
309 | * @inode: inode structure | ||
310 | * @file: file structure | ||
311 | * @cmd: command for ioctl | ||
312 | * @arg: argument pointer | ||
313 | * | ||
314 | * returns 0 on success, <0 on failure | ||
315 | * | ||
316 | * wdrtas_ioctl implements the watchdog API ioctls | ||
317 | */ | ||
318 | static int | ||
319 | wdrtas_ioctl(struct inode *inode, struct file *file, | ||
320 | unsigned int cmd, unsigned long arg) | ||
321 | { | ||
322 | int __user *argp = (void __user *)arg; | ||
323 | int i; | ||
324 | static struct watchdog_info wdinfo = { | ||
325 | .options = WDRTAS_SUPPORTED_MASK, | ||
326 | .firmware_version = 0, | ||
327 | .identity = "wdrtas" | ||
328 | }; | ||
329 | |||
330 | switch (cmd) { | ||
331 | case WDIOC_GETSUPPORT: | ||
332 | if (copy_to_user(argp, &wdinfo, sizeof(wdinfo))) | ||
333 | return -EFAULT; | ||
334 | return 0; | ||
335 | |||
336 | case WDIOC_GETSTATUS: | ||
337 | i = wdrtas_get_status(); | ||
338 | return put_user(i, argp); | ||
339 | |||
340 | case WDIOC_GETBOOTSTATUS: | ||
341 | i = wdrtas_get_boot_status(); | ||
342 | return put_user(i, argp); | ||
343 | |||
344 | case WDIOC_GETTEMP: | ||
345 | if (wdrtas_token_get_sensor_state == RTAS_UNKNOWN_SERVICE) | ||
346 | return -EOPNOTSUPP; | ||
347 | |||
348 | i = wdrtas_get_temperature(); | ||
349 | return put_user(i, argp); | ||
350 | |||
351 | case WDIOC_SETOPTIONS: | ||
352 | if (get_user(i, argp)) | ||
353 | return -EFAULT; | ||
354 | if (i & WDIOS_DISABLECARD) | ||
355 | wdrtas_timer_stop(); | ||
356 | if (i & WDIOS_ENABLECARD) { | ||
357 | wdrtas_timer_keepalive(); | ||
358 | wdrtas_timer_start(); | ||
359 | } | ||
360 | if (i & WDIOS_TEMPPANIC) { | ||
361 | /* not implemented. Done by H8 */ | ||
362 | } | ||
363 | return 0; | ||
364 | |||
365 | case WDIOC_KEEPALIVE: | ||
366 | wdrtas_timer_keepalive(); | ||
367 | return 0; | ||
368 | |||
369 | case WDIOC_SETTIMEOUT: | ||
370 | if (get_user(i, argp)) | ||
371 | return -EFAULT; | ||
372 | |||
373 | if (wdrtas_set_interval(i)) | ||
374 | return -EINVAL; | ||
375 | |||
376 | wdrtas_timer_keepalive(); | ||
377 | |||
378 | if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE) | ||
379 | wdrtas_interval = i; | ||
380 | else | ||
381 | wdrtas_interval = wdrtas_get_interval(i); | ||
382 | /* fallthrough */ | ||
383 | |||
384 | case WDIOC_GETTIMEOUT: | ||
385 | return put_user(wdrtas_interval, argp); | ||
386 | |||
387 | default: | ||
388 | return -ENOTTY; | ||
389 | } | ||
390 | } | ||
391 | |||
392 | /** | ||
393 | * wdrtas_open - open function of watchdog device | ||
394 | * @inode: inode structure | ||
395 | * @file: file structure | ||
396 | * | ||
397 | * returns 0 on success, -EBUSY if the file has been opened already, <0 on | ||
398 | * other failures | ||
399 | * | ||
400 | * function called when watchdog device is opened | ||
401 | */ | ||
402 | static int | ||
403 | wdrtas_open(struct inode *inode, struct file *file) | ||
404 | { | ||
405 | /* only open once */ | ||
406 | if (atomic_inc_return(&wdrtas_miscdev_open) > 1) { | ||
407 | atomic_dec(&wdrtas_miscdev_open); | ||
408 | return -EBUSY; | ||
409 | } | ||
410 | |||
411 | wdrtas_timer_start(); | ||
412 | wdrtas_timer_keepalive(); | ||
413 | |||
414 | return nonseekable_open(inode, file); | ||
415 | } | ||
416 | |||
417 | /** | ||
418 | * wdrtas_close - close function of watchdog device | ||
419 | * @inode: inode structure | ||
420 | * @file: file structure | ||
421 | * | ||
422 | * returns 0 on success | ||
423 | * | ||
424 | * close function. Always succeeds | ||
425 | */ | ||
426 | static int | ||
427 | wdrtas_close(struct inode *inode, struct file *file) | ||
428 | { | ||
429 | /* only stop watchdog, if this was announced using 'V' before */ | ||
430 | if (wdrtas_expect_close == WDRTAS_MAGIC_CHAR) | ||
431 | wdrtas_timer_stop(); | ||
432 | else { | ||
433 | printk(KERN_WARNING "wdrtas: got unexpected close. Watchdog " | ||
434 | "not stopped.\n"); | ||
435 | wdrtas_timer_keepalive(); | ||
436 | } | ||
437 | |||
438 | wdrtas_expect_close = 0; | ||
439 | atomic_dec(&wdrtas_miscdev_open); | ||
440 | return 0; | ||
441 | } | ||
442 | |||
443 | /** | ||
444 | * wdrtas_temp_read - gives back the temperature in fahrenheit | ||
445 | * @file: file structure | ||
446 | * @buf: user buffer | ||
447 | * @count: number of bytes to be read | ||
448 | * @ppos: position in file | ||
449 | * | ||
450 | * returns always 1 or -EFAULT in case of user space copy failures, <0 on | ||
451 | * other failures | ||
452 | * | ||
453 | * wdrtas_temp_read gives the temperature to the users by copying this | ||
454 | * value as one byte into the user space buffer. The unit is Fahrenheit... | ||
455 | */ | ||
456 | static ssize_t | ||
457 | wdrtas_temp_read(struct file *file, char __user *buf, | ||
458 | size_t count, loff_t *ppos) | ||
459 | { | ||
460 | int temperature = 0; | ||
461 | |||
462 | temperature = wdrtas_get_temperature(); | ||
463 | if (temperature < 0) | ||
464 | return temperature; | ||
465 | |||
466 | if (copy_to_user(buf, &temperature, 1)) | ||
467 | return -EFAULT; | ||
468 | |||
469 | return 1; | ||
470 | } | ||
471 | |||
472 | /** | ||
473 | * wdrtas_temp_open - open function of temperature device | ||
474 | * @inode: inode structure | ||
475 | * @file: file structure | ||
476 | * | ||
477 | * returns 0 on success, <0 on failure | ||
478 | * | ||
479 | * function called when temperature device is opened | ||
480 | */ | ||
481 | static int | ||
482 | wdrtas_temp_open(struct inode *inode, struct file *file) | ||
483 | { | ||
484 | return nonseekable_open(inode, file); | ||
485 | } | ||
486 | |||
487 | /** | ||
488 | * wdrtas_temp_close - close function of temperature device | ||
489 | * @inode: inode structure | ||
490 | * @file: file structure | ||
491 | * | ||
492 | * returns 0 on success | ||
493 | * | ||
494 | * close function. Always succeeds | ||
495 | */ | ||
496 | static int | ||
497 | wdrtas_temp_close(struct inode *inode, struct file *file) | ||
498 | { | ||
499 | return 0; | ||
500 | } | ||
501 | |||
502 | /** | ||
503 | * wdrtas_reboot - reboot notifier function | ||
504 | * @nb: notifier block structure | ||
505 | * @code: reboot code | ||
506 | * @ptr: unused | ||
507 | * | ||
508 | * returns NOTIFY_DONE | ||
509 | * | ||
510 | * wdrtas_reboot stops the watchdog in case of a reboot | ||
511 | */ | ||
512 | static int | ||
513 | wdrtas_reboot(struct notifier_block *this, unsigned long code, void *ptr) | ||
514 | { | ||
515 | if ( (code==SYS_DOWN) || (code==SYS_HALT) ) | ||
516 | wdrtas_timer_stop(); | ||
517 | |||
518 | return NOTIFY_DONE; | ||
519 | } | ||
520 | |||
521 | /*** initialization stuff */ | ||
522 | |||
523 | static const struct file_operations wdrtas_fops = { | ||
524 | .owner = THIS_MODULE, | ||
525 | .llseek = no_llseek, | ||
526 | .write = wdrtas_write, | ||
527 | .ioctl = wdrtas_ioctl, | ||
528 | .open = wdrtas_open, | ||
529 | .release = wdrtas_close, | ||
530 | }; | ||
531 | |||
532 | static struct miscdevice wdrtas_miscdev = { | ||
533 | .minor = WATCHDOG_MINOR, | ||
534 | .name = "watchdog", | ||
535 | .fops = &wdrtas_fops, | ||
536 | }; | ||
537 | |||
538 | static const struct file_operations wdrtas_temp_fops = { | ||
539 | .owner = THIS_MODULE, | ||
540 | .llseek = no_llseek, | ||
541 | .read = wdrtas_temp_read, | ||
542 | .open = wdrtas_temp_open, | ||
543 | .release = wdrtas_temp_close, | ||
544 | }; | ||
545 | |||
546 | static struct miscdevice wdrtas_tempdev = { | ||
547 | .minor = TEMP_MINOR, | ||
548 | .name = "temperature", | ||
549 | .fops = &wdrtas_temp_fops, | ||
550 | }; | ||
551 | |||
552 | static struct notifier_block wdrtas_notifier = { | ||
553 | .notifier_call = wdrtas_reboot, | ||
554 | }; | ||
555 | |||
556 | /** | ||
557 | * wdrtas_get_tokens - reads in RTAS tokens | ||
558 | * | ||
559 | * returns 0 on succes, <0 on failure | ||
560 | * | ||
561 | * wdrtas_get_tokens reads in the tokens for the RTAS calls used in | ||
562 | * this watchdog driver. It tolerates, if "get-sensor-state" and | ||
563 | * "ibm,get-system-parameter" are not available. | ||
564 | */ | ||
565 | static int | ||
566 | wdrtas_get_tokens(void) | ||
567 | { | ||
568 | wdrtas_token_get_sensor_state = rtas_token("get-sensor-state"); | ||
569 | if (wdrtas_token_get_sensor_state == RTAS_UNKNOWN_SERVICE) { | ||
570 | printk(KERN_WARNING "wdrtas: couldn't get token for " | ||
571 | "get-sensor-state. Trying to continue without " | ||
572 | "temperature support.\n"); | ||
573 | } | ||
574 | |||
575 | wdrtas_token_get_sp = rtas_token("ibm,get-system-parameter"); | ||
576 | if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE) { | ||
577 | printk(KERN_WARNING "wdrtas: couldn't get token for " | ||
578 | "ibm,get-system-parameter. Trying to continue with " | ||
579 | "a default timeout value of %i seconds.\n", | ||
580 | WDRTAS_DEFAULT_INTERVAL); | ||
581 | } | ||
582 | |||
583 | wdrtas_token_set_indicator = rtas_token("set-indicator"); | ||
584 | if (wdrtas_token_set_indicator == RTAS_UNKNOWN_SERVICE) { | ||
585 | printk(KERN_ERR "wdrtas: couldn't get token for " | ||
586 | "set-indicator. Terminating watchdog code.\n"); | ||
587 | return -EIO; | ||
588 | } | ||
589 | |||
590 | wdrtas_token_event_scan = rtas_token("event-scan"); | ||
591 | if (wdrtas_token_event_scan == RTAS_UNKNOWN_SERVICE) { | ||
592 | printk(KERN_ERR "wdrtas: couldn't get token for event-scan. " | ||
593 | "Terminating watchdog code.\n"); | ||
594 | return -EIO; | ||
595 | } | ||
596 | |||
597 | return 0; | ||
598 | } | ||
599 | |||
600 | /** | ||
601 | * wdrtas_unregister_devs - unregisters the misc dev handlers | ||
602 | * | ||
603 | * wdrtas_register_devs unregisters the watchdog and temperature watchdog | ||
604 | * misc devs | ||
605 | */ | ||
606 | static void | ||
607 | wdrtas_unregister_devs(void) | ||
608 | { | ||
609 | misc_deregister(&wdrtas_miscdev); | ||
610 | if (wdrtas_token_get_sensor_state != RTAS_UNKNOWN_SERVICE) | ||
611 | misc_deregister(&wdrtas_tempdev); | ||
612 | } | ||
613 | |||
614 | /** | ||
615 | * wdrtas_register_devs - registers the misc dev handlers | ||
616 | * | ||
617 | * returns 0 on succes, <0 on failure | ||
618 | * | ||
619 | * wdrtas_register_devs registers the watchdog and temperature watchdog | ||
620 | * misc devs | ||
621 | */ | ||
622 | static int | ||
623 | wdrtas_register_devs(void) | ||
624 | { | ||
625 | int result; | ||
626 | |||
627 | result = misc_register(&wdrtas_miscdev); | ||
628 | if (result) { | ||
629 | printk(KERN_ERR "wdrtas: couldn't register watchdog misc " | ||
630 | "device. Terminating watchdog code.\n"); | ||
631 | return result; | ||
632 | } | ||
633 | |||
634 | if (wdrtas_token_get_sensor_state != RTAS_UNKNOWN_SERVICE) { | ||
635 | result = misc_register(&wdrtas_tempdev); | ||
636 | if (result) { | ||
637 | printk(KERN_WARNING "wdrtas: couldn't register " | ||
638 | "watchdog temperature misc device. Continuing " | ||
639 | "without temperature support.\n"); | ||
640 | wdrtas_token_get_sensor_state = RTAS_UNKNOWN_SERVICE; | ||
641 | } | ||
642 | } | ||
643 | |||
644 | return 0; | ||
645 | } | ||
646 | |||
647 | /** | ||
648 | * wdrtas_init - init function of the watchdog driver | ||
649 | * | ||
650 | * returns 0 on succes, <0 on failure | ||
651 | * | ||
652 | * registers the file handlers and the reboot notifier | ||
653 | */ | ||
654 | static int __init | ||
655 | wdrtas_init(void) | ||
656 | { | ||
657 | if (wdrtas_get_tokens()) | ||
658 | return -ENODEV; | ||
659 | |||
660 | if (wdrtas_register_devs()) | ||
661 | return -ENODEV; | ||
662 | |||
663 | if (register_reboot_notifier(&wdrtas_notifier)) { | ||
664 | printk(KERN_ERR "wdrtas: could not register reboot notifier. " | ||
665 | "Terminating watchdog code.\n"); | ||
666 | wdrtas_unregister_devs(); | ||
667 | return -ENODEV; | ||
668 | } | ||
669 | |||
670 | if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE) | ||
671 | wdrtas_interval = WDRTAS_DEFAULT_INTERVAL; | ||
672 | else | ||
673 | wdrtas_interval = wdrtas_get_interval(WDRTAS_DEFAULT_INTERVAL); | ||
674 | |||
675 | return 0; | ||
676 | } | ||
677 | |||
678 | /** | ||
679 | * wdrtas_exit - exit function of the watchdog driver | ||
680 | * | ||
681 | * unregisters the file handlers and the reboot notifier | ||
682 | */ | ||
683 | static void __exit | ||
684 | wdrtas_exit(void) | ||
685 | { | ||
686 | if (!wdrtas_nowayout) | ||
687 | wdrtas_timer_stop(); | ||
688 | |||
689 | wdrtas_unregister_devs(); | ||
690 | |||
691 | unregister_reboot_notifier(&wdrtas_notifier); | ||
692 | } | ||
693 | |||
694 | module_init(wdrtas_init); | ||
695 | module_exit(wdrtas_exit); | ||
diff --git a/drivers/watchdog/wdt.c b/drivers/watchdog/wdt.c new file mode 100644 index 000000000000..0a3de6a02442 --- /dev/null +++ b/drivers/watchdog/wdt.c | |||
@@ -0,0 +1,640 @@ | |||
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/interrupt.h> | ||
35 | #include <linux/module.h> | ||
36 | #include <linux/moduleparam.h> | ||
37 | #include <linux/types.h> | ||
38 | #include <linux/miscdevice.h> | ||
39 | #include <linux/watchdog.h> | ||
40 | #include <linux/fs.h> | ||
41 | #include <linux/ioport.h> | ||
42 | #include <linux/notifier.h> | ||
43 | #include <linux/reboot.h> | ||
44 | #include <linux/init.h> | ||
45 | |||
46 | #include <asm/io.h> | ||
47 | #include <asm/uaccess.h> | ||
48 | #include <asm/system.h> | ||
49 | #include "wd501p.h" | ||
50 | |||
51 | static unsigned long wdt_is_open; | ||
52 | static char expect_close; | ||
53 | |||
54 | /* | ||
55 | * Module parameters | ||
56 | */ | ||
57 | |||
58 | #define WD_TIMO 60 /* Default heartbeat = 60 seconds */ | ||
59 | |||
60 | static int heartbeat = WD_TIMO; | ||
61 | static int wd_heartbeat; | ||
62 | module_param(heartbeat, int, 0); | ||
63 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")"); | ||
64 | |||
65 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
66 | module_param(nowayout, int, 0); | ||
67 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
68 | |||
69 | /* You must set these - there is no sane way to probe for this board. */ | ||
70 | static int io=0x240; | ||
71 | static int irq=11; | ||
72 | |||
73 | module_param(io, int, 0); | ||
74 | MODULE_PARM_DESC(io, "WDT io port (default=0x240)"); | ||
75 | module_param(irq, int, 0); | ||
76 | MODULE_PARM_DESC(irq, "WDT irq (default=11)"); | ||
77 | |||
78 | #ifdef CONFIG_WDT_501 | ||
79 | /* Support for the Fan Tachometer on the WDT501-P */ | ||
80 | static int tachometer; | ||
81 | |||
82 | module_param(tachometer, int, 0); | ||
83 | MODULE_PARM_DESC(tachometer, "WDT501-P Fan Tachometer support (0=disable, default=0)"); | ||
84 | #endif /* CONFIG_WDT_501 */ | ||
85 | |||
86 | /* | ||
87 | * Programming support | ||
88 | */ | ||
89 | |||
90 | static void wdt_ctr_mode(int ctr, int mode) | ||
91 | { | ||
92 | ctr<<=6; | ||
93 | ctr|=0x30; | ||
94 | ctr|=(mode<<1); | ||
95 | outb_p(ctr, WDT_CR); | ||
96 | } | ||
97 | |||
98 | static void wdt_ctr_load(int ctr, int val) | ||
99 | { | ||
100 | outb_p(val&0xFF, WDT_COUNT0+ctr); | ||
101 | outb_p(val>>8, WDT_COUNT0+ctr); | ||
102 | } | ||
103 | |||
104 | /** | ||
105 | * wdt_start: | ||
106 | * | ||
107 | * Start the watchdog driver. | ||
108 | */ | ||
109 | |||
110 | static int wdt_start(void) | ||
111 | { | ||
112 | inb_p(WDT_DC); /* Disable watchdog */ | ||
113 | wdt_ctr_mode(0,3); /* Program CTR0 for Mode 3: Square Wave Generator */ | ||
114 | wdt_ctr_mode(1,2); /* Program CTR1 for Mode 2: Rate Generator */ | ||
115 | wdt_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */ | ||
116 | wdt_ctr_load(0, 8948); /* Count at 100Hz */ | ||
117 | wdt_ctr_load(1,wd_heartbeat); /* Heartbeat */ | ||
118 | wdt_ctr_load(2,65535); /* Length of reset pulse */ | ||
119 | outb_p(0, WDT_DC); /* Enable watchdog */ | ||
120 | return 0; | ||
121 | } | ||
122 | |||
123 | /** | ||
124 | * wdt_stop: | ||
125 | * | ||
126 | * Stop the watchdog driver. | ||
127 | */ | ||
128 | |||
129 | static int wdt_stop (void) | ||
130 | { | ||
131 | /* Turn the card off */ | ||
132 | inb_p(WDT_DC); /* Disable watchdog */ | ||
133 | wdt_ctr_load(2,0); /* 0 length reset pulses now */ | ||
134 | return 0; | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * wdt_ping: | ||
139 | * | ||
140 | * Reload counter one with the watchdog heartbeat. We don't bother reloading | ||
141 | * the cascade counter. | ||
142 | */ | ||
143 | |||
144 | static int wdt_ping(void) | ||
145 | { | ||
146 | /* Write a watchdog value */ | ||
147 | inb_p(WDT_DC); /* Disable watchdog */ | ||
148 | wdt_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */ | ||
149 | wdt_ctr_load(1,wd_heartbeat); /* Heartbeat */ | ||
150 | outb_p(0, WDT_DC); /* Enable watchdog */ | ||
151 | return 0; | ||
152 | } | ||
153 | |||
154 | /** | ||
155 | * wdt_set_heartbeat: | ||
156 | * @t: the new heartbeat value that needs to be set. | ||
157 | * | ||
158 | * Set a new heartbeat value for the watchdog device. If the heartbeat value is | ||
159 | * incorrect we keep the old value and return -EINVAL. If successfull we | ||
160 | * return 0. | ||
161 | */ | ||
162 | static int wdt_set_heartbeat(int t) | ||
163 | { | ||
164 | if ((t < 1) || (t > 65535)) | ||
165 | return -EINVAL; | ||
166 | |||
167 | heartbeat = t; | ||
168 | wd_heartbeat = t * 100; | ||
169 | return 0; | ||
170 | } | ||
171 | |||
172 | /** | ||
173 | * wdt_get_status: | ||
174 | * @status: the new status. | ||
175 | * | ||
176 | * Extract the status information from a WDT watchdog device. There are | ||
177 | * several board variants so we have to know which bits are valid. Some | ||
178 | * bits default to one and some to zero in order to be maximally painful. | ||
179 | * | ||
180 | * we then map the bits onto the status ioctl flags. | ||
181 | */ | ||
182 | |||
183 | static int wdt_get_status(int *status) | ||
184 | { | ||
185 | unsigned char new_status=inb_p(WDT_SR); | ||
186 | |||
187 | *status=0; | ||
188 | if (new_status & WDC_SR_ISOI0) | ||
189 | *status |= WDIOF_EXTERN1; | ||
190 | if (new_status & WDC_SR_ISII1) | ||
191 | *status |= WDIOF_EXTERN2; | ||
192 | #ifdef CONFIG_WDT_501 | ||
193 | if (!(new_status & WDC_SR_TGOOD)) | ||
194 | *status |= WDIOF_OVERHEAT; | ||
195 | if (!(new_status & WDC_SR_PSUOVER)) | ||
196 | *status |= WDIOF_POWEROVER; | ||
197 | if (!(new_status & WDC_SR_PSUUNDR)) | ||
198 | *status |= WDIOF_POWERUNDER; | ||
199 | if (tachometer) { | ||
200 | if (!(new_status & WDC_SR_FANGOOD)) | ||
201 | *status |= WDIOF_FANFAULT; | ||
202 | } | ||
203 | #endif /* CONFIG_WDT_501 */ | ||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | #ifdef CONFIG_WDT_501 | ||
208 | /** | ||
209 | * wdt_get_temperature: | ||
210 | * | ||
211 | * Reports the temperature in degrees Fahrenheit. The API is in | ||
212 | * farenheit. It was designed by an imperial measurement luddite. | ||
213 | */ | ||
214 | |||
215 | static int wdt_get_temperature(int *temperature) | ||
216 | { | ||
217 | unsigned short c=inb_p(WDT_RT); | ||
218 | |||
219 | *temperature = (c * 11 / 15) + 7; | ||
220 | return 0; | ||
221 | } | ||
222 | #endif /* CONFIG_WDT_501 */ | ||
223 | |||
224 | /** | ||
225 | * wdt_interrupt: | ||
226 | * @irq: Interrupt number | ||
227 | * @dev_id: Unused as we don't allow multiple devices. | ||
228 | * | ||
229 | * Handle an interrupt from the board. These are raised when the status | ||
230 | * map changes in what the board considers an interesting way. That means | ||
231 | * a failure condition occurring. | ||
232 | */ | ||
233 | |||
234 | static irqreturn_t wdt_interrupt(int irq, void *dev_id) | ||
235 | { | ||
236 | /* | ||
237 | * Read the status register see what is up and | ||
238 | * then printk it. | ||
239 | */ | ||
240 | unsigned char status=inb_p(WDT_SR); | ||
241 | |||
242 | printk(KERN_CRIT "WDT status %d\n", status); | ||
243 | |||
244 | #ifdef CONFIG_WDT_501 | ||
245 | if (!(status & WDC_SR_TGOOD)) | ||
246 | printk(KERN_CRIT "Overheat alarm.(%d)\n",inb_p(WDT_RT)); | ||
247 | if (!(status & WDC_SR_PSUOVER)) | ||
248 | printk(KERN_CRIT "PSU over voltage.\n"); | ||
249 | if (!(status & WDC_SR_PSUUNDR)) | ||
250 | printk(KERN_CRIT "PSU under voltage.\n"); | ||
251 | if (tachometer) { | ||
252 | if (!(status & WDC_SR_FANGOOD)) | ||
253 | printk(KERN_CRIT "Possible fan fault.\n"); | ||
254 | } | ||
255 | #endif /* CONFIG_WDT_501 */ | ||
256 | if (!(status & WDC_SR_WCCR)) | ||
257 | #ifdef SOFTWARE_REBOOT | ||
258 | #ifdef ONLY_TESTING | ||
259 | printk(KERN_CRIT "Would Reboot.\n"); | ||
260 | #else | ||
261 | printk(KERN_CRIT "Initiating system reboot.\n"); | ||
262 | emergency_restart(); | ||
263 | #endif | ||
264 | #else | ||
265 | printk(KERN_CRIT "Reset in 5ms.\n"); | ||
266 | #endif | ||
267 | return IRQ_HANDLED; | ||
268 | } | ||
269 | |||
270 | |||
271 | /** | ||
272 | * wdt_write: | ||
273 | * @file: file handle to the watchdog | ||
274 | * @buf: buffer to write (unused as data does not matter here | ||
275 | * @count: count of bytes | ||
276 | * @ppos: pointer to the position to write. No seeks allowed | ||
277 | * | ||
278 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
279 | * write of data will do, as we we don't define content meaning. | ||
280 | */ | ||
281 | |||
282 | static ssize_t wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
283 | { | ||
284 | if(count) { | ||
285 | if (!nowayout) { | ||
286 | size_t i; | ||
287 | |||
288 | /* In case it was set long ago */ | ||
289 | expect_close = 0; | ||
290 | |||
291 | for (i = 0; i != count; i++) { | ||
292 | char c; | ||
293 | if (get_user(c, buf + i)) | ||
294 | return -EFAULT; | ||
295 | if (c == 'V') | ||
296 | expect_close = 42; | ||
297 | } | ||
298 | } | ||
299 | wdt_ping(); | ||
300 | } | ||
301 | return count; | ||
302 | } | ||
303 | |||
304 | /** | ||
305 | * wdt_ioctl: | ||
306 | * @inode: inode of the device | ||
307 | * @file: file handle to the device | ||
308 | * @cmd: watchdog command | ||
309 | * @arg: argument pointer | ||
310 | * | ||
311 | * The watchdog API defines a common set of functions for all watchdogs | ||
312 | * according to their available features. We only actually usefully support | ||
313 | * querying capabilities and current status. | ||
314 | */ | ||
315 | |||
316 | static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
317 | unsigned long arg) | ||
318 | { | ||
319 | void __user *argp = (void __user *)arg; | ||
320 | int __user *p = argp; | ||
321 | int new_heartbeat; | ||
322 | int status; | ||
323 | |||
324 | static struct watchdog_info ident = { | ||
325 | .options = WDIOF_SETTIMEOUT| | ||
326 | WDIOF_MAGICCLOSE| | ||
327 | WDIOF_KEEPALIVEPING, | ||
328 | .firmware_version = 1, | ||
329 | .identity = "WDT500/501", | ||
330 | }; | ||
331 | |||
332 | /* Add options according to the card we have */ | ||
333 | ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2); | ||
334 | #ifdef CONFIG_WDT_501 | ||
335 | ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER); | ||
336 | if (tachometer) | ||
337 | ident.options |= WDIOF_FANFAULT; | ||
338 | #endif /* CONFIG_WDT_501 */ | ||
339 | |||
340 | switch(cmd) | ||
341 | { | ||
342 | default: | ||
343 | return -ENOTTY; | ||
344 | case WDIOC_GETSUPPORT: | ||
345 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
346 | |||
347 | case WDIOC_GETSTATUS: | ||
348 | wdt_get_status(&status); | ||
349 | return put_user(status, p); | ||
350 | case WDIOC_GETBOOTSTATUS: | ||
351 | return put_user(0, p); | ||
352 | case WDIOC_KEEPALIVE: | ||
353 | wdt_ping(); | ||
354 | return 0; | ||
355 | case WDIOC_SETTIMEOUT: | ||
356 | if (get_user(new_heartbeat, p)) | ||
357 | return -EFAULT; | ||
358 | |||
359 | if (wdt_set_heartbeat(new_heartbeat)) | ||
360 | return -EINVAL; | ||
361 | |||
362 | wdt_ping(); | ||
363 | /* Fall */ | ||
364 | case WDIOC_GETTIMEOUT: | ||
365 | return put_user(heartbeat, p); | ||
366 | } | ||
367 | } | ||
368 | |||
369 | /** | ||
370 | * wdt_open: | ||
371 | * @inode: inode of device | ||
372 | * @file: file handle to device | ||
373 | * | ||
374 | * The watchdog device has been opened. The watchdog device is single | ||
375 | * open and on opening we load the counters. Counter zero is a 100Hz | ||
376 | * cascade, into counter 1 which downcounts to reboot. When the counter | ||
377 | * triggers counter 2 downcounts the length of the reset pulse which | ||
378 | * set set to be as long as possible. | ||
379 | */ | ||
380 | |||
381 | static int wdt_open(struct inode *inode, struct file *file) | ||
382 | { | ||
383 | if(test_and_set_bit(0, &wdt_is_open)) | ||
384 | return -EBUSY; | ||
385 | /* | ||
386 | * Activate | ||
387 | */ | ||
388 | wdt_start(); | ||
389 | return nonseekable_open(inode, file); | ||
390 | } | ||
391 | |||
392 | /** | ||
393 | * wdt_release: | ||
394 | * @inode: inode to board | ||
395 | * @file: file handle to board | ||
396 | * | ||
397 | * The watchdog has a configurable API. There is a religious dispute | ||
398 | * between people who want their watchdog to be able to shut down and | ||
399 | * those who want to be sure if the watchdog manager dies the machine | ||
400 | * reboots. In the former case we disable the counters, in the latter | ||
401 | * case you have to open it again very soon. | ||
402 | */ | ||
403 | |||
404 | static int wdt_release(struct inode *inode, struct file *file) | ||
405 | { | ||
406 | if (expect_close == 42) { | ||
407 | wdt_stop(); | ||
408 | clear_bit(0, &wdt_is_open); | ||
409 | } else { | ||
410 | printk(KERN_CRIT "wdt: WDT device closed unexpectedly. WDT will not stop!\n"); | ||
411 | wdt_ping(); | ||
412 | } | ||
413 | expect_close = 0; | ||
414 | return 0; | ||
415 | } | ||
416 | |||
417 | #ifdef CONFIG_WDT_501 | ||
418 | /** | ||
419 | * wdt_temp_read: | ||
420 | * @file: file handle to the watchdog board | ||
421 | * @buf: buffer to write 1 byte into | ||
422 | * @count: length of buffer | ||
423 | * @ptr: offset (no seek allowed) | ||
424 | * | ||
425 | * Temp_read reports the temperature in degrees Fahrenheit. The API is in | ||
426 | * farenheit. It was designed by an imperial measurement luddite. | ||
427 | */ | ||
428 | |||
429 | static ssize_t wdt_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ptr) | ||
430 | { | ||
431 | int temperature; | ||
432 | |||
433 | if (wdt_get_temperature(&temperature)) | ||
434 | return -EFAULT; | ||
435 | |||
436 | if (copy_to_user (buf, &temperature, 1)) | ||
437 | return -EFAULT; | ||
438 | |||
439 | return 1; | ||
440 | } | ||
441 | |||
442 | /** | ||
443 | * wdt_temp_open: | ||
444 | * @inode: inode of device | ||
445 | * @file: file handle to device | ||
446 | * | ||
447 | * The temperature device has been opened. | ||
448 | */ | ||
449 | |||
450 | static int wdt_temp_open(struct inode *inode, struct file *file) | ||
451 | { | ||
452 | return nonseekable_open(inode, file); | ||
453 | } | ||
454 | |||
455 | /** | ||
456 | * wdt_temp_release: | ||
457 | * @inode: inode to board | ||
458 | * @file: file handle to board | ||
459 | * | ||
460 | * The temperature device has been closed. | ||
461 | */ | ||
462 | |||
463 | static int wdt_temp_release(struct inode *inode, struct file *file) | ||
464 | { | ||
465 | return 0; | ||
466 | } | ||
467 | #endif /* CONFIG_WDT_501 */ | ||
468 | |||
469 | /** | ||
470 | * notify_sys: | ||
471 | * @this: our notifier block | ||
472 | * @code: the event being reported | ||
473 | * @unused: unused | ||
474 | * | ||
475 | * Our notifier is called on system shutdowns. We want to turn the card | ||
476 | * off at reboot otherwise the machine will reboot again during memory | ||
477 | * test or worse yet during the following fsck. This would suck, in fact | ||
478 | * trust me - if it happens it does suck. | ||
479 | */ | ||
480 | |||
481 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
482 | void *unused) | ||
483 | { | ||
484 | if(code==SYS_DOWN || code==SYS_HALT) { | ||
485 | /* Turn the card off */ | ||
486 | wdt_stop(); | ||
487 | } | ||
488 | return NOTIFY_DONE; | ||
489 | } | ||
490 | |||
491 | /* | ||
492 | * Kernel Interfaces | ||
493 | */ | ||
494 | |||
495 | |||
496 | static const struct file_operations wdt_fops = { | ||
497 | .owner = THIS_MODULE, | ||
498 | .llseek = no_llseek, | ||
499 | .write = wdt_write, | ||
500 | .ioctl = wdt_ioctl, | ||
501 | .open = wdt_open, | ||
502 | .release = wdt_release, | ||
503 | }; | ||
504 | |||
505 | static struct miscdevice wdt_miscdev = { | ||
506 | .minor = WATCHDOG_MINOR, | ||
507 | .name = "watchdog", | ||
508 | .fops = &wdt_fops, | ||
509 | }; | ||
510 | |||
511 | #ifdef CONFIG_WDT_501 | ||
512 | static const struct file_operations wdt_temp_fops = { | ||
513 | .owner = THIS_MODULE, | ||
514 | .llseek = no_llseek, | ||
515 | .read = wdt_temp_read, | ||
516 | .open = wdt_temp_open, | ||
517 | .release = wdt_temp_release, | ||
518 | }; | ||
519 | |||
520 | static struct miscdevice temp_miscdev = { | ||
521 | .minor = TEMP_MINOR, | ||
522 | .name = "temperature", | ||
523 | .fops = &wdt_temp_fops, | ||
524 | }; | ||
525 | #endif /* CONFIG_WDT_501 */ | ||
526 | |||
527 | /* | ||
528 | * The WDT card needs to learn about soft shutdowns in order to | ||
529 | * turn the timebomb registers off. | ||
530 | */ | ||
531 | |||
532 | static struct notifier_block wdt_notifier = { | ||
533 | .notifier_call = wdt_notify_sys, | ||
534 | }; | ||
535 | |||
536 | /** | ||
537 | * cleanup_module: | ||
538 | * | ||
539 | * Unload the watchdog. You cannot do this with any file handles open. | ||
540 | * If your watchdog is set to continue ticking on close and you unload | ||
541 | * it, well it keeps ticking. We won't get the interrupt but the board | ||
542 | * will not touch PC memory so all is fine. You just have to load a new | ||
543 | * module in 60 seconds or reboot. | ||
544 | */ | ||
545 | |||
546 | static void __exit wdt_exit(void) | ||
547 | { | ||
548 | misc_deregister(&wdt_miscdev); | ||
549 | #ifdef CONFIG_WDT_501 | ||
550 | misc_deregister(&temp_miscdev); | ||
551 | #endif /* CONFIG_WDT_501 */ | ||
552 | unregister_reboot_notifier(&wdt_notifier); | ||
553 | free_irq(irq, NULL); | ||
554 | release_region(io,8); | ||
555 | } | ||
556 | |||
557 | /** | ||
558 | * wdt_init: | ||
559 | * | ||
560 | * Set up the WDT watchdog board. All we have to do is grab the | ||
561 | * resources we require and bitch if anyone beat us to them. | ||
562 | * The open() function will actually kick the board off. | ||
563 | */ | ||
564 | |||
565 | static int __init wdt_init(void) | ||
566 | { | ||
567 | int ret; | ||
568 | |||
569 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
570 | if (wdt_set_heartbeat(heartbeat)) { | ||
571 | wdt_set_heartbeat(WD_TIMO); | ||
572 | printk(KERN_INFO "wdt: heartbeat value must be 0<heartbeat<65536, using %d\n", | ||
573 | WD_TIMO); | ||
574 | } | ||
575 | |||
576 | if (!request_region(io, 8, "wdt501p")) { | ||
577 | printk(KERN_ERR "wdt: I/O address 0x%04x already in use\n", io); | ||
578 | ret = -EBUSY; | ||
579 | goto out; | ||
580 | } | ||
581 | |||
582 | ret = request_irq(irq, wdt_interrupt, IRQF_DISABLED, "wdt501p", NULL); | ||
583 | if(ret) { | ||
584 | printk(KERN_ERR "wdt: IRQ %d is not free.\n", irq); | ||
585 | goto outreg; | ||
586 | } | ||
587 | |||
588 | ret = register_reboot_notifier(&wdt_notifier); | ||
589 | if(ret) { | ||
590 | printk(KERN_ERR "wdt: cannot register reboot notifier (err=%d)\n", ret); | ||
591 | goto outirq; | ||
592 | } | ||
593 | |||
594 | #ifdef CONFIG_WDT_501 | ||
595 | ret = misc_register(&temp_miscdev); | ||
596 | if (ret) { | ||
597 | printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n", | ||
598 | TEMP_MINOR, ret); | ||
599 | goto outrbt; | ||
600 | } | ||
601 | #endif /* CONFIG_WDT_501 */ | ||
602 | |||
603 | ret = misc_register(&wdt_miscdev); | ||
604 | if (ret) { | ||
605 | printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n", | ||
606 | WATCHDOG_MINOR, ret); | ||
607 | goto outmisc; | ||
608 | } | ||
609 | |||
610 | ret = 0; | ||
611 | printk(KERN_INFO "WDT500/501-P driver 0.10 at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)\n", | ||
612 | io, irq, heartbeat, nowayout); | ||
613 | #ifdef CONFIG_WDT_501 | ||
614 | printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled")); | ||
615 | #endif /* CONFIG_WDT_501 */ | ||
616 | |||
617 | out: | ||
618 | return ret; | ||
619 | |||
620 | outmisc: | ||
621 | #ifdef CONFIG_WDT_501 | ||
622 | misc_deregister(&temp_miscdev); | ||
623 | outrbt: | ||
624 | #endif /* CONFIG_WDT_501 */ | ||
625 | unregister_reboot_notifier(&wdt_notifier); | ||
626 | outirq: | ||
627 | free_irq(irq, NULL); | ||
628 | outreg: | ||
629 | release_region(io,8); | ||
630 | goto out; | ||
631 | } | ||
632 | |||
633 | module_init(wdt_init); | ||
634 | module_exit(wdt_exit); | ||
635 | |||
636 | MODULE_AUTHOR("Alan Cox"); | ||
637 | MODULE_DESCRIPTION("Driver for ISA ICS watchdog cards (WDT500/501)"); | ||
638 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
639 | MODULE_ALIAS_MISCDEV(TEMP_MINOR); | ||
640 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/watchdog/wdt285.c b/drivers/watchdog/wdt285.c new file mode 100644 index 000000000000..e4cf661dc890 --- /dev/null +++ b/drivers/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) | ||
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 = -ENOTTY; | ||
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 const 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/watchdog/wdt977.c b/drivers/watchdog/wdt977.c new file mode 100644 index 000000000000..7d300ff7ab07 --- /dev/null +++ b/drivers/watchdog/wdt977.c | |||
@@ -0,0 +1,519 @@ | |||
1 | /* | ||
2 | * Wdt977 0.04: 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 | * 25-Oct-2005 Woody Suwalski: Convert addresses to #defs, add spinlocks | ||
22 | * remove limitiation to be used on Netwinders only | ||
23 | */ | ||
24 | |||
25 | #include <linux/module.h> | ||
26 | #include <linux/moduleparam.h> | ||
27 | #include <linux/types.h> | ||
28 | #include <linux/kernel.h> | ||
29 | #include <linux/fs.h> | ||
30 | #include <linux/miscdevice.h> | ||
31 | #include <linux/init.h> | ||
32 | #include <linux/ioport.h> | ||
33 | #include <linux/watchdog.h> | ||
34 | #include <linux/notifier.h> | ||
35 | #include <linux/reboot.h> | ||
36 | |||
37 | #include <asm/io.h> | ||
38 | #include <asm/system.h> | ||
39 | #include <asm/mach-types.h> | ||
40 | #include <asm/uaccess.h> | ||
41 | |||
42 | #define WATCHDOG_VERSION "0.04" | ||
43 | #define WATCHDOG_NAME "Wdt977" | ||
44 | #define PFX WATCHDOG_NAME ": " | ||
45 | #define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n" | ||
46 | |||
47 | #define IO_INDEX_PORT 0x370 /* on some systems it can be 0x3F0 */ | ||
48 | #define IO_DATA_PORT (IO_INDEX_PORT+1) | ||
49 | |||
50 | #define UNLOCK_DATA 0x87 | ||
51 | #define LOCK_DATA 0xAA | ||
52 | #define DEVICE_REGISTER 0x07 | ||
53 | |||
54 | |||
55 | #define DEFAULT_TIMEOUT 60 /* default timeout in seconds */ | ||
56 | |||
57 | static int timeout = DEFAULT_TIMEOUT; | ||
58 | static int timeoutM; /* timeout in minutes */ | ||
59 | static unsigned long timer_alive; | ||
60 | static int testmode; | ||
61 | static char expect_close; | ||
62 | static spinlock_t spinlock; | ||
63 | |||
64 | module_param(timeout, int, 0); | ||
65 | MODULE_PARM_DESC(timeout,"Watchdog timeout in seconds (60..15300), default=" __MODULE_STRING(DEFAULT_TIMEOUT) ")"); | ||
66 | module_param(testmode, int, 0); | ||
67 | MODULE_PARM_DESC(testmode,"Watchdog testmode (1 = no reboot), default=0"); | ||
68 | |||
69 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
70 | module_param(nowayout, int, 0); | ||
71 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
72 | |||
73 | /* | ||
74 | * Start the watchdog | ||
75 | */ | ||
76 | |||
77 | static int wdt977_start(void) | ||
78 | { | ||
79 | unsigned long flags; | ||
80 | |||
81 | spin_lock_irqsave(&spinlock, flags); | ||
82 | |||
83 | /* unlock the SuperIO chip */ | ||
84 | outb_p(UNLOCK_DATA, IO_INDEX_PORT); | ||
85 | outb_p(UNLOCK_DATA, IO_INDEX_PORT); | ||
86 | |||
87 | /* select device Aux2 (device=8) and set watchdog regs F2, F3 and F4 | ||
88 | * F2 has the timeout in minutes | ||
89 | * F3 could be set to the POWER LED blink (with GP17 set to PowerLed) | ||
90 | * at timeout, and to reset timer on kbd/mouse activity (not impl.) | ||
91 | * F4 is used to just clear the TIMEOUT'ed state (bit 0) | ||
92 | */ | ||
93 | outb_p(DEVICE_REGISTER, IO_INDEX_PORT); | ||
94 | outb_p(0x08, IO_DATA_PORT); | ||
95 | outb_p(0xF2, IO_INDEX_PORT); | ||
96 | outb_p(timeoutM, IO_DATA_PORT); | ||
97 | outb_p(0xF3, IO_INDEX_PORT); | ||
98 | outb_p(0x00, IO_DATA_PORT); /* another setting is 0E for kbd/mouse/LED */ | ||
99 | outb_p(0xF4, IO_INDEX_PORT); | ||
100 | outb_p(0x00, IO_DATA_PORT); | ||
101 | |||
102 | /* at last select device Aux1 (dev=7) and set GP16 as a watchdog output */ | ||
103 | /* in test mode watch the bit 1 on F4 to indicate "triggered" */ | ||
104 | if (!testmode) | ||
105 | { | ||
106 | outb_p(DEVICE_REGISTER, IO_INDEX_PORT); | ||
107 | outb_p(0x07, IO_DATA_PORT); | ||
108 | outb_p(0xE6, IO_INDEX_PORT); | ||
109 | outb_p(0x08, IO_DATA_PORT); | ||
110 | } | ||
111 | |||
112 | /* lock the SuperIO chip */ | ||
113 | outb_p(LOCK_DATA, IO_INDEX_PORT); | ||
114 | |||
115 | spin_unlock_irqrestore(&spinlock, flags); | ||
116 | printk(KERN_INFO PFX "activated.\n"); | ||
117 | |||
118 | return 0; | ||
119 | } | ||
120 | |||
121 | /* | ||
122 | * Stop the watchdog | ||
123 | */ | ||
124 | |||
125 | static int wdt977_stop(void) | ||
126 | { | ||
127 | unsigned long flags; | ||
128 | spin_lock_irqsave(&spinlock, flags); | ||
129 | |||
130 | /* unlock the SuperIO chip */ | ||
131 | outb_p(UNLOCK_DATA, IO_INDEX_PORT); | ||
132 | outb_p(UNLOCK_DATA, IO_INDEX_PORT); | ||
133 | |||
134 | /* select device Aux2 (device=8) and set watchdog regs F2,F3 and F4 | ||
135 | * F3 is reset to its default state | ||
136 | * F4 can clear the TIMEOUT'ed state (bit 0) - back to default | ||
137 | * We can not use GP17 as a PowerLed, as we use its usage as a RedLed | ||
138 | */ | ||
139 | outb_p(DEVICE_REGISTER, IO_INDEX_PORT); | ||
140 | outb_p(0x08, IO_DATA_PORT); | ||
141 | outb_p(0xF2, IO_INDEX_PORT); | ||
142 | outb_p(0xFF, IO_DATA_PORT); | ||
143 | outb_p(0xF3, IO_INDEX_PORT); | ||
144 | outb_p(0x00, IO_DATA_PORT); | ||
145 | outb_p(0xF4, IO_INDEX_PORT); | ||
146 | outb_p(0x00, IO_DATA_PORT); | ||
147 | outb_p(0xF2, IO_INDEX_PORT); | ||
148 | outb_p(0x00, IO_DATA_PORT); | ||
149 | |||
150 | /* at last select device Aux1 (dev=7) and set GP16 as a watchdog output */ | ||
151 | outb_p(DEVICE_REGISTER, IO_INDEX_PORT); | ||
152 | outb_p(0x07, IO_DATA_PORT); | ||
153 | outb_p(0xE6, IO_INDEX_PORT); | ||
154 | outb_p(0x08, IO_DATA_PORT); | ||
155 | |||
156 | /* lock the SuperIO chip */ | ||
157 | outb_p(LOCK_DATA, IO_INDEX_PORT); | ||
158 | |||
159 | spin_unlock_irqrestore(&spinlock, flags); | ||
160 | printk(KERN_INFO PFX "shutdown.\n"); | ||
161 | |||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | /* | ||
166 | * Send a keepalive ping to the watchdog | ||
167 | * This is done by simply re-writing the timeout to reg. 0xF2 | ||
168 | */ | ||
169 | |||
170 | static int wdt977_keepalive(void) | ||
171 | { | ||
172 | unsigned long flags; | ||
173 | spin_lock_irqsave(&spinlock, flags); | ||
174 | |||
175 | /* unlock the SuperIO chip */ | ||
176 | outb_p(UNLOCK_DATA, IO_INDEX_PORT); | ||
177 | outb_p(UNLOCK_DATA, IO_INDEX_PORT); | ||
178 | |||
179 | /* select device Aux2 (device=8) and kicks watchdog reg F2 */ | ||
180 | /* F2 has the timeout in minutes */ | ||
181 | outb_p(DEVICE_REGISTER, IO_INDEX_PORT); | ||
182 | outb_p(0x08, IO_DATA_PORT); | ||
183 | outb_p(0xF2, IO_INDEX_PORT); | ||
184 | outb_p(timeoutM, IO_DATA_PORT); | ||
185 | |||
186 | /* lock the SuperIO chip */ | ||
187 | outb_p(LOCK_DATA, IO_INDEX_PORT); | ||
188 | spin_unlock_irqrestore(&spinlock, flags); | ||
189 | |||
190 | return 0; | ||
191 | } | ||
192 | |||
193 | /* | ||
194 | * Set the watchdog timeout value | ||
195 | */ | ||
196 | |||
197 | static int wdt977_set_timeout(int t) | ||
198 | { | ||
199 | int tmrval; | ||
200 | |||
201 | /* convert seconds to minutes, rounding up */ | ||
202 | tmrval = (t + 59) / 60; | ||
203 | |||
204 | if (machine_is_netwinder()) { | ||
205 | /* we have a hw bug somewhere, so each 977 minute is actually only 30sec | ||
206 | * this limits the max timeout to half of device max of 255 minutes... | ||
207 | */ | ||
208 | tmrval += tmrval; | ||
209 | } | ||
210 | |||
211 | if ((tmrval < 1) || (tmrval > 255)) | ||
212 | return -EINVAL; | ||
213 | |||
214 | /* timeout is the timeout in seconds, timeoutM is the timeout in minutes) */ | ||
215 | timeout = t; | ||
216 | timeoutM = tmrval; | ||
217 | return 0; | ||
218 | } | ||
219 | |||
220 | /* | ||
221 | * Get the watchdog status | ||
222 | */ | ||
223 | |||
224 | static int wdt977_get_status(int *status) | ||
225 | { | ||
226 | int new_status; | ||
227 | unsigned long flags; | ||
228 | |||
229 | spin_lock_irqsave(&spinlock, flags); | ||
230 | |||
231 | /* unlock the SuperIO chip */ | ||
232 | outb_p(UNLOCK_DATA, IO_INDEX_PORT); | ||
233 | outb_p(UNLOCK_DATA, IO_INDEX_PORT); | ||
234 | |||
235 | /* select device Aux2 (device=8) and read watchdog reg F4 */ | ||
236 | outb_p(DEVICE_REGISTER, IO_INDEX_PORT); | ||
237 | outb_p(0x08, IO_DATA_PORT); | ||
238 | outb_p(0xF4, IO_INDEX_PORT); | ||
239 | new_status = inb_p(IO_DATA_PORT); | ||
240 | |||
241 | /* lock the SuperIO chip */ | ||
242 | outb_p(LOCK_DATA, IO_INDEX_PORT); | ||
243 | |||
244 | spin_unlock_irqrestore(&spinlock, flags); | ||
245 | |||
246 | *status=0; | ||
247 | if (new_status & 1) | ||
248 | *status |= WDIOF_CARDRESET; | ||
249 | |||
250 | return 0; | ||
251 | } | ||
252 | |||
253 | |||
254 | /* | ||
255 | * /dev/watchdog handling | ||
256 | */ | ||
257 | |||
258 | static int wdt977_open(struct inode *inode, struct file *file) | ||
259 | { | ||
260 | /* If the watchdog is alive we don't need to start it again */ | ||
261 | if( test_and_set_bit(0,&timer_alive) ) | ||
262 | return -EBUSY; | ||
263 | |||
264 | if (nowayout) | ||
265 | __module_get(THIS_MODULE); | ||
266 | |||
267 | wdt977_start(); | ||
268 | return nonseekable_open(inode, file); | ||
269 | } | ||
270 | |||
271 | static int wdt977_release(struct inode *inode, struct file *file) | ||
272 | { | ||
273 | /* | ||
274 | * Shut off the timer. | ||
275 | * Lock it in if it's a module and we set nowayout | ||
276 | */ | ||
277 | if (expect_close == 42) | ||
278 | { | ||
279 | wdt977_stop(); | ||
280 | clear_bit(0,&timer_alive); | ||
281 | } else { | ||
282 | wdt977_keepalive(); | ||
283 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
284 | } | ||
285 | expect_close = 0; | ||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | |||
290 | /* | ||
291 | * wdt977_write: | ||
292 | * @file: file handle to the watchdog | ||
293 | * @buf: buffer to write (unused as data does not matter here | ||
294 | * @count: count of bytes | ||
295 | * @ppos: pointer to the position to write. No seeks allowed | ||
296 | * | ||
297 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
298 | * write of data will do, as we we don't define content meaning. | ||
299 | */ | ||
300 | |||
301 | static ssize_t wdt977_write(struct file *file, const char __user *buf, | ||
302 | size_t count, loff_t *ppos) | ||
303 | { | ||
304 | if (count) | ||
305 | { | ||
306 | if (!nowayout) | ||
307 | { | ||
308 | size_t i; | ||
309 | |||
310 | /* In case it was set long ago */ | ||
311 | expect_close = 0; | ||
312 | |||
313 | for (i = 0; i != count; i++) | ||
314 | { | ||
315 | char c; | ||
316 | if (get_user(c, buf + i)) | ||
317 | return -EFAULT; | ||
318 | if (c == 'V') | ||
319 | expect_close = 42; | ||
320 | } | ||
321 | } | ||
322 | |||
323 | /* someone wrote to us, we should restart timer */ | ||
324 | wdt977_keepalive(); | ||
325 | } | ||
326 | return count; | ||
327 | } | ||
328 | |||
329 | /* | ||
330 | * wdt977_ioctl: | ||
331 | * @inode: inode of the device | ||
332 | * @file: file handle to the device | ||
333 | * @cmd: watchdog command | ||
334 | * @arg: argument pointer | ||
335 | * | ||
336 | * The watchdog API defines a common set of functions for all watchdogs | ||
337 | * according to their available features. | ||
338 | */ | ||
339 | |||
340 | static struct watchdog_info ident = { | ||
341 | .options = WDIOF_SETTIMEOUT | | ||
342 | WDIOF_MAGICCLOSE | | ||
343 | WDIOF_KEEPALIVEPING, | ||
344 | .firmware_version = 1, | ||
345 | .identity = WATCHDOG_NAME, | ||
346 | }; | ||
347 | |||
348 | static int wdt977_ioctl(struct inode *inode, struct file *file, | ||
349 | unsigned int cmd, unsigned long arg) | ||
350 | { | ||
351 | int status; | ||
352 | int new_options, retval = -EINVAL; | ||
353 | int new_timeout; | ||
354 | union { | ||
355 | struct watchdog_info __user *ident; | ||
356 | int __user *i; | ||
357 | } uarg; | ||
358 | |||
359 | uarg.i = (int __user *)arg; | ||
360 | |||
361 | switch(cmd) | ||
362 | { | ||
363 | default: | ||
364 | return -ENOTTY; | ||
365 | |||
366 | case WDIOC_GETSUPPORT: | ||
367 | return copy_to_user(uarg.ident, &ident, | ||
368 | sizeof(ident)) ? -EFAULT : 0; | ||
369 | |||
370 | case WDIOC_GETSTATUS: | ||
371 | wdt977_get_status(&status); | ||
372 | return put_user(status, uarg.i); | ||
373 | |||
374 | case WDIOC_GETBOOTSTATUS: | ||
375 | return put_user(0, uarg.i); | ||
376 | |||
377 | case WDIOC_KEEPALIVE: | ||
378 | wdt977_keepalive(); | ||
379 | return 0; | ||
380 | |||
381 | case WDIOC_SETOPTIONS: | ||
382 | if (get_user (new_options, uarg.i)) | ||
383 | return -EFAULT; | ||
384 | |||
385 | if (new_options & WDIOS_DISABLECARD) { | ||
386 | wdt977_stop(); | ||
387 | retval = 0; | ||
388 | } | ||
389 | |||
390 | if (new_options & WDIOS_ENABLECARD) { | ||
391 | wdt977_start(); | ||
392 | retval = 0; | ||
393 | } | ||
394 | |||
395 | return retval; | ||
396 | |||
397 | case WDIOC_SETTIMEOUT: | ||
398 | if (get_user(new_timeout, uarg.i)) | ||
399 | return -EFAULT; | ||
400 | |||
401 | if (wdt977_set_timeout(new_timeout)) | ||
402 | return -EINVAL; | ||
403 | |||
404 | wdt977_keepalive(); | ||
405 | /* Fall */ | ||
406 | |||
407 | case WDIOC_GETTIMEOUT: | ||
408 | return put_user(timeout, uarg.i); | ||
409 | |||
410 | } | ||
411 | } | ||
412 | |||
413 | static int wdt977_notify_sys(struct notifier_block *this, unsigned long code, | ||
414 | void *unused) | ||
415 | { | ||
416 | if(code==SYS_DOWN || code==SYS_HALT) | ||
417 | wdt977_stop(); | ||
418 | return NOTIFY_DONE; | ||
419 | } | ||
420 | |||
421 | static const struct file_operations wdt977_fops= | ||
422 | { | ||
423 | .owner = THIS_MODULE, | ||
424 | .llseek = no_llseek, | ||
425 | .write = wdt977_write, | ||
426 | .ioctl = wdt977_ioctl, | ||
427 | .open = wdt977_open, | ||
428 | .release = wdt977_release, | ||
429 | }; | ||
430 | |||
431 | static struct miscdevice wdt977_miscdev= | ||
432 | { | ||
433 | .minor = WATCHDOG_MINOR, | ||
434 | .name = "watchdog", | ||
435 | .fops = &wdt977_fops, | ||
436 | }; | ||
437 | |||
438 | static struct notifier_block wdt977_notifier = { | ||
439 | .notifier_call = wdt977_notify_sys, | ||
440 | }; | ||
441 | |||
442 | static int __init wd977_init(void) | ||
443 | { | ||
444 | int rc; | ||
445 | |||
446 | //if (!machine_is_netwinder()) | ||
447 | // return -ENODEV; | ||
448 | |||
449 | printk(KERN_INFO PFX DRIVER_VERSION); | ||
450 | |||
451 | spin_lock_init(&spinlock); | ||
452 | |||
453 | /* Check that the timeout value is within it's range ; if not reset to the default */ | ||
454 | if (wdt977_set_timeout(timeout)) | ||
455 | { | ||
456 | wdt977_set_timeout(DEFAULT_TIMEOUT); | ||
457 | printk(KERN_INFO PFX "timeout value must be 60<timeout<15300, using %d\n", | ||
458 | DEFAULT_TIMEOUT); | ||
459 | } | ||
460 | |||
461 | /* on Netwinder the IOports are already reserved by | ||
462 | * arch/arm/mach-footbridge/netwinder-hw.c | ||
463 | */ | ||
464 | if (!machine_is_netwinder()) | ||
465 | { | ||
466 | if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME)) | ||
467 | { | ||
468 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
469 | IO_INDEX_PORT); | ||
470 | rc = -EIO; | ||
471 | goto err_out; | ||
472 | } | ||
473 | } | ||
474 | |||
475 | rc = misc_register(&wdt977_miscdev); | ||
476 | if (rc) | ||
477 | { | ||
478 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
479 | wdt977_miscdev.minor, rc); | ||
480 | goto err_out_region; | ||
481 | } | ||
482 | |||
483 | rc = register_reboot_notifier(&wdt977_notifier); | ||
484 | if (rc) | ||
485 | { | ||
486 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", | ||
487 | rc); | ||
488 | goto err_out_miscdev; | ||
489 | } | ||
490 | |||
491 | printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d, testmode=%i)\n", | ||
492 | timeout, nowayout, testmode); | ||
493 | |||
494 | return 0; | ||
495 | |||
496 | err_out_miscdev: | ||
497 | misc_deregister(&wdt977_miscdev); | ||
498 | err_out_region: | ||
499 | if (!machine_is_netwinder()) | ||
500 | release_region(IO_INDEX_PORT,2); | ||
501 | err_out: | ||
502 | return rc; | ||
503 | } | ||
504 | |||
505 | static void __exit wd977_exit(void) | ||
506 | { | ||
507 | wdt977_stop(); | ||
508 | misc_deregister(&wdt977_miscdev); | ||
509 | unregister_reboot_notifier(&wdt977_notifier); | ||
510 | release_region(IO_INDEX_PORT,2); | ||
511 | } | ||
512 | |||
513 | module_init(wd977_init); | ||
514 | module_exit(wd977_exit); | ||
515 | |||
516 | MODULE_AUTHOR("Woody Suwalski <woodys@xandros.com>"); | ||
517 | MODULE_DESCRIPTION("W83977AF Watchdog driver"); | ||
518 | MODULE_LICENSE("GPL"); | ||
519 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/wdt_pci.c b/drivers/watchdog/wdt_pci.c new file mode 100644 index 000000000000..6baf4ae42c9d --- /dev/null +++ b/drivers/watchdog/wdt_pci.c | |||
@@ -0,0 +1,756 @@ | |||
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/interrupt.h> | ||
39 | #include <linux/module.h> | ||
40 | #include <linux/moduleparam.h> | ||
41 | #include <linux/types.h> | ||
42 | #include <linux/miscdevice.h> | ||
43 | #include <linux/watchdog.h> | ||
44 | #include <linux/ioport.h> | ||
45 | #include <linux/notifier.h> | ||
46 | #include <linux/reboot.h> | ||
47 | #include <linux/init.h> | ||
48 | #include <linux/fs.h> | ||
49 | #include <linux/pci.h> | ||
50 | |||
51 | #include <asm/io.h> | ||
52 | #include <asm/uaccess.h> | ||
53 | #include <asm/system.h> | ||
54 | |||
55 | #define WDT_IS_PCI | ||
56 | #include "wd501p.h" | ||
57 | |||
58 | #define PFX "wdt_pci: " | ||
59 | |||
60 | /* | ||
61 | * Until Access I/O gets their application for a PCI vendor ID approved, | ||
62 | * I don't think that it's appropriate to move these constants into the | ||
63 | * regular pci_ids.h file. -- JPN 2000/01/18 | ||
64 | */ | ||
65 | |||
66 | #ifndef PCI_VENDOR_ID_ACCESSIO | ||
67 | #define PCI_VENDOR_ID_ACCESSIO 0x494f | ||
68 | #endif | ||
69 | #ifndef PCI_DEVICE_ID_WDG_CSM | ||
70 | #define PCI_DEVICE_ID_WDG_CSM 0x22c0 | ||
71 | #endif | ||
72 | |||
73 | /* We can only use 1 card due to the /dev/watchdog restriction */ | ||
74 | static int dev_count; | ||
75 | |||
76 | static struct semaphore open_sem; | ||
77 | static spinlock_t wdtpci_lock; | ||
78 | static char expect_close; | ||
79 | |||
80 | static int io; | ||
81 | static int irq; | ||
82 | |||
83 | /* Default timeout */ | ||
84 | #define WD_TIMO 60 /* Default heartbeat = 60 seconds */ | ||
85 | |||
86 | static int heartbeat = WD_TIMO; | ||
87 | static int wd_heartbeat; | ||
88 | module_param(heartbeat, int, 0); | ||
89 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")"); | ||
90 | |||
91 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
92 | module_param(nowayout, int, 0); | ||
93 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
94 | |||
95 | #ifdef CONFIG_WDT_501_PCI | ||
96 | /* Support for the Fan Tachometer on the PCI-WDT501 */ | ||
97 | static int tachometer; | ||
98 | |||
99 | module_param(tachometer, int, 0); | ||
100 | MODULE_PARM_DESC(tachometer, "PCI-WDT501 Fan Tachometer support (0=disable, default=0)"); | ||
101 | #endif /* CONFIG_WDT_501_PCI */ | ||
102 | |||
103 | /* | ||
104 | * Programming support | ||
105 | */ | ||
106 | |||
107 | static void wdtpci_ctr_mode(int ctr, int mode) | ||
108 | { | ||
109 | ctr<<=6; | ||
110 | ctr|=0x30; | ||
111 | ctr|=(mode<<1); | ||
112 | outb_p(ctr, WDT_CR); | ||
113 | } | ||
114 | |||
115 | static void wdtpci_ctr_load(int ctr, int val) | ||
116 | { | ||
117 | outb_p(val&0xFF, WDT_COUNT0+ctr); | ||
118 | outb_p(val>>8, WDT_COUNT0+ctr); | ||
119 | } | ||
120 | |||
121 | /** | ||
122 | * wdtpci_start: | ||
123 | * | ||
124 | * Start the watchdog driver. | ||
125 | */ | ||
126 | |||
127 | static int wdtpci_start(void) | ||
128 | { | ||
129 | unsigned long flags; | ||
130 | |||
131 | spin_lock_irqsave(&wdtpci_lock, flags); | ||
132 | |||
133 | /* | ||
134 | * "pet" the watchdog, as Access says. | ||
135 | * This resets the clock outputs. | ||
136 | */ | ||
137 | inb_p(WDT_DC); /* Disable watchdog */ | ||
138 | wdtpci_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */ | ||
139 | outb_p(0, WDT_DC); /* Enable watchdog */ | ||
140 | |||
141 | inb_p(WDT_DC); /* Disable watchdog */ | ||
142 | outb_p(0, WDT_CLOCK); /* 2.0833MHz clock */ | ||
143 | inb_p(WDT_BUZZER); /* disable */ | ||
144 | inb_p(WDT_OPTONOTRST); /* disable */ | ||
145 | inb_p(WDT_OPTORST); /* disable */ | ||
146 | inb_p(WDT_PROGOUT); /* disable */ | ||
147 | wdtpci_ctr_mode(0,3); /* Program CTR0 for Mode 3: Square Wave Generator */ | ||
148 | wdtpci_ctr_mode(1,2); /* Program CTR1 for Mode 2: Rate Generator */ | ||
149 | wdtpci_ctr_mode(2,1); /* Program CTR2 for Mode 1: Retriggerable One-Shot */ | ||
150 | wdtpci_ctr_load(0,20833); /* count at 100Hz */ | ||
151 | wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */ | ||
152 | /* DO NOT LOAD CTR2 on PCI card! -- JPN */ | ||
153 | outb_p(0, WDT_DC); /* Enable watchdog */ | ||
154 | |||
155 | spin_unlock_irqrestore(&wdtpci_lock, flags); | ||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | /** | ||
160 | * wdtpci_stop: | ||
161 | * | ||
162 | * Stop the watchdog driver. | ||
163 | */ | ||
164 | |||
165 | static int wdtpci_stop (void) | ||
166 | { | ||
167 | unsigned long flags; | ||
168 | |||
169 | /* Turn the card off */ | ||
170 | spin_lock_irqsave(&wdtpci_lock, flags); | ||
171 | inb_p(WDT_DC); /* Disable watchdog */ | ||
172 | wdtpci_ctr_load(2,0); /* 0 length reset pulses now */ | ||
173 | spin_unlock_irqrestore(&wdtpci_lock, flags); | ||
174 | return 0; | ||
175 | } | ||
176 | |||
177 | /** | ||
178 | * wdtpci_ping: | ||
179 | * | ||
180 | * Reload counter one with the watchdog heartbeat. We don't bother reloading | ||
181 | * the cascade counter. | ||
182 | */ | ||
183 | |||
184 | static int wdtpci_ping(void) | ||
185 | { | ||
186 | unsigned long flags; | ||
187 | |||
188 | /* Write a watchdog value */ | ||
189 | spin_lock_irqsave(&wdtpci_lock, flags); | ||
190 | inb_p(WDT_DC); /* Disable watchdog */ | ||
191 | wdtpci_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */ | ||
192 | wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */ | ||
193 | outb_p(0, WDT_DC); /* Enable watchdog */ | ||
194 | spin_unlock_irqrestore(&wdtpci_lock, flags); | ||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | /** | ||
199 | * wdtpci_set_heartbeat: | ||
200 | * @t: the new heartbeat value that needs to be set. | ||
201 | * | ||
202 | * Set a new heartbeat value for the watchdog device. If the heartbeat value is | ||
203 | * incorrect we keep the old value and return -EINVAL. If successfull we | ||
204 | * return 0. | ||
205 | */ | ||
206 | static int wdtpci_set_heartbeat(int t) | ||
207 | { | ||
208 | /* Arbitrary, can't find the card's limits */ | ||
209 | if ((t < 1) || (t > 65535)) | ||
210 | return -EINVAL; | ||
211 | |||
212 | heartbeat = t; | ||
213 | wd_heartbeat = t * 100; | ||
214 | return 0; | ||
215 | } | ||
216 | |||
217 | /** | ||
218 | * wdtpci_get_status: | ||
219 | * @status: the new status. | ||
220 | * | ||
221 | * Extract the status information from a WDT watchdog device. There are | ||
222 | * several board variants so we have to know which bits are valid. Some | ||
223 | * bits default to one and some to zero in order to be maximally painful. | ||
224 | * | ||
225 | * we then map the bits onto the status ioctl flags. | ||
226 | */ | ||
227 | |||
228 | static int wdtpci_get_status(int *status) | ||
229 | { | ||
230 | unsigned char new_status=inb_p(WDT_SR); | ||
231 | |||
232 | *status=0; | ||
233 | if (new_status & WDC_SR_ISOI0) | ||
234 | *status |= WDIOF_EXTERN1; | ||
235 | if (new_status & WDC_SR_ISII1) | ||
236 | *status |= WDIOF_EXTERN2; | ||
237 | #ifdef CONFIG_WDT_501_PCI | ||
238 | if (!(new_status & WDC_SR_TGOOD)) | ||
239 | *status |= WDIOF_OVERHEAT; | ||
240 | if (!(new_status & WDC_SR_PSUOVER)) | ||
241 | *status |= WDIOF_POWEROVER; | ||
242 | if (!(new_status & WDC_SR_PSUUNDR)) | ||
243 | *status |= WDIOF_POWERUNDER; | ||
244 | if (tachometer) { | ||
245 | if (!(new_status & WDC_SR_FANGOOD)) | ||
246 | *status |= WDIOF_FANFAULT; | ||
247 | } | ||
248 | #endif /* CONFIG_WDT_501_PCI */ | ||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | #ifdef CONFIG_WDT_501_PCI | ||
253 | /** | ||
254 | * wdtpci_get_temperature: | ||
255 | * | ||
256 | * Reports the temperature in degrees Fahrenheit. The API is in | ||
257 | * farenheit. It was designed by an imperial measurement luddite. | ||
258 | */ | ||
259 | |||
260 | static int wdtpci_get_temperature(int *temperature) | ||
261 | { | ||
262 | unsigned short c=inb_p(WDT_RT); | ||
263 | |||
264 | *temperature = (c * 11 / 15) + 7; | ||
265 | return 0; | ||
266 | } | ||
267 | #endif /* CONFIG_WDT_501_PCI */ | ||
268 | |||
269 | /** | ||
270 | * wdtpci_interrupt: | ||
271 | * @irq: Interrupt number | ||
272 | * @dev_id: Unused as we don't allow multiple devices. | ||
273 | * | ||
274 | * Handle an interrupt from the board. These are raised when the status | ||
275 | * map changes in what the board considers an interesting way. That means | ||
276 | * a failure condition occurring. | ||
277 | */ | ||
278 | |||
279 | static irqreturn_t wdtpci_interrupt(int irq, void *dev_id) | ||
280 | { | ||
281 | /* | ||
282 | * Read the status register see what is up and | ||
283 | * then printk it. | ||
284 | */ | ||
285 | unsigned char status=inb_p(WDT_SR); | ||
286 | |||
287 | printk(KERN_CRIT PFX "status %d\n", status); | ||
288 | |||
289 | #ifdef CONFIG_WDT_501_PCI | ||
290 | if (!(status & WDC_SR_TGOOD)) | ||
291 | printk(KERN_CRIT PFX "Overheat alarm.(%d)\n",inb_p(WDT_RT)); | ||
292 | if (!(status & WDC_SR_PSUOVER)) | ||
293 | printk(KERN_CRIT PFX "PSU over voltage.\n"); | ||
294 | if (!(status & WDC_SR_PSUUNDR)) | ||
295 | printk(KERN_CRIT PFX "PSU under voltage.\n"); | ||
296 | if (tachometer) { | ||
297 | if (!(status & WDC_SR_FANGOOD)) | ||
298 | printk(KERN_CRIT PFX "Possible fan fault.\n"); | ||
299 | } | ||
300 | #endif /* CONFIG_WDT_501_PCI */ | ||
301 | if (!(status&WDC_SR_WCCR)) | ||
302 | #ifdef SOFTWARE_REBOOT | ||
303 | #ifdef ONLY_TESTING | ||
304 | printk(KERN_CRIT PFX "Would Reboot.\n"); | ||
305 | #else | ||
306 | printk(KERN_CRIT PFX "Initiating system reboot.\n"); | ||
307 | emergency_restart(NULL); | ||
308 | #endif | ||
309 | #else | ||
310 | printk(KERN_CRIT PFX "Reset in 5ms.\n"); | ||
311 | #endif | ||
312 | return IRQ_HANDLED; | ||
313 | } | ||
314 | |||
315 | |||
316 | /** | ||
317 | * wdtpci_write: | ||
318 | * @file: file handle to the watchdog | ||
319 | * @buf: buffer to write (unused as data does not matter here | ||
320 | * @count: count of bytes | ||
321 | * @ppos: pointer to the position to write. No seeks allowed | ||
322 | * | ||
323 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
324 | * write of data will do, as we we don't define content meaning. | ||
325 | */ | ||
326 | |||
327 | static ssize_t wdtpci_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
328 | { | ||
329 | if (count) { | ||
330 | if (!nowayout) { | ||
331 | size_t i; | ||
332 | |||
333 | expect_close = 0; | ||
334 | |||
335 | for (i = 0; i != count; i++) { | ||
336 | char c; | ||
337 | if(get_user(c, buf+i)) | ||
338 | return -EFAULT; | ||
339 | if (c == 'V') | ||
340 | expect_close = 42; | ||
341 | } | ||
342 | } | ||
343 | wdtpci_ping(); | ||
344 | } | ||
345 | |||
346 | return count; | ||
347 | } | ||
348 | |||
349 | /** | ||
350 | * wdtpci_ioctl: | ||
351 | * @inode: inode of the device | ||
352 | * @file: file handle to the device | ||
353 | * @cmd: watchdog command | ||
354 | * @arg: argument pointer | ||
355 | * | ||
356 | * The watchdog API defines a common set of functions for all watchdogs | ||
357 | * according to their available features. We only actually usefully support | ||
358 | * querying capabilities and current status. | ||
359 | */ | ||
360 | |||
361 | static int wdtpci_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
362 | unsigned long arg) | ||
363 | { | ||
364 | int new_heartbeat; | ||
365 | int status; | ||
366 | void __user *argp = (void __user *)arg; | ||
367 | int __user *p = argp; | ||
368 | |||
369 | static struct watchdog_info ident = { | ||
370 | .options = WDIOF_SETTIMEOUT| | ||
371 | WDIOF_MAGICCLOSE| | ||
372 | WDIOF_KEEPALIVEPING, | ||
373 | .firmware_version = 1, | ||
374 | .identity = "PCI-WDT500/501", | ||
375 | }; | ||
376 | |||
377 | /* Add options according to the card we have */ | ||
378 | ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2); | ||
379 | #ifdef CONFIG_WDT_501_PCI | ||
380 | ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER); | ||
381 | if (tachometer) | ||
382 | ident.options |= WDIOF_FANFAULT; | ||
383 | #endif /* CONFIG_WDT_501_PCI */ | ||
384 | |||
385 | switch(cmd) | ||
386 | { | ||
387 | default: | ||
388 | return -ENOTTY; | ||
389 | case WDIOC_GETSUPPORT: | ||
390 | return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; | ||
391 | |||
392 | case WDIOC_GETSTATUS: | ||
393 | wdtpci_get_status(&status); | ||
394 | return put_user(status, p); | ||
395 | case WDIOC_GETBOOTSTATUS: | ||
396 | return put_user(0, p); | ||
397 | case WDIOC_KEEPALIVE: | ||
398 | wdtpci_ping(); | ||
399 | return 0; | ||
400 | case WDIOC_SETTIMEOUT: | ||
401 | if (get_user(new_heartbeat, p)) | ||
402 | return -EFAULT; | ||
403 | |||
404 | if (wdtpci_set_heartbeat(new_heartbeat)) | ||
405 | return -EINVAL; | ||
406 | |||
407 | wdtpci_ping(); | ||
408 | /* Fall */ | ||
409 | case WDIOC_GETTIMEOUT: | ||
410 | return put_user(heartbeat, p); | ||
411 | } | ||
412 | } | ||
413 | |||
414 | /** | ||
415 | * wdtpci_open: | ||
416 | * @inode: inode of device | ||
417 | * @file: file handle to device | ||
418 | * | ||
419 | * The watchdog device has been opened. The watchdog device is single | ||
420 | * open and on opening we load the counters. Counter zero is a 100Hz | ||
421 | * cascade, into counter 1 which downcounts to reboot. When the counter | ||
422 | * triggers counter 2 downcounts the length of the reset pulse which | ||
423 | * set set to be as long as possible. | ||
424 | */ | ||
425 | |||
426 | static int wdtpci_open(struct inode *inode, struct file *file) | ||
427 | { | ||
428 | if (down_trylock(&open_sem)) | ||
429 | return -EBUSY; | ||
430 | |||
431 | if (nowayout) { | ||
432 | __module_get(THIS_MODULE); | ||
433 | } | ||
434 | /* | ||
435 | * Activate | ||
436 | */ | ||
437 | wdtpci_start(); | ||
438 | return nonseekable_open(inode, file); | ||
439 | } | ||
440 | |||
441 | /** | ||
442 | * wdtpci_release: | ||
443 | * @inode: inode to board | ||
444 | * @file: file handle to board | ||
445 | * | ||
446 | * The watchdog has a configurable API. There is a religious dispute | ||
447 | * between people who want their watchdog to be able to shut down and | ||
448 | * those who want to be sure if the watchdog manager dies the machine | ||
449 | * reboots. In the former case we disable the counters, in the latter | ||
450 | * case you have to open it again very soon. | ||
451 | */ | ||
452 | |||
453 | static int wdtpci_release(struct inode *inode, struct file *file) | ||
454 | { | ||
455 | if (expect_close == 42) { | ||
456 | wdtpci_stop(); | ||
457 | } else { | ||
458 | printk(KERN_CRIT PFX "Unexpected close, not stopping timer!"); | ||
459 | wdtpci_ping(); | ||
460 | } | ||
461 | expect_close = 0; | ||
462 | up(&open_sem); | ||
463 | return 0; | ||
464 | } | ||
465 | |||
466 | #ifdef CONFIG_WDT_501_PCI | ||
467 | /** | ||
468 | * wdtpci_temp_read: | ||
469 | * @file: file handle to the watchdog board | ||
470 | * @buf: buffer to write 1 byte into | ||
471 | * @count: length of buffer | ||
472 | * @ptr: offset (no seek allowed) | ||
473 | * | ||
474 | * Read reports the temperature in degrees Fahrenheit. The API is in | ||
475 | * fahrenheit. It was designed by an imperial measurement luddite. | ||
476 | */ | ||
477 | |||
478 | static ssize_t wdtpci_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ptr) | ||
479 | { | ||
480 | int temperature; | ||
481 | |||
482 | if (wdtpci_get_temperature(&temperature)) | ||
483 | return -EFAULT; | ||
484 | |||
485 | if (copy_to_user (buf, &temperature, 1)) | ||
486 | return -EFAULT; | ||
487 | |||
488 | return 1; | ||
489 | } | ||
490 | |||
491 | /** | ||
492 | * wdtpci_temp_open: | ||
493 | * @inode: inode of device | ||
494 | * @file: file handle to device | ||
495 | * | ||
496 | * The temperature device has been opened. | ||
497 | */ | ||
498 | |||
499 | static int wdtpci_temp_open(struct inode *inode, struct file *file) | ||
500 | { | ||
501 | return nonseekable_open(inode, file); | ||
502 | } | ||
503 | |||
504 | /** | ||
505 | * wdtpci_temp_release: | ||
506 | * @inode: inode to board | ||
507 | * @file: file handle to board | ||
508 | * | ||
509 | * The temperature device has been closed. | ||
510 | */ | ||
511 | |||
512 | static int wdtpci_temp_release(struct inode *inode, struct file *file) | ||
513 | { | ||
514 | return 0; | ||
515 | } | ||
516 | #endif /* CONFIG_WDT_501_PCI */ | ||
517 | |||
518 | /** | ||
519 | * notify_sys: | ||
520 | * @this: our notifier block | ||
521 | * @code: the event being reported | ||
522 | * @unused: unused | ||
523 | * | ||
524 | * Our notifier is called on system shutdowns. We want to turn the card | ||
525 | * off at reboot otherwise the machine will reboot again during memory | ||
526 | * test or worse yet during the following fsck. This would suck, in fact | ||
527 | * trust me - if it happens it does suck. | ||
528 | */ | ||
529 | |||
530 | static int wdtpci_notify_sys(struct notifier_block *this, unsigned long code, | ||
531 | void *unused) | ||
532 | { | ||
533 | if (code==SYS_DOWN || code==SYS_HALT) { | ||
534 | /* Turn the card off */ | ||
535 | wdtpci_stop(); | ||
536 | } | ||
537 | return NOTIFY_DONE; | ||
538 | } | ||
539 | |||
540 | /* | ||
541 | * Kernel Interfaces | ||
542 | */ | ||
543 | |||
544 | |||
545 | static const struct file_operations wdtpci_fops = { | ||
546 | .owner = THIS_MODULE, | ||
547 | .llseek = no_llseek, | ||
548 | .write = wdtpci_write, | ||
549 | .ioctl = wdtpci_ioctl, | ||
550 | .open = wdtpci_open, | ||
551 | .release = wdtpci_release, | ||
552 | }; | ||
553 | |||
554 | static struct miscdevice wdtpci_miscdev = { | ||
555 | .minor = WATCHDOG_MINOR, | ||
556 | .name = "watchdog", | ||
557 | .fops = &wdtpci_fops, | ||
558 | }; | ||
559 | |||
560 | #ifdef CONFIG_WDT_501_PCI | ||
561 | static const struct file_operations wdtpci_temp_fops = { | ||
562 | .owner = THIS_MODULE, | ||
563 | .llseek = no_llseek, | ||
564 | .read = wdtpci_temp_read, | ||
565 | .open = wdtpci_temp_open, | ||
566 | .release = wdtpci_temp_release, | ||
567 | }; | ||
568 | |||
569 | static struct miscdevice temp_miscdev = { | ||
570 | .minor = TEMP_MINOR, | ||
571 | .name = "temperature", | ||
572 | .fops = &wdtpci_temp_fops, | ||
573 | }; | ||
574 | #endif /* CONFIG_WDT_501_PCI */ | ||
575 | |||
576 | /* | ||
577 | * The WDT card needs to learn about soft shutdowns in order to | ||
578 | * turn the timebomb registers off. | ||
579 | */ | ||
580 | |||
581 | static struct notifier_block wdtpci_notifier = { | ||
582 | .notifier_call = wdtpci_notify_sys, | ||
583 | }; | ||
584 | |||
585 | |||
586 | static int __devinit wdtpci_init_one (struct pci_dev *dev, | ||
587 | const struct pci_device_id *ent) | ||
588 | { | ||
589 | int ret = -EIO; | ||
590 | |||
591 | dev_count++; | ||
592 | if (dev_count > 1) { | ||
593 | printk (KERN_ERR PFX "this driver only supports 1 device\n"); | ||
594 | return -ENODEV; | ||
595 | } | ||
596 | |||
597 | if (pci_enable_device (dev)) { | ||
598 | printk (KERN_ERR PFX "Not possible to enable PCI Device\n"); | ||
599 | return -ENODEV; | ||
600 | } | ||
601 | |||
602 | if (pci_resource_start (dev, 2) == 0x0000) { | ||
603 | printk (KERN_ERR PFX "No I/O-Address for card detected\n"); | ||
604 | ret = -ENODEV; | ||
605 | goto out_pci; | ||
606 | } | ||
607 | |||
608 | sema_init(&open_sem, 1); | ||
609 | spin_lock_init(&wdtpci_lock); | ||
610 | |||
611 | irq = dev->irq; | ||
612 | io = pci_resource_start (dev, 2); | ||
613 | |||
614 | if (request_region (io, 16, "wdt_pci") == NULL) { | ||
615 | printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", io); | ||
616 | goto out_pci; | ||
617 | } | ||
618 | |||
619 | if (request_irq (irq, wdtpci_interrupt, IRQF_DISABLED | IRQF_SHARED, | ||
620 | "wdt_pci", &wdtpci_miscdev)) { | ||
621 | printk (KERN_ERR PFX "IRQ %d is not free\n", irq); | ||
622 | goto out_reg; | ||
623 | } | ||
624 | |||
625 | printk ("PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%04x (Interrupt %d)\n", | ||
626 | io, irq); | ||
627 | |||
628 | /* Check that the heartbeat value is within it's range ; if not reset to the default */ | ||
629 | if (wdtpci_set_heartbeat(heartbeat)) { | ||
630 | wdtpci_set_heartbeat(WD_TIMO); | ||
631 | printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", | ||
632 | WD_TIMO); | ||
633 | } | ||
634 | |||
635 | ret = register_reboot_notifier (&wdtpci_notifier); | ||
636 | if (ret) { | ||
637 | printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret); | ||
638 | goto out_irq; | ||
639 | } | ||
640 | |||
641 | #ifdef CONFIG_WDT_501_PCI | ||
642 | ret = misc_register (&temp_miscdev); | ||
643 | if (ret) { | ||
644 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
645 | TEMP_MINOR, ret); | ||
646 | goto out_rbt; | ||
647 | } | ||
648 | #endif /* CONFIG_WDT_501_PCI */ | ||
649 | |||
650 | ret = misc_register (&wdtpci_miscdev); | ||
651 | if (ret) { | ||
652 | printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
653 | WATCHDOG_MINOR, ret); | ||
654 | goto out_misc; | ||
655 | } | ||
656 | |||
657 | printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", | ||
658 | heartbeat, nowayout); | ||
659 | #ifdef CONFIG_WDT_501_PCI | ||
660 | printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled")); | ||
661 | #endif /* CONFIG_WDT_501_PCI */ | ||
662 | |||
663 | ret = 0; | ||
664 | out: | ||
665 | return ret; | ||
666 | |||
667 | out_misc: | ||
668 | #ifdef CONFIG_WDT_501_PCI | ||
669 | misc_deregister(&temp_miscdev); | ||
670 | out_rbt: | ||
671 | #endif /* CONFIG_WDT_501_PCI */ | ||
672 | unregister_reboot_notifier(&wdtpci_notifier); | ||
673 | out_irq: | ||
674 | free_irq(irq, &wdtpci_miscdev); | ||
675 | out_reg: | ||
676 | release_region (io, 16); | ||
677 | out_pci: | ||
678 | pci_disable_device(dev); | ||
679 | goto out; | ||
680 | } | ||
681 | |||
682 | |||
683 | static void __devexit wdtpci_remove_one (struct pci_dev *pdev) | ||
684 | { | ||
685 | /* here we assume only one device will ever have | ||
686 | * been picked up and registered by probe function */ | ||
687 | misc_deregister(&wdtpci_miscdev); | ||
688 | #ifdef CONFIG_WDT_501_PCI | ||
689 | misc_deregister(&temp_miscdev); | ||
690 | #endif /* CONFIG_WDT_501_PCI */ | ||
691 | unregister_reboot_notifier(&wdtpci_notifier); | ||
692 | free_irq(irq, &wdtpci_miscdev); | ||
693 | release_region(io, 16); | ||
694 | pci_disable_device(pdev); | ||
695 | dev_count--; | ||
696 | } | ||
697 | |||
698 | |||
699 | static struct pci_device_id wdtpci_pci_tbl[] = { | ||
700 | { | ||
701 | .vendor = PCI_VENDOR_ID_ACCESSIO, | ||
702 | .device = PCI_DEVICE_ID_WDG_CSM, | ||
703 | .subvendor = PCI_ANY_ID, | ||
704 | .subdevice = PCI_ANY_ID, | ||
705 | }, | ||
706 | { 0, }, /* terminate list */ | ||
707 | }; | ||
708 | MODULE_DEVICE_TABLE(pci, wdtpci_pci_tbl); | ||
709 | |||
710 | |||
711 | static struct pci_driver wdtpci_driver = { | ||
712 | .name = "wdt_pci", | ||
713 | .id_table = wdtpci_pci_tbl, | ||
714 | .probe = wdtpci_init_one, | ||
715 | .remove = __devexit_p(wdtpci_remove_one), | ||
716 | }; | ||
717 | |||
718 | |||
719 | /** | ||
720 | * wdtpci_cleanup: | ||
721 | * | ||
722 | * Unload the watchdog. You cannot do this with any file handles open. | ||
723 | * If your watchdog is set to continue ticking on close and you unload | ||
724 | * it, well it keeps ticking. We won't get the interrupt but the board | ||
725 | * will not touch PC memory so all is fine. You just have to load a new | ||
726 | * module in xx seconds or reboot. | ||
727 | */ | ||
728 | |||
729 | static void __exit wdtpci_cleanup(void) | ||
730 | { | ||
731 | pci_unregister_driver (&wdtpci_driver); | ||
732 | } | ||
733 | |||
734 | |||
735 | /** | ||
736 | * wdtpci_init: | ||
737 | * | ||
738 | * Set up the WDT watchdog board. All we have to do is grab the | ||
739 | * resources we require and bitch if anyone beat us to them. | ||
740 | * The open() function will actually kick the board off. | ||
741 | */ | ||
742 | |||
743 | static int __init wdtpci_init(void) | ||
744 | { | ||
745 | return pci_register_driver (&wdtpci_driver); | ||
746 | } | ||
747 | |||
748 | |||
749 | module_init(wdtpci_init); | ||
750 | module_exit(wdtpci_cleanup); | ||
751 | |||
752 | MODULE_AUTHOR("JP Nollmann, Alan Cox"); | ||
753 | MODULE_DESCRIPTION("Driver for the ICS PCI-WDT500/501 watchdog cards"); | ||
754 | MODULE_LICENSE("GPL"); | ||
755 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
756 | MODULE_ALIAS_MISCDEV(TEMP_MINOR); | ||