In some platforms, critical shared regulator is initialized in bootloader. But during kernel booting, the driver probing order and conflicting operations from other regulator consumers, may set the regulator in a undefined state, which will cause serious problem.
This patch try to add a boot_protection flag in regulator constraints. And regulator core will postpone all operations until all consumers have taked their place. The boot_protection flag only work before late_initicall. And as other constraints liked, you can specify this flag in a board file, or in dts file. Signed-off-by: WEN Pingbo <pingbo....@linaro.org> --- drivers/regulator/core.c | 106 +++++++++++++++++++++++++++++++++++--- drivers/regulator/internal.h | 2 + drivers/regulator/of_regulator.c | 3 ++ include/linux/regulator/driver.h | 3 ++ include/linux/regulator/machine.h | 1 + 5 files changed, 109 insertions(+), 6 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index fe47d38..f994a0f 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -55,6 +55,7 @@ static LIST_HEAD(regulator_map_list); static LIST_HEAD(regulator_ena_gpio_list); static LIST_HEAD(regulator_supply_alias_list); static bool has_full_constraints; +static bool regulator_has_booted; static struct dentry *debugfs_root; @@ -1030,6 +1031,13 @@ static int set_machine_constraints(struct regulator_dev *rdev, if (!rdev->constraints) return -ENOMEM; + /* + * If a regulator driver is registered after late_initcall, the + * boot_protection should be ingnored. + */ + if (regulator_has_booted) + rdev->constraints->boot_protection = 0; + ret = machine_constraints_voltage(rdev, rdev->constraints); if (ret != 0) return ret; @@ -2195,8 +2203,14 @@ static int _regulator_disable(struct regulator_dev *rdev) if (rdev->use_count == 1 && (rdev->constraints && !rdev->constraints->always_on)) { - /* we are last user */ - if (regulator_ops_is_valid(rdev, REGULATOR_CHANGE_STATUS)) { + /* + * We are last user. + * + * If boot_protection is set, we only clear use_count, + * and regulator_init_complete() will disable it. + */ + if (!rdev->constraints->boot_protection && + regulator_ops_is_valid(rdev, REGULATOR_CHANGE_STATUS)) { ret = _notifier_call_chain(rdev, REGULATOR_EVENT_PRE_DISABLE, NULL); @@ -2297,6 +2311,10 @@ int regulator_force_disable(struct regulator *regulator) struct regulator_dev *rdev = regulator->rdev; int ret; + WARN(rdev->constraints->boot_protection, + "disable regulator %s with boot protection flag\n", + rdev->desc->name); + mutex_lock(&rdev->mutex); regulator->uA_load = 0; ret = _regulator_force_disable(regulator->rdev); @@ -2852,6 +2870,10 @@ static int regulator_set_voltage_unlocked(struct regulator *regulator, if (ret < 0) goto out2; + /* We need to change voltage, but boot_protection is set. */ + if (rdev->constraints->boot_protection) + goto out; + if (rdev->supply && (rdev->desc->min_dropout_uV || !rdev->desc->ops->get_voltage)) { int current_supply_uV; @@ -3069,6 +3091,9 @@ int regulator_sync_voltage(struct regulator *regulator) if (ret < 0) goto out; + if (rdev->constraints->boot_protection) + goto out; + ret = _regulator_do_set_voltage(rdev, min_uV, max_uV); out: @@ -3161,6 +3186,15 @@ int regulator_set_current_limit(struct regulator *regulator, if (ret < 0) goto out; + /* + * Stage new current value, and applied it later. + */ + if (rdev->constraints->boot_protection) { + regulator->min_uA = min_uA; + regulator->max_uA = max_uA; + goto out; + } + ret = rdev->desc->ops->set_current_limit(rdev, min_uA, max_uA); out: mutex_unlock(&rdev->mutex); @@ -3240,6 +3274,11 @@ int regulator_set_mode(struct regulator *regulator, unsigned int mode) if (ret < 0) goto out; + if (rdev->constraints->boot_protection) { + rdev->boot_mode = mode; + goto out; + } + ret = rdev->desc->ops->set_mode(rdev, mode); out: mutex_unlock(&rdev->mutex); @@ -3306,11 +3345,14 @@ EXPORT_SYMBOL_GPL(regulator_get_mode); int regulator_set_load(struct regulator *regulator, int uA_load) { struct regulator_dev *rdev = regulator->rdev; - int ret; + int ret = 0; mutex_lock(&rdev->mutex); regulator->uA_load = uA_load; - ret = drms_uA_update(rdev); + + if (!rdev->constraints->boot_protection) + ret = drms_uA_update(rdev); + mutex_unlock(&rdev->mutex); return ret; @@ -3344,7 +3386,8 @@ int regulator_allow_bypass(struct regulator *regulator, bool enable) if (enable && !regulator->bypass) { rdev->bypass_count++; - if (rdev->bypass_count == rdev->open_count) { + if (rdev->bypass_count == rdev->open_count && + !rdev->constraints->boot_protection) { ret = rdev->desc->ops->set_bypass(rdev, enable); if (ret != 0) rdev->bypass_count--; @@ -3353,7 +3396,8 @@ int regulator_allow_bypass(struct regulator *regulator, bool enable) } else if (!enable && regulator->bypass) { rdev->bypass_count--; - if (rdev->bypass_count != rdev->open_count) { + if (rdev->bypass_count != rdev->open_count && + !rdev->constraints->boot_protection) { ret = rdev->desc->ops->set_bypass(rdev, enable); if (ret != 0) rdev->bypass_count++; @@ -4346,6 +4390,51 @@ static int __init regulator_init(void) /* init early to allow our consumers to complete system booting */ core_initcall(regulator_init); +static void __init regulator_clear_boot_protection(struct regulator_dev *rdev) +{ + struct regulator *regulator; + int min_uA = INT_MAX, max_uA = 0; + + mutex_lock(&rdev->mutex); + + rdev->constraints->boot_protection = 0; + + /* update current setting */ + list_for_each_entry(regulator, &rdev->consumer_list, list) { + if (regulator->min_uA < min_uA) + min_uA = regulator->min_uA; + if (regulator->max_uA > max_uA) + max_uA = regulator->max_uA; + } + + if (max_uA && !regulator_check_current_limit(rdev, &min_uA, &max_uA)) + rdev->desc->ops->set_current_limit(rdev, min_uA, max_uA); + + /* constraints check has already done */ + if (rdev->boot_mode) + rdev->desc->ops->set_mode(rdev, rdev->boot_mode); + + /* update regulator load */ + drms_uA_update(rdev); + + /* check if we need to set bypass mode */ + if (rdev->desc->ops->set_bypass && rdev->bypass_count && + regulator_ops_is_valid(rdev, REGULATOR_CHANGE_BYPASS)) { + if (rdev->bypass_count == rdev->open_count) + rdev->desc->ops->set_bypass(rdev, true); + else + rdev->desc->ops->set_bypass(rdev, false); + } + + regulator = list_first_entry_or_null(&rdev->consumer_list, + struct regulator, list); + mutex_unlock(&rdev->mutex); + + if (regulator) + regulator_set_voltage(regulator, regulator->min_uV, + regulator->max_uV); +} + static int __init regulator_late_cleanup(struct device *dev, void *data) { struct regulator_dev *rdev = dev_to_rdev(dev); @@ -4353,6 +4442,9 @@ static int __init regulator_late_cleanup(struct device *dev, void *data) struct regulation_constraints *c = rdev->constraints; int enabled, ret; + if (c->boot_protection) + regulator_clear_boot_protection(rdev); + if (c && c->always_on) return 0; @@ -4406,6 +4498,8 @@ static int __init regulator_init_complete(void) if (of_have_populated_dt()) has_full_constraints = true; + regulator_has_booted = true; + /* If we have a full configuration then disable any regulators * we have permission to change the status for and which are * not in use or always_on. This is effectively the default diff --git a/drivers/regulator/internal.h b/drivers/regulator/internal.h index c74ac87..ab81a71 100644 --- a/drivers/regulator/internal.h +++ b/drivers/regulator/internal.h @@ -29,6 +29,8 @@ struct regulator { int uA_load; int min_uV; int max_uV; + int min_uA; + int max_uA; char *supply_name; struct device_attribute dev_attr; struct regulator_dev *rdev; diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c index 6b0aa80..95fd789 100644 --- a/drivers/regulator/of_regulator.c +++ b/drivers/regulator/of_regulator.c @@ -78,6 +78,9 @@ static void of_get_regulation_constraints(struct device_node *np, if (of_property_read_bool(np, "regulator-allow-set-load")) constraints->valid_ops_mask |= REGULATOR_CHANGE_DRMS; + constraints->boot_protection = of_property_read_bool(np, + "regulator-boot-protection"); + ret = of_property_read_u32(np, "regulator-ramp-delay", &pval); if (!ret) { if (pval) diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index cd271e8..ddb80a3 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -389,6 +389,9 @@ struct regulator_dev { u32 open_count; u32 bypass_count; + /* save mode during boot protection */ + unsigned int boot_mode; + /* lists we belong to */ struct list_head list; /* list of all regulators */ diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h index 5d627c8..fd88a12 100644 --- a/include/linux/regulator/machine.h +++ b/include/linux/regulator/machine.h @@ -155,6 +155,7 @@ struct regulation_constraints { /* constraint flags */ unsigned always_on:1; /* regulator never off when system is on */ unsigned boot_on:1; /* bootloader/firmware enabled regulator */ + unsigned boot_protection:1; /* protect regulator initialized by bootloader */ unsigned apply_uV:1; /* apply uV constraint if min == max */ unsigned ramp_disable:1; /* disable ramp delay */ unsigned soft_start:1; /* ramp voltage slowly */ -- 1.9.1