xref: /OK3568_Linux_fs/kernel/drivers/watchdog/smsc37b787_wdt.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  *	SMsC 37B787 Watchdog Timer driver for Linux 2.6.x.x
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  *	Based on acquirewdt.c by Alan Cox <alan@lxorguk.ukuu.org.uk>
6*4882a593Smuzhiyun  *	and some other existing drivers
7*4882a593Smuzhiyun  *
8*4882a593Smuzhiyun  *	The authors do NOT admit liability nor provide warranty for
9*4882a593Smuzhiyun  *	any of this software. This material is provided "AS-IS" in
10*4882a593Smuzhiyun  *	the hope that it may be useful for others.
11*4882a593Smuzhiyun  *
12*4882a593Smuzhiyun  *	(C) Copyright 2003-2006  Sven Anders <anders@anduras.de>
13*4882a593Smuzhiyun  *
14*4882a593Smuzhiyun  *  History:
15*4882a593Smuzhiyun  *	2003 - Created version 1.0 for Linux 2.4.x.
16*4882a593Smuzhiyun  *	2006 - Ported to Linux 2.6, added nowayout and MAGICCLOSE
17*4882a593Smuzhiyun  *	       features. Released version 1.1
18*4882a593Smuzhiyun  *
19*4882a593Smuzhiyun  *  Theory of operation:
20*4882a593Smuzhiyun  *
21*4882a593Smuzhiyun  *	A Watchdog Timer (WDT) is a hardware circuit that can
22*4882a593Smuzhiyun  *	reset the computer system in case of a software fault.
23*4882a593Smuzhiyun  *	You probably knew that already.
24*4882a593Smuzhiyun  *
25*4882a593Smuzhiyun  *	Usually a userspace daemon will notify the kernel WDT driver
26*4882a593Smuzhiyun  *	via the /dev/watchdog special device file that userspace is
27*4882a593Smuzhiyun  *	still alive, at regular intervals.  When such a notification
28*4882a593Smuzhiyun  *	occurs, the driver will usually tell the hardware watchdog
29*4882a593Smuzhiyun  *	that everything is in order, and that the watchdog should wait
30*4882a593Smuzhiyun  *	for yet another little while to reset the system.
31*4882a593Smuzhiyun  *	If userspace fails (RAM error, kernel bug, whatever), the
32*4882a593Smuzhiyun  *	notifications cease to occur, and the hardware watchdog will
33*4882a593Smuzhiyun  *	reset the system (causing a reboot) after the timeout occurs.
34*4882a593Smuzhiyun  *
35*4882a593Smuzhiyun  * Create device with:
36*4882a593Smuzhiyun  *  mknod /dev/watchdog c 10 130
37*4882a593Smuzhiyun  *
38*4882a593Smuzhiyun  * For an example userspace keep-alive daemon, see:
39*4882a593Smuzhiyun  *   Documentation/watchdog/wdt.rst
40*4882a593Smuzhiyun  */
41*4882a593Smuzhiyun 
42*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
43*4882a593Smuzhiyun 
44*4882a593Smuzhiyun #include <linux/module.h>
45*4882a593Smuzhiyun #include <linux/moduleparam.h>
46*4882a593Smuzhiyun #include <linux/types.h>
47*4882a593Smuzhiyun #include <linux/miscdevice.h>
48*4882a593Smuzhiyun #include <linux/watchdog.h>
49*4882a593Smuzhiyun #include <linux/delay.h>
50*4882a593Smuzhiyun #include <linux/fs.h>
51*4882a593Smuzhiyun #include <linux/ioport.h>
52*4882a593Smuzhiyun #include <linux/notifier.h>
53*4882a593Smuzhiyun #include <linux/reboot.h>
54*4882a593Smuzhiyun #include <linux/init.h>
55*4882a593Smuzhiyun #include <linux/spinlock.h>
56*4882a593Smuzhiyun #include <linux/io.h>
57*4882a593Smuzhiyun #include <linux/uaccess.h>
58*4882a593Smuzhiyun 
59*4882a593Smuzhiyun 
60*4882a593Smuzhiyun /* enable support for minutes as units? */
61*4882a593Smuzhiyun /* (does not always work correctly, so disabled by default!) */
62*4882a593Smuzhiyun #define SMSC_SUPPORT_MINUTES
63*4882a593Smuzhiyun #undef SMSC_SUPPORT_MINUTES
64*4882a593Smuzhiyun 
65*4882a593Smuzhiyun #define MAX_TIMEOUT     255
66*4882a593Smuzhiyun 
67*4882a593Smuzhiyun #define UNIT_SECOND     0
68*4882a593Smuzhiyun #define UNIT_MINUTE     1
69*4882a593Smuzhiyun 
70*4882a593Smuzhiyun #define VERSION		"1.1"
71*4882a593Smuzhiyun 
72*4882a593Smuzhiyun #define IOPORT		0x3F0
73*4882a593Smuzhiyun #define IOPORT_SIZE     2
74*4882a593Smuzhiyun #define IODEV_NO	8
75*4882a593Smuzhiyun 
76*4882a593Smuzhiyun static int unit = UNIT_SECOND;	/* timer's unit */
77*4882a593Smuzhiyun static int timeout = 60;	/* timeout value: default is 60 "units" */
78*4882a593Smuzhiyun static unsigned long timer_enabled;   /* is the timer enabled? */
79*4882a593Smuzhiyun 
80*4882a593Smuzhiyun static char expect_close;       /* is the close expected? */
81*4882a593Smuzhiyun 
82*4882a593Smuzhiyun static DEFINE_SPINLOCK(io_lock);/* to guard the watchdog from io races */
83*4882a593Smuzhiyun 
84*4882a593Smuzhiyun static bool nowayout = WATCHDOG_NOWAYOUT;
85*4882a593Smuzhiyun 
86*4882a593Smuzhiyun /* -- Low level function ----------------------------------------*/
87*4882a593Smuzhiyun 
88*4882a593Smuzhiyun /* unlock the IO chip */
89*4882a593Smuzhiyun 
open_io_config(void)90*4882a593Smuzhiyun static inline void open_io_config(void)
91*4882a593Smuzhiyun {
92*4882a593Smuzhiyun 	outb(0x55, IOPORT);
93*4882a593Smuzhiyun 	mdelay(1);
94*4882a593Smuzhiyun 	outb(0x55, IOPORT);
95*4882a593Smuzhiyun }
96*4882a593Smuzhiyun 
97*4882a593Smuzhiyun /* lock the IO chip */
close_io_config(void)98*4882a593Smuzhiyun static inline void close_io_config(void)
99*4882a593Smuzhiyun {
100*4882a593Smuzhiyun 	outb(0xAA, IOPORT);
101*4882a593Smuzhiyun }
102*4882a593Smuzhiyun 
103*4882a593Smuzhiyun /* select the IO device */
select_io_device(unsigned char devno)104*4882a593Smuzhiyun static inline void select_io_device(unsigned char devno)
105*4882a593Smuzhiyun {
106*4882a593Smuzhiyun 	outb(0x07, IOPORT);
107*4882a593Smuzhiyun 	outb(devno, IOPORT+1);
108*4882a593Smuzhiyun }
109*4882a593Smuzhiyun 
110*4882a593Smuzhiyun /* write to the control register */
write_io_cr(unsigned char reg,unsigned char data)111*4882a593Smuzhiyun static inline void write_io_cr(unsigned char reg, unsigned char data)
112*4882a593Smuzhiyun {
113*4882a593Smuzhiyun 	outb(reg, IOPORT);
114*4882a593Smuzhiyun 	outb(data, IOPORT+1);
115*4882a593Smuzhiyun }
116*4882a593Smuzhiyun 
117*4882a593Smuzhiyun /* read from the control register */
read_io_cr(unsigned char reg)118*4882a593Smuzhiyun static inline char read_io_cr(unsigned char reg)
119*4882a593Smuzhiyun {
120*4882a593Smuzhiyun 	outb(reg, IOPORT);
121*4882a593Smuzhiyun 	return inb(IOPORT+1);
122*4882a593Smuzhiyun }
123*4882a593Smuzhiyun 
124*4882a593Smuzhiyun /* -- Medium level functions ------------------------------------*/
125*4882a593Smuzhiyun 
gpio_bit12(unsigned char reg)126*4882a593Smuzhiyun static inline void gpio_bit12(unsigned char reg)
127*4882a593Smuzhiyun {
128*4882a593Smuzhiyun 	/* -- General Purpose I/O Bit 1.2 --
129*4882a593Smuzhiyun 	 * Bit 0,   In/Out: 0 = Output, 1 = Input
130*4882a593Smuzhiyun 	 * Bit 1,   Polarity: 0 = No Invert, 1 = Invert
131*4882a593Smuzhiyun 	 * Bit 2,   Group Enable Intr.: 0 = Disable, 1 = Enable
132*4882a593Smuzhiyun 	 * Bit 3/4, Function select: 00 = GPI/O, 01 = WDT, 10 = P17,
133*4882a593Smuzhiyun 	 *                           11 = Either Edge Triggered Intr. 2
134*4882a593Smuzhiyun 	 * Bit 5/6  (Reserved)
135*4882a593Smuzhiyun 	 * Bit 7,   Output Type: 0 = Push Pull Bit, 1 = Open Drain
136*4882a593Smuzhiyun 	 */
137*4882a593Smuzhiyun 	write_io_cr(0xE2, reg);
138*4882a593Smuzhiyun }
139*4882a593Smuzhiyun 
gpio_bit13(unsigned char reg)140*4882a593Smuzhiyun static inline void gpio_bit13(unsigned char reg)
141*4882a593Smuzhiyun {
142*4882a593Smuzhiyun 	/* -- General Purpose I/O Bit 1.3 --
143*4882a593Smuzhiyun 	 * Bit 0,  In/Out: 0 = Output, 1 = Input
144*4882a593Smuzhiyun 	 * Bit 1,  Polarity: 0 = No Invert, 1 = Invert
145*4882a593Smuzhiyun 	 * Bit 2,  Group Enable Intr.: 0 = Disable, 1 = Enable
146*4882a593Smuzhiyun 	 * Bit 3,  Function select: 0 = GPI/O, 1 = LED
147*4882a593Smuzhiyun 	 * Bit 4-6 (Reserved)
148*4882a593Smuzhiyun 	 * Bit 7,  Output Type: 0 = Push Pull Bit, 1 = Open Drain
149*4882a593Smuzhiyun 	 */
150*4882a593Smuzhiyun 	write_io_cr(0xE3, reg);
151*4882a593Smuzhiyun }
152*4882a593Smuzhiyun 
wdt_timer_units(unsigned char new_units)153*4882a593Smuzhiyun static inline void wdt_timer_units(unsigned char new_units)
154*4882a593Smuzhiyun {
155*4882a593Smuzhiyun 	/* -- Watchdog timer units --
156*4882a593Smuzhiyun 	 * Bit 0-6 (Reserved)
157*4882a593Smuzhiyun 	 * Bit 7,  WDT Time-out Value Units Select
158*4882a593Smuzhiyun 	 *         (0 = Minutes, 1 = Seconds)
159*4882a593Smuzhiyun 	 */
160*4882a593Smuzhiyun 	write_io_cr(0xF1, new_units);
161*4882a593Smuzhiyun }
162*4882a593Smuzhiyun 
wdt_timeout_value(unsigned char new_timeout)163*4882a593Smuzhiyun static inline void wdt_timeout_value(unsigned char new_timeout)
164*4882a593Smuzhiyun {
165*4882a593Smuzhiyun 	/* -- Watchdog Timer Time-out Value --
166*4882a593Smuzhiyun 	 * Bit 0-7 Binary coded units (0=Disabled, 1..255)
167*4882a593Smuzhiyun 	 */
168*4882a593Smuzhiyun 	write_io_cr(0xF2, new_timeout);
169*4882a593Smuzhiyun }
170*4882a593Smuzhiyun 
wdt_timer_conf(unsigned char conf)171*4882a593Smuzhiyun static inline void wdt_timer_conf(unsigned char conf)
172*4882a593Smuzhiyun {
173*4882a593Smuzhiyun 	/* -- Watchdog timer configuration --
174*4882a593Smuzhiyun 	 * Bit 0   Joystick enable: 0* = No Reset, 1 = Reset WDT upon
175*4882a593Smuzhiyun 	 *							Gameport I/O
176*4882a593Smuzhiyun 	 * Bit 1   Keyboard enable: 0* = No Reset, 1 = Reset WDT upon KBD Intr.
177*4882a593Smuzhiyun 	 * Bit 2   Mouse enable: 0* = No Reset, 1 = Reset WDT upon Mouse Intr
178*4882a593Smuzhiyun 	 * Bit 3   Reset the timer
179*4882a593Smuzhiyun 	 *         (Wrong in SMsC documentation? Given as: PowerLED Timout
180*4882a593Smuzhiyun 	 *							Enabled)
181*4882a593Smuzhiyun 	 * Bit 4-7 WDT Interrupt Mapping: (0000* = Disabled,
182*4882a593Smuzhiyun 	 *            0001=IRQ1, 0010=(Invalid), 0011=IRQ3 to 1111=IRQ15)
183*4882a593Smuzhiyun 	 */
184*4882a593Smuzhiyun 	write_io_cr(0xF3, conf);
185*4882a593Smuzhiyun }
186*4882a593Smuzhiyun 
wdt_timer_ctrl(unsigned char reg)187*4882a593Smuzhiyun static inline void wdt_timer_ctrl(unsigned char reg)
188*4882a593Smuzhiyun {
189*4882a593Smuzhiyun 	/* -- Watchdog timer control --
190*4882a593Smuzhiyun 	 * Bit 0   Status Bit: 0 = Timer counting, 1 = Timeout occurred
191*4882a593Smuzhiyun 	 * Bit 1   Power LED Toggle: 0 = Disable Toggle, 1 = Toggle at 1 Hz
192*4882a593Smuzhiyun 	 * Bit 2   Force Timeout: 1 = Forces WD timeout event (self-cleaning)
193*4882a593Smuzhiyun 	 * Bit 3   P20 Force Timeout enabled:
194*4882a593Smuzhiyun 	 *          0 = P20 activity does not generate the WD timeout event
195*4882a593Smuzhiyun 	 *          1 = P20 Allows rising edge of P20, from the keyboard
196*4882a593Smuzhiyun 	 *              controller, to force the WD timeout event.
197*4882a593Smuzhiyun 	 * Bit 4   (Reserved)
198*4882a593Smuzhiyun 	 * -- Soft power management --
199*4882a593Smuzhiyun 	 * Bit 5   Stop Counter: 1 = Stop software power down counter
200*4882a593Smuzhiyun 	 *            set via register 0xB8, (self-cleaning)
201*4882a593Smuzhiyun 	 *            (Upon read: 0 = Counter running, 1 = Counter stopped)
202*4882a593Smuzhiyun 	 * Bit 6   Restart Counter: 1 = Restart software power down counter
203*4882a593Smuzhiyun 	 *            set via register 0xB8, (self-cleaning)
204*4882a593Smuzhiyun 	 * Bit 7   SPOFF: 1 = Force software power down (self-cleaning)
205*4882a593Smuzhiyun 	 */
206*4882a593Smuzhiyun 	write_io_cr(0xF4, reg);
207*4882a593Smuzhiyun }
208*4882a593Smuzhiyun 
209*4882a593Smuzhiyun /* -- Higher level functions ------------------------------------*/
210*4882a593Smuzhiyun 
211*4882a593Smuzhiyun /* initialize watchdog */
212*4882a593Smuzhiyun 
wb_smsc_wdt_initialize(void)213*4882a593Smuzhiyun static void wb_smsc_wdt_initialize(void)
214*4882a593Smuzhiyun {
215*4882a593Smuzhiyun 	unsigned char old;
216*4882a593Smuzhiyun 
217*4882a593Smuzhiyun 	spin_lock(&io_lock);
218*4882a593Smuzhiyun 	open_io_config();
219*4882a593Smuzhiyun 	select_io_device(IODEV_NO);
220*4882a593Smuzhiyun 
221*4882a593Smuzhiyun 	/* enable the watchdog */
222*4882a593Smuzhiyun 	gpio_bit13(0x08);  /* Select pin 80 = LED not GPIO */
223*4882a593Smuzhiyun 	gpio_bit12(0x0A);  /* Set pin 79 = WDT not
224*4882a593Smuzhiyun 			      GPIO/Output/Polarity=Invert */
225*4882a593Smuzhiyun 	/* disable the timeout */
226*4882a593Smuzhiyun 	wdt_timeout_value(0);
227*4882a593Smuzhiyun 
228*4882a593Smuzhiyun 	/* reset control register */
229*4882a593Smuzhiyun 	wdt_timer_ctrl(0x00);
230*4882a593Smuzhiyun 
231*4882a593Smuzhiyun 	/* reset configuration register */
232*4882a593Smuzhiyun 	wdt_timer_conf(0x00);
233*4882a593Smuzhiyun 
234*4882a593Smuzhiyun 	/* read old (timer units) register */
235*4882a593Smuzhiyun 	old = read_io_cr(0xF1) & 0x7F;
236*4882a593Smuzhiyun 	if (unit == UNIT_SECOND)
237*4882a593Smuzhiyun 		old |= 0x80;	/* set to seconds */
238*4882a593Smuzhiyun 
239*4882a593Smuzhiyun 	/* set the watchdog timer units */
240*4882a593Smuzhiyun 	wdt_timer_units(old);
241*4882a593Smuzhiyun 
242*4882a593Smuzhiyun 	close_io_config();
243*4882a593Smuzhiyun 	spin_unlock(&io_lock);
244*4882a593Smuzhiyun }
245*4882a593Smuzhiyun 
246*4882a593Smuzhiyun /* shutdown the watchdog */
247*4882a593Smuzhiyun 
wb_smsc_wdt_shutdown(void)248*4882a593Smuzhiyun static void wb_smsc_wdt_shutdown(void)
249*4882a593Smuzhiyun {
250*4882a593Smuzhiyun 	spin_lock(&io_lock);
251*4882a593Smuzhiyun 	open_io_config();
252*4882a593Smuzhiyun 	select_io_device(IODEV_NO);
253*4882a593Smuzhiyun 
254*4882a593Smuzhiyun 	/* disable the watchdog */
255*4882a593Smuzhiyun 	gpio_bit13(0x09);
256*4882a593Smuzhiyun 	gpio_bit12(0x09);
257*4882a593Smuzhiyun 
258*4882a593Smuzhiyun 	/* reset watchdog config register */
259*4882a593Smuzhiyun 	wdt_timer_conf(0x00);
260*4882a593Smuzhiyun 
261*4882a593Smuzhiyun 	/* reset watchdog control register */
262*4882a593Smuzhiyun 	wdt_timer_ctrl(0x00);
263*4882a593Smuzhiyun 
264*4882a593Smuzhiyun 	/* disable timeout */
265*4882a593Smuzhiyun 	wdt_timeout_value(0x00);
266*4882a593Smuzhiyun 
267*4882a593Smuzhiyun 	close_io_config();
268*4882a593Smuzhiyun 	spin_unlock(&io_lock);
269*4882a593Smuzhiyun }
270*4882a593Smuzhiyun 
271*4882a593Smuzhiyun /* set timeout => enable watchdog */
272*4882a593Smuzhiyun 
wb_smsc_wdt_set_timeout(unsigned char new_timeout)273*4882a593Smuzhiyun static void wb_smsc_wdt_set_timeout(unsigned char new_timeout)
274*4882a593Smuzhiyun {
275*4882a593Smuzhiyun 	spin_lock(&io_lock);
276*4882a593Smuzhiyun 	open_io_config();
277*4882a593Smuzhiyun 	select_io_device(IODEV_NO);
278*4882a593Smuzhiyun 
279*4882a593Smuzhiyun 	/* set Power LED to blink, if we enable the timeout */
280*4882a593Smuzhiyun 	wdt_timer_ctrl((new_timeout == 0) ? 0x00 : 0x02);
281*4882a593Smuzhiyun 
282*4882a593Smuzhiyun 	/* set timeout value */
283*4882a593Smuzhiyun 	wdt_timeout_value(new_timeout);
284*4882a593Smuzhiyun 
285*4882a593Smuzhiyun 	close_io_config();
286*4882a593Smuzhiyun 	spin_unlock(&io_lock);
287*4882a593Smuzhiyun }
288*4882a593Smuzhiyun 
289*4882a593Smuzhiyun /* get timeout */
290*4882a593Smuzhiyun 
wb_smsc_wdt_get_timeout(void)291*4882a593Smuzhiyun static unsigned char wb_smsc_wdt_get_timeout(void)
292*4882a593Smuzhiyun {
293*4882a593Smuzhiyun 	unsigned char set_timeout;
294*4882a593Smuzhiyun 
295*4882a593Smuzhiyun 	spin_lock(&io_lock);
296*4882a593Smuzhiyun 	open_io_config();
297*4882a593Smuzhiyun 	select_io_device(IODEV_NO);
298*4882a593Smuzhiyun 	set_timeout = read_io_cr(0xF2);
299*4882a593Smuzhiyun 	close_io_config();
300*4882a593Smuzhiyun 	spin_unlock(&io_lock);
301*4882a593Smuzhiyun 
302*4882a593Smuzhiyun 	return set_timeout;
303*4882a593Smuzhiyun }
304*4882a593Smuzhiyun 
305*4882a593Smuzhiyun /* disable watchdog */
306*4882a593Smuzhiyun 
wb_smsc_wdt_disable(void)307*4882a593Smuzhiyun static void wb_smsc_wdt_disable(void)
308*4882a593Smuzhiyun {
309*4882a593Smuzhiyun 	/* set the timeout to 0 to disable the watchdog */
310*4882a593Smuzhiyun 	wb_smsc_wdt_set_timeout(0);
311*4882a593Smuzhiyun }
312*4882a593Smuzhiyun 
313*4882a593Smuzhiyun /* enable watchdog by setting the current timeout */
314*4882a593Smuzhiyun 
wb_smsc_wdt_enable(void)315*4882a593Smuzhiyun static void wb_smsc_wdt_enable(void)
316*4882a593Smuzhiyun {
317*4882a593Smuzhiyun 	/* set the current timeout... */
318*4882a593Smuzhiyun 	wb_smsc_wdt_set_timeout(timeout);
319*4882a593Smuzhiyun }
320*4882a593Smuzhiyun 
321*4882a593Smuzhiyun /* reset the timer */
322*4882a593Smuzhiyun 
wb_smsc_wdt_reset_timer(void)323*4882a593Smuzhiyun static void wb_smsc_wdt_reset_timer(void)
324*4882a593Smuzhiyun {
325*4882a593Smuzhiyun 	spin_lock(&io_lock);
326*4882a593Smuzhiyun 	open_io_config();
327*4882a593Smuzhiyun 	select_io_device(IODEV_NO);
328*4882a593Smuzhiyun 
329*4882a593Smuzhiyun 	/* reset the timer */
330*4882a593Smuzhiyun 	wdt_timeout_value(timeout);
331*4882a593Smuzhiyun 	wdt_timer_conf(0x08);
332*4882a593Smuzhiyun 
333*4882a593Smuzhiyun 	close_io_config();
334*4882a593Smuzhiyun 	spin_unlock(&io_lock);
335*4882a593Smuzhiyun }
336*4882a593Smuzhiyun 
337*4882a593Smuzhiyun /* return, if the watchdog is enabled (timeout is set...) */
338*4882a593Smuzhiyun 
wb_smsc_wdt_status(void)339*4882a593Smuzhiyun static int wb_smsc_wdt_status(void)
340*4882a593Smuzhiyun {
341*4882a593Smuzhiyun 	return (wb_smsc_wdt_get_timeout() == 0) ? 0 : WDIOF_KEEPALIVEPING;
342*4882a593Smuzhiyun }
343*4882a593Smuzhiyun 
344*4882a593Smuzhiyun 
345*4882a593Smuzhiyun /* -- File operations -------------------------------------------*/
346*4882a593Smuzhiyun 
347*4882a593Smuzhiyun /* open => enable watchdog and set initial timeout */
348*4882a593Smuzhiyun 
wb_smsc_wdt_open(struct inode * inode,struct file * file)349*4882a593Smuzhiyun static int wb_smsc_wdt_open(struct inode *inode, struct file *file)
350*4882a593Smuzhiyun {
351*4882a593Smuzhiyun 	/* /dev/watchdog can only be opened once */
352*4882a593Smuzhiyun 
353*4882a593Smuzhiyun 	if (test_and_set_bit(0, &timer_enabled))
354*4882a593Smuzhiyun 		return -EBUSY;
355*4882a593Smuzhiyun 
356*4882a593Smuzhiyun 	if (nowayout)
357*4882a593Smuzhiyun 		__module_get(THIS_MODULE);
358*4882a593Smuzhiyun 
359*4882a593Smuzhiyun 	/* Reload and activate timer */
360*4882a593Smuzhiyun 	wb_smsc_wdt_enable();
361*4882a593Smuzhiyun 
362*4882a593Smuzhiyun 	pr_info("Watchdog enabled. Timeout set to %d %s\n",
363*4882a593Smuzhiyun 		timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)");
364*4882a593Smuzhiyun 
365*4882a593Smuzhiyun 	return stream_open(inode, file);
366*4882a593Smuzhiyun }
367*4882a593Smuzhiyun 
368*4882a593Smuzhiyun /* close => shut off the timer */
369*4882a593Smuzhiyun 
wb_smsc_wdt_release(struct inode * inode,struct file * file)370*4882a593Smuzhiyun static int wb_smsc_wdt_release(struct inode *inode, struct file *file)
371*4882a593Smuzhiyun {
372*4882a593Smuzhiyun 	/* Shut off the timer. */
373*4882a593Smuzhiyun 
374*4882a593Smuzhiyun 	if (expect_close == 42) {
375*4882a593Smuzhiyun 		wb_smsc_wdt_disable();
376*4882a593Smuzhiyun 		pr_info("Watchdog disabled, sleeping again...\n");
377*4882a593Smuzhiyun 	} else {
378*4882a593Smuzhiyun 		pr_crit("Unexpected close, not stopping watchdog!\n");
379*4882a593Smuzhiyun 		wb_smsc_wdt_reset_timer();
380*4882a593Smuzhiyun 	}
381*4882a593Smuzhiyun 
382*4882a593Smuzhiyun 	clear_bit(0, &timer_enabled);
383*4882a593Smuzhiyun 	expect_close = 0;
384*4882a593Smuzhiyun 	return 0;
385*4882a593Smuzhiyun }
386*4882a593Smuzhiyun 
387*4882a593Smuzhiyun /* write => update the timer to keep the machine alive */
388*4882a593Smuzhiyun 
wb_smsc_wdt_write(struct file * file,const char __user * data,size_t len,loff_t * ppos)389*4882a593Smuzhiyun static ssize_t wb_smsc_wdt_write(struct file *file, const char __user *data,
390*4882a593Smuzhiyun 				 size_t len, loff_t *ppos)
391*4882a593Smuzhiyun {
392*4882a593Smuzhiyun 	/* See if we got the magic character 'V' and reload the timer */
393*4882a593Smuzhiyun 	if (len) {
394*4882a593Smuzhiyun 		if (!nowayout) {
395*4882a593Smuzhiyun 			size_t i;
396*4882a593Smuzhiyun 
397*4882a593Smuzhiyun 			/* reset expect flag */
398*4882a593Smuzhiyun 			expect_close = 0;
399*4882a593Smuzhiyun 
400*4882a593Smuzhiyun 			/* scan to see whether or not we got the
401*4882a593Smuzhiyun 			   magic character */
402*4882a593Smuzhiyun 			for (i = 0; i != len; i++) {
403*4882a593Smuzhiyun 				char c;
404*4882a593Smuzhiyun 				if (get_user(c, data + i))
405*4882a593Smuzhiyun 					return -EFAULT;
406*4882a593Smuzhiyun 				if (c == 'V')
407*4882a593Smuzhiyun 					expect_close = 42;
408*4882a593Smuzhiyun 			}
409*4882a593Smuzhiyun 		}
410*4882a593Smuzhiyun 
411*4882a593Smuzhiyun 		/* someone wrote to us, we should reload the timer */
412*4882a593Smuzhiyun 		wb_smsc_wdt_reset_timer();
413*4882a593Smuzhiyun 	}
414*4882a593Smuzhiyun 	return len;
415*4882a593Smuzhiyun }
416*4882a593Smuzhiyun 
417*4882a593Smuzhiyun /* ioctl => control interface */
418*4882a593Smuzhiyun 
wb_smsc_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)419*4882a593Smuzhiyun static long wb_smsc_wdt_ioctl(struct file *file,
420*4882a593Smuzhiyun 					unsigned int cmd, unsigned long arg)
421*4882a593Smuzhiyun {
422*4882a593Smuzhiyun 	int new_timeout;
423*4882a593Smuzhiyun 
424*4882a593Smuzhiyun 	union {
425*4882a593Smuzhiyun 		struct watchdog_info __user *ident;
426*4882a593Smuzhiyun 		int __user *i;
427*4882a593Smuzhiyun 	} uarg;
428*4882a593Smuzhiyun 
429*4882a593Smuzhiyun 	static const struct watchdog_info ident = {
430*4882a593Smuzhiyun 		.options =		WDIOF_KEEPALIVEPING |
431*4882a593Smuzhiyun 					WDIOF_SETTIMEOUT |
432*4882a593Smuzhiyun 					WDIOF_MAGICCLOSE,
433*4882a593Smuzhiyun 		.firmware_version =	0,
434*4882a593Smuzhiyun 		.identity =		"SMsC 37B787 Watchdog",
435*4882a593Smuzhiyun 	};
436*4882a593Smuzhiyun 
437*4882a593Smuzhiyun 	uarg.i = (int __user *)arg;
438*4882a593Smuzhiyun 
439*4882a593Smuzhiyun 	switch (cmd) {
440*4882a593Smuzhiyun 	case WDIOC_GETSUPPORT:
441*4882a593Smuzhiyun 		return copy_to_user(uarg.ident, &ident, sizeof(ident))
442*4882a593Smuzhiyun 								? -EFAULT : 0;
443*4882a593Smuzhiyun 	case WDIOC_GETSTATUS:
444*4882a593Smuzhiyun 		return put_user(wb_smsc_wdt_status(), uarg.i);
445*4882a593Smuzhiyun 	case WDIOC_GETBOOTSTATUS:
446*4882a593Smuzhiyun 		return put_user(0, uarg.i);
447*4882a593Smuzhiyun 	case WDIOC_SETOPTIONS:
448*4882a593Smuzhiyun 	{
449*4882a593Smuzhiyun 		int options, retval = -EINVAL;
450*4882a593Smuzhiyun 
451*4882a593Smuzhiyun 		if (get_user(options, uarg.i))
452*4882a593Smuzhiyun 			return -EFAULT;
453*4882a593Smuzhiyun 
454*4882a593Smuzhiyun 		if (options & WDIOS_DISABLECARD) {
455*4882a593Smuzhiyun 			wb_smsc_wdt_disable();
456*4882a593Smuzhiyun 			retval = 0;
457*4882a593Smuzhiyun 		}
458*4882a593Smuzhiyun 		if (options & WDIOS_ENABLECARD) {
459*4882a593Smuzhiyun 			wb_smsc_wdt_enable();
460*4882a593Smuzhiyun 			retval = 0;
461*4882a593Smuzhiyun 		}
462*4882a593Smuzhiyun 		return retval;
463*4882a593Smuzhiyun 	}
464*4882a593Smuzhiyun 	case WDIOC_KEEPALIVE:
465*4882a593Smuzhiyun 		wb_smsc_wdt_reset_timer();
466*4882a593Smuzhiyun 		return 0;
467*4882a593Smuzhiyun 	case WDIOC_SETTIMEOUT:
468*4882a593Smuzhiyun 		if (get_user(new_timeout, uarg.i))
469*4882a593Smuzhiyun 			return -EFAULT;
470*4882a593Smuzhiyun 		/* the API states this is given in secs */
471*4882a593Smuzhiyun 		if (unit == UNIT_MINUTE)
472*4882a593Smuzhiyun 			new_timeout /= 60;
473*4882a593Smuzhiyun 		if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
474*4882a593Smuzhiyun 			return -EINVAL;
475*4882a593Smuzhiyun 		timeout = new_timeout;
476*4882a593Smuzhiyun 		wb_smsc_wdt_set_timeout(timeout);
477*4882a593Smuzhiyun 		fallthrough;	/* and return the new timeout */
478*4882a593Smuzhiyun 	case WDIOC_GETTIMEOUT:
479*4882a593Smuzhiyun 		new_timeout = timeout;
480*4882a593Smuzhiyun 		if (unit == UNIT_MINUTE)
481*4882a593Smuzhiyun 			new_timeout *= 60;
482*4882a593Smuzhiyun 		return put_user(new_timeout, uarg.i);
483*4882a593Smuzhiyun 	default:
484*4882a593Smuzhiyun 		return -ENOTTY;
485*4882a593Smuzhiyun 	}
486*4882a593Smuzhiyun }
487*4882a593Smuzhiyun 
488*4882a593Smuzhiyun /* -- Notifier funtions -----------------------------------------*/
489*4882a593Smuzhiyun 
wb_smsc_wdt_notify_sys(struct notifier_block * this,unsigned long code,void * unused)490*4882a593Smuzhiyun static int wb_smsc_wdt_notify_sys(struct notifier_block *this,
491*4882a593Smuzhiyun 					unsigned long code, void *unused)
492*4882a593Smuzhiyun {
493*4882a593Smuzhiyun 	if (code == SYS_DOWN || code == SYS_HALT) {
494*4882a593Smuzhiyun 		/* set timeout to 0, to avoid possible race-condition */
495*4882a593Smuzhiyun 		timeout = 0;
496*4882a593Smuzhiyun 		wb_smsc_wdt_disable();
497*4882a593Smuzhiyun 	}
498*4882a593Smuzhiyun 	return NOTIFY_DONE;
499*4882a593Smuzhiyun }
500*4882a593Smuzhiyun 
501*4882a593Smuzhiyun /* -- Module's structures ---------------------------------------*/
502*4882a593Smuzhiyun 
503*4882a593Smuzhiyun static const struct file_operations wb_smsc_wdt_fops = {
504*4882a593Smuzhiyun 	.owner	  = THIS_MODULE,
505*4882a593Smuzhiyun 	.llseek		= no_llseek,
506*4882a593Smuzhiyun 	.write		= wb_smsc_wdt_write,
507*4882a593Smuzhiyun 	.unlocked_ioctl	= wb_smsc_wdt_ioctl,
508*4882a593Smuzhiyun 	.compat_ioctl	= compat_ptr_ioctl,
509*4882a593Smuzhiyun 	.open		= wb_smsc_wdt_open,
510*4882a593Smuzhiyun 	.release	= wb_smsc_wdt_release,
511*4882a593Smuzhiyun };
512*4882a593Smuzhiyun 
513*4882a593Smuzhiyun static struct notifier_block wb_smsc_wdt_notifier = {
514*4882a593Smuzhiyun 	.notifier_call  = wb_smsc_wdt_notify_sys,
515*4882a593Smuzhiyun };
516*4882a593Smuzhiyun 
517*4882a593Smuzhiyun static struct miscdevice wb_smsc_wdt_miscdev = {
518*4882a593Smuzhiyun 	.minor		= WATCHDOG_MINOR,
519*4882a593Smuzhiyun 	.name		= "watchdog",
520*4882a593Smuzhiyun 	.fops		= &wb_smsc_wdt_fops,
521*4882a593Smuzhiyun };
522*4882a593Smuzhiyun 
523*4882a593Smuzhiyun /* -- Module init functions -------------------------------------*/
524*4882a593Smuzhiyun 
525*4882a593Smuzhiyun /* module's "constructor" */
526*4882a593Smuzhiyun 
wb_smsc_wdt_init(void)527*4882a593Smuzhiyun static int __init wb_smsc_wdt_init(void)
528*4882a593Smuzhiyun {
529*4882a593Smuzhiyun 	int ret;
530*4882a593Smuzhiyun 
531*4882a593Smuzhiyun 	pr_info("SMsC 37B787 watchdog component driver "
532*4882a593Smuzhiyun 		VERSION " initialising...\n");
533*4882a593Smuzhiyun 
534*4882a593Smuzhiyun 	if (!request_region(IOPORT, IOPORT_SIZE, "SMsC 37B787 watchdog")) {
535*4882a593Smuzhiyun 		pr_err("Unable to register IO port %#x\n", IOPORT);
536*4882a593Smuzhiyun 		ret = -EBUSY;
537*4882a593Smuzhiyun 		goto out_pnp;
538*4882a593Smuzhiyun 	}
539*4882a593Smuzhiyun 
540*4882a593Smuzhiyun 	/* set new maximum, if it's too big */
541*4882a593Smuzhiyun 	if (timeout > MAX_TIMEOUT)
542*4882a593Smuzhiyun 		timeout = MAX_TIMEOUT;
543*4882a593Smuzhiyun 
544*4882a593Smuzhiyun 	/* init the watchdog timer */
545*4882a593Smuzhiyun 	wb_smsc_wdt_initialize();
546*4882a593Smuzhiyun 
547*4882a593Smuzhiyun 	ret = register_reboot_notifier(&wb_smsc_wdt_notifier);
548*4882a593Smuzhiyun 	if (ret) {
549*4882a593Smuzhiyun 		pr_err("Unable to register reboot notifier err = %d\n", ret);
550*4882a593Smuzhiyun 		goto out_io;
551*4882a593Smuzhiyun 	}
552*4882a593Smuzhiyun 
553*4882a593Smuzhiyun 	ret = misc_register(&wb_smsc_wdt_miscdev);
554*4882a593Smuzhiyun 	if (ret) {
555*4882a593Smuzhiyun 		pr_err("Unable to register miscdev on minor %d\n",
556*4882a593Smuzhiyun 		       WATCHDOG_MINOR);
557*4882a593Smuzhiyun 		goto out_rbt;
558*4882a593Smuzhiyun 	}
559*4882a593Smuzhiyun 
560*4882a593Smuzhiyun 	/* output info */
561*4882a593Smuzhiyun 	pr_info("Timeout set to %d %s\n",
562*4882a593Smuzhiyun 		timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)");
563*4882a593Smuzhiyun 	pr_info("Watchdog initialized and sleeping (nowayout=%d)...\n",
564*4882a593Smuzhiyun 		nowayout);
565*4882a593Smuzhiyun out_clean:
566*4882a593Smuzhiyun 	return ret;
567*4882a593Smuzhiyun 
568*4882a593Smuzhiyun out_rbt:
569*4882a593Smuzhiyun 	unregister_reboot_notifier(&wb_smsc_wdt_notifier);
570*4882a593Smuzhiyun 
571*4882a593Smuzhiyun out_io:
572*4882a593Smuzhiyun 	release_region(IOPORT, IOPORT_SIZE);
573*4882a593Smuzhiyun 
574*4882a593Smuzhiyun out_pnp:
575*4882a593Smuzhiyun 	goto out_clean;
576*4882a593Smuzhiyun }
577*4882a593Smuzhiyun 
578*4882a593Smuzhiyun /* module's "destructor" */
579*4882a593Smuzhiyun 
wb_smsc_wdt_exit(void)580*4882a593Smuzhiyun static void __exit wb_smsc_wdt_exit(void)
581*4882a593Smuzhiyun {
582*4882a593Smuzhiyun 	/* Stop the timer before we leave */
583*4882a593Smuzhiyun 	if (!nowayout) {
584*4882a593Smuzhiyun 		wb_smsc_wdt_shutdown();
585*4882a593Smuzhiyun 		pr_info("Watchdog disabled\n");
586*4882a593Smuzhiyun 	}
587*4882a593Smuzhiyun 
588*4882a593Smuzhiyun 	misc_deregister(&wb_smsc_wdt_miscdev);
589*4882a593Smuzhiyun 	unregister_reboot_notifier(&wb_smsc_wdt_notifier);
590*4882a593Smuzhiyun 	release_region(IOPORT, IOPORT_SIZE);
591*4882a593Smuzhiyun 
592*4882a593Smuzhiyun 	pr_info("SMsC 37B787 watchdog component driver removed\n");
593*4882a593Smuzhiyun }
594*4882a593Smuzhiyun 
595*4882a593Smuzhiyun module_init(wb_smsc_wdt_init);
596*4882a593Smuzhiyun module_exit(wb_smsc_wdt_exit);
597*4882a593Smuzhiyun 
598*4882a593Smuzhiyun MODULE_AUTHOR("Sven Anders <anders@anduras.de>");
599*4882a593Smuzhiyun MODULE_DESCRIPTION("Driver for SMsC 37B787 watchdog component (Version "
600*4882a593Smuzhiyun 								VERSION ")");
601*4882a593Smuzhiyun MODULE_LICENSE("GPL");
602*4882a593Smuzhiyun 
603*4882a593Smuzhiyun #ifdef SMSC_SUPPORT_MINUTES
604*4882a593Smuzhiyun module_param(unit, int, 0);
605*4882a593Smuzhiyun MODULE_PARM_DESC(unit,
606*4882a593Smuzhiyun 		"set unit to use, 0=seconds or 1=minutes, default is 0");
607*4882a593Smuzhiyun #endif
608*4882a593Smuzhiyun 
609*4882a593Smuzhiyun module_param(timeout, int, 0);
610*4882a593Smuzhiyun MODULE_PARM_DESC(timeout, "range is 1-255 units, default is 60");
611*4882a593Smuzhiyun 
612*4882a593Smuzhiyun module_param(nowayout, bool, 0);
613*4882a593Smuzhiyun MODULE_PARM_DESC(nowayout,
614*4882a593Smuzhiyun 		"Watchdog cannot be stopped once started (default="
615*4882a593Smuzhiyun 				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
616