xref: /openbmc/linux/drivers/watchdog/machzwd.c (revision 762f99f4f3cb41a775b5157dd761217beba65873)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2b7e04f8cSWim Van Sebroeck /*
3b7e04f8cSWim Van Sebroeck  *  MachZ ZF-Logic Watchdog Timer driver for Linux
4b7e04f8cSWim Van Sebroeck  *
5b7e04f8cSWim Van Sebroeck  *  The author does NOT admit liability nor provide warranty for
6b7e04f8cSWim Van Sebroeck  *  any of this software. This material is provided "AS-IS" in
7b7e04f8cSWim Van Sebroeck  *  the hope that it may be useful for others.
8b7e04f8cSWim Van Sebroeck  *
9b7e04f8cSWim Van Sebroeck  *  Author: Fernando Fuganti <fuganti@conectiva.com.br>
10b7e04f8cSWim Van Sebroeck  *
11b7e04f8cSWim Van Sebroeck  *  Based on sbc60xxwdt.c by Jakob Oestergaard
12b7e04f8cSWim Van Sebroeck  *
13b7e04f8cSWim Van Sebroeck  *  We have two timers (wd#1, wd#2) driven by a 32 KHz clock with the
14b7e04f8cSWim Van Sebroeck  *  following periods:
15b7e04f8cSWim Van Sebroeck  *      wd#1 - 2 seconds;
16b7e04f8cSWim Van Sebroeck  *      wd#2 - 7.2 ms;
17b7e04f8cSWim Van Sebroeck  *  After the expiration of wd#1, it can generate a NMI, SCI, SMI, or
18af901ca1SAndré Goddard Rosa  *  a system RESET and it starts wd#2 that unconditionally will RESET
19b7e04f8cSWim Van Sebroeck  *  the system when the counter reaches zero.
20b7e04f8cSWim Van Sebroeck  *
21b7e04f8cSWim Van Sebroeck  *  14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
22b7e04f8cSWim Van Sebroeck  *      Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
23b7e04f8cSWim Van Sebroeck  */
24b7e04f8cSWim Van Sebroeck 
2527c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2627c766aaSJoe Perches 
27b7e04f8cSWim Van Sebroeck #include <linux/module.h>
28b7e04f8cSWim Van Sebroeck #include <linux/moduleparam.h>
29b7e04f8cSWim Van Sebroeck #include <linux/types.h>
30b7e04f8cSWim Van Sebroeck #include <linux/timer.h>
31b7e04f8cSWim Van Sebroeck #include <linux/jiffies.h>
32b7e04f8cSWim Van Sebroeck #include <linux/miscdevice.h>
33b7e04f8cSWim Van Sebroeck #include <linux/watchdog.h>
34b7e04f8cSWim Van Sebroeck #include <linux/fs.h>
35b7e04f8cSWim Van Sebroeck #include <linux/ioport.h>
36b7e04f8cSWim Van Sebroeck #include <linux/notifier.h>
37b7e04f8cSWim Van Sebroeck #include <linux/reboot.h>
38b7e04f8cSWim Van Sebroeck #include <linux/init.h>
39325ea4d3SAlan Cox #include <linux/io.h>
40325ea4d3SAlan Cox #include <linux/uaccess.h>
41b7e04f8cSWim Van Sebroeck 
42b7e04f8cSWim Van Sebroeck 
43b7e04f8cSWim Van Sebroeck /* ports */
44b7e04f8cSWim Van Sebroeck #define ZF_IOBASE	0x218
45b7e04f8cSWim Van Sebroeck #define INDEX		0x218
46b7e04f8cSWim Van Sebroeck #define DATA_B		0x219
47b7e04f8cSWim Van Sebroeck #define DATA_W		0x21A
48b7e04f8cSWim Van Sebroeck #define DATA_D		0x21A
49b7e04f8cSWim Van Sebroeck 
50b7e04f8cSWim Van Sebroeck /* indexes */			/* size */
51b7e04f8cSWim Van Sebroeck #define ZFL_VERSION	0x02	/* 16   */
52b7e04f8cSWim Van Sebroeck #define CONTROL		0x10	/* 16   */
53b7e04f8cSWim Van Sebroeck #define STATUS		0x12	/* 8    */
54b7e04f8cSWim Van Sebroeck #define COUNTER_1	0x0C	/* 16   */
55b7e04f8cSWim Van Sebroeck #define COUNTER_2	0x0E	/* 8    */
56b7e04f8cSWim Van Sebroeck #define PULSE_LEN	0x0F	/* 8    */
57b7e04f8cSWim Van Sebroeck 
58b7e04f8cSWim Van Sebroeck /* controls */
59b7e04f8cSWim Van Sebroeck #define ENABLE_WD1	0x0001
60b7e04f8cSWim Van Sebroeck #define ENABLE_WD2	0x0002
61b7e04f8cSWim Van Sebroeck #define RESET_WD1	0x0010
62b7e04f8cSWim Van Sebroeck #define RESET_WD2	0x0020
63b7e04f8cSWim Van Sebroeck #define GEN_SCI		0x0100
64b7e04f8cSWim Van Sebroeck #define GEN_NMI		0x0200
65b7e04f8cSWim Van Sebroeck #define GEN_SMI		0x0400
66b7e04f8cSWim Van Sebroeck #define GEN_RESET	0x0800
67b7e04f8cSWim Van Sebroeck 
68b7e04f8cSWim Van Sebroeck 
69b7e04f8cSWim Van Sebroeck /* utilities */
70b7e04f8cSWim Van Sebroeck 
71b7e04f8cSWim Van Sebroeck #define WD1	0
72b7e04f8cSWim Van Sebroeck #define WD2	1
73b7e04f8cSWim Van Sebroeck 
74b7e04f8cSWim Van Sebroeck #define zf_writew(port, data)  { outb(port, INDEX); outw(data, DATA_W); }
75b7e04f8cSWim Van Sebroeck #define zf_writeb(port, data)  { outb(port, INDEX); outb(data, DATA_B); }
76b7e04f8cSWim Van Sebroeck #define zf_get_ZFL_version()   zf_readw(ZFL_VERSION)
77b7e04f8cSWim Van Sebroeck 
78b7e04f8cSWim Van Sebroeck 
zf_readw(unsigned char port)79b7e04f8cSWim Van Sebroeck static unsigned short zf_readw(unsigned char port)
80b7e04f8cSWim Van Sebroeck {
81b7e04f8cSWim Van Sebroeck 	outb(port, INDEX);
82b7e04f8cSWim Van Sebroeck 	return inw(DATA_W);
83b7e04f8cSWim Van Sebroeck }
84b7e04f8cSWim Van Sebroeck 
85b7e04f8cSWim Van Sebroeck 
86b7e04f8cSWim Van Sebroeck MODULE_AUTHOR("Fernando Fuganti <fuganti@conectiva.com.br>");
87b7e04f8cSWim Van Sebroeck MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver");
88b7e04f8cSWim Van Sebroeck MODULE_LICENSE("GPL");
89b7e04f8cSWim Van Sebroeck 
9086a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
9186a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
92325ea4d3SAlan Cox MODULE_PARM_DESC(nowayout,
93325ea4d3SAlan Cox 		"Watchdog cannot be stopped once started (default="
94325ea4d3SAlan Cox 				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
95b7e04f8cSWim Van Sebroeck 
96b7e04f8cSWim Van Sebroeck #define PFX "machzwd"
97b7e04f8cSWim Van Sebroeck 
9842747d71SWim Van Sebroeck static const struct watchdog_info zf_info = {
99b7e04f8cSWim Van Sebroeck 	.options		= WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
100b7e04f8cSWim Van Sebroeck 	.firmware_version	= 1,
101b7e04f8cSWim Van Sebroeck 	.identity		= "ZF-Logic watchdog",
102b7e04f8cSWim Van Sebroeck };
103b7e04f8cSWim Van Sebroeck 
104b7e04f8cSWim Van Sebroeck 
105b7e04f8cSWim Van Sebroeck /*
106b7e04f8cSWim Van Sebroeck  * action refers to action taken when watchdog resets
107b7e04f8cSWim Van Sebroeck  * 0 = GEN_RESET
108b7e04f8cSWim Van Sebroeck  * 1 = GEN_SMI
109b7e04f8cSWim Van Sebroeck  * 2 = GEN_NMI
110b7e04f8cSWim Van Sebroeck  * 3 = GEN_SCI
111b7e04f8cSWim Van Sebroeck  * defaults to GEN_RESET (0)
112b7e04f8cSWim Van Sebroeck  */
113325ea4d3SAlan Cox static int action;
114b7e04f8cSWim Van Sebroeck module_param(action, int, 0);
115a77dba7eSWim Van Sebroeck MODULE_PARM_DESC(action, "after watchdog resets, generate: "
116a77dba7eSWim Van Sebroeck 				"0 = RESET(*)  1 = SMI  2 = NMI  3 = SCI");
117b7e04f8cSWim Van Sebroeck 
11824ed960aSKees Cook static void zf_ping(struct timer_list *unused);
119b7e04f8cSWim Van Sebroeck 
120b7e04f8cSWim Van Sebroeck static int zf_action = GEN_RESET;
121b7e04f8cSWim Van Sebroeck static unsigned long zf_is_open;
122b7e04f8cSWim Van Sebroeck static char zf_expect_close;
123c7dfd0ccSAlexey Dobriyan static DEFINE_SPINLOCK(zf_port_lock);
1241d27e3e2SKees Cook static DEFINE_TIMER(zf_timer, zf_ping);
125325ea4d3SAlan Cox static unsigned long next_heartbeat;
126b7e04f8cSWim Van Sebroeck 
127b7e04f8cSWim Van Sebroeck 
128b7e04f8cSWim Van Sebroeck /* timeout for user land heart beat (10 seconds) */
129b7e04f8cSWim Van Sebroeck #define ZF_USER_TIMEO (HZ*10)
130b7e04f8cSWim Van Sebroeck 
131b7e04f8cSWim Van Sebroeck /* timeout for hardware watchdog (~500ms) */
132b7e04f8cSWim Van Sebroeck #define ZF_HW_TIMEO (HZ/2)
133b7e04f8cSWim Van Sebroeck 
134b7e04f8cSWim Van Sebroeck /* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */
135b7e04f8cSWim Van Sebroeck #define ZF_CTIMEOUT 0xffff
136b7e04f8cSWim Van Sebroeck 
137b7e04f8cSWim Van Sebroeck #ifndef ZF_DEBUG
138b7e04f8cSWim Van Sebroeck #define dprintk(format, args...)
139b7e04f8cSWim Van Sebroeck #else
14027c766aaSJoe Perches #define dprintk(format, args...)					\
14127c766aaSJoe Perches 	pr_debug(":%s:%d: " format, __func__, __LINE__ , ## args)
142b7e04f8cSWim Van Sebroeck #endif
143b7e04f8cSWim Van Sebroeck 
144b7e04f8cSWim Van Sebroeck 
zf_set_status(unsigned char new)145b7e04f8cSWim Van Sebroeck static inline void zf_set_status(unsigned char new)
146b7e04f8cSWim Van Sebroeck {
147b7e04f8cSWim Van Sebroeck 	zf_writeb(STATUS, new);
148b7e04f8cSWim Van Sebroeck }
149b7e04f8cSWim Van Sebroeck 
150b7e04f8cSWim Van Sebroeck 
151b7e04f8cSWim Van Sebroeck /* CONTROL register functions */
152b7e04f8cSWim Van Sebroeck 
zf_get_control(void)153b7e04f8cSWim Van Sebroeck static inline unsigned short zf_get_control(void)
154b7e04f8cSWim Van Sebroeck {
155b7e04f8cSWim Van Sebroeck 	return zf_readw(CONTROL);
156b7e04f8cSWim Van Sebroeck }
157b7e04f8cSWim Van Sebroeck 
zf_set_control(unsigned short new)158b7e04f8cSWim Van Sebroeck static inline void zf_set_control(unsigned short new)
159b7e04f8cSWim Van Sebroeck {
160b7e04f8cSWim Van Sebroeck 	zf_writew(CONTROL, new);
161b7e04f8cSWim Van Sebroeck }
162b7e04f8cSWim Van Sebroeck 
163b7e04f8cSWim Van Sebroeck 
164b7e04f8cSWim Van Sebroeck /* WD#? counter functions */
165b7e04f8cSWim Van Sebroeck /*
166b7e04f8cSWim Van Sebroeck  *	Just set counter value
167b7e04f8cSWim Van Sebroeck  */
168b7e04f8cSWim Van Sebroeck 
zf_set_timer(unsigned short new,unsigned char n)169b7e04f8cSWim Van Sebroeck static inline void zf_set_timer(unsigned short new, unsigned char n)
170b7e04f8cSWim Van Sebroeck {
171b7e04f8cSWim Van Sebroeck 	switch (n) {
172b7e04f8cSWim Van Sebroeck 	case WD1:
173b7e04f8cSWim Van Sebroeck 		zf_writew(COUNTER_1, new);
174bd490f82SGustavo A. R. Silva 		fallthrough;
175b7e04f8cSWim Van Sebroeck 	case WD2:
176b7e04f8cSWim Van Sebroeck 		zf_writeb(COUNTER_2, new > 0xff ? 0xff : new);
177*18a0e8d0SGustavo A. R. Silva 		fallthrough;
178b7e04f8cSWim Van Sebroeck 	default:
179b7e04f8cSWim Van Sebroeck 		return;
180b7e04f8cSWim Van Sebroeck 	}
181b7e04f8cSWim Van Sebroeck }
182b7e04f8cSWim Van Sebroeck 
183b7e04f8cSWim Van Sebroeck /*
184b7e04f8cSWim Van Sebroeck  * stop hardware timer
185b7e04f8cSWim Van Sebroeck  */
zf_timer_off(void)186b7e04f8cSWim Van Sebroeck static void zf_timer_off(void)
187b7e04f8cSWim Van Sebroeck {
188b7e04f8cSWim Van Sebroeck 	unsigned int ctrl_reg = 0;
189b7e04f8cSWim Van Sebroeck 	unsigned long flags;
190b7e04f8cSWim Van Sebroeck 
191b7e04f8cSWim Van Sebroeck 	/* stop internal ping */
192b7e04f8cSWim Van Sebroeck 	del_timer_sync(&zf_timer);
193b7e04f8cSWim Van Sebroeck 
194b7e04f8cSWim Van Sebroeck 	spin_lock_irqsave(&zf_port_lock, flags);
195b7e04f8cSWim Van Sebroeck 	/* stop watchdog timer */
196b7e04f8cSWim Van Sebroeck 	ctrl_reg = zf_get_control();
197b7e04f8cSWim Van Sebroeck 	ctrl_reg |= (ENABLE_WD1|ENABLE_WD2);	/* disable wd1 and wd2 */
198b7e04f8cSWim Van Sebroeck 	ctrl_reg &= ~(ENABLE_WD1|ENABLE_WD2);
199b7e04f8cSWim Van Sebroeck 	zf_set_control(ctrl_reg);
200b7e04f8cSWim Van Sebroeck 	spin_unlock_irqrestore(&zf_port_lock, flags);
201b7e04f8cSWim Van Sebroeck 
20227c766aaSJoe Perches 	pr_info("Watchdog timer is now disabled\n");
203b7e04f8cSWim Van Sebroeck }
204b7e04f8cSWim Van Sebroeck 
205b7e04f8cSWim Van Sebroeck 
206b7e04f8cSWim Van Sebroeck /*
207b7e04f8cSWim Van Sebroeck  * start hardware timer
208b7e04f8cSWim Van Sebroeck  */
zf_timer_on(void)209b7e04f8cSWim Van Sebroeck static void zf_timer_on(void)
210b7e04f8cSWim Van Sebroeck {
211b7e04f8cSWim Van Sebroeck 	unsigned int ctrl_reg = 0;
212b7e04f8cSWim Van Sebroeck 	unsigned long flags;
213b7e04f8cSWim Van Sebroeck 
214b7e04f8cSWim Van Sebroeck 	spin_lock_irqsave(&zf_port_lock, flags);
215b7e04f8cSWim Van Sebroeck 
216b7e04f8cSWim Van Sebroeck 	zf_writeb(PULSE_LEN, 0xff);
217b7e04f8cSWim Van Sebroeck 
218b7e04f8cSWim Van Sebroeck 	zf_set_timer(ZF_CTIMEOUT, WD1);
219b7e04f8cSWim Van Sebroeck 
220b7e04f8cSWim Van Sebroeck 	/* user land ping */
221b7e04f8cSWim Van Sebroeck 	next_heartbeat = jiffies + ZF_USER_TIMEO;
222b7e04f8cSWim Van Sebroeck 
223b7e04f8cSWim Van Sebroeck 	/* start the timer for internal ping */
224b7e04f8cSWim Van Sebroeck 	mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO);
225b7e04f8cSWim Van Sebroeck 
226b7e04f8cSWim Van Sebroeck 	/* start watchdog timer */
227b7e04f8cSWim Van Sebroeck 	ctrl_reg = zf_get_control();
228b7e04f8cSWim Van Sebroeck 	ctrl_reg |= (ENABLE_WD1|zf_action);
229b7e04f8cSWim Van Sebroeck 	zf_set_control(ctrl_reg);
230b7e04f8cSWim Van Sebroeck 	spin_unlock_irqrestore(&zf_port_lock, flags);
231b7e04f8cSWim Van Sebroeck 
23227c766aaSJoe Perches 	pr_info("Watchdog timer is now enabled\n");
233b7e04f8cSWim Van Sebroeck }
234b7e04f8cSWim Van Sebroeck 
235b7e04f8cSWim Van Sebroeck 
zf_ping(struct timer_list * unused)23624ed960aSKees Cook static void zf_ping(struct timer_list *unused)
237b7e04f8cSWim Van Sebroeck {
238b7e04f8cSWim Van Sebroeck 	unsigned int ctrl_reg = 0;
239b7e04f8cSWim Van Sebroeck 	unsigned long flags;
240b7e04f8cSWim Van Sebroeck 
241b7e04f8cSWim Van Sebroeck 	zf_writeb(COUNTER_2, 0xff);
242b7e04f8cSWim Van Sebroeck 
243b7e04f8cSWim Van Sebroeck 	if (time_before(jiffies, next_heartbeat)) {
244b7e04f8cSWim Van Sebroeck 		dprintk("time_before: %ld\n", next_heartbeat - jiffies);
245b7e04f8cSWim Van Sebroeck 		/*
246b7e04f8cSWim Van Sebroeck 		 * reset event is activated by transition from 0 to 1 on
247b7e04f8cSWim Van Sebroeck 		 * RESET_WD1 bit and we assume that it is already zero...
248b7e04f8cSWim Van Sebroeck 		 */
249b7e04f8cSWim Van Sebroeck 
250b7e04f8cSWim Van Sebroeck 		spin_lock_irqsave(&zf_port_lock, flags);
251b7e04f8cSWim Van Sebroeck 		ctrl_reg = zf_get_control();
252b7e04f8cSWim Van Sebroeck 		ctrl_reg |= RESET_WD1;
253b7e04f8cSWim Van Sebroeck 		zf_set_control(ctrl_reg);
254b7e04f8cSWim Van Sebroeck 
255b7e04f8cSWim Van Sebroeck 		/* ...and nothing changes until here */
256b7e04f8cSWim Van Sebroeck 		ctrl_reg &= ~(RESET_WD1);
257b7e04f8cSWim Van Sebroeck 		zf_set_control(ctrl_reg);
258b7e04f8cSWim Van Sebroeck 		spin_unlock_irqrestore(&zf_port_lock, flags);
259b7e04f8cSWim Van Sebroeck 
260b7e04f8cSWim Van Sebroeck 		mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO);
261325ea4d3SAlan Cox 	} else
26227c766aaSJoe Perches 		pr_crit("I will reset your machine\n");
263b7e04f8cSWim Van Sebroeck }
264b7e04f8cSWim Van Sebroeck 
zf_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)265b7e04f8cSWim Van Sebroeck static ssize_t zf_write(struct file *file, const char __user *buf, size_t count,
266b7e04f8cSWim Van Sebroeck 								loff_t *ppos)
267b7e04f8cSWim Van Sebroeck {
268b7e04f8cSWim Van Sebroeck 	/* See if we got the magic character */
269b7e04f8cSWim Van Sebroeck 	if (count) {
270b7e04f8cSWim Van Sebroeck 		/*
271b7e04f8cSWim Van Sebroeck 		 * no need to check for close confirmation
272b7e04f8cSWim Van Sebroeck 		 * no way to disable watchdog ;)
273b7e04f8cSWim Van Sebroeck 		 */
274b7e04f8cSWim Van Sebroeck 		if (!nowayout) {
275b7e04f8cSWim Van Sebroeck 			size_t ofs;
276b7e04f8cSWim Van Sebroeck 			/*
277b7e04f8cSWim Van Sebroeck 			 * note: just in case someone wrote the magic character
278b7e04f8cSWim Van Sebroeck 			 * five months ago...
279b7e04f8cSWim Van Sebroeck 			 */
280b7e04f8cSWim Van Sebroeck 			zf_expect_close = 0;
281b7e04f8cSWim Van Sebroeck 
282b7e04f8cSWim Van Sebroeck 			/* now scan */
283b7e04f8cSWim Van Sebroeck 			for (ofs = 0; ofs != count; ofs++) {
284b7e04f8cSWim Van Sebroeck 				char c;
285b7e04f8cSWim Van Sebroeck 				if (get_user(c, buf + ofs))
286b7e04f8cSWim Van Sebroeck 					return -EFAULT;
287b7e04f8cSWim Van Sebroeck 				if (c == 'V') {
288b7e04f8cSWim Van Sebroeck 					zf_expect_close = 42;
289b7e04f8cSWim Van Sebroeck 					dprintk("zf_expect_close = 42\n");
290b7e04f8cSWim Van Sebroeck 				}
291b7e04f8cSWim Van Sebroeck 			}
292b7e04f8cSWim Van Sebroeck 		}
293b7e04f8cSWim Van Sebroeck 
294b7e04f8cSWim Van Sebroeck 		/*
295b7e04f8cSWim Van Sebroeck 		 * Well, anyhow someone wrote to us,
296b7e04f8cSWim Van Sebroeck 		 * we should return that favour
297b7e04f8cSWim Van Sebroeck 		 */
298b7e04f8cSWim Van Sebroeck 		next_heartbeat = jiffies + ZF_USER_TIMEO;
299b7e04f8cSWim Van Sebroeck 		dprintk("user ping at %ld\n", jiffies);
300b7e04f8cSWim Van Sebroeck 	}
301b7e04f8cSWim Van Sebroeck 	return count;
302b7e04f8cSWim Van Sebroeck }
303b7e04f8cSWim Van Sebroeck 
zf_ioctl(struct file * file,unsigned int cmd,unsigned long arg)304325ea4d3SAlan Cox static long zf_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
305b7e04f8cSWim Van Sebroeck {
306b7e04f8cSWim Van Sebroeck 	void __user *argp = (void __user *)arg;
307b7e04f8cSWim Van Sebroeck 	int __user *p = argp;
308b7e04f8cSWim Van Sebroeck 	switch (cmd) {
309b7e04f8cSWim Van Sebroeck 	case WDIOC_GETSUPPORT:
310b7e04f8cSWim Van Sebroeck 		if (copy_to_user(argp, &zf_info, sizeof(zf_info)))
311b7e04f8cSWim Van Sebroeck 			return -EFAULT;
312b7e04f8cSWim Van Sebroeck 		break;
313b7e04f8cSWim Van Sebroeck 	case WDIOC_GETSTATUS:
314b7e04f8cSWim Van Sebroeck 	case WDIOC_GETBOOTSTATUS:
315b7e04f8cSWim Van Sebroeck 		return put_user(0, p);
316b7e04f8cSWim Van Sebroeck 	case WDIOC_KEEPALIVE:
31733052fbbSHariprasad Kelam 		zf_ping(NULL);
318b7e04f8cSWim Van Sebroeck 		break;
319b7e04f8cSWim Van Sebroeck 	default:
320b7e04f8cSWim Van Sebroeck 		return -ENOTTY;
321b7e04f8cSWim Van Sebroeck 	}
322b7e04f8cSWim Van Sebroeck 	return 0;
323b7e04f8cSWim Van Sebroeck }
324b7e04f8cSWim Van Sebroeck 
zf_open(struct inode * inode,struct file * file)325b7e04f8cSWim Van Sebroeck static int zf_open(struct inode *inode, struct file *file)
326b7e04f8cSWim Van Sebroeck {
327325ea4d3SAlan Cox 	if (test_and_set_bit(0, &zf_is_open))
328b7e04f8cSWim Van Sebroeck 		return -EBUSY;
329b7e04f8cSWim Van Sebroeck 	if (nowayout)
330b7e04f8cSWim Van Sebroeck 		__module_get(THIS_MODULE);
331b7e04f8cSWim Van Sebroeck 	zf_timer_on();
332c5bf68feSKirill Smelkov 	return stream_open(inode, file);
333b7e04f8cSWim Van Sebroeck }
334b7e04f8cSWim Van Sebroeck 
zf_close(struct inode * inode,struct file * file)335b7e04f8cSWim Van Sebroeck static int zf_close(struct inode *inode, struct file *file)
336b7e04f8cSWim Van Sebroeck {
337325ea4d3SAlan Cox 	if (zf_expect_close == 42)
338b7e04f8cSWim Van Sebroeck 		zf_timer_off();
339325ea4d3SAlan Cox 	else {
340b7e04f8cSWim Van Sebroeck 		del_timer(&zf_timer);
34127c766aaSJoe Perches 		pr_err("device file closed unexpectedly. Will not stop the WDT!\n");
342b7e04f8cSWim Van Sebroeck 	}
343b7e04f8cSWim Van Sebroeck 	clear_bit(0, &zf_is_open);
344b7e04f8cSWim Van Sebroeck 	zf_expect_close = 0;
345b7e04f8cSWim Van Sebroeck 	return 0;
346b7e04f8cSWim Van Sebroeck }
347b7e04f8cSWim Van Sebroeck 
348b7e04f8cSWim Van Sebroeck /*
349b7e04f8cSWim Van Sebroeck  * Notifier for system down
350b7e04f8cSWim Van Sebroeck  */
351b7e04f8cSWim Van Sebroeck 
zf_notify_sys(struct notifier_block * this,unsigned long code,void * unused)352b7e04f8cSWim Van Sebroeck static int zf_notify_sys(struct notifier_block *this, unsigned long code,
353b7e04f8cSWim Van Sebroeck 								void *unused)
354b7e04f8cSWim Van Sebroeck {
355325ea4d3SAlan Cox 	if (code == SYS_DOWN || code == SYS_HALT)
356b7e04f8cSWim Van Sebroeck 		zf_timer_off();
357b7e04f8cSWim Van Sebroeck 	return NOTIFY_DONE;
358b7e04f8cSWim Van Sebroeck }
359b7e04f8cSWim Van Sebroeck 
360b7e04f8cSWim Van Sebroeck static const struct file_operations zf_fops = {
361b7e04f8cSWim Van Sebroeck 	.owner		= THIS_MODULE,
362b7e04f8cSWim Van Sebroeck 	.llseek		= no_llseek,
363b7e04f8cSWim Van Sebroeck 	.write		= zf_write,
364325ea4d3SAlan Cox 	.unlocked_ioctl = zf_ioctl,
365b6dfb247SArnd Bergmann 	.compat_ioctl	= compat_ptr_ioctl,
366b7e04f8cSWim Van Sebroeck 	.open		= zf_open,
367b7e04f8cSWim Van Sebroeck 	.release	= zf_close,
368b7e04f8cSWim Van Sebroeck };
369b7e04f8cSWim Van Sebroeck 
370b7e04f8cSWim Van Sebroeck static struct miscdevice zf_miscdev = {
371b7e04f8cSWim Van Sebroeck 	.minor = WATCHDOG_MINOR,
372b7e04f8cSWim Van Sebroeck 	.name = "watchdog",
373b7e04f8cSWim Van Sebroeck 	.fops = &zf_fops,
374b7e04f8cSWim Van Sebroeck };
375b7e04f8cSWim Van Sebroeck 
376b7e04f8cSWim Van Sebroeck 
377b7e04f8cSWim Van Sebroeck /*
378b7e04f8cSWim Van Sebroeck  * The device needs to learn about soft shutdowns in order to
379b7e04f8cSWim Van Sebroeck  * turn the timebomb registers off.
380b7e04f8cSWim Van Sebroeck  */
381b7e04f8cSWim Van Sebroeck static struct notifier_block zf_notifier = {
382b7e04f8cSWim Van Sebroeck 	.notifier_call = zf_notify_sys,
383b7e04f8cSWim Van Sebroeck };
384b7e04f8cSWim Van Sebroeck 
zf_show_action(int act)385b7e04f8cSWim Van Sebroeck static void __init zf_show_action(int act)
386b7e04f8cSWim Van Sebroeck {
387a2b89cd8SJoe Perches 	static const char * const str[] = { "RESET", "SMI", "NMI", "SCI" };
388b7e04f8cSWim Van Sebroeck 
38927c766aaSJoe Perches 	pr_info("Watchdog using action = %s\n", str[act]);
390b7e04f8cSWim Van Sebroeck }
391b7e04f8cSWim Van Sebroeck 
zf_init(void)392b7e04f8cSWim Van Sebroeck static int __init zf_init(void)
393b7e04f8cSWim Van Sebroeck {
394b7e04f8cSWim Van Sebroeck 	int ret;
395b7e04f8cSWim Van Sebroeck 
39627c766aaSJoe Perches 	pr_info("MachZ ZF-Logic Watchdog driver initializing\n");
397b7e04f8cSWim Van Sebroeck 
398b7e04f8cSWim Van Sebroeck 	ret = zf_get_ZFL_version();
399325ea4d3SAlan Cox 	if (!ret || ret == 0xffff) {
40027c766aaSJoe Perches 		pr_warn("no ZF-Logic found\n");
401b7e04f8cSWim Van Sebroeck 		return -ENODEV;
402b7e04f8cSWim Van Sebroeck 	}
403b7e04f8cSWim Van Sebroeck 
404325ea4d3SAlan Cox 	if (action <= 3 && action >= 0)
405b7e04f8cSWim Van Sebroeck 		zf_action = zf_action >> action;
406325ea4d3SAlan Cox 	else
407b7e04f8cSWim Van Sebroeck 		action = 0;
408b7e04f8cSWim Van Sebroeck 
409b7e04f8cSWim Van Sebroeck 	zf_show_action(action);
410b7e04f8cSWim Van Sebroeck 
411b7e04f8cSWim Van Sebroeck 	if (!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")) {
41227c766aaSJoe Perches 		pr_err("cannot reserve I/O ports at %d\n", ZF_IOBASE);
413b7e04f8cSWim Van Sebroeck 		ret = -EBUSY;
414b7e04f8cSWim Van Sebroeck 		goto no_region;
415b7e04f8cSWim Van Sebroeck 	}
416b7e04f8cSWim Van Sebroeck 
417b7e04f8cSWim Van Sebroeck 	ret = register_reboot_notifier(&zf_notifier);
418b7e04f8cSWim Van Sebroeck 	if (ret) {
41927c766aaSJoe Perches 		pr_err("can't register reboot notifier (err=%d)\n", ret);
420b7e04f8cSWim Van Sebroeck 		goto no_reboot;
421b7e04f8cSWim Van Sebroeck 	}
422b7e04f8cSWim Van Sebroeck 
423b7e04f8cSWim Van Sebroeck 	ret = misc_register(&zf_miscdev);
424b7e04f8cSWim Van Sebroeck 	if (ret) {
42527c766aaSJoe Perches 		pr_err("can't misc_register on minor=%d\n", WATCHDOG_MINOR);
426b7e04f8cSWim Van Sebroeck 		goto no_misc;
427b7e04f8cSWim Van Sebroeck 	}
428b7e04f8cSWim Van Sebroeck 
429b7e04f8cSWim Van Sebroeck 	zf_set_status(0);
430b7e04f8cSWim Van Sebroeck 	zf_set_control(0);
431b7e04f8cSWim Van Sebroeck 
432b7e04f8cSWim Van Sebroeck 	return 0;
433b7e04f8cSWim Van Sebroeck 
434b7e04f8cSWim Van Sebroeck no_misc:
435b7e04f8cSWim Van Sebroeck 	unregister_reboot_notifier(&zf_notifier);
436b7e04f8cSWim Van Sebroeck no_reboot:
437b7e04f8cSWim Van Sebroeck 	release_region(ZF_IOBASE, 3);
438b7e04f8cSWim Van Sebroeck no_region:
439b7e04f8cSWim Van Sebroeck 	return ret;
440b7e04f8cSWim Van Sebroeck }
441b7e04f8cSWim Van Sebroeck 
442b7e04f8cSWim Van Sebroeck 
zf_exit(void)443b7e04f8cSWim Van Sebroeck static void __exit zf_exit(void)
444b7e04f8cSWim Van Sebroeck {
445b7e04f8cSWim Van Sebroeck 	zf_timer_off();
446b7e04f8cSWim Van Sebroeck 
447b7e04f8cSWim Van Sebroeck 	misc_deregister(&zf_miscdev);
448b7e04f8cSWim Van Sebroeck 	unregister_reboot_notifier(&zf_notifier);
449b7e04f8cSWim Van Sebroeck 	release_region(ZF_IOBASE, 3);
450b7e04f8cSWim Van Sebroeck }
451b7e04f8cSWim Van Sebroeck 
452b7e04f8cSWim Van Sebroeck module_init(zf_init);
453b7e04f8cSWim Van Sebroeck module_exit(zf_exit);
454