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