From fd531a0a2080e2d25610cc918fb4f79ee08a8da1 Mon Sep 17 00:00:00 2001 From: Audun Korneliussen Date: Fri, 20 Sep 2024 10:37:37 +0200 Subject: [PATCH 1/5] drivers: mfd: npm2100: Add npm2100 mfd driver Add mfd driver for the npm2100 pmic device. The driver contains basic initialization routines, and functionality not covered by other device driver APIs. Signed-off-by: Audun Korneliussen --- drivers/mfd/CMakeLists.txt | 1 + drivers/mfd/Kconfig | 1 + drivers/mfd/Kconfig.npm2100 | 10 + drivers/mfd/mfd_npm2100.c | 426 +++++++++++++++++++++++++++ dts/bindings/mfd/nordic,npm2100.yaml | 72 +++++ include/zephyr/drivers/mfd/npm2100.h | 129 ++++++++ 6 files changed, 639 insertions(+) create mode 100644 drivers/mfd/Kconfig.npm2100 create mode 100644 drivers/mfd/mfd_npm2100.c create mode 100644 dts/bindings/mfd/nordic,npm2100.yaml create mode 100644 include/zephyr/drivers/mfd/npm2100.h 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/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/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_ */ From cdbfa7c98831109747056e8cf9d15439481e968f Mon Sep 17 00:00:00 2001 From: Audun Korneliussen Date: Fri, 20 Sep 2024 10:39:24 +0200 Subject: [PATCH 2/5] drivers: gpio: npm2100: Add driver for npm2100 pmic Add gpio driver for npm2100 pmic. Signed-off-by: Audun Korneliussen --- drivers/gpio/CMakeLists.txt | 1 + drivers/gpio/Kconfig | 1 + drivers/gpio/Kconfig.npm2100 | 19 ++ drivers/gpio/gpio_npm2100.c | 182 ++++++++++++++++++ dts/bindings/gpio/nordic,npm2100-gpio.yaml | 12 ++ .../dt-bindings/gpio/nordic-npm2100-gpio.h | 61 ++++++ tests/drivers/build_all/gpio/app.overlay | 16 +- 7 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 drivers/gpio/Kconfig.npm2100 create mode 100644 drivers/gpio/gpio_npm2100.c create mode 100644 dts/bindings/gpio/nordic,npm2100-gpio.yaml create mode 100644 include/zephyr/dt-bindings/gpio/nordic-npm2100-gpio.h 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/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/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/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 { From 11d1c4d34e4f242759977da3723702043254da0c Mon Sep 17 00:00:00 2001 From: Audun Korneliussen Date: Fri, 20 Sep 2024 10:55:23 +0200 Subject: [PATCH 3/5] drivers: regulator: npm2100: Add driver for npm2100 pmic Add regulator driver for npm2100 pmic. This pmic has one boost and one ldo regulator. Signed-off-by: Audun Korneliussen --- drivers/regulator/CMakeLists.txt | 1 + drivers/regulator/Kconfig | 1 + drivers/regulator/Kconfig.npm2100 | 29 + drivers/regulator/regulator_npm2100.c | 791 ++++++++++++++++++ .../regulator/nordic,npm2100-regulator.yaml | 81 ++ .../zephyr/dt-bindings/regulator/npm2100.h | 50 ++ tests/drivers/build_all/regulator/i2c.dtsi | 12 + 7 files changed, 965 insertions(+) create mode 100644 drivers/regulator/Kconfig.npm2100 create mode 100644 drivers/regulator/regulator_npm2100.c create mode 100644 dts/bindings/regulator/nordic,npm2100-regulator.yaml create mode 100644 include/zephyr/dt-bindings/regulator/npm2100.h 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/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/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/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 {}; + }; +}; From 7e86e7395d947d4ba5f1556511f8e3510d2fc820 Mon Sep 17 00:00:00 2001 From: Audun Korneliussen Date: Tue, 1 Oct 2024 15:04:07 +0200 Subject: [PATCH 4/5] drivers: sensor: npm2100: Add driver for npm2100 pmic Add sensor driver for npm2100 pmic. This pmic performs measurements of battery voltage, regulator voltage and die temperature. Configurable pmic attributes are also organized under this driver. Signed-off-by: Audun Korneliussen --- drivers/sensor/nordic/CMakeLists.txt | 1 + drivers/sensor/nordic/Kconfig | 1 + .../sensor/nordic/npm2100_vbat/CMakeLists.txt | 6 + drivers/sensor/nordic/npm2100_vbat/Kconfig | 12 + .../sensor/nordic/npm2100_vbat/npm2100_vbat.c | 563 ++++++++++++++++++ dts/bindings/sensor/nordic,npm2100-vbat.yaml | 25 + include/zephyr/drivers/sensor/npm2100_vbat.h | 25 + tests/drivers/build_all/sensor/i2c.dtsi | 9 + 8 files changed, 642 insertions(+) create mode 100644 drivers/sensor/nordic/npm2100_vbat/CMakeLists.txt create mode 100644 drivers/sensor/nordic/npm2100_vbat/Kconfig create mode 100644 drivers/sensor/nordic/npm2100_vbat/npm2100_vbat.c create mode 100644 dts/bindings/sensor/nordic,npm2100-vbat.yaml create mode 100644 include/zephyr/drivers/sensor/npm2100_vbat.h 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/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/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/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"; + }; +}; From 7e0cb3e3fd7b3eeb334d1150929374570694a259 Mon Sep 17 00:00:00 2001 From: Audun Korneliussen Date: Tue, 1 Oct 2024 15:27:28 +0200 Subject: [PATCH 5/5] drivers: watchdog: npm2100: Add driver for npm2100 pmic Add watchdog driver for npm2100 pmic. This pmic has one timer that can be used for multiple functions, including a watchdog that can reset or power-cycle connected devices. Signed-off-by: Audun Korneliussen --- drivers/watchdog/CMakeLists.txt | 1 + drivers/watchdog/Kconfig | 2 + drivers/watchdog/Kconfig.npm2100 | 19 +++ drivers/watchdog/wdt_npm2100.c | 146 ++++++++++++++++++ dts/bindings/watchdog/nordic,npm2100-wdt.yaml | 8 + .../build_all/watchdog/i2c_devices.overlay | 9 ++ 6 files changed, 185 insertions(+) create mode 100644 drivers/watchdog/Kconfig.npm2100 create mode 100644 drivers/watchdog/wdt_npm2100.c create mode 100644 dts/bindings/watchdog/nordic,npm2100-wdt.yaml 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/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/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"; + }; + }; }; }; };