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