diff --git a/drivers/regulator/qcom_pm8008-regulator.c b/drivers/regulator/qcom_pm8008-regulator.c index 0f9fbe2b792d..8704473d4212 100644 --- a/drivers/regulator/qcom_pm8008-regulator.c +++ b/drivers/regulator/qcom_pm8008-regulator.c @@ -12,6 +12,7 @@ #define pr_fmt(fmt) "PM8008: %s: " fmt, __func__ +#include #include #include #include @@ -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,