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#include <asm/mach-au1x00/au1000.h> 45 46#define MTX1_WDT_INTERVAL (5 * HZ) 47 48static int ticks = 100 * HZ; 49 50static 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 struct gpio_desc *gpiod; 59 unsigned int gstate; 60} mtx1_wdt_device; 61 62static 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 gpiod_set_value(mtx1_wdt_device.gpiod, 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 79static void mtx1_wdt_reset(void) 80{ 81 ticks = mtx1_wdt_device.default_ticks; 82} 83 84 85static 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 gpiod_set_value(mtx1_wdt_device.gpiod, 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 100static 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 gpiod_set_value(mtx1_wdt_device.gpiod, 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 117static 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 stream_open(inode, file); 122} 123 124 125static int mtx1_wdt_release(struct inode *inode, struct file *file) 126{ 127 clear_bit(0, &mtx1_wdt_device.inuse); 128 return 0; 129} 130 131static 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 171static 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 180static const struct file_operations mtx1_wdt_fops = { 181 .owner = THIS_MODULE, 182 .llseek = no_llseek, 183 .unlocked_ioctl = mtx1_wdt_ioctl, 184 .compat_ioctl = compat_ptr_ioctl, 185 .open = mtx1_wdt_open, 186 .write = mtx1_wdt_write, 187 .release = mtx1_wdt_release, 188}; 189 190 191static struct miscdevice mtx1_wdt_misc = { 192 .minor = WATCHDOG_MINOR, 193 .name = "watchdog", 194 .fops = &mtx1_wdt_fops, 195}; 196 197 198static int mtx1_wdt_probe(struct platform_device *pdev) 199{ 200 int ret; 201 202 mtx1_wdt_device.gpiod = devm_gpiod_get(&pdev->dev, 203 NULL, GPIOD_OUT_HIGH); 204 if (IS_ERR(mtx1_wdt_device.gpiod)) { 205 dev_err(&pdev->dev, "failed to request gpio"); 206 return PTR_ERR(mtx1_wdt_device.gpiod); 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 226static 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 238static 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 245module_platform_driver(mtx1_wdt_driver); 246 247MODULE_AUTHOR("Michael Stickel, Florian Fainelli"); 248MODULE_DESCRIPTION("Driver for the MTX-1 watchdog"); 249MODULE_LICENSE("GPL"); 250MODULE_ALIAS("platform:mtx1-wdt"); 251