diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt index ab18834253953..7db261845e97c 100644 --- a/drivers/gpio/CMakeLists.txt +++ b/drivers/gpio/CMakeLists.txt @@ -59,6 +59,7 @@ zephyr_library_sources_ifdef(CONFIG_GPIO_NCT38XX_ALERT gpio_nct38xx_alert.c) zephyr_library_sources_ifdef(CONFIG_GPIO_NEORV32 gpio_neorv32.c) zephyr_library_sources_ifdef(CONFIG_GPIO_NPCX gpio_npcx.c) zephyr_library_sources_ifdef(CONFIG_GPIO_NPM1300 gpio_npm1300.c) +zephyr_library_sources_ifdef(CONFIG_GPIO_NPM2100 gpio_npm2100.c) zephyr_library_sources_ifdef(CONFIG_GPIO_NPM6001 gpio_npm6001.c) zephyr_library_sources_ifdef(CONFIG_GPIO_NRFX gpio_nrfx.c) zephyr_library_sources_ifdef(CONFIG_GPIO_NUMAKER gpio_numaker.c) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index c7928055ac449..b944a2f5f7187 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -146,6 +146,7 @@ source "drivers/gpio/Kconfig.nct38xx" source "drivers/gpio/Kconfig.neorv32" source "drivers/gpio/Kconfig.npcx" source "drivers/gpio/Kconfig.npm1300" +source "drivers/gpio/Kconfig.npm2100" source "drivers/gpio/Kconfig.npm6001" source "drivers/gpio/Kconfig.nrfx" source "drivers/gpio/Kconfig.numaker" diff --git a/drivers/gpio/Kconfig.npm2100 b/drivers/gpio/Kconfig.npm2100 new file mode 100644 index 0000000000000..8adba8db21473 --- /dev/null +++ b/drivers/gpio/Kconfig.npm2100 @@ -0,0 +1,19 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config GPIO_NPM2100 + bool "nPM2100 GPIO driver" + default y + depends on DT_HAS_NORDIC_NPM2100_GPIO_ENABLED + select I2C + select MFD + help + Enable the nPM2100 GPIO driver. + +config GPIO_NPM2100_INIT_PRIORITY + int "nPM2100 GPIO driver initialization priority" + depends on GPIO_NPM2100 + default 80 + help + Initialization priority for the nPM2100 GPIO driver. It must be + greater than the I2C controller init priority. diff --git a/drivers/gpio/gpio_npm2100.c b/drivers/gpio/gpio_npm2100.c new file mode 100644 index 0000000000000..da7b649e29ad6 --- /dev/null +++ b/drivers/gpio/gpio_npm2100.c @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nordic_npm2100_gpio + +#include + +#include +#include +#include +#include +#include +#include + +#define NPM2100_GPIO_CONFIG 0x80U +#define NPM2100_GPIO_USAGE 0x83U +#define NPM2100_GPIO_OUTPUT 0x86U +#define NPM2100_GPIO_READ 0x89U + +#define NPM2100_GPIO_PINS 2U + +#define NPM2100_GPIO_CONFIG_INPUT 0x01U +#define NPM2100_GPIO_CONFIG_OUTPUT 0x02U +#define NPM2100_GPIO_CONFIG_OPENDRAIN 0x04U +#define NPM2100_GPIO_CONFIG_PULLDOWN 0x08U +#define NPM2100_GPIO_CONFIG_PULLUP 0x10U +#define NPM2100_GPIO_CONFIG_DRIVE 0x20U +#define NPM2100_GPIO_CONFIG_DEBOUNCE 0x40U + +struct gpio_npm2100_config { + struct gpio_driver_config common; + struct i2c_dt_spec i2c; +}; + +struct gpio_npm2100_data { + struct gpio_driver_data common; +}; + +static int gpio_npm2100_port_get_raw(const struct device *dev, uint32_t *value) +{ + const struct gpio_npm2100_config *config = dev->config; + uint8_t data; + int ret; + + ret = i2c_reg_read_byte_dt(&config->i2c, NPM2100_GPIO_READ, &data); + if (ret < 0) { + return ret; + } + + *value = data; + + return 0; +} + +static int gpio_npm2100_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask, + gpio_port_value_t value) +{ + const struct gpio_npm2100_config *config = dev->config; + int ret = 0; + + for (size_t idx = 0; idx < NPM2100_GPIO_PINS; idx++) { + if ((mask & BIT(idx)) != 0U) { + i2c_reg_write_byte_dt(&config->i2c, NPM2100_GPIO_OUTPUT + idx, + !!(value & BIT(idx))); + if (ret != 0U) { + return ret; + } + } + } + + return ret; +} + +static int gpio_npm2100_port_set_bits_raw(const struct device *dev, gpio_port_pins_t pins) +{ + return gpio_npm2100_port_set_masked_raw(dev, pins, pins); +} + +static int gpio_npm2100_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t pins) +{ + return gpio_npm2100_port_set_masked_raw(dev, pins, 0U); +} + +static inline int gpio_npm2100_configure(const struct device *dev, gpio_pin_t pin, + gpio_flags_t flags) +{ + const struct gpio_npm2100_config *config = dev->config; + uint8_t reg = 0U; + + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + if (pin >= NPM2100_GPIO_PINS) { + return -EINVAL; + } + + /* Set initial state if defined */ + if ((flags & (GPIO_OUTPUT_INIT_LOW | GPIO_OUTPUT_INIT_HIGH)) != 0U) { + int ret = i2c_reg_write_byte_dt(&config->i2c, NPM2100_GPIO_OUTPUT + pin, + !!(flags & GPIO_OUTPUT_INIT_HIGH)); + if (ret < 0) { + return ret; + } + } + + /* Set pin configuration */ + if ((flags & GPIO_INPUT) != 0U) { + reg |= NPM2100_GPIO_CONFIG_INPUT; + } + if ((flags & GPIO_OUTPUT) != 0U) { + reg |= NPM2100_GPIO_CONFIG_OUTPUT; + } + if ((flags & GPIO_SINGLE_ENDED) != 0U) { + reg |= NPM2100_GPIO_CONFIG_OPENDRAIN; + } + if ((flags & GPIO_PULL_UP) != 0U) { + reg |= NPM2100_GPIO_CONFIG_PULLUP; + } + if ((flags & GPIO_PULL_DOWN) != 0U) { + reg |= NPM2100_GPIO_CONFIG_PULLDOWN; + } + if ((flags & NPM2100_GPIO_DRIVE_HIGH) != 0U) { + reg |= NPM2100_GPIO_CONFIG_DRIVE; + } + if ((flags & NPM2100_GPIO_DEBOUNCE_ON) != 0U) { + reg |= NPM2100_GPIO_CONFIG_DEBOUNCE; + } + + return i2c_reg_write_byte_dt(&config->i2c, NPM2100_GPIO_CONFIG + pin, reg); +} + +static int gpio_npm2100_port_toggle_bits(const struct device *dev, gpio_port_pins_t pins) +{ + int ret; + uint32_t value; + + ret = gpio_npm2100_port_get_raw(dev, &value); + if (ret < 0) { + return ret; + } + + return gpio_npm2100_port_set_masked_raw(dev, pins, ~value); +} + +static DEVICE_API(gpio, gpio_npm2100_api) = { + .pin_configure = gpio_npm2100_configure, + .port_get_raw = gpio_npm2100_port_get_raw, + .port_set_masked_raw = gpio_npm2100_port_set_masked_raw, + .port_set_bits_raw = gpio_npm2100_port_set_bits_raw, + .port_clear_bits_raw = gpio_npm2100_port_clear_bits_raw, + .port_toggle_bits = gpio_npm2100_port_toggle_bits, +}; + +static int gpio_npm2100_init(const struct device *dev) +{ + const struct gpio_npm2100_config *config = dev->config; + + if (!i2c_is_ready_dt(&config->i2c)) { + return -ENODEV; + } + + return 0; +} + +#define GPIO_NPM2100_DEFINE(n) \ + static const struct gpio_npm2100_config gpio_npm2100_config##n = { \ + .common = \ + { \ + .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n), \ + }, \ + .i2c = I2C_DT_SPEC_GET(DT_INST_PARENT(n))}; \ + \ + static struct gpio_npm2100_data gpio_npm2100_data##n; \ + \ + DEVICE_DT_INST_DEFINE(n, &gpio_npm2100_init, NULL, &gpio_npm2100_data##n, \ + &gpio_npm2100_config##n, POST_KERNEL, \ + CONFIG_GPIO_NPM2100_INIT_PRIORITY, &gpio_npm2100_api); + +DT_INST_FOREACH_STATUS_OKAY(GPIO_NPM2100_DEFINE) diff --git a/drivers/mfd/CMakeLists.txt b/drivers/mfd/CMakeLists.txt index bc4d37cbd22c5..a9886f0d0d68a 100644 --- a/drivers/mfd/CMakeLists.txt +++ b/drivers/mfd/CMakeLists.txt @@ -7,6 +7,7 @@ zephyr_library_sources_ifdef(CONFIG_MFD_ADP5585 mfd_adp5585.c) zephyr_library_sources_ifdef(CONFIG_MFD_MAX20335 mfd_max20335.c) zephyr_library_sources_ifdef(CONFIG_MFD_NCT38XX mfd_nct38xx.c) zephyr_library_sources_ifdef(CONFIG_MFD_NPM1300 mfd_npm1300.c) +zephyr_library_sources_ifdef(CONFIG_MFD_NPM2100 mfd_npm2100.c) zephyr_library_sources_ifdef(CONFIG_MFD_NPM6001 mfd_npm6001.c) zephyr_library_sources_ifdef(CONFIG_MFD_AXP192 mfd_axp192.c) zephyr_library_sources_ifdef(CONFIG_MFD_AD559X mfd_ad559x.c) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 13c1dff795c83..f9705c8bd8cfa 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -26,6 +26,7 @@ source "drivers/mfd/Kconfig.max20335" source "drivers/mfd/Kconfig.max31790" source "drivers/mfd/Kconfig.nct38xx" source "drivers/mfd/Kconfig.npm1300" +source "drivers/mfd/Kconfig.npm2100" source "drivers/mfd/Kconfig.npm6001" source "drivers/mfd/Kconfig.lpflexcomm" source "drivers/mfd/Kconfig.tle9104" diff --git a/drivers/mfd/Kconfig.npm2100 b/drivers/mfd/Kconfig.npm2100 new file mode 100644 index 0000000000000..aed9829b921b2 --- /dev/null +++ b/drivers/mfd/Kconfig.npm2100 @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config MFD_NPM2100 + bool "nPM2100 PMIC multi-function device driver" + default y + depends on DT_HAS_NORDIC_NPM2100_ENABLED + select I2C + help + Enable the Nordic nPM2100 PMIC multi-function device driver diff --git a/drivers/mfd/mfd_npm2100.c b/drivers/mfd/mfd_npm2100.c new file mode 100644 index 0000000000000..316687da1e946 --- /dev/null +++ b/drivers/mfd/mfd_npm2100.c @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nordic_npm2100 + +#include + +#include +#include +#include +#include +#include +#include + +#define EVENTS_SET 0x00U +#define EVENTS_CLR 0x05U +#define INTEN_SET 0x0AU +#define GPIO_CONFIG 0x80U +#define GPIO_USAGE 0x83U +#define TIMER_TASKS_START 0xB0U +#define TIMER_CONFIG 0xB3U +#define TIMER_TARGET 0xB4U +#define TIMER_STATUS 0xB7U +#define SHPHLD_WAKEUP 0xC1U +#define SHPHLD_SHPHLD 0xC2U +#define HIBERNATE_TASKS_HIBER 0xC8U +#define RESET_TASKS_RESET 0xD0U +#define RESET_BUTTON 0xD2U +#define RESET_PIN 0xD3U +#define RESET_WRITESTICKY 0xDBU +#define RESET_STROBESTICKY 0xDCU + +#define SHPHLD_RESISTOR_MASK 0x03U +#define SHPHLD_RESISTOR_PULLUP 0x00U +#define SHPHLD_RESISTOR_NONE 0x01U +#define SHPHLD_RESISTOR_PULLDOWN 0x02U +#define SHPHLD_CURR_MASK 0x0CU +#define SHPHLD_PULL_ENABLE 0x10U + +#define WAKEUP_EDGE_FALLING 0x00U +#define WAKEUP_EDGE_RISING 0x01U +#define WAKEUP_HIBERNATE_PIN 0x00U +#define WAKEUP_HIBERNATE_NOPIN 0x02U + +#define TIMER_CONFIG_WKUP 3U + +#define TIMER_STATUS_IDLE 0U + +#define TIMER_PRESCALER_MUL 64ULL +#define TIMER_PRESCALER_DIV 1000ULL +#define TIMER_MAX 0xFFFFFFU + +#define EVENTS_SIZE 5U + +#define GPIO_USAGE_INTLO 0x01U +#define GPIO_USAGE_INTHI 0x02U +#define GPIO_CONFIG_OUTPUT 0x02U + +#define RESET_STICKY_PWRBUT 0x04U + +#define SHPHLD_LONGPRESS_SHIP 0 +#define SHPHLD_LONGPRESS_DISABLE 1 +#define SHPHLD_LONGPRESS_RESET 2 + +struct mfd_npm2100_config { + struct i2c_dt_spec i2c; + struct gpio_dt_spec host_int_gpios; + gpio_flags_t host_int_flags; + gpio_pin_t pmic_int_pin; + gpio_flags_t pmic_int_flags; + gpio_flags_t shiphold_flags; + uint8_t shiphold_longpress; + uint8_t shiphold_current; + uint8_t shiphold_hibernate_wakeup; +}; + +struct mfd_npm2100_data { + const struct device *dev; + struct gpio_callback gpio_cb; + struct k_work work; + sys_slist_t callbacks; +}; + +struct event_reg_t { + uint8_t offset; + uint8_t mask; +}; + +static const struct event_reg_t event_reg[NPM2100_EVENT_MAX] = { + [NPM2100_EVENT_SYS_DIETEMP_WARN] = {0x00U, 0x01U}, + [NPM2100_EVENT_SYS_SHIPHOLD_FALL] = {0x00U, 0x02U}, + [NPM2100_EVENT_SYS_SHIPHOLD_RISE] = {0x00U, 0x04U}, + [NPM2100_EVENT_SYS_PGRESET_FALL] = {0x00U, 0x08U}, + [NPM2100_EVENT_SYS_PGRESET_RISE] = {0x00U, 0x10U}, + [NPM2100_EVENT_SYS_TIMER_EXPIRY] = {0x00U, 0x20U}, + [NPM2100_EVENT_ADC_VBAT_READY] = {0x01U, 0x01U}, + [NPM2100_EVENT_ADC_DIETEMP_READY] = {0x01U, 0x02U}, + [NPM2100_EVENT_ADC_DROOP_DETECT] = {0x01U, 0x04U}, + [NPM2100_EVENT_ADC_VOUT_READY] = {0x01U, 0x08U}, + [NPM2100_EVENT_GPIO0_FALL] = {0x02U, 0x01U}, + [NPM2100_EVENT_GPIO0_RISE] = {0x02U, 0x02U}, + [NPM2100_EVENT_GPIO1_FALL] = {0x02U, 0x04U}, + [NPM2100_EVENT_GPIO1_RISE] = {0x02U, 0x08U}, + [NPM2100_EVENT_BOOST_VBAT_WARN] = {0x03U, 0x01U}, + [NPM2100_EVENT_BOOST_VOUT_MIN] = {0x03U, 0x02U}, + [NPM2100_EVENT_BOOST_VOUT_WARN] = {0x03U, 0x04U}, + [NPM2100_EVENT_BOOST_VOUT_DPS] = {0x03U, 0x08U}, + [NPM2100_EVENT_BOOST_VOUT_OK] = {0x03U, 0x10U}, + [NPM2100_EVENT_LDOSW_OCP] = {0x04U, 0x01U}, + [NPM2100_EVENT_LDOSW_VINTFAIL] = {0x04U, 0x02U}, +}; + +static void gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) +{ + struct mfd_npm2100_data *data = CONTAINER_OF(cb, struct mfd_npm2100_data, gpio_cb); + const struct mfd_npm2100_config *config = data->dev->config; + + if (config->host_int_flags & GPIO_INT_LEVEL_ACTIVE) { + /* When using level irq, disable until it can be cleared in the work callback */ + gpio_pin_interrupt_configure_dt(&config->host_int_gpios, GPIO_INT_DISABLE); + } + + k_work_submit(&data->work); +} + +static void work_callback(struct k_work *work) +{ + struct mfd_npm2100_data *data = CONTAINER_OF(work, struct mfd_npm2100_data, work); + const struct mfd_npm2100_config *config = data->dev->config; + uint8_t buf[EVENTS_SIZE + 1U] = {EVENTS_SET}; + int ret; + + /* Read MAIN SET registers into buffer, leaving space for register address */ + ret = i2c_write_read_dt(&config->i2c, &buf[0], 1U, &buf[1], EVENTS_SIZE); + if (ret < 0) { + k_work_submit(&data->work); + goto enable_irq; + } + + for (int i = 0; i < NPM2100_EVENT_MAX; i++) { + if ((buf[event_reg[i].offset + 1U] & event_reg[i].mask) != 0U) { + gpio_fire_callbacks(&data->callbacks, data->dev, BIT(i)); + } + } + + /* Write read buffer back to clear registers to clear all processed events */ + buf[0] = EVENTS_CLR; + ret = i2c_write_dt(&config->i2c, buf, EVENTS_SIZE + 1U); + if (ret < 0) { + k_work_submit(&data->work); + goto enable_irq; + } + + /* Resubmit handler to queue if interrupt is still active */ + if (gpio_pin_get_dt(&config->host_int_gpios) != 0) { + k_work_submit(&data->work); + } + +enable_irq: + + if (config->host_int_flags & GPIO_INT_LEVEL_ACTIVE) { + /* Re-enable irq */ + gpio_pin_interrupt_configure_dt(&config->host_int_gpios, config->host_int_flags); + } +} + +static int config_pmic_int(const struct device *dev) +{ + const struct mfd_npm2100_config *config = dev->config; + uint8_t usage = GPIO_USAGE_INTHI; + + if (config->pmic_int_flags & GPIO_ACTIVE_LOW) { + usage = GPIO_USAGE_INTLO; + } + + /* Set specified PMIC pin to be interrupt output */ + int ret = i2c_reg_write_byte_dt(&config->i2c, GPIO_USAGE + config->pmic_int_pin, usage); + + if (ret < 0) { + return ret; + } + + /* Configure PMIC output pin */ + return i2c_reg_write_byte_dt(&config->i2c, GPIO_CONFIG + config->pmic_int_pin, + GPIO_CONFIG_OUTPUT); +} + +static int config_shphold(const struct device *dev) +{ + const struct mfd_npm2100_config *config = dev->config; + uint8_t reg; + int ret; + + if (config->shiphold_longpress != SHPHLD_LONGPRESS_SHIP) { + ret = i2c_reg_write_byte_dt(&config->i2c, RESET_WRITESTICKY, RESET_STICKY_PWRBUT); + if (ret < 0) { + return ret; + } + + ret = i2c_reg_write_byte_dt(&config->i2c, RESET_STROBESTICKY, 1U); + if (ret < 0) { + return ret; + } + + if (config->shiphold_longpress == SHPHLD_LONGPRESS_RESET) { + ret = i2c_reg_write_byte_dt(&config->i2c, RESET_BUTTON, 0U); + if (ret < 0) { + return ret; + } + + ret = i2c_reg_write_byte_dt(&config->i2c, RESET_PIN, 1U); + if (ret < 0) { + return ret; + } + } + } + + reg = config->shiphold_hibernate_wakeup ? WAKEUP_HIBERNATE_PIN : WAKEUP_HIBERNATE_NOPIN; + if ((config->shiphold_flags & GPIO_ACTIVE_HIGH) != 0U) { + reg |= WAKEUP_EDGE_RISING; + } + + ret = i2c_reg_write_byte_dt(&config->i2c, SHPHLD_WAKEUP, reg); + if (ret < 0) { + return ret; + } + + if ((config->shiphold_flags & GPIO_PULL_UP) != 0U) { + reg = SHPHLD_RESISTOR_PULLUP; + } else if ((config->shiphold_flags & GPIO_PULL_DOWN) != 0U) { + reg = SHPHLD_RESISTOR_PULLDOWN; + } else { + reg = SHPHLD_RESISTOR_NONE; + } + if (config->shiphold_current != 0U) { + reg |= FIELD_PREP(SHPHLD_CURR_MASK, (config->shiphold_current - 1U)); + reg |= SHPHLD_PULL_ENABLE; + } + + return i2c_reg_write_byte_dt(&config->i2c, SHPHLD_SHPHLD, reg); +} + +static int mfd_npm2100_init(const struct device *dev) +{ + const struct mfd_npm2100_config *config = dev->config; + struct mfd_npm2100_data *mfd_data = dev->data; + int ret; + + if (!i2c_is_ready_dt(&config->i2c)) { + return -ENODEV; + } + + mfd_data->dev = dev; + + ret = config_shphold(dev); + if (ret < 0) { + return ret; + } + + if (config->host_int_gpios.port == NULL) { + return 0; + } + + ret = config_pmic_int(dev); + if (ret < 0) { + return ret; + } + + /* Configure host interrupt GPIO */ + if (!gpio_is_ready_dt(&config->host_int_gpios)) { + return -ENODEV; + } + + ret = gpio_pin_configure_dt(&config->host_int_gpios, GPIO_INPUT); + if (ret < 0) { + return ret; + } + + gpio_init_callback(&mfd_data->gpio_cb, gpio_callback, BIT(config->host_int_gpios.pin)); + + ret = gpio_add_callback_dt(&config->host_int_gpios, &mfd_data->gpio_cb); + if (ret < 0) { + return ret; + } + + mfd_data->work.handler = work_callback; + + return gpio_pin_interrupt_configure_dt(&config->host_int_gpios, config->host_int_flags); +} + +int mfd_npm2100_set_timer(const struct device *dev, uint32_t time_ms, + enum mfd_npm2100_timer_mode mode) +{ + const struct mfd_npm2100_config *config = dev->config; + uint8_t buff[4] = {TIMER_TARGET}; + uint32_t ticks = (uint32_t)DIV_ROUND_CLOSEST(((uint64_t)time_ms * TIMER_PRESCALER_MUL), + TIMER_PRESCALER_DIV); + uint8_t timer_status; + int ret; + + if (ticks > TIMER_MAX) { + return -EINVAL; + } + + ret = i2c_reg_read_byte_dt(&config->i2c, TIMER_STATUS, &timer_status); + if (ret < 0) { + return ret; + } + + if (timer_status != TIMER_STATUS_IDLE) { + return -EBUSY; + } + + sys_put_be24(ticks, &buff[1]); + + ret = i2c_write_dt(&config->i2c, buff, sizeof(buff)); + if (ret < 0) { + return ret; + } + + return i2c_reg_write_byte_dt(&config->i2c, TIMER_CONFIG, mode); +} + +int mfd_npm2100_start_timer(const struct device *dev) +{ + const struct mfd_npm2100_config *config = dev->config; + + return i2c_reg_write_byte_dt(&config->i2c, TIMER_TASKS_START, 1U); +} + +int mfd_npm2100_reset(const struct device *dev) +{ + const struct mfd_npm2100_config *config = dev->config; + + return i2c_reg_write_byte_dt(&config->i2c, RESET_TASKS_RESET, 1U); +} + +int mfd_npm2100_hibernate(const struct device *dev, uint32_t time_ms) +{ + const struct mfd_npm2100_config *config = dev->config; + int ret; + + if (time_ms > 0) { + ret = mfd_npm2100_set_timer(dev, time_ms, NPM2100_TIMER_MODE_WAKEUP); + if (ret < 0) { + return ret; + } + + ret = mfd_npm2100_start_timer(dev); + if (ret < 0) { + return ret; + } + } + + /* Ensure shiphold button is enabled so that wakeup will work */ + ret = i2c_reg_write_byte_dt(&config->i2c, RESET_WRITESTICKY, 0); + if (ret < 0) { + return ret; + } + + ret = i2c_reg_write_byte_dt(&config->i2c, RESET_STROBESTICKY, 1U); + if (ret < 0) { + return ret; + } + + return i2c_reg_write_byte_dt(&config->i2c, HIBERNATE_TASKS_HIBER, 1U); +} + +int mfd_npm2100_add_callback(const struct device *dev, struct gpio_callback *callback) +{ + const struct mfd_npm2100_config *config = dev->config; + struct mfd_npm2100_data *data = dev->data; + + /* Enable interrupts for specified events */ + for (int i = 0; i < NPM2100_EVENT_MAX; i++) { + if ((callback->pin_mask & BIT(i)) != 0U) { + /* Clear pending interrupt */ + int ret = i2c_reg_write_byte_dt( + &config->i2c, event_reg[i].offset + EVENTS_CLR, event_reg[i].mask); + + if (ret < 0) { + return ret; + } + + ret = i2c_reg_write_byte_dt(&config->i2c, event_reg[i].offset + INTEN_SET, + event_reg[i].mask); + if (ret < 0) { + return ret; + } + } + } + + return gpio_manage_callback(&data->callbacks, callback, true); +} + +int mfd_npm2100_remove_callback(const struct device *dev, struct gpio_callback *callback) +{ + struct mfd_npm2100_data *data = dev->data; + + return gpio_manage_callback(&data->callbacks, callback, false); +} + +#define MFD_NPM2100_DEFINE(inst) \ + static struct mfd_npm2100_data data##inst; \ + \ + static const struct mfd_npm2100_config config##inst = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + .host_int_gpios = GPIO_DT_SPEC_INST_GET_OR(inst, host_int_gpios, {0}), \ + .host_int_flags = DT_INST_ENUM_IDX_OR(inst, host_int_type, 0) == 0 \ + ? GPIO_INT_EDGE_TO_ACTIVE \ + : GPIO_INT_LEVEL_ACTIVE, \ + .pmic_int_pin = DT_INST_PROP_OR(inst, pmic_int_pin, 0), \ + .pmic_int_flags = DT_INST_PROP_OR(inst, pmic_int_flags, 0), \ + .shiphold_flags = \ + DT_INST_PROP_OR(inst, shiphold_flags, (GPIO_ACTIVE_LOW | GPIO_PULL_UP)), \ + .shiphold_longpress = DT_INST_ENUM_IDX_OR(inst, shiphold_longpress, 0), \ + .shiphold_current = DT_INST_ENUM_IDX_OR(inst, shiphold_current, 0), \ + .shiphold_hibernate_wakeup = DT_INST_PROP_OR(inst, shiphold_hibernate_wakeup, 0), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, mfd_npm2100_init, NULL, &data##inst, &config##inst, \ + POST_KERNEL, CONFIG_MFD_INIT_PRIORITY, NULL); + +DT_INST_FOREACH_STATUS_OKAY(MFD_NPM2100_DEFINE) diff --git a/drivers/regulator/CMakeLists.txt b/drivers/regulator/CMakeLists.txt index ddf56929af9a6..7ed90137c3f24 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 88f114f8eb018..d4b70e6b76add 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 0000000000000..4d1476dbbea80 --- /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 0000000000000..afad574c767ea --- /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/drivers/sensor/nordic/CMakeLists.txt b/drivers/sensor/nordic/CMakeLists.txt index 89265c0e59d3b..49a0a48d7fd9b 100644 --- a/drivers/sensor/nordic/CMakeLists.txt +++ b/drivers/sensor/nordic/CMakeLists.txt @@ -4,5 +4,6 @@ # zephyr-keep-sorted-start add_subdirectory(temp) add_subdirectory_ifdef(CONFIG_NPM1300_CHARGER npm1300_charger) +add_subdirectory_ifdef(CONFIG_NPM2100_VBAT npm2100_vbat) add_subdirectory_ifdef(CONFIG_QDEC_NRFX qdec_nrfx) # zephyr-keep-sorted-stop diff --git a/drivers/sensor/nordic/Kconfig b/drivers/sensor/nordic/Kconfig index 27f1cfedf59af..0cdbaaf41e71f 100644 --- a/drivers/sensor/nordic/Kconfig +++ b/drivers/sensor/nordic/Kconfig @@ -3,6 +3,7 @@ # zephyr-keep-sorted-start source "drivers/sensor/nordic/npm1300_charger/Kconfig" +source "drivers/sensor/nordic/npm2100_vbat/Kconfig" source "drivers/sensor/nordic/qdec_nrfx/Kconfig" source "drivers/sensor/nordic/temp/Kconfig" # zephyr-keep-sorted-stop diff --git a/drivers/sensor/nordic/npm2100_vbat/CMakeLists.txt b/drivers/sensor/nordic/npm2100_vbat/CMakeLists.txt new file mode 100644 index 0000000000000..afc2cb36fe546 --- /dev/null +++ b/drivers/sensor/nordic/npm2100_vbat/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(npm2100_vbat.c) diff --git a/drivers/sensor/nordic/npm2100_vbat/Kconfig b/drivers/sensor/nordic/npm2100_vbat/Kconfig new file mode 100644 index 0000000000000..8a2baa658ed19 --- /dev/null +++ b/drivers/sensor/nordic/npm2100_vbat/Kconfig @@ -0,0 +1,12 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +config NPM2100_VBAT + bool "NPM2100 Battery Voltage" + default y + depends on DT_HAS_NORDIC_NPM2100_VBAT_ENABLED + select I2C + select MFD + help + Enable NPM2100 battery voltage driver. diff --git a/drivers/sensor/nordic/npm2100_vbat/npm2100_vbat.c b/drivers/sensor/nordic/npm2100_vbat/npm2100_vbat.c new file mode 100644 index 0000000000000..f1ab25938c3f3 --- /dev/null +++ b/drivers/sensor/nordic/npm2100_vbat/npm2100_vbat.c @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nordic_npm2100_vbat + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(vbat_npm2100, CONFIG_SENSOR_LOG_LEVEL); + +#define BOOST_START 0x20U +#define BOOST_OPER 0x24U +#define BOOST_DPSCOUNT 0x25U +#define BOOST_DPSLIMIT 0x26U +#define BOOST_DPSDUR 0x27U +#define BOOST_CTRLSET 0x2AU +#define BOOST_CTRLCLR 0x2BU +#define BOOST_VBATSEL 0x2EU +#define BOOST_VBATMINL 0x2FU +#define BOOST_VBATMINH 0x30U +#define BOOST_VOUTMIN 0x31U +#define BOOST_VOUTWRN 0x32U +#define BOOST_VOUTDPS 0x33U + +#define ADC_TASKS_ADC 0x90U +#define ADC_CONFIG 0X91U +#define ADC_DELAY 0x92U +#define ADC_OFFSETCFG 0x93U +#define ADC_CTRLSET 0x94U +#define ADC_CTRLCLR 0x95U +#define ADC_RESULTS 0x96U +#define ADC_READVBAT 0x96U +#define ADC_READTEMP 0x97U +#define ADC_READDROOP 0x98U +#define ADC_READVOUT 0x99U +#define ADC_AVERAGE 0x9BU +#define ADC_OFFSETMEASURED 0x9FU + +#define ADC_CONFIG_MODE_MASK 0x07U +#define ADC_CONFIG_MODE_INS_VBAT 0x00U +#define ADC_CONFIG_MODE_DEL_VBAT 0x01U +#define ADC_CONFIG_MODE_TEMP 0x02U +#define ADC_CONFIG_MODE_DROOP 0x03U +#define ADC_CONFIG_MODE_VOUT 0x04U +#define ADC_CONFIG_MODE_OFFSET 0x05U +#define ADC_CONFIG_AVG_MASK 0x38U + +#define ADC_SAMPLE_TIME_US 100 + +#define VBAT_SCALING_OFFSET 0 +#define VBAT_SCALING_MUL 3200000 +#define VBAT_SCALING_DIV 256 +#define VOUT_SCALING_OFFSET 1800000 +#define VOUT_SCALING_MUL 1500000 +#define VOUT_SCALING_DIV 256 +#define TEMP_SCALING_OFFSET 389500000 +#define TEMP_SCALING_MUL 2120000 +#define TEMP_SCALING_DIV -1 +#define DPS_SCALING_OFFSET 0 +#define DPS_SCALING_MUL 1000000 +#define DPS_SCALING_DIV 1 + +static const struct linear_range vbat_range = LINEAR_RANGE_INIT(650000, 50000, 0U, 50U); +static const struct linear_range vout_range = LINEAR_RANGE_INIT(1700000, 50000, 0U, 31U); +static const struct linear_range vdps_range = LINEAR_RANGE_INIT(1900000, 50000, 0U, 31U); +static const struct linear_range dpslim_range = LINEAR_RANGE_INIT(0, 1, 0U, 255U); +static const struct linear_range dpstimer_range = LINEAR_RANGE_INIT(0, 1, 0U, 3U); +static const struct linear_range oversample_range = LINEAR_RANGE_INIT(0, 1, 0U, 4U); +static const struct linear_range adcdelay_range = LINEAR_RANGE_INIT(5000, 4000, 0U, 255U); + +struct npm2100_vbat_config { + struct i2c_dt_spec i2c; + struct sensor_value voutmin; + struct sensor_value vbatmin; +}; + +struct adc_config { + const enum sensor_channel chan; + const uint8_t result_reg; + uint8_t config; + uint8_t result; + bool enabled; +}; + +struct npm2100_vbat_data { + struct adc_config adc[4U]; + uint8_t vbat_delay; + uint8_t dpsdur; +}; + +struct npm2100_attr_t { + enum sensor_channel chan; + enum sensor_attribute attr; + const struct linear_range *range; + uint8_t reg; + uint8_t reg_mask; + uint8_t ctrlsel_mask; +}; + +static const struct npm2100_attr_t npm2100_attr[] = { + {SENSOR_CHAN_GAUGE_VOLTAGE, SENSOR_ATTR_UPPER_THRESH, &vbat_range, BOOST_VBATMINH, 0xFF, 0}, + {SENSOR_CHAN_GAUGE_VOLTAGE, SENSOR_ATTR_LOWER_THRESH, &vbat_range, BOOST_VBATMINL, 0xFF, 0}, + {SENSOR_CHAN_VOLTAGE, SENSOR_ATTR_UPPER_THRESH, &vdps_range, BOOST_VOUTDPS, 0xFF, BIT(2)}, + {SENSOR_CHAN_VOLTAGE, SENSOR_ATTR_LOWER_THRESH, &vout_range, BOOST_VOUTMIN, 0xFF, BIT(0)}, + {SENSOR_CHAN_VOLTAGE, SENSOR_ATTR_ALERT, &vout_range, BOOST_VOUTWRN, 0xFF, BIT(1)}, + {(enum sensor_channel)SENSOR_CHAN_NPM2100_DPS_COUNT, SENSOR_ATTR_UPPER_THRESH, + &dpslim_range, BOOST_DPSLIMIT, 0xFF, 0}, + {(enum sensor_channel)SENSOR_CHAN_NPM2100_DPS_TIMER, SENSOR_ATTR_UPPER_THRESH, + &dpstimer_range, BOOST_OPER, 0x60, 0}, +}; + +static struct adc_config *adc_cfg_get(const struct device *dev, enum sensor_channel chan) +{ + struct npm2100_vbat_data *const data = dev->data; + + for (int idx = 0; idx < ARRAY_SIZE(data->adc); idx++) { + if (data->adc[idx].chan == chan) { + return &data->adc[idx]; + } + } + + return NULL; +} + +int npm2100_vbat_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *valp) +{ + uint8_t *result = NULL; + + if ((uint32_t)chan == SENSOR_CHAN_NPM2100_DPS_DURATION) { + struct npm2100_vbat_data *const data = dev->data; + + result = &data->dpsdur; + } else { + struct adc_config *adc_cfg = adc_cfg_get(dev, chan); + + if (!adc_cfg) { + return -ENOTSUP; + } + + result = &adc_cfg->result; + } + + if (!result) { + return -ENOTSUP; + } + + int32_t scaling_mul; + int32_t scaling_div; + int32_t scaling_off; + + switch ((uint32_t)chan) { + case SENSOR_CHAN_GAUGE_VOLTAGE: + scaling_mul = VBAT_SCALING_MUL; + scaling_div = VBAT_SCALING_DIV; + scaling_off = VBAT_SCALING_OFFSET; + break; + case SENSOR_CHAN_VOLTAGE: + /* Fall through */ + case SENSOR_CHAN_NPM2100_VOLT_DROOP: + scaling_mul = VOUT_SCALING_MUL; + scaling_div = VOUT_SCALING_DIV; + scaling_off = VOUT_SCALING_OFFSET; + break; + case SENSOR_CHAN_DIE_TEMP: + scaling_mul = TEMP_SCALING_MUL; + scaling_div = TEMP_SCALING_DIV; + scaling_off = TEMP_SCALING_OFFSET; + break; + case SENSOR_CHAN_NPM2100_DPS_DURATION: + scaling_mul = DPS_SCALING_MUL; + scaling_div = DPS_SCALING_DIV; + scaling_off = DPS_SCALING_OFFSET; + break; + default: + return -ENOTSUP; + } + + int32_t tmp = scaling_off + ((int32_t)*result * scaling_mul) / scaling_div; + + valp->val1 = tmp / 1000000; + valp->val2 = tmp % 1000000; + + return 0; +} + +int npm2100_vbat_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + const struct npm2100_vbat_config *const config = dev->config; + struct npm2100_vbat_data *data = dev->data; + int ret; + + for (int idx = 0; idx < ARRAY_SIZE(data->adc); idx++) { + if (!data->adc[idx].enabled) { + continue; + } + + /* Oversampling by 2^field */ + const uint8_t oversampling = + BIT(FIELD_GET(ADC_CONFIG_AVG_MASK, data->adc[idx].config)); + int32_t delay_usec; + + if (chan == SENSOR_CHAN_GAUGE_VOLTAGE) { + ret = linear_range_get_value(&adcdelay_range, data->vbat_delay, + &delay_usec); + if (ret < 0) { + return ret; + } + } else { + delay_usec = 0; + } + + ret = i2c_reg_write_byte_dt(&config->i2c, ADC_CONFIG, data->adc[idx].config); + if (ret < 0) { + return ret; + } + + ret = i2c_reg_write_byte_dt(&config->i2c, ADC_TASKS_ADC, 1U); + if (ret < 0) { + return ret; + } + + k_sleep(K_USEC(ADC_SAMPLE_TIME_US * oversampling + delay_usec)); + + if (oversampling > 1) { + ret = i2c_reg_read_byte_dt(&config->i2c, ADC_AVERAGE, + &data->adc[idx].result); + if (ret < 0) { + return ret; + } + } else { + ret = i2c_reg_read_byte_dt(&config->i2c, data->adc[idx].result_reg, + &data->adc[idx].result); + if (ret < 0) { + return ret; + } + } + } + + /* Fetch previous DPS duration result before triggering new one.The time it takes to get + * the DPS duration depends on many factors and cannot be predicted here. + */ + ret = i2c_reg_read_byte_dt(&config->i2c, BOOST_DPSDUR, &data->dpsdur); + if (ret < 0) { + return ret; + } + + return i2c_reg_write_byte_dt(&config->i2c, BOOST_START, 2U); +} + +static int npm2100_vbat_attr_get(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, struct sensor_value *val) +{ + const struct npm2100_vbat_config *const config = dev->config; + + if (attr == SENSOR_ATTR_FEATURE_MASK) { + struct adc_config *adc_cfg = adc_cfg_get(dev, chan); + + if (!adc_cfg) { + return -EINVAL; + } + + *val = (struct sensor_value){.val1 = adc_cfg->enabled, .val2 = 0}; + + return 0; + } + + if (attr == SENSOR_ATTR_OVERSAMPLING) { + struct adc_config *adc_cfg = adc_cfg_get(dev, chan); + + if (!adc_cfg) { + return -EINVAL; + } + + *val = (struct sensor_value){ + .val1 = FIELD_GET(ADC_CONFIG_AVG_MASK, adc_cfg->config), .val2 = 0}; + + return 0; + } + + /* Delay of vbat ADC measurement */ + if ((chan == SENSOR_CHAN_GAUGE_VOLTAGE) && + (attr == (enum sensor_attribute)SENSOR_ATTR_NPM2100_ADC_DELAY)) { + struct npm2100_vbat_data *const data = dev->data; + struct adc_config *adc_cfg = adc_cfg_get(dev, chan); + int32_t val_usec; + + if (!adc_cfg) { + return -ENOENT; + } + + if (FIELD_GET(ADC_CONFIG_MODE_MASK, adc_cfg->config) == ADC_CONFIG_MODE_INS_VBAT) { + /* Instant measurement */ + return sensor_value_from_micro(val, 0); + } + + int ret = linear_range_get_value(&adcdelay_range, data->vbat_delay, &val_usec); + + if (ret < 0) { + return ret; + } + + return sensor_value_from_micro(val, val_usec); + } + + for (int idx = 0; idx < ARRAY_SIZE(npm2100_attr); idx++) { + if ((npm2100_attr[idx].chan == chan) && (npm2100_attr[idx].attr == attr)) { + + int32_t val_mv; + uint8_t reg_data; + + int ret = i2c_reg_read_byte_dt(&config->i2c, npm2100_attr[idx].reg, + ®_data); + + if (ret < 0) { + return ret; + } + + reg_data = FIELD_GET(npm2100_attr[idx].reg_mask, reg_data); + + ret = linear_range_get_value(npm2100_attr[idx].range, reg_data, &val_mv); + if (ret < 0) { + return ret; + } + + val->val1 = val_mv / 1000000; + val->val2 = val_mv % 1000000; + + return 0; + } + } + + return -ENOTSUP; +} + +static int npm2100_vbat_attr_set(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, const struct sensor_value *val) +{ + const struct npm2100_vbat_config *const config = dev->config; + int ret; + + /* ADC sampling feature masks to enable individual measurements */ + if (attr == SENSOR_ATTR_FEATURE_MASK) { + struct adc_config *adc_cfg = adc_cfg_get(dev, chan); + + if (!adc_cfg) { + return -EINVAL; + } + + adc_cfg->enabled = val->val1 == 0 ? false : true; + + return 0; + } + + /* Delay of vbat ADC measurement */ + if ((chan == SENSOR_CHAN_GAUGE_VOLTAGE) && + (attr == (enum sensor_attribute)SENSOR_ATTR_NPM2100_ADC_DELAY)) { + struct npm2100_vbat_data *const data = dev->data; + struct adc_config *adc_cfg = adc_cfg_get(dev, chan); + int32_t val_usec = sensor_value_to_micro(val); + uint16_t delay; + + if (!adc_cfg) { + return -ENOENT; + } + + if (val_usec == 0) { + delay = 0; + } else { + ret = linear_range_get_index(&adcdelay_range, val_usec, &delay); + if (ret < 0) { + return ret; + } + + ret = i2c_reg_write_byte_dt(&config->i2c, ADC_DELAY, delay); + if (ret < 0) { + return ret; + } + } + + /* Delayed vbat measurement uses different mode */ + data->vbat_delay = delay; + adc_cfg->config &= ~ADC_CONFIG_MODE_MASK; + if (delay == 0) { + adc_cfg->config |= + FIELD_PREP(ADC_CONFIG_MODE_MASK, ADC_CONFIG_MODE_INS_VBAT); + } else { + adc_cfg->config |= + FIELD_PREP(ADC_CONFIG_MODE_MASK, ADC_CONFIG_MODE_DEL_VBAT); + } + + return 0; + } + + /* ADC per-channel oversampling */ + if (attr == SENSOR_ATTR_OVERSAMPLING) { + struct adc_config *adc_cfg = adc_cfg_get(dev, chan); + uint16_t oversample; + + if (!adc_cfg) { + return -ENOENT; + } + + /* Oversample factor is 2^value */ + ret = linear_range_get_index(&oversample_range, val->val1, &oversample); + if (ret < 0) { + return ret; + } + + adc_cfg->config &= ~ADC_CONFIG_AVG_MASK; + adc_cfg->config |= FIELD_PREP(ADC_CONFIG_AVG_MASK, oversample); + + return 0; + } + + /* Threshold attributes */ + for (int idx = 0; idx < ARRAY_SIZE(npm2100_attr); idx++) { + if ((npm2100_attr[idx].chan == chan) && (npm2100_attr[idx].attr == attr)) { + + uint16_t range_idx; + uint8_t reg_data; + + ret = linear_range_get_index(npm2100_attr[idx].range, + sensor_value_to_micro(val), &range_idx); + + if (ret < 0) { + return ret; + } + + reg_data = FIELD_PREP(npm2100_attr[idx].reg_mask, range_idx); + + if (npm2100_attr[idx].ctrlsel_mask != 0) { + /* Disable comparator */ + ret = i2c_reg_write_byte_dt(&config->i2c, BOOST_CTRLCLR, + npm2100_attr[idx].ctrlsel_mask); + if (ret < 0) { + return ret; + } + } + + /* Set threshold */ + if (npm2100_attr[idx].reg_mask == 0xFFU) { + ret = i2c_reg_write_byte_dt(&config->i2c, npm2100_attr[idx].reg, + reg_data); + } else { + ret = i2c_reg_update_byte_dt(&config->i2c, npm2100_attr[idx].reg, + npm2100_attr[idx].reg_mask, reg_data); + } + if (ret < 0) { + return ret; + } + + if (npm2100_attr[idx].ctrlsel_mask != 0) { + /* Enable comparator */ + ret = i2c_reg_write_byte_dt(&config->i2c, BOOST_CTRLSET, + npm2100_attr[idx].ctrlsel_mask); + } + + return ret; + } + } + + return -ENOTSUP; +} + +int npm2100_vbat_init(const struct device *dev) +{ + const struct npm2100_vbat_config *const config = dev->config; + int ret; + + if (!i2c_is_ready_dt(&config->i2c)) { + LOG_ERR("%s i2c not ready", dev->name); + return -ENODEV; + } + + /* Set initial voltage thresholds */ + if ((config->voutmin.val1 != 0) || (config->voutmin.val2 != 0)) { + ret = npm2100_vbat_attr_set(dev, SENSOR_CHAN_VOLTAGE, SENSOR_ATTR_LOWER_THRESH, + &config->voutmin); + if (ret < 0) { + return ret; + } + } + + if ((config->vbatmin.val1 != 0) || (config->vbatmin.val2 != 0)) { + ret = npm2100_vbat_attr_set(dev, SENSOR_CHAN_GAUGE_VOLTAGE, + SENSOR_ATTR_UPPER_THRESH, &config->vbatmin); + if (ret < 0) { + return ret; + } + + ret = npm2100_vbat_attr_set(dev, SENSOR_CHAN_GAUGE_VOLTAGE, + SENSOR_ATTR_LOWER_THRESH, &config->vbatmin); + if (ret < 0) { + return ret; + } + } + + /* Set MEE thresholds to SW control */ + ret = i2c_reg_write_byte_dt(&config->i2c, BOOST_VBATSEL, 3U); + if (ret < 0) { + return ret; + } + + /* Allow VOUTMIN comparator to select VBATMIN threshold */ + return i2c_reg_write_byte_dt(&config->i2c, BOOST_CTRLSET, 0x10U); +} + +static DEVICE_API(sensor, npm2100_vbat_battery_driver_api) = { + .sample_fetch = npm2100_vbat_sample_fetch, + .channel_get = npm2100_vbat_channel_get, + .attr_set = npm2100_vbat_attr_set, + .attr_get = npm2100_vbat_attr_get, +}; + +/* DPS-related measurements off by default. Enable via attribute feature masks. */ +#define NPM2100_VBAT_DATA_INIT() \ + { \ + .adc = \ + { \ + {.chan = SENSOR_CHAN_GAUGE_VOLTAGE, \ + .config = ADC_CONFIG_MODE_INS_VBAT, \ + .result_reg = ADC_READVBAT, \ + .enabled = true}, \ + {.chan = SENSOR_CHAN_VOLTAGE, \ + .config = ADC_CONFIG_MODE_VOUT, \ + .result_reg = ADC_READVOUT, \ + .enabled = true}, \ + {.chan = SENSOR_CHAN_DIE_TEMP, \ + .config = ADC_CONFIG_MODE_TEMP, \ + .result_reg = ADC_READTEMP, \ + .enabled = true}, \ + {.chan = (enum sensor_channel)SENSOR_CHAN_NPM2100_VOLT_DROOP, \ + .config = ADC_CONFIG_MODE_DROOP, \ + .result_reg = ADC_READDROOP, \ + .enabled = false}, \ + }, \ + .vbat_delay = 0, .dpsdur = 0, \ + } + +#define NPM2100_VBAT_INIT(n) \ + static struct npm2100_vbat_data npm2100_vbat_data_##n = NPM2100_VBAT_DATA_INIT(); \ + \ + static const struct npm2100_vbat_config npm2100_vbat_config_##n = { \ + .i2c = I2C_DT_SPEC_GET(DT_INST_PARENT(n)), \ + .voutmin = {.val1 = DT_INST_PROP_OR(n, vout_min_microvolt, 0) / 1000000, \ + .val2 = DT_INST_PROP_OR(n, vout_min_microvolt, 0) % 1000000}, \ + .vbatmin = {.val1 = DT_INST_PROP_OR(n, vbat_min_microvolt, 0) / 1000000, \ + .val2 = DT_INST_PROP_OR(n, vbat_min_microvolt, 0) % 1000000}, \ + }; \ + \ + SENSOR_DEVICE_DT_INST_DEFINE( \ + n, &npm2100_vbat_init, NULL, &npm2100_vbat_data_##n, &npm2100_vbat_config_##n, \ + POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &npm2100_vbat_battery_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(NPM2100_VBAT_INIT) diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt index 87dc99d76e7a7..e2ebb95cc76d9 100644 --- a/drivers/watchdog/CMakeLists.txt +++ b/drivers/watchdog/CMakeLists.txt @@ -26,6 +26,7 @@ zephyr_library_sources_ifdef(CONFIG_WDT_MCUX_WDOG32 wdt_mcux_wdog32.c) zephyr_library_sources_ifdef(CONFIG_WDT_MCUX_WWDT wdt_mcux_wwdt.c) zephyr_library_sources_ifdef(CONFIG_WDT_NPCX wdt_npcx.c) zephyr_library_sources_ifdef(CONFIG_WDT_NPM1300 wdt_npm1300.c) +zephyr_library_sources_ifdef(CONFIG_WDT_NPM2100 wdt_npm2100.c) zephyr_library_sources_ifdef(CONFIG_WDT_NPM6001 wdt_npm6001.c) zephyr_library_sources_ifdef(CONFIG_WDT_NRFX wdt_nrfx.c) zephyr_library_sources_ifdef(CONFIG_WDT_RPI_PICO wdt_rpi_pico.c) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 95eaf31216dd7..36515a1f7e464 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -96,6 +96,8 @@ source "drivers/watchdog/Kconfig.gd32" source "drivers/watchdog/Kconfig.npm1300" +source "drivers/watchdog/Kconfig.npm2100" + source "drivers/watchdog/Kconfig.npm6001" source "drivers/watchdog/Kconfig.nxp_s32" diff --git a/drivers/watchdog/Kconfig.npm2100 b/drivers/watchdog/Kconfig.npm2100 new file mode 100644 index 0000000000000..35f5323267d69 --- /dev/null +++ b/drivers/watchdog/Kconfig.npm2100 @@ -0,0 +1,19 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config WDT_NPM2100 + bool "nPM2100 Watchdog driver" + default y + depends on DT_HAS_NORDIC_NPM2100_WDT_ENABLED + select I2C + select MFD + help + Enable nPM2100 Watchdog driver + +config WDT_NPM2100_INIT_PRIORITY + int "nPM2100 Watchdog driver initialization priority" + depends on WDT_NPM2100 + default 85 + help + Initialization priority for the nPM2100 Watchdog driver. + It must be greater than GPIO_NPM2100_INIT_PRIORITY. diff --git a/drivers/watchdog/wdt_npm2100.c b/drivers/watchdog/wdt_npm2100.c new file mode 100644 index 0000000000000..04531edbbe2bb --- /dev/null +++ b/drivers/watchdog/wdt_npm2100.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nordic_npm2100_wdt + +#include + +#include +#include +#include +#include + +#define TIMER_TASKS_START 0xB0U +#define TIMER_TASKS_STOP 0xB1U +#define TIMER_TASKS_KICK 0xB2U +#define TIMER_CONFIG 0xB3U +#define TIMER_TARGET_HI 0xB4U +#define TIMER_TARGET_MID 0xB5U +#define TIMER_TARGET_LO 0xB6U +#define TIMER_STATUS 0xB7U +#define TIMER_BOOT_MON 0xB8U + +struct wdt_npm2100_config { + const struct device *mfd; + struct i2c_dt_spec i2c; +}; + +struct wdt_npm2100_data { + bool timeout_valid; +}; + +static int wdt_npm2100_setup(const struct device *dev, uint8_t options) +{ + const struct wdt_npm2100_config *config = dev->config; + struct wdt_npm2100_data *data = dev->data; + + if (!data->timeout_valid) { + return -EINVAL; + } + + return mfd_npm2100_start_timer(config->mfd); +} + +static int wdt_npm2100_disable(const struct device *dev) +{ + const struct wdt_npm2100_config *config = dev->config; + struct wdt_npm2100_data *data = dev->data; + int ret; + + ret = i2c_reg_write_byte_dt(&config->i2c, TIMER_TASKS_STOP, 1U); + if (ret < 0) { + return ret; + } + + data->timeout_valid = false; + + return 0; +} + +static int wdt_npm2100_install_timeout(const struct device *dev, + const struct wdt_timeout_cfg *timeout) +{ + const struct wdt_npm2100_config *config = dev->config; + struct wdt_npm2100_data *data = dev->data; + uint8_t mode; + int ret; + + if (data->timeout_valid) { + return -ENOMEM; + } + + if (timeout->window.min != 0U) { + return -EINVAL; + } + + switch (timeout->flags & WDT_FLAG_RESET_MASK) { + case WDT_FLAG_RESET_NONE: + /* Watchdog expiry causes event only, and does not reset */ + mode = NPM2100_TIMER_MODE_GENERAL_PURPOSE; + break; + case WDT_FLAG_RESET_CPU_CORE: + /* Watchdog expiry asserts reset output */ + mode = NPM2100_TIMER_MODE_WDT_RESET; + break; + case WDT_FLAG_RESET_SOC: + /* Watchdog expiry causes full power cycle */ + mode = NPM2100_TIMER_MODE_WDT_POWER_CYCLE; + break; + default: + return -EINVAL; + } + + ret = mfd_npm2100_set_timer(config->mfd, timeout->window.max, mode); + if (ret < 0) { + return ret; + } + + data->timeout_valid = true; + + return 0; +} + +static int wdt_npm2100_feed(const struct device *dev, int channel_id) +{ + const struct wdt_npm2100_config *config = dev->config; + + if (channel_id != 0) { + return -EINVAL; + } + + return i2c_reg_write_byte_dt(&config->i2c, TIMER_TASKS_KICK, 1U); +} + +static DEVICE_API(wdt, wdt_npm2100_api) = { + .setup = wdt_npm2100_setup, + .disable = wdt_npm2100_disable, + .install_timeout = wdt_npm2100_install_timeout, + .feed = wdt_npm2100_feed, +}; + +static int wdt_npm2100_init(const struct device *dev) +{ + const struct wdt_npm2100_config *config = dev->config; + + if (!i2c_is_ready_dt(&config->i2c)) { + return -ENODEV; + } + + /* Disable boot monitor */ + return wdt_npm2100_disable(dev); +} + +#define WDT_NPM2100_DEFINE(n) \ + static struct wdt_npm2100_data data##n; \ + \ + static const struct wdt_npm2100_config config##n = { \ + .mfd = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .i2c = I2C_DT_SPEC_GET(DT_INST_PARENT(n)), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, &wdt_npm2100_init, NULL, &data##n, &config##n, POST_KERNEL, \ + CONFIG_WDT_NPM2100_INIT_PRIORITY, &wdt_npm2100_api); + +DT_INST_FOREACH_STATUS_OKAY(WDT_NPM2100_DEFINE) diff --git a/dts/bindings/gpio/nordic,npm2100-gpio.yaml b/dts/bindings/gpio/nordic,npm2100-gpio.yaml new file mode 100644 index 0000000000000..bb9eb015d03bc --- /dev/null +++ b/dts/bindings/gpio/nordic,npm2100-gpio.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: nPM2100 GPIO Controller + +compatible: "nordic,npm2100-gpio" + +include: gpio-controller.yaml + +gpio-cells: + - pin + - flags diff --git a/dts/bindings/mfd/nordic,npm2100.yaml b/dts/bindings/mfd/nordic,npm2100.yaml new file mode 100644 index 0000000000000..e73399cf14bd1 --- /dev/null +++ b/dts/bindings/mfd/nordic,npm2100.yaml @@ -0,0 +1,72 @@ +# Copyright (c) 2024, Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: Nordic nPM2100 + +compatible: "nordic,npm2100" + +include: i2c-device.yaml + +properties: + reg: + required: true + + host-int-gpios: + type: phandle-array + description: Host pin for interrupt input + + host-int-type: + type: string + enum: + - "edge" + - "level" + default: "edge" + description: | + Using interrupt level triggering instead of edge triggering + can reduce power consumption on some platforms at the expense of irq latency. + + pmic-int-pin: + type: int + enum: + - 0 + - 1 + description: Pmic pin number for interrupt output + + pmic-int-flags: + type: int + description: | + GPIO flags for PMIC interrupt output + + shiphold-flags: + type: int + description: | + GPIO flags for shiphold button. + Defaults to active low with pull-up enabled. + + shiphold-hibernate-wakeup: + type: boolean + description: | + Enable shiphold button to trigger wakeup from hibernate state + + shiphold-longpress: + type: string + enum: + - "ship" + - "disable" + - "reset" + description: | + Configure behaviour of shiphold button. + ship: Enter ship mode if SHPHLD is pressed for 2 seconds + disable: SHPHLD button press has no effect + reset: Reset PMIC if SHPHLD is pressed for 10 seconds + + shiphold-current: + type: string + enum: + - "disable" + - "weak" + - "low" + - "moderate" + - "high" + description: | + Configure behaviour of shiphold pull-up current diff --git a/dts/bindings/regulator/nordic,npm2100-regulator.yaml b/dts/bindings/regulator/nordic,npm2100-regulator.yaml new file mode 100644 index 0000000000000..3c2ec28c36114 --- /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/dts/bindings/sensor/nordic,npm2100-vbat.yaml b/dts/bindings/sensor/nordic,npm2100-vbat.yaml new file mode 100644 index 0000000000000..a249c2de77d17 --- /dev/null +++ b/dts/bindings/sensor/nordic,npm2100-vbat.yaml @@ -0,0 +1,25 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: NPM2100 PMIC Battery Voltage + +compatible: "nordic,npm2100-vbat" + +include: [sensor-device.yaml] + +properties: + vout-min-microvolt: + type: int + description: | + Minimum Vout level. + When Vout falls to this level, the PMIC will allow more current to flow + from the battery by dropping to the vbatminl battery threshold. + + vbat-min-microvolt: + type: int + description: | + Initial value for vbatminl and vbatminh. + The boost converter will not allow the battery to drop below this level. diff --git a/dts/bindings/watchdog/nordic,npm2100-wdt.yaml b/dts/bindings/watchdog/nordic,npm2100-wdt.yaml new file mode 100644 index 0000000000000..160b53abea73c --- /dev/null +++ b/dts/bindings/watchdog/nordic,npm2100-wdt.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: nPM2100 Watchdog + +compatible: "nordic,npm2100-wdt" + +include: base.yaml diff --git a/include/zephyr/drivers/mfd/npm2100.h b/include/zephyr/drivers/mfd/npm2100.h new file mode 100644 index 0000000000000..8a45925af99c2 --- /dev/null +++ b/include/zephyr/drivers/mfd/npm2100.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_MFD_NPM2100_H_ +#define ZEPHYR_INCLUDE_DRIVERS_MFD_NPM2100_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup mdf_interface_npm2100 MFD NPM2100 Interface + * @ingroup mfd_interfaces + * @{ + */ + +#include +#include + +#include +#include + +enum mfd_npm2100_event { + NPM2100_EVENT_SYS_DIETEMP_WARN, + NPM2100_EVENT_SYS_SHIPHOLD_FALL, + NPM2100_EVENT_SYS_SHIPHOLD_RISE, + NPM2100_EVENT_SYS_PGRESET_FALL, + NPM2100_EVENT_SYS_PGRESET_RISE, + NPM2100_EVENT_SYS_TIMER_EXPIRY, + NPM2100_EVENT_ADC_VBAT_READY, + NPM2100_EVENT_ADC_DIETEMP_READY, + NPM2100_EVENT_ADC_DROOP_DETECT, + NPM2100_EVENT_ADC_VOUT_READY, + NPM2100_EVENT_GPIO0_FALL, + NPM2100_EVENT_GPIO0_RISE, + NPM2100_EVENT_GPIO1_FALL, + NPM2100_EVENT_GPIO1_RISE, + NPM2100_EVENT_BOOST_VBAT_WARN, + NPM2100_EVENT_BOOST_VOUT_MIN, + NPM2100_EVENT_BOOST_VOUT_WARN, + NPM2100_EVENT_BOOST_VOUT_DPS, + NPM2100_EVENT_BOOST_VOUT_OK, + NPM2100_EVENT_LDOSW_OCP, + NPM2100_EVENT_LDOSW_VINTFAIL, + NPM2100_EVENT_MAX +}; + +enum mfd_npm2100_timer_mode { + NPM2100_TIMER_MODE_GENERAL_PURPOSE, + NPM2100_TIMER_MODE_WDT_RESET, + NPM2100_TIMER_MODE_WDT_POWER_CYCLE, + NPM2100_TIMER_MODE_WAKEUP, +}; + +/** + * @brief Write npm2100 timer register + * + * The timer tick resolution is 1/64 seconds. + * This function does not start the timer (see mfd_npm2100_start_timer()). + * + * @param dev npm2100 mfd device + * @param time_ms timer value in ms + * @param mode timer mode + * @retval 0 If successful + * @retval -EINVAL if time value is too large + * @retval -errno In case of any bus error (see i2c_write_dt()) + */ +int mfd_npm2100_set_timer(const struct device *dev, uint32_t time_ms, + enum mfd_npm2100_timer_mode mode); + +/** + * @brief Start npm2100 timer + * + * @param dev npm2100 mfd device + * @retval 0 If successful + * @retval -errno In case of any bus error (see i2c_write_dt()) + */ +int mfd_npm2100_start_timer(const struct device *dev); + +/** + * @brief npm2100 full power reset + * + * @param dev npm2100 mfd device + * @retval 0 If successful + * @retval -errno In case of any bus error (see i2c_write_dt()) + */ +int mfd_npm2100_reset(const struct device *dev); + +/** + * @brief npm2100 hibernate + * + * Enters low power state, and wakes after specified time or "shphld" pin signal. + * + * @param dev npm2100 mfd device + * @param time_ms timer value in ms. Set to 0 to disable timer. + * @retval 0 If successful + * @retval -EINVAL if time value is too large + * @retval -EBUSY if the timer is already in use. + * @retval -errno In case of any bus error (see i2c_write_dt()) + */ +int mfd_npm2100_hibernate(const struct device *dev, uint32_t time_ms); + +/** + * @brief Add npm2100 event callback + * + * @param dev npm2100 mfd device + * @param callback callback + * @return 0 on success, -errno on failure + */ +int mfd_npm2100_add_callback(const struct device *dev, struct gpio_callback *callback); + +/** + * @brief Remove npm2100 event callback + * + * @param dev npm2100 mfd device + * @param callback callback + * @return 0 on success, -errno on failure + */ +int mfd_npm2100_remove_callback(const struct device *dev, struct gpio_callback *callback); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_MFD_NPM2100_H_ */ diff --git a/include/zephyr/drivers/sensor/npm2100_vbat.h b/include/zephyr/drivers/sensor/npm2100_vbat.h new file mode 100644 index 0000000000000..501657756f6ae --- /dev/null +++ b/include/zephyr/drivers/sensor/npm2100_vbat.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_NPM2100_VBAT_H_ +#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_NPM2100_VBAT_H_ + +#include + +/* NPM2100 vbat specific channels */ +enum sensor_channel_npm2100_vbat { + SENSOR_CHAN_NPM2100_VBAT_STATUS = SENSOR_CHAN_PRIV_START, + SENSOR_CHAN_NPM2100_VOLT_DROOP, + SENSOR_CHAN_NPM2100_DPS_COUNT, + SENSOR_CHAN_NPM2100_DPS_TIMER, + SENSOR_CHAN_NPM2100_DPS_DURATION, +}; + +/* NPM2100 vbat specific attributes */ +enum sensor_attr_npm2100_vbat { + SENSOR_ATTR_NPM2100_ADC_DELAY = SENSOR_ATTR_PRIV_START, +}; + +#endif diff --git a/include/zephyr/dt-bindings/gpio/nordic-npm2100-gpio.h b/include/zephyr/dt-bindings/gpio/nordic-npm2100-gpio.h new file mode 100644 index 0000000000000..8173c128d7dde --- /dev/null +++ b/include/zephyr/dt-bindings/gpio/nordic-npm2100-gpio.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_GPIO_NORDIC_NPM2100_GPIO_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_GPIO_NORDIC_NPM2100_GPIO_H_ + +/** + * @brief nPM2100-specific GPIO Flags + * @defgroup gpio_interface_npm2100 nPM2100-specific GPIO Flags + * + * The drive flags are encoded in the 8 upper bits of @ref gpio_dt_flags_t as + * follows: + * + * - Bit 8: Drive strength (0=1mA, 1=6mA) + * - Bit 9: Debounce (0=OFF, 1=ON) + * + * @ingroup gpio_interface + * @{ + */ + +/** + * @name nPM2100 GPIO drive strength flags + * @brief nPM2100 GPIO drive strength flags + * @{ + */ + +/** @cond INTERNAL_HIDDEN */ +/** Drive mode field mask */ +#define NPM2100_GPIO_DRIVE_MSK 0x0100U +/** @endcond */ + +/** Normal drive */ +#define NPM2100_GPIO_DRIVE_NORMAL (0U << 8U) +/** High drive */ +#define NPM2100_GPIO_DRIVE_HIGH (1U << 8U) + +/** @} */ + +/** + * @name nPM2100 GPIO debounce flags + * @brief nPM2100 GPIO debounce flags + * @{ + */ + +/** @cond INTERNAL_HIDDEN */ +/** Debounce field mask */ +#define NPM2100_GPIO_DEBOUNCE_MSK 0x0200U +/** @endcond */ + +/** Normal drive */ +#define NPM2100_GPIO_DEBOUNCE_OFF (0U << 9U) +/** High drive */ +#define NPM2100_GPIO_DEBOUNCE_ON (1U << 9U) + +/** @} */ + +/** @} */ + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_GPIO_NORDIC_NPM2100_GPIO_H_ */ diff --git a/include/zephyr/dt-bindings/regulator/npm2100.h b/include/zephyr/dt-bindings/regulator/npm2100.h new file mode 100644 index 0000000000000..2ec703bbbc314 --- /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/gpio/app.overlay b/tests/drivers/build_all/gpio/app.overlay index 9bdf45849e02c..ac6b71eb1e32c 100644 --- a/tests/drivers/build_all/gpio/app.overlay +++ b/tests/drivers/build_all/gpio/app.overlay @@ -338,9 +338,9 @@ }; }; - it8801_mfd: it8801@38 { + it8801_mfd: it8801@14 { compatible = "ite,it8801-mfd"; - reg = <0x38>; + reg = <0x14>; irq-gpios = <&test_gpio 1 0>; #address-cells = <1>; #size-cells = <1>; @@ -385,6 +385,18 @@ }; }; + test_i2c_npm2100: pmic@15 { + compatible = "nordic,npm2100"; + reg = <0x15>; + + npm2100_gpio: gpio-controller { + compatible = "nordic,npm2100-gpio"; + gpio-controller; + #gpio-cells = <2>; + ngpios = <2>; + }; + }; + }; nct3807_alert_1 { diff --git a/tests/drivers/build_all/regulator/i2c.dtsi b/tests/drivers/build_all/regulator/i2c.dtsi index 0d6e9c4c1a2ec..489476e69cabb 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 {}; + }; +}; diff --git a/tests/drivers/build_all/sensor/i2c.dtsi b/tests/drivers/build_all/sensor/i2c.dtsi index 3b34609bc4a69..0aae04c9e1c9f 100644 --- a/tests/drivers/build_all/sensor/i2c.dtsi +++ b/tests/drivers/build_all/sensor/i2c.dtsi @@ -1139,3 +1139,12 @@ test_i2c_scd4x: scd4x@9e { reg = <0x9e>; mode = <0>; }; + +test_i2c_npm2100: npm2100@9f { + compatible = "nordic,npm2100"; + reg = <0x9f>; + + vbat { + compatible = "nordic,npm2100-vbat"; + }; +}; diff --git a/tests/drivers/build_all/watchdog/i2c_devices.overlay b/tests/drivers/build_all/watchdog/i2c_devices.overlay index c02130a3ab62a..fcecf91a12285 100644 --- a/tests/drivers/build_all/watchdog/i2c_devices.overlay +++ b/tests/drivers/build_all/watchdog/i2c_devices.overlay @@ -32,6 +32,15 @@ compatible = "nordic,npm6001-wdt"; }; }; + + npm2100_pmic: pmic@1 { + compatible = "nordic,npm2100"; + reg = <0x1>; + + npm2100_wdt: watchdog { + compatible = "nordic,npm2100-wdt"; + }; + }; }; }; };