1 /* 2 * RDC321x watchdog driver 3 * 4 * Copyright (C) 2007 Florian Fainelli <florian@openwrt.org> 5 * 6 * This driver is highly inspired from the cpu5_wdt driver 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 * 22 */ 23 24 #include <linux/module.h> 25 #include <linux/moduleparam.h> 26 #include <linux/types.h> 27 #include <linux/errno.h> 28 #include <linux/miscdevice.h> 29 #include <linux/fs.h> 30 #include <linux/init.h> 31 #include <linux/ioport.h> 32 #include <linux/timer.h> 33 #include <linux/completion.h> 34 #include <linux/jiffies.h> 35 #include <linux/platform_device.h> 36 #include <linux/watchdog.h> 37 #include <linux/io.h> 38 #include <linux/uaccess.h> 39 40 #include <asm/mach-rdc321x/rdc321x_defs.h> 41 42 #define RDC_WDT_MASK 0x80000000 /* Mask */ 43 #define RDC_WDT_EN 0x00800000 /* Enable bit */ 44 #define RDC_WDT_WTI 0x00200000 /* Generate CPU reset/NMI/WDT on timeout */ 45 #define RDC_WDT_RST 0x00100000 /* Reset bit */ 46 #define RDC_WDT_WIF 0x00040000 /* WDT IRQ Flag */ 47 #define RDC_WDT_IRT 0x00000100 /* IRQ Routing table */ 48 #define RDC_WDT_CNT 0x00000001 /* WDT count */ 49 50 #define RDC_CLS_TMR 0x80003844 /* Clear timer */ 51 52 #define RDC_WDT_INTERVAL (HZ/10+1) 53 54 static int ticks = 1000; 55 56 /* some device data */ 57 58 static struct { 59 struct completion stop; 60 int running; 61 struct timer_list timer; 62 int queue; 63 int default_ticks; 64 unsigned long inuse; 65 spinlock_t lock; 66 } rdc321x_wdt_device; 67 68 /* generic helper functions */ 69 70 static void rdc321x_wdt_trigger(unsigned long unused) 71 { 72 unsigned long flags; 73 74 if (rdc321x_wdt_device.running) 75 ticks--; 76 77 /* keep watchdog alive */ 78 spin_lock_irqsave(&rdc321x_wdt_device.lock, flags); 79 outl(RDC_WDT_EN | inl(RDC3210_CFGREG_DATA), 80 RDC3210_CFGREG_DATA); 81 spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags); 82 83 /* requeue?? */ 84 if (rdc321x_wdt_device.queue && ticks) 85 mod_timer(&rdc321x_wdt_device.timer, 86 jiffies + RDC_WDT_INTERVAL); 87 else { 88 /* ticks doesn't matter anyway */ 89 complete(&rdc321x_wdt_device.stop); 90 } 91 92 } 93 94 static void rdc321x_wdt_reset(void) 95 { 96 ticks = rdc321x_wdt_device.default_ticks; 97 } 98 99 static void rdc321x_wdt_start(void) 100 { 101 unsigned long flags; 102 103 if (!rdc321x_wdt_device.queue) { 104 rdc321x_wdt_device.queue = 1; 105 106 /* Clear the timer */ 107 spin_lock_irqsave(&rdc321x_wdt_device.lock, flags); 108 outl(RDC_CLS_TMR, RDC3210_CFGREG_ADDR); 109 110 /* Enable watchdog and set the timeout to 81.92 us */ 111 outl(RDC_WDT_EN | RDC_WDT_CNT, RDC3210_CFGREG_DATA); 112 spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags); 113 114 mod_timer(&rdc321x_wdt_device.timer, 115 jiffies + RDC_WDT_INTERVAL); 116 } 117 118 /* if process dies, counter is not decremented */ 119 rdc321x_wdt_device.running++; 120 } 121 122 static int rdc321x_wdt_stop(void) 123 { 124 if (rdc321x_wdt_device.running) 125 rdc321x_wdt_device.running = 0; 126 127 ticks = rdc321x_wdt_device.default_ticks; 128 129 return -EIO; 130 } 131 132 /* filesystem operations */ 133 static int rdc321x_wdt_open(struct inode *inode, struct file *file) 134 { 135 if (test_and_set_bit(0, &rdc321x_wdt_device.inuse)) 136 return -EBUSY; 137 138 return nonseekable_open(inode, file); 139 } 140 141 static int rdc321x_wdt_release(struct inode *inode, struct file *file) 142 { 143 clear_bit(0, &rdc321x_wdt_device.inuse); 144 return 0; 145 } 146 147 static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd, 148 unsigned long arg) 149 { 150 void __user *argp = (void __user *)arg; 151 unsigned int value; 152 static struct watchdog_info ident = { 153 .options = WDIOF_CARDRESET, 154 .identity = "RDC321x WDT", 155 }; 156 unsigned long flags; 157 158 switch (cmd) { 159 case WDIOC_KEEPALIVE: 160 rdc321x_wdt_reset(); 161 break; 162 case WDIOC_GETSTATUS: 163 /* Read the value from the DATA register */ 164 spin_lock_irqsave(&rdc321x_wdt_device.lock, flags); 165 value = inl(RDC3210_CFGREG_DATA); 166 spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags); 167 if (copy_to_user(argp, &value, sizeof(int))) 168 return -EFAULT; 169 break; 170 case WDIOC_GETSUPPORT: 171 if (copy_to_user(argp, &ident, sizeof(ident))) 172 return -EFAULT; 173 break; 174 case WDIOC_SETOPTIONS: 175 if (copy_from_user(&value, argp, sizeof(int))) 176 return -EFAULT; 177 switch (value) { 178 case WDIOS_ENABLECARD: 179 rdc321x_wdt_start(); 180 break; 181 case WDIOS_DISABLECARD: 182 return rdc321x_wdt_stop(); 183 default: 184 return -EINVAL; 185 } 186 break; 187 default: 188 return -ENOTTY; 189 } 190 return 0; 191 } 192 193 static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf, 194 size_t count, loff_t *ppos) 195 { 196 if (!count) 197 return -EIO; 198 199 rdc321x_wdt_reset(); 200 201 return count; 202 } 203 204 static const struct file_operations rdc321x_wdt_fops = { 205 .owner = THIS_MODULE, 206 .llseek = no_llseek, 207 .unlocked_ioctl = rdc321x_wdt_ioctl, 208 .open = rdc321x_wdt_open, 209 .write = rdc321x_wdt_write, 210 .release = rdc321x_wdt_release, 211 }; 212 213 static struct miscdevice rdc321x_wdt_misc = { 214 .minor = WATCHDOG_MINOR, 215 .name = "watchdog", 216 .fops = &rdc321x_wdt_fops, 217 }; 218 219 static int __devinit rdc321x_wdt_probe(struct platform_device *pdev) 220 { 221 int err; 222 223 err = misc_register(&rdc321x_wdt_misc); 224 if (err < 0) { 225 printk(KERN_ERR PFX "watchdog misc_register failed\n"); 226 return err; 227 } 228 229 spin_lock_init(&rdc321x_wdt_device.lock); 230 231 /* Reset the watchdog */ 232 outl(RDC_WDT_RST, RDC3210_CFGREG_DATA); 233 234 init_completion(&rdc321x_wdt_device.stop); 235 rdc321x_wdt_device.queue = 0; 236 237 clear_bit(0, &rdc321x_wdt_device.inuse); 238 239 setup_timer(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0); 240 241 rdc321x_wdt_device.default_ticks = ticks; 242 243 printk(KERN_INFO PFX "watchdog init success\n"); 244 245 return 0; 246 } 247 248 static int rdc321x_wdt_remove(struct platform_device *pdev) 249 { 250 if (rdc321x_wdt_device.queue) { 251 rdc321x_wdt_device.queue = 0; 252 wait_for_completion(&rdc321x_wdt_device.stop); 253 } 254 255 misc_deregister(&rdc321x_wdt_misc); 256 257 return 0; 258 } 259 260 static struct platform_driver rdc321x_wdt_driver = { 261 .probe = rdc321x_wdt_probe, 262 .remove = rdc321x_wdt_remove, 263 .driver = { 264 .owner = THIS_MODULE, 265 .name = "rdc321x-wdt", 266 }, 267 }; 268 269 static int __init rdc321x_wdt_init(void) 270 { 271 return platform_driver_register(&rdc321x_wdt_driver); 272 } 273 274 static void __exit rdc321x_wdt_exit(void) 275 { 276 platform_driver_unregister(&rdc321x_wdt_driver); 277 } 278 279 module_init(rdc321x_wdt_init); 280 module_exit(rdc321x_wdt_exit); 281 282 MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); 283 MODULE_DESCRIPTION("RDC321x watchdog driver"); 284 MODULE_LICENSE("GPL"); 285 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 286