1 /* 2 * drivers/watchdog/orion_wdt.c 3 * 4 * Watchdog driver for Orion/Kirkwood processors 5 * 6 * Author: Sylver Bruneau <sylver.bruneau@googlemail.com> 7 * 8 * This file is licensed under the terms of the GNU General Public 9 * License version 2. This program is licensed "as is" without any 10 * warranty of any kind, whether express or implied. 11 */ 12 13 #include <linux/module.h> 14 #include <linux/moduleparam.h> 15 #include <linux/types.h> 16 #include <linux/kernel.h> 17 #include <linux/fs.h> 18 #include <linux/miscdevice.h> 19 #include <linux/platform_device.h> 20 #include <linux/watchdog.h> 21 #include <linux/init.h> 22 #include <linux/uaccess.h> 23 #include <linux/io.h> 24 #include <linux/spinlock.h> 25 #include <mach/bridge-regs.h> 26 #include <plat/orion_wdt.h> 27 28 /* 29 * Watchdog timer block registers. 30 */ 31 #define TIMER_CTRL (TIMER_VIRT_BASE + 0x0000) 32 #define WDT_EN 0x0010 33 #define WDT_VAL (TIMER_VIRT_BASE + 0x0024) 34 35 #define WDT_MAX_CYCLE_COUNT 0xffffffff 36 #define WDT_IN_USE 0 37 #define WDT_OK_TO_CLOSE 1 38 39 static int nowayout = WATCHDOG_NOWAYOUT; 40 static int heartbeat = -1; /* module parameter (seconds) */ 41 static unsigned int wdt_max_duration; /* (seconds) */ 42 static unsigned int wdt_tclk; 43 static unsigned long wdt_status; 44 static spinlock_t wdt_lock; 45 46 static void orion_wdt_ping(void) 47 { 48 spin_lock(&wdt_lock); 49 50 /* Reload watchdog duration */ 51 writel(wdt_tclk * heartbeat, WDT_VAL); 52 53 spin_unlock(&wdt_lock); 54 } 55 56 static void orion_wdt_enable(void) 57 { 58 u32 reg; 59 60 spin_lock(&wdt_lock); 61 62 /* Set watchdog duration */ 63 writel(wdt_tclk * heartbeat, WDT_VAL); 64 65 /* Clear watchdog timer interrupt */ 66 reg = readl(BRIDGE_CAUSE); 67 reg &= ~WDT_INT_REQ; 68 writel(reg, BRIDGE_CAUSE); 69 70 /* Enable watchdog timer */ 71 reg = readl(TIMER_CTRL); 72 reg |= WDT_EN; 73 writel(reg, TIMER_CTRL); 74 75 /* Enable reset on watchdog */ 76 reg = readl(RSTOUTn_MASK); 77 reg |= WDT_RESET_OUT_EN; 78 writel(reg, RSTOUTn_MASK); 79 80 spin_unlock(&wdt_lock); 81 } 82 83 static void orion_wdt_disable(void) 84 { 85 u32 reg; 86 87 spin_lock(&wdt_lock); 88 89 /* Disable reset on watchdog */ 90 reg = readl(RSTOUTn_MASK); 91 reg &= ~WDT_RESET_OUT_EN; 92 writel(reg, RSTOUTn_MASK); 93 94 /* Disable watchdog timer */ 95 reg = readl(TIMER_CTRL); 96 reg &= ~WDT_EN; 97 writel(reg, TIMER_CTRL); 98 99 spin_unlock(&wdt_lock); 100 } 101 102 static int orion_wdt_get_timeleft(int *time_left) 103 { 104 spin_lock(&wdt_lock); 105 *time_left = readl(WDT_VAL) / wdt_tclk; 106 spin_unlock(&wdt_lock); 107 return 0; 108 } 109 110 static int orion_wdt_open(struct inode *inode, struct file *file) 111 { 112 if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 113 return -EBUSY; 114 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 115 orion_wdt_enable(); 116 return nonseekable_open(inode, file); 117 } 118 119 static ssize_t orion_wdt_write(struct file *file, const char *data, 120 size_t len, loff_t *ppos) 121 { 122 if (len) { 123 if (!nowayout) { 124 size_t i; 125 126 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 127 for (i = 0; i != len; i++) { 128 char c; 129 130 if (get_user(c, data + i)) 131 return -EFAULT; 132 if (c == 'V') 133 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 134 } 135 } 136 orion_wdt_ping(); 137 } 138 return len; 139 } 140 141 static int orion_wdt_settimeout(int new_time) 142 { 143 if ((new_time <= 0) || (new_time > wdt_max_duration)) 144 return -EINVAL; 145 146 /* Set new watchdog time to be used when 147 * orion_wdt_enable() or orion_wdt_ping() is called. */ 148 heartbeat = new_time; 149 return 0; 150 } 151 152 static const struct watchdog_info ident = { 153 .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | 154 WDIOF_KEEPALIVEPING, 155 .identity = "Orion Watchdog", 156 }; 157 158 static long orion_wdt_ioctl(struct file *file, unsigned int cmd, 159 unsigned long arg) 160 { 161 int ret = -ENOTTY; 162 int time; 163 164 switch (cmd) { 165 case WDIOC_GETSUPPORT: 166 ret = copy_to_user((struct watchdog_info *)arg, &ident, 167 sizeof(ident)) ? -EFAULT : 0; 168 break; 169 170 case WDIOC_GETSTATUS: 171 case WDIOC_GETBOOTSTATUS: 172 ret = put_user(0, (int *)arg); 173 break; 174 175 case WDIOC_KEEPALIVE: 176 orion_wdt_ping(); 177 ret = 0; 178 break; 179 180 case WDIOC_SETTIMEOUT: 181 ret = get_user(time, (int *)arg); 182 if (ret) 183 break; 184 185 if (orion_wdt_settimeout(time)) { 186 ret = -EINVAL; 187 break; 188 } 189 orion_wdt_ping(); 190 /* Fall through */ 191 192 case WDIOC_GETTIMEOUT: 193 ret = put_user(heartbeat, (int *)arg); 194 break; 195 196 case WDIOC_GETTIMELEFT: 197 if (orion_wdt_get_timeleft(&time)) { 198 ret = -EINVAL; 199 break; 200 } 201 ret = put_user(time, (int *)arg); 202 break; 203 } 204 return ret; 205 } 206 207 static int orion_wdt_release(struct inode *inode, struct file *file) 208 { 209 if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) 210 orion_wdt_disable(); 211 else 212 printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " 213 "timer will not stop\n"); 214 clear_bit(WDT_IN_USE, &wdt_status); 215 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 216 217 return 0; 218 } 219 220 221 static const struct file_operations orion_wdt_fops = { 222 .owner = THIS_MODULE, 223 .llseek = no_llseek, 224 .write = orion_wdt_write, 225 .unlocked_ioctl = orion_wdt_ioctl, 226 .open = orion_wdt_open, 227 .release = orion_wdt_release, 228 }; 229 230 static struct miscdevice orion_wdt_miscdev = { 231 .minor = WATCHDOG_MINOR, 232 .name = "watchdog", 233 .fops = &orion_wdt_fops, 234 }; 235 236 static int __devinit orion_wdt_probe(struct platform_device *pdev) 237 { 238 struct orion_wdt_platform_data *pdata = pdev->dev.platform_data; 239 int ret; 240 241 if (pdata) { 242 wdt_tclk = pdata->tclk; 243 } else { 244 printk(KERN_ERR "Orion Watchdog misses platform data\n"); 245 return -ENODEV; 246 } 247 248 if (orion_wdt_miscdev.parent) 249 return -EBUSY; 250 orion_wdt_miscdev.parent = &pdev->dev; 251 252 wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk; 253 if (orion_wdt_settimeout(heartbeat)) 254 heartbeat = wdt_max_duration; 255 256 ret = misc_register(&orion_wdt_miscdev); 257 if (ret) 258 return ret; 259 260 printk(KERN_INFO "Orion Watchdog Timer: Initial timeout %d sec%s\n", 261 heartbeat, nowayout ? ", nowayout" : ""); 262 return 0; 263 } 264 265 static int __devexit orion_wdt_remove(struct platform_device *pdev) 266 { 267 int ret; 268 269 if (test_bit(WDT_IN_USE, &wdt_status)) { 270 orion_wdt_disable(); 271 clear_bit(WDT_IN_USE, &wdt_status); 272 } 273 274 ret = misc_deregister(&orion_wdt_miscdev); 275 if (!ret) 276 orion_wdt_miscdev.parent = NULL; 277 278 return ret; 279 } 280 281 static void orion_wdt_shutdown(struct platform_device *pdev) 282 { 283 if (test_bit(WDT_IN_USE, &wdt_status)) 284 orion_wdt_disable(); 285 } 286 287 static struct platform_driver orion_wdt_driver = { 288 .probe = orion_wdt_probe, 289 .remove = __devexit_p(orion_wdt_remove), 290 .shutdown = orion_wdt_shutdown, 291 .driver = { 292 .owner = THIS_MODULE, 293 .name = "orion_wdt", 294 }, 295 }; 296 297 static int __init orion_wdt_init(void) 298 { 299 spin_lock_init(&wdt_lock); 300 return platform_driver_register(&orion_wdt_driver); 301 } 302 303 static void __exit orion_wdt_exit(void) 304 { 305 platform_driver_unregister(&orion_wdt_driver); 306 } 307 308 module_init(orion_wdt_init); 309 module_exit(orion_wdt_exit); 310 311 MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>"); 312 MODULE_DESCRIPTION("Orion Processor Watchdog"); 313 314 module_param(heartbeat, int, 0); 315 MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds"); 316 317 module_param(nowayout, int, 0); 318 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 319 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 320 321 MODULE_LICENSE("GPL"); 322 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 323