1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * ICP Wafer 5823 Single Board Computer WDT driver
4*4882a593Smuzhiyun * http://www.icpamerica.com/wafer_5823.php
5*4882a593Smuzhiyun * May also work on other similar models
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * (c) Copyright 2002 Justin Cormack <justin@street-vision.com>
8*4882a593Smuzhiyun *
9*4882a593Smuzhiyun * Release 0.02
10*4882a593Smuzhiyun *
11*4882a593Smuzhiyun * Based on advantechwdt.c which is based on wdt.c.
12*4882a593Smuzhiyun * Original copyright messages:
13*4882a593Smuzhiyun *
14*4882a593Smuzhiyun * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>,
15*4882a593Smuzhiyun * All Rights Reserved.
16*4882a593Smuzhiyun *
17*4882a593Smuzhiyun * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
18*4882a593Smuzhiyun * warranty for any of this software. This material is provided
19*4882a593Smuzhiyun * "AS-IS" and at no charge.
20*4882a593Smuzhiyun *
21*4882a593Smuzhiyun * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
22*4882a593Smuzhiyun *
23*4882a593Smuzhiyun */
24*4882a593Smuzhiyun
25*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
26*4882a593Smuzhiyun
27*4882a593Smuzhiyun #include <linux/module.h>
28*4882a593Smuzhiyun #include <linux/moduleparam.h>
29*4882a593Smuzhiyun #include <linux/miscdevice.h>
30*4882a593Smuzhiyun #include <linux/watchdog.h>
31*4882a593Smuzhiyun #include <linux/fs.h>
32*4882a593Smuzhiyun #include <linux/ioport.h>
33*4882a593Smuzhiyun #include <linux/notifier.h>
34*4882a593Smuzhiyun #include <linux/reboot.h>
35*4882a593Smuzhiyun #include <linux/init.h>
36*4882a593Smuzhiyun #include <linux/spinlock.h>
37*4882a593Smuzhiyun #include <linux/io.h>
38*4882a593Smuzhiyun #include <linux/uaccess.h>
39*4882a593Smuzhiyun
40*4882a593Smuzhiyun #define WATCHDOG_NAME "Wafer 5823 WDT"
41*4882a593Smuzhiyun #define PFX WATCHDOG_NAME ": "
42*4882a593Smuzhiyun #define WD_TIMO 60 /* 60 sec default timeout */
43*4882a593Smuzhiyun
44*4882a593Smuzhiyun static unsigned long wafwdt_is_open;
45*4882a593Smuzhiyun static char expect_close;
46*4882a593Smuzhiyun static DEFINE_SPINLOCK(wafwdt_lock);
47*4882a593Smuzhiyun
48*4882a593Smuzhiyun /*
49*4882a593Smuzhiyun * You must set these - there is no sane way to probe for this board.
50*4882a593Smuzhiyun *
51*4882a593Smuzhiyun * To enable, write the timeout value in seconds (1 to 255) to I/O
52*4882a593Smuzhiyun * port WDT_START, then read the port to start the watchdog. To pat
53*4882a593Smuzhiyun * the dog, read port WDT_STOP to stop the timer, then read WDT_START
54*4882a593Smuzhiyun * to restart it again.
55*4882a593Smuzhiyun */
56*4882a593Smuzhiyun
57*4882a593Smuzhiyun static int wdt_stop = 0x843;
58*4882a593Smuzhiyun static int wdt_start = 0x443;
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun static int timeout = WD_TIMO; /* in seconds */
61*4882a593Smuzhiyun module_param(timeout, int, 0);
62*4882a593Smuzhiyun MODULE_PARM_DESC(timeout,
63*4882a593Smuzhiyun "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
64*4882a593Smuzhiyun __MODULE_STRING(WD_TIMO) ".");
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun static bool nowayout = WATCHDOG_NOWAYOUT;
67*4882a593Smuzhiyun module_param(nowayout, bool, 0);
68*4882a593Smuzhiyun MODULE_PARM_DESC(nowayout,
69*4882a593Smuzhiyun "Watchdog cannot be stopped once started (default="
70*4882a593Smuzhiyun __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
71*4882a593Smuzhiyun
wafwdt_ping(void)72*4882a593Smuzhiyun static void wafwdt_ping(void)
73*4882a593Smuzhiyun {
74*4882a593Smuzhiyun /* pat watchdog */
75*4882a593Smuzhiyun spin_lock(&wafwdt_lock);
76*4882a593Smuzhiyun inb_p(wdt_stop);
77*4882a593Smuzhiyun inb_p(wdt_start);
78*4882a593Smuzhiyun spin_unlock(&wafwdt_lock);
79*4882a593Smuzhiyun }
80*4882a593Smuzhiyun
wafwdt_start(void)81*4882a593Smuzhiyun static void wafwdt_start(void)
82*4882a593Smuzhiyun {
83*4882a593Smuzhiyun /* start up watchdog */
84*4882a593Smuzhiyun outb_p(timeout, wdt_start);
85*4882a593Smuzhiyun inb_p(wdt_start);
86*4882a593Smuzhiyun }
87*4882a593Smuzhiyun
wafwdt_stop(void)88*4882a593Smuzhiyun static void wafwdt_stop(void)
89*4882a593Smuzhiyun {
90*4882a593Smuzhiyun /* stop watchdog */
91*4882a593Smuzhiyun inb_p(wdt_stop);
92*4882a593Smuzhiyun }
93*4882a593Smuzhiyun
wafwdt_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)94*4882a593Smuzhiyun static ssize_t wafwdt_write(struct file *file, const char __user *buf,
95*4882a593Smuzhiyun size_t count, loff_t *ppos)
96*4882a593Smuzhiyun {
97*4882a593Smuzhiyun /* See if we got the magic character 'V' and reload the timer */
98*4882a593Smuzhiyun if (count) {
99*4882a593Smuzhiyun if (!nowayout) {
100*4882a593Smuzhiyun size_t i;
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun /* In case it was set long ago */
103*4882a593Smuzhiyun expect_close = 0;
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun /* scan to see whether or not we got the magic
106*4882a593Smuzhiyun character */
107*4882a593Smuzhiyun for (i = 0; i != count; i++) {
108*4882a593Smuzhiyun char c;
109*4882a593Smuzhiyun if (get_user(c, buf + i))
110*4882a593Smuzhiyun return -EFAULT;
111*4882a593Smuzhiyun if (c == 'V')
112*4882a593Smuzhiyun expect_close = 42;
113*4882a593Smuzhiyun }
114*4882a593Smuzhiyun }
115*4882a593Smuzhiyun /* Well, anyhow someone wrote to us, we should
116*4882a593Smuzhiyun return that favour */
117*4882a593Smuzhiyun wafwdt_ping();
118*4882a593Smuzhiyun }
119*4882a593Smuzhiyun return count;
120*4882a593Smuzhiyun }
121*4882a593Smuzhiyun
wafwdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)122*4882a593Smuzhiyun static long wafwdt_ioctl(struct file *file, unsigned int cmd,
123*4882a593Smuzhiyun unsigned long arg)
124*4882a593Smuzhiyun {
125*4882a593Smuzhiyun int new_timeout;
126*4882a593Smuzhiyun void __user *argp = (void __user *)arg;
127*4882a593Smuzhiyun int __user *p = argp;
128*4882a593Smuzhiyun static const struct watchdog_info ident = {
129*4882a593Smuzhiyun .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
130*4882a593Smuzhiyun WDIOF_MAGICCLOSE,
131*4882a593Smuzhiyun .firmware_version = 1,
132*4882a593Smuzhiyun .identity = "Wafer 5823 WDT",
133*4882a593Smuzhiyun };
134*4882a593Smuzhiyun
135*4882a593Smuzhiyun switch (cmd) {
136*4882a593Smuzhiyun case WDIOC_GETSUPPORT:
137*4882a593Smuzhiyun if (copy_to_user(argp, &ident, sizeof(ident)))
138*4882a593Smuzhiyun return -EFAULT;
139*4882a593Smuzhiyun break;
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun case WDIOC_GETSTATUS:
142*4882a593Smuzhiyun case WDIOC_GETBOOTSTATUS:
143*4882a593Smuzhiyun return put_user(0, p);
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun case WDIOC_SETOPTIONS:
146*4882a593Smuzhiyun {
147*4882a593Smuzhiyun int options, retval = -EINVAL;
148*4882a593Smuzhiyun
149*4882a593Smuzhiyun if (get_user(options, p))
150*4882a593Smuzhiyun return -EFAULT;
151*4882a593Smuzhiyun
152*4882a593Smuzhiyun if (options & WDIOS_DISABLECARD) {
153*4882a593Smuzhiyun wafwdt_stop();
154*4882a593Smuzhiyun retval = 0;
155*4882a593Smuzhiyun }
156*4882a593Smuzhiyun
157*4882a593Smuzhiyun if (options & WDIOS_ENABLECARD) {
158*4882a593Smuzhiyun wafwdt_start();
159*4882a593Smuzhiyun retval = 0;
160*4882a593Smuzhiyun }
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun return retval;
163*4882a593Smuzhiyun }
164*4882a593Smuzhiyun
165*4882a593Smuzhiyun case WDIOC_KEEPALIVE:
166*4882a593Smuzhiyun wafwdt_ping();
167*4882a593Smuzhiyun break;
168*4882a593Smuzhiyun
169*4882a593Smuzhiyun case WDIOC_SETTIMEOUT:
170*4882a593Smuzhiyun if (get_user(new_timeout, p))
171*4882a593Smuzhiyun return -EFAULT;
172*4882a593Smuzhiyun if ((new_timeout < 1) || (new_timeout > 255))
173*4882a593Smuzhiyun return -EINVAL;
174*4882a593Smuzhiyun timeout = new_timeout;
175*4882a593Smuzhiyun wafwdt_stop();
176*4882a593Smuzhiyun wafwdt_start();
177*4882a593Smuzhiyun fallthrough;
178*4882a593Smuzhiyun case WDIOC_GETTIMEOUT:
179*4882a593Smuzhiyun return put_user(timeout, p);
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun default:
182*4882a593Smuzhiyun return -ENOTTY;
183*4882a593Smuzhiyun }
184*4882a593Smuzhiyun return 0;
185*4882a593Smuzhiyun }
186*4882a593Smuzhiyun
wafwdt_open(struct inode * inode,struct file * file)187*4882a593Smuzhiyun static int wafwdt_open(struct inode *inode, struct file *file)
188*4882a593Smuzhiyun {
189*4882a593Smuzhiyun if (test_and_set_bit(0, &wafwdt_is_open))
190*4882a593Smuzhiyun return -EBUSY;
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun /*
193*4882a593Smuzhiyun * Activate
194*4882a593Smuzhiyun */
195*4882a593Smuzhiyun wafwdt_start();
196*4882a593Smuzhiyun return stream_open(inode, file);
197*4882a593Smuzhiyun }
198*4882a593Smuzhiyun
wafwdt_close(struct inode * inode,struct file * file)199*4882a593Smuzhiyun static int wafwdt_close(struct inode *inode, struct file *file)
200*4882a593Smuzhiyun {
201*4882a593Smuzhiyun if (expect_close == 42)
202*4882a593Smuzhiyun wafwdt_stop();
203*4882a593Smuzhiyun else {
204*4882a593Smuzhiyun pr_crit("WDT device closed unexpectedly. WDT will not stop!\n");
205*4882a593Smuzhiyun wafwdt_ping();
206*4882a593Smuzhiyun }
207*4882a593Smuzhiyun clear_bit(0, &wafwdt_is_open);
208*4882a593Smuzhiyun expect_close = 0;
209*4882a593Smuzhiyun return 0;
210*4882a593Smuzhiyun }
211*4882a593Smuzhiyun
212*4882a593Smuzhiyun /*
213*4882a593Smuzhiyun * Notifier for system down
214*4882a593Smuzhiyun */
215*4882a593Smuzhiyun
wafwdt_notify_sys(struct notifier_block * this,unsigned long code,void * unused)216*4882a593Smuzhiyun static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code,
217*4882a593Smuzhiyun void *unused)
218*4882a593Smuzhiyun {
219*4882a593Smuzhiyun if (code == SYS_DOWN || code == SYS_HALT)
220*4882a593Smuzhiyun wafwdt_stop();
221*4882a593Smuzhiyun return NOTIFY_DONE;
222*4882a593Smuzhiyun }
223*4882a593Smuzhiyun
224*4882a593Smuzhiyun /*
225*4882a593Smuzhiyun * Kernel Interfaces
226*4882a593Smuzhiyun */
227*4882a593Smuzhiyun
228*4882a593Smuzhiyun static const struct file_operations wafwdt_fops = {
229*4882a593Smuzhiyun .owner = THIS_MODULE,
230*4882a593Smuzhiyun .llseek = no_llseek,
231*4882a593Smuzhiyun .write = wafwdt_write,
232*4882a593Smuzhiyun .unlocked_ioctl = wafwdt_ioctl,
233*4882a593Smuzhiyun .compat_ioctl = compat_ptr_ioctl,
234*4882a593Smuzhiyun .open = wafwdt_open,
235*4882a593Smuzhiyun .release = wafwdt_close,
236*4882a593Smuzhiyun };
237*4882a593Smuzhiyun
238*4882a593Smuzhiyun static struct miscdevice wafwdt_miscdev = {
239*4882a593Smuzhiyun .minor = WATCHDOG_MINOR,
240*4882a593Smuzhiyun .name = "watchdog",
241*4882a593Smuzhiyun .fops = &wafwdt_fops,
242*4882a593Smuzhiyun };
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun /*
245*4882a593Smuzhiyun * The WDT needs to learn about soft shutdowns in order to
246*4882a593Smuzhiyun * turn the timebomb registers off.
247*4882a593Smuzhiyun */
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun static struct notifier_block wafwdt_notifier = {
250*4882a593Smuzhiyun .notifier_call = wafwdt_notify_sys,
251*4882a593Smuzhiyun };
252*4882a593Smuzhiyun
wafwdt_init(void)253*4882a593Smuzhiyun static int __init wafwdt_init(void)
254*4882a593Smuzhiyun {
255*4882a593Smuzhiyun int ret;
256*4882a593Smuzhiyun
257*4882a593Smuzhiyun pr_info("WDT driver for Wafer 5823 single board computer initialising\n");
258*4882a593Smuzhiyun
259*4882a593Smuzhiyun if (timeout < 1 || timeout > 255) {
260*4882a593Smuzhiyun timeout = WD_TIMO;
261*4882a593Smuzhiyun pr_info("timeout value must be 1 <= x <= 255, using %d\n",
262*4882a593Smuzhiyun timeout);
263*4882a593Smuzhiyun }
264*4882a593Smuzhiyun
265*4882a593Smuzhiyun if (wdt_stop != wdt_start) {
266*4882a593Smuzhiyun if (!request_region(wdt_stop, 1, "Wafer 5823 WDT")) {
267*4882a593Smuzhiyun pr_err("I/O address 0x%04x already in use\n", wdt_stop);
268*4882a593Smuzhiyun ret = -EIO;
269*4882a593Smuzhiyun goto error;
270*4882a593Smuzhiyun }
271*4882a593Smuzhiyun }
272*4882a593Smuzhiyun
273*4882a593Smuzhiyun if (!request_region(wdt_start, 1, "Wafer 5823 WDT")) {
274*4882a593Smuzhiyun pr_err("I/O address 0x%04x already in use\n", wdt_start);
275*4882a593Smuzhiyun ret = -EIO;
276*4882a593Smuzhiyun goto error2;
277*4882a593Smuzhiyun }
278*4882a593Smuzhiyun
279*4882a593Smuzhiyun ret = register_reboot_notifier(&wafwdt_notifier);
280*4882a593Smuzhiyun if (ret != 0) {
281*4882a593Smuzhiyun pr_err("cannot register reboot notifier (err=%d)\n", ret);
282*4882a593Smuzhiyun goto error3;
283*4882a593Smuzhiyun }
284*4882a593Smuzhiyun
285*4882a593Smuzhiyun ret = misc_register(&wafwdt_miscdev);
286*4882a593Smuzhiyun if (ret != 0) {
287*4882a593Smuzhiyun pr_err("cannot register miscdev on minor=%d (err=%d)\n",
288*4882a593Smuzhiyun WATCHDOG_MINOR, ret);
289*4882a593Smuzhiyun goto error4;
290*4882a593Smuzhiyun }
291*4882a593Smuzhiyun
292*4882a593Smuzhiyun pr_info("initialized. timeout=%d sec (nowayout=%d)\n",
293*4882a593Smuzhiyun timeout, nowayout);
294*4882a593Smuzhiyun
295*4882a593Smuzhiyun return ret;
296*4882a593Smuzhiyun error4:
297*4882a593Smuzhiyun unregister_reboot_notifier(&wafwdt_notifier);
298*4882a593Smuzhiyun error3:
299*4882a593Smuzhiyun release_region(wdt_start, 1);
300*4882a593Smuzhiyun error2:
301*4882a593Smuzhiyun if (wdt_stop != wdt_start)
302*4882a593Smuzhiyun release_region(wdt_stop, 1);
303*4882a593Smuzhiyun error:
304*4882a593Smuzhiyun return ret;
305*4882a593Smuzhiyun }
306*4882a593Smuzhiyun
wafwdt_exit(void)307*4882a593Smuzhiyun static void __exit wafwdt_exit(void)
308*4882a593Smuzhiyun {
309*4882a593Smuzhiyun misc_deregister(&wafwdt_miscdev);
310*4882a593Smuzhiyun unregister_reboot_notifier(&wafwdt_notifier);
311*4882a593Smuzhiyun if (wdt_stop != wdt_start)
312*4882a593Smuzhiyun release_region(wdt_stop, 1);
313*4882a593Smuzhiyun release_region(wdt_start, 1);
314*4882a593Smuzhiyun }
315*4882a593Smuzhiyun
316*4882a593Smuzhiyun module_init(wafwdt_init);
317*4882a593Smuzhiyun module_exit(wafwdt_exit);
318*4882a593Smuzhiyun
319*4882a593Smuzhiyun MODULE_AUTHOR("Justin Cormack");
320*4882a593Smuzhiyun MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver");
321*4882a593Smuzhiyun MODULE_LICENSE("GPL");
322*4882a593Smuzhiyun
323*4882a593Smuzhiyun /* end of wafer5823wdt.c */
324