drivers: regulator: add support to PMIC regulator driver for modes
Add support for setting the target mode for a PMIC regulator. Some regulators support multiple modes, each with distinctive voltage and current configuration data. This function allows the consumer to switch the PMIC into a new mode. The PMIC can then be configured to use a new set of voltages. Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>
This commit is contained in:
parent
1d50265a2f
commit
f37c8cc77e
2 changed files with 140 additions and 14 deletions
|
@ -18,6 +18,7 @@
|
||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/drivers/regulator.h>
|
#include <zephyr/drivers/regulator.h>
|
||||||
#include <zephyr/drivers/regulator/consumer.h>
|
#include <zephyr/drivers/regulator/consumer.h>
|
||||||
|
#include <zephyr/dt-bindings/regulator/pmic_i2c.h>
|
||||||
#include <zephyr/drivers/i2c.h>
|
#include <zephyr/drivers/i2c.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
|
@ -38,11 +39,13 @@ struct regulator_data {
|
||||||
struct onoff_sync_service srv;
|
struct onoff_sync_service srv;
|
||||||
const struct voltage_range *voltages;
|
const struct voltage_range *voltages;
|
||||||
const struct current_range *current_levels;
|
const struct current_range *current_levels;
|
||||||
|
uint8_t reg_offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct regulator_config {
|
struct regulator_config {
|
||||||
int num_voltages;
|
int num_voltages;
|
||||||
int num_current_levels;
|
int num_current_levels;
|
||||||
|
int num_modes;
|
||||||
uint8_t vsel_reg;
|
uint8_t vsel_reg;
|
||||||
uint8_t vsel_mask;
|
uint8_t vsel_mask;
|
||||||
uint32_t max_uV;
|
uint32_t max_uV;
|
||||||
|
@ -54,38 +57,56 @@ struct regulator_config {
|
||||||
uint8_t ilim_reg;
|
uint8_t ilim_reg;
|
||||||
uint8_t ilim_mask;
|
uint8_t ilim_mask;
|
||||||
struct i2c_dt_spec i2c;
|
struct i2c_dt_spec i2c;
|
||||||
|
uint16_t initial_mode;
|
||||||
uint32_t *voltage_array;
|
uint32_t *voltage_array;
|
||||||
uint32_t *current_array;
|
uint32_t *current_array;
|
||||||
|
uint16_t *allowed_modes;
|
||||||
|
uint8_t modesel_offset;
|
||||||
|
uint8_t modesel_reg;
|
||||||
|
uint8_t modesel_mask;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a register from the PMIC
|
* Reads a register from the PMIC
|
||||||
* Returns 0 on success, or errno on error
|
* Returns 0 on success, or errno on error
|
||||||
*/
|
*/
|
||||||
static int regulator_read_register(const struct regulator_config *conf,
|
static int regulator_read_register(const struct device *dev,
|
||||||
uint8_t reg, uint8_t *out)
|
uint8_t reg, uint8_t *out)
|
||||||
{
|
{
|
||||||
return i2c_reg_read_byte_dt(&conf->i2c, reg, out);
|
const struct regulator_config *conf = dev->config;
|
||||||
|
struct regulator_data *data = dev->data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Apply mode offset to register */
|
||||||
|
reg += data->reg_offset;
|
||||||
|
ret = i2c_reg_read_byte_dt(&conf->i2c, reg, out);
|
||||||
|
LOG_DBG("READ 0x%x: 0x%x", reg, *out);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifies a register within the PMIC
|
* Modifies a register within the PMIC
|
||||||
* Returns 0 on success, or errno on error
|
* Returns 0 on success, or errno on error
|
||||||
*/
|
*/
|
||||||
static int regulator_modify_register(const struct regulator_config *conf,
|
static int regulator_modify_register(const struct device *dev,
|
||||||
uint8_t reg, uint8_t reg_mask, uint8_t reg_val)
|
uint8_t reg, uint8_t reg_mask, uint8_t reg_val)
|
||||||
{
|
{
|
||||||
|
const struct regulator_config *conf = dev->config;
|
||||||
|
struct regulator_data *data = dev->data;
|
||||||
uint8_t reg_current;
|
uint8_t reg_current;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
rc = regulator_read_register(conf, reg, ®_current);
|
rc = regulator_read_register(dev, reg, ®_current);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Apply mode offset to register */
|
||||||
|
reg += data->reg_offset;
|
||||||
reg_current &= ~reg_mask;
|
reg_current &= ~reg_mask;
|
||||||
reg_current |= reg_val;
|
reg_current |= (reg_val & reg_mask);
|
||||||
LOG_DBG("Writing 0x%02X to reg 0x%02X at I2C addr 0x%02X", reg_current, reg,
|
LOG_DBG("WRITE 0x%02X to 0x%02X at I2C addr 0x%02X", reg_current,
|
||||||
conf->i2c.addr);
|
reg, conf->i2c.addr);
|
||||||
return i2c_reg_write_byte_dt(&conf->i2c, reg, reg_current);
|
return i2c_reg_write_byte_dt(&conf->i2c, reg, reg_current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +122,17 @@ int regulator_count_voltages(const struct device *dev)
|
||||||
return config->num_voltages;
|
return config->num_voltages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of the extended regulator consumer API
|
||||||
|
* Counts the number of modes supported by a regulator
|
||||||
|
*/
|
||||||
|
int regulator_count_modes(const struct device *dev)
|
||||||
|
{
|
||||||
|
const struct regulator_config *config = dev->config;
|
||||||
|
|
||||||
|
return config->num_modes;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Part of the extended regulator consumer API
|
* Part of the extended regulator consumer API
|
||||||
* Returns the supported voltage in uV for a given selector value
|
* Returns the supported voltage in uV for a given selector value
|
||||||
|
@ -157,7 +189,7 @@ int regulator_set_voltage(const struct device *dev, int min_uV, int max_uV)
|
||||||
}
|
}
|
||||||
LOG_DBG("Setting regulator %s to %duV", dev->name,
|
LOG_DBG("Setting regulator %s to %duV", dev->name,
|
||||||
data->voltages[i].uV);
|
data->voltages[i].uV);
|
||||||
return regulator_modify_register(config, config->vsel_reg,
|
return regulator_modify_register(dev, config->vsel_reg,
|
||||||
config->vsel_mask, data->voltages[i].reg_val);
|
config->vsel_mask, data->voltages[i].reg_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +204,7 @@ int regulator_get_voltage(const struct device *dev)
|
||||||
int rc, i = 0;
|
int rc, i = 0;
|
||||||
uint8_t raw_reg;
|
uint8_t raw_reg;
|
||||||
|
|
||||||
rc = regulator_read_register(config, config->vsel_reg, &raw_reg);
|
rc = regulator_read_register(dev, config->vsel_reg, &raw_reg);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
@ -213,7 +245,7 @@ int regulator_set_current_limit(const struct device *dev, int min_uA, int max_uA
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
/* Set the current limit */
|
/* Set the current limit */
|
||||||
return regulator_modify_register(config, config->ilim_reg,
|
return regulator_modify_register(dev, config->ilim_reg,
|
||||||
config->ilim_mask, data->current_levels[i].reg_val);
|
config->ilim_mask, data->current_levels[i].reg_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +263,7 @@ int regulator_get_current_limit(const struct device *dev)
|
||||||
if (config->num_current_levels == 0) {
|
if (config->num_current_levels == 0) {
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
rc = regulator_read_register(config, config->ilim_reg, &raw_reg);
|
rc = regulator_read_register(dev, config->ilim_reg, &raw_reg);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
@ -246,6 +278,61 @@ int regulator_get_current_limit(const struct device *dev)
|
||||||
return data->current_levels[i].uA;
|
return data->current_levels[i].uA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Part of the extended regulator consumer API
|
||||||
|
* switches the regulator to a given mode. This API will apply a mode for
|
||||||
|
* the regulator, and also configure the remainder of the regulator APIs,
|
||||||
|
* such as those disabling, changing voltage/current targets, or querying
|
||||||
|
* voltage/current targets to target that mode.
|
||||||
|
*/
|
||||||
|
int regulator_set_mode(const struct device *dev, uint32_t mode)
|
||||||
|
{
|
||||||
|
const struct regulator_config *config = dev->config;
|
||||||
|
struct regulator_data *data = dev->data;
|
||||||
|
int rc;
|
||||||
|
uint8_t i, sel_off;
|
||||||
|
|
||||||
|
if (config->num_modes == 0) {
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search for mode ID in allowed modes. */
|
||||||
|
for (i = 0 ; i < config->num_modes; i++) {
|
||||||
|
if (config->allowed_modes[i] == mode) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == config->num_modes) {
|
||||||
|
/* Mode was not found */
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
sel_off = ((mode & PMIC_MODE_OFFSET_MASK) >> PMIC_MODE_OFFSET_SHIFT);
|
||||||
|
/* Configure mode */
|
||||||
|
if (mode & PMIC_MODE_FLAG_MODESEL_MULTI_REG) {
|
||||||
|
/* Select mode with offset calculation */
|
||||||
|
/* Set reg_offset here so it takes effect for the write
|
||||||
|
* to modesel_reg
|
||||||
|
*/
|
||||||
|
data->reg_offset = sel_off;
|
||||||
|
rc = regulator_modify_register(dev, config->modesel_reg,
|
||||||
|
mode & PMIC_MODE_SELECTOR_MASK, config->modesel_mask);
|
||||||
|
} else {
|
||||||
|
/* Select mode without offset to modesel_reg */
|
||||||
|
/* Clear register offset */
|
||||||
|
data->reg_offset = 0;
|
||||||
|
rc = regulator_modify_register(dev, config->modesel_reg,
|
||||||
|
mode & PMIC_MODE_SELECTOR_MASK, config->modesel_mask);
|
||||||
|
if (rc) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
/* Since we did not use a register offset when selecting the
|
||||||
|
* mode, but we now are targeting a specific mode's bank
|
||||||
|
* of registers, we must still set the register offset here
|
||||||
|
*/
|
||||||
|
data->reg_offset = sel_off;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
static int enable_regulator(const struct device *dev, struct onoff_client *cli)
|
static int enable_regulator(const struct device *dev, struct onoff_client *cli)
|
||||||
{
|
{
|
||||||
|
@ -262,7 +349,7 @@ static int enable_regulator(const struct device *dev, struct onoff_client *cli)
|
||||||
return onoff_sync_finalize(&data->srv, key, cli, rc, true);
|
return onoff_sync_finalize(&data->srv, key, cli, rc, true);
|
||||||
}
|
}
|
||||||
en_val = config->enable_inverted ? 0 : config->enable_val;
|
en_val = config->enable_inverted ? 0 : config->enable_val;
|
||||||
rc = regulator_modify_register(config, config->enable_reg,
|
rc = regulator_modify_register(dev, config->enable_reg,
|
||||||
config->enable_mask, en_val);
|
config->enable_mask, en_val);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
return onoff_sync_finalize(&data->srv, key, NULL, rc, false);
|
return onoff_sync_finalize(&data->srv, key, NULL, rc, false);
|
||||||
|
@ -285,7 +372,7 @@ static int disable_regulator(const struct device *dev)
|
||||||
return onoff_sync_finalize(&data->srv, key, NULL, rc, false);
|
return onoff_sync_finalize(&data->srv, key, NULL, rc, false);
|
||||||
}
|
}
|
||||||
dis_val = config->enable_inverted ? config->enable_val : 0;
|
dis_val = config->enable_inverted ? config->enable_val : 0;
|
||||||
rc = regulator_modify_register(config, config->enable_reg,
|
rc = regulator_modify_register(dev, config->enable_reg,
|
||||||
config->enable_mask, dis_val);
|
config->enable_mask, dis_val);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
/* Error writing configs */
|
/* Error writing configs */
|
||||||
|
@ -309,6 +396,9 @@ static int pmic_reg_init(const struct device *dev)
|
||||||
if (!device_is_ready(config->i2c.bus)) {
|
if (!device_is_ready(config->i2c.bus)) {
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
if (config->initial_mode) {
|
||||||
|
return regulator_set_mode(dev, config->initial_mode);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,7 +421,11 @@ static const struct regulator_driver_api api = {
|
||||||
DT_PROP_OR(node, current_levels, {}); \
|
DT_PROP_OR(node, current_levels, {}); \
|
||||||
static uint32_t pmic_reg_##ord##_vol_range[] = \
|
static uint32_t pmic_reg_##ord##_vol_range[] = \
|
||||||
DT_PROP(node, voltage_range); \
|
DT_PROP(node, voltage_range); \
|
||||||
static struct regulator_data pmic_reg_##ord##_data; \
|
static uint16_t pmic_reg_##ord##_allowed_modes[] = \
|
||||||
|
DT_PROP_OR(DT_PARENT(node), regulator_allowed_modes, {}); \
|
||||||
|
static struct regulator_data pmic_reg_##ord##_data = { \
|
||||||
|
.reg_offset = 0, \
|
||||||
|
}; \
|
||||||
static struct regulator_config pmic_reg_##ord##_cfg = { \
|
static struct regulator_config pmic_reg_##ord##_cfg = { \
|
||||||
.vsel_mask = DT_PROP(node, vsel_mask), \
|
.vsel_mask = DT_PROP(node, vsel_mask), \
|
||||||
.vsel_reg = DT_PROP(node, vsel_reg), \
|
.vsel_reg = DT_PROP(node, vsel_reg), \
|
||||||
|
@ -345,9 +439,15 @@ static const struct regulator_driver_api api = {
|
||||||
.ilim_reg = DT_PROP_OR(node, ilim_reg, 0), \
|
.ilim_reg = DT_PROP_OR(node, ilim_reg, 0), \
|
||||||
.ilim_mask = DT_PROP_OR(node, ilim_mask, 0), \
|
.ilim_mask = DT_PROP_OR(node, ilim_mask, 0), \
|
||||||
.enable_inverted = DT_PROP(node, enable_inverted), \
|
.enable_inverted = DT_PROP(node, enable_inverted), \
|
||||||
|
.num_modes = ARRAY_SIZE(pmic_reg_##ord##_allowed_modes), \
|
||||||
|
.initial_mode = DT_PROP_OR(DT_PARENT(node), regulator_initial_mode, 0), \
|
||||||
.i2c = I2C_DT_SPEC_GET(DT_PARENT(node)), \
|
.i2c = I2C_DT_SPEC_GET(DT_PARENT(node)), \
|
||||||
.voltage_array = pmic_reg_##ord##_vol_range, \
|
.voltage_array = pmic_reg_##ord##_vol_range, \
|
||||||
.current_array = pmic_reg_##ord##_cur_limits, \
|
.current_array = pmic_reg_##ord##_cur_limits, \
|
||||||
|
.allowed_modes = pmic_reg_##ord##_allowed_modes, \
|
||||||
|
.modesel_offset = DT_PROP_OR(DT_PARENT(node), modesel_offset, 0), \
|
||||||
|
.modesel_reg = DT_PROP_OR(DT_PARENT(node), modesel_reg, 0), \
|
||||||
|
.modesel_mask = DT_PROP_OR(DT_PARENT(node), modesel_mask, 0), \
|
||||||
}; \
|
}; \
|
||||||
DEVICE_DT_DEFINE(node, pmic_reg_init, NULL, \
|
DEVICE_DT_DEFINE(node, pmic_reg_init, NULL, \
|
||||||
&pmic_reg_##ord##_data, \
|
&pmic_reg_##ord##_data, \
|
||||||
|
|
|
@ -34,6 +34,17 @@ extern "C" {
|
||||||
*/
|
*/
|
||||||
int regulator_count_voltages(const struct device *dev);
|
int regulator_count_voltages(const struct device *dev);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the number of supported regulator modes
|
||||||
|
* Returns the number of supported regulator modes. Many regulators will only
|
||||||
|
* support one mode. Regulator modes can be set and selected with
|
||||||
|
* regulator_set_mode
|
||||||
|
*
|
||||||
|
* @param dev: Regulator device to count supported regulator modes for
|
||||||
|
* @return number of supported modes
|
||||||
|
*/
|
||||||
|
int regulator_count_modes(const struct device *dev);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Return supported voltage
|
* @brief Return supported voltage
|
||||||
* Returns a voltage that can be passed to @ref regulator_set_voltage(), zero
|
* Returns a voltage that can be passed to @ref regulator_set_voltage(), zero
|
||||||
|
@ -91,6 +102,21 @@ int regulator_set_current_limit(const struct device *dev, int min_uA, int max_uA
|
||||||
*/
|
*/
|
||||||
int regulator_get_current_limit(const struct device *dev);
|
int regulator_get_current_limit(const struct device *dev);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Select mode of regulator
|
||||||
|
* Regulators can support multiple modes in order to permit different voltage
|
||||||
|
* configuration or better power savings. This API will apply a mode for
|
||||||
|
* the regulator, and also configure the remainder of the regulator APIs,
|
||||||
|
* such as those disabling, changing voltage/current targets, or querying
|
||||||
|
* voltage/current targets to target that mode.
|
||||||
|
* @param dev: regulator to switch mode for
|
||||||
|
* @param mode: Mode to select for this regulator. Only modes present
|
||||||
|
* in the regulator-allowed-modes property are permitted.
|
||||||
|
* @return 0 on success, or errno on error
|
||||||
|
*/
|
||||||
|
int regulator_set_mode(const struct device *dev, uint32_t mode);
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue