1/* 2 * IDT Interprise 79RC32434 watchdog driver 3 * 4 * Copyright (C) 2006, Ondrej Zajicek <santiago@crfreenet.org> 5 * Copyright (C) 2008, Florian Fainelli <florian@openwrt.org> 6 * 7 * based on 8 * SoftDog 0.05: A Software Watchdog Device 9 * 10 * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, 11 * All Rights Reserved. 12 * 13 * This program is free software; you can redistribute it and/or 14 * modify it under the terms of the GNU General Public License 15 * as published by the Free Software Foundation; either version 16 * 2 of the License, or (at your option) any later version. 17 * 18 */ 19 20#include <linux/module.h> /* For module specific items */ 21#include <linux/moduleparam.h> /* For new moduleparam's */ 22#include <linux/types.h> /* For standard types (like size_t) */ 23#include <linux/errno.h> /* For the -ENODEV/... values */ 24#include <linux/kernel.h> /* For printk/panic/... */ 25#include <linux/fs.h> /* For file operations */ 26#include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV 27 (WATCHDOG_MINOR) */ 28#include <linux/watchdog.h> /* For the watchdog specific items */ 29#include <linux/init.h> /* For __init/__exit/... */ 30#include <linux/platform_device.h> /* For platform_driver framework */ 31#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ 32#include <linux/uaccess.h> /* For copy_to_user/put_user/... */ 33 34#include <asm/mach-rc32434/integ.h> /* For the Watchdog registers */ 35 36#define PFX KBUILD_MODNAME ": " 37 38#define VERSION "1.0" 39 40static struct { 41 unsigned long inuse; 42 spinlock_t io_lock; 43} rc32434_wdt_device; 44 45static struct integ __iomem *wdt_reg; 46 47static int expect_close; 48 49/* Board internal clock speed in Hz, 50 * the watchdog timer ticks at. */ 51extern unsigned int idt_cpu_freq; 52 53/* translate wtcompare value to seconds and vice versa */ 54#define WTCOMP2SEC(x) (x / idt_cpu_freq) 55#define SEC2WTCOMP(x) (x * idt_cpu_freq) 56 57/* Use a default timeout of 20s. This should be 58 * safe for CPU clock speeds up to 400MHz, as 59 * ((2 ^ 32) - 1) / (400MHz / 2) = 21s. */ 60#define WATCHDOG_TIMEOUT 20 61 62static int timeout = WATCHDOG_TIMEOUT; 63module_param(timeout, int, 0); 64MODULE_PARM_DESC(timeout, "Watchdog timeout value, in seconds (default=" 65 __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); 66 67static int nowayout = WATCHDOG_NOWAYOUT; 68module_param(nowayout, int, 0); 69MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 70 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 71 72/* apply or and nand masks to data read from addr and write back */ 73#define SET_BITS(addr, or, nand) \ 74 writel((readl(&addr) | or) & ~nand, &addr) 75 76static int rc32434_wdt_set(int new_timeout) 77{ 78 int max_to = WTCOMP2SEC((u32)-1); 79 80 if (new_timeout < 0 || new_timeout > max_to) { 81 printk(KERN_ERR PFX "timeout value must be between 0 and %d", 82 max_to); 83 return -EINVAL; 84 } 85 timeout = new_timeout; 86 spin_lock(&rc32434_wdt_device.io_lock); 87 writel(SEC2WTCOMP(timeout), &wdt_reg->wtcompare); 88 spin_unlock(&rc32434_wdt_device.io_lock); 89 90 return 0; 91} 92 93static void rc32434_wdt_start(void) 94{ 95 u32 or, nand; 96 97 spin_lock(&rc32434_wdt_device.io_lock); 98 99 /* zero the counter before enabling */ 100 writel(0, &wdt_reg->wtcount); 101 102 /* don't generate a non-maskable interrupt, 103 * do a warm reset instead */ 104 nand = 1 << RC32434_ERR_WNE; 105 or = 1 << RC32434_ERR_WRE; 106 107 /* reset the ERRCS timeout bit in case it's set */ 108 nand |= 1 << RC32434_ERR_WTO; 109 110 SET_BITS(wdt_reg->errcs, or, nand); 111 112 /* set the timeout (either default or based on module param) */ 113 rc32434_wdt_set(timeout); 114 115 /* reset WTC timeout bit and enable WDT */ 116 nand = 1 << RC32434_WTC_TO; 117 or = 1 << RC32434_WTC_EN; 118 119 SET_BITS(wdt_reg->wtc, or, nand); 120 121 spin_unlock(&rc32434_wdt_device.io_lock); 122 printk(KERN_INFO PFX "Started watchdog timer.\n"); 123} 124 125static void rc32434_wdt_stop(void) 126{ 127 spin_lock(&rc32434_wdt_device.io_lock); 128 129 /* Disable WDT */ 130 SET_BITS(wdt_reg->wtc, 0, 1 << RC32434_WTC_EN); 131 132 spin_unlock(&rc32434_wdt_device.io_lock); 133 printk(KERN_INFO PFX "Stopped watchdog timer.\n"); 134} 135 136static void rc32434_wdt_ping(void) 137{ 138 spin_lock(&rc32434_wdt_device.io_lock); 139 writel(0, &wdt_reg->wtcount); 140 spin_unlock(&rc32434_wdt_device.io_lock); 141} 142 143static int rc32434_wdt_open(struct inode *inode, struct file *file) 144{ 145 if (test_and_set_bit(0, &rc32434_wdt_device.inuse)) 146 return -EBUSY; 147 148 if (nowayout) 149 __module_get(THIS_MODULE); 150 151 rc32434_wdt_start(); 152 rc32434_wdt_ping(); 153 154 return nonseekable_open(inode, file); 155} 156 157static int rc32434_wdt_release(struct inode *inode, struct file *file) 158{ 159 if (expect_close == 42) { 160 rc32434_wdt_stop(); 161 module_put(THIS_MODULE); 162 } else { 163 printk(KERN_CRIT PFX 164 "device closed unexpectedly. WDT will not stop!\n"); 165 rc32434_wdt_ping(); 166 } 167 clear_bit(0, &rc32434_wdt_device.inuse); 168 return 0; 169} 170 171static ssize_t rc32434_wdt_write(struct file *file, const char *data, 172 size_t len, loff_t *ppos) 173{ 174 if (len) { 175 if (!nowayout) { 176 size_t i; 177 178 /* In case it was set long ago */ 179 expect_close = 0; 180 181 for (i = 0; i != len; i++) { 182 char c; 183 if (get_user(c, data + i)) 184 return -EFAULT; 185 if (c == 'V') 186 expect_close = 42; 187 } 188 } 189 rc32434_wdt_ping(); 190 return len; 191 } 192 return 0; 193} 194 195static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd, 196 unsigned long arg) 197{ 198 void __user *argp = (void __user *)arg; 199 int new_timeout; 200 unsigned int value; 201 static const struct watchdog_info ident = { 202 .options = WDIOF_SETTIMEOUT | 203 WDIOF_KEEPALIVEPING | 204 WDIOF_MAGICCLOSE, 205 .identity = "RC32434_WDT Watchdog", 206 }; 207 switch (cmd) { 208 case WDIOC_GETSUPPORT: 209 if (copy_to_user(argp, &ident, sizeof(ident))) 210 return -EFAULT; 211 break; 212 case WDIOC_GETSTATUS: 213 case WDIOC_GETBOOTSTATUS: 214 value = 0; 215 if (copy_to_user(argp, &value, sizeof(int))) 216 return -EFAULT; 217 break; 218 case WDIOC_SETOPTIONS: 219 if (copy_from_user(&value, argp, sizeof(int))) 220 return -EFAULT; 221 switch (value) { 222 case WDIOS_ENABLECARD: 223 rc32434_wdt_start(); 224 break; 225 case WDIOS_DISABLECARD: 226 rc32434_wdt_stop(); 227 break; 228 default: 229 return -EINVAL; 230 } 231 break; 232 case WDIOC_KEEPALIVE: 233 rc32434_wdt_ping(); 234 break; 235 case WDIOC_SETTIMEOUT: 236 if (copy_from_user(&new_timeout, argp, sizeof(int))) 237 return -EFAULT; 238 if (rc32434_wdt_set(new_timeout)) 239 return -EINVAL; 240 /* Fall through */ 241 case WDIOC_GETTIMEOUT: 242 return copy_to_user(argp, &timeout, sizeof(int)); 243 default: 244 return -ENOTTY; 245 } 246 247 return 0; 248} 249 250static const struct file_operations rc32434_wdt_fops = { 251 .owner = THIS_MODULE, 252 .llseek = no_llseek, 253 .write = rc32434_wdt_write, 254 .unlocked_ioctl = rc32434_wdt_ioctl, 255 .open = rc32434_wdt_open, 256 .release = rc32434_wdt_release, 257}; 258 259static struct miscdevice rc32434_wdt_miscdev = { 260 .minor = WATCHDOG_MINOR, 261 .name = "watchdog", 262 .fops = &rc32434_wdt_fops, 263}; 264 265static char banner[] __devinitdata = KERN_INFO PFX 266 "Watchdog Timer version " VERSION ", timer margin: %d sec\n"; 267 268static int __devinit rc32434_wdt_probe(struct platform_device *pdev) 269{ 270 int ret; 271 struct resource *r; 272 273 r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb532_wdt_res"); 274 if (!r) { 275 printk(KERN_ERR PFX "failed to retrieve resources\n"); 276 return -ENODEV; 277 } 278 279 wdt_reg = ioremap_nocache(r->start, resource_size(r)); 280 if (!wdt_reg) { 281 printk(KERN_ERR PFX "failed to remap I/O resources\n"); 282 return -ENXIO; 283 } 284 285 spin_lock_init(&rc32434_wdt_device.io_lock); 286 287 /* Make sure the watchdog is not running */ 288 rc32434_wdt_stop(); 289 290 /* Check that the heartbeat value is within it's range; 291 * if not reset to the default */ 292 if (rc32434_wdt_set(timeout)) { 293 rc32434_wdt_set(WATCHDOG_TIMEOUT); 294 printk(KERN_INFO PFX 295 "timeout value must be between 0 and %d\n", 296 WTCOMP2SEC((u32)-1)); 297 } 298 299 ret = misc_register(&rc32434_wdt_miscdev); 300 if (ret < 0) { 301 printk(KERN_ERR PFX "failed to register watchdog device\n"); 302 goto unmap; 303 } 304 305 printk(banner, timeout); 306 307 return 0; 308 309unmap: 310 iounmap(wdt_reg); 311 return ret; 312} 313 314static int __devexit rc32434_wdt_remove(struct platform_device *pdev) 315{ 316 misc_deregister(&rc32434_wdt_miscdev); 317 iounmap(wdt_reg); 318 return 0; 319} 320 321static void rc32434_wdt_shutdown(struct platform_device *pdev) 322{ 323 rc32434_wdt_stop(); 324} 325 326static struct platform_driver rc32434_wdt_driver = { 327 .probe = rc32434_wdt_probe, 328 .remove = __devexit_p(rc32434_wdt_remove), 329 .shutdown = rc32434_wdt_shutdown, 330 .driver = { 331 .name = "rc32434_wdt", 332 } 333}; 334 335static int __init rc32434_wdt_init(void) 336{ 337 return platform_driver_register(&rc32434_wdt_driver); 338} 339 340static void __exit rc32434_wdt_exit(void) 341{ 342 platform_driver_unregister(&rc32434_wdt_driver); 343} 344 345module_init(rc32434_wdt_init); 346module_exit(rc32434_wdt_exit); 347 348MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>," 349 "Florian Fainelli <florian@openwrt.org>"); 350MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog"); 351MODULE_LICENSE("GPL"); 352MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 353