1/* 2 * Watchdog driver for Kendin/Micrel KS8695. 3 * 4 * (C) 2007 Andrew Victor 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 */ 10 11#include <linux/bitops.h> 12#include <linux/errno.h> 13#include <linux/fs.h> 14#include <linux/init.h> 15#include <linux/kernel.h> 16#include <linux/miscdevice.h> 17#include <linux/module.h> 18#include <linux/moduleparam.h> 19#include <linux/platform_device.h> 20#include <linux/types.h> 21#include <linux/watchdog.h> 22#include <linux/io.h> 23#include <linux/uaccess.h> 24#include <mach/timex.h> 25#include <mach/regs-timer.h> 26 27#define WDT_DEFAULT_TIME 5 /* seconds */ 28#define WDT_MAX_TIME 171 /* seconds */ 29 30static int wdt_time = WDT_DEFAULT_TIME; 31static int nowayout = WATCHDOG_NOWAYOUT; 32 33module_param(wdt_time, int, 0); 34MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default=" 35 __MODULE_STRING(WDT_DEFAULT_TIME) ")"); 36 37#ifdef CONFIG_WATCHDOG_NOWAYOUT 38module_param(nowayout, int, 0); 39MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 40 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 41#endif 42 43 44static unsigned long ks8695wdt_busy; 45static spinlock_t ks8695_lock; 46 47/* ......................................................................... */ 48 49/* 50 * Disable the watchdog. 51 */ 52static inline void ks8695_wdt_stop(void) 53{ 54 unsigned long tmcon; 55 56 spin_lock(&ks8695_lock); 57 /* disable timer0 */ 58 tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); 59 __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); 60 spin_unlock(&ks8695_lock); 61} 62 63/* 64 * Enable and reset the watchdog. 65 */ 66static inline void ks8695_wdt_start(void) 67{ 68 unsigned long tmcon; 69 unsigned long tval = wdt_time * KS8695_CLOCK_RATE; 70 71 spin_lock(&ks8695_lock); 72 /* disable timer0 */ 73 tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); 74 __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); 75 76 /* program timer0 */ 77 __raw_writel(tval | T0TC_WATCHDOG, KS8695_TMR_VA + KS8695_T0TC); 78 79 /* re-enable timer0 */ 80 tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); 81 __raw_writel(tmcon | TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); 82 spin_unlock(&ks8695_lock); 83} 84 85/* 86 * Reload the watchdog timer. (ie, pat the watchdog) 87 */ 88static inline void ks8695_wdt_reload(void) 89{ 90 unsigned long tmcon; 91 92 spin_lock(&ks8695_lock); 93 /* disable, then re-enable timer0 */ 94 tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); 95 __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); 96 __raw_writel(tmcon | TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); 97 spin_unlock(&ks8695_lock); 98} 99 100/* 101 * Change the watchdog time interval. 102 */ 103static int ks8695_wdt_settimeout(int new_time) 104{ 105 /* 106 * All counting occurs at KS8695_CLOCK_RATE / 128 = 0.256 Hz 107 * 108 * Since WDV is a 16-bit counter, the maximum period is 109 * 65536 / 0.256 = 256 seconds. 110 */ 111 if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) 112 return -EINVAL; 113 114 /* Set new watchdog time. It will be used when 115 ks8695_wdt_start() is called. */ 116 wdt_time = new_time; 117 return 0; 118} 119 120/* ......................................................................... */ 121 122/* 123 * Watchdog device is opened, and watchdog starts running. 124 */ 125static int ks8695_wdt_open(struct inode *inode, struct file *file) 126{ 127 if (test_and_set_bit(0, &ks8695wdt_busy)) 128 return -EBUSY; 129 130 ks8695_wdt_start(); 131 return nonseekable_open(inode, file); 132} 133 134/* 135 * Close the watchdog device. 136 * If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also 137 * disabled. 138 */ 139static int ks8695_wdt_close(struct inode *inode, struct file *file) 140{ 141 /* Disable the watchdog when file is closed */ 142 if (!nowayout) 143 ks8695_wdt_stop(); 144 clear_bit(0, &ks8695wdt_busy); 145 return 0; 146} 147 148static const struct watchdog_info ks8695_wdt_info = { 149 .identity = "ks8695 watchdog", 150 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 151}; 152 153/* 154 * Handle commands from user-space. 155 */ 156static long ks8695_wdt_ioctl(struct file *file, unsigned int cmd, 157 unsigned long arg) 158{ 159 void __user *argp = (void __user *)arg; 160 int __user *p = argp; 161 int new_value; 162 163 switch (cmd) { 164 case WDIOC_GETSUPPORT: 165 return copy_to_user(argp, &ks8695_wdt_info, 166 sizeof(ks8695_wdt_info)) ? -EFAULT : 0; 167 case WDIOC_GETSTATUS: 168 case WDIOC_GETBOOTSTATUS: 169 return put_user(0, p); 170 case WDIOC_SETOPTIONS: 171 if (get_user(new_value, p)) 172 return -EFAULT; 173 if (new_value & WDIOS_DISABLECARD) 174 ks8695_wdt_stop(); 175 if (new_value & WDIOS_ENABLECARD) 176 ks8695_wdt_start(); 177 return 0; 178 case WDIOC_KEEPALIVE: 179 ks8695_wdt_reload(); /* pat the watchdog */ 180 return 0; 181 case WDIOC_SETTIMEOUT: 182 if (get_user(new_value, p)) 183 return -EFAULT; 184 if (ks8695_wdt_settimeout(new_value)) 185 return -EINVAL; 186 /* Enable new time value */ 187 ks8695_wdt_start(); 188 /* Return current value */ 189 return put_user(wdt_time, p); 190 case WDIOC_GETTIMEOUT: 191 return put_user(wdt_time, p); 192 default: 193 return -ENOTTY; 194 } 195} 196 197/* 198 * Pat the watchdog whenever device is written to. 199 */ 200static ssize_t ks8695_wdt_write(struct file *file, const char *data, 201 size_t len, loff_t *ppos) 202{ 203 ks8695_wdt_reload(); /* pat the watchdog */ 204 return len; 205} 206 207/* ......................................................................... */ 208 209static const struct file_operations ks8695wdt_fops = { 210 .owner = THIS_MODULE, 211 .llseek = no_llseek, 212 .unlocked_ioctl = ks8695_wdt_ioctl, 213 .open = ks8695_wdt_open, 214 .release = ks8695_wdt_close, 215 .write = ks8695_wdt_write, 216}; 217 218static struct miscdevice ks8695wdt_miscdev = { 219 .minor = WATCHDOG_MINOR, 220 .name = "watchdog", 221 .fops = &ks8695wdt_fops, 222}; 223 224static int __devinit ks8695wdt_probe(struct platform_device *pdev) 225{ 226 int res; 227 228 if (ks8695wdt_miscdev.parent) 229 return -EBUSY; 230 ks8695wdt_miscdev.parent = &pdev->dev; 231 232 res = misc_register(&ks8695wdt_miscdev); 233 if (res) 234 return res; 235 236 printk(KERN_INFO "KS8695 Watchdog Timer enabled (%d seconds%s)\n", 237 wdt_time, nowayout ? ", nowayout" : ""); 238 return 0; 239} 240 241static int __devexit ks8695wdt_remove(struct platform_device *pdev) 242{ 243 int res; 244 245 res = misc_deregister(&ks8695wdt_miscdev); 246 if (!res) 247 ks8695wdt_miscdev.parent = NULL; 248 249 return res; 250} 251 252static void ks8695wdt_shutdown(struct platform_device *pdev) 253{ 254 ks8695_wdt_stop(); 255} 256 257#ifdef CONFIG_PM 258 259static int ks8695wdt_suspend(struct platform_device *pdev, pm_message_t message) 260{ 261 ks8695_wdt_stop(); 262 return 0; 263} 264 265static int ks8695wdt_resume(struct platform_device *pdev) 266{ 267 if (ks8695wdt_busy) 268 ks8695_wdt_start(); 269 return 0; 270} 271 272#else 273#define ks8695wdt_suspend NULL 274#define ks8695wdt_resume NULL 275#endif 276 277static struct platform_driver ks8695wdt_driver = { 278 .probe = ks8695wdt_probe, 279 .remove = __devexit_p(ks8695wdt_remove), 280 .shutdown = ks8695wdt_shutdown, 281 .suspend = ks8695wdt_suspend, 282 .resume = ks8695wdt_resume, 283 .driver = { 284 .name = "ks8695_wdt", 285 .owner = THIS_MODULE, 286 }, 287}; 288 289static int __init ks8695_wdt_init(void) 290{ 291 spin_lock_init(&ks8695_lock); 292 /* Check that the heartbeat value is within range; 293 if not reset to the default */ 294 if (ks8695_wdt_settimeout(wdt_time)) { 295 ks8695_wdt_settimeout(WDT_DEFAULT_TIME); 296 pr_info("ks8695_wdt: wdt_time value must be 1 <= wdt_time <= %i" 297 ", using %d\n", wdt_time, WDT_MAX_TIME); 298 } 299 return platform_driver_register(&ks8695wdt_driver); 300} 301 302static void __exit ks8695_wdt_exit(void) 303{ 304 platform_driver_unregister(&ks8695wdt_driver); 305} 306 307module_init(ks8695_wdt_init); 308module_exit(ks8695_wdt_exit); 309 310MODULE_AUTHOR("Andrew Victor"); 311MODULE_DESCRIPTION("Watchdog driver for KS8695"); 312MODULE_LICENSE("GPL"); 313MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 314MODULE_ALIAS("platform:ks8695_wdt"); 315