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