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