regulator: implement selector stepping
authorBartosz Golaszewski <bgolaszewski@baylibre.com>
Wed, 3 Jul 2019 16:10:34 +0000 (18:10 +0200)
committerMark Brown <broonie@kernel.org>
Thu, 4 Jul 2019 16:07:25 +0000 (17:07 +0100)
Some regulators require that the requested voltage be reached gradually
by setting all or some of the intermediate values. Implement a new field
in the regulator description struct that allows users to specify the
number of selectors by which the regulator API should step when ramping
the voltage up/down.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Link: https://lore.kernel.org/r/20190703161035.31808-2-brgl@bgdev.pl
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/regulator/core.c
include/linux/regulator/driver.h

index 9d3ed13..df82e2a 100644 (file)
@@ -3106,6 +3106,66 @@ static int _regulator_call_set_voltage_sel(struct regulator_dev *rdev,
        return ret;
 }
 
+static int _regulator_set_voltage_sel_step(struct regulator_dev *rdev,
+                                          int uV, int new_selector)
+{
+       const struct regulator_ops *ops = rdev->desc->ops;
+       int diff, old_sel, curr_sel, ret;
+
+       /* Stepping is only needed if the regulator is enabled. */
+       if (!_regulator_is_enabled(rdev))
+               goto final_set;
+
+       if (!ops->get_voltage_sel)
+               return -EINVAL;
+
+       old_sel = ops->get_voltage_sel(rdev);
+       if (old_sel < 0)
+               return old_sel;
+
+       diff = new_selector - old_sel;
+       if (diff == 0)
+               return 0; /* No change needed. */
+
+       if (diff > 0) {
+               /* Stepping up. */
+               for (curr_sel = old_sel + rdev->desc->vsel_step;
+                    curr_sel < new_selector;
+                    curr_sel += rdev->desc->vsel_step) {
+                       /*
+                        * Call the callback directly instead of using
+                        * _regulator_call_set_voltage_sel() as we don't
+                        * want to notify anyone yet. Same in the branch
+                        * below.
+                        */
+                       ret = ops->set_voltage_sel(rdev, curr_sel);
+                       if (ret)
+                               goto try_revert;
+               }
+       } else {
+               /* Stepping down. */
+               for (curr_sel = old_sel - rdev->desc->vsel_step;
+                    curr_sel > new_selector;
+                    curr_sel -= rdev->desc->vsel_step) {
+                       ret = ops->set_voltage_sel(rdev, curr_sel);
+                       if (ret)
+                               goto try_revert;
+               }
+       }
+
+final_set:
+       /* The final selector will trigger the notifiers. */
+       return _regulator_call_set_voltage_sel(rdev, uV, new_selector);
+
+try_revert:
+       /*
+        * At least try to return to the previous voltage if setting a new
+        * one failed.
+        */
+       (void)ops->set_voltage_sel(rdev, old_sel);
+       return ret;
+}
+
 static int _regulator_set_voltage_time(struct regulator_dev *rdev,
                                       int old_uV, int new_uV)
 {
@@ -3179,6 +3239,9 @@ static int _regulator_do_set_voltage(struct regulator_dev *rdev,
                                selector = ret;
                                if (old_selector == selector)
                                        ret = 0;
+                               else if (rdev->desc->vsel_step)
+                                       ret = _regulator_set_voltage_sel_step(
+                                               rdev, best_val, selector);
                                else
                                        ret = _regulator_call_set_voltage_sel(
                                                rdev, best_val, selector);
index 377da23..f0d7b04 100644 (file)
@@ -286,6 +286,11 @@ enum regulator_type {
  * @vsel_range_mask: Mask for register bitfield used for range selector
  * @vsel_reg: Register for selector when using regulator_regmap_X_voltage_
  * @vsel_mask: Mask for register bitfield used for selector
+ * @vsel_step: Specify the resolution of selector stepping when setting
+ *            voltage. If 0, then no stepping is done (requested selector is
+ *            set directly), if >0 then the regulator API will ramp the
+ *            voltage up/down gradually each time increasing/decreasing the
+ *            selector by the specified step value.
  * @csel_reg: Register for current limit selector using regmap set_current_limit
  * @csel_mask: Mask for register bitfield used for current limit selector
  * @apply_reg: Register for initiate voltage change on the output when
@@ -360,6 +365,7 @@ struct regulator_desc {
        unsigned int vsel_range_mask;
        unsigned int vsel_reg;
        unsigned int vsel_mask;
+       unsigned int vsel_step;
        unsigned int csel_reg;
        unsigned int csel_mask;
        unsigned int apply_reg;