diff options
| -rw-r--r-- | drivers/watchdog/Kconfig | 13 | ||||
| -rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
| -rw-r--r-- | drivers/watchdog/sb_wdog.c | 353 |
3 files changed, 367 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index afcdc69e37d6..591e3f399114 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
| @@ -633,6 +633,19 @@ config WDT_RM9K_GPI | |||
| 633 | To compile this driver as a module, choose M here: the | 633 | To compile this driver as a module, choose M here: the |
| 634 | module will be called rm9k_wdt. | 634 | module will be called rm9k_wdt. |
| 635 | 635 | ||
| 636 | config SIBYTE_WDOG | ||
| 637 | tristate "Sibyte SoC hardware watchdog" | ||
| 638 | depends on CPU_SB1 | ||
| 639 | help | ||
| 640 | Watchdog driver for the built in watchdog hardware in Sibyte | ||
| 641 | SoC processors. There are apparently two watchdog timers | ||
| 642 | on such processors; this driver supports only the first one, | ||
| 643 | because currently Linux only supports exporting one watchdog | ||
| 644 | to userspace. | ||
| 645 | |||
| 646 | To compile this driver as a loadable module, choose M here. | ||
| 647 | The module will be called sb_wdog. | ||
| 648 | |||
| 636 | config AR7_WDT | 649 | config AR7_WDT |
| 637 | tristate "TI AR7 Watchdog Timer" | 650 | tristate "TI AR7 Watchdog Timer" |
| 638 | depends on AR7 | 651 | depends on AR7 |
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index ebc21146d40c..90e4bbffbbfd 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
| @@ -92,6 +92,7 @@ obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o | |||
| 92 | obj-$(CONFIG_INDYDOG) += indydog.o | 92 | obj-$(CONFIG_INDYDOG) += indydog.o |
| 93 | obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o | 93 | obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o |
| 94 | obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o | 94 | obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o |
| 95 | obj-$(CONFIG_SIBYTE_WDOG) += sb_wdog.o | ||
| 95 | obj-$(CONFIG_AR7_WDT) += ar7_wdt.o | 96 | obj-$(CONFIG_AR7_WDT) += ar7_wdt.o |
| 96 | obj-$(CONFIG_TXX9_WDT) += txx9wdt.o | 97 | obj-$(CONFIG_TXX9_WDT) += txx9wdt.o |
| 97 | 98 | ||
diff --git a/drivers/watchdog/sb_wdog.c b/drivers/watchdog/sb_wdog.c new file mode 100644 index 000000000000..b94431433695 --- /dev/null +++ b/drivers/watchdog/sb_wdog.c | |||
| @@ -0,0 +1,353 @@ | |||
| 1 | /* | ||
| 2 | * Watchdog driver for SiByte SB1 SoCs | ||
| 3 | * | ||
| 4 | * Copyright (C) 2007 OnStor, Inc. * Andrew Sharp <andy.sharp@onstor.com> | ||
| 5 | * | ||
| 6 | * This driver is intended to make the second of two hardware watchdogs | ||
| 7 | * on the Sibyte 12XX and 11XX SoCs available to the user. There are two | ||
| 8 | * such devices available on the SoC, but it seems that there isn't an | ||
| 9 | * enumeration class for watchdogs in Linux like there is for RTCs. | ||
| 10 | * The second is used rather than the first because it uses IRQ 1, | ||
| 11 | * thereby avoiding all that IRQ 0 problematic nonsense. | ||
| 12 | * | ||
| 13 | * I have not tried this driver on a 1480 processor; it might work | ||
| 14 | * just well enough to really screw things up. | ||
| 15 | * | ||
| 16 | * It is a simple timer, and there is an interrupt that is raised the | ||
| 17 | * first time the timer expires. The second time it expires, the chip | ||
| 18 | * is reset and there is no way to redirect that NMI. Which could | ||
| 19 | * be problematic in some cases where this chip is sitting on the HT | ||
| 20 | * bus and has just taken responsibility for providing a cache block. | ||
| 21 | * Since the reset can't be redirected to the external reset pin, it is | ||
| 22 | * possible that other HT connected processors might hang and not reset. | ||
| 23 | * For Linux, a soft reset would probably be even worse than a hard reset. | ||
| 24 | * There you have it. | ||
| 25 | * | ||
| 26 | * The timer takes 23 bits of a 64 bit register (?) as a count value, | ||
| 27 | * and decrements the count every microsecond, for a max value of | ||
| 28 | * 0x7fffff usec or about 8.3ish seconds. | ||
| 29 | * | ||
| 30 | * This watchdog borrows some user semantics from the softdog driver, | ||
| 31 | * in that if you close the fd, it leaves the watchdog running, unless | ||
| 32 | * you previously wrote a 'V' to the fd, in which case it disables | ||
| 33 | * the watchdog when you close the fd like some other drivers. | ||
| 34 | * | ||
| 35 | * Based on various other watchdog drivers, which are probably all | ||
| 36 | * loosely based on something Alan Cox wrote years ago. | ||
| 37 | * | ||
| 38 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
| 39 | * http://www.redhat.com | ||
| 40 | * | ||
| 41 | * This program is free software; you can redistribute it and/or | ||
| 42 | * modify it under the terms of the GNU General Public License | ||
| 43 | * version 1 or 2 as published by the Free Software Foundation. | ||
| 44 | * | ||
| 45 | */ | ||
| 46 | #include <linux/module.h> | ||
| 47 | #include <linux/io.h> | ||
| 48 | #include <linux/uaccess.h> | ||
| 49 | #include <linux/fs.h> | ||
| 50 | #include <linux/reboot.h> | ||
| 51 | #include <linux/miscdevice.h> | ||
| 52 | #include <linux/watchdog.h> | ||
| 53 | #include <linux/interrupt.h> | ||
| 54 | |||
| 55 | #include <asm/sibyte/sb1250.h> | ||
| 56 | #include <asm/sibyte/sb1250_regs.h> | ||
| 57 | #include <asm/sibyte/sb1250_int.h> | ||
| 58 | #include <asm/sibyte/sb1250_scd.h> | ||
| 59 | |||
| 60 | |||
| 61 | /* | ||
| 62 | * set the initial count value of a timer | ||
| 63 | * | ||
| 64 | * wdog is the iomem address of the cfg register | ||
| 65 | */ | ||
| 66 | void sbwdog_set(char __iomem *wdog, unsigned long t) | ||
| 67 | { | ||
| 68 | __raw_writeb(0, wdog - 0x10); | ||
| 69 | __raw_writeq(t & 0x7fffffUL, wdog); | ||
| 70 | } | ||
| 71 | |||
| 72 | /* | ||
| 73 | * cause the timer to [re]load it's initial count and start counting | ||
| 74 | * all over again | ||
| 75 | * | ||
| 76 | * wdog is the iomem address of the cfg register | ||
| 77 | */ | ||
| 78 | void sbwdog_pet(char __iomem *wdog) | ||
| 79 | { | ||
| 80 | __raw_writeb(__raw_readb(wdog) | 1, wdog); | ||
| 81 | } | ||
| 82 | |||
| 83 | static unsigned long sbwdog_gate; /* keeps it to one thread only */ | ||
| 84 | static char __iomem *kern_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_0)); | ||
| 85 | static char __iomem *user_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_1)); | ||
| 86 | static unsigned long timeout = 0x7fffffUL; /* useconds: 8.3ish secs. */ | ||
| 87 | static int expect_close; | ||
| 88 | |||
| 89 | static struct watchdog_info ident = { | ||
| 90 | .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
| 91 | .identity = "SiByte Watchdog", | ||
| 92 | }; | ||
| 93 | |||
| 94 | /* | ||
| 95 | * Allow only a single thread to walk the dog | ||
| 96 | */ | ||
| 97 | static int sbwdog_open(struct inode *inode, struct file *file) | ||
| 98 | { | ||
| 99 | nonseekable_open(inode, file); | ||
| 100 | if (test_and_set_bit(0, &sbwdog_gate)) { | ||
| 101 | return -EBUSY; | ||
| 102 | } | ||
| 103 | __module_get(THIS_MODULE); | ||
| 104 | |||
| 105 | /* | ||
| 106 | * Activate the timer | ||
| 107 | */ | ||
| 108 | sbwdog_set(user_dog, timeout); | ||
| 109 | __raw_writeb(1, user_dog); | ||
| 110 | |||
| 111 | return 0; | ||
| 112 | } | ||
| 113 | |||
| 114 | /* | ||
| 115 | * Put the dog back in the kennel. | ||
| 116 | */ | ||
| 117 | static int sbwdog_release(struct inode *inode, struct file *file) | ||
| 118 | { | ||
| 119 | if (expect_close == 42) { | ||
| 120 | __raw_writeb(0, user_dog); | ||
| 121 | module_put(THIS_MODULE); | ||
| 122 | } else { | ||
| 123 | printk(KERN_CRIT "%s: Unexpected close, not stopping watchdog!\n", | ||
| 124 | ident.identity); | ||
| 125 | sbwdog_pet(user_dog); | ||
| 126 | } | ||
| 127 | clear_bit(0, &sbwdog_gate); | ||
| 128 | expect_close = 0; | ||
| 129 | |||
| 130 | return 0; | ||
| 131 | } | ||
| 132 | |||
| 133 | /* | ||
| 134 | * 42 - the answer | ||
| 135 | */ | ||
| 136 | static ssize_t sbwdog_write(struct file *file, const char __user *data, | ||
| 137 | size_t len, loff_t *ppos) | ||
| 138 | { | ||
| 139 | int i; | ||
| 140 | |||
| 141 | if (len) { | ||
| 142 | /* | ||
| 143 | * restart the timer | ||
| 144 | */ | ||
| 145 | expect_close = 0; | ||
| 146 | |||
| 147 | for (i = 0; i != len; i++) { | ||
| 148 | char c; | ||
| 149 | |||
| 150 | if (get_user(c, data + i)) { | ||
| 151 | return -EFAULT; | ||
| 152 | } | ||
| 153 | if (c == 'V') { | ||
| 154 | expect_close = 42; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | sbwdog_pet(user_dog); | ||
| 158 | } | ||
| 159 | |||
| 160 | return len; | ||
| 161 | } | ||
| 162 | |||
| 163 | static int sbwdog_ioctl(struct inode *inode, struct file *file, | ||
| 164 | unsigned int cmd, unsigned long arg) | ||
| 165 | { | ||
| 166 | int ret = -ENOTTY; | ||
| 167 | unsigned long time; | ||
| 168 | void __user *argp = (void __user *)arg; | ||
| 169 | int __user *p = argp; | ||
| 170 | |||
| 171 | switch (cmd) { | ||
| 172 | case WDIOC_GETSUPPORT: | ||
| 173 | ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; | ||
| 174 | break; | ||
| 175 | |||
| 176 | case WDIOC_GETSTATUS: | ||
| 177 | case WDIOC_GETBOOTSTATUS: | ||
| 178 | ret = put_user(0, p); | ||
| 179 | break; | ||
| 180 | |||
| 181 | case WDIOC_SETTIMEOUT: | ||
| 182 | ret = get_user(time, p); | ||
| 183 | if (ret) { | ||
| 184 | break; | ||
| 185 | } | ||
| 186 | |||
| 187 | time *= 1000000; | ||
| 188 | if (time > 0x7fffffUL) { | ||
| 189 | ret = -EINVAL; | ||
| 190 | break; | ||
| 191 | } | ||
| 192 | timeout = time; | ||
| 193 | sbwdog_set(user_dog, timeout); | ||
| 194 | sbwdog_pet(user_dog); | ||
| 195 | |||
| 196 | case WDIOC_GETTIMEOUT: | ||
| 197 | /* | ||
| 198 | * get the remaining count from the ... count register | ||
| 199 | * which is 1*8 before the config register | ||
| 200 | */ | ||
| 201 | ret = put_user(__raw_readq(user_dog - 8) / 1000000, p); | ||
| 202 | break; | ||
| 203 | |||
| 204 | case WDIOC_KEEPALIVE: | ||
| 205 | sbwdog_pet(user_dog); | ||
| 206 | ret = 0; | ||
| 207 | break; | ||
| 208 | } | ||
| 209 | return ret; | ||
| 210 | } | ||
| 211 | |||
| 212 | /* | ||
| 213 | * Notifier for system down | ||
| 214 | */ | ||
| 215 | static int | ||
| 216 | sbwdog_notify_sys(struct notifier_block *this, unsigned long code, void *erf) | ||
| 217 | { | ||
| 218 | if (code == SYS_DOWN || code == SYS_HALT) { | ||
| 219 | /* | ||
| 220 | * sit and sit | ||
| 221 | */ | ||
| 222 | __raw_writeb(0, user_dog); | ||
| 223 | __raw_writeb(0, kern_dog); | ||
| 224 | } | ||
| 225 | |||
| 226 | return NOTIFY_DONE; | ||
| 227 | } | ||
| 228 | |||
| 229 | static const struct file_operations sbwdog_fops = | ||
| 230 | { | ||
| 231 | .owner = THIS_MODULE, | ||
| 232 | .llseek = no_llseek, | ||
| 233 | .write = sbwdog_write, | ||
| 234 | .ioctl = sbwdog_ioctl, | ||
| 235 | .open = sbwdog_open, | ||
| 236 | .release = sbwdog_release, | ||
| 237 | }; | ||
| 238 | |||
| 239 | static struct miscdevice sbwdog_miscdev = | ||
| 240 | { | ||
| 241 | .minor = WATCHDOG_MINOR, | ||
| 242 | .name = "watchdog", | ||
| 243 | .fops = &sbwdog_fops, | ||
| 244 | }; | ||
| 245 | |||
| 246 | static struct notifier_block sbwdog_notifier = { | ||
| 247 | .notifier_call = sbwdog_notify_sys, | ||
| 248 | }; | ||
| 249 | |||
| 250 | /* | ||
| 251 | * interrupt handler | ||
| 252 | * | ||
| 253 | * doesn't do a whole lot for user, but oh so cleverly written so kernel | ||
| 254 | * code can use it to re-up the watchdog, thereby saving the kernel from | ||
| 255 | * having to create and maintain a timer, just to tickle another timer, | ||
| 256 | * which is just so wrong. | ||
| 257 | */ | ||
| 258 | irqreturn_t sbwdog_interrupt(int irq, void *addr) | ||
| 259 | { | ||
| 260 | unsigned long wd_init; | ||
| 261 | char *wd_cfg_reg = (char *)addr; | ||
| 262 | u8 cfg; | ||
| 263 | |||
| 264 | cfg = __raw_readb(wd_cfg_reg); | ||
| 265 | wd_init = __raw_readq(wd_cfg_reg - 8) & 0x7fffff; | ||
| 266 | |||
| 267 | /* | ||
| 268 | * if it's the second watchdog timer, it's for those users | ||
| 269 | */ | ||
| 270 | if (wd_cfg_reg == user_dog) { | ||
| 271 | printk(KERN_CRIT | ||
| 272 | "%s in danger of initiating system reset in %ld.%01ld seconds\n", | ||
| 273 | ident.identity, wd_init / 1000000, (wd_init / 100000) % 10); | ||
| 274 | } else { | ||
| 275 | cfg |= 1; | ||
| 276 | } | ||
| 277 | |||
| 278 | __raw_writeb(cfg, wd_cfg_reg); | ||
| 279 | |||
| 280 | return IRQ_HANDLED; | ||
| 281 | } | ||
| 282 | |||
| 283 | static int __init sbwdog_init(void) | ||
| 284 | { | ||
| 285 | int ret; | ||
| 286 | |||
| 287 | /* | ||
| 288 | * register a reboot notifier | ||
| 289 | */ | ||
| 290 | ret = register_reboot_notifier(&sbwdog_notifier); | ||
| 291 | if (ret) { | ||
| 292 | printk (KERN_ERR "%s: cannot register reboot notifier (err=%d)\n", | ||
| 293 | ident.identity, ret); | ||
| 294 | return ret; | ||
| 295 | } | ||
| 296 | |||
| 297 | /* | ||
| 298 | * get the resources | ||
| 299 | */ | ||
| 300 | ret = misc_register(&sbwdog_miscdev); | ||
| 301 | if (ret == 0) { | ||
| 302 | printk(KERN_INFO "%s: timeout is %ld.%ld secs\n", ident.identity, | ||
| 303 | timeout / 1000000, (timeout / 100000) % 10); | ||
| 304 | } | ||
| 305 | |||
| 306 | ret = request_irq(1, sbwdog_interrupt, IRQF_DISABLED | IRQF_SHARED, | ||
| 307 | ident.identity, (void *)user_dog); | ||
| 308 | if (ret) { | ||
| 309 | printk(KERN_ERR "%s: failed to request irq 1 - %d\n", ident.identity, | ||
| 310 | ret); | ||
| 311 | misc_deregister(&sbwdog_miscdev); | ||
| 312 | } | ||
| 313 | |||
| 314 | return ret; | ||
| 315 | } | ||
| 316 | |||
| 317 | static void __exit sbwdog_exit(void) | ||
| 318 | { | ||
| 319 | misc_deregister(&sbwdog_miscdev); | ||
| 320 | } | ||
| 321 | |||
| 322 | module_init(sbwdog_init); | ||
| 323 | module_exit(sbwdog_exit); | ||
| 324 | |||
| 325 | MODULE_AUTHOR("Andrew Sharp <andy.sharp@onstor.com>"); | ||
| 326 | MODULE_DESCRIPTION("SiByte Watchdog"); | ||
| 327 | |||
| 328 | module_param(timeout, ulong, 0); | ||
| 329 | MODULE_PARM_DESC(timeout, | ||
| 330 | "Watchdog timeout in microseconds (max/default 8388607 or 8.3ish secs)"); | ||
| 331 | |||
| 332 | MODULE_LICENSE("GPL"); | ||
| 333 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
| 334 | |||
| 335 | /* | ||
| 336 | * example code that can be put in a platform code area to utilize the | ||
| 337 | * first watchdog timer for the kernels own purpose. | ||
| 338 | |||
| 339 | void | ||
| 340 | platform_wd_setup(void) | ||
| 341 | { | ||
| 342 | int ret; | ||
| 343 | |||
| 344 | ret = request_irq(0, sbwdog_interrupt, IRQF_DISABLED | IRQF_SHARED, | ||
| 345 | "Kernel Watchdog", IOADDR(A_SCD_WDOG_CFG_0)); | ||
| 346 | if (ret) { | ||
| 347 | printk(KERN_CRIT "Watchdog IRQ zero(0) failed to be requested - %d\n", | ||
| 348 | ret); | ||
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | |||
| 353 | */ | ||
