brintos

brintos / linux-shallow public Read only

0
0
Text · 9.7 KiB · b776e67 Raw
450 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 *	IT8712F "Smart Guardian" Watchdog support4 *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.c10 *	drivers/hwmon/it87.c11 *	IT8712F EC-LPC I/O Preliminary Specification 0.8.212 *	IT8712F EC-LPC I/O Preliminary Specification 0.9.313 *14 *	The author(s) of this software shall not be held liable for damages15 *	of any nature resulting due to the use of this software. This16 *	software is provided AS-IS with no warranties.17 */18 19#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt20 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 36MODULE_AUTHOR("Jorge Boncompte - DTI2 <jorge@dti2.net>");37MODULE_DESCRIPTION("IT8712F Watchdog Driver");38MODULE_LICENSE("GPL");39 40static int max_units = 255;41static int margin = 60;		/* in seconds */42module_param(margin, int, 0);43MODULE_PARM_DESC(margin, "Watchdog margin in seconds");44 45static bool nowayout = WATCHDOG_NOWAYOUT;46module_param(nowayout, bool, 0);47MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");48 49static unsigned long wdt_open;50static unsigned expect_close;51static unsigned char revision;52 53/* Dog Food address - We use the game port address */54static 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	0x871266 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 84static int wdt_control_reg = WDT_RESET_GAME;85module_param(wdt_control_reg, int, 0);86MODULE_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 92static int superio_inb(int reg)93{94	outb(reg, REG);95	return inb(VAL);96}97 98static void superio_outb(int val, int reg)99{100	outb(reg, REG);101	outb(val, VAL);102}103 104static 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 114static inline void superio_select(int ldn)115{116	outb(LDN, REG);117	outb(ldn, VAL);118}119 120static 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 135static inline void superio_exit(void)136{137	outb(0x02, REG);138	outb(0x02, VAL);139	release_region(REG, 2);140}141 142static inline void it8712f_wdt_ping(void)143{144	if (wdt_control_reg & WDT_RESET_GAME)145		inb(address);146}147 148static 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 margin154	 * 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 170static int it8712f_wdt_get_status(void)171{172	if (superio_inb(WDT_CONTROL) & 0x01)173		return WDIOF_CARDRESET;174	else175		return 0;176}177 178static 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 198static 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 217static 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 227static struct notifier_block it8712f_wdt_notifier = {228	.notifier_call = it8712f_wdt_notify,229};230 231static 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 253static 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 315static 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 328static 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 342static 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 351static struct miscdevice it8712f_wdt_miscdev = {352	.minor = WATCHDOG_MINOR,353	.name = "watchdog",354	.fops = &it8712f_wdt_fops,355};356 357static 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 395exit:396	superio_exit();397	return err;398}399 400static 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 434reboot_out:435	unregister_reboot_notifier(&it8712f_wdt_notifier);436out:437	release_region(address, 1);438	return err;439}440 441static 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 448module_init(it8712f_wdt_init);449module_exit(it8712f_wdt_exit);450