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 
mtx1_wdt_trigger(struct timer_list * unused)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 
mtx1_wdt_reset(void)77 static void mtx1_wdt_reset(void)
78 {
79 	ticks = mtx1_wdt_device.default_ticks;
80 }
81 
82 
mtx1_wdt_start(void)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 
mtx1_wdt_stop(void)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 
mtx1_wdt_open(struct inode * inode,struct file * file)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 
mtx1_wdt_release(struct inode * inode,struct file * file)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 
mtx1_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)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 
mtx1_wdt_write(struct file * file,const char * buf,size_t count,loff_t * ppos)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 
mtx1_wdt_probe(struct platform_device * pdev)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 
mtx1_wdt_remove(struct platform_device * pdev)224 static int 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 	return 0;
234 }
235 
236 static struct platform_driver mtx1_wdt_driver = {
237 	.probe = mtx1_wdt_probe,
238 	.remove = mtx1_wdt_remove,
239 	.driver.name = "mtx1-wdt",
240 	.driver.owner = THIS_MODULE,
241 };
242 
243 module_platform_driver(mtx1_wdt_driver);
244 
245 MODULE_AUTHOR("Michael Stickel, Florian Fainelli");
246 MODULE_DESCRIPTION("Driver for the MTX-1 watchdog");
247 MODULE_LICENSE("GPL");
248 MODULE_ALIAS("platform:mtx1-wdt");
249