]> git.karo-electronics.de Git - mv-sheeva.git/commitdiff
Merge git://git.kernel.org/pub/scm/linux/kernel/git/wim/linux-2.6-watchdog
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 13 Jan 2011 18:24:52 +0000 (10:24 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 13 Jan 2011 18:24:52 +0000 (10:24 -0800)
* git://git.kernel.org/pub/scm/linux/kernel/git/wim/linux-2.6-watchdog:
  watchdog: Add MCF548x watchdog driver.
  watchdog: add driver for the Atheros AR71XX/AR724X/AR913X SoCs
  watchdog: Add TCO support for nVidia chipsets
  watchdog: Add support for sp5100 chipset TCO
  watchdog: f71808e_wdt: add F71862FG, F71869 to Kconfig
  watchdog: iTCO_wdt: TCO Watchdog patch for Intel DH89xxCC PCH
  watchdog: iTCO_wdt: TCO Watchdog patch for Intel NM10 DeviceIDs
  watchdog: ks8695_wdt: include mach/hardware.h instead of mach/timex.h.
  watchdog: Propagate Book E WDT period changes to all cores
  watchdog: add CONFIG_WATCHDOG_NOWAYOUT support to PowerPC Book-E watchdog driver
  watchdog: alim7101_wdt: fix compiler warning on alim7101_pci_tbl
  watchdog: alim1535_wdt: fix compiler warning on ali_pci_tbl
  watchdog: Fix reboot on W83627ehf chipset.
  watchdog: Add watchdog support for W83627DHG chip
  watchdog: f71808e_wdt: Add Fintek F71869 watchdog
  watchdog: add f71862fg support
  watchdog: clean-up f71808e_wdt.c

16 files changed:
arch/m68k/include/asm/m548xgpt.h
drivers/watchdog/Kconfig
drivers/watchdog/Makefile
drivers/watchdog/alim1535_wdt.c
drivers/watchdog/alim7101_wdt.c
drivers/watchdog/ath79_wdt.c [new file with mode: 0644]
drivers/watchdog/booke_wdt.c
drivers/watchdog/f71808e_wdt.c
drivers/watchdog/iTCO_wdt.c
drivers/watchdog/ks8695_wdt.c
drivers/watchdog/m548x_wdt.c [new file with mode: 0644]
drivers/watchdog/nv_tco.c [new file with mode: 0644]
drivers/watchdog/nv_tco.h [new file with mode: 0644]
drivers/watchdog/sp5100_tco.c [new file with mode: 0644]
drivers/watchdog/sp5100_tco.h [new file with mode: 0644]
drivers/watchdog/w83627hf_wdt.c

index c8ef158a1c4e5e789181d43896a98f35dacbf1be..33b2eef90f0a99f913873922e166233b48c5c6e6 100644 (file)
 #define MCF_GPT_GMS_GPIO_INPUT     (0x00000000)
 #define MCF_GPT_GMS_GPIO_OUTLO     (0x00000020)
 #define MCF_GPT_GMS_GPIO_OUTHI     (0x00000030)
+#define MCF_GPT_GMS_GPIO_MASK      (0x00000030)
 #define MCF_GPT_GMS_TMS_DISABLE    (0x00000000)
 #define MCF_GPT_GMS_TMS_INCAPT     (0x00000001)
 #define MCF_GPT_GMS_TMS_OUTCAPT    (0x00000002)
 #define MCF_GPT_GMS_TMS_PWM        (0x00000003)
 #define MCF_GPT_GMS_TMS_GPIO       (0x00000004)
+#define MCF_GPT_GMS_TMS_MASK       (0x00000007)
 
 /* Bit definitions and macros for MCF_GPT_GCIR */
 #define MCF_GPT_GCIR_CNT(x)        (((x)&0x0000FFFF)<<0)
index a5ad77ef4266f22b166d8d86a5484aec5094178f..2e2400e7322e8b0520c570b0920b19a6bb0c6c53 100644 (file)
@@ -409,15 +409,26 @@ config ALIM7101_WDT
          Most people will say N.
 
 config F71808E_WDT
-       tristate "Fintek F71808E, F71882FG and F71889FG Watchdog"
+       tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog"
        depends on X86 && EXPERIMENTAL
        help
          This is the driver for the hardware watchdog on the Fintek
-         F71808E, F71882FG and F71889FG Super I/O controllers.
+         F71808E, F71862FG, F71869, F71882FG and F71889FG Super I/O controllers.
 
          You can compile this driver directly into the kernel, or use
          it as a module.  The module will be called f71808e_wdt.
 
+config SP5100_TCO
+       tristate "AMD/ATI SP5100 TCO Timer/Watchdog"
+       depends on X86 && PCI
+       ---help---
+         Hardware watchdog driver for the AMD/ATI SP5100 chipset. The TCO
+         (Total Cost of Ownership) timer is a watchdog timer that will reboot
+         the machine after its expiration. The expiration time can be
+         configured with the "heartbeat" parameter.
+
+         To compile this driver as a module, choose M here: the
+         module will be called sp5100_tco.
 
 config GEODE_WDT
        tristate "AMD Geode CS5535/CS5536 Watchdog"
@@ -631,6 +642,24 @@ config PC87413_WDT
 
          Most people will say N.
 
+config NV_TCO
+       tristate "nVidia TCO Timer/Watchdog"
+       depends on X86 && PCI
+       ---help---
+         Hardware driver for the TCO timer built into the nVidia Hub family
+         (such as the MCP51).  The TCO (Total Cost of Ownership) timer is a
+         watchdog timer that will reboot the machine after its second
+         expiration. The expiration time can be configured with the
+         "heartbeat" parameter.
+
+         On some motherboards the driver may fail to reset the chipset's
+         NO_REBOOT flag which prevents the watchdog from rebooting the
+         machine. If this is the case you will get a kernel message like
+         "failed to reset NO_REBOOT flag, reboot disabled by hardware".
+
+         To compile this driver as a module, choose M here: the
+         module will be called nv_tco.
+
 config RDC321X_WDT
        tristate "RDC R-321x SoC watchdog"
        depends on X86_RDC321X
@@ -722,14 +751,15 @@ config SMSC37B787_WDT
          Most people will say N.
 
 config W83627HF_WDT
-       tristate "W83627HF Watchdog Timer"
+       tristate "W83627HF/W83627DHG Watchdog Timer"
        depends on X86
        ---help---
          This is the driver for the hardware watchdog on the W83627HF chipset
          as used in Advantech PC-9578 and Tyan S2721-533 motherboards
-         (and likely others).  This watchdog simply watches your kernel to
-         make sure it doesn't freeze, and if it does, it reboots your computer
-         after a certain amount of time.
+         (and likely others). The driver also supports the W83627DHG chip.
+         This watchdog simply watches your kernel to make sure it doesn't
+         freeze, and if it does, it reboots your computer after a certain
+         amount of time.
 
          To compile this driver as a module, choose M here: the
          module will be called w83627hf_wdt.
@@ -832,10 +862,22 @@ config SBC_EPX_C3_WATCHDOG
 
 # M68K Architecture
 
-# M68KNOMMU Architecture
+config M548x_WATCHDOG
+       tristate "MCF548x watchdog support"
+       depends on M548x
+       help
+         To compile this driver as a module, choose M here: the
+         module will be called m548x_wdt.
 
 # MIPS Architecture
 
+config ATH79_WDT
+       tristate "Atheros AR71XX/AR724X/AR913X hardware watchdog"
+       depends on ATH79
+       help
+         Hardware driver for the built-in watchdog timer on the Atheros
+         AR71XX/AR724X/AR913X SoCs.
+
 config BCM47XX_WDT
        tristate "Broadcom BCM47xx Watchdog Timer"
        depends on BCM47XX
index 4b0ef386229d006b7acdd1398dc5e8713e4c6943..dd776651917ce691f5c714a057b651b15dd0cd92 100644 (file)
@@ -68,6 +68,7 @@ obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o
 obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
 obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
 obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o
+obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o
 obj-$(CONFIG_GEODE_WDT) += geodewdt.o
 obj-$(CONFIG_SC520_WDT) += sc520_wdt.o
 obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o
@@ -86,6 +87,7 @@ obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
 obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
 obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
 obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
+obj-$(CONFIG_NV_TCO) += nv_tco.o
 obj-$(CONFIG_RDC321X_WDT) += rdc321x_wdt.o
 obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o
 obj-$(CONFIG_SBC8360_WDT) += sbc8360.o
@@ -104,10 +106,10 @@ obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o
 # M32R Architecture
 
 # M68K Architecture
-
-# M68KNOMMU Architecture
+obj-$(CONFIG_M548x_WATCHDOG) += m548x_wdt.o
 
 # MIPS Architecture
+obj-$(CONFIG_ATH79_WDT) += ath79_wdt.o
 obj-$(CONFIG_BCM47XX_WDT) += bcm47xx_wdt.o
 obj-$(CONFIG_BCM63XX_WDT) += bcm63xx_wdt.o
 obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o
index 1e9caea8ff8a283e87509624dd9ca8fc996f96e2..fa4d36033552fac0594bfabea42fbe10233e47f7 100644 (file)
@@ -301,7 +301,7 @@ static int ali_notify_sys(struct notifier_block *this,
  *     want to register another driver on the same PCI id.
  */
 
-static struct pci_device_id ali_pci_tbl[] = {
+static struct pci_device_id ali_pci_tbl[] __used = {
        { PCI_VENDOR_ID_AL, 0x1533, PCI_ANY_ID, PCI_ANY_ID,},
        { PCI_VENDOR_ID_AL, 0x1535, PCI_ANY_ID, PCI_ANY_ID,},
        { 0, },
index d8d4da9a483d07e1968b4710934467b8ebc91347..4b7a2b4138ed917a703c5d08b0b07616ba2d66b5 100644 (file)
@@ -430,7 +430,7 @@ err_out:
 module_init(alim7101_wdt_init);
 module_exit(alim7101_wdt_unload);
 
-static struct pci_device_id alim7101_pci_tbl[] __devinitdata = {
+static struct pci_device_id alim7101_pci_tbl[] __devinitdata __used = {
        { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533) },
        { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) },
        { }
diff --git a/drivers/watchdog/ath79_wdt.c b/drivers/watchdog/ath79_wdt.c
new file mode 100644 (file)
index 0000000..725c84b
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * Atheros AR71XX/AR724X/AR913X built-in hardware watchdog timer.
+ *
+ * Copyright (C) 2008-2011 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org>
+ *
+ * This driver was based on: drivers/watchdog/ixp4xx_wdt.c
+ *     Author: Deepak Saxena <dsaxena@plexity.net>
+ *     Copyright 2004 (c) MontaVista, Software, Inc.
+ *
+ * which again was based on sa1100 driver,
+ *     Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ */
+
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+#include <asm/mach-ath79/ath79.h>
+#include <asm/mach-ath79/ar71xx_regs.h>
+
+#define DRIVER_NAME    "ath79-wdt"
+
+#define WDT_TIMEOUT    15      /* seconds */
+
+#define WDOG_CTRL_LAST_RESET   BIT(31)
+#define WDOG_CTRL_ACTION_MASK  3
+#define WDOG_CTRL_ACTION_NONE  0       /* no action */
+#define WDOG_CTRL_ACTION_GPI   1       /* general purpose interrupt */
+#define WDOG_CTRL_ACTION_NMI   2       /* NMI */
+#define WDOG_CTRL_ACTION_FCR   3       /* full chip reset */
+
+static int nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
+                          "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static int timeout = WDT_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds "
+                         "(default=" __MODULE_STRING(WDT_TIMEOUT) "s)");
+
+static unsigned long wdt_flags;
+
+#define WDT_FLAGS_BUSY         0
+#define WDT_FLAGS_EXPECT_CLOSE 1
+
+static struct clk *wdt_clk;
+static unsigned long wdt_freq;
+static int boot_status;
+static int max_timeout;
+
+static inline void ath79_wdt_keepalive(void)
+{
+       ath79_reset_wr(AR71XX_RESET_REG_WDOG, wdt_freq * timeout);
+}
+
+static inline void ath79_wdt_enable(void)
+{
+       ath79_wdt_keepalive();
+       ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_FCR);
+}
+
+static inline void ath79_wdt_disable(void)
+{
+       ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_NONE);
+}
+
+static int ath79_wdt_set_timeout(int val)
+{
+       if (val < 1 || val > max_timeout)
+               return -EINVAL;
+
+       timeout = val;
+       ath79_wdt_keepalive();
+
+       return 0;
+}
+
+static int ath79_wdt_open(struct inode *inode, struct file *file)
+{
+       if (test_and_set_bit(WDT_FLAGS_BUSY, &wdt_flags))
+               return -EBUSY;
+
+       clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
+       ath79_wdt_enable();
+
+       return nonseekable_open(inode, file);
+}
+
+static int ath79_wdt_release(struct inode *inode, struct file *file)
+{
+       if (test_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags))
+               ath79_wdt_disable();
+       else {
+               pr_crit(DRIVER_NAME ": device closed unexpectedly, "
+                       "watchdog timer will not stop!\n");
+               ath79_wdt_keepalive();
+       }
+
+       clear_bit(WDT_FLAGS_BUSY, &wdt_flags);
+       clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
+
+       return 0;
+}
+
+static ssize_t ath79_wdt_write(struct file *file, const char *data,
+                               size_t len, loff_t *ppos)
+{
+       if (len) {
+               if (!nowayout) {
+                       size_t i;
+
+                       clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
+
+                       for (i = 0; i != len; i++) {
+                               char c;
+
+                               if (get_user(c, data + i))
+                                       return -EFAULT;
+
+                               if (c == 'V')
+                                       set_bit(WDT_FLAGS_EXPECT_CLOSE,
+                                               &wdt_flags);
+                       }
+               }
+
+               ath79_wdt_keepalive();
+       }
+
+       return len;
+}
+
+static const struct watchdog_info ath79_wdt_info = {
+       .options                = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+                                 WDIOF_MAGICCLOSE | WDIOF_CARDRESET,
+       .firmware_version       = 0,
+       .identity               = "ATH79 watchdog",
+};
+
+static long ath79_wdt_ioctl(struct file *file, unsigned int cmd,
+                           unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       int __user *p = argp;
+       int err;
+       int t;
+
+       switch (cmd) {
+       case WDIOC_GETSUPPORT:
+               err = copy_to_user(argp, &ath79_wdt_info,
+                                  sizeof(ath79_wdt_info)) ? -EFAULT : 0;
+               break;
+
+       case WDIOC_GETSTATUS:
+               err = put_user(0, p);
+               break;
+
+       case WDIOC_GETBOOTSTATUS:
+               err = put_user(boot_status, p);
+               break;
+
+       case WDIOC_KEEPALIVE:
+               ath79_wdt_keepalive();
+               err = 0;
+               break;
+
+       case WDIOC_SETTIMEOUT:
+               err = get_user(t, p);
+               if (err)
+                       break;
+
+               err = ath79_wdt_set_timeout(t);
+               if (err)
+                       break;
+
+               /* fallthrough */
+       case WDIOC_GETTIMEOUT:
+               err = put_user(timeout, p);
+               break;
+
+       default:
+               err = -ENOTTY;
+               break;
+       }
+
+       return err;
+}
+
+static const struct file_operations ath79_wdt_fops = {
+       .owner          = THIS_MODULE,
+       .llseek         = no_llseek,
+       .write          = ath79_wdt_write,
+       .unlocked_ioctl = ath79_wdt_ioctl,
+       .open           = ath79_wdt_open,
+       .release        = ath79_wdt_release,
+};
+
+static struct miscdevice ath79_wdt_miscdev = {
+       .minor = WATCHDOG_MINOR,
+       .name = "watchdog",
+       .fops = &ath79_wdt_fops,
+};
+
+static int __devinit ath79_wdt_probe(struct platform_device *pdev)
+{
+       u32 ctrl;
+       int err;
+
+       wdt_clk = clk_get(&pdev->dev, "wdt");
+       if (IS_ERR(wdt_clk))
+               return PTR_ERR(wdt_clk);
+
+       err = clk_enable(wdt_clk);
+       if (err)
+               goto err_clk_put;
+
+       wdt_freq = clk_get_rate(wdt_clk);
+       if (!wdt_freq) {
+               err = -EINVAL;
+               goto err_clk_disable;
+       }
+
+       max_timeout = (0xfffffffful / wdt_freq);
+       if (timeout < 1 || timeout > max_timeout) {
+               timeout = max_timeout;
+               dev_info(&pdev->dev,
+                       "timeout value must be 0 < timeout < %d, using %d\n",
+                       max_timeout, timeout);
+       }
+
+       ctrl = ath79_reset_rr(AR71XX_RESET_REG_WDOG_CTRL);
+       boot_status = (ctrl & WDOG_CTRL_LAST_RESET) ? WDIOF_CARDRESET : 0;
+
+       err = misc_register(&ath79_wdt_miscdev);
+       if (err) {
+               dev_err(&pdev->dev,
+                       "unable to register misc device, err=%d\n", err);
+               goto err_clk_disable;
+       }
+
+       return 0;
+
+err_clk_disable:
+       clk_disable(wdt_clk);
+err_clk_put:
+       clk_put(wdt_clk);
+       return err;
+}
+
+static int __devexit ath79_wdt_remove(struct platform_device *pdev)
+{
+       misc_deregister(&ath79_wdt_miscdev);
+       clk_disable(wdt_clk);
+       clk_put(wdt_clk);
+       return 0;
+}
+
+static void ath97_wdt_shutdown(struct platform_device *pdev)
+{
+       ath79_wdt_disable();
+}
+
+static struct platform_driver ath79_wdt_driver = {
+       .remove         = __devexit_p(ath79_wdt_remove),
+       .shutdown       = ath97_wdt_shutdown,
+       .driver         = {
+               .name   = DRIVER_NAME,
+               .owner  = THIS_MODULE,
+       },
+};
+
+static int __init ath79_wdt_init(void)
+{
+       return platform_driver_probe(&ath79_wdt_driver, ath79_wdt_probe);
+}
+module_init(ath79_wdt_init);
+
+static void __exit ath79_wdt_exit(void)
+{
+       platform_driver_unregister(&ath79_wdt_driver);
+}
+module_exit(ath79_wdt_exit);
+
+MODULE_DESCRIPTION("Atheros AR71XX/AR724X/AR913X hardware watchdog driver");
+MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org");
+MODULE_AUTHOR("Imre Kaloz <kaloz@openwrt.org");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
index d11ffb091b0dfc0fa67edb4422145ce959091182..7e7ec9c35b6a442e1569f4c25d5711ea6668e586 100644 (file)
@@ -85,6 +85,22 @@ static unsigned int sec_to_period(unsigned int secs)
        return 0;
 }
 
+static void __booke_wdt_set(void *data)
+{
+       u32 val;
+
+       val = mfspr(SPRN_TCR);
+       val &= ~WDTP_MASK;
+       val |= WDTP(booke_wdt_period);
+
+       mtspr(SPRN_TCR, val);
+}
+
+static void booke_wdt_set(void)
+{
+       on_each_cpu(__booke_wdt_set, NULL, 0);
+}
+
 static void __booke_wdt_ping(void *data)
 {
        mtspr(SPRN_TSR, TSR_ENW|TSR_WIS);
@@ -181,8 +197,7 @@ static long booke_wdt_ioctl(struct file *file,
 #else
                booke_wdt_period = tmp;
 #endif
-               mtspr(SPRN_TCR, (mfspr(SPRN_TCR) & ~WDTP_MASK) |
-                                               WDTP(booke_wdt_period));
+               booke_wdt_set();
                return 0;
        case WDIOC_GETTIMEOUT:
                return put_user(booke_wdt_period, p);
@@ -193,8 +208,15 @@ static long booke_wdt_ioctl(struct file *file,
        return 0;
 }
 
+/* wdt_is_active stores wether or not the /dev/watchdog device is opened */
+static unsigned long wdt_is_active;
+
 static int booke_wdt_open(struct inode *inode, struct file *file)
 {
+       /* /dev/watchdog can only be opened once */
+       if (test_and_set_bit(0, &wdt_is_active))
+               return -EBUSY;
+
        spin_lock(&booke_wdt_lock);
        if (booke_wdt_enabled == 0) {
                booke_wdt_enabled = 1;
@@ -210,8 +232,17 @@ static int booke_wdt_open(struct inode *inode, struct file *file)
 
 static int booke_wdt_release(struct inode *inode, struct file *file)
 {
+#ifndef CONFIG_WATCHDOG_NOWAYOUT
+       /* Normally, the watchdog is disabled when /dev/watchdog is closed, but
+        * if CONFIG_WATCHDOG_NOWAYOUT is defined, then it means that the
+        * watchdog should remain enabled.  So we disable it only if
+        * CONFIG_WATCHDOG_NOWAYOUT is not defined.
+        */
        on_each_cpu(__booke_wdt_disable, NULL, 0);
        booke_wdt_enabled = 0;
+#endif
+
+       clear_bit(0, &wdt_is_active);
 
        return 0;
 }
index 65e579635dba6cfa1754c0e648440b1dca6ff857..d4d8d1fdccc42987c4919a12157f89ebd47b291f 100644 (file)
 #define SIO_REG_DEVID          0x20    /* Device ID (2 bytes) */
 #define SIO_REG_DEVREV         0x22    /* Device revision */
 #define SIO_REG_MANID          0x23    /* Fintek ID (2 bytes) */
+#define SIO_REG_ROM_ADDR_SEL   0x27    /* ROM address select */
+#define SIO_REG_MFUNCT1                0x29    /* Multi function select 1 */
+#define SIO_REG_MFUNCT2                0x2a    /* Multi function select 2 */
+#define SIO_REG_MFUNCT3                0x2b    /* Multi function select 3 */
 #define SIO_REG_ENABLE         0x30    /* Logical device enable */
 #define SIO_REG_ADDR           0x60    /* Logical device address (2 bytes) */
 
 #define SIO_FINTEK_ID          0x1934  /* Manufacturers ID */
-#define SIO_F71808_ID          0x0901  /* Chipset ID */
-#define SIO_F71858_ID          0x0507  /* Chipset ID */
+#define SIO_F71808_ID          0x0901  /* Chipset ID */
+#define SIO_F71858_ID          0x0507  /* Chipset ID */
 #define SIO_F71862_ID          0x0601  /* Chipset ID */
+#define SIO_F71869_ID          0x0814  /* Chipset ID */
 #define SIO_F71882_ID          0x0541  /* Chipset ID */
 #define SIO_F71889_ID          0x0723  /* Chipset ID */
 
-#define        F71882FG_REG_START              0x01
-
 #define F71808FG_REG_WDO_CONF          0xf0
 #define F71808FG_REG_WDT_CONF          0xf5
 #define F71808FG_REG_WD_TIME           0xf6
 #define WATCHDOG_MAX_TIMEOUT   (60 * 255)
 #define WATCHDOG_PULSE_WIDTH   125     /* 125 ms, default pulse width for
                                           watchdog signal */
+#define WATCHDOG_F71862FG_PIN  63      /* default watchdog reset output
+                                          pin number 63 */
 
 static unsigned short force_id;
 module_param(force_id, ushort, 0);
 MODULE_PARM_DESC(force_id, "Override the detected device ID");
 
 static const int max_timeout = WATCHDOG_MAX_TIMEOUT;
-static int timeout = 60;       /* default timeout in seconds */
+static int timeout = WATCHDOG_TIMEOUT; /* default timeout in seconds */
 module_param(timeout, int, 0);
 MODULE_PARM_DESC(timeout,
        "Watchdog timeout in seconds. 1<= timeout <="
@@ -89,6 +94,12 @@ MODULE_PARM_DESC(pulse_width,
        "Watchdog signal pulse width. 0(=level), 1 ms, 25 ms, 125 ms or 5000 ms"
                        " (default=" __MODULE_STRING(WATCHDOG_PULSE_WIDTH) ")");
 
+static unsigned int f71862fg_pin = WATCHDOG_F71862FG_PIN;
+module_param(f71862fg_pin, uint, 0);
+MODULE_PARM_DESC(f71862fg_pin,
+       "Watchdog f71862fg reset output pin configuration. Choose pin 56 or 63"
+                       " (default=" __MODULE_STRING(WATCHDOG_F71862FG_PIN)")");
+
 static int nowayout = WATCHDOG_NOWAYOUT;
 module_param(nowayout, bool, 0444);
 MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
@@ -98,12 +109,13 @@ module_param(start_withtimeout, uint, 0);
 MODULE_PARM_DESC(start_withtimeout, "Start watchdog timer on module load with"
        " given initial timeout. Zero (default) disables this feature.");
 
-enum chips { f71808fg, f71858fg, f71862fg, f71882fg, f71889fg };
+enum chips { f71808fg, f71858fg, f71862fg, f71869, f71882fg, f71889fg };
 
 static const char *f71808e_names[] = {
        "f71808fg",
        "f71858fg",
        "f71862fg",
+       "f71869",
        "f71882fg",
        "f71889fg",
 };
@@ -282,6 +294,28 @@ exit_unlock:
        return err;
 }
 
+static int f71862fg_pin_configure(unsigned short ioaddr)
+{
+       /* When ioaddr is non-zero the calling function has to take care of
+          mutex handling and superio preparation! */
+
+       if (f71862fg_pin == 63) {
+               if (ioaddr) {
+                       /* SPI must be disabled first to use this pin! */
+                       superio_clear_bit(ioaddr, SIO_REG_ROM_ADDR_SEL, 6);
+                       superio_set_bit(ioaddr, SIO_REG_MFUNCT3, 4);
+               }
+       } else if (f71862fg_pin == 56) {
+               if (ioaddr)
+                       superio_set_bit(ioaddr, SIO_REG_MFUNCT1, 1);
+       } else {
+               printk(KERN_ERR DRVNAME ": Invalid argument f71862fg_pin=%d\n",
+                               f71862fg_pin);
+               return -EINVAL;
+       }
+       return 0;
+}
+
 static int watchdog_start(void)
 {
        /* Make sure we don't die as soon as the watchdog is enabled below */
@@ -299,19 +333,30 @@ static int watchdog_start(void)
        switch (watchdog.type) {
        case f71808fg:
                /* Set pin 21 to GPIO23/WDTRST#, then to WDTRST# */
-               superio_clear_bit(watchdog.sioaddr, 0x2a, 3);
-               superio_clear_bit(watchdog.sioaddr, 0x2b, 3);
+               superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT2, 3);
+               superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT3, 3);
+               break;
+
+       case f71862fg:
+               err = f71862fg_pin_configure(watchdog.sioaddr);
+               if (err)
+                       goto exit_superio;
+               break;
+
+       case f71869:
+               /* GPIO14 --> WDTRST# */
+               superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 4);
                break;
 
        case f71882fg:
                /* Set pin 56 to WDTRST# */
-               superio_set_bit(watchdog.sioaddr, 0x29, 1);
+               superio_set_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 1);
                break;
 
        case f71889fg:
                /* set pin 40 to WDTRST# */
-               superio_outb(watchdog.sioaddr, 0x2b,
-                               superio_inb(watchdog.sioaddr, 0x2b) & 0xcf);
+               superio_outb(watchdog.sioaddr, SIO_REG_MFUNCT3,
+                       superio_inb(watchdog.sioaddr, SIO_REG_MFUNCT3) & 0xcf);
                break;
 
        default:
@@ -711,16 +756,19 @@ static int __init f71808e_find(int sioaddr)
        case SIO_F71808_ID:
                watchdog.type = f71808fg;
                break;
+       case SIO_F71862_ID:
+               watchdog.type = f71862fg;
+               err = f71862fg_pin_configure(0); /* validate module parameter */
+               break;
+       case SIO_F71869_ID:
+               watchdog.type = f71869;
+               break;
        case SIO_F71882_ID:
                watchdog.type = f71882fg;
                break;
        case SIO_F71889_ID:
                watchdog.type = f71889fg;
                break;
-       case SIO_F71862_ID:
-               /* These have a watchdog, though it isn't implemented (yet). */
-               err = -ENOSYS;
-               goto exit;
        case SIO_F71858_ID:
                /* Confirmed (by datasheet) not to have a watchdog. */
                err = -ENODEV;
index b8838d2c67a63bae588a7a06edbe0fb4c796f822..2c6c2b4ad8bf5f8a6d1cfbd68d923fb4da2fc4cf 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *     intel TCO Watchdog Driver
  *
- *     (c) Copyright 2006-2009 Wim Van Sebroeck <wim@iguana.be>.
+ *     (c) Copyright 2006-2010 Wim Van Sebroeck <wim@iguana.be>.
  *
  *     This program is free software; you can redistribute it and/or
  *     modify it under the terms of the GNU General Public License
  *     document number 301473-002, 301474-026: 82801F (ICH6)
  *     document number 313082-001, 313075-006: 631xESB, 632xESB
  *     document number 307013-003, 307014-024: 82801G (ICH7)
+ *     document number 322896-001, 322897-001: NM10
  *     document number 313056-003, 313057-017: 82801H (ICH8)
  *     document number 316972-004, 316973-012: 82801I (ICH9)
  *     document number 319973-002, 319974-002: 82801J (ICH10)
  *     document number 322169-001, 322170-003: 5 Series, 3400 Series (PCH)
  *     document number 320066-003, 320257-008: EP80597 (IICH)
- *     document number TBD                   : Cougar Point (CPT)
+ *     document number 324645-001, 324646-001: Cougar Point (CPT)
  *     document number TBD                   : Patsburg (PBG)
+ *     document number TBD                   : DH89xxCC
  */
 
 /*
@@ -85,6 +87,7 @@ enum iTCO_chipsets {
        TCO_ICH7DH,     /* ICH7DH */
        TCO_ICH7M,      /* ICH7-M & ICH7-U */
        TCO_ICH7MDH,    /* ICH7-M DH */
+       TCO_NM10,       /* NM10 */
        TCO_ICH8,       /* ICH8 & ICH8R */
        TCO_ICH8DH,     /* ICH8DH */
        TCO_ICH8DO,     /* ICH8DO */
@@ -149,6 +152,7 @@ enum iTCO_chipsets {
        TCO_CPT31,      /* Cougar Point */
        TCO_PBG1,       /* Patsburg */
        TCO_PBG2,       /* Patsburg */
+       TCO_DH89XXCC,   /* DH89xxCC */
 };
 
 static struct {
@@ -174,6 +178,7 @@ static struct {
        {"ICH7DH", 2},
        {"ICH7-M or ICH7-U", 2},
        {"ICH7-M DH", 2},
+       {"NM10", 2},
        {"ICH8 or ICH8R", 2},
        {"ICH8DH", 2},
        {"ICH8DO", 2},
@@ -238,6 +243,7 @@ static struct {
        {"Cougar Point", 2},
        {"Patsburg", 2},
        {"Patsburg", 2},
+       {"DH89xxCC", 2},
        {NULL, 0}
 };
 
@@ -291,6 +297,7 @@ static struct pci_device_id iTCO_wdt_pci_tbl[] = {
        { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_30,          TCO_ICH7DH)},
        { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_1,           TCO_ICH7M)},
        { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_31,          TCO_ICH7MDH)},
+       { ITCO_PCI_DEVICE(0x27bc,                               TCO_NM10)},
        { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_0,           TCO_ICH8)},
        { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_2,           TCO_ICH8DH)},
        { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_3,           TCO_ICH8DO)},
@@ -355,6 +362,7 @@ static struct pci_device_id iTCO_wdt_pci_tbl[] = {
        { ITCO_PCI_DEVICE(0x1c5f,                               TCO_CPT31)},
        { ITCO_PCI_DEVICE(0x1d40,                               TCO_PBG1)},
        { ITCO_PCI_DEVICE(0x1d41,                               TCO_PBG2)},
+       { ITCO_PCI_DEVICE(0x2310,                               TCO_DH89XXCC)},
        { 0, },                 /* End of list */
 };
 MODULE_DEVICE_TABLE(pci, iTCO_wdt_pci_tbl);
index 2852bb2e3fd9c023c8d54e121d1bddf31d20e7b3..811471903e8a7390f68d46b60998307be76b2be1 100644 (file)
@@ -21,7 +21,7 @@
 #include <linux/watchdog.h>
 #include <linux/io.h>
 #include <linux/uaccess.h>
-#include <mach/timex.h>
+#include <mach/hardware.h>
 #include <mach/regs-timer.h>
 
 #define WDT_DEFAULT_TIME       5       /* seconds */
diff --git a/drivers/watchdog/m548x_wdt.c b/drivers/watchdog/m548x_wdt.c
new file mode 100644 (file)
index 0000000..cabbcfe
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * drivers/watchdog/m548x_wdt.c
+ *
+ * Watchdog driver for ColdFire MCF548x processors
+ * Copyright 2010 (c) Philippe De Muyter <phdm@macqel.be>
+ *
+ * Adapted from the IXP4xx watchdog driver, which carries these notices:
+ *
+ *  Author: Deepak Saxena <dsaxena@plexity.net>
+ *
+ *  Copyright 2004 (c) MontaVista, Software, Inc.
+ *  Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
+ *
+ * This file is licensed under  the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/ioport.h>
+#include <linux/uaccess.h>
+
+#include <asm/coldfire.h>
+#include <asm/m548xsim.h>
+#include <asm/m548xgpt.h>
+
+static int nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int heartbeat = 30;    /* (secs) Default is 0.5 minute */
+static unsigned long wdt_status;
+
+#define        WDT_IN_USE              0
+#define        WDT_OK_TO_CLOSE         1
+
+static void wdt_enable(void)
+{
+       unsigned int gms0;
+
+       /* preserve GPIO usage, if any */
+       gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0);
+       if (gms0 & MCF_GPT_GMS_TMS_GPIO)
+               gms0 &= (MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_GPIO_MASK
+                                                       | MCF_GPT_GMS_OD);
+       else
+               gms0 = MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_OD;
+       __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0);
+       __raw_writel(MCF_GPT_GCIR_PRE(heartbeat*(MCF_BUSCLK/0xffff)) |
+                       MCF_GPT_GCIR_CNT(0xffff), MCF_MBAR + MCF_GPT_GCIR0);
+       gms0 |= MCF_GPT_GMS_OCPW(0xA5) | MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE;
+       __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0);
+}
+
+static void wdt_disable(void)
+{
+       unsigned int gms0;
+
+       /* disable watchdog */
+       gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0);
+       gms0 &= ~(MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE);
+       __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0);
+}
+
+static void wdt_keepalive(void)
+{
+       unsigned int gms0;
+
+       gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0);
+       gms0 |= MCF_GPT_GMS_OCPW(0xA5);
+       __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0);
+}
+
+static int m548x_wdt_open(struct inode *inode, struct file *file)
+{
+       if (test_and_set_bit(WDT_IN_USE, &wdt_status))
+               return -EBUSY;
+
+       clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+       wdt_enable();
+       return nonseekable_open(inode, file);
+}
+
+static ssize_t m548x_wdt_write(struct file *file, const char *data,
+                                               size_t len, loff_t *ppos)
+{
+       if (len) {
+               if (!nowayout) {
+                       size_t i;
+
+                       clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+                       for (i = 0; i != len; i++) {
+                               char c;
+
+                               if (get_user(c, data + i))
+                                       return -EFAULT;
+                               if (c == 'V')
+                                       set_bit(WDT_OK_TO_CLOSE, &wdt_status);
+                       }
+               }
+               wdt_keepalive();
+       }
+       return len;
+}
+
+static const struct watchdog_info ident = {
+       .options        = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
+                               WDIOF_KEEPALIVEPING,
+       .identity       = "Coldfire M548x Watchdog",
+};
+
+static long m548x_wdt_ioctl(struct file *file, unsigned int cmd,
+                                                        unsigned long arg)
+{
+       int ret = -ENOTTY;
+       int time;
+
+       switch (cmd) {
+       case WDIOC_GETSUPPORT:
+               ret = copy_to_user((struct watchdog_info *)arg, &ident,
+                                  sizeof(ident)) ? -EFAULT : 0;
+               break;
+
+       case WDIOC_GETSTATUS:
+               ret = put_user(0, (int *)arg);
+               break;
+
+       case WDIOC_GETBOOTSTATUS:
+               ret = put_user(0, (int *)arg);
+               break;
+
+       case WDIOC_KEEPALIVE:
+               wdt_keepalive();
+               ret = 0;
+               break;
+
+       case WDIOC_SETTIMEOUT:
+               ret = get_user(time, (int *)arg);
+               if (ret)
+                       break;
+
+               if (time <= 0 || time > 30) {
+                       ret = -EINVAL;
+                       break;
+               }
+
+               heartbeat = time;
+               wdt_enable();
+               /* Fall through */
+
+       case WDIOC_GETTIMEOUT:
+               ret = put_user(heartbeat, (int *)arg);
+               break;
+       }
+       return ret;
+}
+
+static int m548x_wdt_release(struct inode *inode, struct file *file)
+{
+       if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
+               wdt_disable();
+       else {
+               printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - "
+                                       "timer will not stop\n");
+               wdt_keepalive();
+       }
+       clear_bit(WDT_IN_USE, &wdt_status);
+       clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+       return 0;
+}
+
+
+static const struct file_operations m548x_wdt_fops = {
+       .owner          = THIS_MODULE,
+       .llseek         = no_llseek,
+       .write          = m548x_wdt_write,
+       .unlocked_ioctl = m548x_wdt_ioctl,
+       .open           = m548x_wdt_open,
+       .release        = m548x_wdt_release,
+};
+
+static struct miscdevice m548x_wdt_miscdev = {
+       .minor          = WATCHDOG_MINOR,
+       .name           = "watchdog",
+       .fops           = &m548x_wdt_fops,
+};
+
+static int __init m548x_wdt_init(void)
+{
+       if (!request_mem_region(MCF_MBAR + MCF_GPT_GCIR0, 4,
+                                               "Coldfire M548x Watchdog")) {
+               printk(KERN_WARNING
+                               "Coldfire M548x Watchdog : I/O region busy\n");
+               return -EBUSY;
+       }
+       printk(KERN_INFO "ColdFire watchdog driver is loaded.\n");
+
+       return misc_register(&m548x_wdt_miscdev);
+}
+
+static void __exit m548x_wdt_exit(void)
+{
+       misc_deregister(&m548x_wdt_miscdev);
+       release_mem_region(MCF_MBAR + MCF_GPT_GCIR0, 4);
+}
+
+module_init(m548x_wdt_init);
+module_exit(m548x_wdt_exit);
+
+MODULE_AUTHOR("Philippe De Muyter <phdm@macqel.be>");
+MODULE_DESCRIPTION("Coldfire M548x Watchdog");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 30s)");
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/watchdog/nv_tco.c b/drivers/watchdog/nv_tco.c
new file mode 100644 (file)
index 0000000..1a50aa7
--- /dev/null
@@ -0,0 +1,512 @@
+/*
+ *     nv_tco 0.01:    TCO timer driver for NV chipsets
+ *
+ *     (c) Copyright 2005 Google Inc., All Rights Reserved.
+ *
+ *     Based off i8xx_tco.c:
+ *     (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights
+ *     Reserved.
+ *                             http://www.kernelconcepts.de
+ *
+ *     This program is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License
+ *     as published by the Free Software Foundation; either version
+ *     2 of the License, or (at your option) any later version.
+ *
+ *     TCO timer driver for NV chipsets
+ *     based on softdog.c by Alan Cox <alan@redhat.com>
+ */
+
+/*
+ *     Includes, defines, variables, module parameters, ...
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#include "nv_tco.h"
+
+/* Module and version information */
+#define TCO_VERSION "0.01"
+#define TCO_MODULE_NAME "NV_TCO"
+#define TCO_DRIVER_NAME   TCO_MODULE_NAME ", v" TCO_VERSION
+#define PFX TCO_MODULE_NAME ": "
+
+/* internal variables */
+static unsigned int tcobase;
+static DEFINE_SPINLOCK(tco_lock);      /* Guards the hardware */
+static unsigned long timer_alive;
+static char tco_expect_close;
+static struct pci_dev *tco_pci;
+
+/* the watchdog platform device */
+static struct platform_device *nv_tco_platform_device;
+
+/* module parameters */
+#define WATCHDOG_HEARTBEAT 30  /* 30 sec default heartbeat (2<heartbeat<39) */
+static int heartbeat = WATCHDOG_HEARTBEAT;  /* in seconds */
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39, "
+                           "default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+static int nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"
+               " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+/*
+ * Some TCO specific functions
+ */
+static inline unsigned char seconds_to_ticks(int seconds)
+{
+       /* the internal timer is stored as ticks which decrement
+        * every 0.6 seconds */
+       return (seconds * 10) / 6;
+}
+
+static void tco_timer_start(void)
+{
+       u32 val;
+       unsigned long flags;
+
+       spin_lock_irqsave(&tco_lock, flags);
+       val = inl(TCO_CNT(tcobase));
+       val &= ~TCO_CNT_TCOHALT;
+       outl(val, TCO_CNT(tcobase));
+       spin_unlock_irqrestore(&tco_lock, flags);
+}
+
+static void tco_timer_stop(void)
+{
+       u32 val;
+       unsigned long flags;
+
+       spin_lock_irqsave(&tco_lock, flags);
+       val = inl(TCO_CNT(tcobase));
+       val |= TCO_CNT_TCOHALT;
+       outl(val, TCO_CNT(tcobase));
+       spin_unlock_irqrestore(&tco_lock, flags);
+}
+
+static void tco_timer_keepalive(void)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&tco_lock, flags);
+       outb(0x01, TCO_RLD(tcobase));
+       spin_unlock_irqrestore(&tco_lock, flags);
+}
+
+static int tco_timer_set_heartbeat(int t)
+{
+       int ret = 0;
+       unsigned char tmrval;
+       unsigned long flags;
+       u8 val;
+
+       /*
+        * note seconds_to_ticks(t) > t, so if t > 0x3f, so is
+        * tmrval=seconds_to_ticks(t).  Check that the count in seconds isn't
+        * out of range on it's own (to avoid overflow in tmrval).
+        */
+       if (t < 0 || t > 0x3f)
+               return -EINVAL;
+       tmrval = seconds_to_ticks(t);
+
+       /* "Values of 0h-3h are ignored and should not be attempted" */
+       if (tmrval > 0x3f || tmrval < 0x04)
+               return -EINVAL;
+
+       /* Write new heartbeat to watchdog */
+       spin_lock_irqsave(&tco_lock, flags);
+       val = inb(TCO_TMR(tcobase));
+       val &= 0xc0;
+       val |= tmrval;
+       outb(val, TCO_TMR(tcobase));
+       val = inb(TCO_TMR(tcobase));
+
+       if ((val & 0x3f) != tmrval)
+               ret = -EINVAL;
+       spin_unlock_irqrestore(&tco_lock, flags);
+
+       if (ret)
+               return ret;
+
+       heartbeat = t;
+       return 0;
+}
+
+/*
+ *     /dev/watchdog handling
+ */
+
+static int nv_tco_open(struct inode *inode, struct file *file)
+{
+       /* /dev/watchdog can only be opened once */
+       if (test_and_set_bit(0, &timer_alive))
+               return -EBUSY;
+
+       /* Reload and activate timer */
+       tco_timer_keepalive();
+       tco_timer_start();
+       return nonseekable_open(inode, file);
+}
+
+static int nv_tco_release(struct inode *inode, struct file *file)
+{
+       /* Shut off the timer */
+       if (tco_expect_close == 42) {
+               tco_timer_stop();
+       } else {
+               printk(KERN_CRIT PFX "Unexpected close, not stopping "
+                      "watchdog!\n");
+               tco_timer_keepalive();
+       }
+       clear_bit(0, &timer_alive);
+       tco_expect_close = 0;
+       return 0;
+}
+
+static ssize_t nv_tco_write(struct file *file, const char __user *data,
+                           size_t len, loff_t *ppos)
+{
+       /* See if we got the magic character 'V' and reload the timer */
+       if (len) {
+               if (!nowayout) {
+                       size_t i;
+
+                       /*
+                        * note: just in case someone wrote the magic character
+                        * five months ago...
+                        */
+                       tco_expect_close = 0;
+
+                       /*
+                        * scan to see whether or not we got the magic
+                        * character
+                        */
+                       for (i = 0; i != len; i++) {
+                               char c;
+                               if (get_user(c, data + i))
+                                       return -EFAULT;
+                               if (c == 'V')
+                                       tco_expect_close = 42;
+                       }
+               }
+
+               /* someone wrote to us, we should reload the timer */
+               tco_timer_keepalive();
+       }
+       return len;
+}
+
+static long nv_tco_ioctl(struct file *file, unsigned int cmd,
+                        unsigned long arg)
+{
+       int new_options, retval = -EINVAL;
+       int new_heartbeat;
+       void __user *argp = (void __user *)arg;
+       int __user *p = argp;
+       static const struct watchdog_info ident = {
+               .options =              WDIOF_SETTIMEOUT |
+                                       WDIOF_KEEPALIVEPING |
+                                       WDIOF_MAGICCLOSE,
+               .firmware_version =     0,
+               .identity =             TCO_MODULE_NAME,
+       };
+
+       switch (cmd) {
+       case WDIOC_GETSUPPORT:
+               return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+       case WDIOC_GETSTATUS:
+       case WDIOC_GETBOOTSTATUS:
+               return put_user(0, p);
+       case WDIOC_SETOPTIONS:
+               if (get_user(new_options, p))
+                       return -EFAULT;
+               if (new_options & WDIOS_DISABLECARD) {
+                       tco_timer_stop();
+                       retval = 0;
+               }
+               if (new_options & WDIOS_ENABLECARD) {
+                       tco_timer_keepalive();
+                       tco_timer_start();
+                       retval = 0;
+               }
+               return retval;
+       case WDIOC_KEEPALIVE:
+               tco_timer_keepalive();
+               return 0;
+       case WDIOC_SETTIMEOUT:
+               if (get_user(new_heartbeat, p))
+                       return -EFAULT;
+               if (tco_timer_set_heartbeat(new_heartbeat))
+                       return -EINVAL;
+               tco_timer_keepalive();
+               /* Fall through */
+       case WDIOC_GETTIMEOUT:
+               return put_user(heartbeat, p);
+       default:
+               return -ENOTTY;
+       }
+}
+
+/*
+ *     Kernel Interfaces
+ */
+
+static const struct file_operations nv_tco_fops = {
+       .owner =                THIS_MODULE,
+       .llseek =               no_llseek,
+       .write =                nv_tco_write,
+       .unlocked_ioctl =       nv_tco_ioctl,
+       .open =                 nv_tco_open,
+       .release =              nv_tco_release,
+};
+
+static struct miscdevice nv_tco_miscdev = {
+       .minor =        WATCHDOG_MINOR,
+       .name =         "watchdog",
+       .fops =         &nv_tco_fops,
+};
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE.  We do not actually
+ * register a pci_driver, because someone else might one day
+ * want to register another driver on the same PCI id.
+ */
+static struct pci_device_id tco_pci_tbl[] = {
+       { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_SMBUS,
+         PCI_ANY_ID, PCI_ANY_ID, },
+       { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SMBUS,
+         PCI_ANY_ID, PCI_ANY_ID, },
+       { 0, },                 /* End of list */
+};
+MODULE_DEVICE_TABLE(pci, tco_pci_tbl);
+
+/*
+ *     Init & exit routines
+ */
+
+static unsigned char __init nv_tco_getdevice(void)
+{
+       struct pci_dev *dev = NULL;
+       u32 val;
+
+       /* Find the PCI device */
+       for_each_pci_dev(dev) {
+               if (pci_match_id(tco_pci_tbl, dev) != NULL) {
+                       tco_pci = dev;
+                       break;
+               }
+       }
+
+       if (!tco_pci)
+               return 0;
+
+       /* Find the base io port */
+       pci_read_config_dword(tco_pci, 0x64, &val);
+       val &= 0xffff;
+       if (val == 0x0001 || val == 0x0000) {
+               /* Something is wrong here, bar isn't setup */
+               printk(KERN_ERR PFX "failed to get tcobase address\n");
+               return 0;
+       }
+       val &= 0xff00;
+       tcobase = val + 0x40;
+
+       if (!request_region(tcobase, 0x10, "NV TCO")) {
+               printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+                      tcobase);
+               return 0;
+       }
+
+       /* Set a reasonable heartbeat before we stop the timer */
+       tco_timer_set_heartbeat(30);
+
+       /*
+        * Stop the TCO before we change anything so we don't race with
+        * a zeroed timer.
+        */
+       tco_timer_keepalive();
+       tco_timer_stop();
+
+       /* Disable SMI caused by TCO */
+       if (!request_region(MCP51_SMI_EN(tcobase), 4, "NV TCO")) {
+               printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+                      MCP51_SMI_EN(tcobase));
+               goto out;
+       }
+       val = inl(MCP51_SMI_EN(tcobase));
+       val &= ~MCP51_SMI_EN_TCO;
+       outl(val, MCP51_SMI_EN(tcobase));
+       val = inl(MCP51_SMI_EN(tcobase));
+       release_region(MCP51_SMI_EN(tcobase), 4);
+       if (val & MCP51_SMI_EN_TCO) {
+               printk(KERN_ERR PFX "Could not disable SMI caused by TCO\n");
+               goto out;
+       }
+
+       /* Check chipset's NO_REBOOT bit */
+       pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
+       val |= MCP51_SMBUS_SETUP_B_TCO_REBOOT;
+       pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
+       pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
+       if (!(val & MCP51_SMBUS_SETUP_B_TCO_REBOOT)) {
+               printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot "
+                      "disabled by hardware\n");
+               goto out;
+       }
+
+       return 1;
+out:
+       release_region(tcobase, 0x10);
+       return 0;
+}
+
+static int __devinit nv_tco_init(struct platform_device *dev)
+{
+       int ret;
+
+       /* Check whether or not the hardware watchdog is there */
+       if (!nv_tco_getdevice())
+               return -ENODEV;
+
+       /* Check to see if last reboot was due to watchdog timeout */
+       printk(KERN_INFO PFX "Watchdog reboot %sdetected.\n",
+              inl(TCO_STS(tcobase)) & TCO_STS_TCO2TO_STS ? "" : "not ");
+
+       /* Clear out the old status */
+       outl(TCO_STS_RESET, TCO_STS(tcobase));
+
+       /*
+        * Check that the heartbeat value is within it's range.
+        * If not, reset to the default.
+        */
+       if (tco_timer_set_heartbeat(heartbeat)) {
+               heartbeat = WATCHDOG_HEARTBEAT;
+               tco_timer_set_heartbeat(heartbeat);
+               printk(KERN_INFO PFX "heartbeat value must be 2<heartbeat<39, "
+                      "using %d\n", heartbeat);
+       }
+
+       ret = misc_register(&nv_tco_miscdev);
+       if (ret != 0) {
+               printk(KERN_ERR PFX "cannot register miscdev on minor=%d "
+                      "(err=%d)\n", WATCHDOG_MINOR, ret);
+               goto unreg_region;
+       }
+
+       clear_bit(0, &timer_alive);
+
+       tco_timer_stop();
+
+       printk(KERN_INFO PFX "initialized (0x%04x). heartbeat=%d sec "
+              "(nowayout=%d)\n", tcobase, heartbeat, nowayout);
+
+       return 0;
+
+unreg_region:
+       release_region(tcobase, 0x10);
+       return ret;
+}
+
+static void __devexit nv_tco_cleanup(void)
+{
+       u32 val;
+
+       /* Stop the timer before we leave */
+       if (!nowayout)
+               tco_timer_stop();
+
+       /* Set the NO_REBOOT bit to prevent later reboots, just for sure */
+       pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
+       val &= ~MCP51_SMBUS_SETUP_B_TCO_REBOOT;
+       pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
+       pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
+       if (val & MCP51_SMBUS_SETUP_B_TCO_REBOOT) {
+               printk(KERN_CRIT PFX "Couldn't unset REBOOT bit.  Machine may "
+                      "soon reset\n");
+       }
+
+       /* Deregister */
+       misc_deregister(&nv_tco_miscdev);
+       release_region(tcobase, 0x10);
+}
+
+static int __devexit nv_tco_remove(struct platform_device *dev)
+{
+       if (tcobase)
+               nv_tco_cleanup();
+
+       return 0;
+}
+
+static void nv_tco_shutdown(struct platform_device *dev)
+{
+       tco_timer_stop();
+}
+
+static struct platform_driver nv_tco_driver = {
+       .probe          = nv_tco_init,
+       .remove         = __devexit_p(nv_tco_remove),
+       .shutdown       = nv_tco_shutdown,
+       .driver         = {
+               .owner  = THIS_MODULE,
+               .name   = TCO_MODULE_NAME,
+       },
+};
+
+static int __init nv_tco_init_module(void)
+{
+       int err;
+
+       printk(KERN_INFO PFX "NV TCO WatchDog Timer Driver v%s\n",
+              TCO_VERSION);
+
+       err = platform_driver_register(&nv_tco_driver);
+       if (err)
+               return err;
+
+       nv_tco_platform_device = platform_device_register_simple(
+                                       TCO_MODULE_NAME, -1, NULL, 0);
+       if (IS_ERR(nv_tco_platform_device)) {
+               err = PTR_ERR(nv_tco_platform_device);
+               goto unreg_platform_driver;
+       }
+
+       return 0;
+
+unreg_platform_driver:
+       platform_driver_unregister(&nv_tco_driver);
+       return err;
+}
+
+static void __exit nv_tco_cleanup_module(void)
+{
+       platform_device_unregister(nv_tco_platform_device);
+       platform_driver_unregister(&nv_tco_driver);
+       printk(KERN_INFO PFX "NV TCO Watchdog Module Unloaded.\n");
+}
+
+module_init(nv_tco_init_module);
+module_exit(nv_tco_cleanup_module);
+
+MODULE_AUTHOR("Mike Waychison");
+MODULE_DESCRIPTION("TCO timer driver for NV chipsets");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/watchdog/nv_tco.h b/drivers/watchdog/nv_tco.h
new file mode 100644 (file)
index 0000000..c2d1d04
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *     nv_tco: TCO timer driver for nVidia chipsets.
+ *
+ *     (c) Copyright 2005 Google Inc., All Rights Reserved.
+ *
+ *     Supported Chipsets:
+ *             - MCP51/MCP55
+ *
+ *     (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights
+ *     Reserved.
+ *                             http://www.kernelconcepts.de
+ *
+ *     This program is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License
+ *     as published by the Free Software Foundation; either version
+ *     2 of the License, or (at your option) any later version.
+ *
+ *     Neither kernel concepts nor Nils Faerber admit liability nor provide
+ *     warranty for any of this software. This material is provided
+ *     "AS-IS" and at no charge.
+ *
+ *     (c) Copyright 2000      kernel concepts <nils@kernelconcepts.de>
+ *                             developed for
+ *                              Jentro AG, Haar/Munich (Germany)
+ *
+ *     TCO timer driver for NV chipsets
+ *     based on softdog.c by Alan Cox <alan@redhat.com>
+ */
+
+/*
+ * Some address definitions for the TCO
+ */
+
+#define TCO_RLD(base)  ((base) + 0x00) /* TCO Timer Reload and Current Value */
+#define TCO_TMR(base)  ((base) + 0x01) /* TCO Timer Initial Value      */
+
+#define TCO_STS(base)  ((base) + 0x04) /* TCO Status Register          */
+/*
+ * TCO Boot Status bit: set on TCO reset, reset by software or standby
+ * power-good (survives reboots), unfortunately this bit is never
+ * set.
+ */
+#  define TCO_STS_BOOT_STS     (1 << 9)
+/*
+ * first and 2nd timeout status bits, these also survive a warm boot,
+ * and they work, so we use them.
+ */
+#  define TCO_STS_TCO_INT_STS  (1 << 1)
+#  define TCO_STS_TCO2TO_STS   (1 << 10)
+#  define TCO_STS_RESET                (TCO_STS_BOOT_STS | TCO_STS_TCO2TO_STS | \
+                                TCO_STS_TCO_INT_STS)
+
+#define TCO_CNT(base)  ((base) + 0x08) /* TCO Control Register */
+#  define TCO_CNT_TCOHALT      (1 << 12)
+
+#define MCP51_SMBUS_SETUP_B 0xe8
+#  define MCP51_SMBUS_SETUP_B_TCO_REBOOT (1 << 25)
+
+/*
+ * The SMI_EN register is at the base io address + 0x04,
+ * while TCOBASE is + 0x40.
+ */
+#define MCP51_SMI_EN(base)     ((base) - 0x40 + 0x04)
+#  define MCP51_SMI_EN_TCO     ((1 << 4) | (1 << 5))
diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c
new file mode 100644 (file)
index 0000000..8083728
--- /dev/null
@@ -0,0 +1,480 @@
+/*
+ *     sp5100_tco :    TCO timer driver for sp5100 chipsets
+ *
+ *     (c) Copyright 2009 Google Inc., All Rights Reserved.
+ *
+ *     Based on i8xx_tco.c:
+ *     (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights
+ *     Reserved.
+ *                             http://www.kernelconcepts.de
+ *
+ *     This program is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License
+ *     as published by the Free Software Foundation; either version
+ *     2 of the License, or (at your option) any later version.
+ *
+ *     See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide"
+ */
+
+/*
+ *     Includes, defines, variables, module parameters, ...
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#include "sp5100_tco.h"
+
+/* Module and version information */
+#define TCO_VERSION "0.01"
+#define TCO_MODULE_NAME "SP5100 TCO timer"
+#define TCO_DRIVER_NAME   TCO_MODULE_NAME ", v" TCO_VERSION
+#define PFX TCO_MODULE_NAME ": "
+
+/* internal variables */
+static void __iomem *tcobase;
+static unsigned int pm_iobase;
+static DEFINE_SPINLOCK(tco_lock);      /* Guards the hardware */
+static unsigned long timer_alive;
+static char tco_expect_close;
+static struct pci_dev *sp5100_tco_pci;
+
+/* the watchdog platform device */
+static struct platform_device *sp5100_tco_platform_device;
+
+/* module parameters */
+
+#define WATCHDOG_HEARTBEAT 60  /* 60 sec default heartbeat. */
+static int heartbeat = WATCHDOG_HEARTBEAT;  /* in seconds */
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
+                __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+static int nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"
+               " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+/*
+ * Some TCO specific functions
+ */
+static void tco_timer_start(void)
+{
+       u32 val;
+       unsigned long flags;
+
+       spin_lock_irqsave(&tco_lock, flags);
+       val = readl(SP5100_WDT_CONTROL(tcobase));
+       val |= SP5100_WDT_START_STOP_BIT;
+       writel(val, SP5100_WDT_CONTROL(tcobase));
+       spin_unlock_irqrestore(&tco_lock, flags);
+}
+
+static void tco_timer_stop(void)
+{
+       u32 val;
+       unsigned long flags;
+
+       spin_lock_irqsave(&tco_lock, flags);
+       val = readl(SP5100_WDT_CONTROL(tcobase));
+       val &= ~SP5100_WDT_START_STOP_BIT;
+       writel(val, SP5100_WDT_CONTROL(tcobase));
+       spin_unlock_irqrestore(&tco_lock, flags);
+}
+
+static void tco_timer_keepalive(void)
+{
+       u32 val;
+       unsigned long flags;
+
+       spin_lock_irqsave(&tco_lock, flags);
+       val = readl(SP5100_WDT_CONTROL(tcobase));
+       val |= SP5100_WDT_TRIGGER_BIT;
+       writel(val, SP5100_WDT_CONTROL(tcobase));
+       spin_unlock_irqrestore(&tco_lock, flags);
+}
+
+static int tco_timer_set_heartbeat(int t)
+{
+       unsigned long flags;
+
+       if (t < 0 || t > 0xffff)
+               return -EINVAL;
+
+       /* Write new heartbeat to watchdog */
+       spin_lock_irqsave(&tco_lock, flags);
+       writel(t, SP5100_WDT_COUNT(tcobase));
+       spin_unlock_irqrestore(&tco_lock, flags);
+
+       heartbeat = t;
+       return 0;
+}
+
+/*
+ *     /dev/watchdog handling
+ */
+
+static int sp5100_tco_open(struct inode *inode, struct file *file)
+{
+       /* /dev/watchdog can only be opened once */
+       if (test_and_set_bit(0, &timer_alive))
+               return -EBUSY;
+
+       /* Reload and activate timer */
+       tco_timer_start();
+       tco_timer_keepalive();
+       return nonseekable_open(inode, file);
+}
+
+static int sp5100_tco_release(struct inode *inode, struct file *file)
+{
+       /* Shut off the timer. */
+       if (tco_expect_close == 42) {
+               tco_timer_stop();
+       } else {
+               printk(KERN_CRIT PFX
+                       "Unexpected close, not stopping watchdog!\n");
+               tco_timer_keepalive();
+       }
+       clear_bit(0, &timer_alive);
+       tco_expect_close = 0;
+       return 0;
+}
+
+static ssize_t sp5100_tco_write(struct file *file, const char __user *data,
+                               size_t len, loff_t *ppos)
+{
+       /* See if we got the magic character 'V' and reload the timer */
+       if (len) {
+               if (!nowayout) {
+                       size_t i;
+
+                       /* note: just in case someone wrote the magic character
+                        * five months ago... */
+                       tco_expect_close = 0;
+
+                       /* scan to see whether or not we got the magic character
+                        */
+                       for (i = 0; i != len; i++) {
+                               char c;
+                               if (get_user(c, data + i))
+                                       return -EFAULT;
+                               if (c == 'V')
+                                       tco_expect_close = 42;
+                       }
+               }
+
+               /* someone wrote to us, we should reload the timer */
+               tco_timer_keepalive();
+       }
+       return len;
+}
+
+static long sp5100_tco_ioctl(struct file *file, unsigned int cmd,
+                            unsigned long arg)
+{
+       int new_options, retval = -EINVAL;
+       int new_heartbeat;
+       void __user *argp = (void __user *)arg;
+       int __user *p = argp;
+       static const struct watchdog_info ident = {
+               .options =              WDIOF_SETTIMEOUT |
+                                       WDIOF_KEEPALIVEPING |
+                                       WDIOF_MAGICCLOSE,
+               .firmware_version =     0,
+               .identity =             TCO_MODULE_NAME,
+       };
+
+       switch (cmd) {
+       case WDIOC_GETSUPPORT:
+               return copy_to_user(argp, &ident,
+                       sizeof(ident)) ? -EFAULT : 0;
+       case WDIOC_GETSTATUS:
+       case WDIOC_GETBOOTSTATUS:
+               return put_user(0, p);
+       case WDIOC_SETOPTIONS:
+               if (get_user(new_options, p))
+                       return -EFAULT;
+               if (new_options & WDIOS_DISABLECARD) {
+                       tco_timer_stop();
+                       retval = 0;
+               }
+               if (new_options & WDIOS_ENABLECARD) {
+                       tco_timer_start();
+                       tco_timer_keepalive();
+                       retval = 0;
+               }
+               return retval;
+       case WDIOC_KEEPALIVE:
+               tco_timer_keepalive();
+               return 0;
+       case WDIOC_SETTIMEOUT:
+               if (get_user(new_heartbeat, p))
+                       return -EFAULT;
+               if (tco_timer_set_heartbeat(new_heartbeat))
+                       return -EINVAL;
+               tco_timer_keepalive();
+               /* Fall through */
+       case WDIOC_GETTIMEOUT:
+               return put_user(heartbeat, p);
+       default:
+               return -ENOTTY;
+       }
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations sp5100_tco_fops = {
+       .owner =                THIS_MODULE,
+       .llseek =               no_llseek,
+       .write =                sp5100_tco_write,
+       .unlocked_ioctl =       sp5100_tco_ioctl,
+       .open =                 sp5100_tco_open,
+       .release =              sp5100_tco_release,
+};
+
+static struct miscdevice sp5100_tco_miscdev = {
+       .minor =        WATCHDOG_MINOR,
+       .name =         "watchdog",
+       .fops =         &sp5100_tco_fops,
+};
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE.  We do not actually
+ * register a pci_driver, because someone else might
+ * want to register another driver on the same PCI id.
+ */
+static struct pci_device_id sp5100_tco_pci_tbl[] = {
+       { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID,
+         PCI_ANY_ID, },
+       { 0, },                 /* End of list */
+};
+MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl);
+
+/*
+ * Init & exit routines
+ */
+
+static unsigned char __devinit sp5100_tco_setupdevice(void)
+{
+       struct pci_dev *dev = NULL;
+       u32 val;
+
+       /* Match the PCI device */
+       for_each_pci_dev(dev) {
+               if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) {
+                       sp5100_tco_pci = dev;
+                       break;
+               }
+       }
+
+       if (!sp5100_tco_pci)
+               return 0;
+
+       /* Request the IO ports used by this driver */
+       pm_iobase = SP5100_IO_PM_INDEX_REG;
+       if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, "SP5100 TCO")) {
+               printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+                       pm_iobase);
+               goto exit;
+       }
+
+       /* Find the watchdog base address. */
+       outb(SP5100_PM_WATCHDOG_BASE3, SP5100_IO_PM_INDEX_REG);
+       val = inb(SP5100_IO_PM_DATA_REG);
+       outb(SP5100_PM_WATCHDOG_BASE2, SP5100_IO_PM_INDEX_REG);
+       val = val << 8 | inb(SP5100_IO_PM_DATA_REG);
+       outb(SP5100_PM_WATCHDOG_BASE1, SP5100_IO_PM_INDEX_REG);
+       val = val << 8 | inb(SP5100_IO_PM_DATA_REG);
+       outb(SP5100_PM_WATCHDOG_BASE0, SP5100_IO_PM_INDEX_REG);
+       /* Low three bits of BASE0 are reserved. */
+       val = val << 8 | (inb(SP5100_IO_PM_DATA_REG) & 0xf8);
+
+       tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE);
+       if (tcobase == 0) {
+               printk(KERN_ERR PFX "failed to get tcobase address\n");
+               goto unreg_region;
+       }
+
+       /* Enable watchdog decode bit */
+       pci_read_config_dword(sp5100_tco_pci,
+                             SP5100_PCI_WATCHDOG_MISC_REG,
+                             &val);
+
+       val |= SP5100_PCI_WATCHDOG_DECODE_EN;
+
+       pci_write_config_dword(sp5100_tco_pci,
+                              SP5100_PCI_WATCHDOG_MISC_REG,
+                              val);
+
+       /* Enable Watchdog timer and set the resolution to 1 sec. */
+       outb(SP5100_PM_WATCHDOG_CONTROL, SP5100_IO_PM_INDEX_REG);
+       val = inb(SP5100_IO_PM_DATA_REG);
+       val |= SP5100_PM_WATCHDOG_SECOND_RES;
+       val &= ~SP5100_PM_WATCHDOG_DISABLE;
+       outb(val, SP5100_IO_PM_DATA_REG);
+
+       /* Check that the watchdog action is set to reset the system. */
+       val = readl(SP5100_WDT_CONTROL(tcobase));
+       val &= ~SP5100_PM_WATCHDOG_ACTION_RESET;
+       writel(val, SP5100_WDT_CONTROL(tcobase));
+
+       /* Set a reasonable heartbeat before we stop the timer */
+       tco_timer_set_heartbeat(heartbeat);
+
+       /*
+        * Stop the TCO before we change anything so we don't race with
+        * a zeroed timer.
+        */
+       tco_timer_stop();
+
+       /* Done */
+       return 1;
+
+       iounmap(tcobase);
+unreg_region:
+       release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE);
+exit:
+       return 0;
+}
+
+static int __devinit sp5100_tco_init(struct platform_device *dev)
+{
+       int ret;
+       u32 val;
+
+       /* Check whether or not the hardware watchdog is there. If found, then
+        * set it up.
+        */
+       if (!sp5100_tco_setupdevice())
+               return -ENODEV;
+
+       /* Check to see if last reboot was due to watchdog timeout */
+       printk(KERN_INFO PFX "Watchdog reboot %sdetected.\n",
+              readl(SP5100_WDT_CONTROL(tcobase)) & SP5100_PM_WATCHDOG_FIRED ?
+                     "" : "not ");
+
+       /* Clear out the old status */
+       val = readl(SP5100_WDT_CONTROL(tcobase));
+       val &= ~SP5100_PM_WATCHDOG_FIRED;
+       writel(val, SP5100_WDT_CONTROL(tcobase));
+
+       /*
+        * Check that the heartbeat value is within it's range.
+        * If not, reset to the default.
+        */
+       if (tco_timer_set_heartbeat(heartbeat)) {
+               heartbeat = WATCHDOG_HEARTBEAT;
+               tco_timer_set_heartbeat(heartbeat);
+       }
+
+       ret = misc_register(&sp5100_tco_miscdev);
+       if (ret != 0) {
+               printk(KERN_ERR PFX "cannot register miscdev on minor="
+                      "%d (err=%d)\n",
+                      WATCHDOG_MINOR, ret);
+               goto exit;
+       }
+
+       clear_bit(0, &timer_alive);
+
+       printk(KERN_INFO PFX "initialized (0x%p). heartbeat=%d sec"
+               " (nowayout=%d)\n",
+               tcobase, heartbeat, nowayout);
+
+       return 0;
+
+exit:
+       iounmap(tcobase);
+       release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE);
+       return ret;
+}
+
+static void __devexit sp5100_tco_cleanup(void)
+{
+       /* Stop the timer before we leave */
+       if (!nowayout)
+               tco_timer_stop();
+
+       /* Deregister */
+       misc_deregister(&sp5100_tco_miscdev);
+       iounmap(tcobase);
+       release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE);
+}
+
+static int __devexit sp5100_tco_remove(struct platform_device *dev)
+{
+       if (tcobase)
+               sp5100_tco_cleanup();
+       return 0;
+}
+
+static void sp5100_tco_shutdown(struct platform_device *dev)
+{
+       tco_timer_stop();
+}
+
+static struct platform_driver sp5100_tco_driver = {
+       .probe          = sp5100_tco_init,
+       .remove         = __devexit_p(sp5100_tco_remove),
+       .shutdown       = sp5100_tco_shutdown,
+       .driver         = {
+               .owner  = THIS_MODULE,
+               .name   = TCO_MODULE_NAME,
+       },
+};
+
+static int __init sp5100_tco_init_module(void)
+{
+       int err;
+
+       printk(KERN_INFO PFX "SP5100 TCO WatchDog Timer Driver v%s\n",
+              TCO_VERSION);
+
+       err = platform_driver_register(&sp5100_tco_driver);
+       if (err)
+               return err;
+
+       sp5100_tco_platform_device = platform_device_register_simple(
+                                       TCO_MODULE_NAME, -1, NULL, 0);
+       if (IS_ERR(sp5100_tco_platform_device)) {
+               err = PTR_ERR(sp5100_tco_platform_device);
+               goto unreg_platform_driver;
+       }
+
+       return 0;
+
+unreg_platform_driver:
+       platform_driver_unregister(&sp5100_tco_driver);
+       return err;
+}
+
+static void __exit sp5100_tco_cleanup_module(void)
+{
+       platform_device_unregister(sp5100_tco_platform_device);
+       platform_driver_unregister(&sp5100_tco_driver);
+       printk(KERN_INFO PFX "SP5100 TCO Watchdog Module Unloaded.\n");
+}
+
+module_init(sp5100_tco_init_module);
+module_exit(sp5100_tco_cleanup_module);
+
+MODULE_AUTHOR("Priyanka Gupta");
+MODULE_DESCRIPTION("TCO timer driver for SP5100 chipset");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h
new file mode 100644 (file)
index 0000000..a5a16cc
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ *     sp5100_tco:     TCO timer driver for sp5100 chipsets.
+ *
+ *     (c) Copyright 2009 Google Inc., All Rights Reserved.
+ *
+ *     TCO timer driver for sp5100 chipsets
+ */
+
+/*
+ * Some address definitions for the Watchdog
+ */
+
+#define SP5100_WDT_MEM_MAP_SIZE                0x08
+#define SP5100_WDT_CONTROL(base)       ((base) + 0x00) /* Watchdog Control */
+#define SP5100_WDT_COUNT(base)         ((base) + 0x04) /* Watchdog Count */
+
+#define SP5100_WDT_START_STOP_BIT      1
+#define SP5100_WDT_TRIGGER_BIT         (1 << 7)
+
+#define SP5100_PCI_WATCHDOG_MISC_REG   0x41
+#define SP5100_PCI_WATCHDOG_DECODE_EN  (1 << 3)
+
+#define SP5100_PM_IOPORTS_SIZE         0x02
+
+/* These two IO registers are hardcoded and there doesn't seem to be a way to
+ * read them from a register.
+ */
+#define SP5100_IO_PM_INDEX_REG         0xCD6
+#define SP5100_IO_PM_DATA_REG          0xCD7
+
+#define SP5100_PM_WATCHDOG_CONTROL     0x69
+#define SP5100_PM_WATCHDOG_BASE0       0x6C
+#define SP5100_PM_WATCHDOG_BASE1       0x6D
+#define SP5100_PM_WATCHDOG_BASE2       0x6E
+#define SP5100_PM_WATCHDOG_BASE3       0x6F
+
+#define SP5100_PM_WATCHDOG_FIRED       (1 << 1)
+#define SP5100_PM_WATCHDOG_ACTION_RESET        (1 << 2)
+
+#define SP5100_PM_WATCHDOG_DISABLE     1
+#define SP5100_PM_WATCHDOG_SECOND_RES  (3 << 1)
index 0f5288df0091730299f59bb9685962492a2dca89..e5c91d4404edfdb244e6a70c3e87edf63b163a63 100644 (file)
@@ -42,7 +42,7 @@
 
 #include <asm/system.h>
 
-#define WATCHDOG_NAME "w83627hf/thf/hg WDT"
+#define WATCHDOG_NAME "w83627hf/thf/hg/dhg WDT"
 #define PFX WATCHDOG_NAME ": "
 #define WATCHDOG_TIMEOUT 60            /* 60 sec default timeout */
 
@@ -89,7 +89,7 @@ static void w83627hf_select_wd_register(void)
                c = ((inb_p(WDT_EFDR) & 0xf7) | 0x04); /* select WDT0 */
                outb_p(0x2b, WDT_EFER);
                outb_p(c, WDT_EFDR);    /* set GPIO3 to WDT0 */
-       } else if (c == 0x88) { /* W83627EHF */
+       } else if (c == 0x88 || c == 0xa0) {    /* W83627EHF / W83627DHG */
                outb_p(0x2d, WDT_EFER); /* select GPIO5 */
                c = inb_p(WDT_EFDR) & ~0x01; /* PIN77 -> WDT0# */
                outb_p(0x2d, WDT_EFER);
@@ -129,6 +129,8 @@ static void w83627hf_init(void)
        t = inb_p(WDT_EFDR);      /* read CRF5 */
        t &= ~0x0C;               /* set second mode & disable keyboard
                                    turning off watchdog */
+       t |= 0x02;                /* enable the WDTO# output low pulse
+                                   to the KBRST# pin (PIN60) */
        outb_p(t, WDT_EFDR);    /* Write back to CRF5 */
 
        outb_p(0xF7, WDT_EFER); /* Select CRF7 */
@@ -321,7 +323,7 @@ static int __init wdt_init(void)
 {
        int ret;
 
-       printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF/THF/HG Super I/O chip initialising.\n");
+       printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF/THF/HG/DHG Super I/O chip initialising.\n");
 
        if (wdt_set_heartbeat(timeout)) {
                wdt_set_heartbeat(WATCHDOG_TIMEOUT);