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