Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

watchdog: Add support for sp5100 chipset TCO

This driver adds /dev/watchdog support for the AMD sp5100 aka SB7x0 chipsets.

It follows the same conventions found in other /dev/watchdog drivers.

Signed-off-by: Priyanka Gupta <priyankag@google.com>
Signed-off-by: Mike Waychison <mikew@google.com>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>

authored by

Priyanka Gupta and committed by
Wim Van Sebroeck
15e28bf1 e13752a1

+533
+11
drivers/watchdog/Kconfig
··· 418 418 You can compile this driver directly into the kernel, or use 419 419 it as a module. The module will be called f71808e_wdt. 420 420 421 + config SP5100_TCO 422 + tristate "AMD/ATI SP5100 TCO Timer/Watchdog" 423 + depends on X86 && PCI 424 + ---help--- 425 + Hardware watchdog driver for the AMD/ATI SP5100 chipset. The TCO 426 + (Total Cost of Ownership) timer is a watchdog timer that will reboot 427 + the machine after its expiration. The expiration time can be 428 + configured with the "heartbeat" parameter. 429 + 430 + To compile this driver as a module, choose M here: the 431 + module will be called sp5100_tco. 421 432 422 433 config GEODE_WDT 423 434 tristate "AMD Geode CS5535/CS5536 Watchdog"
+1
drivers/watchdog/Makefile
··· 68 68 obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o 69 69 obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o 70 70 obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o 71 + obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o 71 72 obj-$(CONFIG_GEODE_WDT) += geodewdt.o 72 73 obj-$(CONFIG_SC520_WDT) += sc520_wdt.o 73 74 obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o
+480
drivers/watchdog/sp5100_tco.c
··· 1 + /* 2 + * sp5100_tco : TCO timer driver for sp5100 chipsets 3 + * 4 + * (c) Copyright 2009 Google Inc., All Rights Reserved. 5 + * 6 + * Based on i8xx_tco.c: 7 + * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights 8 + * Reserved. 9 + * http://www.kernelconcepts.de 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 + * See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide" 17 + */ 18 + 19 + /* 20 + * Includes, defines, variables, module parameters, ... 21 + */ 22 + 23 + #include <linux/module.h> 24 + #include <linux/moduleparam.h> 25 + #include <linux/types.h> 26 + #include <linux/miscdevice.h> 27 + #include <linux/watchdog.h> 28 + #include <linux/init.h> 29 + #include <linux/fs.h> 30 + #include <linux/pci.h> 31 + #include <linux/ioport.h> 32 + #include <linux/platform_device.h> 33 + #include <linux/uaccess.h> 34 + #include <linux/io.h> 35 + 36 + #include "sp5100_tco.h" 37 + 38 + /* Module and version information */ 39 + #define TCO_VERSION "0.01" 40 + #define TCO_MODULE_NAME "SP5100 TCO timer" 41 + #define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION 42 + #define PFX TCO_MODULE_NAME ": " 43 + 44 + /* internal variables */ 45 + static void __iomem *tcobase; 46 + static unsigned int pm_iobase; 47 + static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */ 48 + static unsigned long timer_alive; 49 + static char tco_expect_close; 50 + static struct pci_dev *sp5100_tco_pci; 51 + 52 + /* the watchdog platform device */ 53 + static struct platform_device *sp5100_tco_platform_device; 54 + 55 + /* module parameters */ 56 + 57 + #define WATCHDOG_HEARTBEAT 60 /* 60 sec default heartbeat. */ 58 + static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ 59 + module_param(heartbeat, int, 0); 60 + MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default=" 61 + __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); 62 + 63 + static int nowayout = WATCHDOG_NOWAYOUT; 64 + module_param(nowayout, int, 0); 65 + MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started" 66 + " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 67 + 68 + /* 69 + * Some TCO specific functions 70 + */ 71 + static void tco_timer_start(void) 72 + { 73 + u32 val; 74 + unsigned long flags; 75 + 76 + spin_lock_irqsave(&tco_lock, flags); 77 + val = readl(SP5100_WDT_CONTROL(tcobase)); 78 + val |= SP5100_WDT_START_STOP_BIT; 79 + writel(val, SP5100_WDT_CONTROL(tcobase)); 80 + spin_unlock_irqrestore(&tco_lock, flags); 81 + } 82 + 83 + static void tco_timer_stop(void) 84 + { 85 + u32 val; 86 + unsigned long flags; 87 + 88 + spin_lock_irqsave(&tco_lock, flags); 89 + val = readl(SP5100_WDT_CONTROL(tcobase)); 90 + val &= ~SP5100_WDT_START_STOP_BIT; 91 + writel(val, SP5100_WDT_CONTROL(tcobase)); 92 + spin_unlock_irqrestore(&tco_lock, flags); 93 + } 94 + 95 + static void tco_timer_keepalive(void) 96 + { 97 + u32 val; 98 + unsigned long flags; 99 + 100 + spin_lock_irqsave(&tco_lock, flags); 101 + val = readl(SP5100_WDT_CONTROL(tcobase)); 102 + val |= SP5100_WDT_TRIGGER_BIT; 103 + writel(val, SP5100_WDT_CONTROL(tcobase)); 104 + spin_unlock_irqrestore(&tco_lock, flags); 105 + } 106 + 107 + static int tco_timer_set_heartbeat(int t) 108 + { 109 + unsigned long flags; 110 + 111 + if (t < 0 || t > 0xffff) 112 + return -EINVAL; 113 + 114 + /* Write new heartbeat to watchdog */ 115 + spin_lock_irqsave(&tco_lock, flags); 116 + writel(t, SP5100_WDT_COUNT(tcobase)); 117 + spin_unlock_irqrestore(&tco_lock, flags); 118 + 119 + heartbeat = t; 120 + return 0; 121 + } 122 + 123 + /* 124 + * /dev/watchdog handling 125 + */ 126 + 127 + static int sp5100_tco_open(struct inode *inode, struct file *file) 128 + { 129 + /* /dev/watchdog can only be opened once */ 130 + if (test_and_set_bit(0, &timer_alive)) 131 + return -EBUSY; 132 + 133 + /* Reload and activate timer */ 134 + tco_timer_start(); 135 + tco_timer_keepalive(); 136 + return nonseekable_open(inode, file); 137 + } 138 + 139 + static int sp5100_tco_release(struct inode *inode, struct file *file) 140 + { 141 + /* Shut off the timer. */ 142 + if (tco_expect_close == 42) { 143 + tco_timer_stop(); 144 + } else { 145 + printk(KERN_CRIT PFX 146 + "Unexpected close, not stopping watchdog!\n"); 147 + tco_timer_keepalive(); 148 + } 149 + clear_bit(0, &timer_alive); 150 + tco_expect_close = 0; 151 + return 0; 152 + } 153 + 154 + static ssize_t sp5100_tco_write(struct file *file, const char __user *data, 155 + size_t len, loff_t *ppos) 156 + { 157 + /* See if we got the magic character 'V' and reload the timer */ 158 + if (len) { 159 + if (!nowayout) { 160 + size_t i; 161 + 162 + /* note: just in case someone wrote the magic character 163 + * five months ago... */ 164 + tco_expect_close = 0; 165 + 166 + /* scan to see whether or not we got the magic character 167 + */ 168 + for (i = 0; i != len; i++) { 169 + char c; 170 + if (get_user(c, data + i)) 171 + return -EFAULT; 172 + if (c == 'V') 173 + tco_expect_close = 42; 174 + } 175 + } 176 + 177 + /* someone wrote to us, we should reload the timer */ 178 + tco_timer_keepalive(); 179 + } 180 + return len; 181 + } 182 + 183 + static long sp5100_tco_ioctl(struct file *file, unsigned int cmd, 184 + unsigned long arg) 185 + { 186 + int new_options, retval = -EINVAL; 187 + int new_heartbeat; 188 + void __user *argp = (void __user *)arg; 189 + int __user *p = argp; 190 + static const struct watchdog_info ident = { 191 + .options = WDIOF_SETTIMEOUT | 192 + WDIOF_KEEPALIVEPING | 193 + WDIOF_MAGICCLOSE, 194 + .firmware_version = 0, 195 + .identity = TCO_MODULE_NAME, 196 + }; 197 + 198 + switch (cmd) { 199 + case WDIOC_GETSUPPORT: 200 + return copy_to_user(argp, &ident, 201 + sizeof(ident)) ? -EFAULT : 0; 202 + case WDIOC_GETSTATUS: 203 + case WDIOC_GETBOOTSTATUS: 204 + return put_user(0, p); 205 + case WDIOC_SETOPTIONS: 206 + if (get_user(new_options, p)) 207 + return -EFAULT; 208 + if (new_options & WDIOS_DISABLECARD) { 209 + tco_timer_stop(); 210 + retval = 0; 211 + } 212 + if (new_options & WDIOS_ENABLECARD) { 213 + tco_timer_start(); 214 + tco_timer_keepalive(); 215 + retval = 0; 216 + } 217 + return retval; 218 + case WDIOC_KEEPALIVE: 219 + tco_timer_keepalive(); 220 + return 0; 221 + case WDIOC_SETTIMEOUT: 222 + if (get_user(new_heartbeat, p)) 223 + return -EFAULT; 224 + if (tco_timer_set_heartbeat(new_heartbeat)) 225 + return -EINVAL; 226 + tco_timer_keepalive(); 227 + /* Fall through */ 228 + case WDIOC_GETTIMEOUT: 229 + return put_user(heartbeat, p); 230 + default: 231 + return -ENOTTY; 232 + } 233 + } 234 + 235 + /* 236 + * Kernel Interfaces 237 + */ 238 + 239 + static const struct file_operations sp5100_tco_fops = { 240 + .owner = THIS_MODULE, 241 + .llseek = no_llseek, 242 + .write = sp5100_tco_write, 243 + .unlocked_ioctl = sp5100_tco_ioctl, 244 + .open = sp5100_tco_open, 245 + .release = sp5100_tco_release, 246 + }; 247 + 248 + static struct miscdevice sp5100_tco_miscdev = { 249 + .minor = WATCHDOG_MINOR, 250 + .name = "watchdog", 251 + .fops = &sp5100_tco_fops, 252 + }; 253 + 254 + /* 255 + * Data for PCI driver interface 256 + * 257 + * This data only exists for exporting the supported 258 + * PCI ids via MODULE_DEVICE_TABLE. We do not actually 259 + * register a pci_driver, because someone else might 260 + * want to register another driver on the same PCI id. 261 + */ 262 + static struct pci_device_id sp5100_tco_pci_tbl[] = { 263 + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID, 264 + PCI_ANY_ID, }, 265 + { 0, }, /* End of list */ 266 + }; 267 + MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl); 268 + 269 + /* 270 + * Init & exit routines 271 + */ 272 + 273 + static unsigned char __devinit sp5100_tco_setupdevice(void) 274 + { 275 + struct pci_dev *dev = NULL; 276 + u32 val; 277 + 278 + /* Match the PCI device */ 279 + for_each_pci_dev(dev) { 280 + if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) { 281 + sp5100_tco_pci = dev; 282 + break; 283 + } 284 + } 285 + 286 + if (!sp5100_tco_pci) 287 + return 0; 288 + 289 + /* Request the IO ports used by this driver */ 290 + pm_iobase = SP5100_IO_PM_INDEX_REG; 291 + if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, "SP5100 TCO")) { 292 + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", 293 + pm_iobase); 294 + goto exit; 295 + } 296 + 297 + /* Find the watchdog base address. */ 298 + outb(SP5100_PM_WATCHDOG_BASE3, SP5100_IO_PM_INDEX_REG); 299 + val = inb(SP5100_IO_PM_DATA_REG); 300 + outb(SP5100_PM_WATCHDOG_BASE2, SP5100_IO_PM_INDEX_REG); 301 + val = val << 8 | inb(SP5100_IO_PM_DATA_REG); 302 + outb(SP5100_PM_WATCHDOG_BASE1, SP5100_IO_PM_INDEX_REG); 303 + val = val << 8 | inb(SP5100_IO_PM_DATA_REG); 304 + outb(SP5100_PM_WATCHDOG_BASE0, SP5100_IO_PM_INDEX_REG); 305 + /* Low three bits of BASE0 are reserved. */ 306 + val = val << 8 | (inb(SP5100_IO_PM_DATA_REG) & 0xf8); 307 + 308 + tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); 309 + if (tcobase == 0) { 310 + printk(KERN_ERR PFX "failed to get tcobase address\n"); 311 + goto unreg_region; 312 + } 313 + 314 + /* Enable watchdog decode bit */ 315 + pci_read_config_dword(sp5100_tco_pci, 316 + SP5100_PCI_WATCHDOG_MISC_REG, 317 + &val); 318 + 319 + val |= SP5100_PCI_WATCHDOG_DECODE_EN; 320 + 321 + pci_write_config_dword(sp5100_tco_pci, 322 + SP5100_PCI_WATCHDOG_MISC_REG, 323 + val); 324 + 325 + /* Enable Watchdog timer and set the resolution to 1 sec. */ 326 + outb(SP5100_PM_WATCHDOG_CONTROL, SP5100_IO_PM_INDEX_REG); 327 + val = inb(SP5100_IO_PM_DATA_REG); 328 + val |= SP5100_PM_WATCHDOG_SECOND_RES; 329 + val &= ~SP5100_PM_WATCHDOG_DISABLE; 330 + outb(val, SP5100_IO_PM_DATA_REG); 331 + 332 + /* Check that the watchdog action is set to reset the system. */ 333 + val = readl(SP5100_WDT_CONTROL(tcobase)); 334 + val &= ~SP5100_PM_WATCHDOG_ACTION_RESET; 335 + writel(val, SP5100_WDT_CONTROL(tcobase)); 336 + 337 + /* Set a reasonable heartbeat before we stop the timer */ 338 + tco_timer_set_heartbeat(heartbeat); 339 + 340 + /* 341 + * Stop the TCO before we change anything so we don't race with 342 + * a zeroed timer. 343 + */ 344 + tco_timer_stop(); 345 + 346 + /* Done */ 347 + return 1; 348 + 349 + iounmap(tcobase); 350 + unreg_region: 351 + release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); 352 + exit: 353 + return 0; 354 + } 355 + 356 + static int __devinit sp5100_tco_init(struct platform_device *dev) 357 + { 358 + int ret; 359 + u32 val; 360 + 361 + /* Check whether or not the hardware watchdog is there. If found, then 362 + * set it up. 363 + */ 364 + if (!sp5100_tco_setupdevice()) 365 + return -ENODEV; 366 + 367 + /* Check to see if last reboot was due to watchdog timeout */ 368 + printk(KERN_INFO PFX "Watchdog reboot %sdetected.\n", 369 + readl(SP5100_WDT_CONTROL(tcobase)) & SP5100_PM_WATCHDOG_FIRED ? 370 + "" : "not "); 371 + 372 + /* Clear out the old status */ 373 + val = readl(SP5100_WDT_CONTROL(tcobase)); 374 + val &= ~SP5100_PM_WATCHDOG_FIRED; 375 + writel(val, SP5100_WDT_CONTROL(tcobase)); 376 + 377 + /* 378 + * Check that the heartbeat value is within it's range. 379 + * If not, reset to the default. 380 + */ 381 + if (tco_timer_set_heartbeat(heartbeat)) { 382 + heartbeat = WATCHDOG_HEARTBEAT; 383 + tco_timer_set_heartbeat(heartbeat); 384 + } 385 + 386 + ret = misc_register(&sp5100_tco_miscdev); 387 + if (ret != 0) { 388 + printk(KERN_ERR PFX "cannot register miscdev on minor=" 389 + "%d (err=%d)\n", 390 + WATCHDOG_MINOR, ret); 391 + goto exit; 392 + } 393 + 394 + clear_bit(0, &timer_alive); 395 + 396 + printk(KERN_INFO PFX "initialized (0x%p). heartbeat=%d sec" 397 + " (nowayout=%d)\n", 398 + tcobase, heartbeat, nowayout); 399 + 400 + return 0; 401 + 402 + exit: 403 + iounmap(tcobase); 404 + release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); 405 + return ret; 406 + } 407 + 408 + static void __devexit sp5100_tco_cleanup(void) 409 + { 410 + /* Stop the timer before we leave */ 411 + if (!nowayout) 412 + tco_timer_stop(); 413 + 414 + /* Deregister */ 415 + misc_deregister(&sp5100_tco_miscdev); 416 + iounmap(tcobase); 417 + release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); 418 + } 419 + 420 + static int __devexit sp5100_tco_remove(struct platform_device *dev) 421 + { 422 + if (tcobase) 423 + sp5100_tco_cleanup(); 424 + return 0; 425 + } 426 + 427 + static void sp5100_tco_shutdown(struct platform_device *dev) 428 + { 429 + tco_timer_stop(); 430 + } 431 + 432 + static struct platform_driver sp5100_tco_driver = { 433 + .probe = sp5100_tco_init, 434 + .remove = __devexit_p(sp5100_tco_remove), 435 + .shutdown = sp5100_tco_shutdown, 436 + .driver = { 437 + .owner = THIS_MODULE, 438 + .name = TCO_MODULE_NAME, 439 + }, 440 + }; 441 + 442 + static int __init sp5100_tco_init_module(void) 443 + { 444 + int err; 445 + 446 + printk(KERN_INFO PFX "SP5100 TCO WatchDog Timer Driver v%s\n", 447 + TCO_VERSION); 448 + 449 + err = platform_driver_register(&sp5100_tco_driver); 450 + if (err) 451 + return err; 452 + 453 + sp5100_tco_platform_device = platform_device_register_simple( 454 + TCO_MODULE_NAME, -1, NULL, 0); 455 + if (IS_ERR(sp5100_tco_platform_device)) { 456 + err = PTR_ERR(sp5100_tco_platform_device); 457 + goto unreg_platform_driver; 458 + } 459 + 460 + return 0; 461 + 462 + unreg_platform_driver: 463 + platform_driver_unregister(&sp5100_tco_driver); 464 + return err; 465 + } 466 + 467 + static void __exit sp5100_tco_cleanup_module(void) 468 + { 469 + platform_device_unregister(sp5100_tco_platform_device); 470 + platform_driver_unregister(&sp5100_tco_driver); 471 + printk(KERN_INFO PFX "SP5100 TCO Watchdog Module Unloaded.\n"); 472 + } 473 + 474 + module_init(sp5100_tco_init_module); 475 + module_exit(sp5100_tco_cleanup_module); 476 + 477 + MODULE_AUTHOR("Priyanka Gupta"); 478 + MODULE_DESCRIPTION("TCO timer driver for SP5100 chipset"); 479 + MODULE_LICENSE("GPL"); 480 + MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+41
drivers/watchdog/sp5100_tco.h
··· 1 + /* 2 + * sp5100_tco: TCO timer driver for sp5100 chipsets. 3 + * 4 + * (c) Copyright 2009 Google Inc., All Rights Reserved. 5 + * 6 + * TCO timer driver for sp5100 chipsets 7 + */ 8 + 9 + /* 10 + * Some address definitions for the Watchdog 11 + */ 12 + 13 + #define SP5100_WDT_MEM_MAP_SIZE 0x08 14 + #define SP5100_WDT_CONTROL(base) ((base) + 0x00) /* Watchdog Control */ 15 + #define SP5100_WDT_COUNT(base) ((base) + 0x04) /* Watchdog Count */ 16 + 17 + #define SP5100_WDT_START_STOP_BIT 1 18 + #define SP5100_WDT_TRIGGER_BIT (1 << 7) 19 + 20 + #define SP5100_PCI_WATCHDOG_MISC_REG 0x41 21 + #define SP5100_PCI_WATCHDOG_DECODE_EN (1 << 3) 22 + 23 + #define SP5100_PM_IOPORTS_SIZE 0x02 24 + 25 + /* These two IO registers are hardcoded and there doesn't seem to be a way to 26 + * read them from a register. 27 + */ 28 + #define SP5100_IO_PM_INDEX_REG 0xCD6 29 + #define SP5100_IO_PM_DATA_REG 0xCD7 30 + 31 + #define SP5100_PM_WATCHDOG_CONTROL 0x69 32 + #define SP5100_PM_WATCHDOG_BASE0 0x6C 33 + #define SP5100_PM_WATCHDOG_BASE1 0x6D 34 + #define SP5100_PM_WATCHDOG_BASE2 0x6E 35 + #define SP5100_PM_WATCHDOG_BASE3 0x6F 36 + 37 + #define SP5100_PM_WATCHDOG_FIRED (1 << 1) 38 + #define SP5100_PM_WATCHDOG_ACTION_RESET (1 << 2) 39 + 40 + #define SP5100_PM_WATCHDOG_DISABLE 1 41 + #define SP5100_PM_WATCHDOG_SECOND_RES (3 << 1)