mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 21:38:32 +08:00
222 lines
5.3 KiB
C
222 lines
5.3 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
|
||
|
* http://www.samsung.com/
|
||
|
* Author: Marek Szyprowski <m.szyprowski@samsung.com>
|
||
|
*
|
||
|
* Simplified generic voltage coupler from regulator core.c
|
||
|
* The main difference is that it keeps current regulator voltage
|
||
|
* if consumers didn't apply their constraints yet.
|
||
|
*/
|
||
|
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/regulator/coupler.h>
|
||
|
#include <linux/regulator/driver.h>
|
||
|
#include <linux/regulator/machine.h>
|
||
|
|
||
|
static int regulator_get_optimal_voltage(struct regulator_dev *rdev,
|
||
|
int *current_uV,
|
||
|
int *min_uV, int *max_uV,
|
||
|
suspend_state_t state)
|
||
|
{
|
||
|
struct coupling_desc *c_desc = &rdev->coupling_desc;
|
||
|
struct regulator_dev **c_rdevs = c_desc->coupled_rdevs;
|
||
|
struct regulation_constraints *constraints = rdev->constraints;
|
||
|
int desired_min_uV = 0, desired_max_uV = INT_MAX;
|
||
|
int max_current_uV = 0, min_current_uV = INT_MAX;
|
||
|
int highest_min_uV = 0, target_uV, possible_uV;
|
||
|
int i, ret, max_spread, n_coupled = c_desc->n_coupled;
|
||
|
bool done;
|
||
|
|
||
|
*current_uV = -1;
|
||
|
|
||
|
/* Find highest min desired voltage */
|
||
|
for (i = 0; i < n_coupled; i++) {
|
||
|
int tmp_min = 0;
|
||
|
int tmp_max = INT_MAX;
|
||
|
|
||
|
lockdep_assert_held_once(&c_rdevs[i]->mutex.base);
|
||
|
|
||
|
ret = regulator_check_consumers(c_rdevs[i],
|
||
|
&tmp_min,
|
||
|
&tmp_max, state);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
if (tmp_min == 0) {
|
||
|
ret = regulator_get_voltage_rdev(c_rdevs[i]);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
tmp_min = ret;
|
||
|
}
|
||
|
|
||
|
/* apply constraints */
|
||
|
ret = regulator_check_voltage(c_rdevs[i], &tmp_min, &tmp_max);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
highest_min_uV = max(highest_min_uV, tmp_min);
|
||
|
|
||
|
if (i == 0) {
|
||
|
desired_min_uV = tmp_min;
|
||
|
desired_max_uV = tmp_max;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
max_spread = constraints->max_spread[0];
|
||
|
|
||
|
/*
|
||
|
* 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 = 1; i < n_coupled; i++) {
|
||
|
int tmp_act;
|
||
|
|
||
|
tmp_act = regulator_get_voltage_rdev(c_rdevs[i]);
|
||
|
if (tmp_act < 0)
|
||
|
return tmp_act;
|
||
|
|
||
|
min_current_uV = min(tmp_act, min_current_uV);
|
||
|
max_current_uV = max(tmp_act, max_current_uV);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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)
|
||
|
return -EINVAL;
|
||
|
|
||
|
done = (possible_uV == target_uV);
|
||
|
desired_min_uV = possible_uV;
|
||
|
|
||
|
/* Set current_uV if wasn't done earlier in the code and if necessary */
|
||
|
if (*current_uV == -1) {
|
||
|
ret = regulator_get_voltage_rdev(rdev);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
*current_uV = ret;
|
||
|
}
|
||
|
|
||
|
*min_uV = desired_min_uV;
|
||
|
*max_uV = desired_max_uV;
|
||
|
|
||
|
return done;
|
||
|
}
|
||
|
|
||
|
static int exynos_coupler_balance_voltage(struct regulator_coupler *coupler,
|
||
|
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 i, ret, n_coupled, best_min_uV, best_max_uV, best_c_rdev;
|
||
|
unsigned int delta, best_delta;
|
||
|
unsigned long c_rdev_done = 0;
|
||
|
bool best_c_rdev_done;
|
||
|
|
||
|
c_rdevs = c_desc->coupled_rdevs;
|
||
|
n_coupled = c_desc->n_coupled;
|
||
|
|
||
|
/*
|
||
|
* Find the best possible voltage change on each loop. Leave the loop
|
||
|
* if there isn't any possible change.
|
||
|
*/
|
||
|
do {
|
||
|
best_c_rdev_done = false;
|
||
|
best_delta = 0;
|
||
|
best_min_uV = 0;
|
||
|
best_max_uV = 0;
|
||
|
best_c_rdev = 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 = 0, optimal_max_uV = 0, current_uV = 0;
|
||
|
|
||
|
if (test_bit(i, &c_rdev_done))
|
||
|
continue;
|
||
|
|
||
|
ret = regulator_get_optimal_voltage(c_rdevs[i],
|
||
|
¤t_uV,
|
||
|
&optimal_uV,
|
||
|
&optimal_max_uV,
|
||
|
state);
|
||
|
if (ret < 0)
|
||
|
goto out;
|
||
|
|
||
|
delta = abs(optimal_uV - current_uV);
|
||
|
|
||
|
if (delta && best_delta <= delta) {
|
||
|
best_c_rdev_done = ret;
|
||
|
best_delta = delta;
|
||
|
best_rdev = c_rdevs[i];
|
||
|
best_min_uV = optimal_uV;
|
||
|
best_max_uV = optimal_max_uV;
|
||
|
best_c_rdev = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Nothing to change, return successfully */
|
||
|
if (!best_rdev) {
|
||
|
ret = 0;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
ret = regulator_set_voltage_rdev(best_rdev, best_min_uV,
|
||
|
best_max_uV, state);
|
||
|
|
||
|
if (ret < 0)
|
||
|
goto out;
|
||
|
|
||
|
if (best_c_rdev_done)
|
||
|
set_bit(best_c_rdev, &c_rdev_done);
|
||
|
|
||
|
} while (n_coupled > 1);
|
||
|
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int exynos_coupler_attach(struct regulator_coupler *coupler,
|
||
|
struct regulator_dev *rdev)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct regulator_coupler exynos_coupler = {
|
||
|
.attach_regulator = exynos_coupler_attach,
|
||
|
.balance_voltage = exynos_coupler_balance_voltage,
|
||
|
};
|
||
|
|
||
|
static int __init exynos_coupler_init(void)
|
||
|
{
|
||
|
if (!of_machine_is_compatible("samsung,exynos5800"))
|
||
|
return 0;
|
||
|
|
||
|
return regulator_coupler_register(&exynos_coupler);
|
||
|
}
|
||
|
arch_initcall(exynos_coupler_init);
|