1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * IT8712F "Smart Guardian" Watchdog support
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Copyright (c) 2006-2007 Jorge Boncompte - DTI2 <jorge@dti2.net>
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * Based on info and code taken from:
8*4882a593Smuzhiyun *
9*4882a593Smuzhiyun * drivers/char/watchdog/scx200_wdt.c
10*4882a593Smuzhiyun * drivers/hwmon/it87.c
11*4882a593Smuzhiyun * IT8712F EC-LPC I/O Preliminary Specification 0.8.2
12*4882a593Smuzhiyun * IT8712F EC-LPC I/O Preliminary Specification 0.9.3
13*4882a593Smuzhiyun *
14*4882a593Smuzhiyun * The author(s) of this software shall not be held liable for damages
15*4882a593Smuzhiyun * of any nature resulting due to the use of this software. This
16*4882a593Smuzhiyun * software is provided AS-IS with no warranties.
17*4882a593Smuzhiyun */
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
20*4882a593Smuzhiyun
21*4882a593Smuzhiyun #include <linux/module.h>
22*4882a593Smuzhiyun #include <linux/moduleparam.h>
23*4882a593Smuzhiyun #include <linux/init.h>
24*4882a593Smuzhiyun #include <linux/miscdevice.h>
25*4882a593Smuzhiyun #include <linux/watchdog.h>
26*4882a593Smuzhiyun #include <linux/notifier.h>
27*4882a593Smuzhiyun #include <linux/reboot.h>
28*4882a593Smuzhiyun #include <linux/fs.h>
29*4882a593Smuzhiyun #include <linux/spinlock.h>
30*4882a593Smuzhiyun #include <linux/uaccess.h>
31*4882a593Smuzhiyun #include <linux/io.h>
32*4882a593Smuzhiyun #include <linux/ioport.h>
33*4882a593Smuzhiyun
34*4882a593Smuzhiyun #define DEBUG
35*4882a593Smuzhiyun #define NAME "it8712f_wdt"
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun MODULE_AUTHOR("Jorge Boncompte - DTI2 <jorge@dti2.net>");
38*4882a593Smuzhiyun MODULE_DESCRIPTION("IT8712F Watchdog Driver");
39*4882a593Smuzhiyun MODULE_LICENSE("GPL");
40*4882a593Smuzhiyun
41*4882a593Smuzhiyun static int max_units = 255;
42*4882a593Smuzhiyun static int margin = 60; /* in seconds */
43*4882a593Smuzhiyun module_param(margin, int, 0);
44*4882a593Smuzhiyun MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun static bool nowayout = WATCHDOG_NOWAYOUT;
47*4882a593Smuzhiyun module_param(nowayout, bool, 0);
48*4882a593Smuzhiyun MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun static unsigned long wdt_open;
51*4882a593Smuzhiyun static unsigned expect_close;
52*4882a593Smuzhiyun static unsigned char revision;
53*4882a593Smuzhiyun
54*4882a593Smuzhiyun /* Dog Food address - We use the game port address */
55*4882a593Smuzhiyun static unsigned short address;
56*4882a593Smuzhiyun
57*4882a593Smuzhiyun #define REG 0x2e /* The register to read/write */
58*4882a593Smuzhiyun #define VAL 0x2f /* The value to read/write */
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun #define LDN 0x07 /* Register: Logical device select */
61*4882a593Smuzhiyun #define DEVID 0x20 /* Register: Device ID */
62*4882a593Smuzhiyun #define DEVREV 0x22 /* Register: Device Revision */
63*4882a593Smuzhiyun #define ACT_REG 0x30 /* LDN Register: Activation */
64*4882a593Smuzhiyun #define BASE_REG 0x60 /* LDN Register: Base address */
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun #define IT8712F_DEVID 0x8712
67*4882a593Smuzhiyun
68*4882a593Smuzhiyun #define LDN_GPIO 0x07 /* GPIO and Watch Dog Timer */
69*4882a593Smuzhiyun #define LDN_GAME 0x09 /* Game Port */
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun #define WDT_CONTROL 0x71 /* WDT Register: Control */
72*4882a593Smuzhiyun #define WDT_CONFIG 0x72 /* WDT Register: Configuration */
73*4882a593Smuzhiyun #define WDT_TIMEOUT 0x73 /* WDT Register: Timeout Value */
74*4882a593Smuzhiyun
75*4882a593Smuzhiyun #define WDT_RESET_GAME 0x10 /* Reset timer on read or write to game port */
76*4882a593Smuzhiyun #define WDT_RESET_KBD 0x20 /* Reset timer on keyboard interrupt */
77*4882a593Smuzhiyun #define WDT_RESET_MOUSE 0x40 /* Reset timer on mouse interrupt */
78*4882a593Smuzhiyun #define WDT_RESET_CIR 0x80 /* Reset timer on consumer IR interrupt */
79*4882a593Smuzhiyun
80*4882a593Smuzhiyun #define WDT_UNIT_SEC 0x80 /* If 0 in MINUTES */
81*4882a593Smuzhiyun
82*4882a593Smuzhiyun #define WDT_OUT_PWROK 0x10 /* Pulse PWROK on timeout */
83*4882a593Smuzhiyun #define WDT_OUT_KRST 0x40 /* Pulse reset on timeout */
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun static int wdt_control_reg = WDT_RESET_GAME;
86*4882a593Smuzhiyun module_param(wdt_control_reg, int, 0);
87*4882a593Smuzhiyun MODULE_PARM_DESC(wdt_control_reg, "Value to write to watchdog control "
88*4882a593Smuzhiyun "register. The default WDT_RESET_GAME resets the timer on "
89*4882a593Smuzhiyun "game port reads that this driver generates. You can also "
90*4882a593Smuzhiyun "use KBD, MOUSE or CIR if you have some external way to "
91*4882a593Smuzhiyun "generate those interrupts.");
92*4882a593Smuzhiyun
superio_inb(int reg)93*4882a593Smuzhiyun static int superio_inb(int reg)
94*4882a593Smuzhiyun {
95*4882a593Smuzhiyun outb(reg, REG);
96*4882a593Smuzhiyun return inb(VAL);
97*4882a593Smuzhiyun }
98*4882a593Smuzhiyun
superio_outb(int val,int reg)99*4882a593Smuzhiyun static void superio_outb(int val, int reg)
100*4882a593Smuzhiyun {
101*4882a593Smuzhiyun outb(reg, REG);
102*4882a593Smuzhiyun outb(val, VAL);
103*4882a593Smuzhiyun }
104*4882a593Smuzhiyun
superio_inw(int reg)105*4882a593Smuzhiyun static int superio_inw(int reg)
106*4882a593Smuzhiyun {
107*4882a593Smuzhiyun int val;
108*4882a593Smuzhiyun outb(reg++, REG);
109*4882a593Smuzhiyun val = inb(VAL) << 8;
110*4882a593Smuzhiyun outb(reg, REG);
111*4882a593Smuzhiyun val |= inb(VAL);
112*4882a593Smuzhiyun return val;
113*4882a593Smuzhiyun }
114*4882a593Smuzhiyun
superio_select(int ldn)115*4882a593Smuzhiyun static inline void superio_select(int ldn)
116*4882a593Smuzhiyun {
117*4882a593Smuzhiyun outb(LDN, REG);
118*4882a593Smuzhiyun outb(ldn, VAL);
119*4882a593Smuzhiyun }
120*4882a593Smuzhiyun
superio_enter(void)121*4882a593Smuzhiyun static inline int superio_enter(void)
122*4882a593Smuzhiyun {
123*4882a593Smuzhiyun /*
124*4882a593Smuzhiyun * Try to reserve REG and REG + 1 for exclusive access.
125*4882a593Smuzhiyun */
126*4882a593Smuzhiyun if (!request_muxed_region(REG, 2, NAME))
127*4882a593Smuzhiyun return -EBUSY;
128*4882a593Smuzhiyun
129*4882a593Smuzhiyun outb(0x87, REG);
130*4882a593Smuzhiyun outb(0x01, REG);
131*4882a593Smuzhiyun outb(0x55, REG);
132*4882a593Smuzhiyun outb(0x55, REG);
133*4882a593Smuzhiyun return 0;
134*4882a593Smuzhiyun }
135*4882a593Smuzhiyun
superio_exit(void)136*4882a593Smuzhiyun static inline void superio_exit(void)
137*4882a593Smuzhiyun {
138*4882a593Smuzhiyun outb(0x02, REG);
139*4882a593Smuzhiyun outb(0x02, VAL);
140*4882a593Smuzhiyun release_region(REG, 2);
141*4882a593Smuzhiyun }
142*4882a593Smuzhiyun
it8712f_wdt_ping(void)143*4882a593Smuzhiyun static inline void it8712f_wdt_ping(void)
144*4882a593Smuzhiyun {
145*4882a593Smuzhiyun if (wdt_control_reg & WDT_RESET_GAME)
146*4882a593Smuzhiyun inb(address);
147*4882a593Smuzhiyun }
148*4882a593Smuzhiyun
it8712f_wdt_update_margin(void)149*4882a593Smuzhiyun static void it8712f_wdt_update_margin(void)
150*4882a593Smuzhiyun {
151*4882a593Smuzhiyun int config = WDT_OUT_KRST | WDT_OUT_PWROK;
152*4882a593Smuzhiyun int units = margin;
153*4882a593Smuzhiyun
154*4882a593Smuzhiyun /* Switch to minutes precision if the configured margin
155*4882a593Smuzhiyun * value does not fit within the register width.
156*4882a593Smuzhiyun */
157*4882a593Smuzhiyun if (units <= max_units) {
158*4882a593Smuzhiyun config |= WDT_UNIT_SEC; /* else UNIT is MINUTES */
159*4882a593Smuzhiyun pr_info("timer margin %d seconds\n", units);
160*4882a593Smuzhiyun } else {
161*4882a593Smuzhiyun units /= 60;
162*4882a593Smuzhiyun pr_info("timer margin %d minutes\n", units);
163*4882a593Smuzhiyun }
164*4882a593Smuzhiyun superio_outb(config, WDT_CONFIG);
165*4882a593Smuzhiyun
166*4882a593Smuzhiyun if (revision >= 0x08)
167*4882a593Smuzhiyun superio_outb(units >> 8, WDT_TIMEOUT + 1);
168*4882a593Smuzhiyun superio_outb(units, WDT_TIMEOUT);
169*4882a593Smuzhiyun }
170*4882a593Smuzhiyun
it8712f_wdt_get_status(void)171*4882a593Smuzhiyun static int it8712f_wdt_get_status(void)
172*4882a593Smuzhiyun {
173*4882a593Smuzhiyun if (superio_inb(WDT_CONTROL) & 0x01)
174*4882a593Smuzhiyun return WDIOF_CARDRESET;
175*4882a593Smuzhiyun else
176*4882a593Smuzhiyun return 0;
177*4882a593Smuzhiyun }
178*4882a593Smuzhiyun
it8712f_wdt_enable(void)179*4882a593Smuzhiyun static int it8712f_wdt_enable(void)
180*4882a593Smuzhiyun {
181*4882a593Smuzhiyun int ret = superio_enter();
182*4882a593Smuzhiyun if (ret)
183*4882a593Smuzhiyun return ret;
184*4882a593Smuzhiyun
185*4882a593Smuzhiyun pr_debug("enabling watchdog timer\n");
186*4882a593Smuzhiyun superio_select(LDN_GPIO);
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun superio_outb(wdt_control_reg, WDT_CONTROL);
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun it8712f_wdt_update_margin();
191*4882a593Smuzhiyun
192*4882a593Smuzhiyun superio_exit();
193*4882a593Smuzhiyun
194*4882a593Smuzhiyun it8712f_wdt_ping();
195*4882a593Smuzhiyun
196*4882a593Smuzhiyun return 0;
197*4882a593Smuzhiyun }
198*4882a593Smuzhiyun
it8712f_wdt_disable(void)199*4882a593Smuzhiyun static int it8712f_wdt_disable(void)
200*4882a593Smuzhiyun {
201*4882a593Smuzhiyun int ret = superio_enter();
202*4882a593Smuzhiyun if (ret)
203*4882a593Smuzhiyun return ret;
204*4882a593Smuzhiyun
205*4882a593Smuzhiyun pr_debug("disabling watchdog timer\n");
206*4882a593Smuzhiyun superio_select(LDN_GPIO);
207*4882a593Smuzhiyun
208*4882a593Smuzhiyun superio_outb(0, WDT_CONFIG);
209*4882a593Smuzhiyun superio_outb(0, WDT_CONTROL);
210*4882a593Smuzhiyun if (revision >= 0x08)
211*4882a593Smuzhiyun superio_outb(0, WDT_TIMEOUT + 1);
212*4882a593Smuzhiyun superio_outb(0, WDT_TIMEOUT);
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun superio_exit();
215*4882a593Smuzhiyun return 0;
216*4882a593Smuzhiyun }
217*4882a593Smuzhiyun
it8712f_wdt_notify(struct notifier_block * this,unsigned long code,void * unused)218*4882a593Smuzhiyun static int it8712f_wdt_notify(struct notifier_block *this,
219*4882a593Smuzhiyun unsigned long code, void *unused)
220*4882a593Smuzhiyun {
221*4882a593Smuzhiyun if (code == SYS_HALT || code == SYS_POWER_OFF)
222*4882a593Smuzhiyun if (!nowayout)
223*4882a593Smuzhiyun it8712f_wdt_disable();
224*4882a593Smuzhiyun
225*4882a593Smuzhiyun return NOTIFY_DONE;
226*4882a593Smuzhiyun }
227*4882a593Smuzhiyun
228*4882a593Smuzhiyun static struct notifier_block it8712f_wdt_notifier = {
229*4882a593Smuzhiyun .notifier_call = it8712f_wdt_notify,
230*4882a593Smuzhiyun };
231*4882a593Smuzhiyun
it8712f_wdt_write(struct file * file,const char __user * data,size_t len,loff_t * ppos)232*4882a593Smuzhiyun static ssize_t it8712f_wdt_write(struct file *file, const char __user *data,
233*4882a593Smuzhiyun size_t len, loff_t *ppos)
234*4882a593Smuzhiyun {
235*4882a593Smuzhiyun /* check for a magic close character */
236*4882a593Smuzhiyun if (len) {
237*4882a593Smuzhiyun size_t i;
238*4882a593Smuzhiyun
239*4882a593Smuzhiyun it8712f_wdt_ping();
240*4882a593Smuzhiyun
241*4882a593Smuzhiyun expect_close = 0;
242*4882a593Smuzhiyun for (i = 0; i < len; ++i) {
243*4882a593Smuzhiyun char c;
244*4882a593Smuzhiyun if (get_user(c, data + i))
245*4882a593Smuzhiyun return -EFAULT;
246*4882a593Smuzhiyun if (c == 'V')
247*4882a593Smuzhiyun expect_close = 42;
248*4882a593Smuzhiyun }
249*4882a593Smuzhiyun }
250*4882a593Smuzhiyun
251*4882a593Smuzhiyun return len;
252*4882a593Smuzhiyun }
253*4882a593Smuzhiyun
it8712f_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)254*4882a593Smuzhiyun static long it8712f_wdt_ioctl(struct file *file, unsigned int cmd,
255*4882a593Smuzhiyun unsigned long arg)
256*4882a593Smuzhiyun {
257*4882a593Smuzhiyun void __user *argp = (void __user *)arg;
258*4882a593Smuzhiyun int __user *p = argp;
259*4882a593Smuzhiyun static const struct watchdog_info ident = {
260*4882a593Smuzhiyun .identity = "IT8712F Watchdog",
261*4882a593Smuzhiyun .firmware_version = 1,
262*4882a593Smuzhiyun .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
263*4882a593Smuzhiyun WDIOF_MAGICCLOSE,
264*4882a593Smuzhiyun };
265*4882a593Smuzhiyun int value;
266*4882a593Smuzhiyun int ret;
267*4882a593Smuzhiyun
268*4882a593Smuzhiyun switch (cmd) {
269*4882a593Smuzhiyun case WDIOC_GETSUPPORT:
270*4882a593Smuzhiyun if (copy_to_user(argp, &ident, sizeof(ident)))
271*4882a593Smuzhiyun return -EFAULT;
272*4882a593Smuzhiyun return 0;
273*4882a593Smuzhiyun case WDIOC_GETSTATUS:
274*4882a593Smuzhiyun ret = superio_enter();
275*4882a593Smuzhiyun if (ret)
276*4882a593Smuzhiyun return ret;
277*4882a593Smuzhiyun superio_select(LDN_GPIO);
278*4882a593Smuzhiyun
279*4882a593Smuzhiyun value = it8712f_wdt_get_status();
280*4882a593Smuzhiyun
281*4882a593Smuzhiyun superio_exit();
282*4882a593Smuzhiyun
283*4882a593Smuzhiyun return put_user(value, p);
284*4882a593Smuzhiyun case WDIOC_GETBOOTSTATUS:
285*4882a593Smuzhiyun return put_user(0, p);
286*4882a593Smuzhiyun case WDIOC_KEEPALIVE:
287*4882a593Smuzhiyun it8712f_wdt_ping();
288*4882a593Smuzhiyun return 0;
289*4882a593Smuzhiyun case WDIOC_SETTIMEOUT:
290*4882a593Smuzhiyun if (get_user(value, p))
291*4882a593Smuzhiyun return -EFAULT;
292*4882a593Smuzhiyun if (value < 1)
293*4882a593Smuzhiyun return -EINVAL;
294*4882a593Smuzhiyun if (value > (max_units * 60))
295*4882a593Smuzhiyun return -EINVAL;
296*4882a593Smuzhiyun margin = value;
297*4882a593Smuzhiyun ret = superio_enter();
298*4882a593Smuzhiyun if (ret)
299*4882a593Smuzhiyun return ret;
300*4882a593Smuzhiyun superio_select(LDN_GPIO);
301*4882a593Smuzhiyun
302*4882a593Smuzhiyun it8712f_wdt_update_margin();
303*4882a593Smuzhiyun
304*4882a593Smuzhiyun superio_exit();
305*4882a593Smuzhiyun it8712f_wdt_ping();
306*4882a593Smuzhiyun fallthrough;
307*4882a593Smuzhiyun case WDIOC_GETTIMEOUT:
308*4882a593Smuzhiyun if (put_user(margin, p))
309*4882a593Smuzhiyun return -EFAULT;
310*4882a593Smuzhiyun return 0;
311*4882a593Smuzhiyun default:
312*4882a593Smuzhiyun return -ENOTTY;
313*4882a593Smuzhiyun }
314*4882a593Smuzhiyun }
315*4882a593Smuzhiyun
it8712f_wdt_open(struct inode * inode,struct file * file)316*4882a593Smuzhiyun static int it8712f_wdt_open(struct inode *inode, struct file *file)
317*4882a593Smuzhiyun {
318*4882a593Smuzhiyun int ret;
319*4882a593Smuzhiyun /* only allow one at a time */
320*4882a593Smuzhiyun if (test_and_set_bit(0, &wdt_open))
321*4882a593Smuzhiyun return -EBUSY;
322*4882a593Smuzhiyun
323*4882a593Smuzhiyun ret = it8712f_wdt_enable();
324*4882a593Smuzhiyun if (ret)
325*4882a593Smuzhiyun return ret;
326*4882a593Smuzhiyun return stream_open(inode, file);
327*4882a593Smuzhiyun }
328*4882a593Smuzhiyun
it8712f_wdt_release(struct inode * inode,struct file * file)329*4882a593Smuzhiyun static int it8712f_wdt_release(struct inode *inode, struct file *file)
330*4882a593Smuzhiyun {
331*4882a593Smuzhiyun if (expect_close != 42) {
332*4882a593Smuzhiyun pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
333*4882a593Smuzhiyun } else if (!nowayout) {
334*4882a593Smuzhiyun if (it8712f_wdt_disable())
335*4882a593Smuzhiyun pr_warn("Watchdog disable failed\n");
336*4882a593Smuzhiyun }
337*4882a593Smuzhiyun expect_close = 0;
338*4882a593Smuzhiyun clear_bit(0, &wdt_open);
339*4882a593Smuzhiyun
340*4882a593Smuzhiyun return 0;
341*4882a593Smuzhiyun }
342*4882a593Smuzhiyun
343*4882a593Smuzhiyun static const struct file_operations it8712f_wdt_fops = {
344*4882a593Smuzhiyun .owner = THIS_MODULE,
345*4882a593Smuzhiyun .llseek = no_llseek,
346*4882a593Smuzhiyun .write = it8712f_wdt_write,
347*4882a593Smuzhiyun .unlocked_ioctl = it8712f_wdt_ioctl,
348*4882a593Smuzhiyun .compat_ioctl = compat_ptr_ioctl,
349*4882a593Smuzhiyun .open = it8712f_wdt_open,
350*4882a593Smuzhiyun .release = it8712f_wdt_release,
351*4882a593Smuzhiyun };
352*4882a593Smuzhiyun
353*4882a593Smuzhiyun static struct miscdevice it8712f_wdt_miscdev = {
354*4882a593Smuzhiyun .minor = WATCHDOG_MINOR,
355*4882a593Smuzhiyun .name = "watchdog",
356*4882a593Smuzhiyun .fops = &it8712f_wdt_fops,
357*4882a593Smuzhiyun };
358*4882a593Smuzhiyun
it8712f_wdt_find(unsigned short * address)359*4882a593Smuzhiyun static int __init it8712f_wdt_find(unsigned short *address)
360*4882a593Smuzhiyun {
361*4882a593Smuzhiyun int err = -ENODEV;
362*4882a593Smuzhiyun int chip_type;
363*4882a593Smuzhiyun int ret = superio_enter();
364*4882a593Smuzhiyun if (ret)
365*4882a593Smuzhiyun return ret;
366*4882a593Smuzhiyun
367*4882a593Smuzhiyun chip_type = superio_inw(DEVID);
368*4882a593Smuzhiyun if (chip_type != IT8712F_DEVID)
369*4882a593Smuzhiyun goto exit;
370*4882a593Smuzhiyun
371*4882a593Smuzhiyun superio_select(LDN_GAME);
372*4882a593Smuzhiyun superio_outb(1, ACT_REG);
373*4882a593Smuzhiyun if (!(superio_inb(ACT_REG) & 0x01)) {
374*4882a593Smuzhiyun pr_err("Device not activated, skipping\n");
375*4882a593Smuzhiyun goto exit;
376*4882a593Smuzhiyun }
377*4882a593Smuzhiyun
378*4882a593Smuzhiyun *address = superio_inw(BASE_REG);
379*4882a593Smuzhiyun if (*address == 0) {
380*4882a593Smuzhiyun pr_err("Base address not set, skipping\n");
381*4882a593Smuzhiyun goto exit;
382*4882a593Smuzhiyun }
383*4882a593Smuzhiyun
384*4882a593Smuzhiyun err = 0;
385*4882a593Smuzhiyun revision = superio_inb(DEVREV) & 0x0f;
386*4882a593Smuzhiyun
387*4882a593Smuzhiyun /* Later revisions have 16-bit values per datasheet 0.9.1 */
388*4882a593Smuzhiyun if (revision >= 0x08)
389*4882a593Smuzhiyun max_units = 65535;
390*4882a593Smuzhiyun
391*4882a593Smuzhiyun if (margin > (max_units * 60))
392*4882a593Smuzhiyun margin = (max_units * 60);
393*4882a593Smuzhiyun
394*4882a593Smuzhiyun pr_info("Found IT%04xF chip revision %d - using DogFood address 0x%x\n",
395*4882a593Smuzhiyun chip_type, revision, *address);
396*4882a593Smuzhiyun
397*4882a593Smuzhiyun exit:
398*4882a593Smuzhiyun superio_exit();
399*4882a593Smuzhiyun return err;
400*4882a593Smuzhiyun }
401*4882a593Smuzhiyun
it8712f_wdt_init(void)402*4882a593Smuzhiyun static int __init it8712f_wdt_init(void)
403*4882a593Smuzhiyun {
404*4882a593Smuzhiyun int err = 0;
405*4882a593Smuzhiyun
406*4882a593Smuzhiyun if (it8712f_wdt_find(&address))
407*4882a593Smuzhiyun return -ENODEV;
408*4882a593Smuzhiyun
409*4882a593Smuzhiyun if (!request_region(address, 1, "IT8712F Watchdog")) {
410*4882a593Smuzhiyun pr_warn("watchdog I/O region busy\n");
411*4882a593Smuzhiyun return -EBUSY;
412*4882a593Smuzhiyun }
413*4882a593Smuzhiyun
414*4882a593Smuzhiyun err = it8712f_wdt_disable();
415*4882a593Smuzhiyun if (err) {
416*4882a593Smuzhiyun pr_err("unable to disable watchdog timer\n");
417*4882a593Smuzhiyun goto out;
418*4882a593Smuzhiyun }
419*4882a593Smuzhiyun
420*4882a593Smuzhiyun err = register_reboot_notifier(&it8712f_wdt_notifier);
421*4882a593Smuzhiyun if (err) {
422*4882a593Smuzhiyun pr_err("unable to register reboot notifier\n");
423*4882a593Smuzhiyun goto out;
424*4882a593Smuzhiyun }
425*4882a593Smuzhiyun
426*4882a593Smuzhiyun err = misc_register(&it8712f_wdt_miscdev);
427*4882a593Smuzhiyun if (err) {
428*4882a593Smuzhiyun pr_err("cannot register miscdev on minor=%d (err=%d)\n",
429*4882a593Smuzhiyun WATCHDOG_MINOR, err);
430*4882a593Smuzhiyun goto reboot_out;
431*4882a593Smuzhiyun }
432*4882a593Smuzhiyun
433*4882a593Smuzhiyun return 0;
434*4882a593Smuzhiyun
435*4882a593Smuzhiyun
436*4882a593Smuzhiyun reboot_out:
437*4882a593Smuzhiyun unregister_reboot_notifier(&it8712f_wdt_notifier);
438*4882a593Smuzhiyun out:
439*4882a593Smuzhiyun release_region(address, 1);
440*4882a593Smuzhiyun return err;
441*4882a593Smuzhiyun }
442*4882a593Smuzhiyun
it8712f_wdt_exit(void)443*4882a593Smuzhiyun static void __exit it8712f_wdt_exit(void)
444*4882a593Smuzhiyun {
445*4882a593Smuzhiyun misc_deregister(&it8712f_wdt_miscdev);
446*4882a593Smuzhiyun unregister_reboot_notifier(&it8712f_wdt_notifier);
447*4882a593Smuzhiyun release_region(address, 1);
448*4882a593Smuzhiyun }
449*4882a593Smuzhiyun
450*4882a593Smuzhiyun module_init(it8712f_wdt_init);
451*4882a593Smuzhiyun module_exit(it8712f_wdt_exit);
452