xref: /openbmc/linux/drivers/watchdog/wdt977.c (revision 4b4193256c8d3bc3a5397b5cd9494c2ad386317d)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2b7e04f8cSWim Van Sebroeck /*
3b7e04f8cSWim Van Sebroeck  *	Wdt977	0.04:	A Watchdog Device for Netwinder W83977AF chip
4b7e04f8cSWim Van Sebroeck  *
5b7e04f8cSWim Van Sebroeck  *	(c) Copyright 1998 Rebel.com (Woody Suwalski <woody@netwinder.org>)
6b7e04f8cSWim Van Sebroeck  *
7b7e04f8cSWim Van Sebroeck  *			-----------------------
8b7e04f8cSWim Van Sebroeck  *
9b7e04f8cSWim Van Sebroeck  *			-----------------------
10b7e04f8cSWim Van Sebroeck  *      14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
11b7e04f8cSWim Van Sebroeck  *           Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
12b7e04f8cSWim Van Sebroeck  *	19-Dec-2001 Woody Suwalski: Netwinder fixes, ioctl interface
13b7e04f8cSWim Van Sebroeck  *	06-Jan-2002 Woody Suwalski: For compatibility, convert all timeouts
14b7e04f8cSWim Van Sebroeck  *				    from minutes to seconds.
15b7e04f8cSWim Van Sebroeck  *      07-Jul-2003 Daniele Bellucci: Audit return code of misc_register in
16b7e04f8cSWim Van Sebroeck  *                                    nwwatchdog_init.
17b7e04f8cSWim Van Sebroeck  *      25-Oct-2005 Woody Suwalski: Convert addresses to #defs, add spinlocks
18f2b79c6eSAlan Cox  *				    remove limitiation to be used on
19f2b79c6eSAlan Cox  *				    Netwinders only
20b7e04f8cSWim Van Sebroeck  */
21b7e04f8cSWim Van Sebroeck 
2227c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2327c766aaSJoe Perches 
24b7e04f8cSWim Van Sebroeck #include <linux/module.h>
25b7e04f8cSWim Van Sebroeck #include <linux/moduleparam.h>
26b7e04f8cSWim Van Sebroeck #include <linux/types.h>
27b7e04f8cSWim Van Sebroeck #include <linux/kernel.h>
28b7e04f8cSWim Van Sebroeck #include <linux/fs.h>
29b7e04f8cSWim Van Sebroeck #include <linux/miscdevice.h>
30b7e04f8cSWim Van Sebroeck #include <linux/init.h>
31b7e04f8cSWim Van Sebroeck #include <linux/ioport.h>
32b7e04f8cSWim Van Sebroeck #include <linux/watchdog.h>
33b7e04f8cSWim Van Sebroeck #include <linux/notifier.h>
34b7e04f8cSWim Van Sebroeck #include <linux/reboot.h>
35f2b79c6eSAlan Cox #include <linux/io.h>
36f2b79c6eSAlan Cox #include <linux/uaccess.h>
37b7e04f8cSWim Van Sebroeck 
38b7e04f8cSWim Van Sebroeck #include <asm/mach-types.h>
39b7e04f8cSWim Van Sebroeck 
40b7e04f8cSWim Van Sebroeck #define WATCHDOG_VERSION  "0.04"
41b7e04f8cSWim Van Sebroeck #define WATCHDOG_NAME     "Wdt977"
42b7e04f8cSWim Van Sebroeck 
43b7e04f8cSWim Van Sebroeck #define IO_INDEX_PORT	0x370		/* on some systems it can be 0x3F0 */
44b7e04f8cSWim Van Sebroeck #define IO_DATA_PORT	(IO_INDEX_PORT + 1)
45b7e04f8cSWim Van Sebroeck 
46b7e04f8cSWim Van Sebroeck #define UNLOCK_DATA	0x87
47b7e04f8cSWim Van Sebroeck #define LOCK_DATA	0xAA
48b7e04f8cSWim Van Sebroeck #define DEVICE_REGISTER	0x07
49b7e04f8cSWim Van Sebroeck 
50b7e04f8cSWim Van Sebroeck 
51b7e04f8cSWim Van Sebroeck #define	DEFAULT_TIMEOUT	60			/* default timeout in seconds */
52b7e04f8cSWim Van Sebroeck 
53b7e04f8cSWim Van Sebroeck static	int timeout = DEFAULT_TIMEOUT;
54b7e04f8cSWim Van Sebroeck static	int timeoutM;				/* timeout in minutes */
55b7e04f8cSWim Van Sebroeck static	unsigned long timer_alive;
56b7e04f8cSWim Van Sebroeck static	int testmode;
57b7e04f8cSWim Van Sebroeck static	char expect_close;
58c7dfd0ccSAlexey Dobriyan static	DEFINE_SPINLOCK(spinlock);
59b7e04f8cSWim Van Sebroeck 
60b7e04f8cSWim Van Sebroeck module_param(timeout, int, 0);
6176550d32SRandy Dunlap MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (60..15300, default="
62f2b79c6eSAlan Cox 				__MODULE_STRING(DEFAULT_TIMEOUT) ")");
63b7e04f8cSWim Van Sebroeck module_param(testmode, int, 0);
64b7e04f8cSWim Van Sebroeck MODULE_PARM_DESC(testmode, "Watchdog testmode (1 = no reboot), default=0");
65b7e04f8cSWim Van Sebroeck 
6686a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
6786a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
68f2b79c6eSAlan Cox MODULE_PARM_DESC(nowayout,
69f2b79c6eSAlan Cox 		"Watchdog cannot be stopped once started (default="
70f2b79c6eSAlan Cox 				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
71b7e04f8cSWim Van Sebroeck 
72b7e04f8cSWim Van Sebroeck /*
73b7e04f8cSWim Van Sebroeck  * Start the watchdog
74b7e04f8cSWim Van Sebroeck  */
75b7e04f8cSWim Van Sebroeck 
wdt977_start(void)76b7e04f8cSWim Van Sebroeck static int wdt977_start(void)
77b7e04f8cSWim Van Sebroeck {
78b7e04f8cSWim Van Sebroeck 	unsigned long flags;
79b7e04f8cSWim Van Sebroeck 
80b7e04f8cSWim Van Sebroeck 	spin_lock_irqsave(&spinlock, flags);
81b7e04f8cSWim Van Sebroeck 
82b7e04f8cSWim Van Sebroeck 	/* unlock the SuperIO chip */
83b7e04f8cSWim Van Sebroeck 	outb_p(UNLOCK_DATA, IO_INDEX_PORT);
84b7e04f8cSWim Van Sebroeck 	outb_p(UNLOCK_DATA, IO_INDEX_PORT);
85b7e04f8cSWim Van Sebroeck 
86b7e04f8cSWim Van Sebroeck 	/* select device Aux2 (device=8) and set watchdog regs F2, F3 and F4
87b7e04f8cSWim Van Sebroeck 	 * F2 has the timeout in minutes
88b7e04f8cSWim Van Sebroeck 	 * F3 could be set to the POWER LED blink (with GP17 set to PowerLed)
89b7e04f8cSWim Van Sebroeck 	 *   at timeout, and to reset timer on kbd/mouse activity (not impl.)
90b7e04f8cSWim Van Sebroeck 	 * F4 is used to just clear the TIMEOUT'ed state (bit 0)
91b7e04f8cSWim Van Sebroeck 	 */
92b7e04f8cSWim Van Sebroeck 	outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
93b7e04f8cSWim Van Sebroeck 	outb_p(0x08, IO_DATA_PORT);
94b7e04f8cSWim Van Sebroeck 	outb_p(0xF2, IO_INDEX_PORT);
95b7e04f8cSWim Van Sebroeck 	outb_p(timeoutM, IO_DATA_PORT);
96b7e04f8cSWim Van Sebroeck 	outb_p(0xF3, IO_INDEX_PORT);
97f2b79c6eSAlan Cox 	outb_p(0x00, IO_DATA_PORT);	/* another setting is 0E for
98f2b79c6eSAlan Cox 					   kbd/mouse/LED */
99b7e04f8cSWim Van Sebroeck 	outb_p(0xF4, IO_INDEX_PORT);
100b7e04f8cSWim Van Sebroeck 	outb_p(0x00, IO_DATA_PORT);
101b7e04f8cSWim Van Sebroeck 
102f2b79c6eSAlan Cox 	/* At last select device Aux1 (dev=7) and set GP16 as a
103f2b79c6eSAlan Cox 	 * watchdog output. In test mode watch the bit 1 on F4 to
104f2b79c6eSAlan Cox 	 * indicate "triggered"
105f2b79c6eSAlan Cox 	 */
106f2b79c6eSAlan Cox 	if (!testmode) {
107b7e04f8cSWim Van Sebroeck 		outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
108b7e04f8cSWim Van Sebroeck 		outb_p(0x07, IO_DATA_PORT);
109b7e04f8cSWim Van Sebroeck 		outb_p(0xE6, IO_INDEX_PORT);
110b7e04f8cSWim Van Sebroeck 		outb_p(0x08, IO_DATA_PORT);
111b7e04f8cSWim Van Sebroeck 	}
112b7e04f8cSWim Van Sebroeck 
113b7e04f8cSWim Van Sebroeck 	/* lock the SuperIO chip */
114b7e04f8cSWim Van Sebroeck 	outb_p(LOCK_DATA, IO_INDEX_PORT);
115b7e04f8cSWim Van Sebroeck 
116b7e04f8cSWim Van Sebroeck 	spin_unlock_irqrestore(&spinlock, flags);
11727c766aaSJoe Perches 	pr_info("activated\n");
118b7e04f8cSWim Van Sebroeck 
119b7e04f8cSWim Van Sebroeck 	return 0;
120b7e04f8cSWim Van Sebroeck }
121b7e04f8cSWim Van Sebroeck 
122b7e04f8cSWim Van Sebroeck /*
123b7e04f8cSWim Van Sebroeck  * Stop the watchdog
124b7e04f8cSWim Van Sebroeck  */
125b7e04f8cSWim Van Sebroeck 
wdt977_stop(void)126b7e04f8cSWim Van Sebroeck static int wdt977_stop(void)
127b7e04f8cSWim Van Sebroeck {
128b7e04f8cSWim Van Sebroeck 	unsigned long flags;
129b7e04f8cSWim Van Sebroeck 	spin_lock_irqsave(&spinlock, flags);
130b7e04f8cSWim Van Sebroeck 
131b7e04f8cSWim Van Sebroeck 	/* unlock the SuperIO chip */
132b7e04f8cSWim Van Sebroeck 	outb_p(UNLOCK_DATA, IO_INDEX_PORT);
133b7e04f8cSWim Van Sebroeck 	outb_p(UNLOCK_DATA, IO_INDEX_PORT);
134b7e04f8cSWim Van Sebroeck 
135b7e04f8cSWim Van Sebroeck 	/* select device Aux2 (device=8) and set watchdog regs F2,F3 and F4
136b7e04f8cSWim Van Sebroeck 	* F3 is reset to its default state
137b7e04f8cSWim Van Sebroeck 	* F4 can clear the TIMEOUT'ed state (bit 0) - back to default
138b7e04f8cSWim Van Sebroeck 	* We can not use GP17 as a PowerLed, as we use its usage as a RedLed
139b7e04f8cSWim Van Sebroeck 	*/
140b7e04f8cSWim Van Sebroeck 	outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
141b7e04f8cSWim Van Sebroeck 	outb_p(0x08, IO_DATA_PORT);
142b7e04f8cSWim Van Sebroeck 	outb_p(0xF2, IO_INDEX_PORT);
143b7e04f8cSWim Van Sebroeck 	outb_p(0xFF, IO_DATA_PORT);
144b7e04f8cSWim Van Sebroeck 	outb_p(0xF3, IO_INDEX_PORT);
145b7e04f8cSWim Van Sebroeck 	outb_p(0x00, IO_DATA_PORT);
146b7e04f8cSWim Van Sebroeck 	outb_p(0xF4, IO_INDEX_PORT);
147b7e04f8cSWim Van Sebroeck 	outb_p(0x00, IO_DATA_PORT);
148b7e04f8cSWim Van Sebroeck 	outb_p(0xF2, IO_INDEX_PORT);
149b7e04f8cSWim Van Sebroeck 	outb_p(0x00, IO_DATA_PORT);
150b7e04f8cSWim Van Sebroeck 
151f2b79c6eSAlan Cox 	/* at last select device Aux1 (dev=7) and set
152f2b79c6eSAlan Cox 	   GP16 as a watchdog output */
153b7e04f8cSWim Van Sebroeck 	outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
154b7e04f8cSWim Van Sebroeck 	outb_p(0x07, IO_DATA_PORT);
155b7e04f8cSWim Van Sebroeck 	outb_p(0xE6, IO_INDEX_PORT);
156b7e04f8cSWim Van Sebroeck 	outb_p(0x08, IO_DATA_PORT);
157b7e04f8cSWim Van Sebroeck 
158b7e04f8cSWim Van Sebroeck 	/* lock the SuperIO chip */
159b7e04f8cSWim Van Sebroeck 	outb_p(LOCK_DATA, IO_INDEX_PORT);
160b7e04f8cSWim Van Sebroeck 
161b7e04f8cSWim Van Sebroeck 	spin_unlock_irqrestore(&spinlock, flags);
16227c766aaSJoe Perches 	pr_info("shutdown\n");
163b7e04f8cSWim Van Sebroeck 
164b7e04f8cSWim Van Sebroeck 	return 0;
165b7e04f8cSWim Van Sebroeck }
166b7e04f8cSWim Van Sebroeck 
167b7e04f8cSWim Van Sebroeck /*
168b7e04f8cSWim Van Sebroeck  * Send a keepalive ping to the watchdog
169b7e04f8cSWim Van Sebroeck  * This is done by simply re-writing the timeout to reg. 0xF2
170b7e04f8cSWim Van Sebroeck  */
171b7e04f8cSWim Van Sebroeck 
wdt977_keepalive(void)172b7e04f8cSWim Van Sebroeck static int wdt977_keepalive(void)
173b7e04f8cSWim Van Sebroeck {
174b7e04f8cSWim Van Sebroeck 	unsigned long flags;
175b7e04f8cSWim Van Sebroeck 	spin_lock_irqsave(&spinlock, flags);
176b7e04f8cSWim Van Sebroeck 
177b7e04f8cSWim Van Sebroeck 	/* unlock the SuperIO chip */
178b7e04f8cSWim Van Sebroeck 	outb_p(UNLOCK_DATA, IO_INDEX_PORT);
179b7e04f8cSWim Van Sebroeck 	outb_p(UNLOCK_DATA, IO_INDEX_PORT);
180b7e04f8cSWim Van Sebroeck 
181b7e04f8cSWim Van Sebroeck 	/* select device Aux2 (device=8) and kicks watchdog reg F2 */
182b7e04f8cSWim Van Sebroeck 	/* F2 has the timeout in minutes */
183b7e04f8cSWim Van Sebroeck 	outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
184b7e04f8cSWim Van Sebroeck 	outb_p(0x08, IO_DATA_PORT);
185b7e04f8cSWim Van Sebroeck 	outb_p(0xF2, IO_INDEX_PORT);
186b7e04f8cSWim Van Sebroeck 	outb_p(timeoutM, IO_DATA_PORT);
187b7e04f8cSWim Van Sebroeck 
188b7e04f8cSWim Van Sebroeck 	/* lock the SuperIO chip */
189b7e04f8cSWim Van Sebroeck 	outb_p(LOCK_DATA, IO_INDEX_PORT);
190b7e04f8cSWim Van Sebroeck 	spin_unlock_irqrestore(&spinlock, flags);
191b7e04f8cSWim Van Sebroeck 
192b7e04f8cSWim Van Sebroeck 	return 0;
193b7e04f8cSWim Van Sebroeck }
194b7e04f8cSWim Van Sebroeck 
195b7e04f8cSWim Van Sebroeck /*
196b7e04f8cSWim Van Sebroeck  * Set the watchdog timeout value
197b7e04f8cSWim Van Sebroeck  */
198b7e04f8cSWim Van Sebroeck 
wdt977_set_timeout(int t)199b7e04f8cSWim Van Sebroeck static int wdt977_set_timeout(int t)
200b7e04f8cSWim Van Sebroeck {
201b7e04f8cSWim Van Sebroeck 	int tmrval;
202b7e04f8cSWim Van Sebroeck 
203b7e04f8cSWim Van Sebroeck 	/* convert seconds to minutes, rounding up */
204b7e04f8cSWim Van Sebroeck 	tmrval = (t + 59) / 60;
205b7e04f8cSWim Van Sebroeck 
206b7e04f8cSWim Van Sebroeck 	if (machine_is_netwinder()) {
207f2b79c6eSAlan Cox 		/* we have a hw bug somewhere, so each 977 minute is actually
208f2b79c6eSAlan Cox 		 * only 30sec. This limits the max timeout to half of device
209f2b79c6eSAlan Cox 		 * max of 255 minutes...
210b7e04f8cSWim Van Sebroeck 		 */
211b7e04f8cSWim Van Sebroeck 		tmrval += tmrval;
212b7e04f8cSWim Van Sebroeck 	}
213b7e04f8cSWim Van Sebroeck 
214f2b79c6eSAlan Cox 	if (tmrval < 1 || tmrval > 255)
215b7e04f8cSWim Van Sebroeck 		return -EINVAL;
216b7e04f8cSWim Van Sebroeck 
217f2b79c6eSAlan Cox 	/* timeout is the timeout in seconds, timeoutM is
218f2b79c6eSAlan Cox 	   the timeout in minutes) */
219b7e04f8cSWim Van Sebroeck 	timeout = t;
220b7e04f8cSWim Van Sebroeck 	timeoutM = tmrval;
221b7e04f8cSWim Van Sebroeck 	return 0;
222b7e04f8cSWim Van Sebroeck }
223b7e04f8cSWim Van Sebroeck 
224b7e04f8cSWim Van Sebroeck /*
225b7e04f8cSWim Van Sebroeck  * Get the watchdog status
226b7e04f8cSWim Van Sebroeck  */
227b7e04f8cSWim Van Sebroeck 
wdt977_get_status(int * status)228b7e04f8cSWim Van Sebroeck static int wdt977_get_status(int *status)
229b7e04f8cSWim Van Sebroeck {
230b7e04f8cSWim Van Sebroeck 	int new_status;
231b7e04f8cSWim Van Sebroeck 	unsigned long flags;
232b7e04f8cSWim Van Sebroeck 
233b7e04f8cSWim Van Sebroeck 	spin_lock_irqsave(&spinlock, flags);
234b7e04f8cSWim Van Sebroeck 
235b7e04f8cSWim Van Sebroeck 	/* unlock the SuperIO chip */
236b7e04f8cSWim Van Sebroeck 	outb_p(UNLOCK_DATA, IO_INDEX_PORT);
237b7e04f8cSWim Van Sebroeck 	outb_p(UNLOCK_DATA, IO_INDEX_PORT);
238b7e04f8cSWim Van Sebroeck 
239b7e04f8cSWim Van Sebroeck 	/* select device Aux2 (device=8) and read watchdog reg F4 */
240b7e04f8cSWim Van Sebroeck 	outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
241b7e04f8cSWim Van Sebroeck 	outb_p(0x08, IO_DATA_PORT);
242b7e04f8cSWim Van Sebroeck 	outb_p(0xF4, IO_INDEX_PORT);
243b7e04f8cSWim Van Sebroeck 	new_status = inb_p(IO_DATA_PORT);
244b7e04f8cSWim Van Sebroeck 
245b7e04f8cSWim Van Sebroeck 	/* lock the SuperIO chip */
246b7e04f8cSWim Van Sebroeck 	outb_p(LOCK_DATA, IO_INDEX_PORT);
247b7e04f8cSWim Van Sebroeck 
248b7e04f8cSWim Van Sebroeck 	spin_unlock_irqrestore(&spinlock, flags);
249b7e04f8cSWim Van Sebroeck 
250b7e04f8cSWim Van Sebroeck 	*status = 0;
251b7e04f8cSWim Van Sebroeck 	if (new_status & 1)
252b7e04f8cSWim Van Sebroeck 		*status |= WDIOF_CARDRESET;
253b7e04f8cSWim Van Sebroeck 
254b7e04f8cSWim Van Sebroeck 	return 0;
255b7e04f8cSWim Van Sebroeck }
256b7e04f8cSWim Van Sebroeck 
257b7e04f8cSWim Van Sebroeck 
258b7e04f8cSWim Van Sebroeck /*
259b7e04f8cSWim Van Sebroeck  *	/dev/watchdog handling
260b7e04f8cSWim Van Sebroeck  */
261b7e04f8cSWim Van Sebroeck 
wdt977_open(struct inode * inode,struct file * file)262b7e04f8cSWim Van Sebroeck static int wdt977_open(struct inode *inode, struct file *file)
263b7e04f8cSWim Van Sebroeck {
264b7e04f8cSWim Van Sebroeck 	/* If the watchdog is alive we don't need to start it again */
265b7e04f8cSWim Van Sebroeck 	if (test_and_set_bit(0, &timer_alive))
266b7e04f8cSWim Van Sebroeck 		return -EBUSY;
267b7e04f8cSWim Van Sebroeck 
268b7e04f8cSWim Van Sebroeck 	if (nowayout)
269b7e04f8cSWim Van Sebroeck 		__module_get(THIS_MODULE);
270b7e04f8cSWim Van Sebroeck 
271b7e04f8cSWim Van Sebroeck 	wdt977_start();
272c5bf68feSKirill Smelkov 	return stream_open(inode, file);
273b7e04f8cSWim Van Sebroeck }
274b7e04f8cSWim Van Sebroeck 
wdt977_release(struct inode * inode,struct file * file)275b7e04f8cSWim Van Sebroeck static int wdt977_release(struct inode *inode, struct file *file)
276b7e04f8cSWim Van Sebroeck {
277b7e04f8cSWim Van Sebroeck 	/*
278b7e04f8cSWim Van Sebroeck 	 *	Shut off the timer.
279b7e04f8cSWim Van Sebroeck 	 *	Lock it in if it's a module and we set nowayout
280b7e04f8cSWim Van Sebroeck 	 */
281f2b79c6eSAlan Cox 	if (expect_close == 42) {
282b7e04f8cSWim Van Sebroeck 		wdt977_stop();
283b7e04f8cSWim Van Sebroeck 		clear_bit(0, &timer_alive);
284b7e04f8cSWim Van Sebroeck 	} else {
285b7e04f8cSWim Van Sebroeck 		wdt977_keepalive();
28627c766aaSJoe Perches 		pr_crit("Unexpected close, not stopping watchdog!\n");
287b7e04f8cSWim Van Sebroeck 	}
288b7e04f8cSWim Van Sebroeck 	expect_close = 0;
289b7e04f8cSWim Van Sebroeck 	return 0;
290b7e04f8cSWim Van Sebroeck }
291b7e04f8cSWim Van Sebroeck 
292b7e04f8cSWim Van Sebroeck 
293b7e04f8cSWim Van Sebroeck /*
294b7e04f8cSWim Van Sebroeck  *      wdt977_write:
295b7e04f8cSWim Van Sebroeck  *      @file: file handle to the watchdog
296b7e04f8cSWim Van Sebroeck  *      @buf: buffer to write (unused as data does not matter here
297b7e04f8cSWim Van Sebroeck  *      @count: count of bytes
298b7e04f8cSWim Van Sebroeck  *      @ppos: pointer to the position to write. No seeks allowed
299b7e04f8cSWim Van Sebroeck  *
300b7e04f8cSWim Van Sebroeck  *      A write to a watchdog device is defined as a keepalive signal. Any
301b7e04f8cSWim Van Sebroeck  *      write of data will do, as we we don't define content meaning.
302b7e04f8cSWim Van Sebroeck  */
303b7e04f8cSWim Van Sebroeck 
wdt977_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)304b7e04f8cSWim Van Sebroeck static ssize_t wdt977_write(struct file *file, const char __user *buf,
305b7e04f8cSWim Van Sebroeck 			    size_t count, loff_t *ppos)
306b7e04f8cSWim Van Sebroeck {
307f2b79c6eSAlan Cox 	if (count) {
308f2b79c6eSAlan Cox 		if (!nowayout) {
309b7e04f8cSWim Van Sebroeck 			size_t i;
310b7e04f8cSWim Van Sebroeck 
311b7e04f8cSWim Van Sebroeck 			/* In case it was set long ago */
312b7e04f8cSWim Van Sebroeck 			expect_close = 0;
313b7e04f8cSWim Van Sebroeck 
314f2b79c6eSAlan Cox 			for (i = 0; i != count; i++) {
315b7e04f8cSWim Van Sebroeck 				char c;
316b7e04f8cSWim Van Sebroeck 				if (get_user(c, buf + i))
317b7e04f8cSWim Van Sebroeck 					return -EFAULT;
318b7e04f8cSWim Van Sebroeck 				if (c == 'V')
319b7e04f8cSWim Van Sebroeck 					expect_close = 42;
320b7e04f8cSWim Van Sebroeck 			}
321b7e04f8cSWim Van Sebroeck 		}
322b7e04f8cSWim Van Sebroeck 
323b7e04f8cSWim Van Sebroeck 		/* someone wrote to us, we should restart timer */
324b7e04f8cSWim Van Sebroeck 		wdt977_keepalive();
325b7e04f8cSWim Van Sebroeck 	}
326b7e04f8cSWim Van Sebroeck 	return count;
327b7e04f8cSWim Van Sebroeck }
328b7e04f8cSWim Van Sebroeck 
329f2b79c6eSAlan Cox static const struct watchdog_info ident = {
330f2b79c6eSAlan Cox 	.options =		WDIOF_SETTIMEOUT |
331f2b79c6eSAlan Cox 				WDIOF_MAGICCLOSE |
332f2b79c6eSAlan Cox 				WDIOF_KEEPALIVEPING,
333f2b79c6eSAlan Cox 	.firmware_version =	1,
334f2b79c6eSAlan Cox 	.identity =		WATCHDOG_NAME,
335f2b79c6eSAlan Cox };
336f2b79c6eSAlan Cox 
337b7e04f8cSWim Van Sebroeck /*
338b7e04f8cSWim Van Sebroeck  *      wdt977_ioctl:
339b7e04f8cSWim Van Sebroeck  *      @inode: inode of the device
340b7e04f8cSWim Van Sebroeck  *      @file: file handle to the device
341b7e04f8cSWim Van Sebroeck  *      @cmd: watchdog command
342b7e04f8cSWim Van Sebroeck  *      @arg: argument pointer
343b7e04f8cSWim Van Sebroeck  *
344b7e04f8cSWim Van Sebroeck  *      The watchdog API defines a common set of functions for all watchdogs
345b7e04f8cSWim Van Sebroeck  *      according to their available features.
346b7e04f8cSWim Van Sebroeck  */
347b7e04f8cSWim Van Sebroeck 
wdt977_ioctl(struct file * file,unsigned int cmd,unsigned long arg)348f2b79c6eSAlan Cox static long wdt977_ioctl(struct file *file, unsigned int cmd,
349f2b79c6eSAlan Cox 							unsigned long arg)
350b7e04f8cSWim Van Sebroeck {
351b7e04f8cSWim Van Sebroeck 	int status;
352b7e04f8cSWim Van Sebroeck 	int new_options, retval = -EINVAL;
353b7e04f8cSWim Van Sebroeck 	int new_timeout;
354b7e04f8cSWim Van Sebroeck 	union {
355b7e04f8cSWim Van Sebroeck 		struct watchdog_info __user *ident;
356b7e04f8cSWim Van Sebroeck 		int __user *i;
357b7e04f8cSWim Van Sebroeck 	} uarg;
358b7e04f8cSWim Van Sebroeck 
359b7e04f8cSWim Van Sebroeck 	uarg.i = (int __user *)arg;
360b7e04f8cSWim Van Sebroeck 
361f2b79c6eSAlan Cox 	switch (cmd) {
362b7e04f8cSWim Van Sebroeck 	case WDIOC_GETSUPPORT:
363b7e04f8cSWim Van Sebroeck 		return copy_to_user(uarg.ident, &ident,
364b7e04f8cSWim Van Sebroeck 			sizeof(ident)) ? -EFAULT : 0;
365b7e04f8cSWim Van Sebroeck 
366b7e04f8cSWim Van Sebroeck 	case WDIOC_GETSTATUS:
367b7e04f8cSWim Van Sebroeck 		wdt977_get_status(&status);
368b7e04f8cSWim Van Sebroeck 		return put_user(status, uarg.i);
369b7e04f8cSWim Van Sebroeck 
370b7e04f8cSWim Van Sebroeck 	case WDIOC_GETBOOTSTATUS:
371b7e04f8cSWim Van Sebroeck 		return put_user(0, uarg.i);
372b7e04f8cSWim Van Sebroeck 
373b7e04f8cSWim Van Sebroeck 	case WDIOC_SETOPTIONS:
374b7e04f8cSWim Van Sebroeck 		if (get_user(new_options, uarg.i))
375b7e04f8cSWim Van Sebroeck 			return -EFAULT;
376b7e04f8cSWim Van Sebroeck 
377b7e04f8cSWim Van Sebroeck 		if (new_options & WDIOS_DISABLECARD) {
378b7e04f8cSWim Van Sebroeck 			wdt977_stop();
379b7e04f8cSWim Van Sebroeck 			retval = 0;
380b7e04f8cSWim Van Sebroeck 		}
381b7e04f8cSWim Van Sebroeck 
382b7e04f8cSWim Van Sebroeck 		if (new_options & WDIOS_ENABLECARD) {
383b7e04f8cSWim Van Sebroeck 			wdt977_start();
384b7e04f8cSWim Van Sebroeck 			retval = 0;
385b7e04f8cSWim Van Sebroeck 		}
386b7e04f8cSWim Van Sebroeck 
387b7e04f8cSWim Van Sebroeck 		return retval;
388b7e04f8cSWim Van Sebroeck 
3890c06090cSWim Van Sebroeck 	case WDIOC_KEEPALIVE:
3900c06090cSWim Van Sebroeck 		wdt977_keepalive();
3910c06090cSWim Van Sebroeck 		return 0;
3920c06090cSWim Van Sebroeck 
393b7e04f8cSWim Van Sebroeck 	case WDIOC_SETTIMEOUT:
394b7e04f8cSWim Van Sebroeck 		if (get_user(new_timeout, uarg.i))
395b7e04f8cSWim Van Sebroeck 			return -EFAULT;
396b7e04f8cSWim Van Sebroeck 
397b7e04f8cSWim Van Sebroeck 		if (wdt977_set_timeout(new_timeout))
398b7e04f8cSWim Van Sebroeck 			return -EINVAL;
399b7e04f8cSWim Van Sebroeck 
400b7e04f8cSWim Van Sebroeck 		wdt977_keepalive();
401*bd490f82SGustavo A. R. Silva 		fallthrough;
402b7e04f8cSWim Van Sebroeck 
403b7e04f8cSWim Van Sebroeck 	case WDIOC_GETTIMEOUT:
404b7e04f8cSWim Van Sebroeck 		return put_user(timeout, uarg.i);
405b7e04f8cSWim Van Sebroeck 
4060c06090cSWim Van Sebroeck 	default:
4070c06090cSWim Van Sebroeck 		return -ENOTTY;
4080c06090cSWim Van Sebroeck 
409b7e04f8cSWim Van Sebroeck 	}
410b7e04f8cSWim Van Sebroeck }
411b7e04f8cSWim Van Sebroeck 
wdt977_notify_sys(struct notifier_block * this,unsigned long code,void * unused)412b7e04f8cSWim Van Sebroeck static int wdt977_notify_sys(struct notifier_block *this, unsigned long code,
413b7e04f8cSWim Van Sebroeck 	void *unused)
414b7e04f8cSWim Van Sebroeck {
415b7e04f8cSWim Van Sebroeck 	if (code == SYS_DOWN || code == SYS_HALT)
416b7e04f8cSWim Van Sebroeck 		wdt977_stop();
417b7e04f8cSWim Van Sebroeck 	return NOTIFY_DONE;
418b7e04f8cSWim Van Sebroeck }
419b7e04f8cSWim Van Sebroeck 
420f2b79c6eSAlan Cox static const struct file_operations wdt977_fops = {
421b7e04f8cSWim Van Sebroeck 	.owner		= THIS_MODULE,
422b7e04f8cSWim Van Sebroeck 	.llseek		= no_llseek,
423b7e04f8cSWim Van Sebroeck 	.write		= wdt977_write,
424f2b79c6eSAlan Cox 	.unlocked_ioctl	= wdt977_ioctl,
425b6dfb247SArnd Bergmann 	.compat_ioctl	= compat_ptr_ioctl,
426b7e04f8cSWim Van Sebroeck 	.open		= wdt977_open,
427b7e04f8cSWim Van Sebroeck 	.release	= wdt977_release,
428b7e04f8cSWim Van Sebroeck };
429b7e04f8cSWim Van Sebroeck 
430f2b79c6eSAlan Cox static struct miscdevice wdt977_miscdev = {
431b7e04f8cSWim Van Sebroeck 	.minor		= WATCHDOG_MINOR,
432b7e04f8cSWim Van Sebroeck 	.name		= "watchdog",
433b7e04f8cSWim Van Sebroeck 	.fops		= &wdt977_fops,
434b7e04f8cSWim Van Sebroeck };
435b7e04f8cSWim Van Sebroeck 
436b7e04f8cSWim Van Sebroeck static struct notifier_block wdt977_notifier = {
437b7e04f8cSWim Van Sebroeck 	.notifier_call = wdt977_notify_sys,
438b7e04f8cSWim Van Sebroeck };
439b7e04f8cSWim Van Sebroeck 
wd977_init(void)440b7e04f8cSWim Van Sebroeck static int __init wd977_init(void)
441b7e04f8cSWim Van Sebroeck {
442b7e04f8cSWim Van Sebroeck 	int rc;
443b7e04f8cSWim Van Sebroeck 
44427c766aaSJoe Perches 	pr_info("driver v%s\n", WATCHDOG_VERSION);
445b7e04f8cSWim Van Sebroeck 
446f2b79c6eSAlan Cox 	/* Check that the timeout value is within its range;
447f2b79c6eSAlan Cox 	   if not reset to the default */
448f2b79c6eSAlan Cox 	if (wdt977_set_timeout(timeout)) {
449b7e04f8cSWim Van Sebroeck 		wdt977_set_timeout(DEFAULT_TIMEOUT);
45027c766aaSJoe Perches 		pr_info("timeout value must be 60 < timeout < 15300, using %d\n",
451b7e04f8cSWim Van Sebroeck 			DEFAULT_TIMEOUT);
452b7e04f8cSWim Van Sebroeck 	}
453b7e04f8cSWim Van Sebroeck 
454b7e04f8cSWim Van Sebroeck 	/* on Netwinder the IOports are already reserved by
455b7e04f8cSWim Van Sebroeck 	 * arch/arm/mach-footbridge/netwinder-hw.c
456b7e04f8cSWim Van Sebroeck 	 */
457f2b79c6eSAlan Cox 	if (!machine_is_netwinder()) {
458f2b79c6eSAlan Cox 		if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME)) {
45927c766aaSJoe Perches 			pr_err("I/O address 0x%04x already in use\n",
460b7e04f8cSWim Van Sebroeck 			       IO_INDEX_PORT);
461b7e04f8cSWim Van Sebroeck 			rc = -EIO;
462b7e04f8cSWim Van Sebroeck 			goto err_out;
463b7e04f8cSWim Van Sebroeck 		}
464b7e04f8cSWim Van Sebroeck 	}
465b7e04f8cSWim Van Sebroeck 
466b7e04f8cSWim Van Sebroeck 	rc = register_reboot_notifier(&wdt977_notifier);
467f2b79c6eSAlan Cox 	if (rc) {
46827c766aaSJoe Perches 		pr_err("cannot register reboot notifier (err=%d)\n", rc);
469c6cb13aeSWim Van Sebroeck 		goto err_out_region;
470c6cb13aeSWim Van Sebroeck 	}
471c6cb13aeSWim Van Sebroeck 
472c6cb13aeSWim Van Sebroeck 	rc = misc_register(&wdt977_miscdev);
473f2b79c6eSAlan Cox 	if (rc) {
47427c766aaSJoe Perches 		pr_err("cannot register miscdev on minor=%d (err=%d)\n",
475c6cb13aeSWim Van Sebroeck 		       wdt977_miscdev.minor, rc);
476c6cb13aeSWim Van Sebroeck 		goto err_out_reboot;
477b7e04f8cSWim Van Sebroeck 	}
478b7e04f8cSWim Van Sebroeck 
47927c766aaSJoe Perches 	pr_info("initialized. timeout=%d sec (nowayout=%d, testmode=%i)\n",
480b7e04f8cSWim Van Sebroeck 		timeout, nowayout, testmode);
481b7e04f8cSWim Van Sebroeck 
482b7e04f8cSWim Van Sebroeck 	return 0;
483b7e04f8cSWim Van Sebroeck 
484c6cb13aeSWim Van Sebroeck err_out_reboot:
485c6cb13aeSWim Van Sebroeck 	unregister_reboot_notifier(&wdt977_notifier);
486b7e04f8cSWim Van Sebroeck err_out_region:
487b7e04f8cSWim Van Sebroeck 	if (!machine_is_netwinder())
488b7e04f8cSWim Van Sebroeck 		release_region(IO_INDEX_PORT, 2);
489b7e04f8cSWim Van Sebroeck err_out:
490b7e04f8cSWim Van Sebroeck 	return rc;
491b7e04f8cSWim Van Sebroeck }
492b7e04f8cSWim Van Sebroeck 
wd977_exit(void)493b7e04f8cSWim Van Sebroeck static void __exit wd977_exit(void)
494b7e04f8cSWim Van Sebroeck {
495b7e04f8cSWim Van Sebroeck 	wdt977_stop();
496b7e04f8cSWim Van Sebroeck 	misc_deregister(&wdt977_miscdev);
497b7e04f8cSWim Van Sebroeck 	unregister_reboot_notifier(&wdt977_notifier);
498b7e04f8cSWim Van Sebroeck 	release_region(IO_INDEX_PORT, 2);
499b7e04f8cSWim Van Sebroeck }
500b7e04f8cSWim Van Sebroeck 
501b7e04f8cSWim Van Sebroeck module_init(wd977_init);
502b7e04f8cSWim Van Sebroeck module_exit(wd977_exit);
503b7e04f8cSWim Van Sebroeck 
504b7e04f8cSWim Van Sebroeck MODULE_AUTHOR("Woody Suwalski <woodys@xandros.com>");
505b7e04f8cSWim Van Sebroeck MODULE_DESCRIPTION("W83977AF Watchdog driver");
506b7e04f8cSWim Van Sebroeck MODULE_LICENSE("GPL");
507