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