drivers: regulator: npm1300: Initial driver for nPM1300 PMIC

Initial regulator driver for Nordic NPM1300 PMIC.

Signed-off-by: Andy Sinclair <andy.sinclair@nordicsemi.no>
This commit is contained in:
Andy Sinclair 2023-03-13 16:33:07 +00:00 committed by Carles Cufí
commit d1e201ccf6
7 changed files with 755 additions and 0 deletions

View file

@ -7,6 +7,7 @@ zephyr_library_sources(regulator_common.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_FAKE regulator_fake.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_FIXED regulator_fixed.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_NPM1100 regulator_npm1100.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_NPM1300 regulator_npm1300.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_NPM6001 regulator_npm6001.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_PCA9420 regulator_pca9420.c)
zephyr_library_sources_ifdef(CONFIG_REGULATOR_SHELL regulator_shell.c)

View file

@ -23,6 +23,7 @@ source "subsys/logging/Kconfig.template.log_config"
source "drivers/regulator/Kconfig.fake"
source "drivers/regulator/Kconfig.fixed"
source "drivers/regulator/Kconfig.npm1100"
source "drivers/regulator/Kconfig.npm1300"
source "drivers/regulator/Kconfig.npm6001"
source "drivers/regulator/Kconfig.pca9420"
source "drivers/regulator/Kconfig.rpi_pico"

View file

@ -0,0 +1,28 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
# SPDX -License-Identifier: Apache-2.0
config REGULATOR_NPM1300
bool "nPM1300 PMIC regulator driver"
default y
depends on DT_HAS_NORDIC_NPM1300_REGULATOR_ENABLED
select I2C
help
Enable the Nordic nPM1300 PMIC regulator driver
if REGULATOR_NPM1300
config REGULATOR_NPM1300_COMMON_INIT_PRIORITY
int "nPM1300 regulator driver init priority (common part)"
default 75
help
Init priority for the Nordic nPM1300 regulator driver (common part).
It must be greater than I2C init priority.
config REGULATOR_NPM1300_INIT_PRIORITY
int "nPM1300 regulator driver init priority"
default 76
help
Init priority for the Nordic nPM1300 regulator driver. It must be
greater than REGULATOR_NPM1300_COMMON_INIT_PRIORITY.
endif

View file

@ -0,0 +1,588 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nordic_npm1300_regulator
#include <errno.h>
#include <string.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/regulator.h>
#include <zephyr/dt-bindings/regulator/npm1300.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/linear_range.h>
#include <zephyr/sys/util.h>
/* nPM1300 voltage sources */
enum npm1300_sources {
NPM1300_SOURCE_BUCK1,
NPM1300_SOURCE_BUCK2,
NPM1300_SOURCE_LDO1,
NPM1300_SOURCE_LDO2,
};
/* nPM1300 gpio control channels */
enum npm1300_gpio_type {
NPM1300_GPIO_TYPE_ENABLE,
NPM1300_GPIO_TYPE_RETENTION,
NPM1300_GPIO_TYPE_PWM
};
/* nPM1300 regulator base addresses */
#define BUCK_BASE 0x04U
#define LDSW_BASE 0x08U
/* nPM1300 regulator register offsets */
#define BUCK_OFFSET_EN_SET 0x00U
#define BUCK_OFFSET_EN_CLR 0x01U
#define BUCK_OFFSET_PWM_SET 0x04U
#define BUCK_OFFSET_PWM_CLR 0x05U
#define BUCK_OFFSET_VOUT_NORM 0x08U
#define BUCK_OFFSET_VOUT_RET 0x09U
#define BUCK_OFFSET_EN_CTRL 0x0CU
#define BUCK_OFFSET_VRET_CTRL 0x0DU
#define BUCK_OFFSET_PWM_CTRL 0x0EU
#define BUCK_OFFSET_SW_CTRL 0x0FU
#define BUCK_OFFSET_VOUT_STAT 0x10U
#define BUCK_OFFSET_CTRL0 0x15U
/* nPM1300 ldsw register offsets */
#define LDSW_OFFSET_EN_SET 0x00U
#define LDSW_OFFSET_EN_CLR 0x01U
#define LDSW_OFFSET_STATUS 0x04U
#define LDSW_OFFSET_GPISEL 0x05U
#define LDSW_OFFSET_CONFIG 0x07U
#define LDSW_OFFSET_LDOSEL 0x08U
#define LDSW_OFFSET_VOUTSEL 0x0CU
struct regulator_npm1300_pconfig {
struct i2c_dt_spec i2c;
struct gpio_dt_spec dvs_state_pins[5];
uint8_t pad_val;
};
struct regulator_npm1300_config {
struct regulator_common_config common;
const struct device *p;
uint8_t source;
int32_t retention_uv;
struct gpio_dt_spec enable_gpios;
struct gpio_dt_spec retention_gpios;
struct gpio_dt_spec pwm_gpios;
};
struct regulator_npm1300_data {
struct regulator_common_data data;
};
/* Linear range for output voltage, common for all bucks and LDOs on this device */
static const struct linear_range buckldo_range = LINEAR_RANGE_INIT(1000000, 100000, 0U, 23U);
/* Read multiple registers from specified address */
static int reg_read_burst(const struct device *dev, uint8_t base, uint8_t offset, uint8_t *data,
size_t len)
{
const struct regulator_npm1300_config *config = dev->config;
const struct regulator_npm1300_pconfig *pconfig = config->p->config;
uint8_t buff[] = {base, offset};
return i2c_write_read_dt(&pconfig->i2c, buff, sizeof(buff), data, len);
}
static int reg_read(const struct device *dev, uint8_t base, uint8_t offset, uint8_t *data)
{
return reg_read_burst(dev, base, offset, data, 1U);
}
/* Write single register to specified address */
static int reg_write(const struct device *dev, uint8_t base, uint8_t offset, uint8_t data)
{
const struct regulator_npm1300_config *config = dev->config;
const struct regulator_npm1300_pconfig *pconfig = config->p->config;
uint8_t buff[] = {base, offset, data};
return i2c_write_dt(&pconfig->i2c, buff, sizeof(buff));
}
/* Modify bits in single register */
static int reg_update(const struct device *dev, uint8_t base, uint8_t offset, uint8_t data,
uint8_t mask)
{
uint8_t reg;
int ret;
ret = reg_read(dev, base, offset, &reg);
if (ret < 0) {
return ret;
}
reg = (reg & ~mask) | (data & mask);
return reg_write(dev, base, offset, reg);
}
unsigned int regulator_npm1300_count_voltages(const struct device *dev)
{
const struct regulator_npm1300_config *config = dev->config;
switch (config->source) {
case NPM1300_SOURCE_BUCK1:
case NPM1300_SOURCE_BUCK2:
case NPM1300_SOURCE_LDO1:
case NPM1300_SOURCE_LDO2:
return linear_range_values_count(&buckldo_range);
default:
return 0;
}
}
int regulator_npm1300_list_voltage(const struct device *dev, unsigned int idx, int32_t *volt_uv)
{
const struct regulator_npm1300_config *config = dev->config;
switch (config->source) {
case NPM1300_SOURCE_BUCK1:
case NPM1300_SOURCE_BUCK2:
case NPM1300_SOURCE_LDO1:
case NPM1300_SOURCE_LDO2:
return linear_range_get_value(&buckldo_range, idx, volt_uv);
default:
return -EINVAL;
}
}
static int retention_set_voltage(const struct device *dev, int32_t retention_uv)
{
const struct regulator_npm1300_config *config = dev->config;
uint16_t idx;
uint8_t chan;
int ret;
switch (config->source) {
case NPM1300_SOURCE_BUCK1:
chan = 0U;
break;
case NPM1300_SOURCE_BUCK2:
chan = 1U;
break;
default:
return -ENOTSUP;
}
ret = linear_range_get_win_index(&buckldo_range, retention_uv, retention_uv, &idx);
if (ret == -EINVAL) {
return ret;
}
return reg_write(dev, BUCK_BASE, BUCK_OFFSET_VOUT_RET + (chan * 2U), idx);
}
static int buck_set_voltage(const struct device *dev, uint8_t chan, int32_t min_uv, int32_t max_uv)
{
uint8_t mask;
uint16_t idx;
int ret;
ret = linear_range_get_win_index(&buckldo_range, min_uv, max_uv, &idx);
if (ret == -EINVAL) {
return ret;
}
ret = reg_write(dev, BUCK_BASE, BUCK_OFFSET_VOUT_NORM + (chan * 2U), idx);
if (ret < 0) {
return ret;
}
/* Enable SW control of buck output */
mask = BIT(chan);
return reg_update(dev, BUCK_BASE, BUCK_OFFSET_SW_CTRL, mask, mask);
}
static int ldo_set_voltage(const struct device *dev, uint8_t chan, int32_t min_uv, int32_t max_uv)
{
uint16_t idx;
int ret;
ret = linear_range_get_win_index(&buckldo_range, min_uv, max_uv, &idx);
if (ret == -EINVAL) {
return ret;
}
return reg_write(dev, LDSW_BASE, LDSW_OFFSET_VOUTSEL + chan, idx);
}
int regulator_npm1300_set_voltage(const struct device *dev, int32_t min_uv, int32_t max_uv)
{
const struct regulator_npm1300_config *config = dev->config;
switch (config->source) {
case NPM1300_SOURCE_BUCK1:
return buck_set_voltage(dev, 0, min_uv, max_uv);
case NPM1300_SOURCE_BUCK2:
return buck_set_voltage(dev, 1, min_uv, max_uv);
case NPM1300_SOURCE_LDO1:
return ldo_set_voltage(dev, 0, min_uv, max_uv);
case NPM1300_SOURCE_LDO2:
return ldo_set_voltage(dev, 1, min_uv, max_uv);
default:
return -ENODEV;
}
}
static int buck_get_voltage(const struct device *dev, uint8_t chan, int32_t *volt_uv)
{
uint8_t sel;
uint8_t idx;
int ret;
ret = reg_read(dev, BUCK_BASE, BUCK_OFFSET_SW_CTRL, &sel);
if (ret < 0) {
return ret;
}
if ((sel >> chan) & 1U) {
/* SW control */
ret = reg_read(dev, BUCK_BASE, BUCK_OFFSET_VOUT_NORM + (chan * 2U), &idx);
} else {
/* VSET pin control */
ret = reg_read(dev, BUCK_BASE, BUCK_OFFSET_VOUT_STAT + chan, &idx);
}
if (ret < 0) {
return ret;
}
return linear_range_get_value(&buckldo_range, idx, volt_uv);
}
static int ldo_get_voltage(const struct device *dev, uint8_t chan, int32_t *volt_uv)
{
uint8_t idx;
int ret;
ret = reg_read(dev, LDSW_BASE, LDSW_OFFSET_VOUTSEL + chan, &idx);
if (ret < 0) {
return ret;
}
return linear_range_get_value(&buckldo_range, idx, volt_uv);
}
int regulator_npm1300_get_voltage(const struct device *dev, int32_t *volt_uv)
{
const struct regulator_npm1300_config *config = dev->config;
switch (config->source) {
case NPM1300_SOURCE_BUCK1:
return buck_get_voltage(dev, 0, volt_uv);
case NPM1300_SOURCE_BUCK2:
return buck_get_voltage(dev, 1, volt_uv);
case NPM1300_SOURCE_LDO1:
return ldo_get_voltage(dev, 0, volt_uv);
case NPM1300_SOURCE_LDO2:
return ldo_get_voltage(dev, 1, volt_uv);
default:
return -ENODEV;
}
}
static int set_buck_mode(const struct device *dev, uint8_t chan, regulator_mode_t mode)
{
switch (mode) {
case NPM1300_BUCK_MODE_PWM:
return reg_write(dev, BUCK_BASE, BUCK_OFFSET_PWM_SET + (chan * 2U), 1U);
case NPM1300_BUCK_MODE_AUTO:
return reg_write(dev, BUCK_BASE, BUCK_OFFSET_PWM_CLR + (chan * 2U), 1U);
default:
return -ENOTSUP;
}
}
static int set_ldsw_mode(const struct device *dev, uint8_t chan, regulator_mode_t mode)
{
switch (mode) {
case NPM1300_LDSW_MODE_LDO:
return reg_write(dev, LDSW_BASE, LDSW_OFFSET_LDOSEL + chan, 1U);
case NPM1300_LDSW_MODE_LDSW:
return reg_write(dev, LDSW_BASE, LDSW_OFFSET_LDOSEL + chan, 0U);
default:
return -ENOTSUP;
}
}
int regulator_npm1300_set_mode(const struct device *dev, regulator_mode_t mode)
{
const struct regulator_npm1300_config *config = dev->config;
switch (config->source) {
case NPM1300_SOURCE_BUCK1:
return set_buck_mode(dev, 0, mode);
case NPM1300_SOURCE_BUCK2:
return set_buck_mode(dev, 1, mode);
case NPM1300_SOURCE_LDO1:
return set_ldsw_mode(dev, 0, mode);
case NPM1300_SOURCE_LDO2:
return set_ldsw_mode(dev, 1, mode);
default:
return -ENOTSUP;
}
}
int regulator_npm1300_enable(const struct device *dev)
{
const struct regulator_npm1300_config *config = dev->config;
switch (config->source) {
case NPM1300_SOURCE_BUCK1:
return reg_write(dev, BUCK_BASE, BUCK_OFFSET_EN_SET, 1U);
case NPM1300_SOURCE_BUCK2:
return reg_write(dev, BUCK_BASE, BUCK_OFFSET_EN_SET + 2U, 1U);
case NPM1300_SOURCE_LDO1:
return reg_write(dev, LDSW_BASE, LDSW_OFFSET_EN_SET, 1U);
case NPM1300_SOURCE_LDO2:
return reg_write(dev, LDSW_BASE, LDSW_OFFSET_EN_SET + 2U, 1U);
default:
return 0;
}
}
int regulator_npm1300_disable(const struct device *dev)
{
const struct regulator_npm1300_config *config = dev->config;
switch (config->source) {
case NPM1300_SOURCE_BUCK1:
return reg_write(dev, BUCK_BASE, BUCK_OFFSET_EN_CLR, 1U);
case NPM1300_SOURCE_BUCK2:
return reg_write(dev, BUCK_BASE, BUCK_OFFSET_EN_CLR + 2U, 1U);
case NPM1300_SOURCE_LDO1:
return reg_write(dev, LDSW_BASE, LDSW_OFFSET_EN_CLR, 1U);
case NPM1300_SOURCE_LDO2:
return reg_write(dev, LDSW_BASE, LDSW_OFFSET_EN_CLR + 2U, 1U);
default:
return 0;
}
}
static int regulator_npm1300_set_buck_pin_ctrl(const struct device *dev, uint8_t chan, uint8_t pin,
uint8_t inv, enum npm1300_gpio_type type)
{
uint8_t ctrl;
uint8_t mask;
switch (chan) {
case 0:
/* Invert control in bit 6, pin control in bits 2-0 */
ctrl = (inv << 6U) | (pin + 1U);
mask = BIT(6U) | BIT_MASK(3U);
break;
case 1:
/* Invert control in bit 7, pin control in bits 5-3 */
ctrl = (inv << 7U) | ((pin + 1U) << 3U);
mask = BIT(7U) | (BIT_MASK(3U) << 3U);
break;
default:
return -EINVAL;
}
switch (type) {
case NPM1300_GPIO_TYPE_ENABLE:
return reg_update(dev, BUCK_BASE, BUCK_OFFSET_EN_CTRL, ctrl, mask);
case NPM1300_GPIO_TYPE_PWM:
return reg_update(dev, BUCK_BASE, BUCK_OFFSET_PWM_CTRL, ctrl, mask);
case NPM1300_GPIO_TYPE_RETENTION:
return reg_update(dev, BUCK_BASE, BUCK_OFFSET_VRET_CTRL, ctrl, mask);
default:
return -ENOTSUP;
}
}
static int regulator_npm1300_set_ldsw_pin_ctrl(const struct device *dev, uint8_t chan, uint8_t pin,
uint8_t inv, enum npm1300_gpio_type type)
{
uint8_t ctrl;
if (type != NPM1300_GPIO_TYPE_ENABLE) {
return -ENOTSUP;
}
ctrl = (pin + 1U) | (inv << 3U);
return reg_write(dev, LDSW_BASE, LDSW_OFFSET_GPISEL + chan, type);
}
int regulator_npm1300_set_pin_ctrl(const struct device *dev, const struct gpio_dt_spec *spec,
enum npm1300_gpio_type type)
{
const struct regulator_npm1300_config *config = dev->config;
uint8_t inv;
if (spec->port == NULL) {
return 0;
}
inv = (spec->dt_flags & GPIO_ACTIVE_LOW) != 0U;
switch (config->source) {
case NPM1300_SOURCE_BUCK1:
return regulator_npm1300_set_buck_pin_ctrl(dev, 0, spec->pin, inv, type);
case NPM1300_SOURCE_BUCK2:
return regulator_npm1300_set_buck_pin_ctrl(dev, 1, spec->pin, inv, type);
case NPM1300_SOURCE_LDO1:
return regulator_npm1300_set_ldsw_pin_ctrl(dev, 0, spec->pin, inv, type);
case NPM1300_SOURCE_LDO2:
return regulator_npm1300_set_ldsw_pin_ctrl(dev, 1, spec->pin, inv, type);
default:
return -ENODEV;
}
}
int regulator_npm1300_dvs_state_set(const struct device *dev, regulator_dvs_state_t state)
{
const struct regulator_npm1300_pconfig *pconfig = dev->config;
const struct gpio_dt_spec *spec;
int ret;
for (size_t idx = 0U; idx < 5U; idx++) {
spec = &pconfig->dvs_state_pins[idx];
if (spec->port != NULL) {
ret = gpio_pin_set_dt(spec, ((state >> idx) & 1U) != 0U);
if (ret != 0) {
return ret;
}
}
}
return 0;
}
static const struct regulator_parent_driver_api parent_api = {
.dvs_state_set = regulator_npm1300_dvs_state_set,
};
int regulator_npm1300_common_init(const struct device *dev)
{
const struct regulator_npm1300_pconfig *pconfig = dev->config;
const struct gpio_dt_spec *spec;
int ret;
for (size_t idx = 0U; idx < 5U; idx++) {
spec = &pconfig->dvs_state_pins[idx];
if (spec->port != NULL) {
if (!gpio_is_ready_dt(spec)) {
return -ENODEV;
}
ret = gpio_pin_configure_dt(spec, GPIO_OUTPUT);
if (ret != 0) {
return ret;
}
}
}
return 0;
}
int regulator_npm1300_init(const struct device *dev)
{
const struct regulator_npm1300_config *config = dev->config;
int ret = 0;
if (!device_is_ready(config->p)) {
return -ENODEV;
}
ret = regulator_common_init(dev, false);
if (ret < 0) {
return ret;
}
/* Configure retention voltage */
if (config->retention_uv != 0) {
ret = retention_set_voltage(dev, config->retention_uv);
if (ret != 0) {
return ret;
}
}
/* Configure GPIO pin control */
ret = regulator_npm1300_set_pin_ctrl(dev, &config->enable_gpios, NPM1300_GPIO_TYPE_ENABLE);
if (ret != 0) {
return ret;
}
ret = regulator_npm1300_set_pin_ctrl(dev, &config->retention_gpios,
NPM1300_GPIO_TYPE_RETENTION);
if (ret != 0) {
return ret;
}
ret = regulator_npm1300_set_pin_ctrl(dev, &config->pwm_gpios, NPM1300_GPIO_TYPE_PWM);
if (ret != 0) {
return ret;
}
return ret;
}
static const struct regulator_driver_api api = {.enable = regulator_npm1300_enable,
.disable = regulator_npm1300_disable,
.count_voltages = regulator_npm1300_count_voltages,
.list_voltage = regulator_npm1300_list_voltage,
.set_voltage = regulator_npm1300_set_voltage,
.get_voltage = regulator_npm1300_get_voltage,
.set_mode = regulator_npm1300_set_mode};
#define REGULATOR_NPM1300_DEFINE(node_id, id, _source, parent) \
static struct regulator_npm1300_data data_##id; \
\
static const struct regulator_npm1300_config config_##id = { \
.common = REGULATOR_DT_COMMON_CONFIG_INIT(node_id), \
.p = parent, \
.source = _source, \
.retention_uv = DT_PROP_OR(node_id, retention_microvolt, 0), \
.enable_gpios = GPIO_DT_SPEC_GET_OR(node_id, enable_gpios, {0}), \
.retention_gpios = GPIO_DT_SPEC_GET_OR(node_id, retention_gpios, {0}), \
.pwm_gpios = GPIO_DT_SPEC_GET_OR(node_id, pwm_gpios, {0})}; \
\
DEVICE_DT_DEFINE(node_id, regulator_npm1300_init, NULL, &data_##id, &config_##id, \
POST_KERNEL, CONFIG_REGULATOR_NPM1300_INIT_PRIORITY, &api);
#define REGULATOR_NPM1300_DEFINE_COND(inst, child, source, parent) \
COND_CODE_1(DT_NODE_EXISTS(DT_INST_CHILD(inst, child)), \
(REGULATOR_NPM1300_DEFINE(DT_INST_CHILD(inst, child), child##inst, source, \
parent)), \
())
#define REGULATOR_NPM1300_DEFINE_ALL(inst) \
static const struct regulator_npm1300_pconfig config_##inst = { \
.i2c = I2C_DT_SPEC_GET(DT_INST_PARENT(inst)), \
.dvs_state_pins = {GPIO_DT_SPEC_INST_GET_BY_IDX_OR(inst, dvs_gpios, 0, {0}), \
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(inst, dvs_gpios, 1, {0}), \
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(inst, dvs_gpios, 2, {0}), \
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(inst, dvs_gpios, 3, {0}), \
GPIO_DT_SPEC_INST_GET_BY_IDX_OR(inst, dvs_gpios, 4, {0})}}; \
\
DEVICE_DT_INST_DEFINE(inst, regulator_npm1300_common_init, NULL, NULL, &config_##inst, \
POST_KERNEL, CONFIG_REGULATOR_NPM1300_COMMON_INIT_PRIORITY, \
&parent_api); \
\
REGULATOR_NPM1300_DEFINE_COND(inst, buck1, NPM1300_SOURCE_BUCK1, DEVICE_DT_INST_GET(inst)) \
REGULATOR_NPM1300_DEFINE_COND(inst, buck2, NPM1300_SOURCE_BUCK2, DEVICE_DT_INST_GET(inst)) \
REGULATOR_NPM1300_DEFINE_COND(inst, ldo1, NPM1300_SOURCE_LDO1, DEVICE_DT_INST_GET(inst)) \
REGULATOR_NPM1300_DEFINE_COND(inst, ldo2, NPM1300_SOURCE_LDO1, DEVICE_DT_INST_GET(inst))
DT_INST_FOREACH_STATUS_OKAY(REGULATOR_NPM1300_DEFINE_ALL)