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),