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