^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) * nv_tco 0.01: TCO timer driver for NV chipsets
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) * (c) Copyright 2005 Google Inc., All Rights Reserved.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) * Based off i8xx_tco.c:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) * Reserved.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10) * https://www.kernelconcepts.de
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) * TCO timer driver for NV chipsets
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13) * based on softdog.c by Alan Cox <alan@redhat.com>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17) * Includes, defines, variables, module parameters, ...
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20) #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) #include <linux/module.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23) #include <linux/moduleparam.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24) #include <linux/types.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) #include <linux/miscdevice.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26) #include <linux/watchdog.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) #include <linux/init.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/pci.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30) #include <linux/ioport.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31) #include <linux/jiffies.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32) #include <linux/platform_device.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33) #include <linux/uaccess.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) #include <linux/io.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36) #include "nv_tco.h"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38) /* Module and version information */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39) #define TCO_VERSION "0.01"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) #define TCO_MODULE_NAME "NV_TCO"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41) #define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43) /* internal variables */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44) static unsigned int tcobase;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45) static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) static unsigned long timer_alive;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47) static char tco_expect_close;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48) static struct pci_dev *tco_pci;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) /* the watchdog platform device */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) static struct platform_device *nv_tco_platform_device;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53) /* module parameters */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54) #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (2<heartbeat<39) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) module_param(heartbeat, int, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57) MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39, "
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58) "default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60) static bool nowayout = WATCHDOG_NOWAYOUT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) module_param(nowayout, bool, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66) * Some TCO specific functions
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68) static inline unsigned char seconds_to_ticks(int seconds)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70) /* the internal timer is stored as ticks which decrement
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) * every 0.6 seconds */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72) return (seconds * 10) / 6;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75) static void tco_timer_start(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77) u32 val;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78) unsigned long flags;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) spin_lock_irqsave(&tco_lock, flags);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81) val = inl(TCO_CNT(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82) val &= ~TCO_CNT_TCOHALT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83) outl(val, TCO_CNT(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) spin_unlock_irqrestore(&tco_lock, flags);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) static void tco_timer_stop(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89) u32 val;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) unsigned long flags;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) spin_lock_irqsave(&tco_lock, flags);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93) val = inl(TCO_CNT(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94) val |= TCO_CNT_TCOHALT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95) outl(val, TCO_CNT(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96) spin_unlock_irqrestore(&tco_lock, flags);
^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 tco_timer_keepalive(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) unsigned long flags;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103) spin_lock_irqsave(&tco_lock, flags);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) outb(0x01, TCO_RLD(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105) spin_unlock_irqrestore(&tco_lock, flags);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108) static int tco_timer_set_heartbeat(int t)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) int ret = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) unsigned char tmrval;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) unsigned long flags;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) u8 val;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) * note seconds_to_ticks(t) > t, so if t > 0x3f, so is
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) * tmrval=seconds_to_ticks(t). Check that the count in seconds isn't
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) * out of range on it's own (to avoid overflow in tmrval).
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) if (t < 0 || t > 0x3f)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121) return -EINVAL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) tmrval = seconds_to_ticks(t);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124) /* "Values of 0h-3h are ignored and should not be attempted" */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125) if (tmrval > 0x3f || tmrval < 0x04)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126) return -EINVAL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) /* Write new heartbeat to watchdog */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129) spin_lock_irqsave(&tco_lock, flags);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) val = inb(TCO_TMR(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131) val &= 0xc0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132) val |= tmrval;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) outb(val, TCO_TMR(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) val = inb(TCO_TMR(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) if ((val & 0x3f) != tmrval)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137) ret = -EINVAL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138) spin_unlock_irqrestore(&tco_lock, flags);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140) if (ret)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) return ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143) heartbeat = t;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148) * /dev/watchdog handling
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151) static int nv_tco_open(struct inode *inode, struct file *file)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153) /* /dev/watchdog can only be opened once */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154) if (test_and_set_bit(0, &timer_alive))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 155) return -EBUSY;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 156)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 157) /* Reload and activate timer */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158) tco_timer_keepalive();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159) tco_timer_start();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160) return stream_open(inode, file);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 162)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 163) static int nv_tco_release(struct inode *inode, struct file *file)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 164) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 165) /* Shut off the timer */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 166) if (tco_expect_close == 42) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 167) tco_timer_stop();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 168) } else {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 169) pr_crit("Unexpected close, not stopping watchdog!\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 170) tco_timer_keepalive();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 171) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 172) clear_bit(0, &timer_alive);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 173) tco_expect_close = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 174) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 175) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 176)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 177) static ssize_t nv_tco_write(struct file *file, const char __user *data,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 178) size_t len, loff_t *ppos)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 179) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 180) /* See if we got the magic character 'V' and reload the timer */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 181) if (len) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 182) if (!nowayout) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 183) size_t i;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 184)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 185) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 186) * note: just in case someone wrote the magic character
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 187) * five months ago...
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 188) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 189) tco_expect_close = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 190)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 191) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 192) * scan to see whether or not we got the magic
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 193) * character
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 194) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 195) for (i = 0; i != len; i++) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 196) char c;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 197) if (get_user(c, data + i))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 198) return -EFAULT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 199) if (c == 'V')
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 200) tco_expect_close = 42;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 201) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 202) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 203)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 204) /* someone wrote to us, we should reload the timer */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 205) tco_timer_keepalive();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 206) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 207) return len;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 208) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 209)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 210) static long nv_tco_ioctl(struct file *file, unsigned int cmd,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 211) unsigned long arg)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 212) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 213) int new_options, retval = -EINVAL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 214) int new_heartbeat;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 215) void __user *argp = (void __user *)arg;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 216) int __user *p = argp;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 217) static const struct watchdog_info ident = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 218) .options = WDIOF_SETTIMEOUT |
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 219) WDIOF_KEEPALIVEPING |
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 220) WDIOF_MAGICCLOSE,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 221) .firmware_version = 0,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 222) .identity = TCO_MODULE_NAME,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 223) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 224)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 225) switch (cmd) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 226) case WDIOC_GETSUPPORT:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 227) return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 228) case WDIOC_GETSTATUS:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 229) case WDIOC_GETBOOTSTATUS:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 230) return put_user(0, p);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 231) case WDIOC_SETOPTIONS:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 232) if (get_user(new_options, p))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 233) return -EFAULT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 234) if (new_options & WDIOS_DISABLECARD) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 235) tco_timer_stop();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 236) retval = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 237) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 238) if (new_options & WDIOS_ENABLECARD) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 239) tco_timer_keepalive();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 240) tco_timer_start();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 241) retval = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 242) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 243) return retval;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 244) case WDIOC_KEEPALIVE:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 245) tco_timer_keepalive();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 246) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 247) case WDIOC_SETTIMEOUT:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 248) if (get_user(new_heartbeat, p))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 249) return -EFAULT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 250) if (tco_timer_set_heartbeat(new_heartbeat))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 251) return -EINVAL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 252) tco_timer_keepalive();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 253) fallthrough;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 254) case WDIOC_GETTIMEOUT:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 255) return put_user(heartbeat, p);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 256) default:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 257) return -ENOTTY;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 258) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 259) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 260)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 261) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 262) * Kernel Interfaces
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 263) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 264)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 265) static const struct file_operations nv_tco_fops = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 266) .owner = THIS_MODULE,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 267) .llseek = no_llseek,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 268) .write = nv_tco_write,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 269) .unlocked_ioctl = nv_tco_ioctl,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 270) .compat_ioctl = compat_ptr_ioctl,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 271) .open = nv_tco_open,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 272) .release = nv_tco_release,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 273) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 274)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 275) static struct miscdevice nv_tco_miscdev = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 276) .minor = WATCHDOG_MINOR,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 277) .name = "watchdog",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 278) .fops = &nv_tco_fops,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 279) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 280)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 281) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 282) * Data for PCI driver interface
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 283) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 284) * This data only exists for exporting the supported
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 285) * PCI ids via MODULE_DEVICE_TABLE. We do not actually
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 286) * register a pci_driver, because someone else might one day
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 287) * want to register another driver on the same PCI id.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 288) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 289) static const struct pci_device_id tco_pci_tbl[] = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 290) { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_SMBUS,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 291) PCI_ANY_ID, PCI_ANY_ID, },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 292) { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SMBUS,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 293) PCI_ANY_ID, PCI_ANY_ID, },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 294) { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP78S_SMBUS,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 295) PCI_ANY_ID, PCI_ANY_ID, },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 296) { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP79_SMBUS,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 297) PCI_ANY_ID, PCI_ANY_ID, },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 298) { 0, }, /* End of list */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 299) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 300) MODULE_DEVICE_TABLE(pci, tco_pci_tbl);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 301)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 302) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 303) * Init & exit routines
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 304) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 305)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 306) static unsigned char nv_tco_getdevice(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 307) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 308) struct pci_dev *dev = NULL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 309) u32 val;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 310)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 311) /* Find the PCI device */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 312) for_each_pci_dev(dev) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 313) if (pci_match_id(tco_pci_tbl, dev) != NULL) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 314) tco_pci = dev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 315) break;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 316) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 317) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 318)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 319) if (!tco_pci)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 320) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 321)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 322) /* Find the base io port */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 323) pci_read_config_dword(tco_pci, 0x64, &val);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 324) val &= 0xffff;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 325) if (val == 0x0001 || val == 0x0000) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 326) /* Something is wrong here, bar isn't setup */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 327) pr_err("failed to get tcobase address\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 328) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 329) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 330) val &= 0xff00;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 331) tcobase = val + 0x40;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 332)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 333) if (!request_region(tcobase, 0x10, "NV TCO")) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 334) pr_err("I/O address 0x%04x already in use\n", tcobase);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 335) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 336) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 337)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 338) /* Set a reasonable heartbeat before we stop the timer */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 339) tco_timer_set_heartbeat(30);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 340)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 341) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 342) * Stop the TCO before we change anything so we don't race with
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 343) * a zeroed timer.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 344) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 345) tco_timer_keepalive();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 346) tco_timer_stop();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 347)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 348) /* Disable SMI caused by TCO */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 349) if (!request_region(MCP51_SMI_EN(tcobase), 4, "NV TCO")) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 350) pr_err("I/O address 0x%04x already in use\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 351) MCP51_SMI_EN(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 352) goto out;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 353) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 354) val = inl(MCP51_SMI_EN(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 355) val &= ~MCP51_SMI_EN_TCO;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 356) outl(val, MCP51_SMI_EN(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 357) val = inl(MCP51_SMI_EN(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 358) release_region(MCP51_SMI_EN(tcobase), 4);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 359) if (val & MCP51_SMI_EN_TCO) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 360) pr_err("Could not disable SMI caused by TCO\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 361) goto out;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 362) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 363)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 364) /* Check chipset's NO_REBOOT bit */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 365) pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 366) val |= MCP51_SMBUS_SETUP_B_TCO_REBOOT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 367) pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 368) pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 369) if (!(val & MCP51_SMBUS_SETUP_B_TCO_REBOOT)) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 370) pr_err("failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 371) goto out;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 372) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 373)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 374) return 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 375) out:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 376) release_region(tcobase, 0x10);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 377) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 378) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 379)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 380) static int nv_tco_init(struct platform_device *dev)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 381) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 382) int ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 383)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 384) /* Check whether or not the hardware watchdog is there */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 385) if (!nv_tco_getdevice())
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 386) return -ENODEV;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 387)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 388) /* Check to see if last reboot was due to watchdog timeout */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 389) pr_info("Watchdog reboot %sdetected\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 390) inl(TCO_STS(tcobase)) & TCO_STS_TCO2TO_STS ? "" : "not ");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 391)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 392) /* Clear out the old status */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 393) outl(TCO_STS_RESET, TCO_STS(tcobase));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 394)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 395) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 396) * Check that the heartbeat value is within it's range.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 397) * If not, reset to the default.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 398) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 399) if (tco_timer_set_heartbeat(heartbeat)) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 400) heartbeat = WATCHDOG_HEARTBEAT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 401) tco_timer_set_heartbeat(heartbeat);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 402) pr_info("heartbeat value must be 2<heartbeat<39, using %d\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 403) heartbeat);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 404) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 405)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 406) ret = misc_register(&nv_tco_miscdev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 407) if (ret != 0) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 408) pr_err("cannot register miscdev on minor=%d (err=%d)\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 409) WATCHDOG_MINOR, ret);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 410) goto unreg_region;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 411) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 412)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 413) clear_bit(0, &timer_alive);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 414)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 415) tco_timer_stop();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 416)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 417) pr_info("initialized (0x%04x). heartbeat=%d sec (nowayout=%d)\n",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 418) tcobase, heartbeat, nowayout);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 419)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 420) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 421)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 422) unreg_region:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 423) release_region(tcobase, 0x10);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 424) return ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 425) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 426)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 427) static void nv_tco_cleanup(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 428) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 429) u32 val;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 430)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 431) /* Stop the timer before we leave */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 432) if (!nowayout)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 433) tco_timer_stop();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 434)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 435) /* Set the NO_REBOOT bit to prevent later reboots, just for sure */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 436) pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 437) val &= ~MCP51_SMBUS_SETUP_B_TCO_REBOOT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 438) pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 439) pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 440) if (val & MCP51_SMBUS_SETUP_B_TCO_REBOOT) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 441) pr_crit("Couldn't unset REBOOT bit. Machine may soon reset\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 442) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 443)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 444) /* Deregister */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 445) misc_deregister(&nv_tco_miscdev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 446) release_region(tcobase, 0x10);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 447) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 448)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 449) static int nv_tco_remove(struct platform_device *dev)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 450) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 451) if (tcobase)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 452) nv_tco_cleanup();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 453)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 454) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 455) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 456)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 457) static void nv_tco_shutdown(struct platform_device *dev)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 458) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 459) u32 val;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 460)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 461) tco_timer_stop();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 462)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 463) /* Some BIOSes fail the POST (once) if the NO_REBOOT flag is not
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 464) * unset during shutdown. */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 465) pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 466) val &= ~MCP51_SMBUS_SETUP_B_TCO_REBOOT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 467) pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 468) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 469)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 470) static struct platform_driver nv_tco_driver = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 471) .probe = nv_tco_init,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 472) .remove = nv_tco_remove,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 473) .shutdown = nv_tco_shutdown,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 474) .driver = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 475) .name = TCO_MODULE_NAME,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 476) },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 477) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 478)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 479) static int __init nv_tco_init_module(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 480) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 481) int err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 482)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 483) pr_info("NV TCO WatchDog Timer Driver v%s\n", TCO_VERSION);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 484)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 485) err = platform_driver_register(&nv_tco_driver);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 486) if (err)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 487) return err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 488)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 489) nv_tco_platform_device = platform_device_register_simple(
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 490) TCO_MODULE_NAME, -1, NULL, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 491) if (IS_ERR(nv_tco_platform_device)) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 492) err = PTR_ERR(nv_tco_platform_device);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 493) goto unreg_platform_driver;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 494) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 495)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 496) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 497)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 498) unreg_platform_driver:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 499) platform_driver_unregister(&nv_tco_driver);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 500) return err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 501) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 502)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 503) static void __exit nv_tco_cleanup_module(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 504) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 505) platform_device_unregister(nv_tco_platform_device);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 506) platform_driver_unregister(&nv_tco_driver);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 507) pr_info("NV TCO Watchdog Module Unloaded\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 508) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 509)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 510) module_init(nv_tco_init_module);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 511) module_exit(nv_tco_cleanup_module);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 512)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 513) MODULE_AUTHOR("Mike Waychison");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 514) MODULE_DESCRIPTION("TCO timer driver for NV chipsets");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 515) MODULE_LICENSE("GPL");