summary refs log tree commit diff
path: root/drivers/watchdog/iTCO_wdt.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2014-04-07 10:24:18 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2014-04-07 10:24:18 -0700
commite5744abb2fa3629aa5a94e21ca1eae32ff2fe00b (patch)
treeef90c96390256b073f5255d224aecb2fc1f6ee84 /drivers/watchdog/iTCO_wdt.c
parentc29aa153ef0469cddf0146d41ce6494bd76be78b (diff)
parent2d28ca731b9bb6262f7711241628c7844b0cf7dc (diff)
downloadlinux-e5744abb2fa3629aa5a94e21ca1eae32ff2fe00b.tar.gz
Merge tag 'mfd-for-linus-3.15' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
Pull MFD updates from Lee Jones:
 "Changes to existing drivers:
   - Use of managed resources - omap, twl4030, ti_am335x_tscadc
   - Advanced error handling - omap
   - Rework clk management - omap
   - Device Tree (re-)work - tc3589x, pm8921, da9055, sec
   - IRC management overhaul and !BROKEN - pm8921
   - Convert to regmap - ssbi, pm8921
   - Use simple power-management ops - ucb1x00
   - Include file clean-up - adp5520, cs5535, janz, lpc_ich,
      - lpc_sch, max14577, mcp-sa11x0, pcf50633-adc, rc5t583,
      	rdc321x-southbridge, retu, smsc-ece1099, ti-ssp, ti_am335x_tscadc,
	tps65912, vexpress-config, wm8350, ywm8350
   - Various bug fixes across the subsystem
      - NULL/invalid pointer dereference prevention
      - Resource leak mitigation,
      - Variable used initialised
      - Staticise various containers
      - Enforce return value checks

  New drivers/supported devices:
   - Add support for s2mps14 and s2mpa01 to sec
   - Add support for da9063 (v5) to da9063
   - Add support for atom-c2000 to gpio-ich
   - Add support for come-{mbt10,cbt6,chl6} to kempld
   - Add support for da9053 to da9052
   - Add support for itco-wdt (v3) and baytrail to lpc_ich
   - Add new drivers for tps65218, rtsx_usb, bcm590xx

  (Re-)moved drivers:
   - twl4030 ==> drivers/iio
   - ti-ssp  ==> /dev/null"

* tag 'mfd-for-linus-3.15' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd: (103 commits)
  mfd: wm5110: Correct default for HEADPHONE_DETECT_1
  mfd: arizona: Correct small errors in the DT binding documentation
  mfd: arizona: Mark DSP clocking register as volatile
  mfd: devicetree: bindings: Add pm8xxx RTC description
  mfd: kempld-core: Fix potential hang-up during boot
  mfd: sec-core: Fix uninitialized 'regmap_rtc' on S2MPA01
  mfd: tps65910: Fix regmap_irq_chip_data leak on mfd_add_devices fail
  mfd: tps65910: Fix possible invalid pointer dereference on regmap_add_irq_chip fail
  mfd: sec-core: Fix I2C dummy device resource leak on probe failure
  mfd: sec-core: Add of_compatible strings for clock MFD cells
  mfd: Remove obsolete ti-ssp driver
  Documentation: mfd: s2mps11: Describe S5M8767 and S2MPS14 clocks
  mfd: bcm590xx: Fix type argument for module device table
  mfd: lpc_ich: Add support for Intel Bay Trail SoC
  mfd: lpc_ich: Add support for NM10 GPIO
  mfd: lpc_ich: Change Avoton to iTCO v3
  watchdog: iTCO_wdt: Add support for v3 silicon
  mfd: lpc_ich: Add support for iTCO v3
  mfd: lpc_ich: Remove lpc_ich_cfg struct use
  mfd: lpc_ich: Only configure watchdog or GPIO when present
  ...
Diffstat (limited to 'drivers/watchdog/iTCO_wdt.c')
-rw-r--r--drivers/watchdog/iTCO_wdt.c137
1 files changed, 82 insertions, 55 deletions
diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c
index 0e6c0333f775..0ba1b7c99760 100644
--- a/drivers/watchdog/iTCO_wdt.c
+++ b/drivers/watchdog/iTCO_wdt.c
@@ -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;
 }