0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0020
0021 #include <linux/module.h>
0022 #include <linux/moduleparam.h>
0023 #include <linux/init.h>
0024 #include <linux/miscdevice.h>
0025 #include <linux/watchdog.h>
0026 #include <linux/notifier.h>
0027 #include <linux/reboot.h>
0028 #include <linux/fs.h>
0029 #include <linux/spinlock.h>
0030 #include <linux/uaccess.h>
0031 #include <linux/io.h>
0032 #include <linux/ioport.h>
0033
0034 #define NAME "it8712f_wdt"
0035
0036 MODULE_AUTHOR("Jorge Boncompte - DTI2 <jorge@dti2.net>");
0037 MODULE_DESCRIPTION("IT8712F Watchdog Driver");
0038 MODULE_LICENSE("GPL");
0039
0040 static int max_units = 255;
0041 static int margin = 60;
0042 module_param(margin, int, 0);
0043 MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
0044
0045 static bool nowayout = WATCHDOG_NOWAYOUT;
0046 module_param(nowayout, bool, 0);
0047 MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
0048
0049 static unsigned long wdt_open;
0050 static unsigned expect_close;
0051 static unsigned char revision;
0052
0053
0054 static unsigned short address;
0055
0056 #define REG 0x2e
0057 #define VAL 0x2f
0058
0059 #define LDN 0x07
0060 #define DEVID 0x20
0061 #define DEVREV 0x22
0062 #define ACT_REG 0x30
0063 #define BASE_REG 0x60
0064
0065 #define IT8712F_DEVID 0x8712
0066
0067 #define LDN_GPIO 0x07
0068 #define LDN_GAME 0x09
0069
0070 #define WDT_CONTROL 0x71
0071 #define WDT_CONFIG 0x72
0072 #define WDT_TIMEOUT 0x73
0073
0074 #define WDT_RESET_GAME 0x10
0075 #define WDT_RESET_KBD 0x20
0076 #define WDT_RESET_MOUSE 0x40
0077 #define WDT_RESET_CIR 0x80
0078
0079 #define WDT_UNIT_SEC 0x80
0080
0081 #define WDT_OUT_PWROK 0x10
0082 #define WDT_OUT_KRST 0x40
0083
0084 static int wdt_control_reg = WDT_RESET_GAME;
0085 module_param(wdt_control_reg, int, 0);
0086 MODULE_PARM_DESC(wdt_control_reg, "Value to write to watchdog control "
0087 "register. The default WDT_RESET_GAME resets the timer on "
0088 "game port reads that this driver generates. You can also "
0089 "use KBD, MOUSE or CIR if you have some external way to "
0090 "generate those interrupts.");
0091
0092 static int superio_inb(int reg)
0093 {
0094 outb(reg, REG);
0095 return inb(VAL);
0096 }
0097
0098 static void superio_outb(int val, int reg)
0099 {
0100 outb(reg, REG);
0101 outb(val, VAL);
0102 }
0103
0104 static int superio_inw(int reg)
0105 {
0106 int val;
0107 outb(reg++, REG);
0108 val = inb(VAL) << 8;
0109 outb(reg, REG);
0110 val |= inb(VAL);
0111 return val;
0112 }
0113
0114 static inline void superio_select(int ldn)
0115 {
0116 outb(LDN, REG);
0117 outb(ldn, VAL);
0118 }
0119
0120 static inline int superio_enter(void)
0121 {
0122
0123
0124
0125 if (!request_muxed_region(REG, 2, NAME))
0126 return -EBUSY;
0127
0128 outb(0x87, REG);
0129 outb(0x01, REG);
0130 outb(0x55, REG);
0131 outb(0x55, REG);
0132 return 0;
0133 }
0134
0135 static inline void superio_exit(void)
0136 {
0137 outb(0x02, REG);
0138 outb(0x02, VAL);
0139 release_region(REG, 2);
0140 }
0141
0142 static inline void it8712f_wdt_ping(void)
0143 {
0144 if (wdt_control_reg & WDT_RESET_GAME)
0145 inb(address);
0146 }
0147
0148 static void it8712f_wdt_update_margin(void)
0149 {
0150 int config = WDT_OUT_KRST | WDT_OUT_PWROK;
0151 int units = margin;
0152
0153
0154
0155
0156 if (units <= max_units) {
0157 config |= WDT_UNIT_SEC;
0158 pr_info("timer margin %d seconds\n", units);
0159 } else {
0160 units /= 60;
0161 pr_info("timer margin %d minutes\n", units);
0162 }
0163 superio_outb(config, WDT_CONFIG);
0164
0165 if (revision >= 0x08)
0166 superio_outb(units >> 8, WDT_TIMEOUT + 1);
0167 superio_outb(units, WDT_TIMEOUT);
0168 }
0169
0170 static int it8712f_wdt_get_status(void)
0171 {
0172 if (superio_inb(WDT_CONTROL) & 0x01)
0173 return WDIOF_CARDRESET;
0174 else
0175 return 0;
0176 }
0177
0178 static int it8712f_wdt_enable(void)
0179 {
0180 int ret = superio_enter();
0181 if (ret)
0182 return ret;
0183
0184 pr_debug("enabling watchdog timer\n");
0185 superio_select(LDN_GPIO);
0186
0187 superio_outb(wdt_control_reg, WDT_CONTROL);
0188
0189 it8712f_wdt_update_margin();
0190
0191 superio_exit();
0192
0193 it8712f_wdt_ping();
0194
0195 return 0;
0196 }
0197
0198 static int it8712f_wdt_disable(void)
0199 {
0200 int ret = superio_enter();
0201 if (ret)
0202 return ret;
0203
0204 pr_debug("disabling watchdog timer\n");
0205 superio_select(LDN_GPIO);
0206
0207 superio_outb(0, WDT_CONFIG);
0208 superio_outb(0, WDT_CONTROL);
0209 if (revision >= 0x08)
0210 superio_outb(0, WDT_TIMEOUT + 1);
0211 superio_outb(0, WDT_TIMEOUT);
0212
0213 superio_exit();
0214 return 0;
0215 }
0216
0217 static int it8712f_wdt_notify(struct notifier_block *this,
0218 unsigned long code, void *unused)
0219 {
0220 if (code == SYS_HALT || code == SYS_POWER_OFF)
0221 if (!nowayout)
0222 it8712f_wdt_disable();
0223
0224 return NOTIFY_DONE;
0225 }
0226
0227 static struct notifier_block it8712f_wdt_notifier = {
0228 .notifier_call = it8712f_wdt_notify,
0229 };
0230
0231 static ssize_t it8712f_wdt_write(struct file *file, const char __user *data,
0232 size_t len, loff_t *ppos)
0233 {
0234
0235 if (len) {
0236 size_t i;
0237
0238 it8712f_wdt_ping();
0239
0240 expect_close = 0;
0241 for (i = 0; i < len; ++i) {
0242 char c;
0243 if (get_user(c, data + i))
0244 return -EFAULT;
0245 if (c == 'V')
0246 expect_close = 42;
0247 }
0248 }
0249
0250 return len;
0251 }
0252
0253 static long it8712f_wdt_ioctl(struct file *file, unsigned int cmd,
0254 unsigned long arg)
0255 {
0256 void __user *argp = (void __user *)arg;
0257 int __user *p = argp;
0258 static const struct watchdog_info ident = {
0259 .identity = "IT8712F Watchdog",
0260 .firmware_version = 1,
0261 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
0262 WDIOF_MAGICCLOSE,
0263 };
0264 int value;
0265 int ret;
0266
0267 switch (cmd) {
0268 case WDIOC_GETSUPPORT:
0269 if (copy_to_user(argp, &ident, sizeof(ident)))
0270 return -EFAULT;
0271 return 0;
0272 case WDIOC_GETSTATUS:
0273 ret = superio_enter();
0274 if (ret)
0275 return ret;
0276 superio_select(LDN_GPIO);
0277
0278 value = it8712f_wdt_get_status();
0279
0280 superio_exit();
0281
0282 return put_user(value, p);
0283 case WDIOC_GETBOOTSTATUS:
0284 return put_user(0, p);
0285 case WDIOC_KEEPALIVE:
0286 it8712f_wdt_ping();
0287 return 0;
0288 case WDIOC_SETTIMEOUT:
0289 if (get_user(value, p))
0290 return -EFAULT;
0291 if (value < 1)
0292 return -EINVAL;
0293 if (value > (max_units * 60))
0294 return -EINVAL;
0295 margin = value;
0296 ret = superio_enter();
0297 if (ret)
0298 return ret;
0299 superio_select(LDN_GPIO);
0300
0301 it8712f_wdt_update_margin();
0302
0303 superio_exit();
0304 it8712f_wdt_ping();
0305 fallthrough;
0306 case WDIOC_GETTIMEOUT:
0307 if (put_user(margin, p))
0308 return -EFAULT;
0309 return 0;
0310 default:
0311 return -ENOTTY;
0312 }
0313 }
0314
0315 static int it8712f_wdt_open(struct inode *inode, struct file *file)
0316 {
0317 int ret;
0318
0319 if (test_and_set_bit(0, &wdt_open))
0320 return -EBUSY;
0321
0322 ret = it8712f_wdt_enable();
0323 if (ret)
0324 return ret;
0325 return stream_open(inode, file);
0326 }
0327
0328 static int it8712f_wdt_release(struct inode *inode, struct file *file)
0329 {
0330 if (expect_close != 42) {
0331 pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
0332 } else if (!nowayout) {
0333 if (it8712f_wdt_disable())
0334 pr_warn("Watchdog disable failed\n");
0335 }
0336 expect_close = 0;
0337 clear_bit(0, &wdt_open);
0338
0339 return 0;
0340 }
0341
0342 static const struct file_operations it8712f_wdt_fops = {
0343 .owner = THIS_MODULE,
0344 .llseek = no_llseek,
0345 .write = it8712f_wdt_write,
0346 .unlocked_ioctl = it8712f_wdt_ioctl,
0347 .compat_ioctl = compat_ptr_ioctl,
0348 .open = it8712f_wdt_open,
0349 .release = it8712f_wdt_release,
0350 };
0351
0352 static struct miscdevice it8712f_wdt_miscdev = {
0353 .minor = WATCHDOG_MINOR,
0354 .name = "watchdog",
0355 .fops = &it8712f_wdt_fops,
0356 };
0357
0358 static int __init it8712f_wdt_find(unsigned short *address)
0359 {
0360 int err = -ENODEV;
0361 int chip_type;
0362 int ret = superio_enter();
0363 if (ret)
0364 return ret;
0365
0366 chip_type = superio_inw(DEVID);
0367 if (chip_type != IT8712F_DEVID)
0368 goto exit;
0369
0370 superio_select(LDN_GAME);
0371 superio_outb(1, ACT_REG);
0372 if (!(superio_inb(ACT_REG) & 0x01)) {
0373 pr_err("Device not activated, skipping\n");
0374 goto exit;
0375 }
0376
0377 *address = superio_inw(BASE_REG);
0378 if (*address == 0) {
0379 pr_err("Base address not set, skipping\n");
0380 goto exit;
0381 }
0382
0383 err = 0;
0384 revision = superio_inb(DEVREV) & 0x0f;
0385
0386
0387 if (revision >= 0x08)
0388 max_units = 65535;
0389
0390 if (margin > (max_units * 60))
0391 margin = (max_units * 60);
0392
0393 pr_info("Found IT%04xF chip revision %d - using DogFood address 0x%x\n",
0394 chip_type, revision, *address);
0395
0396 exit:
0397 superio_exit();
0398 return err;
0399 }
0400
0401 static int __init it8712f_wdt_init(void)
0402 {
0403 int err = 0;
0404
0405 if (it8712f_wdt_find(&address))
0406 return -ENODEV;
0407
0408 if (!request_region(address, 1, "IT8712F Watchdog")) {
0409 pr_warn("watchdog I/O region busy\n");
0410 return -EBUSY;
0411 }
0412
0413 err = it8712f_wdt_disable();
0414 if (err) {
0415 pr_err("unable to disable watchdog timer\n");
0416 goto out;
0417 }
0418
0419 err = register_reboot_notifier(&it8712f_wdt_notifier);
0420 if (err) {
0421 pr_err("unable to register reboot notifier\n");
0422 goto out;
0423 }
0424
0425 err = misc_register(&it8712f_wdt_miscdev);
0426 if (err) {
0427 pr_err("cannot register miscdev on minor=%d (err=%d)\n",
0428 WATCHDOG_MINOR, err);
0429 goto reboot_out;
0430 }
0431
0432 return 0;
0433
0434
0435 reboot_out:
0436 unregister_reboot_notifier(&it8712f_wdt_notifier);
0437 out:
0438 release_region(address, 1);
0439 return err;
0440 }
0441
0442 static void __exit it8712f_wdt_exit(void)
0443 {
0444 misc_deregister(&it8712f_wdt_miscdev);
0445 unregister_reboot_notifier(&it8712f_wdt_notifier);
0446 release_region(address, 1);
0447 }
0448
0449 module_init(it8712f_wdt_init);
0450 module_exit(it8712f_wdt_exit);