From eb71d4dec4a5e010e34b9d7afdb5af41884c388e Mon Sep 17 00:00:00 2001
From: Peter Tyser <ptyser@xes-inc.com>
Date: Mon, 10 Mar 2014 16:34:54 -0500
Subject: [PATCH] mfd: lpc_ich: Add support for iTCO v3
Git-commit: eb71d4dec4a5e010e34b9d7afdb5af41884c388e
Patch-mainline: 3.15-rc1
References: bnc#908012,FATE#317358

Some newer Atom CPUs, eg Avoton and Bay Trail, use slightly different
register layouts for the iTCO than the current v1 and v2 iTCO.
Differences from previous iTCO versions include:
- The ACPI space is enabled in the "ACPI base address" register instead
  of the "ACPI control register"

- The "no reboot" functionality is set in the "Power Management
  Configuration" register instead of the "General Control and Status"
  (GCS) register or PCI configuration space.

- The "ACPI Control Register" is not present on v3.  The "Power
  Management Configuration Base Address" register resides at the same
  address is Avoton/Bay Trail.

To differentiate these newer chipsets create a new v3 iTCO version and
update the MFD driver to support them.

[The patch is heavily modified to be applied to iTCO_wdt.c instead of
 mfd/lpc_ich.c, and the only part relevant with iTCO wd is retrieved -- tiwai]

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>
Signed-off-by: Takashi Iwai <tiwai@suse.de>

---
 drivers/watchdog/iTCO_wdt.c |   42 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 40 insertions(+), 2 deletions(-)

--- a/drivers/watchdog/iTCO_wdt.c
+++ b/drivers/watchdog/iTCO_wdt.c
@@ -456,6 +456,8 @@ static struct {		/* this is private data
 	spinlock_t io_lock;
 	/* the PCI-device */
 	struct pci_dev *pdev;
+	/* Cached ACPI control or PMC base value */
+	int actrl_pbase_save;
 } iTCO_wdt_private;
 
 /* the watchdog platform device */
@@ -859,6 +861,28 @@ static struct miscdevice iTCO_wdt_miscde
 	.fops =		&iTCO_wdt_fops,
 };
 
+#define ACPIBASE_PMC_OFF	0x08
+#define ACPIBASE_PMC_END	0x0c
+#define ACPICTRL_PMCBASE	0x44
+
+static void iTCO_enable_pmc_space(struct pci_dev *dev)
+{
+	u8 reg_save;
+
+	pci_read_config_byte(dev, ACPICTRL_PMCBASE, &reg_save);
+	pci_write_config_byte(dev, ACPICTRL_PMCBASE, reg_save | 0x2);
+
+	iTCO_wdt_private.actrl_pbase_save = reg_save;
+}
+
+static void iTCO_restore_config_space(struct pci_dev *dev)
+{
+	if (iTCO_wdt_private.iTCO_version == 3) {
+		pci_write_config_byte(dev, ACPICTRL_PMCBASE,
+				      iTCO_wdt_private.actrl_pbase_save);
+	}
+}
+
 /*
  *	Init & exit routines
  */
@@ -889,10 +913,17 @@ static int __devinit iTCO_wdt_init(struc
 	iTCO_wdt_private.ACPIBASE = base_address;
 	iTCO_wdt_private.pdev = pdev;
 
-	/* Get the Memory-Mapped GCS register, we need it for the
+	/* iTCO v2:
+	   Get the Memory-Mapped GCS register, we need it for the
 	   NO_REBOOT flag (TCO v2). To get access to it you have to
 	   read RCBA from PCI Config space 0xf0 and use it as base.
-	   GCS = RCBA + ICH6_GCS(0x3410). */
+	   GCS = RCBA + ICH6_GCS(0x3410).
+
+	   iTCO v3:
+	   Get the Power Management Configuration register.  To get access
+	   to it we have to read the PMC BASE from config space and address
+	   the register at offset 0x8.
+	 */
 	if (iTCO_wdt_private.iTCO_version == 2) {
 		pci_read_config_dword(pdev, 0xf0, &base_address);
 		if ((base_address & 1) == 0) {
@@ -903,6 +934,11 @@ static int __devinit iTCO_wdt_init(struc
 		}
 		RCBA = base_address & 0xffffc000;
 		iTCO_wdt_private.gcs_pmc = ioremap((RCBA + 0x3410), 4);
+	} else if (iTCO_wdt_private.iTCO_version == 3) {
+		iTCO_enable_pmc_space(pdev);
+		pci_read_config_dword(pdev, ACPICTRL_PMCBASE, &base_address);
+		base_address &= 0xfffffe00;
+		iTCO_wdt_private.gcs_pmc = ioremap(base_address + ACPIBASE_PMC_OFF, 4);
 	}
 
 	/* Check chipset's NO_REBOOT bit */
@@ -986,6 +1022,7 @@ unreg_smi_en:
 out_unmap:
 	if (iTCO_wdt_private.iTCO_version >= 2)
 		iounmap(iTCO_wdt_private.gcs_pmc);
+	iTCO_restore_config_space(pdev);
 out:
 	iTCO_wdt_private.ACPIBASE = 0;
 	return ret;
@@ -1003,6 +1040,7 @@ static void __devexit iTCO_wdt_cleanup(v
 	release_region(SMI_EN, 4);
 	if (iTCO_wdt_private.iTCO_version >= 2)
 		iounmap(iTCO_wdt_private.gcs_pmc);
+	iTCO_restore_config_space(iTCO_wdt_private.pdev);
 	pci_dev_put(iTCO_wdt_private.pdev);
 	iTCO_wdt_private.ACPIBASE = 0;
 }
