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