[WATCHDOG] AR7: watchdog timer

Driver for the watchdog timer. Still doesn't reboots the machine
on some boards, but we have improved and cleaned it

Signed-off-by: Matteo Croce <technoboy85@gmail.com>
Signed-off-by: Nicolas Thill <nico@openwrt.org>
Signed-off-by: Enrik Berkhan <Enrik.Berkhan@akk.org>
Signed-off-by: Christer Weinigel <wingel@nano-system.com>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>

authored by Matteo Croce and committed by Wim Van Sebroeck c283cf2c 01ed08c1

+356
+6
drivers/watchdog/Kconfig
··· 610 610 To compile this driver as a module, choose M here: the 611 611 module will be called rm9k_wdt. 612 612 613 + config AR7_WDT 614 + tristate "TI AR7 Watchdog Timer" 615 + depends on AR7 616 + help 617 + Hardware driver for the TI AR7 Watchdog Timer. 618 + 613 619 # PARISC Architecture 614 620 615 621 # POWERPC Architecture
+1
drivers/watchdog/Makefile
··· 90 90 obj-$(CONFIG_INDYDOG) += indydog.o 91 91 obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o 92 92 obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o 93 + obj-$(CONFIG_AR7_WDT) += ar7_wdt.o 93 94 94 95 # PARISC Architecture 95 96
+349
drivers/watchdog/ar7_wdt.c
··· 1 + /* 2 + * drivers/watchdog/ar7_wdt.c 3 + * 4 + * Copyright (C) 2007 Nicolas Thill <nico@openwrt.org> 5 + * Copyright (c) 2005 Enrik Berkhan <Enrik.Berkhan@akk.org> 6 + * 7 + * Some code taken from: 8 + * National Semiconductor SCx200 Watchdog support 9 + * Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> 10 + * 11 + * This program is free software; you can redistribute it and/or modify 12 + * it under the terms of the GNU General Public License as published by 13 + * the Free Software Foundation; either version 2 of the License, or 14 + * (at your option) any later version. 15 + * 16 + * This program is distributed in the hope that it will be useful, 17 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 + * GNU General Public License for more details. 20 + * 21 + * You should have received a copy of the GNU General Public License 22 + * along with this program; if not, write to the Free Software 23 + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 24 + */ 25 + 26 + #include <linux/module.h> 27 + #include <linux/moduleparam.h> 28 + #include <linux/errno.h> 29 + #include <linux/init.h> 30 + #include <linux/miscdevice.h> 31 + #include <linux/watchdog.h> 32 + #include <linux/notifier.h> 33 + #include <linux/reboot.h> 34 + #include <linux/fs.h> 35 + #include <linux/ioport.h> 36 + #include <linux/io.h> 37 + #include <linux/uaccess.h> 38 + 39 + #include <asm/addrspace.h> 40 + #include <asm/ar7/ar7.h> 41 + 42 + #define DRVNAME "ar7_wdt" 43 + #define LONGNAME "TI AR7 Watchdog Timer" 44 + 45 + MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>"); 46 + MODULE_DESCRIPTION(LONGNAME); 47 + MODULE_LICENSE("GPL"); 48 + MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 49 + 50 + static int margin = 60; 51 + module_param(margin, int, 0); 52 + MODULE_PARM_DESC(margin, "Watchdog margin in seconds"); 53 + 54 + static int nowayout = WATCHDOG_NOWAYOUT; 55 + module_param(nowayout, int, 0); 56 + MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); 57 + 58 + #define READ_REG(x) readl((void __iomem *)&(x)) 59 + #define WRITE_REG(x, v) writel((v), (void __iomem *)&(x)) 60 + 61 + struct ar7_wdt { 62 + u32 kick_lock; 63 + u32 kick; 64 + u32 change_lock; 65 + u32 change; 66 + u32 disable_lock; 67 + u32 disable; 68 + u32 prescale_lock; 69 + u32 prescale; 70 + }; 71 + 72 + static struct semaphore open_semaphore; 73 + static unsigned expect_close; 74 + 75 + /* XXX currently fixed, allows max margin ~68.72 secs */ 76 + #define prescale_value 0xffff 77 + 78 + /* Offset of the WDT registers */ 79 + static unsigned long ar7_regs_wdt; 80 + /* Pointer to the remapped WDT IO space */ 81 + static struct ar7_wdt *ar7_wdt; 82 + static void ar7_wdt_get_regs(void) 83 + { 84 + u16 chip_id = ar7_chip_id(); 85 + switch (chip_id) { 86 + case AR7_CHIP_7100: 87 + case AR7_CHIP_7200: 88 + ar7_regs_wdt = AR7_REGS_WDT; 89 + break; 90 + default: 91 + ar7_regs_wdt = UR8_REGS_WDT; 92 + break; 93 + } 94 + } 95 + 96 + 97 + static void ar7_wdt_kick(u32 value) 98 + { 99 + WRITE_REG(ar7_wdt->kick_lock, 0x5555); 100 + if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) { 101 + WRITE_REG(ar7_wdt->kick_lock, 0xaaaa); 102 + if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) { 103 + WRITE_REG(ar7_wdt->kick, value); 104 + return; 105 + } 106 + } 107 + printk(KERN_ERR DRVNAME ": failed to unlock WDT kick reg\n"); 108 + } 109 + 110 + static void ar7_wdt_prescale(u32 value) 111 + { 112 + WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a); 113 + if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) { 114 + WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5); 115 + if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) { 116 + WRITE_REG(ar7_wdt->prescale, value); 117 + return; 118 + } 119 + } 120 + printk(KERN_ERR DRVNAME ": failed to unlock WDT prescale reg\n"); 121 + } 122 + 123 + static void ar7_wdt_change(u32 value) 124 + { 125 + WRITE_REG(ar7_wdt->change_lock, 0x6666); 126 + if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) { 127 + WRITE_REG(ar7_wdt->change_lock, 0xbbbb); 128 + if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) { 129 + WRITE_REG(ar7_wdt->change, value); 130 + return; 131 + } 132 + } 133 + printk(KERN_ERR DRVNAME ": failed to unlock WDT change reg\n"); 134 + } 135 + 136 + static void ar7_wdt_disable(u32 value) 137 + { 138 + WRITE_REG(ar7_wdt->disable_lock, 0x7777); 139 + if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) { 140 + WRITE_REG(ar7_wdt->disable_lock, 0xcccc); 141 + if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) { 142 + WRITE_REG(ar7_wdt->disable_lock, 0xdddd); 143 + if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) { 144 + WRITE_REG(ar7_wdt->disable, value); 145 + return; 146 + } 147 + } 148 + } 149 + printk(KERN_ERR DRVNAME ": failed to unlock WDT disable reg\n"); 150 + } 151 + 152 + static void ar7_wdt_update_margin(int new_margin) 153 + { 154 + u32 change; 155 + 156 + change = new_margin * (ar7_vbus_freq() / prescale_value); 157 + if (change < 1) change = 1; 158 + if (change > 0xffff) change = 0xffff; 159 + ar7_wdt_change(change); 160 + margin = change * prescale_value / ar7_vbus_freq(); 161 + printk(KERN_INFO DRVNAME 162 + ": timer margin %d seconds (prescale %d, change %d, freq %d)\n", 163 + margin, prescale_value, change, ar7_vbus_freq()); 164 + } 165 + 166 + static void ar7_wdt_enable_wdt(void) 167 + { 168 + printk(KERN_DEBUG DRVNAME ": enabling watchdog timer\n"); 169 + ar7_wdt_disable(1); 170 + ar7_wdt_kick(1); 171 + } 172 + 173 + static void ar7_wdt_disable_wdt(void) 174 + { 175 + printk(KERN_DEBUG DRVNAME ": disabling watchdog timer\n"); 176 + ar7_wdt_disable(0); 177 + } 178 + 179 + static int ar7_wdt_open(struct inode *inode, struct file *file) 180 + { 181 + /* only allow one at a time */ 182 + if (down_trylock(&open_semaphore)) 183 + return -EBUSY; 184 + ar7_wdt_enable_wdt(); 185 + expect_close = 0; 186 + 187 + return nonseekable_open(inode, file); 188 + } 189 + 190 + static int ar7_wdt_release(struct inode *inode, struct file *file) 191 + { 192 + if (!expect_close) 193 + printk(KERN_WARNING DRVNAME 194 + ": watchdog device closed unexpectedly," 195 + "will not disable the watchdog timer\n"); 196 + else if (!nowayout) 197 + ar7_wdt_disable_wdt(); 198 + 199 + up(&open_semaphore); 200 + 201 + return 0; 202 + } 203 + 204 + static int ar7_wdt_notify_sys(struct notifier_block *this, 205 + unsigned long code, void *unused) 206 + { 207 + if (code == SYS_HALT || code == SYS_POWER_OFF) 208 + if (!nowayout) 209 + ar7_wdt_disable_wdt(); 210 + 211 + return NOTIFY_DONE; 212 + } 213 + 214 + static struct notifier_block ar7_wdt_notifier = { 215 + .notifier_call = ar7_wdt_notify_sys 216 + }; 217 + 218 + static ssize_t ar7_wdt_write(struct file *file, const char *data, 219 + size_t len, loff_t *ppos) 220 + { 221 + /* check for a magic close character */ 222 + if (len) { 223 + size_t i; 224 + 225 + ar7_wdt_kick(1); 226 + 227 + expect_close = 0; 228 + for (i = 0; i < len; ++i) { 229 + char c; 230 + if (get_user(c, data+i)) 231 + return -EFAULT; 232 + if (c == 'V') 233 + expect_close = 1; 234 + } 235 + 236 + } 237 + return len; 238 + } 239 + 240 + static int ar7_wdt_ioctl(struct inode *inode, struct file *file, 241 + unsigned int cmd, unsigned long arg) 242 + { 243 + static struct watchdog_info ident = { 244 + .identity = LONGNAME, 245 + .firmware_version = 1, 246 + .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING), 247 + }; 248 + int new_margin; 249 + 250 + switch (cmd) { 251 + default: 252 + return -ENOTTY; 253 + case WDIOC_GETSUPPORT: 254 + if (copy_to_user((struct watchdog_info *)arg, &ident, 255 + sizeof(ident))) 256 + return -EFAULT; 257 + return 0; 258 + case WDIOC_GETSTATUS: 259 + case WDIOC_GETBOOTSTATUS: 260 + if (put_user(0, (int *)arg)) 261 + return -EFAULT; 262 + return 0; 263 + case WDIOC_KEEPALIVE: 264 + ar7_wdt_kick(1); 265 + return 0; 266 + case WDIOC_SETTIMEOUT: 267 + if (get_user(new_margin, (int *)arg)) 268 + return -EFAULT; 269 + if (new_margin < 1) 270 + return -EINVAL; 271 + 272 + ar7_wdt_update_margin(new_margin); 273 + ar7_wdt_kick(1); 274 + 275 + case WDIOC_GETTIMEOUT: 276 + if (put_user(margin, (int *)arg)) 277 + return -EFAULT; 278 + return 0; 279 + } 280 + } 281 + 282 + static struct file_operations ar7_wdt_fops = { 283 + .owner = THIS_MODULE, 284 + .write = ar7_wdt_write, 285 + .ioctl = ar7_wdt_ioctl, 286 + .open = ar7_wdt_open, 287 + .release = ar7_wdt_release, 288 + }; 289 + 290 + static struct miscdevice ar7_wdt_miscdev = { 291 + .minor = WATCHDOG_MINOR, 292 + .name = "watchdog", 293 + .fops = &ar7_wdt_fops, 294 + }; 295 + 296 + static int __init ar7_wdt_init(void) 297 + { 298 + int rc; 299 + 300 + ar7_wdt_get_regs(); 301 + 302 + if (!request_mem_region(ar7_regs_wdt, sizeof(struct ar7_wdt), 303 + LONGNAME)) { 304 + printk(KERN_WARNING DRVNAME ": watchdog I/O region busy\n"); 305 + return -EBUSY; 306 + } 307 + 308 + ar7_wdt = (struct ar7_wdt *) 309 + ioremap(ar7_regs_wdt, sizeof(struct ar7_wdt)); 310 + 311 + ar7_wdt_disable_wdt(); 312 + ar7_wdt_prescale(prescale_value); 313 + ar7_wdt_update_margin(margin); 314 + 315 + sema_init(&open_semaphore, 1); 316 + 317 + rc = register_reboot_notifier(&ar7_wdt_notifier); 318 + if (rc) { 319 + printk(KERN_ERR DRVNAME 320 + ": unable to register reboot notifier\n"); 321 + goto out_alloc; 322 + } 323 + 324 + rc = misc_register(&ar7_wdt_miscdev); 325 + if (rc) { 326 + printk(KERN_ERR DRVNAME ": unable to register misc device\n"); 327 + goto out_register; 328 + } 329 + goto out; 330 + 331 + out_register: 332 + unregister_reboot_notifier(&ar7_wdt_notifier); 333 + out_alloc: 334 + iounmap(ar7_wdt); 335 + release_mem_region(ar7_regs_wdt, sizeof(struct ar7_wdt)); 336 + out: 337 + return rc; 338 + } 339 + 340 + static void __exit ar7_wdt_cleanup(void) 341 + { 342 + misc_deregister(&ar7_wdt_miscdev); 343 + unregister_reboot_notifier(&ar7_wdt_notifier); 344 + iounmap(ar7_wdt); 345 + release_mem_region(ar7_regs_wdt, sizeof(struct ar7_wdt)); 346 + } 347 + 348 + module_init(ar7_wdt_init); 349 + module_exit(ar7_wdt_cleanup);