1 /* 2 * Watchdog driver for Broadcom BCM47XX 3 * 4 * Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs> 5 * Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.fr> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 10 * 2 of the License, or (at your option) any later version. 11 */ 12 13 #include <linux/bitops.h> 14 #include <linux/errno.h> 15 #include <linux/fs.h> 16 #include <linux/init.h> 17 #include <linux/kernel.h> 18 #include <linux/miscdevice.h> 19 #include <linux/module.h> 20 #include <linux/moduleparam.h> 21 #include <linux/reboot.h> 22 #include <linux/types.h> 23 #include <linux/uaccess.h> 24 #include <linux/watchdog.h> 25 #include <linux/timer.h> 26 #include <linux/jiffies.h> 27 #include <linux/ssb/ssb_embedded.h> 28 #include <asm/mach-bcm47xx/bcm47xx.h> 29 30 #define DRV_NAME "bcm47xx_wdt" 31 32 #define WDT_DEFAULT_TIME 30 /* seconds */ 33 #define WDT_MAX_TIME 255 /* seconds */ 34 35 static int wdt_time = WDT_DEFAULT_TIME; 36 static int nowayout = WATCHDOG_NOWAYOUT; 37 38 module_param(wdt_time, int, 0); 39 MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default=" 40 __MODULE_STRING(WDT_DEFAULT_TIME) ")"); 41 42 #ifdef CONFIG_WATCHDOG_NOWAYOUT 43 module_param(nowayout, int, 0); 44 MODULE_PARM_DESC(nowayout, 45 "Watchdog cannot be stopped once started (default=" 46 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 47 #endif 48 49 static unsigned long bcm47xx_wdt_busy; 50 static char expect_release; 51 static struct timer_list wdt_timer; 52 static atomic_t ticks; 53 54 static inline void bcm47xx_wdt_hw_start(void) 55 { 56 /* this is 2,5s on 100Mhz clock and 2s on 133 Mhz */ 57 switch (bcm47xx_bus_type) { 58 #ifdef CONFIG_BCM47XX_SSB 59 case BCM47XX_BUS_TYPE_SSB: 60 ssb_watchdog_timer_set(&bcm47xx_bus.ssb, 0xfffffff); 61 break; 62 #endif 63 #ifdef CONFIG_BCM47XX_BCMA 64 case BCM47XX_BUS_TYPE_BCMA: 65 bcma_chipco_watchdog_timer_set(&bcm47xx_bus.bcma.bus.drv_cc, 66 0xfffffff); 67 break; 68 #endif 69 } 70 } 71 72 static inline int bcm47xx_wdt_hw_stop(void) 73 { 74 switch (bcm47xx_bus_type) { 75 #ifdef CONFIG_BCM47XX_SSB 76 case BCM47XX_BUS_TYPE_SSB: 77 return ssb_watchdog_timer_set(&bcm47xx_bus.ssb, 0); 78 #endif 79 #ifdef CONFIG_BCM47XX_BCMA 80 case BCM47XX_BUS_TYPE_BCMA: 81 bcma_chipco_watchdog_timer_set(&bcm47xx_bus.bcma.bus.drv_cc, 0); 82 return 0; 83 #endif 84 } 85 return -EINVAL; 86 } 87 88 static void bcm47xx_timer_tick(unsigned long unused) 89 { 90 if (!atomic_dec_and_test(&ticks)) { 91 bcm47xx_wdt_hw_start(); 92 mod_timer(&wdt_timer, jiffies + HZ); 93 } else { 94 printk(KERN_CRIT DRV_NAME "Watchdog will fire soon!!!\n"); 95 } 96 } 97 98 static inline void bcm47xx_wdt_pet(void) 99 { 100 atomic_set(&ticks, wdt_time); 101 } 102 103 static void bcm47xx_wdt_start(void) 104 { 105 bcm47xx_wdt_pet(); 106 bcm47xx_timer_tick(0); 107 } 108 109 static void bcm47xx_wdt_pause(void) 110 { 111 del_timer_sync(&wdt_timer); 112 bcm47xx_wdt_hw_stop(); 113 } 114 115 static void bcm47xx_wdt_stop(void) 116 { 117 bcm47xx_wdt_pause(); 118 } 119 120 static int bcm47xx_wdt_settimeout(int new_time) 121 { 122 if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) 123 return -EINVAL; 124 125 wdt_time = new_time; 126 return 0; 127 } 128 129 static int bcm47xx_wdt_open(struct inode *inode, struct file *file) 130 { 131 if (test_and_set_bit(0, &bcm47xx_wdt_busy)) 132 return -EBUSY; 133 134 bcm47xx_wdt_start(); 135 return nonseekable_open(inode, file); 136 } 137 138 static int bcm47xx_wdt_release(struct inode *inode, struct file *file) 139 { 140 if (expect_release == 42) { 141 bcm47xx_wdt_stop(); 142 } else { 143 printk(KERN_CRIT DRV_NAME 144 ": Unexpected close, not stopping watchdog!\n"); 145 bcm47xx_wdt_start(); 146 } 147 148 clear_bit(0, &bcm47xx_wdt_busy); 149 expect_release = 0; 150 return 0; 151 } 152 153 static ssize_t bcm47xx_wdt_write(struct file *file, const char __user *data, 154 size_t len, loff_t *ppos) 155 { 156 if (len) { 157 if (!nowayout) { 158 size_t i; 159 160 expect_release = 0; 161 162 for (i = 0; i != len; i++) { 163 char c; 164 if (get_user(c, data + i)) 165 return -EFAULT; 166 if (c == 'V') 167 expect_release = 42; 168 } 169 } 170 bcm47xx_wdt_pet(); 171 } 172 return len; 173 } 174 175 static const struct watchdog_info bcm47xx_wdt_info = { 176 .identity = DRV_NAME, 177 .options = WDIOF_SETTIMEOUT | 178 WDIOF_KEEPALIVEPING | 179 WDIOF_MAGICCLOSE, 180 }; 181 182 static long bcm47xx_wdt_ioctl(struct file *file, 183 unsigned int cmd, unsigned long arg) 184 { 185 void __user *argp = (void __user *)arg; 186 int __user *p = argp; 187 int new_value, retval = -EINVAL; 188 189 switch (cmd) { 190 case WDIOC_GETSUPPORT: 191 return copy_to_user(argp, &bcm47xx_wdt_info, 192 sizeof(bcm47xx_wdt_info)) ? -EFAULT : 0; 193 194 case WDIOC_GETSTATUS: 195 case WDIOC_GETBOOTSTATUS: 196 return put_user(0, p); 197 198 case WDIOC_SETOPTIONS: 199 if (get_user(new_value, p)) 200 return -EFAULT; 201 202 if (new_value & WDIOS_DISABLECARD) { 203 bcm47xx_wdt_stop(); 204 retval = 0; 205 } 206 207 if (new_value & WDIOS_ENABLECARD) { 208 bcm47xx_wdt_start(); 209 retval = 0; 210 } 211 212 return retval; 213 214 case WDIOC_KEEPALIVE: 215 bcm47xx_wdt_pet(); 216 return 0; 217 218 case WDIOC_SETTIMEOUT: 219 if (get_user(new_value, p)) 220 return -EFAULT; 221 222 if (bcm47xx_wdt_settimeout(new_value)) 223 return -EINVAL; 224 225 bcm47xx_wdt_pet(); 226 227 case WDIOC_GETTIMEOUT: 228 return put_user(wdt_time, p); 229 230 default: 231 return -ENOTTY; 232 } 233 } 234 235 static int bcm47xx_wdt_notify_sys(struct notifier_block *this, 236 unsigned long code, void *unused) 237 { 238 if (code == SYS_DOWN || code == SYS_HALT) 239 bcm47xx_wdt_stop(); 240 return NOTIFY_DONE; 241 } 242 243 static const struct file_operations bcm47xx_wdt_fops = { 244 .owner = THIS_MODULE, 245 .llseek = no_llseek, 246 .unlocked_ioctl = bcm47xx_wdt_ioctl, 247 .open = bcm47xx_wdt_open, 248 .release = bcm47xx_wdt_release, 249 .write = bcm47xx_wdt_write, 250 }; 251 252 static struct miscdevice bcm47xx_wdt_miscdev = { 253 .minor = WATCHDOG_MINOR, 254 .name = "watchdog", 255 .fops = &bcm47xx_wdt_fops, 256 }; 257 258 static struct notifier_block bcm47xx_wdt_notifier = { 259 .notifier_call = bcm47xx_wdt_notify_sys, 260 }; 261 262 static int __init bcm47xx_wdt_init(void) 263 { 264 int ret; 265 266 if (bcm47xx_wdt_hw_stop() < 0) 267 return -ENODEV; 268 269 setup_timer(&wdt_timer, bcm47xx_timer_tick, 0L); 270 271 if (bcm47xx_wdt_settimeout(wdt_time)) { 272 bcm47xx_wdt_settimeout(WDT_DEFAULT_TIME); 273 printk(KERN_INFO DRV_NAME ": " 274 "wdt_time value must be 0 < wdt_time < %d, using %d\n", 275 (WDT_MAX_TIME + 1), wdt_time); 276 } 277 278 ret = register_reboot_notifier(&bcm47xx_wdt_notifier); 279 if (ret) 280 return ret; 281 282 ret = misc_register(&bcm47xx_wdt_miscdev); 283 if (ret) { 284 unregister_reboot_notifier(&bcm47xx_wdt_notifier); 285 return ret; 286 } 287 288 printk(KERN_INFO "BCM47xx Watchdog Timer enabled (%d seconds%s)\n", 289 wdt_time, nowayout ? ", nowayout" : ""); 290 return 0; 291 } 292 293 static void __exit bcm47xx_wdt_exit(void) 294 { 295 if (!nowayout) 296 bcm47xx_wdt_stop(); 297 298 misc_deregister(&bcm47xx_wdt_miscdev); 299 300 unregister_reboot_notifier(&bcm47xx_wdt_notifier); 301 } 302 303 module_init(bcm47xx_wdt_init); 304 module_exit(bcm47xx_wdt_exit); 305 306 MODULE_AUTHOR("Aleksandar Radovanovic"); 307 MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx"); 308 MODULE_LICENSE("GPL"); 309 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 310