Merge remote-tracking branch 'stable/linux-5.15.y' into rpi-5.15.y
[platform/kernel/linux-rpi.git] / drivers / watchdog / mtx-1_wdt.c
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 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");