zephyr/drivers/regulator/regulator_pca9420.c
Daniel DeGrasse 402d4eb0da drivers: regulator: pca9420: fix support for DVS modes
Fix support for DVS modes, as the dvs_state_set implementation
previously had the mask and value parameters in the
i2c_reg_update_byte_dt function swapped.

Also, record the active DVS state and update the voltage get/set and
regulator enable/disable function to target the active DVS mode. This
will enable a user to configure multiple run modes, and modify target
voltages for those modes when in the new mode. When a user is utilizing
the MODESEL pins, update the active state but return an error so that
the application can still edit settings for the new DVS mode once it has
reconfigured the appropriate pins.

Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>
2023-06-07 05:48:11 -04:00

483 lines
16 KiB
C

/*
* Copyright (c) 2021 NXP
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_pca9420
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/regulator.h>
#include <zephyr/drivers/regulator/pca9420.h>
#include <zephyr/sys/linear_range.h>
#include <zephyr/sys/util.h>
/** Register memory map. See datasheet for more details. */
/** General purpose registers */
/** @brief Top level system ctrl 0 */
#define PCA9420_TOP_CNTL0 0x09U
/** @brief Top level system ctrl 3 */
#define PCA9420_TOP_CNTL3 0x0CU
/** Regulator status indication registers */
/** @brief Mode configuration for mode 0_0 */
#define PCA9420_MODECFG_0_0 0x22U
/** @brief Mode configuration for mode 0_1 */
#define PCA9420_MODECFG_0_1 0x23U
/** @brief Mode configuration for mode 0_2 */
#define PCA9420_MODECFG_0_2 0x24U
/** @brief Mode configuration for mode 0_3 */
#define PCA9420_MODECFG_0_3 0x25U
/** @brief VIN input current limit selection */
#define PCA9420_TOP_CNTL0_VIN_ILIM_SEL_POS 5U
#define PCA9420_TOP_CNTL0_VIN_ILIM_SEL_MASK 0xE0U
#define PCA9420_TOP_CNTL0_VIN_ILIM_SEL_DISABLED 0x7U
/** @brief I2C Mode control mask */
#define PCA9420_TOP_CNTL3_MODE_I2C_POS 3U
#define PCA9420_TOP_CNTL3_MODE_I2C_MASK 0x18U
/*
* @brief Mode control selection mask. When this bit is set, the external
* PMIC pins MODESEL0 and MODESEL1 can be used to select the active mode
*/
#define PCA9420_MODECFG_0_X_EN_MODE_SEL_BY_PIN 0x40U
/*
* @brief Mode configuration upon falling edge applied to ON pin. If set,
* the device will switch to mode 0 when a valid falling edge is applied.
* to the ON pin
*/
/** @brief Mode output voltage mask */
#define PCA9420_MODECFG_0_SW1_OUT_MASK 0x3FU
#define PCA9420_MODECFG_0_SW1_OUT_POS 0U
/** @brief SW2_OUT offset and voltage level mask */
#define PCA9420_MODECFG_1_SW2_OUT_MASK 0x3FU
#define PCA9420_MODECFG_1_SW2_OUT_POS 0U
/** @brief LDO1_OUT voltage level mask */
#define PCA9420_MODECFG_2_LDO1_OUT_MASK 0xF0U
#define PCA9420_MODECFG_2_LDO1_OUT_POS 4U
/** @brief SW1 Enable */
#define PCA9420_MODECFG_2_SW1_EN_MASK 0x08U
#define PCA9420_MODECFG_2_SW1_EN_VAL 0x08U
/** @brief SW2 Enable */
#define PCA9420_MODECFG_2_SW2_EN_MASK 0x04U
#define PCA9420_MODECFG_2_SW2_EN_VAL 0x04U
/** @brief LDO1 Enable */
#define PCA9420_MODECFG_2_LDO1_EN_MASK 0x02U
#define PCA9420_MODECFG_2_LDO1_EN_VAL 0x02U
/** @brief LDO2 Enable */
#define PCA9420_MODECFG_2_LDO2_EN_MASK 0x01U
#define PCA9420_MODECFG_2_LDO2_EN_VAL 0x01U
/** @brief LDO2_OUT offset and voltage level mask */
#define PCA9420_MODECFG_3_LDO2_OUT_MASK 0x3FU
#define PCA9420_MODECFG_3_LDO2_OUT_POS 0U
/** VIN ILIM resolution, uA/LSB */
#define PCA9420_VIN_ILIM_UA_LSB 170000
/** VIN ILIM minimum value, uA */
#define PCA9420_VIN_ILIM_MIN_UA 85000
/** Number of modes */
#define PCA9420_NUM_MODES 4U
/** Offset applied to MODECFG* registers for a given mode */
#define PCA9420_MODECFG_OFFSET(mode) ((mode) * 4U)
struct regulator_pca9420_desc {
uint8_t enable_reg;
uint8_t enable_mask;
uint8_t enable_val;
uint8_t vsel_reg;
uint8_t vsel_mask;
uint8_t vsel_pos;
int32_t max_ua;
uint8_t num_ranges;
const struct linear_range *ranges;
};
struct regulator_pca9420_common_config {
struct i2c_dt_spec i2c;
int32_t vin_ilim_ua;
bool enable_modesel_pins;
};
struct regulator_pca9420_common_data {
regulator_dvs_state_t dvs_state;
};
struct regulator_pca9420_config {
struct regulator_common_config common;
bool enable_inverted;
int32_t modes_uv[4];
const struct regulator_pca9420_desc *desc;
const struct device *parent;
};
struct regulator_pca9420_data {
struct regulator_common_data data;
};
static const struct linear_range buck1_ranges[] = {
LINEAR_RANGE_INIT(500000, 25000U, 0x0U, 0x28U),
LINEAR_RANGE_INIT(1500000, 0U, 0x29U, 0x3E),
LINEAR_RANGE_INIT(1800000, 0U, 0x3FU, 0x3FU),
};
static const struct linear_range buck2_ranges[] = {
LINEAR_RANGE_INIT(1500000, 25000U, 0x0U, 0x18U),
LINEAR_RANGE_INIT(2100000, 0U, 0x19U, 0x1F),
LINEAR_RANGE_INIT(2700000, 25000U, 0x20U, 0x38U),
LINEAR_RANGE_INIT(3300000, 0U, 0x39U, 0x3F),
};
static const struct linear_range ldo1_ranges[] = {
LINEAR_RANGE_INIT(1700000, 25000U, 0x0U, 0x9U),
LINEAR_RANGE_INIT(1900000, 0U, 0x9U, 0xFU),
};
static const struct linear_range ldo2_ranges[] = {
LINEAR_RANGE_INIT(1500000, 25000U, 0x0U, 0x18U),
LINEAR_RANGE_INIT(2100000, 0U, 0x19U, 0x1FU),
LINEAR_RANGE_INIT(2700000, 25000U, 0x20U, 0x38U),
LINEAR_RANGE_INIT(3300000, 0U, 0x39U, 0x3FU),
};
static const struct regulator_pca9420_desc buck1_desc = {
.enable_reg = PCA9420_MODECFG_0_2,
.enable_mask = PCA9420_MODECFG_2_SW1_EN_MASK,
.enable_val = PCA9420_MODECFG_2_SW1_EN_VAL,
.vsel_mask = PCA9420_MODECFG_0_SW1_OUT_MASK,
.vsel_pos = PCA9420_MODECFG_0_SW1_OUT_POS,
.vsel_reg = PCA9420_MODECFG_0_0,
.max_ua = 250000,
.ranges = buck1_ranges,
.num_ranges = ARRAY_SIZE(buck1_ranges),
};
static const struct regulator_pca9420_desc buck2_desc = {
.enable_reg = PCA9420_MODECFG_0_2,
.enable_mask = PCA9420_MODECFG_2_SW2_EN_MASK,
.enable_val = PCA9420_MODECFG_2_SW2_EN_VAL,
.vsel_mask = PCA9420_MODECFG_1_SW2_OUT_MASK,
.vsel_pos = PCA9420_MODECFG_1_SW2_OUT_POS,
.vsel_reg = PCA9420_MODECFG_0_1,
.max_ua = 500000,
.ranges = buck2_ranges,
.num_ranges = ARRAY_SIZE(buck2_ranges),
};
static const struct regulator_pca9420_desc ldo1_desc = {
.enable_reg = PCA9420_MODECFG_0_2,
.enable_mask = PCA9420_MODECFG_2_LDO1_EN_MASK,
.enable_val = PCA9420_MODECFG_2_LDO1_EN_VAL,
.vsel_mask = PCA9420_MODECFG_2_LDO1_OUT_MASK,
.vsel_pos = PCA9420_MODECFG_2_LDO1_OUT_POS,
.vsel_reg = PCA9420_MODECFG_0_2,
.max_ua = 1000,
.ranges = ldo1_ranges,
.num_ranges = ARRAY_SIZE(ldo1_ranges),
};
static const struct regulator_pca9420_desc ldo2_desc = {
.enable_reg = PCA9420_MODECFG_0_2,
.enable_mask = PCA9420_MODECFG_2_LDO2_EN_MASK,
.enable_val = PCA9420_MODECFG_2_LDO2_EN_VAL,
.vsel_reg = PCA9420_MODECFG_0_3,
.vsel_mask = PCA9420_MODECFG_3_LDO2_OUT_MASK,
.vsel_pos = PCA9420_MODECFG_3_LDO2_OUT_POS,
.max_ua = 250000,
.ranges = ldo2_ranges,
.num_ranges = ARRAY_SIZE(ldo2_ranges),
};
static unsigned int regulator_pca9420_count_voltages(const struct device *dev)
{
const struct regulator_pca9420_config *config = dev->config;
return linear_range_group_values_count(config->desc->ranges,
config->desc->num_ranges);
}
static int regulator_pca9420_list_voltage(const struct device *dev,
unsigned int idx, int32_t *volt_uv)
{
const struct regulator_pca9420_config *config = dev->config;
return linear_range_group_get_value(config->desc->ranges,
config->desc->num_ranges, idx,
volt_uv);
}
static int regulator_pca9420_set_voltage(const struct device *dev,
int32_t min_uv, int32_t max_uv)
{
const struct regulator_pca9420_config *config = dev->config;
const struct regulator_pca9420_common_config *cconfig = config->parent->config;
struct regulator_pca9420_common_data *cdata = config->parent->data;
uint16_t idx;
int ret;
ret = linear_range_group_get_win_index(config->desc->ranges,
config->desc->num_ranges, min_uv,
max_uv, &idx);
if (ret == -EINVAL) {
return ret;
}
idx <<= config->desc->vsel_pos;
return i2c_reg_update_byte_dt(&cconfig->i2c, config->desc->vsel_reg +
PCA9420_MODECFG_OFFSET(cdata->dvs_state),
config->desc->vsel_mask, (uint8_t)idx);
}
static int regulator_pca9420_get_voltage(const struct device *dev,
int32_t *volt_uv)
{
const struct regulator_pca9420_config *config = dev->config;
const struct regulator_pca9420_common_config *cconfig = config->parent->config;
struct regulator_pca9420_common_data *cdata = config->parent->data;
int ret;
uint8_t raw_reg;
ret = i2c_reg_read_byte_dt(&cconfig->i2c, config->desc->vsel_reg +
PCA9420_MODECFG_OFFSET(cdata->dvs_state),
&raw_reg);
if (ret < 0) {
return ret;
}
raw_reg = (raw_reg & config->desc->vsel_mask) >> config->desc->vsel_pos;
return linear_range_group_get_value(config->desc->ranges,
config->desc->num_ranges, raw_reg,
volt_uv);
}
static int regulator_pca9420_get_current_limit(const struct device *dev,
int32_t *curr_ua)
{
const struct regulator_pca9420_config *config = dev->config;
const struct regulator_pca9420_common_config *cconfig = config->parent->config;
if (cconfig->vin_ilim_ua == 0U) {
*curr_ua = config->desc->max_ua;
} else {
*curr_ua = MIN(config->desc->max_ua, cconfig->vin_ilim_ua);
}
return 0;
}
static int regulator_pca9420_enable(const struct device *dev)
{
const struct regulator_pca9420_config *config = dev->config;
const struct regulator_pca9420_common_config *cconfig = config->parent->config;
struct regulator_pca9420_common_data *cdata = config->parent->data;
uint8_t en_val;
en_val = config->enable_inverted ? 0 : config->desc->enable_val;
return i2c_reg_update_byte_dt(&cconfig->i2c, config->desc->enable_reg
+ PCA9420_MODECFG_OFFSET(cdata->dvs_state),
config->desc->enable_mask, en_val);
}
static int regulator_pca9420_disable(const struct device *dev)
{
const struct regulator_pca9420_config *config = dev->config;
const struct regulator_pca9420_common_config *cconfig = config->parent->config;
struct regulator_pca9420_common_data *cdata = config->parent->data;
uint8_t dis_val;
dis_val = config->enable_inverted ? config->desc->enable_val : 0;
return i2c_reg_update_byte_dt(&cconfig->i2c, config->desc->enable_reg
+ PCA9420_MODECFG_OFFSET(cdata->dvs_state),
config->desc->enable_mask, dis_val);
}
static const struct regulator_driver_api api = {
.enable = regulator_pca9420_enable,
.disable = regulator_pca9420_disable,
.count_voltages = regulator_pca9420_count_voltages,
.list_voltage = regulator_pca9420_list_voltage,
.set_voltage = regulator_pca9420_set_voltage,
.get_voltage = regulator_pca9420_get_voltage,
.get_current_limit = regulator_pca9420_get_current_limit,
};
static int regulator_pca9420_init(const struct device *dev)
{
const struct regulator_pca9420_config *config = dev->config;
const struct regulator_pca9420_common_config *cconfig = config->parent->config;
regulator_common_data_init(dev);
if (!device_is_ready(config->parent)) {
return -ENODEV;
}
/* configure mode voltages */
for (uint8_t i = 0U; i < ARRAY_SIZE(config->modes_uv); i++) {
int ret;
if (config->modes_uv[i] == 0) {
/* disable mode if voltage is 0 */
ret = i2c_reg_update_byte_dt(
&cconfig->i2c,
config->desc->enable_reg + PCA9420_MODECFG_OFFSET(i),
config->desc->enable_mask, 0U);
if (ret < 0) {
return ret;
}
} else if (config->modes_uv[i] > 0) {
uint16_t idx;
/* program mode voltage */
ret = linear_range_group_get_win_index(
config->desc->ranges, config->desc->num_ranges,
config->modes_uv[i], config->modes_uv[i], &idx);
if (ret == -EINVAL) {
return ret;
}
idx <<= config->desc->vsel_pos;
ret = i2c_reg_update_byte_dt(
&cconfig->i2c,
config->desc->vsel_reg + PCA9420_MODECFG_OFFSET(i),
config->desc->vsel_mask, (uint8_t)idx);
if (ret < 0) {
return ret;
}
}
}
return regulator_common_init(dev, false);
}
int regulator_pca9420_dvs_state_set(const struct device *dev,
regulator_dvs_state_t state)
{
const struct regulator_pca9420_common_config *config = dev->config;
struct regulator_pca9420_common_data *data = dev->data;
int ret;
if (state >= PCA9420_NUM_MODES) {
return -ENOTSUP;
}
if (config->enable_modesel_pins) {
/*
* The user cannot set DVS state via this API,
* but they may want to query/set voltages for another mode.
* Return -EPERM to indicate change failed, but change the
* dvs_state variable so the user can access the alternative
* dvs mode settings.
*/
data->dvs_state = state;
return -EPERM;
}
ret = i2c_reg_update_byte_dt(&config->i2c, PCA9420_TOP_CNTL3,
PCA9420_TOP_CNTL3_MODE_I2C_MASK,
state << PCA9420_TOP_CNTL3_MODE_I2C_POS);
if (ret < 0) {
return ret;
}
/* Record new DVS state */
data->dvs_state = state;
return 0;
}
static const struct regulator_parent_driver_api parent_api = {
.dvs_state_set = regulator_pca9420_dvs_state_set,
};
static int regulator_pca9420_common_init(const struct device *dev)
{
const struct regulator_pca9420_common_config *config = dev->config;
uint8_t reg_val = PCA9420_TOP_CNTL0_VIN_ILIM_SEL_DISABLED;
int ret;
if (!device_is_ready(config->i2c.bus)) {
return -ENODEV;
}
if (config->enable_modesel_pins) {
/* enable MODESEL0/1 pins for each mode */
for (uint8_t i = 0U; i < PCA9420_NUM_MODES; i++) {
ret = i2c_reg_update_byte_dt(
&config->i2c,
PCA9420_MODECFG_0_0 +
PCA9420_MODECFG_OFFSET(i),
PCA9420_MODECFG_0_X_EN_MODE_SEL_BY_PIN,
PCA9420_MODECFG_0_X_EN_MODE_SEL_BY_PIN);
if (ret < 0) {
return ret;
}
}
}
/* configure VIN current limit */
if (config->vin_ilim_ua != 0U) {
reg_val = (config->vin_ilim_ua - PCA9420_VIN_ILIM_MIN_UA) /
PCA9420_VIN_ILIM_UA_LSB;
}
return i2c_reg_update_byte_dt(
&config->i2c, PCA9420_TOP_CNTL0,
PCA9420_TOP_CNTL0_VIN_ILIM_SEL_MASK,
reg_val << PCA9420_TOP_CNTL0_VIN_ILIM_SEL_POS);
}
#define REGULATOR_PCA9420_DEFINE(node_id, id, name, _parent) \
static struct regulator_pca9420_data data_##id; \
\
static const struct regulator_pca9420_config config_##id = { \
.common = REGULATOR_DT_COMMON_CONFIG_INIT(node_id), \
.enable_inverted = DT_PROP(node_id, enable_inverted), \
.modes_uv = { \
DT_PROP_OR(node_id, nxp_mode0_microvolt, -1), \
DT_PROP_OR(node_id, nxp_mode1_microvolt, -1), \
DT_PROP_OR(node_id, nxp_mode2_microvolt, -1), \
DT_PROP_OR(node_id, nxp_mode3_microvolt, -1), \
}, \
.desc = &name ## _desc, \
.parent = _parent, \
}; \
\
DEVICE_DT_DEFINE(node_id, regulator_pca9420_init, NULL, &data_##id, \
&config_##id, POST_KERNEL, \
CONFIG_REGULATOR_PCA9420_INIT_PRIORITY, &api);
#define REGULATOR_PCA9420_DEFINE_COND(inst, child, parent) \
COND_CODE_1(DT_NODE_EXISTS(DT_INST_CHILD(inst, child)), \
(REGULATOR_PCA9420_DEFINE(DT_INST_CHILD(inst, child), \
child ## inst, child, parent)), \
())
#define REGULATOR_PCA9420_DEFINE_ALL(inst) \
static const struct regulator_pca9420_common_config config_##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
.vin_ilim_ua = DT_INST_PROP(inst, nxp_vin_ilim_microamp), \
.enable_modesel_pins = \
DT_INST_PROP(inst, nxp_enable_modesel_pins), \
}; \
\
static struct regulator_pca9420_common_data data_##inst; \
\
DEVICE_DT_INST_DEFINE(inst, regulator_pca9420_common_init, NULL, \
&data_##inst, \
&config_##inst, POST_KERNEL, \
CONFIG_REGULATOR_PCA9420_COMMON_INIT_PRIORITY, \
&parent_api); \
\
REGULATOR_PCA9420_DEFINE_COND(inst, buck1, DEVICE_DT_INST_GET(inst)) \
REGULATOR_PCA9420_DEFINE_COND(inst, buck2, DEVICE_DT_INST_GET(inst)) \
REGULATOR_PCA9420_DEFINE_COND(inst, ldo1, DEVICE_DT_INST_GET(inst)) \
REGULATOR_PCA9420_DEFINE_COND(inst, ldo2, DEVICE_DT_INST_GET(inst))
DT_INST_FOREACH_STATUS_OKAY(REGULATOR_PCA9420_DEFINE_ALL)