feat(regulator): add a regulator framework

Add a regulator framework to:
- provide an interface to consumers and drivers,
- connect consumers with drivers,
- handle most of devicetree-parsing,
- handle always-on and boot-on regulators,
- handle min/max voltages,

Change-Id: I23c939fdef2c71a416c44c9de332f70db0d2aa53
Signed-off-by: Pascal Paillet <p.paillet@st.com>
This commit is contained in:
Pascal Paillet 2020-12-15 18:26:39 +01:00 committed by Yann Gautier
parent ea552bf5a5
commit d5b4a2c4e7
2 changed files with 644 additions and 0 deletions

View File

@ -0,0 +1,536 @@
/*
* Copyright (c) 2021, STMicroelectronics - All Rights Reserved
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <string.h>
#include <common/debug.h>
#include <drivers/delay_timer.h>
#include <drivers/st/regulator.h>
#include <libfdt.h>
#define MAX_PROPERTY_LEN 64
static struct rdev rdev_array[PLAT_NB_RDEVS];
#define for_each_rdev(rdev) \
for (rdev = rdev_array; rdev < (rdev_array + PLAT_NB_RDEVS); rdev++)
#define for_each_registered_rdev(rdev) \
for (rdev = rdev_array; \
(rdev < (rdev_array + PLAT_NB_RDEVS)) && (rdev->desc != NULL); rdev++)
static void lock_driver(const struct rdev *rdev)
{
if (rdev->desc->ops->lock != NULL) {
rdev->desc->ops->lock(rdev->desc);
}
}
static void unlock_driver(const struct rdev *rdev)
{
if (rdev->desc->ops->unlock != NULL) {
rdev->desc->ops->unlock(rdev->desc);
}
}
static struct rdev *regulator_get_by_phandle(int32_t phandle)
{
struct rdev *rdev;
for_each_registered_rdev(rdev) {
if (rdev->phandle == phandle) {
return rdev;
}
}
WARN("%s: phandle %d not found\n", __func__, phandle);
return NULL;
}
/*
* Get a regulator from its node name
*
* @fdt - pointer to device tree memory
* @node_name - name of the node "ldo1"
* Return pointer to rdev if succeed, NULL else.
*/
struct rdev *regulator_get_by_name(const char *node_name)
{
struct rdev *rdev;
assert(node_name != NULL);
VERBOSE("get %s\n", node_name);
for_each_registered_rdev(rdev) {
if (strcmp(rdev->desc->node_name, node_name) == 0) {
return rdev;
}
}
WARN("%s: %s not found\n", __func__, node_name);
return NULL;
}
static int32_t get_supply_phandle(const void *fdt, int node, const char *name)
{
const fdt32_t *cuint;
int len __unused;
int supply_phandle = -FDT_ERR_NOTFOUND;
char prop_name[MAX_PROPERTY_LEN];
len = snprintf(prop_name, MAX_PROPERTY_LEN - 1, "%s-supply", name);
assert((len >= 0) && (len < MAX_PROPERTY_LEN - 1));
cuint = fdt_getprop(fdt, node, prop_name, NULL);
if (cuint != NULL) {
supply_phandle = fdt32_to_cpu(*cuint);
VERBOSE("%s: supplied by %d\n", name, supply_phandle);
}
return supply_phandle;
}
/*
* Get a regulator from a supply name
*
* @fdt - pointer to device tree memory
* @node - offset of the node that contains the supply description
* @name - name of the supply "vdd" for "vdd-supply'
* Return pointer to rdev if succeed, NULL else.
*/
struct rdev *regulator_get_by_supply_name(const void *fdt, int node, const char *name)
{
const int p = get_supply_phandle(fdt, node, name);
if (p < 0) {
return NULL;
}
return regulator_get_by_phandle(p);
}
static int __regulator_set_state(struct rdev *rdev, bool state)
{
if (rdev->desc->ops->set_state == NULL) {
return -ENODEV;
}
return rdev->desc->ops->set_state(rdev->desc, state);
}
/*
* Enable regulator
*
* @rdev - pointer to rdev struct
* Return 0 if succeed, non 0 else.
*/
int regulator_enable(struct rdev *rdev)
{
int ret;
assert(rdev != NULL);
ret = __regulator_set_state(rdev, STATE_ENABLE);
udelay(rdev->enable_ramp_delay);
return ret;
}
/*
* Disable regulator
*
* @rdev - pointer to rdev struct
* Return 0 if succeed, non 0 else.
*/
int regulator_disable(struct rdev *rdev)
{
int ret;
assert(rdev != NULL);
ret = __regulator_set_state(rdev, STATE_DISABLE);
udelay(rdev->enable_ramp_delay);
return ret;
}
/*
* Regulator enabled query
*
* @rdev - pointer to rdev struct
* Return 0 if disabled, 1 if enabled, <0 else.
*/
int regulator_is_enabled(const struct rdev *rdev)
{
int ret;
assert(rdev != NULL);
VERBOSE("%s: is en\n", rdev->desc->node_name);
if (rdev->desc->ops->get_state == NULL) {
return -ENODEV;
}
lock_driver(rdev);
ret = rdev->desc->ops->get_state(rdev->desc);
if (ret < 0) {
ERROR("regul %s get state failed: err:%d\n",
rdev->desc->node_name, ret);
}
unlock_driver(rdev);
return ret;
}
/*
* Set regulator voltage
*
* @rdev - pointer to rdev struct
* @mvolt - Target voltage level in millivolt
* Return 0 if succeed, non 0 else.
*/
int regulator_set_voltage(struct rdev *rdev, uint16_t mvolt)
{
int ret;
assert(rdev != NULL);
VERBOSE("%s: set mvolt\n", rdev->desc->node_name);
if (rdev->desc->ops->set_voltage == NULL) {
return -ENODEV;
}
if ((mvolt < rdev->min_mv) || (mvolt > rdev->max_mv)) {
return -EPERM;
}
lock_driver(rdev);
ret = rdev->desc->ops->set_voltage(rdev->desc, mvolt);
if (ret < 0) {
ERROR("regul %s set volt failed: err:%d\n",
rdev->desc->node_name, ret);
}
unlock_driver(rdev);
return ret;
}
/*
* Set regulator min voltage
*
* @rdev - pointer to rdev struct
* Return 0 if succeed, non 0 else.
*/
int regulator_set_min_voltage(struct rdev *rdev)
{
return regulator_set_voltage(rdev, rdev->min_mv);
}
/*
* Get regulator voltage
*
* @rdev - pointer to rdev struct
* Return milli volts if succeed, <0 else.
*/
int regulator_get_voltage(const struct rdev *rdev)
{
int ret;
assert(rdev != NULL);
VERBOSE("%s: get volt\n", rdev->desc->node_name);
if (rdev->desc->ops->get_voltage == NULL) {
return rdev->min_mv;
}
lock_driver(rdev);
ret = rdev->desc->ops->get_voltage(rdev->desc);
if (ret < 0) {
ERROR("regul %s get voltage failed: err:%d\n",
rdev->desc->node_name, ret);
}
unlock_driver(rdev);
return ret;
}
/*
* List regulator voltages
*
* @rdev - pointer to rdev struct
* @levels - out: array of supported millitvolt levels from min to max value
* @count - out: number of possible millivolt values
* Return 0 if succeed, non 0 else.
*/
int regulator_list_voltages(const struct rdev *rdev, const uint16_t **levels, size_t *count)
{
int ret;
size_t n;
assert(rdev != NULL);
assert(levels != NULL);
assert(count != NULL);
VERBOSE("%s: list volt\n", rdev->desc->node_name);
if (rdev->desc->ops->list_voltages == NULL) {
return -ENODEV;
}
lock_driver(rdev);
ret = rdev->desc->ops->list_voltages(rdev->desc, levels, count);
unlock_driver(rdev);
if (ret < 0) {
ERROR("regul %s list_voltages failed: err: %d\n",
rdev->desc->node_name, ret);
return ret;
}
/*
* Reduce the possible values depending on min and max from device-tree
*/
n = *count;
while ((n > 1U) && ((*levels)[n - 1U] > rdev->max_mv)) {
n--;
}
/* Verify that max val is a valid value */
if (rdev->max_mv != (*levels)[n - 1]) {
ERROR("regul %s: max value %u is invalid\n",
rdev->desc->node_name, rdev->max_mv);
return -EINVAL;
}
while ((n > 1U) && ((*levels[0U]) < rdev->min_mv)) {
(*levels)++;
n--;
}
/* Verify that min is not too high */
if (n == 0U) {
ERROR("regul %s set min voltage is too high\n",
rdev->desc->node_name);
return -EINVAL;
}
/* Verify that min val is a valid vlue */
if (rdev->min_mv != (*levels)[0U]) {
ERROR("regul %s: min value %u is invalid\n",
rdev->desc->node_name, rdev->min_mv);
return -EINVAL;
}
*count = n;
VERBOSE("rdev->min_mv=%u rdev->max_mv=%u\n", rdev->min_mv, rdev->max_mv);
return 0;
}
/*
* Get regulator voltages range
*
* @rdev - pointer to rdev struct
* @min_mv - out: min possible millivolt value
* @max_mv - out: max possible millivolt value
* Return 0 if succeed, non 0 else.
*/
void regulator_get_range(const struct rdev *rdev, uint16_t *min_mv, uint16_t *max_mv)
{
assert(rdev != NULL);
if (min_mv != NULL) {
*min_mv = rdev->min_mv;
}
if (max_mv != NULL) {
*max_mv = rdev->max_mv;
}
}
/*
* Set regulator flag
*
* @rdev - pointer to rdev struct
* @flag - flag value to set (eg: REGUL_OCP)
* Return 0 if succeed, non 0 else.
*/
int regulator_set_flag(struct rdev *rdev, uint16_t flag)
{
int ret;
/* check that only one bit is set on flag */
if (__builtin_popcount(flag) != 1) {
return -EINVAL;
}
/* REGUL_ALWAYS_ON and REGUL_BOOT_ON are internal properties of the core */
if ((flag == REGUL_ALWAYS_ON) || (flag == REGUL_BOOT_ON)) {
rdev->flags |= flag;
return 0;
}
if (rdev->desc->ops->set_flag == NULL) {
ERROR("%s can not set any flag\n", rdev->desc->node_name);
return -ENODEV;
}
lock_driver(rdev);
ret = rdev->desc->ops->set_flag(rdev->desc, flag);
unlock_driver(rdev);
if (ret != 0) {
ERROR("%s: could not set flag %d ret=%d\n",
rdev->desc->node_name, flag, ret);
return ret;
}
rdev->flags |= flag;
return 0;
}
/*
* Parse the device-tree for a regulator
*
* Read min/max voltage from dt and check its validity
* Read the properties, and call the driver to set flags
* Read power supply phandle
* Read and store low power mode states
*
* @rdev - pointer to rdev struct
* @node - device-tree node offset of the regulator
* Return 0 if disabled, 1 if enabled, <0 else.
*/
static int parse_dt(struct rdev *rdev, int node)
{
void *fdt;
const fdt32_t *cuint;
const uint16_t *levels;
size_t size;
int ret;
VERBOSE("%s: parse dt\n", rdev->desc->node_name);
if (fdt_get_address(&fdt) == 0) {
return -ENOENT;
}
rdev->phandle = fdt_get_phandle(fdt, node);
cuint = fdt_getprop(fdt, node, "regulator-min-microvolt", NULL);
if (cuint != NULL) {
uint16_t min_mv;
min_mv = (uint16_t)(fdt32_to_cpu(*cuint) / 1000U);
VERBOSE("%s: min_mv=%d\n", rdev->desc->node_name, (int)min_mv);
if (min_mv <= rdev->max_mv) {
rdev->min_mv = min_mv;
} else {
ERROR("%s: min_mv=%d is too high\n",
rdev->desc->node_name, (int)min_mv);
return -EINVAL;
}
}
cuint = fdt_getprop(fdt, node, "regulator-max-microvolt", NULL);
if (cuint != NULL) {
uint16_t max_mv;
max_mv = (uint16_t)(fdt32_to_cpu(*cuint) / 1000U);
VERBOSE("%s: max_mv=%d\n", rdev->desc->node_name, (int)max_mv);
if (max_mv >= rdev->min_mv) {
rdev->max_mv = max_mv;
} else {
ERROR("%s: max_mv=%d is too low\n",
rdev->desc->node_name, (int)max_mv);
return -EINVAL;
}
}
/* validate that min and max values can be used */
ret = regulator_list_voltages(rdev, &levels, &size);
if ((ret != 0) && (ret != -ENODEV)) {
return ret;
}
return 0;
}
/*
* Register a regulator driver in regulator framework.
* Initialize voltage range from driver description
*
* @desc - pointer to the regulator description
* @node - device-tree node offset of the regulator
* Return 0 if succeed, non 0 else.
*/
int regulator_register(const struct regul_description *desc, int node)
{
struct rdev *rdev;
assert(desc != NULL);
VERBOSE("register %s\n", desc->node_name);
for_each_rdev(rdev) {
if (rdev->desc == NULL) {
break;
}
}
if (rdev == rdev_array + PLAT_NB_RDEVS) {
WARN("Not enough place for regulators, PLAT_NB_RDEVS should be increased.\n");
return -ENOMEM;
}
rdev->desc = desc;
rdev->enable_ramp_delay = rdev->desc->enable_ramp_delay;
if (rdev->desc->ops->list_voltages != NULL) {
int ret;
const uint16_t *levels;
size_t count;
lock_driver(rdev);
ret = rdev->desc->ops->list_voltages(rdev->desc, &levels, &count);
unlock_driver(rdev);
if (ret < 0) {
ERROR("regul %s set state failed: err:%d\n",
rdev->desc->node_name, ret);
return ret;
}
rdev->min_mv = levels[0];
rdev->max_mv = levels[count - 1U];
} else {
rdev->max_mv = UINT16_MAX;
}
return parse_dt(rdev, node);
}

View File

@ -0,0 +1,108 @@
/*
* Copyright (c) 2021, STMicroelectronics - All Rights Reserved
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef REGULATOR_H
#define REGULATOR_H
#include <platform_def.h>
#ifndef PLAT_NB_RDEVS
#error "Missing PLAT_NB_RDEVS"
#endif
/*
* Consumer interface
*/
/* regulator-always-on : regulator should never be disabled */
#define REGUL_ALWAYS_ON BIT(0)
/*
* regulator-boot-on:
* It's expected that this regulator was left on by the bootloader.
* The core shouldn't prevent it from being turned off later.
* The regulator is needed to exit from suspend so it is turned on during suspend entry.
*/
#define REGUL_BOOT_ON BIT(1)
/* regulator-over-current-protection: Enable over current protection. */
#define REGUL_OCP BIT(2)
/* regulator-active-discharge: enable active discharge. */
#define REGUL_ACTIVE_DISCHARGE BIT(3)
/* regulator-pull-down: Enable pull down resistor when the regulator is disabled. */
#define REGUL_PULL_DOWN BIT(4)
/*
* st,mask-reset: set mask reset for the regulator, meaning that the regulator
* setting is maintained during pmic reset.
*/
#define REGUL_MASK_RESET BIT(5)
/* st,regulator-sink-source: set the regulator in sink source mode */
#define REGUL_SINK_SOURCE BIT(6)
/* st,regulator-bypass: set the regulator in bypass mode */
#define REGUL_ENABLE_BYPASS BIT(7)
struct rdev *regulator_get_by_name(const char *node_name);
struct rdev *regulator_get_by_supply_name(const void *fdt, int node, const char *name);
int regulator_enable(struct rdev *rdev);
int regulator_disable(struct rdev *rdev);
int regulator_is_enabled(const struct rdev *rdev);
int regulator_set_voltage(struct rdev *rdev, uint16_t volt);
int regulator_set_min_voltage(struct rdev *rdev);
int regulator_get_voltage(const struct rdev *rdev);
int regulator_list_voltages(const struct rdev *rdev, const uint16_t **levels, size_t *count);
void regulator_get_range(const struct rdev *rdev, uint16_t *min_mv, uint16_t *max_mv);
int regulator_set_flag(struct rdev *rdev, uint16_t flag);
/*
* Driver Interface
*/
/* set_state() arguments */
#define STATE_DISABLE false
#define STATE_ENABLE true
struct regul_description {
const char *node_name;
const struct regul_ops *ops;
const void *driver_data;
const char *supply_name;
const uint32_t enable_ramp_delay;
};
struct regul_ops {
int (*set_state)(const struct regul_description *desc, bool state);
int (*get_state)(const struct regul_description *desc);
int (*set_voltage)(const struct regul_description *desc, uint16_t mv);
int (*get_voltage)(const struct regul_description *desc);
int (*list_voltages)(const struct regul_description *desc,
const uint16_t **levels, size_t *count);
int (*set_flag)(const struct regul_description *desc, uint16_t flag);
void (*lock)(const struct regul_description *desc);
void (*unlock)(const struct regul_description *desc);
};
int regulator_register(const struct regul_description *desc, int node);
/*
* Internal regulator structure
* The structure is internal to the core, and the content should not be used
* by a consumer nor a driver.
*/
struct rdev {
const struct regul_description *desc;
int32_t phandle;
uint16_t min_mv;
uint16_t max_mv;
uint16_t flags;
uint32_t enable_ramp_delay;
};
#endif /* REGULATOR_H */