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