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/consumer.h> 43 44 #define MTX1_WDT_INTERVAL (5 * HZ) 45 46 static int ticks = 100 * HZ; 47 48 static struct { 49 struct completion stop; 50 spinlock_t lock; 51 int running; 52 struct timer_list timer; 53 int queue; 54 int default_ticks; 55 unsigned long inuse; 56 struct gpio_desc *gpiod; 57 unsigned int gstate; 58 } mtx1_wdt_device; 59 60 static void mtx1_wdt_trigger(struct timer_list *unused) 61 { 62 spin_lock(&mtx1_wdt_device.lock); 63 if (mtx1_wdt_device.running) 64 ticks--; 65 66 /* toggle wdt gpio */ 67 mtx1_wdt_device.gstate = !mtx1_wdt_device.gstate; 68 gpiod_set_value(mtx1_wdt_device.gpiod, mtx1_wdt_device.gstate); 69 70 if (mtx1_wdt_device.queue && ticks) 71 mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); 72 else 73 complete(&mtx1_wdt_device.stop); 74 spin_unlock(&mtx1_wdt_device.lock); 75 } 76 77 static void mtx1_wdt_reset(void) 78 { 79 ticks = mtx1_wdt_device.default_ticks; 80 } 81 82 83 static void mtx1_wdt_start(void) 84 { 85 unsigned long flags; 86 87 spin_lock_irqsave(&mtx1_wdt_device.lock, flags); 88 if (!mtx1_wdt_device.queue) { 89 mtx1_wdt_device.queue = 1; 90 mtx1_wdt_device.gstate = 1; 91 gpiod_set_value(mtx1_wdt_device.gpiod, 1); 92 mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); 93 } 94 mtx1_wdt_device.running++; 95 spin_unlock_irqrestore(&mtx1_wdt_device.lock, flags); 96 } 97 98 static int mtx1_wdt_stop(void) 99 { 100 unsigned long flags; 101 102 spin_lock_irqsave(&mtx1_wdt_device.lock, flags); 103 if (mtx1_wdt_device.queue) { 104 mtx1_wdt_device.queue = 0; 105 mtx1_wdt_device.gstate = 0; 106 gpiod_set_value(mtx1_wdt_device.gpiod, 0); 107 } 108 ticks = mtx1_wdt_device.default_ticks; 109 spin_unlock_irqrestore(&mtx1_wdt_device.lock, flags); 110 return 0; 111 } 112 113 /* Filesystem functions */ 114 115 static int mtx1_wdt_open(struct inode *inode, struct file *file) 116 { 117 if (test_and_set_bit(0, &mtx1_wdt_device.inuse)) 118 return -EBUSY; 119 return stream_open(inode, file); 120 } 121 122 123 static int mtx1_wdt_release(struct inode *inode, struct file *file) 124 { 125 clear_bit(0, &mtx1_wdt_device.inuse); 126 return 0; 127 } 128 129 static long mtx1_wdt_ioctl(struct file *file, unsigned int cmd, 130 unsigned long arg) 131 { 132 void __user *argp = (void __user *)arg; 133 int __user *p = (int __user *)argp; 134 unsigned int value; 135 static const struct watchdog_info ident = { 136 .options = WDIOF_CARDRESET, 137 .identity = "MTX-1 WDT", 138 }; 139 140 switch (cmd) { 141 case WDIOC_GETSUPPORT: 142 if (copy_to_user(argp, &ident, sizeof(ident))) 143 return -EFAULT; 144 break; 145 case WDIOC_GETSTATUS: 146 case WDIOC_GETBOOTSTATUS: 147 put_user(0, p); 148 break; 149 case WDIOC_SETOPTIONS: 150 if (get_user(value, p)) 151 return -EFAULT; 152 if (value & WDIOS_ENABLECARD) 153 mtx1_wdt_start(); 154 else if (value & WDIOS_DISABLECARD) 155 mtx1_wdt_stop(); 156 else 157 return -EINVAL; 158 return 0; 159 case WDIOC_KEEPALIVE: 160 mtx1_wdt_reset(); 161 break; 162 default: 163 return -ENOTTY; 164 } 165 return 0; 166 } 167 168 169 static ssize_t mtx1_wdt_write(struct file *file, const char *buf, 170 size_t count, loff_t *ppos) 171 { 172 if (!count) 173 return -EIO; 174 mtx1_wdt_reset(); 175 return count; 176 } 177 178 static const struct file_operations mtx1_wdt_fops = { 179 .owner = THIS_MODULE, 180 .llseek = no_llseek, 181 .unlocked_ioctl = mtx1_wdt_ioctl, 182 .compat_ioctl = compat_ptr_ioctl, 183 .open = mtx1_wdt_open, 184 .write = mtx1_wdt_write, 185 .release = mtx1_wdt_release, 186 }; 187 188 189 static struct miscdevice mtx1_wdt_misc = { 190 .minor = WATCHDOG_MINOR, 191 .name = "watchdog", 192 .fops = &mtx1_wdt_fops, 193 }; 194 195 196 static int mtx1_wdt_probe(struct platform_device *pdev) 197 { 198 int ret; 199 200 mtx1_wdt_device.gpiod = devm_gpiod_get(&pdev->dev, 201 NULL, GPIOD_OUT_HIGH); 202 if (IS_ERR(mtx1_wdt_device.gpiod)) { 203 dev_err(&pdev->dev, "failed to request gpio"); 204 return PTR_ERR(mtx1_wdt_device.gpiod); 205 } 206 207 spin_lock_init(&mtx1_wdt_device.lock); 208 init_completion(&mtx1_wdt_device.stop); 209 mtx1_wdt_device.queue = 0; 210 clear_bit(0, &mtx1_wdt_device.inuse); 211 timer_setup(&mtx1_wdt_device.timer, mtx1_wdt_trigger, 0); 212 mtx1_wdt_device.default_ticks = ticks; 213 214 ret = misc_register(&mtx1_wdt_misc); 215 if (ret < 0) { 216 dev_err(&pdev->dev, "failed to register\n"); 217 return ret; 218 } 219 mtx1_wdt_start(); 220 dev_info(&pdev->dev, "MTX-1 Watchdog driver\n"); 221 return 0; 222 } 223 224 static void mtx1_wdt_remove(struct platform_device *pdev) 225 { 226 /* FIXME: do we need to lock this test ? */ 227 if (mtx1_wdt_device.queue) { 228 mtx1_wdt_device.queue = 0; 229 wait_for_completion(&mtx1_wdt_device.stop); 230 } 231 232 misc_deregister(&mtx1_wdt_misc); 233 } 234 235 static struct platform_driver mtx1_wdt_driver = { 236 .probe = mtx1_wdt_probe, 237 .remove_new = mtx1_wdt_remove, 238 .driver.name = "mtx1-wdt", 239 .driver.owner = THIS_MODULE, 240 }; 241 242 module_platform_driver(mtx1_wdt_driver); 243 244 MODULE_AUTHOR("Michael Stickel, Florian Fainelli"); 245 MODULE_DESCRIPTION("Driver for the MTX-1 watchdog"); 246 MODULE_LICENSE("GPL"); 247 MODULE_ALIAS("platform:mtx1-wdt"); 248