summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/regulator/core.c192
1 files changed, 192 insertions, 0 deletions
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 6ed568b96c0e..225eaca24921 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -105,6 +105,8 @@ static int _notifier_call_chain(struct regulator_dev *rdev,
unsigned long event, void *data);
static int _regulator_do_set_voltage(struct regulator_dev *rdev,
int min_uV, int max_uV);
+static int regulator_balance_voltage(struct regulator_dev *rdev,
+ suspend_state_t state);
static struct regulator *create_regulator(struct regulator_dev *rdev,
struct device *dev,
const char *supply_name);
@@ -3102,6 +3104,196 @@ out2:
return ret;
}
+static int regulator_get_optimal_voltage(struct regulator_dev *rdev)
+{
+ struct coupling_desc *c_desc = &rdev->coupling_desc;
+ struct regulator_dev **c_rdevs = c_desc->coupled_rdevs;
+ int max_spread = rdev->constraints->max_spread;
+ int n_coupled = c_desc->n_coupled;
+ int desired_min_uV, desired_max_uV, min_current_uV = INT_MAX;
+ int max_current_uV = 0, highest_min_uV = 0, target_uV, possible_uV;
+ int i, ret;
+
+ /* If consumers don't provide any demands, set voltage to min_uV */
+ desired_min_uV = rdev->constraints->min_uV;
+ desired_max_uV = rdev->constraints->max_uV;
+ ret = regulator_check_consumers(rdev,
+ &desired_min_uV,
+ &desired_max_uV, PM_SUSPEND_ON);
+ if (ret < 0)
+ goto out;
+
+ /*
+ * If there are no coupled regulators, simply set the voltage demanded
+ * by consumers.
+ */
+ if (n_coupled == 1) {
+ ret = desired_min_uV;
+ goto out;
+ }
+
+ /* Find highest min desired voltage */
+ for (i = 0; i < n_coupled; i++) {
+ int tmp_min = 0;
+ int tmp_max = INT_MAX;
+
+ if (!_regulator_is_enabled(c_rdevs[i]))
+ continue;
+
+ ret = regulator_check_consumers(c_rdevs[i],
+ &tmp_min,
+ &tmp_max, PM_SUSPEND_ON);
+ if (ret < 0)
+ goto out;
+
+ if (tmp_min > highest_min_uV)
+ highest_min_uV = tmp_min;
+ }
+
+ /*
+ * Let target_uV be equal to the desired one if possible.
+ * If not, set it to minimum voltage, allowed by other coupled
+ * regulators.
+ */
+ target_uV = max(desired_min_uV, highest_min_uV - max_spread);
+
+ /*
+ * Find min and max voltages, which currently aren't
+ * violating max_spread
+ */
+ for (i = 0; i < n_coupled; i++) {
+ int tmp_act;
+
+ /*
+ * Don't check the regulator, which is about
+ * to change voltage
+ */
+ if (c_rdevs[i] == rdev)
+ continue;
+ if (!_regulator_is_enabled(c_rdevs[i]))
+ continue;
+
+ tmp_act = _regulator_get_voltage(c_rdevs[i]);
+ if (tmp_act < 0) {
+ ret = tmp_act;
+ goto out;
+ }
+
+ if (tmp_act < min_current_uV)
+ min_current_uV = tmp_act;
+
+ if (tmp_act > max_current_uV)
+ max_current_uV = tmp_act;
+ }
+
+ /* There aren't any other regulators enabled */
+ if (max_current_uV == 0) {
+ possible_uV = target_uV;
+ } else {
+ /*
+ * Correct target voltage, so as it currently isn't
+ * violating max_spread
+ */
+ possible_uV = max(target_uV, max_current_uV - max_spread);
+ possible_uV = min(possible_uV, min_current_uV + max_spread);
+ }
+
+ if (possible_uV > desired_max_uV) {
+ ret = -EINVAL;
+ goto out;
+ }
+ ret = possible_uV;
+
+out:
+ return ret;
+}
+
+static int regulator_balance_voltage(struct regulator_dev *rdev,
+ suspend_state_t state)
+{
+ struct regulator_dev **c_rdevs;
+ struct regulator_dev *best_rdev;
+ struct coupling_desc *c_desc = &rdev->coupling_desc;
+ int n_coupled;
+ int i, best_delta, best_uV, ret = 1;
+
+ c_rdevs = c_desc->coupled_rdevs;
+ n_coupled = c_desc->n_coupled;
+
+ /*
+ * if system is in a state other than PM_SUSPEND_ON, don't check
+ * other coupled regulators
+ */
+ if (state != PM_SUSPEND_ON)
+ n_coupled = 1;
+
+ /*
+ * Find the best possible voltage change on each loop. Leave the loop
+ * if there isn't any possible change.
+ */
+ while (1) {
+ best_delta = 0;
+ best_uV = 0;
+ best_rdev = NULL;
+
+ /*
+ * Find highest difference between optimal voltage
+ * and current voltage.
+ */
+ for (i = 0; i < n_coupled; i++) {
+ /*
+ * optimal_uV is the best voltage that can be set for
+ * i-th regulator at the moment without violating
+ * max_spread constraint in order to balance
+ * the coupled voltages.
+ */
+ int optimal_uV, current_uV;
+
+ optimal_uV = regulator_get_optimal_voltage(c_rdevs[i]);
+ if (optimal_uV < 0) {
+ ret = optimal_uV;
+ goto out;
+ }
+
+ current_uV = _regulator_get_voltage(c_rdevs[i]);
+ if (current_uV < 0) {
+ ret = optimal_uV;
+ goto out;
+ }
+
+ if (abs(best_delta) < abs(optimal_uV - current_uV)) {
+ best_delta = optimal_uV - current_uV;
+ best_rdev = c_rdevs[i];
+ best_uV = optimal_uV;
+ }
+ }
+
+ /* Nothing to change, return successfully */
+ if (!best_rdev) {
+ ret = 0;
+ goto out;
+ }
+
+ /*
+ * Lock just the supply regulators, as the regulator itself
+ * is already locked by regulator_lock_coupled().
+ */
+ if (best_rdev->supply)
+ regulator_lock_supply(best_rdev->supply->rdev);
+
+ ret = regulator_set_voltage_rdev(best_rdev, best_uV,
+ best_uV, state);
+ if (best_rdev->supply)
+ regulator_unlock_supply(best_rdev->supply->rdev);
+
+ if (ret < 0)
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
/**
* regulator_set_voltage - set regulator output voltage
* @regulator: regulator source