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