1 /* 2 * Driver for the MTX-1 Watchdog. 3 * 4 * (C) Copyright 2005 4G Systems <info@4g-systems.biz>, 5 * All Rights Reserved. 6 * http://www.4g-systems.biz 7 * 8 * (C) Copyright 2007 OpenWrt.org, Florian Fainelli <florian@openwrt.org> 9 * 10 * This program is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU General Public License 12 * as published by the Free Software Foundation; either version 13 * 2 of the License, or (at your option) any later version. 14 * 15 * Neither Michael Stickel nor 4G Systems admit liability nor provide 16 * warranty for any of this software. This material is provided 17 * "AS-IS" and at no charge. 18 * 19 * (c) Copyright 2005 4G Systems <info@4g-systems.biz> 20 * 21 * Release 0.01. 22 * Author: Michael Stickel michael.stickel@4g-systems.biz 23 * 24 * Release 0.02. 25 * Author: Florian Fainelli florian@openwrt.org 26 * use the Linux watchdog/timer APIs 27 * 28 * The Watchdog is configured to reset the MTX-1 29 * if it is not triggered for 100 seconds. 30 * It should not be triggered more often than 1.6 seconds. 31 * 32 * A timer triggers the watchdog every 5 seconds, until 33 * it is opened for the first time. After the first open 34 * it MUST be triggered every 2..95 seconds. 35 */ 36 37 #include <linux/module.h> 38 #include <linux/moduleparam.h> 39 #include <linux/types.h> 40 #include <linux/errno.h> 41 #include <linux/miscdevice.h> 42 #include <linux/fs.h> 43 #include <linux/init.h> 44 #include <linux/ioport.h> 45 #include <linux/timer.h> 46 #include <linux/completion.h> 47 #include <linux/jiffies.h> 48 #include <linux/watchdog.h> 49 #include <linux/platform_device.h> 50 #include <linux/io.h> 51 #include <linux/uaccess.h> 52 #include <linux/gpio.h> 53 54 #include <asm/mach-au1x00/au1000.h> 55 56 #define MTX1_WDT_INTERVAL (5 * HZ) 57 58 static int ticks = 100 * HZ; 59 60 static struct { 61 struct completion stop; 62 spinlock_t lock; 63 int running; 64 struct timer_list timer; 65 int queue; 66 int default_ticks; 67 unsigned long inuse; 68 unsigned gpio; 69 unsigned int gstate; 70 } mtx1_wdt_device; 71 72 static void mtx1_wdt_trigger(unsigned long unused) 73 { 74 spin_lock(&mtx1_wdt_device.lock); 75 if (mtx1_wdt_device.running) 76 ticks--; 77 78 /* toggle wdt gpio */ 79 mtx1_wdt_device.gstate = !mtx1_wdt_device.gstate; 80 gpio_set_value(mtx1_wdt_device.gpio, mtx1_wdt_device.gstate); 81 82 if (mtx1_wdt_device.queue && ticks) 83 mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); 84 else 85 complete(&mtx1_wdt_device.stop); 86 spin_unlock(&mtx1_wdt_device.lock); 87 } 88 89 static void mtx1_wdt_reset(void) 90 { 91 ticks = mtx1_wdt_device.default_ticks; 92 } 93 94 95 static void mtx1_wdt_start(void) 96 { 97 unsigned long flags; 98 99 spin_lock_irqsave(&mtx1_wdt_device.lock, flags); 100 if (!mtx1_wdt_device.queue) { 101 mtx1_wdt_device.queue = 1; 102 mtx1_wdt_device.gstate = 1; 103 gpio_set_value(mtx1_wdt_device.gpio, 1); 104 mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); 105 } 106 mtx1_wdt_device.running++; 107 spin_unlock_irqrestore(&mtx1_wdt_device.lock, flags); 108 } 109 110 static int mtx1_wdt_stop(void) 111 { 112 unsigned long flags; 113 114 spin_lock_irqsave(&mtx1_wdt_device.lock, flags); 115 if (mtx1_wdt_device.queue) { 116 mtx1_wdt_device.queue = 0; 117 mtx1_wdt_device.gstate = 0; 118 gpio_set_value(mtx1_wdt_device.gpio, 0); 119 } 120 ticks = mtx1_wdt_device.default_ticks; 121 spin_unlock_irqrestore(&mtx1_wdt_device.lock, flags); 122 return 0; 123 } 124 125 /* Filesystem functions */ 126 127 static int mtx1_wdt_open(struct inode *inode, struct file *file) 128 { 129 if (test_and_set_bit(0, &mtx1_wdt_device.inuse)) 130 return -EBUSY; 131 return nonseekable_open(inode, file); 132 } 133 134 135 static int mtx1_wdt_release(struct inode *inode, struct file *file) 136 { 137 clear_bit(0, &mtx1_wdt_device.inuse); 138 return 0; 139 } 140 141 static long mtx1_wdt_ioctl(struct file *file, unsigned int cmd, 142 unsigned long arg) 143 { 144 void __user *argp = (void __user *)arg; 145 int __user *p = (int __user *)argp; 146 unsigned int value; 147 static const struct watchdog_info ident = { 148 .options = WDIOF_CARDRESET, 149 .identity = "MTX-1 WDT", 150 }; 151 152 switch (cmd) { 153 case WDIOC_GETSUPPORT: 154 if (copy_to_user(argp, &ident, sizeof(ident))) 155 return -EFAULT; 156 break; 157 case WDIOC_GETSTATUS: 158 case WDIOC_GETBOOTSTATUS: 159 put_user(0, p); 160 break; 161 case WDIOC_SETOPTIONS: 162 if (get_user(value, p)) 163 return -EFAULT; 164 if (value & WDIOS_ENABLECARD) 165 mtx1_wdt_start(); 166 else if (value & WDIOS_DISABLECARD) 167 mtx1_wdt_stop(); 168 else 169 return -EINVAL; 170 return 0; 171 case WDIOC_KEEPALIVE: 172 mtx1_wdt_reset(); 173 break; 174 default: 175 return -ENOTTY; 176 } 177 return 0; 178 } 179 180 181 static ssize_t mtx1_wdt_write(struct file *file, const char *buf, 182 size_t count, loff_t *ppos) 183 { 184 if (!count) 185 return -EIO; 186 mtx1_wdt_reset(); 187 return count; 188 } 189 190 static const struct file_operations mtx1_wdt_fops = { 191 .owner = THIS_MODULE, 192 .llseek = no_llseek, 193 .unlocked_ioctl = mtx1_wdt_ioctl, 194 .open = mtx1_wdt_open, 195 .write = mtx1_wdt_write, 196 .release = mtx1_wdt_release, 197 }; 198 199 200 static struct miscdevice mtx1_wdt_misc = { 201 .minor = WATCHDOG_MINOR, 202 .name = "watchdog", 203 .fops = &mtx1_wdt_fops, 204 }; 205 206 207 static int mtx1_wdt_probe(struct platform_device *pdev) 208 { 209 int ret; 210 211 mtx1_wdt_device.gpio = pdev->resource[0].start; 212 ret = devm_gpio_request_one(&pdev->dev, mtx1_wdt_device.gpio, 213 GPIOF_OUT_INIT_HIGH, "mtx1-wdt"); 214 if (ret < 0) { 215 dev_err(&pdev->dev, "failed to request gpio"); 216 return ret; 217 } 218 219 spin_lock_init(&mtx1_wdt_device.lock); 220 init_completion(&mtx1_wdt_device.stop); 221 mtx1_wdt_device.queue = 0; 222 clear_bit(0, &mtx1_wdt_device.inuse); 223 setup_timer(&mtx1_wdt_device.timer, mtx1_wdt_trigger, 0L); 224 mtx1_wdt_device.default_ticks = ticks; 225 226 ret = misc_register(&mtx1_wdt_misc); 227 if (ret < 0) { 228 dev_err(&pdev->dev, "failed to register\n"); 229 return ret; 230 } 231 mtx1_wdt_start(); 232 dev_info(&pdev->dev, "MTX-1 Watchdog driver\n"); 233 return 0; 234 } 235 236 static int mtx1_wdt_remove(struct platform_device *pdev) 237 { 238 /* FIXME: do we need to lock this test ? */ 239 if (mtx1_wdt_device.queue) { 240 mtx1_wdt_device.queue = 0; 241 wait_for_completion(&mtx1_wdt_device.stop); 242 } 243 244 misc_deregister(&mtx1_wdt_misc); 245 return 0; 246 } 247 248 static struct platform_driver mtx1_wdt_driver = { 249 .probe = mtx1_wdt_probe, 250 .remove = mtx1_wdt_remove, 251 .driver.name = "mtx1-wdt", 252 .driver.owner = THIS_MODULE, 253 }; 254 255 module_platform_driver(mtx1_wdt_driver); 256 257 MODULE_AUTHOR("Michael Stickel, Florian Fainelli"); 258 MODULE_DESCRIPTION("Driver for the MTX-1 watchdog"); 259 MODULE_LICENSE("GPL"); 260 MODULE_ALIAS("platform:mtx1-wdt"); 261