diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index 573ecb079..790f3ff40 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -3,4 +3,5 @@ zephyr_sources_ifdef(CONFIG_BQ25798 bq25798.c) zephyr_sources_ifdef(CONFIG_NRF9X_TEMP nrf9x_temp.c) zephyr_sources_ifdef(CONFIG_NRF9X_BATT nrf9x_batt.c) +zephyr_sources_ifdef(CONFIG_PAC194X pac194x.c) zephyr_sources_ifdef(CONFIG_SENSOR_GENERIC_SIM generic_sim.c) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index 37ea7f831..84136a4ea 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -36,6 +36,12 @@ config BQ25798_FETCH_POOR_SOURCE_RETRY endif +config PAC194X + bool "Microchip PAC194X single/multi-channel power monitor" + depends on DT_HAS_MICROCHIP_PAC1941_1_ENABLED + depends on SENSOR + default y + config SENSOR_GENERIC_SIM bool "Generic simulated sensor" depends on DT_HAS_ZEPHYR_GENERIC_SIM_SENSOR_ENABLED diff --git a/drivers/sensor/pac194x.c b/drivers/sensor/pac194x.c new file mode 100644 index 000000000..a10fd1e15 --- /dev/null +++ b/drivers/sensor/pac194x.c @@ -0,0 +1,284 @@ +/** + * @file + * @copyright 2025 Embeint Holdings Pty Ltd + * @author Jordan Yates + * + * SPDX-License-Identifier: FSL-1.1-ALv2 + */ + +#include +#include +#include +#include + +#include "pac194x.h" + +LOG_MODULE_REGISTER(PAC194X, CONFIG_SENSOR_LOG_LEVEL); + +static int pac194x_write_cmd(const struct device *dev, uint8_t reg, uint8_t delay_ms) +{ + const struct pac194x_config *config = dev->config; + uint8_t cmd = reg; + int rc; + + rc = i2c_write_dt(&config->bus, &cmd, 1); + if (rc < 0) { + LOG_DBG("Failed to write %02X register", reg); + } + /* All registers change dynamically for up to 1ms after the REFRESH commands */ + if (delay_ms > 0) { + k_sleep(K_MSEC(delay_ms)); + } + return rc; +} + +static int pac194x_write_u16(const struct device *dev, uint8_t reg, uint16_t reg_val) +{ + const struct pac194x_config *config = dev->config; + uint8_t regs[3] = { + reg, + reg_val >> 8, + reg_val & 0xFF, + }; + int rc; + + rc = i2c_write_dt(&config->bus, regs, 3); + if (rc < 0) { + LOG_DBG("Failed to write %02X register", reg); + } + return rc; +} + +static int pac194x_read_n(const struct device *dev, uint8_t reg, uint8_t *buf, uint8_t buf_len) +{ + const struct pac194x_config *config = dev->config; + int rc; + + rc = i2c_burst_read_dt(&config->bus, reg, buf, buf_len); + if (rc < 0) { + LOG_DBG("Failed to read %02X register", reg); + } + return rc; +} + +static int pac194x_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + struct pac194x_data *data = dev->data; + uint32_t acc_count; + uint8_t values[4]; + int rc; + + ARG_UNUSED(chan); + + /* REFRESH to read the latest sample */ + rc = pac194x_write_cmd(dev, PAC194X_REG_REFRESH, 5); + if (rc < 0) { + return rc; + } + + /* Read number of samples */ + rc = pac194x_read_n(dev, PAC194X_REG_ACC_COUNT, values, 4); + if (rc < 0) { + return rc; + } + /* Ignore for now until we move back to single-shot sampling */ + acc_count = sys_get_be32(values); + ARG_UNUSED(acc_count); + + rc = pac194x_read_n(dev, PAC194X_REG_VBUS_0, values, 2); + if (rc < 0) { + return rc; + } + data->v_bus = sys_get_be16(values); + + rc = pac194x_read_n(dev, PAC194X_REG_VSENSE_0, values, 2); + if (rc < 0) { + return rc; + } + data->v_sense = sys_get_be16(values); + + LOG_DBG(" VBUS raw: %d", data->v_bus); + LOG_DBG("VSENSE raw: %d", data->v_sense); + return 0; +} + +static int pac194x_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + const struct pac194x_config *config = dev->config; + struct pac194x_data *data = dev->data; + int64_t val_micro; + + switch (chan) { + case SENSOR_CHAN_VOLTAGE: + val_micro = 9000000 * (int64_t)data->v_bus >> config->vbus_shift; + break; + case SENSOR_CHAN_CURRENT: + val_micro = (config->full_scale_current_microamps * (int64_t)data->v_sense) >> + config->vsense_shift; + break; + default: + return -ENOTSUP; + } + + return sensor_value_from_micro(val, val_micro); +} + +static int pac194x_power_up(const struct device *dev) +{ + const struct pac194x_config *config = dev->config; + uint16_t ctrl = PAC194X_CTRL_SLOW_ALERT_SLOW | PAC194X_CTRL_GPIO_ALERT_INPUT | + PAC194X_CTRL_MODE_SLEEP; + uint8_t regs[3] = {0}; + int rc = 0; + + /* Time to first communications after power up is maximum 50ms */ + if (config->power_down_gpio.port) { + gpio_pin_configure_dt(&config->power_down_gpio, GPIO_OUTPUT_INACTIVE); + } + + /* Up to 50 ms before first communications */ + k_sleep(K_MSEC(50)); + + /* Write the control register */ + rc = pac194x_write_u16(dev, PAC194X_REG_CTRL, ctrl); + if (rc < 0) { + return rc; + } + + /* REFRESH to update internal registers */ + rc = pac194x_write_cmd(dev, PAC194X_REG_REFRESH, 1); + if (rc < 0) { + return rc; + } + + /* Read the ID bytes */ + rc = pac194x_read_n(dev, PAC194X_REG_PRODUCT_ID, regs, 3); + if (rc < 0) { + LOG_DBG("Failed to read ID registers"); + return -EIO; + } + LOG_DBG("Manu: 0x%02X Part: 0x%02X Rev: 0x%02X", regs[1], regs[0], regs[2]); + if (regs[0] != config->product_id) { + LOG_ERR("Unexpected product ID (%02X != %02X)", regs[0], config->product_id); + return -EINVAL; + } + return 0; +} + +static void pac194x_suspend(const struct device *dev) +{ + const struct pac194x_config *config = dev->config; + + /* Without a power down pin, we just sit in the sleep state from the single shot sampling */ + if (config->power_down_gpio.port) { + gpio_pin_set_dt(&config->power_down_gpio, 1); + } +} + +static int pac194x_resume(const struct device *dev) +{ + const struct pac194x_config *config = dev->config; + uint16_t ctrl = PAC194X_CTRL_SLOW_ALERT_SLOW | PAC194X_CTRL_GPIO_ALERT_INPUT | + PAC194X_CTRL_MODE_1024_SPS; + int rc = 0; + + if (config->power_down_gpio.port) { + gpio_pin_configure_dt(&config->power_down_gpio, GPIO_OUTPUT_INACTIVE); + /* Up to 50 ms before first communications */ + k_sleep(K_MSEC(50)); + } + + /* Configure the full scale ranges */ + rc = pac194x_write_u16(dev, PAC194X_REG_NEG_PWR_FSR, config->fsr_config); + if (rc < 0) { + return rc; + } + + /* Prepare the control register for the first REFRESH command */ + rc = pac194x_write_u16(dev, PAC194X_REG_CTRL, ctrl); + if (rc < 0) { + return rc; + } + return pac194x_write_cmd(dev, PAC194X_REG_REFRESH, 2); +} + +static int pac194x_pm_control(const struct device *dev, enum pm_device_action action) +{ + const struct pac194x_config *config = dev->config; + int rc = 0; + + switch (action) { + case PM_DEVICE_ACTION_TURN_ON: + /* Ensure device is ready to talk to us */ + rc = pac194x_power_up(dev); + if (rc < 0) { + LOG_DBG("Failed to power up"); + return -EIO; + } + /* Return to low power mode */ + pac194x_suspend(dev); + break; + case PM_DEVICE_ACTION_TURN_OFF: + if (config->power_down_gpio.port) { + gpio_pin_configure_dt(&config->power_down_gpio, GPIO_DISCONNECTED); + } + break; + case PM_DEVICE_ACTION_SUSPEND: + /* Return to low power mode */ + pac194x_suspend(dev); + break; + case PM_DEVICE_ACTION_RESUME: + rc = pac194x_resume(dev); + break; + } + return rc; +} + +static int pac194x_init(const struct device *dev) +{ + const struct pac194x_config *const config = dev->config; + + if (!device_is_ready(config->bus.bus)) { + return -ENODEV; + } + + if (config->power_down_gpio.port) { + if (!gpio_is_ready_dt(&config->power_down_gpio)) { + return -ENODEV; + } + } + + return pm_device_driver_init(dev, pac194x_pm_control); +} + +static DEVICE_API(sensor, pac194x_driver_api) = { + .sample_fetch = pac194x_sample_fetch, + .channel_get = pac194x_channel_get, +}; + +/* FSC = 0.1V / R_sense */ +#define INST_FULL_SCALE_CURRENT(inst) \ + ((1000000 * 100) / DT_INST_PROP(inst, sense_resistor_milli_ohms)) + +#define PAC194X_DRIVER_INIT(inst, type) \ + static const struct pac194x_config drv_config_##type##inst = { \ + .bus = I2C_DT_SPEC_INST_GET(inst), \ + .power_down_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, power_down_gpios, {0}), \ + .full_scale_current_microamps = INST_FULL_SCALE_CURRENT(inst), \ + .fsr_config = (DT_INST_ENUM_IDX(inst, fsr_vbus_channel_1) << 6) | \ + (DT_INST_ENUM_IDX(inst, fsr_vsense_channel_1) << 14), \ + .vbus_shift = DT_INST_ENUM_IDX(inst, fsr_vbus_channel_1) == 1 ? 15 : 16, \ + .vsense_shift = DT_INST_ENUM_IDX(inst, fsr_vsense_channel_1) == 1 ? 15 : 16, \ + .product_id = PAC194X_PRODUCT_ID_##type, \ + }; \ + static struct pac194x_data drv_data_##type##inst; \ + PM_DEVICE_DT_INST_DEFINE(inst, pac194x_pm_control); \ + SENSOR_DEVICE_DT_INST_DEFINE(inst, &pac194x_init, PM_DEVICE_DT_INST_GET(inst), \ + &drv_data_##type##inst, &drv_config_##type##inst, \ + POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \ + &pac194x_driver_api); + +#define DT_DRV_COMPAT microchip_pac1941_1 +DT_INST_FOREACH_STATUS_OKAY_VARGS(PAC194X_DRIVER_INIT, PAC1941_1) +#undef DT_DRV_COMPAT diff --git a/drivers/sensor/pac194x.h b/drivers/sensor/pac194x.h new file mode 100644 index 000000000..49c442096 --- /dev/null +++ b/drivers/sensor/pac194x.h @@ -0,0 +1,113 @@ +/** + * @file + * @copyright 2025 Embeint Holdings Pty Ltd + * @author Jordan Yates + * + * SPDX-License-Identifier: FSL-1.1-ALv2 + */ + +#ifndef INFUSE_SDK_DRIVERS_SENSOR_PAC194X_H_ +#define INFUSE_SDK_DRIVERS_SENSOR_PAC194X_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum pac194x_reg { + PAC194X_REG_REFRESH = 0x00, + PAC194X_REG_CTRL = 0x01, + PAC194X_REG_ACC_COUNT = 0x02, + PAC194X_REG_VACC_0 = 0x03, + PAC194X_REG_VACC_1 = 0x04, + PAC194X_REG_VACC_2 = 0x05, + PAC194X_REG_VACC_3 = 0x06, + PAC194X_REG_VBUS_0 = 0x07, + PAC194X_REG_VBUS_1 = 0x08, + PAC194X_REG_VBUS_2 = 0x09, + PAC194X_REG_VBUS_3 = 0x0A, + PAC194X_REG_VSENSE_0 = 0x0B, + PAC194X_REG_VSENSE_1 = 0x0C, + PAC194X_REG_VSENSE_2 = 0x0D, + PAC194X_REG_VSENSE_3 = 0x0E, + PAC194X_REG_VBUS_0_AVG = 0x0F, + PAC194X_REG_VBUS_1_AVG = 0x10, + PAC194X_REG_VBUS_2_AVG = 0x11, + PAC194X_REG_VBUS_3_AVG = 0x12, + PAC194X_REG_VSENSE_0_AVG = 0x13, + PAC194X_REG_VSENSE_1_AVG = 0x14, + PAC194X_REG_VSENSE_2_AVG = 0x15, + PAC194X_REG_VSENSE_3_AVG = 0x16, + PAC194X_REG_VPOWER_0 = 0x17, + PAC194X_REG_VPOWER_1 = 0x18, + PAC194X_REG_VPOWER_2 = 0x19, + PAC194X_REG_VPOWER_3 = 0x1A, + PAC194X_REG_SMBUS_SETTINGS = 0x1C, + PAC194X_REG_NEG_PWR_FSR = 0x1D, + PAC194X_REG_REFRESH_G = 0x1E, + PAC194X_REG_REFRESH_V = 0x1F, + + PAC194X_REG_PRODUCT_ID = 0xFD, + PAC194X_REG_MANUFACTURER_ID = 0xFE, + PAC194X_REG_REVISION_ID = 0xFF, +}; + +enum pac194x_ctrl { + PAC194X_CTRL_SLOW_ALERT_ALERT = (0b00 << 8), + PAC194X_CTRL_SLOW_ALERT_INPUT = (0b01 << 8), + PAC194X_CTRL_SLOW_ALERT_OUTPUT = (0b10 << 8), + PAC194X_CTRL_SLOW_ALERT_SLOW = (0b11 << 8), + PAC194X_CTRL_GPIO_ALERT_ALERT = (0b00 << 10), + PAC194X_CTRL_GPIO_ALERT_INPUT = (0b01 << 10), + PAC194X_CTRL_GPIO_ALERT_OUTPUT = (0b10 << 10), + PAC194X_CTRL_GPIO_ALERT_SLOW = (0b11 << 10), + PAC194X_CTRL_MODE_1024_SPS_ACCUM = (0b0000 << 12), + PAC194X_CTRL_MODE_256_SPS_ACCUM = (0b0001 << 12), + PAC194X_CTRL_MODE_64_SPS_ACCUM = (0b0010 << 12), + PAC194X_CTRL_MODE_8_SPS_ACCUM = (0b0011 << 12), + PAC194X_CTRL_MODE_1024_SPS = (0b0100 << 12), + PAC194X_CTRL_MODE_256_SPS = (0b0101 << 12), + PAC194X_CTRL_MODE_64_SPS = (0b0110 << 12), + PAC194X_CTRL_MODE_8_SPS = (0b0111 << 12), + PAC194X_CTRL_MODE_SINGLE_SHOT = (0b1000 << 12), + PAC194X_CTRL_MODE_SINGLE_SHOT_8X = (0b1001 << 12), + PAC194X_CTRL_MODE_FAST_MODE = (0b1010 << 12), + PAC194X_CTRL_MODE_BURST_MODE = (0b1011 << 12), + PAC194X_CTRL_MODE_SLEEP = (0b1111 << 12), +}; + +enum pac194x_product_id { + PAC194X_PRODUCT_ID_PAC1941_1 = 0b01101000, + PAC194X_PRODUCT_ID_PAC1942_1 = 0b01101001, + PAC194X_PRODUCT_ID_PAC1943_1 = 0b01101010, + PAC194X_PRODUCT_ID_PAC1944_1 = 0b01101011, + PAC194X_PRODUCT_ID_PAC1941_2 = 0b01101100, + PAC194X_PRODUCT_ID_PAC1942_2 = 0b01101101, +}; + +enum pac194x_manufacturer_id { + PAC194X_MANUFACTURER_ID_MICROCHIP = 0x54, +}; + +struct pac194x_config { + struct i2c_dt_spec bus; + struct gpio_dt_spec power_down_gpio; + int32_t full_scale_current_microamps; + uint16_t fsr_config; + uint8_t vbus_shift; + uint8_t vsense_shift; + enum pac194x_product_id product_id; +}; + +struct pac194x_data { + uint16_t v_bus; + int16_t v_sense; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* INFUSE_SDK_DRIVERS_SENSOR_PAC194X_H_ */ diff --git a/dts/bindings/sensor/microchip,pac1941-1.yaml b/dts/bindings/sensor/microchip,pac1941-1.yaml new file mode 100644 index 000000000..e04c24304 --- /dev/null +++ b/dts/bindings/sensor/microchip,pac1941-1.yaml @@ -0,0 +1,41 @@ +# Copyright (c) 2025 Embeint Holdings Pty Ltd +# SPDX-License-Identifier: FSL-1.1-ALv2 + +description: | + Single Channel High-Side Power Monitor with Accumulator, 9V Full-Scale Range + +compatible: "microchip,pac1941-1" + +include: [i2c-device.yaml] + +properties: + power-down-gpios: + type: phandle-array + description: GPIO used to put the device in a power down state + + sense-resistor-milli-ohms: + type: int + required: true + description: Value of the sense resistor in milli-ohms + + fsr-vbus-channel-1: + type: string + enum: + - "0V to 9V" + - "-9V to 9V" + - "-4.5V to 4.5V" + default: "0V to 9V" + description: + Full scale range of the VBUS measurements on channel 1. Default + is the same as the hardware reset value. + + fsr-vsense-channel-1: + type: string + enum: + - "0mV to 100mV" + - "-100mV to 100mV" + - "-50mV to 50mV" + default: "0mV to 100mV" + description: + Full scale range of the VBUS measurements on channel 1. Default + is the same as the hardware reset value. diff --git a/samples/task_runner/gnss/src/main.c b/samples/task_runner/gnss/src/main.c index 48cc40c95..a64a46dc8 100644 --- a/samples/task_runner/gnss/src/main.c +++ b/samples/task_runner/gnss/src/main.c @@ -52,6 +52,21 @@ static const struct task_schedule schedules[] = { .position_dop = 40, }, }, +#if DT_NODE_EXISTS(DT_ALIAS(fuel_gauge0)) + { + .task_id = TASK_ID_BATTERY, + .validity = TASK_VALID_ALWAYS, + .periodicity_type = TASK_PERIODICITY_FIXED, + .periodicity.fixed.period_s = 2, + .task_logging = + { + { + .loggers = TDF_DATA_LOGGER_SERIAL, + .tdf_mask = TASK_BATTERY_LOG_COMPLETE, + }, + }, + }, +#endif /* DT_NODE_EXISTS(DT_ALIAS(fuel_gauge0)) */ #ifdef CONFIG_BT { .task_id = TASK_ID_TDF_LOGGER, @@ -61,14 +76,21 @@ static const struct task_schedule schedules[] = { .loggers = TDF_DATA_LOGGER_BT_ADV, .logging_period_ms = 900, .random_delay_ms = 200, - .tdfs = TASK_TDF_LOGGER_LOG_ANNOUNCE | TASK_TDF_LOGGER_LOG_LOCATION, + .tdfs = TASK_TDF_LOGGER_LOG_ANNOUNCE | + TASK_TDF_LOGGER_LOG_LOCATION | TASK_TDF_LOGGER_LOG_BATTERY, }, }, #endif /* CONFIG_BT */ }; +#if DT_NODE_EXISTS(DT_ALIAS(fuel_gauge0)) +#define BAT_TASK_DEFINE (BATTERY_TASK, DEVICE_DT_GET(DT_ALIAS(fuel_gauge0))) +#else +#define BAT_TASK_DEFINE +#endif /* DT_NODE_EXISTS(DT_ALIAS(fuel_gauge0)) */ + TASK_SCHEDULE_STATES_DEFINE(states, schedules); -TASK_RUNNER_TASKS_DEFINE(app_tasks, app_tasks_data, (TDF_LOGGER_TASK, NULL), +TASK_RUNNER_TASKS_DEFINE(app_tasks, app_tasks_data, (TDF_LOGGER_TASK, NULL), BAT_TASK_DEFINE, (GNSS_TASK, DEVICE_DT_GET(DT_ALIAS(gnss)))); int main(void) diff --git a/samples/validation/src/main.c b/samples/validation/src/main.c index 548e261ec..4dc4afc77 100644 --- a/samples/validation/src/main.c +++ b/samples/validation/src/main.c @@ -370,6 +370,25 @@ SYS_INIT(validation_init, APPLICATION, 99); int main(void) { VALIDATION_REPORT_INFO("SYS", "Starting"); + +#if DT_NODE_EXISTS(DT_ALIAS(charger0)) + /* No API checking, just validate the device initialised correctly */ + const struct device *charger = DEVICE_DT_GET(DT_ALIAS(charger0)); + + atomic_inc(&validators_registered); + VALIDATION_REPORT_INFO("CHARGER", "DEV=%s", charger->name); + + if (device_is_ready(charger)) { + VALIDATION_REPORT_INFO("CHARGER", "Device ready"); + VALIDATION_REPORT_PASS("CHARGER", "DEV=%s", charger->name); + atomic_inc(&validators_passed); + } else { + VALIDATION_REPORT_ERROR("CHARGER", "Device not ready"); + atomic_inc(&validators_failed); + } + atomic_inc(&validators_complete); +#endif /* DT_NODE_EXISTS(DT_ALIAS(charger0)) */ + for (;;) { k_sem_take(&task_complete, K_FOREVER); if (validators_registered == validators_complete) { diff --git a/tests/drivers/build_all/i2c.dtsi b/tests/drivers/build_all/i2c.dtsi index a085de6d1..72c001ca6 100644 --- a/tests/drivers/build_all/i2c.dtsi +++ b/tests/drivers/build_all/i2c.dtsi @@ -77,3 +77,12 @@ test_i2c_lp5817: lp5817@8 { reg = <0x8>; out0-current-max = <0xFF>; }; + +pax194x: pax1941@10 { + compatible = "microchip,pac1941-1"; + reg = <0x10>; + power-down-gpios = <&test_gpio 0 GPIO_ACTIVE_LOW>; + sense-resistor-milli-ohms = <100>; + fsr-vbus-channel-1 = "0V to 9V"; + fsr-vsense-channel-1 = "-100mV to 100mV"; +}; diff --git a/west.yml b/west.yml index 6d08981f1..3fe28a463 100644 --- a/west.yml +++ b/west.yml @@ -12,7 +12,7 @@ manifest: projects: - name: zephyr - revision: 06e8098d018a6bde1d6e6aa33f0b735407f8dd87 + revision: ceebd3c5b7fd52ee4305fd928054eacb19365884 # Limit imported repositories to reduce clone time import: name-allowlist: