regulator: pm8xxx-regulator: Add OCP control functionality for switches

Add the ability to selectively enable over current protection
(OCP) for LVS and MVS type PMIC voltage switches.  OCP is used to
automatically disable the output of a PMIC switch in hardware when
the load on the switch becomes too large.

The sequence used when enabling a switch when OCP is desired is as
follows:
1. Disable OCP.
2. Enable the switch.
3. Wait for ocp_enable_time microseconds.
4. Enable OCP.

This sequence is used to ensure that sufficient time is allowed
for inrush current to subside so that a false OCP event is not
triggered.  The delay time is board specific.

Change-Id: I0ea73d3bddd3280ff25f232ece0f1175a52d36cc
Signed-off-by: David Collins <collinsd@codeaurora.org>
diff --git a/drivers/regulator/pm8xxx-regulator.c b/drivers/regulator/pm8xxx-regulator.c
index 15a9cb1..94b028d 100644
--- a/drivers/regulator/pm8xxx-regulator.c
+++ b/drivers/regulator/pm8xxx-regulator.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ * Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -14,6 +14,7 @@
 #define pr_fmt(fmt) "%s: " fmt, __func__
 
 #include <linux/module.h>
+#include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/string.h>
 #include <linux/kernel.h>
@@ -356,12 +357,21 @@
 #define VS_PULL_DOWN_DISABLE		0x40
 #define VS_PULL_DOWN_ENABLE		0x00
 
+#define VS_MODE_MASK			0x30
+#define VS_MODE_NORMAL			0x10
+#define VS_MODE_LPM			0x20
+
 #define VS_PIN_CTRL_MASK		0x0F
 #define VS_PIN_CTRL_EN0			0x08
 #define VS_PIN_CTRL_EN1			0x04
 #define VS_PIN_CTRL_EN2			0x02
 #define VS_PIN_CTRL_EN3			0x01
 
+/* TEST register */
+#define VS_OCP_MASK			0x10
+#define VS_OCP_ENABLE			0x00
+#define VS_OCP_DISABLE			0x10
+
 /* VS300 masks and values */
 
 /* CTRL register */
@@ -372,6 +382,10 @@
 #define VS300_PULL_DOWN_ENABLE_MASK	0x20
 #define VS300_PULL_DOWN_ENABLE		0x20
 
+#define VS300_MODE_MASK			0x18
+#define VS300_MODE_NORMAL		0x00
+#define VS300_MODE_LPM			0x08
+
 /* NCP masks and values */
 
 /* CTRL register */
@@ -1900,9 +1914,32 @@
 
 	mutex_lock(&vreg->pc_lock);
 
-	rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, VS_ENABLE,
-		VS_ENABLE_MASK, &vreg->ctrl_reg);
+	if (vreg->pdata.ocp_enable) {
+		/* Disable OCP. */
+		rc = pm8xxx_vreg_masked_write(vreg, vreg->test_addr,
+			VS_OCP_DISABLE, VS_OCP_MASK, &vreg->test_reg[0]);
+		if (rc)
+			goto done;
 
+		/* Enable the switch while OCP is disabled. */
+		rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr,
+			VS_ENABLE | VS_MODE_NORMAL,
+			VS_ENABLE_MASK | VS_MODE_MASK,
+			&vreg->ctrl_reg);
+		if (rc)
+			goto done;
+
+		/* Wait for inrush current to subside, then enable OCP. */
+		udelay(vreg->pdata.ocp_enable_time);
+		rc = pm8xxx_vreg_masked_write(vreg, vreg->test_addr,
+			VS_OCP_ENABLE, VS_OCP_MASK, &vreg->test_reg[0]);
+	} else {
+		/* Enable the switch without touching OCP. */
+		rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, VS_ENABLE,
+			VS_ENABLE_MASK, &vreg->ctrl_reg);
+	}
+
+done:
 	if (!rc)
 		vreg->is_enabled = true;
 
@@ -1944,13 +1981,39 @@
 	struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev);
 	int rc;
 
-	rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, VS300_CTRL_ENABLE,
-		VS300_CTRL_ENABLE_MASK, &vreg->ctrl_reg);
+	if (vreg->pdata.ocp_enable) {
+		/* Disable OCP. */
+		rc = pm8xxx_vreg_masked_write(vreg, vreg->test_addr,
+			VS_OCP_DISABLE, VS_OCP_MASK, &vreg->test_reg[0]);
+		if (rc)
+			goto done;
 
-	if (rc)
+		/* Enable the switch while OCP is disabled. */
+		rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr,
+			VS300_CTRL_ENABLE | VS300_MODE_NORMAL,
+			VS300_CTRL_ENABLE_MASK | VS300_MODE_MASK,
+			&vreg->ctrl_reg);
+		if (rc)
+			goto done;
+
+		/* Wait for inrush current to subside, then enable OCP. */
+		udelay(vreg->pdata.ocp_enable_time);
+		rc = pm8xxx_vreg_masked_write(vreg, vreg->test_addr,
+			VS_OCP_ENABLE, VS_OCP_MASK, &vreg->test_reg[0]);
+	} else {
+		/* Enable the regulator without touching OCP. */
+		rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr,
+			VS300_CTRL_ENABLE, VS300_CTRL_ENABLE_MASK,
+			&vreg->ctrl_reg);
+	}
+
+done:
+	if (rc) {
 		vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc);
-	else
+	} else {
+		vreg->is_enabled = true;
 		pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_ENABLE);
+	}
 
 	return rc;
 }
@@ -2805,6 +2868,14 @@
 		return rc;
 	}
 
+	/* Save the current test register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr,
+		&vreg->test_reg[0]);
+	if (rc) {
+		vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc);
+		return rc;
+	}
+
 	if (is_real) {
 		/* Set pull down enable based on platform data. */
 		rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr,
@@ -2833,6 +2904,14 @@
 		return rc;
 	}
 
+	/* Save the current test register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr,
+		&vreg->test_reg[0]);
+	if (rc) {
+		vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc);
+		return rc;
+	}
+
 	/* Set pull down enable based on platform data. */
 	rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr,
 		    (vreg->pdata.pull_down_enable ? VS300_PULL_DOWN_ENABLE : 0),