watchdog: iTCO_wdt: Add support for v3 silicon
authorPeter Tyser <ptyser@xes-inc.com>
Mon, 10 Mar 2014 21:34:55 +0000 (16:34 -0500)
committerLee Jones <lee.jones@linaro.org>
Wed, 19 Mar 2014 09:00:03 +0000 (09:00 +0000)
Some new Atom's, eg Avoton and Bay Trail, have slightly different iTCO
functionality:
- The watchdog timer ticks at 1 second instead of .6 seconds

- Some 8 and 16-bit registers were combined into 32-bit registers

- Some registers were removed (DAT_IN, DAT_OUT, MESSAGE)

- The BOOT_STS field in TCO_STS was removed

- The NO_REBOOT bit is in the PMC area instead of GCS

Update the driver to support the above changes and bump the version to
1.11.

Signed-off-by: Peter Tyser <ptyser@xes-inc.com>
Tested-by: Rajat Jain <rajatjain@juniper.net>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
drivers/watchdog/iTCO_wdt.c

index 04f8af65acfd568b486c68460ff5227a841cf9a2..6d5928ffe604056a24157a3dc12a6d4df7708622 100644 (file)
@@ -48,7 +48,7 @@
 
 /* Module and version information */
 #define DRV_NAME       "iTCO_wdt"
-#define DRV_VERSION    "1.10"
+#define DRV_VERSION    "1.11"
 
 /* Includes */
 #include <linux/module.h>              /* For module specific items */
@@ -92,9 +92,12 @@ static struct {              /* this is private data for the iTCO_wdt device */
        unsigned int iTCO_version;
        struct resource *tco_res;
        struct resource *smi_res;
-       struct resource *gcs_res;
-       /* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2)*/
-       unsigned long __iomem *gcs;
+       /*
+        * NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2),
+        * or memory-mapped PMC register bit 4 (TCO version 3).
+        */
+       struct resource *gcs_pmc_res;
+       unsigned long __iomem *gcs_pmc;
        /* the lock for io operations */
        spinlock_t io_lock;
        struct platform_device *dev;
@@ -125,11 +128,19 @@ MODULE_PARM_DESC(turn_SMI_watchdog_clear_off,
  * Some TCO specific functions
  */
 
-static inline unsigned int seconds_to_ticks(int seconds)
+/*
+ * The iTCO v1 and v2's internal timer is stored as ticks which decrement
+ * every 0.6 seconds.  v3's internal timer is stored as seconds (some
+ * datasheets incorrectly state 0.6 seconds).
+ */
+static inline unsigned int seconds_to_ticks(int secs)
 {
-       /* the internal timer is stored as ticks which decrement
-        * every 0.6 seconds */
-       return (seconds * 10) / 6;
+       return iTCO_wdt_private.iTCO_version == 3 ? secs : (secs * 10) / 6;
+}
+
+static inline unsigned int ticks_to_seconds(int ticks)
+{
+       return iTCO_wdt_private.iTCO_version == 3 ? ticks : (ticks * 6) / 10;
 }
 
 static void iTCO_wdt_set_NO_REBOOT_bit(void)
@@ -137,10 +148,14 @@ static void iTCO_wdt_set_NO_REBOOT_bit(void)
        u32 val32;
 
        /* Set the NO_REBOOT bit: this disables reboots */
-       if (iTCO_wdt_private.iTCO_version == 2) {
-               val32 = readl(iTCO_wdt_private.gcs);
+       if (iTCO_wdt_private.iTCO_version == 3) {
+               val32 = readl(iTCO_wdt_private.gcs_pmc);
+               val32 |= 0x00000010;
+               writel(val32, iTCO_wdt_private.gcs_pmc);
+       } else if (iTCO_wdt_private.iTCO_version == 2) {
+               val32 = readl(iTCO_wdt_private.gcs_pmc);
                val32 |= 0x00000020;
-               writel(val32, iTCO_wdt_private.gcs);
+               writel(val32, iTCO_wdt_private.gcs_pmc);
        } else if (iTCO_wdt_private.iTCO_version == 1) {
                pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32);
                val32 |= 0x00000002;
@@ -154,12 +169,20 @@ static int iTCO_wdt_unset_NO_REBOOT_bit(void)
        u32 val32;
 
        /* Unset the NO_REBOOT bit: this enables reboots */
-       if (iTCO_wdt_private.iTCO_version == 2) {
-               val32 = readl(iTCO_wdt_private.gcs);
+       if (iTCO_wdt_private.iTCO_version == 3) {
+               val32 = readl(iTCO_wdt_private.gcs_pmc);
+               val32 &= 0xffffffef;
+               writel(val32, iTCO_wdt_private.gcs_pmc);
+
+               val32 = readl(iTCO_wdt_private.gcs_pmc);
+               if (val32 & 0x00000010)
+                       ret = -EIO;
+       } else if (iTCO_wdt_private.iTCO_version == 2) {
+               val32 = readl(iTCO_wdt_private.gcs_pmc);
                val32 &= 0xffffffdf;
-               writel(val32, iTCO_wdt_private.gcs);
+               writel(val32, iTCO_wdt_private.gcs_pmc);
 
-               val32 = readl(iTCO_wdt_private.gcs);
+               val32 = readl(iTCO_wdt_private.gcs_pmc);
                if (val32 & 0x00000020)
                        ret = -EIO;
        } else if (iTCO_wdt_private.iTCO_version == 1) {
@@ -192,7 +215,7 @@ static int iTCO_wdt_start(struct watchdog_device *wd_dev)
 
        /* Force the timer to its reload value by writing to the TCO_RLD
           register */
-       if (iTCO_wdt_private.iTCO_version == 2)
+       if (iTCO_wdt_private.iTCO_version >= 2)
                outw(0x01, TCO_RLD);
        else if (iTCO_wdt_private.iTCO_version == 1)
                outb(0x01, TCO_RLD);
@@ -240,9 +263,9 @@ static int iTCO_wdt_ping(struct watchdog_device *wd_dev)
        iTCO_vendor_pre_keepalive(iTCO_wdt_private.smi_res, wd_dev->timeout);
 
        /* Reload the timer by writing to the TCO Timer Counter register */
-       if (iTCO_wdt_private.iTCO_version == 2)
+       if (iTCO_wdt_private.iTCO_version >= 2) {
                outw(0x01, TCO_RLD);
-       else if (iTCO_wdt_private.iTCO_version == 1) {
+       else if (iTCO_wdt_private.iTCO_version == 1) {
                /* Reset the timeout status bit so that the timer
                 * needs to count down twice again before rebooting */
                outw(0x0008, TCO1_STS); /* write 1 to clear bit */
@@ -270,14 +293,14 @@ static int iTCO_wdt_set_timeout(struct watchdog_device *wd_dev, unsigned int t)
        /* "Values of 0h-3h are ignored and should not be attempted" */
        if (tmrval < 0x04)
                return -EINVAL;
-       if (((iTCO_wdt_private.iTCO_version == 2) && (tmrval > 0x3ff)) ||
+       if (((iTCO_wdt_private.iTCO_version >= 2) && (tmrval > 0x3ff)) ||
            ((iTCO_wdt_private.iTCO_version == 1) && (tmrval > 0x03f)))
                return -EINVAL;
 
        iTCO_vendor_pre_set_heartbeat(tmrval);
 
        /* Write new heartbeat to watchdog */
-       if (iTCO_wdt_private.iTCO_version == 2) {
+       if (iTCO_wdt_private.iTCO_version >= 2) {
                spin_lock(&iTCO_wdt_private.io_lock);
                val16 = inw(TCOv2_TMR);
                val16 &= 0xfc00;
@@ -312,13 +335,13 @@ static unsigned int iTCO_wdt_get_timeleft(struct watchdog_device *wd_dev)
        unsigned int time_left = 0;
 
        /* read the TCO Timer */
-       if (iTCO_wdt_private.iTCO_version == 2) {
+       if (iTCO_wdt_private.iTCO_version >= 2) {
                spin_lock(&iTCO_wdt_private.io_lock);
                val16 = inw(TCO_RLD);
                val16 &= 0x3ff;
                spin_unlock(&iTCO_wdt_private.io_lock);
 
-               time_left = (val16 * 6) / 10;
+               time_left = ticks_to_seconds(val16);
        } else if (iTCO_wdt_private.iTCO_version == 1) {
                spin_lock(&iTCO_wdt_private.io_lock);
                val8 = inb(TCO_RLD);
@@ -327,7 +350,7 @@ static unsigned int iTCO_wdt_get_timeleft(struct watchdog_device *wd_dev)
                        val8 += (inb(TCOv1_TMR) & 0x3f);
                spin_unlock(&iTCO_wdt_private.io_lock);
 
-               time_left = (val8 * 6) / 10;
+               time_left = ticks_to_seconds(val8);
        }
        return time_left;
 }
@@ -376,16 +399,16 @@ static void iTCO_wdt_cleanup(void)
                        resource_size(iTCO_wdt_private.tco_res));
        release_region(iTCO_wdt_private.smi_res->start,
                        resource_size(iTCO_wdt_private.smi_res));
-       if (iTCO_wdt_private.iTCO_version == 2) {
-               iounmap(iTCO_wdt_private.gcs);
-               release_mem_region(iTCO_wdt_private.gcs_res->start,
-                               resource_size(iTCO_wdt_private.gcs_res));
+       if (iTCO_wdt_private.iTCO_version >= 2) {
+               iounmap(iTCO_wdt_private.gcs_pmc);
+               release_mem_region(iTCO_wdt_private.gcs_pmc_res->start,
+                               resource_size(iTCO_wdt_private.gcs_pmc_res));
        }
 
        iTCO_wdt_private.tco_res = NULL;
        iTCO_wdt_private.smi_res = NULL;
-       iTCO_wdt_private.gcs_res = NULL;
-       iTCO_wdt_private.gcs = NULL;
+       iTCO_wdt_private.gcs_pmc_res = NULL;
+       iTCO_wdt_private.gcs_pmc = NULL;
 }
 
 static int iTCO_wdt_probe(struct platform_device *dev)
@@ -414,27 +437,27 @@ static int iTCO_wdt_probe(struct platform_device *dev)
        iTCO_wdt_private.pdev = to_pci_dev(dev->dev.parent);
 
        /*
-        * Get the Memory-Mapped GCS register, we need it for the
-        * NO_REBOOT flag (TCO v2).
+        * Get the Memory-Mapped GCS or PMC register, we need it for the
+        * NO_REBOOT flag (TCO v2 and v3).
         */
-       if (iTCO_wdt_private.iTCO_version == 2) {
-               iTCO_wdt_private.gcs_res = platform_get_resource(dev,
+       if (iTCO_wdt_private.iTCO_version >= 2) {
+               iTCO_wdt_private.gcs_pmc_res = platform_get_resource(dev,
                                                        IORESOURCE_MEM,
-                                                       ICH_RES_MEM_GCS);
+                                                       ICH_RES_MEM_GCS_PMC);
 
-               if (!iTCO_wdt_private.gcs_res)
+               if (!iTCO_wdt_private.gcs_pmc_res)
                        goto out;
 
-               if (!request_mem_region(iTCO_wdt_private.gcs_res->start,
-                       resource_size(iTCO_wdt_private.gcs_res), dev->name)) {
+               if (!request_mem_region(iTCO_wdt_private.gcs_pmc_res->start,
+                       resource_size(iTCO_wdt_private.gcs_pmc_res), dev->name)) {
                        ret = -EBUSY;
                        goto out;
                }
-               iTCO_wdt_private.gcs = ioremap(iTCO_wdt_private.gcs_res->start,
-                       resource_size(iTCO_wdt_private.gcs_res));
-               if (!iTCO_wdt_private.gcs) {
+               iTCO_wdt_private.gcs_pmc = ioremap(iTCO_wdt_private.gcs_pmc_res->start,
+                       resource_size(iTCO_wdt_private.gcs_pmc_res));
+               if (!iTCO_wdt_private.gcs_pmc) {
                        ret = -EIO;
-                       goto unreg_gcs;
+                       goto unreg_gcs_pmc;
                }
        }
 
@@ -442,7 +465,7 @@ static int iTCO_wdt_probe(struct platform_device *dev)
        if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) {
                pr_info("unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n");
                ret = -ENODEV;  /* Cannot reset NO_REBOOT bit */
-               goto unmap_gcs;
+               goto unmap_gcs_pmc;
        }
 
        /* Set the NO_REBOOT bit to prevent later reboots, just for sure */
@@ -454,7 +477,7 @@ static int iTCO_wdt_probe(struct platform_device *dev)
                pr_err("I/O address 0x%04llx already in use, device disabled\n",
                       (u64)SMI_EN);
                ret = -EBUSY;
-               goto unmap_gcs;
+               goto unmap_gcs_pmc;
        }
        if (turn_SMI_watchdog_clear_off >= iTCO_wdt_private.iTCO_version) {
                /*
@@ -478,9 +501,13 @@ static int iTCO_wdt_probe(struct platform_device *dev)
                ich_info->name, ich_info->iTCO_version, (u64)TCOBASE);
 
        /* Clear out the (probably old) status */
-       outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */
-       outw(0x0002, TCO2_STS); /* Clear SECOND_TO_STS bit */
-       outw(0x0004, TCO2_STS); /* Clear BOOT_STS bit */
+       if (iTCO_wdt_private.iTCO_version == 3) {
+               outl(0x20008, TCO1_STS);
+       } else {
+               outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */
+               outw(0x0002, TCO2_STS); /* Clear SECOND_TO_STS bit */
+               outw(0x0004, TCO2_STS); /* Clear BOOT_STS bit */
+       }
 
        iTCO_wdt_watchdog_dev.bootstatus = 0;
        iTCO_wdt_watchdog_dev.timeout = WATCHDOG_TIMEOUT;
@@ -515,18 +542,18 @@ unreg_tco:
 unreg_smi:
        release_region(iTCO_wdt_private.smi_res->start,
                        resource_size(iTCO_wdt_private.smi_res));
-unmap_gcs:
-       if (iTCO_wdt_private.iTCO_version == 2)
-               iounmap(iTCO_wdt_private.gcs);
-unreg_gcs:
-       if (iTCO_wdt_private.iTCO_version == 2)
-               release_mem_region(iTCO_wdt_private.gcs_res->start,
-                               resource_size(iTCO_wdt_private.gcs_res));
+unmap_gcs_pmc:
+       if (iTCO_wdt_private.iTCO_version >= 2)
+               iounmap(iTCO_wdt_private.gcs_pmc);
+unreg_gcs_pmc:
+       if (iTCO_wdt_private.iTCO_version >= 2)
+               release_mem_region(iTCO_wdt_private.gcs_pmc_res->start,
+                               resource_size(iTCO_wdt_private.gcs_pmc_res));
 out:
        iTCO_wdt_private.tco_res = NULL;
        iTCO_wdt_private.smi_res = NULL;
-       iTCO_wdt_private.gcs_res = NULL;
-       iTCO_wdt_private.gcs = NULL;
+       iTCO_wdt_private.gcs_pmc_res = NULL;
+       iTCO_wdt_private.gcs_pmc = NULL;
 
        return ret;
 }