diff --git a/drivers/regulator/CMakeLists.txt b/drivers/regulator/CMakeLists.txt index ddf56929af9..7ed90137c3f 100644 --- a/drivers/regulator/CMakeLists.txt +++ b/drivers/regulator/CMakeLists.txt @@ -14,6 +14,7 @@ zephyr_library_sources_ifdef(CONFIG_REGULATOR_GPIO regulator_gpio.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_MAX20335 regulator_max20335.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_NPM2100 regulator_npm2100.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) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 88f114f8eb0..d4b70e6b76a 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -36,6 +36,7 @@ source "drivers/regulator/Kconfig.gpio" source "drivers/regulator/Kconfig.max20335" source "drivers/regulator/Kconfig.npm1100" source "drivers/regulator/Kconfig.npm1300" +source "drivers/regulator/Kconfig.npm2100" source "drivers/regulator/Kconfig.npm6001" source "drivers/regulator/Kconfig.pca9420" source "drivers/regulator/Kconfig.rpi_pico" diff --git a/drivers/regulator/Kconfig.npm2100 b/drivers/regulator/Kconfig.npm2100 new file mode 100644 index 00000000000..4d1476dbbea --- /dev/null +++ b/drivers/regulator/Kconfig.npm2100 @@ -0,0 +1,29 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config REGULATOR_NPM2100 + bool "nPM2100 PMIC regulator driver" + default y + depends on DT_HAS_NORDIC_NPM2100_REGULATOR_ENABLED + select I2C + select MFD + help + Enable the Nordic nPM2100 PMIC regulator driver + +if REGULATOR_NPM2100 + +config REGULATOR_NPM2100_COMMON_INIT_PRIORITY + int "nPM2100 regulator driver init priority (common part)" + default 85 + help + Init priority for the Nordic nPM2100 regulator driver (common part). + It must be greater than I2C init priority. + +config REGULATOR_NPM2100_INIT_PRIORITY + int "nPM2100 regulator driver init priority" + default 86 + help + Init priority for the Nordic nPM2100 regulator driver. It must be + greater than REGULATOR_NPM2100_COMMON_INIT_PRIORITY. + +endif diff --git a/drivers/regulator/regulator_npm2100.c b/drivers/regulator/regulator_npm2100.c new file mode 100644 index 00000000000..afad574c767 --- /dev/null +++ b/drivers/regulator/regulator_npm2100.c @@ -0,0 +1,791 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nordic_npm2100_regulator + +#include +#include + +#include +#include +#include +#include +#include +#include + +enum npm2100_sources { + NPM2100_SOURCE_BOOST, + NPM2100_SOURCE_LDOSW, +}; + +#define BOOST_VOUT 0x22U +#define BOOST_VOUTSEL 0x23U +#define BOOST_OPER 0x24U +#define BOOST_LIMIT 0x26U +#define BOOST_GPIO 0x28U +#define BOOST_PIN 0x29U +#define BOOST_CTRLSET 0x2AU +#define BOOST_CTRLCLR 0x2BU +#define BOOST_IBATLIM 0x2DU +#define BOOST_VBATMINL 0x2FU +#define BOOST_VBATMINH 0x30U +#define BOOST_VOUTMIN 0x31U +#define BOOST_VOUTWRN 0x32U +#define BOOST_VOUTDPS 0x33U +#define BOOST_STATUS0 0x34U +#define BOOST_STATUS1 0x35U +#define BOOST_VSET0 0x36U +#define BOOST_VSET1 0x37U + +#define LDOSW_VOUT 0x68U +#define LDOSW_ENABLE 0x69U +#define LDOSW_SEL 0x6AU +#define LDOSW_GPIO 0x6BU +#define LDOSW_STATUS 0x6EU +#define LDOSW_PRGOCP 0x6FU + +#define SHIP_TASK_SHIP 0xC0U + +#define RESET_ALTCONFIG 0xD6U +#define RESET_WRITESTICKY 0xDBU +#define RESET_STROBESTICKY 0xDCU + +#define BOOST_OPER_MODE_MASK 0x07U +#define BOOST_OPER_MODE_AUTO 0x00U +#define BOOST_OPER_MODE_HP 0x01U +#define BOOST_OPER_MODE_LP 0x02U +#define BOOST_OPER_MODE_PASS 0x03U +#define BOOST_OPER_MODE_NOHP 0x04U +#define BOOST_OPER_DPS_MASK 0x18U +#define BOOST_OPER_DPS_DISABLE 0x00U +#define BOOST_OPER_DPS_ALLOW 0x01U +#define BOOST_OPER_DPS_ALLOWLP 0x02U +#define BOOST_OPER_DPSTIMER_MASK 0x60U + +#define BOOST_PIN_FORCE_HP 0x00U +#define BOOST_PIN_FORCE_LP 0x01U +#define BOOST_PIN_FORCE_PASS 0x02U +#define BOOST_PIN_FORCE_NOHP 0x03U + +#define BOOST_STATUS0_MODE_MASK 0x07U +#define BOOST_STATUS0_MODE_HP 0x00U +#define BOOST_STATUS0_MODE_LP 0x01U +#define BOOST_STATUS0_MODE_ULP 0x02U +#define BOOST_STATUS0_MODE_PT 0x03U +#define BOOST_STATUS0_MODE_DPS 0x04U + +#define BOOST_STATUS1_VSET_MASK 0x40U + +#define LDOSW_SEL_OPER_MASK 0x06U +#define LDOSW_SEL_OPER_AUTO 0x00U +#define LDOSW_SEL_OPER_ULP 0x02U +#define LDOSW_SEL_OPER_HP 0x04U +#define LDOSW_SEL_OPER_PIN 0x06U + +#define LDOSW_GPIO_PIN_MASK 0x07U +#define LDOSW_GPIO_PINACT_MASK 0x18U +#define LDOSW_GPIO_PINACT_HP 0x00U +#define LDOSW_GPIO_PINACT_ULP 0x08U +#define LDOSW_GPIO_PININACT_OFF 0x00U +#define LDOSW_GPIO_PININACT_ULP 0x10U + +#define LDOSW_STATUS_LDO 0x01U +#define LDOSW_STATUS_SW 0x02U +#define LDOSW_STATUS_HP 0x04U +#define LDOSW_STATUS_ULP 0x08U +#define LDOSW_STATUS_OCP 0x10U + +#define RESET_ALTCONFIG_LDOSW_OFF 0x01U + +struct regulator_npm2100_pconfig { + struct i2c_dt_spec i2c; + struct gpio_dt_spec dvs_state_pins[2]; +}; + +struct regulator_npm2100_config { + struct regulator_common_config common; + struct i2c_dt_spec i2c; + uint8_t source; + struct gpio_dt_spec mode_gpios; + bool ldosw_wd_reset; + uint8_t dps_timer; + uint8_t dps_pulse_limit; +}; + +struct regulator_npm2100_data { + struct regulator_common_data data; + bool ldsw_mode; +}; + +static const struct linear_range boost_range = LINEAR_RANGE_INIT(1800000, 50000, 0U, 30U); +static const struct linear_range ldosw_range = LINEAR_RANGE_INIT(800000, 50000, 8U, 52U); +static const struct linear_range vset0_range = LINEAR_RANGE_INIT(1800000, 100000, 0U, 6U); +static const struct linear_range vset1_ranges[] = {LINEAR_RANGE_INIT(3000000, 0, 0U, 0U), + LINEAR_RANGE_INIT(2700000, 100000, 1U, 3U), + LINEAR_RANGE_INIT(3100000, 100000, 4U, 6U)}; +static const struct linear_range boost_ocp_range = LINEAR_RANGE_INIT(0, 300000, 0U, 1U); + +static const struct linear_range ldsw_ocp_ranges[] = {LINEAR_RANGE_INIT(40000, 0, 0U, 0U), + LINEAR_RANGE_INIT(70000, 5000, 1U, 3U), + LINEAR_RANGE_INIT(110000, 0, 4U, 4U)}; +static const uint8_t ldo_ocp_lookup[] = {13, 7, 6, 4, 1}; + +static const struct linear_range ldo_ocp_ranges[] = {LINEAR_RANGE_INIT(25000, 13000, 0U, 1U), + LINEAR_RANGE_INIT(50000, 25000, 2U, 3U), + LINEAR_RANGE_INIT(150000, 0, 4U, 4U)}; +static const uint8_t ldsw_ocp_lookup[] = {1, 7, 8, 9, 15}; + +static unsigned int regulator_npm2100_count_voltages(const struct device *dev) +{ + const struct regulator_npm2100_config *config = dev->config; + + switch (config->source) { + case NPM2100_SOURCE_BOOST: + return linear_range_values_count(&boost_range); + case NPM2100_SOURCE_LDOSW: + return linear_range_values_count(&ldosw_range); + default: + return 0; + } +} + +static int regulator_npm2100_list_voltage(const struct device *dev, unsigned int idx, + int32_t *volt_uv) +{ + const struct regulator_npm2100_config *config = dev->config; + + switch (config->source) { + case NPM2100_SOURCE_BOOST: + return linear_range_get_value(&boost_range, idx, volt_uv); + case NPM2100_SOURCE_LDOSW: + return linear_range_get_value(&ldosw_range, idx, volt_uv); + default: + return -EINVAL; + } +} + +static int regulator_npm2100_set_voltage(const struct device *dev, int32_t min_uv, int32_t max_uv) +{ + const struct regulator_npm2100_config *config = dev->config; + uint16_t idx; + int ret; + + switch (config->source) { + case NPM2100_SOURCE_BOOST: + ret = linear_range_get_win_index(&boost_range, min_uv, max_uv, &idx); + if (ret == -EINVAL) { + return ret; + } + + ret = i2c_reg_write_byte_dt(&config->i2c, BOOST_VOUT, idx); + if (ret < 0) { + return ret; + } + + /* Enable SW control of boost voltage */ + return i2c_reg_write_byte_dt(&config->i2c, BOOST_VOUTSEL, 1U); + + case NPM2100_SOURCE_LDOSW: + ret = linear_range_get_win_index(&ldosw_range, min_uv, max_uv, &idx); + if (ret == -EINVAL) { + return ret; + } + + return i2c_reg_write_byte_dt(&config->i2c, LDOSW_VOUT, idx); + + default: + return -ENODEV; + } +} + +static int regulator_npm2100_get_voltage(const struct device *dev, int32_t *volt_uv) +{ + const struct regulator_npm2100_config *config = dev->config; + uint8_t idx; + int ret; + + switch (config->source) { + case NPM2100_SOURCE_BOOST: + ret = i2c_reg_read_byte_dt(&config->i2c, BOOST_VOUTSEL, &idx); + if (ret < 0) { + return ret; + } + + if (idx == 1U) { + /* Voltage is selected by register value */ + ret = i2c_reg_read_byte_dt(&config->i2c, BOOST_VOUT, &idx); + if (ret < 0) { + return ret; + } + + return linear_range_get_value(&boost_range, idx, volt_uv); + } + + /* Voltage is selected by VSET pin */ + ret = i2c_reg_read_byte_dt(&config->i2c, BOOST_STATUS1, &idx); + if (ret < 0) { + return ret; + } + + if ((idx & BOOST_STATUS1_VSET_MASK) == 0U) { + /* VSET low, voltage is selected by VSET0 register */ + ret = i2c_reg_read_byte_dt(&config->i2c, BOOST_VSET0, &idx); + if (ret < 0) { + return ret; + } + + return linear_range_get_value(&vset0_range, idx, volt_uv); + } + + /* VSET high, voltage is selected by VSET1 register */ + ret = i2c_reg_read_byte_dt(&config->i2c, BOOST_VSET1, &idx); + if (ret < 0) { + return ret; + } + + return linear_range_group_get_value(vset1_ranges, ARRAY_SIZE(vset1_ranges), idx, + volt_uv); + + case NPM2100_SOURCE_LDOSW: + ret = i2c_reg_read_byte_dt(&config->i2c, LDOSW_VOUT, &idx); + if (ret < 0) { + return ret; + } + + return linear_range_get_value(&ldosw_range, idx, volt_uv); + + default: + return -ENODEV; + } +} + +static unsigned int regulator_npm2100_count_currents(const struct device *dev) +{ + const struct regulator_npm2100_config *config = dev->config; + const struct regulator_npm2100_data *data = dev->data; + + switch (config->source) { + case NPM2100_SOURCE_BOOST: + return linear_range_values_count(&boost_ocp_range); + case NPM2100_SOURCE_LDOSW: + if (data->ldsw_mode) { + return linear_range_group_values_count(ldsw_ocp_ranges, + ARRAY_SIZE(ldsw_ocp_ranges)); + } + return linear_range_group_values_count(ldo_ocp_ranges, ARRAY_SIZE(ldo_ocp_ranges)); + default: + return 0; + } +} + +static int regulator_npm2100_list_currents(const struct device *dev, unsigned int idx, + int32_t *current_ua) +{ + const struct regulator_npm2100_config *config = dev->config; + const struct regulator_npm2100_data *data = dev->data; + + switch (config->source) { + case NPM2100_SOURCE_BOOST: + return linear_range_get_value(&boost_ocp_range, idx, current_ua); + case NPM2100_SOURCE_LDOSW: + if (data->ldsw_mode) { + return linear_range_group_get_value( + ldsw_ocp_ranges, ARRAY_SIZE(ldsw_ocp_ranges), idx, current_ua); + } + return linear_range_group_get_value(ldo_ocp_ranges, ARRAY_SIZE(ldo_ocp_ranges), idx, + current_ua); + default: + return -EINVAL; + } +} + +static int regulator_npm2100_set_current(const struct device *dev, int32_t min_ua, int32_t max_ua) +{ + const struct regulator_npm2100_config *config = dev->config; + const struct regulator_npm2100_data *data = dev->data; + uint16_t idx = 0; + uint8_t shift = 0; + int ret; + + switch (config->source) { + case NPM2100_SOURCE_BOOST: + ret = linear_range_get_win_index(&boost_ocp_range, min_ua, max_ua, &idx); + if (ret == -EINVAL) { + return ret; + } + + if (idx == 1) { + return i2c_reg_write_byte_dt(&config->i2c, BOOST_CTRLSET, BIT(3)); + } + return i2c_reg_write_byte_dt(&config->i2c, BOOST_CTRLCLR, BIT(3)); + + case NPM2100_SOURCE_LDOSW: + if (data->ldsw_mode) { + ret = linear_range_group_get_win_index( + ldsw_ocp_ranges, ARRAY_SIZE(ldsw_ocp_ranges), min_ua, max_ua, &idx); + idx = ldsw_ocp_lookup[idx]; + shift = 4U; + } else { + ret = linear_range_group_get_win_index( + ldo_ocp_ranges, ARRAY_SIZE(ldo_ocp_ranges), min_ua, max_ua, &idx); + idx = ldo_ocp_lookup[idx]; + } + + if (ret == -EINVAL) { + return ret; + } + + return i2c_reg_update_byte_dt(&config->i2c, LDOSW_PRGOCP, BIT_MASK(3) << shift, + idx << shift); + + default: + return -ENODEV; + } +} + +static int set_boost_mode(const struct device *dev, regulator_mode_t mode) +{ + const struct regulator_npm2100_config *config = dev->config; + uint8_t reg; + + /* Normal mode */ + switch (mode & NPM2100_REG_OPER_MASK) { + case NPM2100_REG_OPER_AUTO: + reg = BOOST_OPER_MODE_AUTO; + break; + case NPM2100_REG_OPER_HP: + reg = BOOST_OPER_MODE_HP; + break; + case NPM2100_REG_OPER_LP: + reg = BOOST_OPER_MODE_LP; + break; + case NPM2100_REG_OPER_PASS: + reg = BOOST_OPER_MODE_PASS; + break; + case NPM2100_REG_OPER_NOHP: + reg = BOOST_OPER_MODE_NOHP; + break; + default: + return -ENOTSUP; + } + + /* Configure DPS mode */ + if ((mode & NPM2100_REG_DPS_MASK) != 0) { + uint8_t dps_val = (mode & NPM2100_REG_DPS_MASK) == NPM2100_REG_DPS_ALLOW + ? BOOST_OPER_DPS_ALLOW + : BOOST_OPER_DPS_ALLOWLP; + + reg |= FIELD_PREP(BOOST_OPER_DPS_MASK, dps_val); + } + + /* Update mode and dps fields, but not dpstimer */ + int ret = i2c_reg_update_byte_dt(&config->i2c, BOOST_OPER, + BOOST_OPER_MODE_MASK | BOOST_OPER_DPS_MASK, reg); + if (ret < 0) { + return ret; + } + + /* Forced mode */ + switch (mode & NPM2100_REG_FORCE_MASK) { + case 0U: + return 0; + case NPM2100_REG_FORCE_HP: + reg = BOOST_PIN_FORCE_HP; + break; + case NPM2100_REG_FORCE_LP: + reg = BOOST_PIN_FORCE_LP; + break; + case NPM2100_REG_FORCE_PASS: + reg = BOOST_PIN_FORCE_PASS; + break; + case NPM2100_REG_FORCE_NOHP: + reg = BOOST_PIN_FORCE_NOHP; + break; + default: + return -ENOTSUP; + } + + /* Forced mode is only valid when gpio is configured */ + if (config->mode_gpios.port == NULL) { + return -EINVAL; + } + + return i2c_reg_write_byte_dt(&config->i2c, BOOST_PIN, reg); +} + +static int get_boost_mode(const struct device *dev, regulator_mode_t *mode) +{ + const struct regulator_npm2100_config *config = dev->config; + uint8_t reg; + int ret; + + ret = i2c_reg_read_byte_dt(&config->i2c, BOOST_STATUS0, ®); + if (ret < 0) { + return ret; + } + + switch (reg & BOOST_STATUS0_MODE_MASK) { + case BOOST_STATUS0_MODE_HP: + *mode = NPM2100_REG_OPER_HP; + break; + case BOOST_STATUS0_MODE_LP: + *mode = NPM2100_REG_OPER_LP; + break; + case BOOST_STATUS0_MODE_ULP: + *mode = NPM2100_REG_OPER_ULP; + break; + case BOOST_STATUS0_MODE_PT: + *mode = NPM2100_REG_OPER_PASS; + break; + case BOOST_STATUS0_MODE_DPS: + /* STATUS0 indicates whether DPS is enabled, regardless of ALLOW/ALLOWLP setting. + * BOOST_OPER_DPS_ALLOW chosen instead of creating new enum value. + */ + *mode = BOOST_OPER_DPS_ALLOW; + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static int get_ldosw_mode(const struct device *dev, regulator_mode_t *mode) +{ + const struct regulator_npm2100_config *config = dev->config; + uint8_t reg; + int ret; + + ret = i2c_reg_read_byte_dt(&config->i2c, LDOSW_STATUS, ®); + if (ret < 0) { + return ret; + } + + *mode = 0U; + if (reg & LDOSW_STATUS_SW) { + *mode |= NPM2100_REG_LDSW_EN; + } + + if (reg & LDOSW_STATUS_HP) { + *mode |= NPM2100_REG_OPER_HP; + } else if (reg & LDOSW_STATUS_ULP) { + *mode |= NPM2100_REG_OPER_ULP; + } + + return 0; +} + +static int set_ldosw_gpio_mode(const struct device *dev, uint8_t inact, uint8_t act, uint8_t ldsw) +{ + const struct regulator_npm2100_config *config = dev->config; + int ret; + + ret = i2c_reg_update_byte_dt(&config->i2c, LDOSW_GPIO, LDOSW_GPIO_PINACT_MASK, inact | act); + if (ret < 0) { + return ret; + } + + /* Set operating mode to pin control */ + return i2c_reg_write_byte_dt(&config->i2c, LDOSW_SEL, LDOSW_SEL_OPER_PIN | ldsw); +} + +static int set_ldosw_mode(const struct device *dev, regulator_mode_t mode) +{ + const struct regulator_npm2100_config *config = dev->config; + struct regulator_npm2100_data *data = dev->data; + uint8_t ldsw = mode & NPM2100_REG_LDSW_EN; + uint8_t oper = mode & NPM2100_REG_OPER_MASK; + uint8_t force = mode & NPM2100_REG_FORCE_MASK; + + /* Save load switch state, needed for OCP configuration */ + data->ldsw_mode = ldsw != 0; + + if (force == 0U) { + /* SW control of mode */ + switch (oper) { + case NPM2100_REG_OPER_AUTO: + return i2c_reg_write_byte_dt(&config->i2c, LDOSW_SEL, + LDOSW_SEL_OPER_AUTO | ldsw); + case NPM2100_REG_OPER_ULP: + return i2c_reg_write_byte_dt(&config->i2c, LDOSW_SEL, + LDOSW_SEL_OPER_ULP | ldsw); + case NPM2100_REG_OPER_HP: + return i2c_reg_write_byte_dt(&config->i2c, LDOSW_SEL, + LDOSW_SEL_OPER_HP | ldsw); + default: + return -ENOTSUP; + } + } + + /* Forced mode is only valid when gpio is configured */ + if (config->mode_gpios.port == NULL) { + return -EINVAL; + } + + switch (oper | force) { + case NPM2100_REG_OPER_OFF | NPM2100_REG_FORCE_ULP: + return set_ldosw_gpio_mode(dev, LDOSW_GPIO_PININACT_OFF, LDOSW_GPIO_PINACT_ULP, + ldsw); + case NPM2100_REG_OPER_OFF | NPM2100_REG_FORCE_HP: + return set_ldosw_gpio_mode(dev, LDOSW_GPIO_PININACT_OFF, LDOSW_GPIO_PINACT_HP, + ldsw); + case NPM2100_REG_OPER_ULP | NPM2100_REG_FORCE_HP: + return set_ldosw_gpio_mode(dev, LDOSW_GPIO_PININACT_ULP, LDOSW_GPIO_PINACT_HP, + ldsw); + default: + return -ENOTSUP; + } +} + +static int regulator_npm2100_set_mode(const struct device *dev, regulator_mode_t mode) +{ + const struct regulator_npm2100_config *config = dev->config; + + switch (config->source) { + case NPM2100_SOURCE_BOOST: + return set_boost_mode(dev, mode); + case NPM2100_SOURCE_LDOSW: + return set_ldosw_mode(dev, mode); + default: + return -ENOTSUP; + } +} + +static int regulator_npm2100_get_mode(const struct device *dev, regulator_mode_t *mode) +{ + const struct regulator_npm2100_config *config = dev->config; + + switch (config->source) { + case NPM2100_SOURCE_BOOST: + return get_boost_mode(dev, mode); + case NPM2100_SOURCE_LDOSW: + return get_ldosw_mode(dev, mode); + default: + return -ENOTSUP; + } +} + +static int regulator_npm2100_enable(const struct device *dev) +{ + const struct regulator_npm2100_config *config = dev->config; + + if (config->source != NPM2100_SOURCE_LDOSW) { + return 0; + } + + return i2c_reg_write_byte_dt(&config->i2c, LDOSW_ENABLE, 1U); +} + +static int regulator_npm2100_disable(const struct device *dev) +{ + const struct regulator_npm2100_config *config = dev->config; + + if (config->source != NPM2100_SOURCE_LDOSW) { + return 0; + } + + return i2c_reg_write_byte_dt(&config->i2c, LDOSW_ENABLE, 0U); +} + +static int init_pin_ctrl(const struct device *dev, const struct gpio_dt_spec *spec) +{ + const struct regulator_npm2100_config *config = dev->config; + + if (spec->port == NULL) { + return 0; + } + + int ret = gpio_pin_configure_dt(spec, GPIO_INPUT); + + if (ret != 0) { + return ret; + } + + uint8_t pin = spec->pin << 1U; + uint8_t offset = ((spec->dt_flags & GPIO_ACTIVE_LOW) != 0U) ? 0U : 1U; + + switch (config->source) { + case NPM2100_SOURCE_BOOST: + return i2c_reg_write_byte_dt(&config->i2c, BOOST_GPIO, pin + offset + 1U); + case NPM2100_SOURCE_LDOSW: + return i2c_reg_update_byte_dt(&config->i2c, LDOSW_GPIO, LDOSW_GPIO_PIN_MASK, + pin + offset); + default: + return -ENODEV; + } +} + +static int regulator_npm2100_dvs_state_set(const struct device *dev, regulator_dvs_state_t state) +{ + const struct regulator_npm2100_pconfig *pconfig = dev->config; + const struct gpio_dt_spec *spec; + int ret; + + for (size_t idx = 0U; idx < 2U; 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 int regulator_npm2100_ship_mode(const struct device *dev) +{ + const struct regulator_npm2100_pconfig *pconfig = dev->config; + + /* Ensure shiphold button is enabled so that wakeup will work */ + int ret = i2c_reg_write_byte_dt(&pconfig->i2c, RESET_WRITESTICKY, 0); + + if (ret < 0) { + return ret; + } + + ret = i2c_reg_write_byte_dt(&pconfig->i2c, RESET_STROBESTICKY, 1U); + if (ret < 0) { + return ret; + } + + return i2c_reg_write_byte_dt(&pconfig->i2c, SHIP_TASK_SHIP, 1U); +} + + +static DEVICE_API(regulator_parent, parent_api) = { + .dvs_state_set = regulator_npm2100_dvs_state_set, + .ship_mode = regulator_npm2100_ship_mode, +}; + +static int regulator_npm2100_common_init(const struct device *dev) +{ + const struct regulator_npm2100_pconfig *pconfig = dev->config; + const struct gpio_dt_spec *spec; + int ret; + + for (size_t idx = 0U; idx < 2U; 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; +} + +static int regulator_npm2100_init(const struct device *dev) +{ + const struct regulator_npm2100_config *config = dev->config; + int ret; + + if (!i2c_is_ready_dt(&config->i2c)) { + return -ENODEV; + } + + /* Configure GPIO pin control */ + ret = init_pin_ctrl(dev, &config->mode_gpios); + if (ret != 0) { + return ret; + } + + /* BOOST is always enabled */ + if (config->source == NPM2100_SOURCE_BOOST) { + ret = i2c_reg_write_byte_dt( + &config->i2c, BOOST_OPER, + FIELD_PREP(BOOST_OPER_DPSTIMER_MASK, config->dps_timer)); + if (ret < 0) { + return ret; + } + + ret = i2c_reg_write_byte_dt(&config->i2c, BOOST_LIMIT, config->dps_pulse_limit); + if (ret < 0) { + return ret; + } + + return regulator_common_init(dev, true); + } + + /* Configure LDOSW behavior during watchdog reset */ + if (config->ldosw_wd_reset) { + ret = i2c_reg_write_byte_dt(&config->i2c, RESET_ALTCONFIG, + RESET_ALTCONFIG_LDOSW_OFF); + if (ret < 0) { + return ret; + } + } + + /* Get enable state for LDOSW */ + uint8_t enabled; + + ret = i2c_reg_read_byte_dt(&config->i2c, LDOSW_ENABLE, &enabled); + if (ret < 0) { + return ret; + } + + return regulator_common_init(dev, enabled != 0U); +} + +static DEVICE_API(regulator, api) = { + .enable = regulator_npm2100_enable, + .disable = regulator_npm2100_disable, + .count_voltages = regulator_npm2100_count_voltages, + .list_voltage = regulator_npm2100_list_voltage, + .set_voltage = regulator_npm2100_set_voltage, + .get_voltage = regulator_npm2100_get_voltage, + .count_current_limits = regulator_npm2100_count_currents, + .list_current_limit = regulator_npm2100_list_currents, + .set_current_limit = regulator_npm2100_set_current, + .set_mode = regulator_npm2100_set_mode, + .get_mode = regulator_npm2100_get_mode}; + +#define REGULATOR_NPM2100_DEFINE(node_id, id, _source) \ + static struct regulator_npm2100_data data_##id; \ + \ + static const struct regulator_npm2100_config config_##id = { \ + .common = REGULATOR_DT_COMMON_CONFIG_INIT(node_id), \ + .i2c = I2C_DT_SPEC_GET(DT_GPARENT(node_id)), \ + .source = _source, \ + .mode_gpios = GPIO_DT_SPEC_GET_OR(node_id, mode_gpios, {0}), \ + .ldosw_wd_reset = DT_PROP(node_id, ldosw_wd_reset), \ + .dps_timer = DT_ENUM_IDX_OR(node_id, dps_timer_us, 0), \ + .dps_pulse_limit = DT_PROP_OR(node_id, dps_pulse_limit, 0)}; \ + BUILD_ASSERT(DT_PROP_OR(node_id, dps_pulse_limit, 0) >= 3 || \ + DT_PROP_OR(node_id, dps_pulse_limit, 0) == 0, \ + "Invalid dps_pulse_limit value"); \ + \ + DEVICE_DT_DEFINE(node_id, regulator_npm2100_init, NULL, &data_##id, &config_##id, \ + POST_KERNEL, CONFIG_REGULATOR_NPM2100_INIT_PRIORITY, &api); + +#define REGULATOR_NPM2100_DEFINE_COND(inst, child, source) \ + COND_CODE_1(DT_NODE_EXISTS(DT_INST_CHILD(inst, child)), \ + (REGULATOR_NPM2100_DEFINE(DT_INST_CHILD(inst, child), child##inst, source)), \ + ()) + +#define REGULATOR_NPM2100_DEFINE_ALL(inst) \ + static const struct regulator_npm2100_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})}}; \ + \ + DEVICE_DT_INST_DEFINE(inst, regulator_npm2100_common_init, NULL, NULL, &config_##inst, \ + POST_KERNEL, CONFIG_REGULATOR_NPM2100_COMMON_INIT_PRIORITY, \ + &parent_api); \ + \ + REGULATOR_NPM2100_DEFINE_COND(inst, boost, NPM2100_SOURCE_BOOST) \ + REGULATOR_NPM2100_DEFINE_COND(inst, ldosw, NPM2100_SOURCE_LDOSW) + +DT_INST_FOREACH_STATUS_OKAY(REGULATOR_NPM2100_DEFINE_ALL) diff --git a/dts/bindings/regulator/nordic,npm2100-regulator.yaml b/dts/bindings/regulator/nordic,npm2100-regulator.yaml new file mode 100644 index 00000000000..3c2ec28c361 --- /dev/null +++ b/dts/bindings/regulator/nordic,npm2100-regulator.yaml @@ -0,0 +1,81 @@ +# Copyright (c), 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: | + Nordic nPM2100 PMIC + + The PMIC has one boost converter and one LDO/LDSW. + The regulators need to be defined as child nodes, + strictly following the BOOST, LDOSW node names. + For example: + + pmic@74 { + reg = <0x74>; + ... + regulators { + compatible = "nordic,npm2100-regulator"; + + BOOST { + /* all properties for BOOST */ + }; + LDOSW { + /* all properties for LDOSW */ + }; + }; + }; + +compatible: "nordic,npm2100-regulator" + +include: base.yaml + +properties: + dvs-gpios: + type: phandle-array + description: | + List of SOC GPIOs connected to PMIC GPIOs. + Set_dvs_mode will drive these pins as follows: + DVS mode 1 will enable the first pin + DVS mode 2 will enable the second pin + The effect of the mode change is defined by the mode-gpios + fields for each of the regulator blocks. + +child-binding: + include: + - name: regulator.yaml + property-allowlist: + - regulator-always-on + - regulator-boot-on + - regulator-min-microvolt + - regulator-max-microvolt + - regulator-init-microvolt + - regulator-allowed-modes + - regulator-initial-mode + - regulator-min-microamp + - regulator-max-microamp + - regulator-init-microamp + + properties: + mode-gpios: + type: phandle-array + description: | + Regulator mode controlled by specified regulator GPIO pin. + + ldosw-wd-reset: + type: boolean + description: | + LDOSW turned off by watchdog reset. + + dps-timer-us: + type: int + description: | + Interval between DPS refresh cycles in microseconds. + enum: + - 100 + - 200 + - 400 + - 800 + + dps-pulse-limit: + type: int + description: | + DPS coil pulse limit per refresh cycle. diff --git a/include/zephyr/dt-bindings/regulator/npm2100.h b/include/zephyr/dt-bindings/regulator/npm2100.h new file mode 100644 index 00000000000..2ec703bbbc3 --- /dev/null +++ b/include/zephyr/dt-bindings/regulator/npm2100.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_NPM2100_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_NPM2100_H_ + +/** + * @defgroup regulator_npm2100 NPM2100 Devicetree helpers. + * @ingroup regulator_interface + * @{ + */ + +/** + * @name NPM2100 Regulator modes + * @{ + */ +/* Load switch selection, applies to LDOSW only */ +#define NPM2100_REG_LDSW_EN 0x01U + +/* DPS modes applies to BOOST only */ +#define NPM2100_REG_DPS_MASK 0x03U +#define NPM2100_REG_DPS_ALLOW 0x01U +#define NPM2100_REG_DPS_ALLOWLP 0x02U + +/* Operating mode */ +#define NPM2100_REG_OPER_MASK 0x1CU +#define NPM2100_REG_OPER_AUTO 0x00U +#define NPM2100_REG_OPER_HP 0x04U +#define NPM2100_REG_OPER_LP 0x08U +#define NPM2100_REG_OPER_ULP 0x0CU +#define NPM2100_REG_OPER_PASS 0x10U +#define NPM2100_REG_OPER_NOHP 0x14U +#define NPM2100_REG_OPER_OFF 0x18U + +/* Forced mode when GPIO active */ +#define NPM2100_REG_FORCE_MASK 0xE0U +#define NPM2100_REG_FORCE_HP 0x20U +#define NPM2100_REG_FORCE_LP 0x40U +#define NPM2100_REG_FORCE_ULP 0x60U +#define NPM2100_REG_FORCE_PASS 0x80U +#define NPM2100_REG_FORCE_NOHP 0xA0U + +/** @} */ + +/** @} */ + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_NPM2100_H_*/ diff --git a/tests/drivers/build_all/regulator/i2c.dtsi b/tests/drivers/build_all/regulator/i2c.dtsi index 0d6e9c4c1a2..489476e69ca 100644 --- a/tests/drivers/build_all/regulator/i2c.dtsi +++ b/tests/drivers/build_all/regulator/i2c.dtsi @@ -107,3 +107,15 @@ mpm54304@7 { BUCK3 {}; BUCK4 {}; }; + +npm2100@8 { + compatible = "nordic,npm2100"; + reg = <0x8>; + + regulators { + compatible = "nordic,npm2100-regulator"; + + BOOST {}; + LDOSW {}; + }; +};