regulator: qcom_pm8008: correct parent supply voltage voting

Make a request for the parent supply voltage (with headroom
included) inside of pm8008_regulator_enable().  This ensures that
the required headroom is enforced if a consumer calls
regulator_disable() followed by regulator_enable() for one of
the PM8008 LDO regulators.

Also, change the procedure used to request the parent supply
voltage inside of pm8008_regulator_set_voltage().  The parent
voltage request should only be made if the PM8008 LDO is enabled.
Additionally, the request should be made after the PM8008 LDO
voltage is changed if the voltage is being reduced.  This ensures
that there are no transient headroom voltage violations.

Change-Id: Ic008689e3cb6361265767fe7daa36cf4d14ae0f3
Signed-off-by: David Collins <collinsd@codeaurora.org>
tirimbino
David Collins 5 years ago committed by Kiran Gunda
parent 1cc222289b
commit 8542d9be8c
  1. 176
      drivers/regulator/qcom_pm8008-regulator.c

@ -12,6 +12,7 @@
#define pr_fmt(fmt) "PM8008: %s: " fmt, __func__
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/interrupt.h>
@ -33,6 +34,7 @@
#define STARTUP_DELAY_USEC 20
#define VSET_STEP_SIZE_MV 1
#define VSET_STEP_MV 8
#define VSET_STEP_UV (VSET_STEP_MV * 1000)
#define MISC_BASE 0x900
@ -186,9 +188,16 @@ static int pm8008_regulator_is_enabled(struct regulator_dev *rdev)
static int pm8008_regulator_enable(struct regulator_dev *rdev)
{
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
int rc, init_mv, delay_us, delay_ms, retry_count = 10;
int rc, rc2, current_uv, delay_us, delay_ms, retry_count = 10;
u8 reg;
current_uv = pm8008_regulator_get_voltage(rdev);
if (current_uv < 0) {
pm8008_err(pm8008_reg, "failed to get current voltage rc=%d\n",
current_uv);
return current_uv;
}
rc = regulator_enable(pm8008_reg->en_supply);
if (rc < 0) {
pm8008_err(pm8008_reg,
@ -197,12 +206,22 @@ static int pm8008_regulator_enable(struct regulator_dev *rdev)
}
if (pm8008_reg->parent_supply) {
rc = regulator_set_voltage(pm8008_reg->parent_supply,
current_uv + pm8008_reg->min_dropout_uv,
INT_MAX);
if (rc < 0) {
pm8008_err(pm8008_reg, "failed to request parent supply voltage rc=%d\n",
rc);
goto remove_en;
}
rc = regulator_enable(pm8008_reg->parent_supply);
if (rc < 0) {
pm8008_err(pm8008_reg,
"failed to enable parent rc=%d\n", rc);
regulator_disable(pm8008_reg->en_supply);
return rc;
regulator_set_voltage(pm8008_reg->parent_supply, 0,
INT_MAX);
goto remove_en;
}
}
@ -216,20 +235,14 @@ static int pm8008_regulator_enable(struct regulator_dev *rdev)
}
/*
* wait for VREG_OK
* Read voltage and calculate the delay.
* Wait for the VREG_READY status bit to be set using a timeout delay
* calculated from the current commanded voltage.
*/
init_mv = pm8008_regulator_get_voltage(rdev) / 1000;
if (init_mv < 0) {
pm8008_err(pm8008_reg,
"failed to get regulator voltage rc=%d\n", rc);
goto out;
}
delay_us = STARTUP_DELAY_USEC
+ DIV_ROUND_UP(init_mv * 1000, pm8008_reg->step_rate);
+ DIV_ROUND_UP(current_uv, pm8008_reg->step_rate);
delay_ms = DIV_ROUND_UP(delay_us, 1000);
/* Retry 10 times for VREG_OK before bailing out */
/* Retry 10 times for VREG_READY before bailing out */
while (retry_count--) {
if (delay_ms > 20)
msleep(delay_ms);
@ -241,7 +254,7 @@ static int pm8008_regulator_enable(struct regulator_dev *rdev)
if (rc < 0) {
pm8008_err(pm8008_reg,
"failed to read regulator status rc=%d\n", rc);
goto out;
goto disable_ldo;
}
if (reg & VREG_READY_BIT) {
pm8008_debug(pm8008_reg, "regulator enabled\n");
@ -249,20 +262,33 @@ static int pm8008_regulator_enable(struct regulator_dev *rdev)
}
}
pm8008_err(pm8008_reg,
"failed to enable regulator VREG_READY not set\n");
out:
pm8008_err(pm8008_reg, "failed to enable regulator, VREG_READY not set\n");
rc = -ETIME;
disable_ldo:
pm8008_masked_write(pm8008_reg->regmap,
LDO_ENABLE_REG(pm8008_reg->base), ENABLE_BIT, 0);
remove_vote:
rc = regulator_disable(pm8008_reg->en_supply);
if (pm8008_reg->parent_supply)
rc |= regulator_disable(pm8008_reg->parent_supply);
if (rc < 0)
pm8008_err(pm8008_reg,
"failed to disable parent regulator rc=%d\n", rc);
if (pm8008_reg->parent_supply) {
rc2 = regulator_disable(pm8008_reg->parent_supply);
if (rc2 < 0)
pm8008_err(pm8008_reg, "failed to disable parent supply rc=%d\n",
rc2);
rc2 = regulator_set_voltage(pm8008_reg->parent_supply, 0,
INT_MAX);
if (rc2 < 0)
pm8008_err(pm8008_reg, "failed to remove voltage vote for parent supply rc=%d\n",
rc2);
}
remove_en:
rc2 = regulator_disable(pm8008_reg->en_supply);
if (rc2 < 0)
pm8008_err(pm8008_reg, "failed to disable en_supply rc=%d\n",
rc2);
return -ETIME;
return rc;
}
static int pm8008_regulator_disable(struct regulator_dev *rdev)
@ -279,30 +305,31 @@ static int pm8008_regulator_disable(struct regulator_dev *rdev)
return rc;
}
/* remove vote from chip enable regulator */
rc = regulator_disable(pm8008_reg->en_supply);
if (rc < 0) {
pm8008_err(pm8008_reg,
"failed to disable en_supply rc=%d\n", rc);
}
/* remove voltage vote from parent regulator */
if (pm8008_reg->parent_supply) {
rc = regulator_set_voltage(pm8008_reg->parent_supply,
0, INT_MAX);
rc = regulator_disable(pm8008_reg->parent_supply);
if (rc < 0) {
pm8008_err(pm8008_reg,
"failed to remove parent voltage rc=%d\n", rc);
pm8008_err(pm8008_reg, "failed to disable parent rc=%d\n",
rc);
return rc;
}
rc = regulator_disable(pm8008_reg->parent_supply);
rc = regulator_set_voltage(pm8008_reg->parent_supply,
0, INT_MAX);
if (rc < 0) {
pm8008_err(pm8008_reg,
"failed to disable parent rc=%d\n", rc);
pm8008_err(pm8008_reg, "failed to remove parent voltage rc=%d\n",
rc);
return rc;
}
}
/* remove vote from chip enable regulator */
rc = regulator_disable(pm8008_reg->en_supply);
if (rc < 0) {
pm8008_err(pm8008_reg, "failed to disable en_supply rc=%d\n",
rc);
return rc;
}
pm8008_debug(pm8008_reg, "regulator disabled\n");
return 0;
}
@ -340,31 +367,76 @@ static int pm8008_write_voltage(struct pm8008_regulator *pm8008_reg, int min_uv,
return 0;
}
static int pm8008_regulator_set_voltage_time(struct regulator_dev *rdev,
int old_uV, int new_uv)
{
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
return DIV_ROUND_UP(abs(new_uv - old_uV), pm8008_reg->step_rate);
}
static int pm8008_regulator_set_voltage(struct regulator_dev *rdev,
int min_uv, int max_uv, unsigned int *selector)
{
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
int rc = 0;
int rc = 0, current_uv = 0, rounded_uv = 0, enabled = 0;
if (pm8008_reg->parent_supply) {
/* request on parent regulator with headroom */
enabled = pm8008_regulator_is_enabled(rdev);
if (enabled < 0) {
return enabled;
} else if (enabled) {
current_uv = pm8008_regulator_get_voltage(rdev);
if (current_uv < 0)
return current_uv;
rounded_uv = roundup(min_uv, VSET_STEP_UV);
}
}
/*
* Set the parent_supply voltage before changing the LDO voltage when
* the LDO voltage is being increased.
*/
if (pm8008_reg->parent_supply && enabled && rounded_uv >= current_uv) {
/* Request parent voltage with headroom */
rc = regulator_set_voltage(pm8008_reg->parent_supply,
pm8008_reg->min_dropout_uv + min_uv,
rounded_uv + pm8008_reg->min_dropout_uv,
INT_MAX);
if (rc < 0) {
pm8008_err(pm8008_reg,
"failed to request parent supply voltage rc=%d\n",
pm8008_err(pm8008_reg, "failed to request parent supply voltage rc=%d\n",
rc);
return rc;
}
}
rc = pm8008_write_voltage(pm8008_reg, min_uv, max_uv);
if (rc < 0) {
/* remove parent's voltage vote */
if (pm8008_reg->parent_supply)
regulator_set_voltage(pm8008_reg->parent_supply,
0, INT_MAX);
if (rc < 0)
return rc;
/*
* Set the parent_supply voltage after changing the LDO voltage when
* the LDO voltage is being reduced.
*/
if (pm8008_reg->parent_supply && enabled && rounded_uv < current_uv) {
/*
* Ensure sufficient time for the LDO voltage to slew down
* before reducing the parent supply voltage. The regulator
* framework will add the same delay after this function returns
* in all cases (i.e. enabled/disabled and increasing/decreasing
* voltage).
*/
udelay(pm8008_regulator_set_voltage_time(rdev, rounded_uv,
current_uv));
/* Request parent voltage with headroom */
rc = regulator_set_voltage(pm8008_reg->parent_supply,
rounded_uv + pm8008_reg->min_dropout_uv,
INT_MAX);
if (rc < 0) {
pm8008_err(pm8008_reg, "failed to request parent supply voltage rc=%d\n",
rc);
return rc;
}
}
pm8008_debug(pm8008_reg, "voltage set to %d\n", min_uv);
@ -422,14 +494,6 @@ static int pm8008_regulator_set_load(struct regulator_dev *rdev, int load_uA)
return pm8008_regulator_set_mode(rdev, mode);
}
static int pm8008_regulator_set_voltage_time(struct regulator_dev *rdev,
int old_uV, int new_uv)
{
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
return DIV_ROUND_UP(abs(new_uv - old_uV), pm8008_reg->step_rate);
}
static struct regulator_ops pm8008_regulator_ops = {
.enable = pm8008_regulator_enable,
.disable = pm8008_regulator_disable,

Loading…
Cancel
Save