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 36static int timeout = WATCHDOG_TIMEOUT; 37module_param(timeout, int, 0); 38MODULE_PARM_DESC(timeout, 39 "Watchdog timeout in seconds. 1<= timeout <=131, default=" 40 __MODULE_STRING(WATCHDOG_TIMEOUT) "."); 41 42static int nowayout = WATCHDOG_NOWAYOUT; 43module_param(nowayout, int, 0); 44MODULE_PARM_DESC(nowayout, 45 "Watchdog cannot be stopped once started (default=" 46 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 47 48static struct platform_device *geodewdt_platform_device; 49static unsigned long wdt_flags; 50static struct cs5535_mfgpt_timer *wdt_timer; 51static int safe_close; 52 53static 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 65static 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 71static 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 85static 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 97static 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 114static 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 138static 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 202static 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 211static struct miscdevice geodewdt_miscdev = { 212 .minor = WATCHDOG_MINOR, 213 .name = "watchdog", 214 .fops = &geodewdt_fops, 215}; 216 217static 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 245static int __devexit geodewdt_remove(struct platform_device *dev) 246{ 247 misc_deregister(&geodewdt_miscdev); 248 return 0; 249} 250 251static void geodewdt_shutdown(struct platform_device *dev) 252{ 253 geodewdt_disable(); 254} 255 256static 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 266static 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; 282err: 283 platform_driver_unregister(&geodewdt_driver); 284 return ret; 285} 286 287static void __exit geodewdt_exit(void) 288{ 289 platform_device_unregister(geodewdt_platform_device); 290 platform_driver_unregister(&geodewdt_driver); 291} 292 293module_init(geodewdt_init); 294module_exit(geodewdt_exit); 295 296MODULE_AUTHOR("Advanced Micro Devices, Inc"); 297MODULE_DESCRIPTION("Geode GX/LX Watchdog Driver"); 298MODULE_LICENSE("GPL"); 299MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 300