1 /* Watchdog timer for machines with the CS5535/CS5536 companion chip 2 * 3 * Copyright (C) 2006-2007, Advanced Micro Devices, Inc. 4 * Copyright (C) 2009 Andres Salomon <dilinger@collabora.co.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 9 * 2 of the License, or (at your option) any later version. 10 */ 11 12 13 #include <linux/module.h> 14 #include <linux/moduleparam.h> 15 #include <linux/types.h> 16 #include <linux/miscdevice.h> 17 #include <linux/watchdog.h> 18 #include <linux/fs.h> 19 #include <linux/platform_device.h> 20 #include <linux/reboot.h> 21 #include <linux/uaccess.h> 22 23 #include <linux/cs5535.h> 24 25 #define GEODEWDT_HZ 500 26 #define GEODEWDT_SCALE 6 27 #define GEODEWDT_MAX_SECONDS 131 28 29 #define WDT_FLAGS_OPEN 1 30 #define WDT_FLAGS_ORPHAN 2 31 32 #define DRV_NAME "geodewdt" 33 #define WATCHDOG_NAME "Geode GX/LX WDT" 34 #define WATCHDOG_TIMEOUT 60 35 36 static int timeout = WATCHDOG_TIMEOUT; 37 module_param(timeout, int, 0); 38 MODULE_PARM_DESC(timeout, 39 "Watchdog timeout in seconds. 1<= timeout <=131, default=" 40 __MODULE_STRING(WATCHDOG_TIMEOUT) "."); 41 42 static int nowayout = WATCHDOG_NOWAYOUT; 43 module_param(nowayout, int, 0); 44 MODULE_PARM_DESC(nowayout, 45 "Watchdog cannot be stopped once started (default=" 46 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 47 48 static struct platform_device *geodewdt_platform_device; 49 static unsigned long wdt_flags; 50 static struct cs5535_mfgpt_timer *wdt_timer; 51 static int safe_close; 52 53 static void geodewdt_ping(void) 54 { 55 /* Stop the counter */ 56 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0); 57 58 /* Reset the counter */ 59 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0); 60 61 /* Enable the counter */ 62 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, MFGPT_SETUP_CNTEN); 63 } 64 65 static void geodewdt_disable(void) 66 { 67 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0); 68 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0); 69 } 70 71 static int geodewdt_set_heartbeat(int val) 72 { 73 if (val < 1 || val > GEODEWDT_MAX_SECONDS) 74 return -EINVAL; 75 76 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0); 77 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_CMP2, val * GEODEWDT_HZ); 78 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0); 79 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, MFGPT_SETUP_CNTEN); 80 81 timeout = val; 82 return 0; 83 } 84 85 static int geodewdt_open(struct inode *inode, struct file *file) 86 { 87 if (test_and_set_bit(WDT_FLAGS_OPEN, &wdt_flags)) 88 return -EBUSY; 89 90 if (!test_and_clear_bit(WDT_FLAGS_ORPHAN, &wdt_flags)) 91 __module_get(THIS_MODULE); 92 93 geodewdt_ping(); 94 return nonseekable_open(inode, file); 95 } 96 97 static int geodewdt_release(struct inode *inode, struct file *file) 98 { 99 if (safe_close) { 100 geodewdt_disable(); 101 module_put(THIS_MODULE); 102 } else { 103 printk(KERN_CRIT "Unexpected close - watchdog is not stopping.\n"); 104 geodewdt_ping(); 105 106 set_bit(WDT_FLAGS_ORPHAN, &wdt_flags); 107 } 108 109 clear_bit(WDT_FLAGS_OPEN, &wdt_flags); 110 safe_close = 0; 111 return 0; 112 } 113 114 static ssize_t geodewdt_write(struct file *file, const char __user *data, 115 size_t len, loff_t *ppos) 116 { 117 if (len) { 118 if (!nowayout) { 119 size_t i; 120 safe_close = 0; 121 122 for (i = 0; i != len; i++) { 123 char c; 124 125 if (get_user(c, data + i)) 126 return -EFAULT; 127 128 if (c == 'V') 129 safe_close = 1; 130 } 131 } 132 133 geodewdt_ping(); 134 } 135 return len; 136 } 137 138 static long geodewdt_ioctl(struct file *file, unsigned int cmd, 139 unsigned long arg) 140 { 141 void __user *argp = (void __user *)arg; 142 int __user *p = argp; 143 int interval; 144 145 static const struct watchdog_info ident = { 146 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING 147 | WDIOF_MAGICCLOSE, 148 .firmware_version = 1, 149 .identity = WATCHDOG_NAME, 150 }; 151 152 switch (cmd) { 153 case WDIOC_GETSUPPORT: 154 return copy_to_user(argp, &ident, 155 sizeof(ident)) ? -EFAULT : 0; 156 break; 157 158 case WDIOC_GETSTATUS: 159 case WDIOC_GETBOOTSTATUS: 160 return put_user(0, p); 161 162 case WDIOC_SETOPTIONS: 163 { 164 int options, ret = -EINVAL; 165 166 if (get_user(options, p)) 167 return -EFAULT; 168 169 if (options & WDIOS_DISABLECARD) { 170 geodewdt_disable(); 171 ret = 0; 172 } 173 174 if (options & WDIOS_ENABLECARD) { 175 geodewdt_ping(); 176 ret = 0; 177 } 178 179 return ret; 180 } 181 case WDIOC_KEEPALIVE: 182 geodewdt_ping(); 183 return 0; 184 185 case WDIOC_SETTIMEOUT: 186 if (get_user(interval, p)) 187 return -EFAULT; 188 189 if (geodewdt_set_heartbeat(interval)) 190 return -EINVAL; 191 /* Fall through */ 192 case WDIOC_GETTIMEOUT: 193 return put_user(timeout, p); 194 195 default: 196 return -ENOTTY; 197 } 198 199 return 0; 200 } 201 202 static const struct file_operations geodewdt_fops = { 203 .owner = THIS_MODULE, 204 .llseek = no_llseek, 205 .write = geodewdt_write, 206 .unlocked_ioctl = geodewdt_ioctl, 207 .open = geodewdt_open, 208 .release = geodewdt_release, 209 }; 210 211 static struct miscdevice geodewdt_miscdev = { 212 .minor = WATCHDOG_MINOR, 213 .name = "watchdog", 214 .fops = &geodewdt_fops, 215 }; 216 217 static int __devinit geodewdt_probe(struct platform_device *dev) 218 { 219 int ret; 220 221 wdt_timer = cs5535_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING); 222 if (!wdt_timer) { 223 printk(KERN_ERR "geodewdt: No timers were available\n"); 224 return -ENODEV; 225 } 226 227 /* Set up the timer */ 228 229 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 230 GEODEWDT_SCALE | (3 << 8)); 231 232 /* Set up comparator 2 to reset when the event fires */ 233 cs5535_mfgpt_toggle_event(wdt_timer, MFGPT_CMP2, MFGPT_EVENT_RESET, 1); 234 235 /* Set up the initial timeout */ 236 237 cs5535_mfgpt_write(wdt_timer, MFGPT_REG_CMP2, 238 timeout * GEODEWDT_HZ); 239 240 ret = misc_register(&geodewdt_miscdev); 241 242 return ret; 243 } 244 245 static int __devexit geodewdt_remove(struct platform_device *dev) 246 { 247 misc_deregister(&geodewdt_miscdev); 248 return 0; 249 } 250 251 static void geodewdt_shutdown(struct platform_device *dev) 252 { 253 geodewdt_disable(); 254 } 255 256 static struct platform_driver geodewdt_driver = { 257 .probe = geodewdt_probe, 258 .remove = __devexit_p(geodewdt_remove), 259 .shutdown = geodewdt_shutdown, 260 .driver = { 261 .owner = THIS_MODULE, 262 .name = DRV_NAME, 263 }, 264 }; 265 266 static int __init geodewdt_init(void) 267 { 268 int ret; 269 270 ret = platform_driver_register(&geodewdt_driver); 271 if (ret) 272 return ret; 273 274 geodewdt_platform_device = platform_device_register_simple(DRV_NAME, 275 -1, NULL, 0); 276 if (IS_ERR(geodewdt_platform_device)) { 277 ret = PTR_ERR(geodewdt_platform_device); 278 goto err; 279 } 280 281 return 0; 282 err: 283 platform_driver_unregister(&geodewdt_driver); 284 return ret; 285 } 286 287 static void __exit geodewdt_exit(void) 288 { 289 platform_device_unregister(geodewdt_platform_device); 290 platform_driver_unregister(&geodewdt_driver); 291 } 292 293 module_init(geodewdt_init); 294 module_exit(geodewdt_exit); 295 296 MODULE_AUTHOR("Advanced Micro Devices, Inc"); 297 MODULE_DESCRIPTION("Geode GX/LX Watchdog Driver"); 298 MODULE_LICENSE("GPL"); 299 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 300