12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2456c7301SMike Waychison /*
3456c7301SMike Waychison * nv_tco 0.01: TCO timer driver for NV chipsets
4456c7301SMike Waychison *
5456c7301SMike Waychison * (c) Copyright 2005 Google Inc., All Rights Reserved.
6456c7301SMike Waychison *
7456c7301SMike Waychison * Based off i8xx_tco.c:
8456c7301SMike Waychison * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights
9456c7301SMike Waychison * Reserved.
102ab77a34SAlexander A. Klimov * https://www.kernelconcepts.de
11456c7301SMike Waychison *
12456c7301SMike Waychison * TCO timer driver for NV chipsets
13456c7301SMike Waychison * based on softdog.c by Alan Cox <alan@redhat.com>
14456c7301SMike Waychison */
15456c7301SMike Waychison
16456c7301SMike Waychison /*
17456c7301SMike Waychison * Includes, defines, variables, module parameters, ...
18456c7301SMike Waychison */
19456c7301SMike Waychison
2027c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2127c766aaSJoe Perches
22456c7301SMike Waychison #include <linux/module.h>
23456c7301SMike Waychison #include <linux/moduleparam.h>
24456c7301SMike Waychison #include <linux/types.h>
25456c7301SMike Waychison #include <linux/miscdevice.h>
26456c7301SMike Waychison #include <linux/watchdog.h>
27456c7301SMike Waychison #include <linux/init.h>
28456c7301SMike Waychison #include <linux/fs.h>
29456c7301SMike Waychison #include <linux/pci.h>
30456c7301SMike Waychison #include <linux/ioport.h>
31456c7301SMike Waychison #include <linux/jiffies.h>
32456c7301SMike Waychison #include <linux/platform_device.h>
33456c7301SMike Waychison #include <linux/uaccess.h>
34456c7301SMike Waychison #include <linux/io.h>
35456c7301SMike Waychison
36456c7301SMike Waychison #include "nv_tco.h"
37456c7301SMike Waychison
38456c7301SMike Waychison /* Module and version information */
39456c7301SMike Waychison #define TCO_VERSION "0.01"
40456c7301SMike Waychison #define TCO_MODULE_NAME "NV_TCO"
41456c7301SMike Waychison #define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
42456c7301SMike Waychison
43456c7301SMike Waychison /* internal variables */
44456c7301SMike Waychison static unsigned int tcobase;
45456c7301SMike Waychison static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */
46456c7301SMike Waychison static unsigned long timer_alive;
47456c7301SMike Waychison static char tco_expect_close;
48456c7301SMike Waychison static struct pci_dev *tco_pci;
49456c7301SMike Waychison
50456c7301SMike Waychison /* the watchdog platform device */
51456c7301SMike Waychison static struct platform_device *nv_tco_platform_device;
52456c7301SMike Waychison
53456c7301SMike Waychison /* module parameters */
54456c7301SMike Waychison #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (2<heartbeat<39) */
55456c7301SMike Waychison static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
56456c7301SMike Waychison module_param(heartbeat, int, 0);
57456c7301SMike Waychison MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39, "
58456c7301SMike Waychison "default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
59456c7301SMike Waychison
6086a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
6186a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
62456c7301SMike Waychison MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"
63456c7301SMike Waychison " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
64456c7301SMike Waychison
65456c7301SMike Waychison /*
66456c7301SMike Waychison * Some TCO specific functions
67456c7301SMike Waychison */
seconds_to_ticks(int seconds)68456c7301SMike Waychison static inline unsigned char seconds_to_ticks(int seconds)
69456c7301SMike Waychison {
70456c7301SMike Waychison /* the internal timer is stored as ticks which decrement
71456c7301SMike Waychison * every 0.6 seconds */
72456c7301SMike Waychison return (seconds * 10) / 6;
73456c7301SMike Waychison }
74456c7301SMike Waychison
tco_timer_start(void)75456c7301SMike Waychison static void tco_timer_start(void)
76456c7301SMike Waychison {
77456c7301SMike Waychison u32 val;
78456c7301SMike Waychison unsigned long flags;
79456c7301SMike Waychison
80456c7301SMike Waychison spin_lock_irqsave(&tco_lock, flags);
81456c7301SMike Waychison val = inl(TCO_CNT(tcobase));
82456c7301SMike Waychison val &= ~TCO_CNT_TCOHALT;
83456c7301SMike Waychison outl(val, TCO_CNT(tcobase));
84456c7301SMike Waychison spin_unlock_irqrestore(&tco_lock, flags);
85456c7301SMike Waychison }
86456c7301SMike Waychison
tco_timer_stop(void)87456c7301SMike Waychison static void tco_timer_stop(void)
88456c7301SMike Waychison {
89456c7301SMike Waychison u32 val;
90456c7301SMike Waychison unsigned long flags;
91456c7301SMike Waychison
92456c7301SMike Waychison spin_lock_irqsave(&tco_lock, flags);
93456c7301SMike Waychison val = inl(TCO_CNT(tcobase));
94456c7301SMike Waychison val |= TCO_CNT_TCOHALT;
95456c7301SMike Waychison outl(val, TCO_CNT(tcobase));
96456c7301SMike Waychison spin_unlock_irqrestore(&tco_lock, flags);
97456c7301SMike Waychison }
98456c7301SMike Waychison
tco_timer_keepalive(void)99456c7301SMike Waychison static void tco_timer_keepalive(void)
100456c7301SMike Waychison {
101456c7301SMike Waychison unsigned long flags;
102456c7301SMike Waychison
103456c7301SMike Waychison spin_lock_irqsave(&tco_lock, flags);
104456c7301SMike Waychison outb(0x01, TCO_RLD(tcobase));
105456c7301SMike Waychison spin_unlock_irqrestore(&tco_lock, flags);
106456c7301SMike Waychison }
107456c7301SMike Waychison
tco_timer_set_heartbeat(int t)108456c7301SMike Waychison static int tco_timer_set_heartbeat(int t)
109456c7301SMike Waychison {
110456c7301SMike Waychison int ret = 0;
111456c7301SMike Waychison unsigned char tmrval;
112456c7301SMike Waychison unsigned long flags;
113456c7301SMike Waychison u8 val;
114456c7301SMike Waychison
115456c7301SMike Waychison /*
116456c7301SMike Waychison * note seconds_to_ticks(t) > t, so if t > 0x3f, so is
117456c7301SMike Waychison * tmrval=seconds_to_ticks(t). Check that the count in seconds isn't
118456c7301SMike Waychison * out of range on it's own (to avoid overflow in tmrval).
119456c7301SMike Waychison */
120456c7301SMike Waychison if (t < 0 || t > 0x3f)
121456c7301SMike Waychison return -EINVAL;
122456c7301SMike Waychison tmrval = seconds_to_ticks(t);
123456c7301SMike Waychison
124456c7301SMike Waychison /* "Values of 0h-3h are ignored and should not be attempted" */
125456c7301SMike Waychison if (tmrval > 0x3f || tmrval < 0x04)
126456c7301SMike Waychison return -EINVAL;
127456c7301SMike Waychison
128456c7301SMike Waychison /* Write new heartbeat to watchdog */
129456c7301SMike Waychison spin_lock_irqsave(&tco_lock, flags);
130456c7301SMike Waychison val = inb(TCO_TMR(tcobase));
131456c7301SMike Waychison val &= 0xc0;
132456c7301SMike Waychison val |= tmrval;
133456c7301SMike Waychison outb(val, TCO_TMR(tcobase));
134456c7301SMike Waychison val = inb(TCO_TMR(tcobase));
135456c7301SMike Waychison
136456c7301SMike Waychison if ((val & 0x3f) != tmrval)
137456c7301SMike Waychison ret = -EINVAL;
138456c7301SMike Waychison spin_unlock_irqrestore(&tco_lock, flags);
139456c7301SMike Waychison
140456c7301SMike Waychison if (ret)
141456c7301SMike Waychison return ret;
142456c7301SMike Waychison
143456c7301SMike Waychison heartbeat = t;
144456c7301SMike Waychison return 0;
145456c7301SMike Waychison }
146456c7301SMike Waychison
147456c7301SMike Waychison /*
148456c7301SMike Waychison * /dev/watchdog handling
149456c7301SMike Waychison */
150456c7301SMike Waychison
nv_tco_open(struct inode * inode,struct file * file)151456c7301SMike Waychison static int nv_tco_open(struct inode *inode, struct file *file)
152456c7301SMike Waychison {
153456c7301SMike Waychison /* /dev/watchdog can only be opened once */
154456c7301SMike Waychison if (test_and_set_bit(0, &timer_alive))
155456c7301SMike Waychison return -EBUSY;
156456c7301SMike Waychison
157456c7301SMike Waychison /* Reload and activate timer */
158456c7301SMike Waychison tco_timer_keepalive();
159456c7301SMike Waychison tco_timer_start();
160c5bf68feSKirill Smelkov return stream_open(inode, file);
161456c7301SMike Waychison }
162456c7301SMike Waychison
nv_tco_release(struct inode * inode,struct file * file)163456c7301SMike Waychison static int nv_tco_release(struct inode *inode, struct file *file)
164456c7301SMike Waychison {
165456c7301SMike Waychison /* Shut off the timer */
166456c7301SMike Waychison if (tco_expect_close == 42) {
167456c7301SMike Waychison tco_timer_stop();
168456c7301SMike Waychison } else {
16927c766aaSJoe Perches pr_crit("Unexpected close, not stopping watchdog!\n");
170456c7301SMike Waychison tco_timer_keepalive();
171456c7301SMike Waychison }
172456c7301SMike Waychison clear_bit(0, &timer_alive);
173456c7301SMike Waychison tco_expect_close = 0;
174456c7301SMike Waychison return 0;
175456c7301SMike Waychison }
176456c7301SMike Waychison
nv_tco_write(struct file * file,const char __user * data,size_t len,loff_t * ppos)177456c7301SMike Waychison static ssize_t nv_tco_write(struct file *file, const char __user *data,
178456c7301SMike Waychison size_t len, loff_t *ppos)
179456c7301SMike Waychison {
180456c7301SMike Waychison /* See if we got the magic character 'V' and reload the timer */
181456c7301SMike Waychison if (len) {
182456c7301SMike Waychison if (!nowayout) {
183456c7301SMike Waychison size_t i;
184456c7301SMike Waychison
185456c7301SMike Waychison /*
186456c7301SMike Waychison * note: just in case someone wrote the magic character
187456c7301SMike Waychison * five months ago...
188456c7301SMike Waychison */
189456c7301SMike Waychison tco_expect_close = 0;
190456c7301SMike Waychison
191456c7301SMike Waychison /*
192456c7301SMike Waychison * scan to see whether or not we got the magic
193456c7301SMike Waychison * character
194456c7301SMike Waychison */
195456c7301SMike Waychison for (i = 0; i != len; i++) {
196456c7301SMike Waychison char c;
197456c7301SMike Waychison if (get_user(c, data + i))
198456c7301SMike Waychison return -EFAULT;
199456c7301SMike Waychison if (c == 'V')
200456c7301SMike Waychison tco_expect_close = 42;
201456c7301SMike Waychison }
202456c7301SMike Waychison }
203456c7301SMike Waychison
204456c7301SMike Waychison /* someone wrote to us, we should reload the timer */
205456c7301SMike Waychison tco_timer_keepalive();
206456c7301SMike Waychison }
207456c7301SMike Waychison return len;
208456c7301SMike Waychison }
209456c7301SMike Waychison
nv_tco_ioctl(struct file * file,unsigned int cmd,unsigned long arg)210456c7301SMike Waychison static long nv_tco_ioctl(struct file *file, unsigned int cmd,
211456c7301SMike Waychison unsigned long arg)
212456c7301SMike Waychison {
213456c7301SMike Waychison int new_options, retval = -EINVAL;
214456c7301SMike Waychison int new_heartbeat;
215456c7301SMike Waychison void __user *argp = (void __user *)arg;
216456c7301SMike Waychison int __user *p = argp;
217456c7301SMike Waychison static const struct watchdog_info ident = {
218456c7301SMike Waychison .options = WDIOF_SETTIMEOUT |
219456c7301SMike Waychison WDIOF_KEEPALIVEPING |
220456c7301SMike Waychison WDIOF_MAGICCLOSE,
221456c7301SMike Waychison .firmware_version = 0,
222456c7301SMike Waychison .identity = TCO_MODULE_NAME,
223456c7301SMike Waychison };
224456c7301SMike Waychison
225456c7301SMike Waychison switch (cmd) {
226456c7301SMike Waychison case WDIOC_GETSUPPORT:
227456c7301SMike Waychison return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
228456c7301SMike Waychison case WDIOC_GETSTATUS:
229456c7301SMike Waychison case WDIOC_GETBOOTSTATUS:
230456c7301SMike Waychison return put_user(0, p);
231456c7301SMike Waychison case WDIOC_SETOPTIONS:
232456c7301SMike Waychison if (get_user(new_options, p))
233456c7301SMike Waychison return -EFAULT;
234456c7301SMike Waychison if (new_options & WDIOS_DISABLECARD) {
235456c7301SMike Waychison tco_timer_stop();
236456c7301SMike Waychison retval = 0;
237456c7301SMike Waychison }
238456c7301SMike Waychison if (new_options & WDIOS_ENABLECARD) {
239456c7301SMike Waychison tco_timer_keepalive();
240456c7301SMike Waychison tco_timer_start();
241456c7301SMike Waychison retval = 0;
242456c7301SMike Waychison }
243456c7301SMike Waychison return retval;
244456c7301SMike Waychison case WDIOC_KEEPALIVE:
245456c7301SMike Waychison tco_timer_keepalive();
246456c7301SMike Waychison return 0;
247456c7301SMike Waychison case WDIOC_SETTIMEOUT:
248456c7301SMike Waychison if (get_user(new_heartbeat, p))
249456c7301SMike Waychison return -EFAULT;
250456c7301SMike Waychison if (tco_timer_set_heartbeat(new_heartbeat))
251456c7301SMike Waychison return -EINVAL;
252456c7301SMike Waychison tco_timer_keepalive();
253bd490f82SGustavo A. R. Silva fallthrough;
254456c7301SMike Waychison case WDIOC_GETTIMEOUT:
255456c7301SMike Waychison return put_user(heartbeat, p);
256456c7301SMike Waychison default:
257456c7301SMike Waychison return -ENOTTY;
258456c7301SMike Waychison }
259456c7301SMike Waychison }
260456c7301SMike Waychison
261456c7301SMike Waychison /*
262456c7301SMike Waychison * Kernel Interfaces
263456c7301SMike Waychison */
264456c7301SMike Waychison
265456c7301SMike Waychison static const struct file_operations nv_tco_fops = {
266456c7301SMike Waychison .owner = THIS_MODULE,
267456c7301SMike Waychison .llseek = no_llseek,
268456c7301SMike Waychison .write = nv_tco_write,
269456c7301SMike Waychison .unlocked_ioctl = nv_tco_ioctl,
270b6dfb247SArnd Bergmann .compat_ioctl = compat_ptr_ioctl,
271456c7301SMike Waychison .open = nv_tco_open,
272456c7301SMike Waychison .release = nv_tco_release,
273456c7301SMike Waychison };
274456c7301SMike Waychison
275456c7301SMike Waychison static struct miscdevice nv_tco_miscdev = {
276456c7301SMike Waychison .minor = WATCHDOG_MINOR,
277456c7301SMike Waychison .name = "watchdog",
278456c7301SMike Waychison .fops = &nv_tco_fops,
279456c7301SMike Waychison };
280456c7301SMike Waychison
281456c7301SMike Waychison /*
282456c7301SMike Waychison * Data for PCI driver interface
283456c7301SMike Waychison *
284456c7301SMike Waychison * This data only exists for exporting the supported
285456c7301SMike Waychison * PCI ids via MODULE_DEVICE_TABLE. We do not actually
286456c7301SMike Waychison * register a pci_driver, because someone else might one day
287456c7301SMike Waychison * want to register another driver on the same PCI id.
288456c7301SMike Waychison */
289bc17f9dcSJingoo Han static const struct pci_device_id tco_pci_tbl[] = {
290456c7301SMike Waychison { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_SMBUS,
291456c7301SMike Waychison PCI_ANY_ID, PCI_ANY_ID, },
292456c7301SMike Waychison { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SMBUS,
293456c7301SMike Waychison PCI_ANY_ID, PCI_ANY_ID, },
29465b5b5e6SAlexey Kunitskiy { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP78S_SMBUS,
29565b5b5e6SAlexey Kunitskiy PCI_ANY_ID, PCI_ANY_ID, },
29664307b48SVivien Didelot { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP79_SMBUS,
29764307b48SVivien Didelot PCI_ANY_ID, PCI_ANY_ID, },
298456c7301SMike Waychison { 0, }, /* End of list */
299456c7301SMike Waychison };
300456c7301SMike Waychison MODULE_DEVICE_TABLE(pci, tco_pci_tbl);
301456c7301SMike Waychison
302456c7301SMike Waychison /*
303456c7301SMike Waychison * Init & exit routines
304456c7301SMike Waychison */
305456c7301SMike Waychison
nv_tco_getdevice(void)3062d991a16SBill Pemberton static unsigned char nv_tco_getdevice(void)
307456c7301SMike Waychison {
308456c7301SMike Waychison struct pci_dev *dev = NULL;
309456c7301SMike Waychison u32 val;
310456c7301SMike Waychison
311456c7301SMike Waychison /* Find the PCI device */
312456c7301SMike Waychison for_each_pci_dev(dev) {
313456c7301SMike Waychison if (pci_match_id(tco_pci_tbl, dev) != NULL) {
314456c7301SMike Waychison tco_pci = dev;
315456c7301SMike Waychison break;
316456c7301SMike Waychison }
317456c7301SMike Waychison }
318456c7301SMike Waychison
319456c7301SMike Waychison if (!tco_pci)
320456c7301SMike Waychison return 0;
321456c7301SMike Waychison
322456c7301SMike Waychison /* Find the base io port */
323456c7301SMike Waychison pci_read_config_dword(tco_pci, 0x64, &val);
324456c7301SMike Waychison val &= 0xffff;
325456c7301SMike Waychison if (val == 0x0001 || val == 0x0000) {
326456c7301SMike Waychison /* Something is wrong here, bar isn't setup */
32727c766aaSJoe Perches pr_err("failed to get tcobase address\n");
328456c7301SMike Waychison return 0;
329456c7301SMike Waychison }
330456c7301SMike Waychison val &= 0xff00;
331456c7301SMike Waychison tcobase = val + 0x40;
332456c7301SMike Waychison
333456c7301SMike Waychison if (!request_region(tcobase, 0x10, "NV TCO")) {
33427c766aaSJoe Perches pr_err("I/O address 0x%04x already in use\n", tcobase);
335456c7301SMike Waychison return 0;
336456c7301SMike Waychison }
337456c7301SMike Waychison
338456c7301SMike Waychison /* Set a reasonable heartbeat before we stop the timer */
339456c7301SMike Waychison tco_timer_set_heartbeat(30);
340456c7301SMike Waychison
341456c7301SMike Waychison /*
342456c7301SMike Waychison * Stop the TCO before we change anything so we don't race with
343456c7301SMike Waychison * a zeroed timer.
344456c7301SMike Waychison */
345456c7301SMike Waychison tco_timer_keepalive();
346456c7301SMike Waychison tco_timer_stop();
347456c7301SMike Waychison
348456c7301SMike Waychison /* Disable SMI caused by TCO */
349456c7301SMike Waychison if (!request_region(MCP51_SMI_EN(tcobase), 4, "NV TCO")) {
35027c766aaSJoe Perches pr_err("I/O address 0x%04x already in use\n",
351456c7301SMike Waychison MCP51_SMI_EN(tcobase));
352456c7301SMike Waychison goto out;
353456c7301SMike Waychison }
354456c7301SMike Waychison val = inl(MCP51_SMI_EN(tcobase));
355456c7301SMike Waychison val &= ~MCP51_SMI_EN_TCO;
356456c7301SMike Waychison outl(val, MCP51_SMI_EN(tcobase));
357456c7301SMike Waychison val = inl(MCP51_SMI_EN(tcobase));
358456c7301SMike Waychison release_region(MCP51_SMI_EN(tcobase), 4);
359456c7301SMike Waychison if (val & MCP51_SMI_EN_TCO) {
36027c766aaSJoe Perches pr_err("Could not disable SMI caused by TCO\n");
361456c7301SMike Waychison goto out;
362456c7301SMike Waychison }
363456c7301SMike Waychison
364456c7301SMike Waychison /* Check chipset's NO_REBOOT bit */
365456c7301SMike Waychison pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
366456c7301SMike Waychison val |= MCP51_SMBUS_SETUP_B_TCO_REBOOT;
367456c7301SMike Waychison pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
368456c7301SMike Waychison pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
369456c7301SMike Waychison if (!(val & MCP51_SMBUS_SETUP_B_TCO_REBOOT)) {
37027c766aaSJoe Perches pr_err("failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
371456c7301SMike Waychison goto out;
372456c7301SMike Waychison }
373456c7301SMike Waychison
374456c7301SMike Waychison return 1;
375456c7301SMike Waychison out:
376456c7301SMike Waychison release_region(tcobase, 0x10);
377456c7301SMike Waychison return 0;
378456c7301SMike Waychison }
379456c7301SMike Waychison
nv_tco_init(struct platform_device * dev)3802d991a16SBill Pemberton static int nv_tco_init(struct platform_device *dev)
381456c7301SMike Waychison {
382456c7301SMike Waychison int ret;
383456c7301SMike Waychison
384456c7301SMike Waychison /* Check whether or not the hardware watchdog is there */
385456c7301SMike Waychison if (!nv_tco_getdevice())
386456c7301SMike Waychison return -ENODEV;
387456c7301SMike Waychison
388456c7301SMike Waychison /* Check to see if last reboot was due to watchdog timeout */
38927c766aaSJoe Perches pr_info("Watchdog reboot %sdetected\n",
390456c7301SMike Waychison inl(TCO_STS(tcobase)) & TCO_STS_TCO2TO_STS ? "" : "not ");
391456c7301SMike Waychison
392456c7301SMike Waychison /* Clear out the old status */
393456c7301SMike Waychison outl(TCO_STS_RESET, TCO_STS(tcobase));
394456c7301SMike Waychison
395456c7301SMike Waychison /*
396456c7301SMike Waychison * Check that the heartbeat value is within it's range.
397456c7301SMike Waychison * If not, reset to the default.
398456c7301SMike Waychison */
399456c7301SMike Waychison if (tco_timer_set_heartbeat(heartbeat)) {
400456c7301SMike Waychison heartbeat = WATCHDOG_HEARTBEAT;
401456c7301SMike Waychison tco_timer_set_heartbeat(heartbeat);
40227c766aaSJoe Perches pr_info("heartbeat value must be 2<heartbeat<39, using %d\n",
40327c766aaSJoe Perches heartbeat);
404456c7301SMike Waychison }
405456c7301SMike Waychison
406456c7301SMike Waychison ret = misc_register(&nv_tco_miscdev);
407456c7301SMike Waychison if (ret != 0) {
40827c766aaSJoe Perches pr_err("cannot register miscdev on minor=%d (err=%d)\n",
40927c766aaSJoe Perches WATCHDOG_MINOR, ret);
410456c7301SMike Waychison goto unreg_region;
411456c7301SMike Waychison }
412456c7301SMike Waychison
413456c7301SMike Waychison clear_bit(0, &timer_alive);
414456c7301SMike Waychison
415456c7301SMike Waychison tco_timer_stop();
416456c7301SMike Waychison
41727c766aaSJoe Perches pr_info("initialized (0x%04x). heartbeat=%d sec (nowayout=%d)\n",
41827c766aaSJoe Perches tcobase, heartbeat, nowayout);
419456c7301SMike Waychison
420456c7301SMike Waychison return 0;
421456c7301SMike Waychison
422456c7301SMike Waychison unreg_region:
423456c7301SMike Waychison release_region(tcobase, 0x10);
424456c7301SMike Waychison return ret;
425456c7301SMike Waychison }
426456c7301SMike Waychison
nv_tco_cleanup(void)4274b12b896SBill Pemberton static void nv_tco_cleanup(void)
428456c7301SMike Waychison {
429456c7301SMike Waychison u32 val;
430456c7301SMike Waychison
431456c7301SMike Waychison /* Stop the timer before we leave */
432456c7301SMike Waychison if (!nowayout)
433456c7301SMike Waychison tco_timer_stop();
434456c7301SMike Waychison
435456c7301SMike Waychison /* Set the NO_REBOOT bit to prevent later reboots, just for sure */
436456c7301SMike Waychison pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
437456c7301SMike Waychison val &= ~MCP51_SMBUS_SETUP_B_TCO_REBOOT;
438456c7301SMike Waychison pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
439456c7301SMike Waychison pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
440456c7301SMike Waychison if (val & MCP51_SMBUS_SETUP_B_TCO_REBOOT) {
44127c766aaSJoe Perches pr_crit("Couldn't unset REBOOT bit. Machine may soon reset\n");
442456c7301SMike Waychison }
443456c7301SMike Waychison
444456c7301SMike Waychison /* Deregister */
445456c7301SMike Waychison misc_deregister(&nv_tco_miscdev);
446456c7301SMike Waychison release_region(tcobase, 0x10);
447456c7301SMike Waychison }
448456c7301SMike Waychison
nv_tco_remove(struct platform_device * dev)449*3a973109SUwe Kleine-König static void nv_tco_remove(struct platform_device *dev)
450456c7301SMike Waychison {
451456c7301SMike Waychison if (tcobase)
452456c7301SMike Waychison nv_tco_cleanup();
453456c7301SMike Waychison }
454456c7301SMike Waychison
nv_tco_shutdown(struct platform_device * dev)455456c7301SMike Waychison static void nv_tco_shutdown(struct platform_device *dev)
456456c7301SMike Waychison {
4576b01d30eSMart Gerrits u32 val;
4586b01d30eSMart Gerrits
459456c7301SMike Waychison tco_timer_stop();
4606b01d30eSMart Gerrits
4616b01d30eSMart Gerrits /* Some BIOSes fail the POST (once) if the NO_REBOOT flag is not
4626b01d30eSMart Gerrits * unset during shutdown. */
4636b01d30eSMart Gerrits pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
4646b01d30eSMart Gerrits val &= ~MCP51_SMBUS_SETUP_B_TCO_REBOOT;
4656b01d30eSMart Gerrits pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
466456c7301SMike Waychison }
467456c7301SMike Waychison
468456c7301SMike Waychison static struct platform_driver nv_tco_driver = {
469456c7301SMike Waychison .probe = nv_tco_init,
470*3a973109SUwe Kleine-König .remove_new = nv_tco_remove,
471456c7301SMike Waychison .shutdown = nv_tco_shutdown,
472456c7301SMike Waychison .driver = {
473456c7301SMike Waychison .name = TCO_MODULE_NAME,
474456c7301SMike Waychison },
475456c7301SMike Waychison };
476456c7301SMike Waychison
nv_tco_init_module(void)477456c7301SMike Waychison static int __init nv_tco_init_module(void)
478456c7301SMike Waychison {
479456c7301SMike Waychison int err;
480456c7301SMike Waychison
48127c766aaSJoe Perches pr_info("NV TCO WatchDog Timer Driver v%s\n", TCO_VERSION);
482456c7301SMike Waychison
483456c7301SMike Waychison err = platform_driver_register(&nv_tco_driver);
484456c7301SMike Waychison if (err)
485456c7301SMike Waychison return err;
486456c7301SMike Waychison
487456c7301SMike Waychison nv_tco_platform_device = platform_device_register_simple(
488456c7301SMike Waychison TCO_MODULE_NAME, -1, NULL, 0);
489456c7301SMike Waychison if (IS_ERR(nv_tco_platform_device)) {
490456c7301SMike Waychison err = PTR_ERR(nv_tco_platform_device);
491456c7301SMike Waychison goto unreg_platform_driver;
492456c7301SMike Waychison }
493456c7301SMike Waychison
494456c7301SMike Waychison return 0;
495456c7301SMike Waychison
496456c7301SMike Waychison unreg_platform_driver:
497456c7301SMike Waychison platform_driver_unregister(&nv_tco_driver);
498456c7301SMike Waychison return err;
499456c7301SMike Waychison }
500456c7301SMike Waychison
nv_tco_cleanup_module(void)501456c7301SMike Waychison static void __exit nv_tco_cleanup_module(void)
502456c7301SMike Waychison {
503456c7301SMike Waychison platform_device_unregister(nv_tco_platform_device);
504456c7301SMike Waychison platform_driver_unregister(&nv_tco_driver);
50527c766aaSJoe Perches pr_info("NV TCO Watchdog Module Unloaded\n");
506456c7301SMike Waychison }
507456c7301SMike Waychison
508456c7301SMike Waychison module_init(nv_tco_init_module);
509456c7301SMike Waychison module_exit(nv_tco_cleanup_module);
510456c7301SMike Waychison
511456c7301SMike Waychison MODULE_AUTHOR("Mike Waychison");
512456c7301SMike Waychison MODULE_DESCRIPTION("TCO timer driver for NV chipsets");
513456c7301SMike Waychison MODULE_LICENSE("GPL");
514