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