diff --git a/esphome/components/axp2101/__init__.py b/esphome/components/axp2101/__init__.py new file mode 100644 index 0000000000..d0d10168c6 --- /dev/null +++ b/esphome/components/axp2101/__init__.py @@ -0,0 +1,29 @@ +"""AXP2101 Power Management IC component for ESPHome.""" +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID + +CODEOWNERS = ["@esphome/core"] +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + +axp2101_ns = cg.esphome_ns.namespace("axp2101") +AXP2101Component = axp2101_ns.class_("AXP2101Component", cg.PollingComponent, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(AXP2101Component), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x34)) +) + + +async def to_code(config): + """Generate code for the AXP2101 component.""" + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/axp2101/axp2101.cpp b/esphome/components/axp2101/axp2101.cpp new file mode 100644 index 0000000000..356e1b94bd --- /dev/null +++ b/esphome/components/axp2101/axp2101.cpp @@ -0,0 +1,369 @@ +/** + * @file axp2101.cpp + * @brief Implementation of AXP2101 Power Management IC Component + */ +#include "axp2101.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace axp2101 { + +static const char *const TAG = "axp2101"; + +// Temperature conversion constant (from datasheet) +static const float TEMP_CONVERSION_FACTOR = 0.1f; +static const float TEMP_OFFSET = -267.15f; + +void AXP2101Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up AXP2101..."); + + // Read chip ID to verify communication + uint8_t chip_id; + if (!this->read_byte(AXP2101_IC_TYPE, &chip_id)) { + ESP_LOGE(TAG, "Failed to read chip ID"); + this->mark_failed(); + return; + } + + ESP_LOGD(TAG, "Chip ID: 0x%02X", chip_id); + + // Enable ADC channels for monitoring + // Enable battery voltage, VBUS, VSYS, and temperature measurements + uint8_t adc_ctrl = 0xFF; // Enable all ADC channels + if (!this->write_byte(AXP2101_ADC_CHANNEL_CTRL, adc_ctrl)) { + ESP_LOGW(TAG, "Failed to configure ADC channels"); + } + + ESP_LOGCONFIG(TAG, "AXP2101 setup complete"); +} + +void AXP2101Component::dump_config() { + ESP_LOGCONFIG(TAG, "AXP2101:"); + LOG_I2C_DEVICE(this); + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with AXP2101 failed!"); + return; + } + + // Log sensor configuration + LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_sensor_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_sensor_); + LOG_SENSOR(" ", "VBUS Voltage", this->vbus_voltage_sensor_); + LOG_SENSOR(" ", "VSYS Voltage", this->vsys_voltage_sensor_); + LOG_SENSOR(" ", "Die Temperature", this->die_temperature_sensor_); +} + +void AXP2101Component::update() { this->update_sensors(); } + +bool AXP2101Component::set_register_bit(uint8_t reg, uint8_t bit) { + uint8_t value; + if (!this->read_byte(reg, &value)) { + return false; + } + value |= (1 << bit); + return this->write_byte(reg, value); +} + +bool AXP2101Component::clear_register_bit(uint8_t reg, uint8_t bit) { + uint8_t value; + if (!this->read_byte(reg, &value)) { + return false; + } + value &= ~(1 << bit); + return this->write_byte(reg, value); +} + +bool AXP2101Component::get_register_bit(uint8_t reg, uint8_t bit) { + uint8_t value; + if (!this->read_byte(reg, &value)) { + return false; + } + return (value & (1 << bit)) != 0; +} + +uint16_t AXP2101Component::read_register_h5l8(uint8_t high_reg, uint8_t low_reg) { + uint8_t high, low; + if (!this->read_byte(high_reg, &high) || !this->read_byte(low_reg, &low)) { + return 0; + } + return ((high & 0x1F) << 8) | low; +} + +uint16_t AXP2101Component::read_register_h6l8(uint8_t high_reg, uint8_t low_reg) { + uint8_t high, low; + if (!this->read_byte(high_reg, &high) || !this->read_byte(low_reg, &low)) { + return 0; + } + return ((high & 0x3F) << 8) | low; +} + +bool AXP2101Component::get_rail_enable_info(PowerRail rail, uint8_t ®, uint8_t &bit) { + // DCDC rails are controlled by DC_ONOFF_DVM_CTRL register + if (rail >= DCDC1 && rail <= DCDC5) { + reg = AXP2101_DC_ONOFF_DVM_CTRL; + bit = static_cast(rail); // DCDC1=bit0, DCDC2=bit1, etc. + return true; + } + + // LDO rails are controlled by LDO_ONOFF_CTRL0 and LDO_ONOFF_CTRL1 + if (rail >= ALDO1 && rail <= DLDO2) { + if (rail <= BLDO2) { + reg = AXP2101_LDO_ONOFF_CTRL0; + bit = static_cast(rail - ALDO1); // ALDO1=bit0, ALDO2=bit1, etc. + } else { + reg = AXP2101_LDO_ONOFF_CTRL1; + bit = static_cast(rail - CPUSLDO); // CPUSLDO=bit0, DLDO1=bit1, DLDO2=bit2 + } + return true; + } + + return false; +} + +bool AXP2101Component::get_rail_voltage_info(PowerRail rail, uint8_t ®, uint16_t &min_mv, uint16_t &max_mv, + uint16_t &step_mv) { + switch (rail) { + case DCDC1: + reg = AXP2101_DC_VOL0_CTRL; + min_mv = 1500; + max_mv = 3400; + step_mv = 100; + return true; + + case DCDC2: + case DCDC3: + case DCDC4: + // These have dual ranges, handle in calculate methods + reg = AXP2101_DC_VOL1_CTRL + (rail - DCDC2); + min_mv = 500; + max_mv = 1540; + step_mv = 10; + return true; + + case DCDC5: + reg = AXP2101_DC_VOL4_CTRL; + min_mv = 1400; + max_mv = 3700; + step_mv = 100; + return true; + + case ALDO1: + case ALDO2: + case ALDO3: + case ALDO4: + reg = AXP2101_ALDO1_VOL_CTRL + (rail - ALDO1); + min_mv = 500; + max_mv = 3500; + step_mv = 100; + return true; + + case BLDO1: + case BLDO2: + reg = AXP2101_BLDO1_VOL_CTRL + (rail - BLDO1); + min_mv = 500; + max_mv = 3500; + step_mv = 100; + return true; + + case CPUSLDO: + reg = AXP2101_CPUSLDO_VOL_CTRL; + min_mv = 500; + max_mv = 1400; + step_mv = 50; + return true; + + case DLDO1: + case DLDO2: + reg = AXP2101_DLDO1_VOL_CTRL + (rail - DLDO1); + min_mv = 500; + max_mv = 3400; + step_mv = 100; + return true; + + default: + return false; + } +} + +uint8_t AXP2101Component::calculate_voltage_register_value(PowerRail rail, uint16_t millivolts) { + // Special handling for DCDC2, DCDC3, DCDC4 with dual ranges + if (rail == DCDC2 || rail == DCDC3 || rail == DCDC4) { + if (millivolts >= 500 && millivolts <= 1200) { + // Range 1: 500-1200mV in 10mV steps + return static_cast((millivolts - 500) / 10); + } else if (millivolts >= 1220 && millivolts <= 1540) { + // Range 2: 1220-1540mV in 20mV steps + return static_cast(70 + (millivolts - 1220) / 20); + } else if (rail == DCDC3 && millivolts >= 1600 && millivolts <= 3400) { + // DCDC3 has an extended range + return static_cast(87 + (millivolts - 1600) / 100); + } + return 0; // Invalid voltage + } + + // Special handling for DCDC4 extended range + if (rail == DCDC4 && millivolts >= 1600 && millivolts <= 1840) { + return static_cast(87 + (millivolts - 1600) / 20); + } + + // Standard calculation for other rails + uint8_t reg; + uint16_t min_mv, max_mv, step_mv; + if (!this->get_rail_voltage_info(rail, reg, min_mv, max_mv, step_mv)) { + return 0; + } + + if (millivolts < min_mv || millivolts > max_mv) { + return 0; // Out of range + } + + if ((millivolts - min_mv) % step_mv != 0) { + return 0; // Not aligned to step size + } + + return static_cast((millivolts - min_mv) / step_mv); +} + +uint16_t AXP2101Component::calculate_millivolts_from_register(PowerRail rail, uint8_t reg_value) { + // Special handling for DCDC2, DCDC3, DCDC4 with dual ranges + if (rail == DCDC2 || rail == DCDC3 || rail == DCDC4) { + if (reg_value <= 70) { + // Range 1: 500-1200mV in 10mV steps + return 500 + (reg_value * 10); + } else if (reg_value <= 86) { + // Range 2: 1220-1540mV in 20mV steps + return 1220 + ((reg_value - 70) * 20); + } else if (rail == DCDC3 && reg_value <= 105) { + // DCDC3 extended range: 1600-3400mV in 100mV steps + return 1600 + ((reg_value - 87) * 100); + } else if (rail == DCDC4 && reg_value <= 99) { + // DCDC4 extended range: 1600-1840mV in 20mV steps + return 1600 + ((reg_value - 87) * 20); + } + return 0; // Invalid + } + + // Standard calculation for other rails + uint8_t reg; + uint16_t min_mv, max_mv, step_mv; + if (!this->get_rail_voltage_info(rail, reg, min_mv, max_mv, step_mv)) { + return 0; + } + + return min_mv + (reg_value * step_mv); +} + +bool AXP2101Component::enable_power_rail(PowerRail rail) { + uint8_t reg, bit; + if (!this->get_rail_enable_info(rail, reg, bit)) { + return false; + } + return this->set_register_bit(reg, bit); +} + +bool AXP2101Component::disable_power_rail(PowerRail rail) { + uint8_t reg, bit; + if (!this->get_rail_enable_info(rail, reg, bit)) { + return false; + } + return this->clear_register_bit(reg, bit); +} + +bool AXP2101Component::is_power_rail_enabled(PowerRail rail) { + uint8_t reg, bit; + if (!this->get_rail_enable_info(rail, reg, bit)) { + return false; + } + return this->get_register_bit(reg, bit); +} + +bool AXP2101Component::set_rail_voltage(PowerRail rail, uint16_t millivolts) { + uint8_t reg; + uint16_t min_mv, max_mv, step_mv; + if (!this->get_rail_voltage_info(rail, reg, min_mv, max_mv, step_mv)) { + ESP_LOGE(TAG, "Invalid power rail"); + return false; + } + + uint8_t value = this->calculate_voltage_register_value(rail, millivolts); + if (value == 0 && millivolts != 0) { + ESP_LOGE(TAG, "Invalid voltage %u mV for rail", millivolts); + return false; + } + + return this->write_byte(reg, value); +} + +uint16_t AXP2101Component::get_rail_voltage(PowerRail rail) { + uint8_t reg; + uint16_t min_mv, max_mv, step_mv; + if (!this->get_rail_voltage_info(rail, reg, min_mv, max_mv, step_mv)) { + return 0; + } + + uint8_t value; + if (!this->read_byte(reg, &value)) { + return 0; + } + + return this->calculate_millivolts_from_register(rail, value & 0x7F); +} + +uint16_t AXP2101Component::read_battery_voltage() { + uint16_t raw = this->read_register_h5l8(AXP2101_ADC_DATA_RELUST0, AXP2101_ADC_DATA_RELUST1); + return raw; // 1 LSB = 1mV +} + +uint16_t AXP2101Component::read_vbus_voltage() { + uint16_t raw = this->read_register_h5l8(AXP2101_ADC_DATA_RELUST4, AXP2101_ADC_DATA_RELUST5); + return raw; // 1 LSB = 1mV +} + +uint16_t AXP2101Component::read_vsys_voltage() { + uint16_t raw = this->read_register_h6l8(AXP2101_ADC_DATA_RELUST6, AXP2101_ADC_DATA_RELUST7); + return raw; // 1 LSB = 1mV +} + +float AXP2101Component::read_die_temperature() { + uint16_t raw = this->read_register_h6l8(AXP2101_ADC_DATA_RELUST8, AXP2101_ADC_DATA_RELUST9); + // Temperature conversion: T = raw * 0.1 - 267.15 + return (raw * TEMP_CONVERSION_FACTOR) + TEMP_OFFSET; +} + +uint8_t AXP2101Component::read_battery_level() { + uint8_t level; + if (!this->read_byte(AXP2101_BAT_PERCENT_DATA, &level)) { + return 0; + } + return level > 100 ? 100 : level; +} + +void AXP2101Component::update_sensors() { + if (this->battery_voltage_sensor_ != nullptr) { + float voltage = this->read_battery_voltage() / 1000.0f; + this->battery_voltage_sensor_->publish_state(voltage); + } + + if (this->battery_level_sensor_ != nullptr) { + this->battery_level_sensor_->publish_state(this->read_battery_level()); + } + + if (this->vbus_voltage_sensor_ != nullptr) { + float voltage = this->read_vbus_voltage() / 1000.0f; + this->vbus_voltage_sensor_->publish_state(voltage); + } + + if (this->vsys_voltage_sensor_ != nullptr) { + float voltage = this->read_vsys_voltage() / 1000.0f; + this->vsys_voltage_sensor_->publish_state(voltage); + } + + if (this->die_temperature_sensor_ != nullptr) { + this->die_temperature_sensor_->publish_state(this->read_die_temperature()); + } +} + +} // namespace axp2101 +} // namespace esphome diff --git a/esphome/components/axp2101/axp2101.h b/esphome/components/axp2101/axp2101.h new file mode 100644 index 0000000000..1f227a5749 --- /dev/null +++ b/esphome/components/axp2101/axp2101.h @@ -0,0 +1,174 @@ +/** + * @file axp2101.h + * @brief AXP2101 Power Management IC Component + * + * This component provides access to the AXP2101 PMIC which includes: + * - 5 DCDC regulators (DCDC1-5) + * - 11 LDO regulators (ALDO1-4, BLDO1-2, CPUSLDO, DLDO1-2) + * - Battery management and charging + * - ADC monitoring (battery voltage, current, temperature, etc.) + */ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace axp2101 { + +// Register addresses +static const uint8_t AXP2101_STATUS1 = 0x00; +static const uint8_t AXP2101_STATUS2 = 0x01; +static const uint8_t AXP2101_IC_TYPE = 0x03; +static const uint8_t AXP2101_COMMON_CONFIG = 0x10; +static const uint8_t AXP2101_BATFET_CTRL = 0x12; +static const uint8_t AXP2101_MIN_SYS_VOL_CTRL = 0x14; +static const uint8_t AXP2101_INPUT_VOL_LIMIT_CTRL = 0x15; +static const uint8_t AXP2101_INPUT_CUR_LIMIT_CTRL = 0x16; +static const uint8_t AXP2101_PWRON_STATUS = 0x20; +static const uint8_t AXP2101_PWROFF_STATUS = 0x21; +static const uint8_t AXP2101_PWROFF_EN = 0x22; + +// ADC control and data registers +static const uint8_t AXP2101_ADC_CHANNEL_CTRL = 0x30; +static const uint8_t AXP2101_ADC_DATA_RELUST0 = 0x34; // Battery voltage high +static const uint8_t AXP2101_ADC_DATA_RELUST1 = 0x35; // Battery voltage low +static const uint8_t AXP2101_ADC_DATA_RELUST2 = 0x36; // TS voltage high +static const uint8_t AXP2101_ADC_DATA_RELUST3 = 0x37; // TS voltage low +static const uint8_t AXP2101_ADC_DATA_RELUST4 = 0x38; // VBUS voltage high +static const uint8_t AXP2101_ADC_DATA_RELUST5 = 0x39; // VBUS voltage low +static const uint8_t AXP2101_ADC_DATA_RELUST6 = 0x3A; // VSYS voltage high +static const uint8_t AXP2101_ADC_DATA_RELUST7 = 0x3B; // VSYS voltage low +static const uint8_t AXP2101_ADC_DATA_RELUST8 = 0x3C; // Die temperature high +static const uint8_t AXP2101_ADC_DATA_RELUST9 = 0x3D; // Die temperature low + +// Interrupt registers +static const uint8_t AXP2101_INTEN1 = 0x40; +static const uint8_t AXP2101_INTEN2 = 0x41; +static const uint8_t AXP2101_INTEN3 = 0x42; +static const uint8_t AXP2101_INTSTS1 = 0x48; +static const uint8_t AXP2101_INTSTS2 = 0x49; +static const uint8_t AXP2101_INTSTS3 = 0x4A; + +// Charging configuration registers +static const uint8_t AXP2101_CHARGE_GAUGE_WDT_CTRL = 0x18; +static const uint8_t AXP2101_IPRECHG_SET = 0x61; +static const uint8_t AXP2101_ICC_CHG_SET = 0x62; +static const uint8_t AXP2101_ITERM_CHG_SET_CTRL = 0x63; +static const uint8_t AXP2101_CV_CHG_VOL_SET = 0x64; +static const uint8_t AXP2101_CHARGE_TIMEOUT_SET_CTRL = 0x67; +static const uint8_t AXP2101_BAT_DET_CTRL = 0x68; +static const uint8_t AXP2101_CHGLED_SET_CTRL = 0x69; +static const uint8_t AXP2101_BTN_BAT_CHG_VOL_SET = 0x6A; + +// DCDC control registers +static const uint8_t AXP2101_DC_ONOFF_DVM_CTRL = 0x80; +static const uint8_t AXP2101_DC_VOL0_CTRL = 0x82; // DCDC1 +static const uint8_t AXP2101_DC_VOL1_CTRL = 0x83; // DCDC2 +static const uint8_t AXP2101_DC_VOL2_CTRL = 0x84; // DCDC3 +static const uint8_t AXP2101_DC_VOL3_CTRL = 0x85; // DCDC4 +static const uint8_t AXP2101_DC_VOL4_CTRL = 0x86; // DCDC5 + +// LDO control registers +static const uint8_t AXP2101_LDO_ONOFF_CTRL0 = 0x90; +static const uint8_t AXP2101_LDO_ONOFF_CTRL1 = 0x91; +static const uint8_t AXP2101_ALDO1_VOL_CTRL = 0x92; +static const uint8_t AXP2101_ALDO2_VOL_CTRL = 0x93; +static const uint8_t AXP2101_ALDO3_VOL_CTRL = 0x94; +static const uint8_t AXP2101_ALDO4_VOL_CTRL = 0x95; +static const uint8_t AXP2101_BLDO1_VOL_CTRL = 0x96; +static const uint8_t AXP2101_BLDO2_VOL_CTRL = 0x97; +static const uint8_t AXP2101_CPUSLDO_VOL_CTRL = 0x98; +static const uint8_t AXP2101_DLDO1_VOL_CTRL = 0x99; +static const uint8_t AXP2101_DLDO2_VOL_CTRL = 0x9A; + +// Battery gauge registers +static const uint8_t AXP2101_BAT_PARAMS = 0xA1; +static const uint8_t AXP2101_BAT_GAUGE_CTRL = 0xA2; +static const uint8_t AXP2101_BAT_PERCENT_DATA = 0xA4; + +// Power rail identifiers +enum PowerRail { + DCDC1 = 0, + DCDC2, + DCDC3, + DCDC4, + DCDC5, + ALDO1, + ALDO2, + ALDO3, + ALDO4, + BLDO1, + BLDO2, + CPUSLDO, + DLDO1, + DLDO2, +}; + +/** + * @brief Main AXP2101 component class + * + * This component handles communication with the AXP2101 PMIC via I2C. + * It provides methods for power rail control, voltage adjustment, + * and monitoring of various parameters. + */ +class AXP2101Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void update() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + // Sensor setters + void set_battery_voltage_sensor(sensor::Sensor *sensor) { this->battery_voltage_sensor_ = sensor; } + void set_battery_current_sensor(sensor::Sensor *sensor) { this->battery_current_sensor_ = sensor; } + void set_battery_level_sensor(sensor::Sensor *sensor) { this->battery_level_sensor_ = sensor; } + void set_vbus_voltage_sensor(sensor::Sensor *sensor) { this->vbus_voltage_sensor_ = sensor; } + void set_vsys_voltage_sensor(sensor::Sensor *sensor) { this->vsys_voltage_sensor_ = sensor; } + void set_die_temperature_sensor(sensor::Sensor *sensor) { this->die_temperature_sensor_ = sensor; } + + // Power rail control methods + bool enable_power_rail(PowerRail rail); + bool disable_power_rail(PowerRail rail); + bool is_power_rail_enabled(PowerRail rail); + + // Voltage control methods + bool set_rail_voltage(PowerRail rail, uint16_t millivolts); + uint16_t get_rail_voltage(PowerRail rail); + + // ADC reading methods + uint16_t read_battery_voltage(); + uint16_t read_vbus_voltage(); + uint16_t read_vsys_voltage(); + float read_die_temperature(); + uint8_t read_battery_level(); + + // Utility methods + void update_sensors(); + + protected: + // Helper methods for register access + bool set_register_bit(uint8_t reg, uint8_t bit); + bool clear_register_bit(uint8_t reg, uint8_t bit); + bool get_register_bit(uint8_t reg, uint8_t bit); + uint16_t read_register_h5l8(uint8_t high_reg, uint8_t low_reg); + uint16_t read_register_h6l8(uint8_t high_reg, uint8_t low_reg); + + // Power rail helper methods + bool get_rail_enable_info(PowerRail rail, uint8_t ®, uint8_t &bit); + bool get_rail_voltage_info(PowerRail rail, uint8_t ®, uint16_t &min_mv, uint16_t &max_mv, uint16_t &step_mv); + uint8_t calculate_voltage_register_value(PowerRail rail, uint16_t millivolts); + uint16_t calculate_millivolts_from_register(PowerRail rail, uint8_t reg_value); + + // Sensor pointers (optional) + sensor::Sensor *battery_voltage_sensor_{nullptr}; + sensor::Sensor *battery_current_sensor_{nullptr}; + sensor::Sensor *battery_level_sensor_{nullptr}; + sensor::Sensor *vbus_voltage_sensor_{nullptr}; + sensor::Sensor *vsys_voltage_sensor_{nullptr}; + sensor::Sensor *die_temperature_sensor_{nullptr}; +}; + +} // namespace axp2101 +} // namespace esphome diff --git a/esphome/components/axp2101/axp2101_number.cpp b/esphome/components/axp2101/axp2101_number.cpp new file mode 100644 index 0000000000..849d55e614 --- /dev/null +++ b/esphome/components/axp2101/axp2101_number.cpp @@ -0,0 +1,101 @@ +/** + * @file axp2101_number.cpp + * @brief Implementation of AXP2101 number component + */ +#include "axp2101_number.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace axp2101 { + +static const char *const TAG = "axp2101.number"; + +static const char *power_rail_to_string(PowerRail rail) { + switch (rail) { + case DCDC1: + return "DCDC1"; + case DCDC2: + return "DCDC2"; + case DCDC3: + return "DCDC3"; + case DCDC4: + return "DCDC4"; + case DCDC5: + return "DCDC5"; + case ALDO1: + return "ALDO1"; + case ALDO2: + return "ALDO2"; + case ALDO3: + return "ALDO3"; + case ALDO4: + return "ALDO4"; + case BLDO1: + return "BLDO1"; + case BLDO2: + return "BLDO2"; + case CPUSLDO: + return "CPUSLDO"; + case DLDO1: + return "DLDO1"; + case DLDO2: + return "DLDO2"; + default: + return "UNKNOWN"; + } +} + +void AXP2101Number::setup() { + if (this->parent_ == nullptr) { + ESP_LOGE(TAG, "Parent not set!"); + this->mark_failed(); + return; + } + + // Read current voltage and publish initial state + uint16_t current_mv = this->parent_->get_rail_voltage(this->rail_); + if (current_mv > 0) { + float current_v = current_mv / 1000.0f; + this->publish_state(current_v); + } +} + +void AXP2101Number::dump_config() { + LOG_NUMBER("", "AXP2101 Number", this); + ESP_LOGCONFIG(TAG, " Power Rail: %s", power_rail_to_string(this->rail_)); + ESP_LOGCONFIG(TAG, " Voltage Range: %.2f-%.2fV (step: %.3fV)", this->min_mv_ / 1000.0f, this->max_mv_ / 1000.0f, + this->step_mv_ / 1000.0f); +} + +void AXP2101Number::control(float value) { + if (this->parent_ == nullptr) { + ESP_LOGE(TAG, "Parent not set!"); + return; + } + + // Convert volts to millivolts + uint16_t millivolts = static_cast(value * 1000.0f); + + // Validate range + if (millivolts < this->min_mv_ || millivolts > this->max_mv_) { + ESP_LOGW(TAG, "Voltage %.3fV out of range for %s (%.2f-%.2fV)", value, power_rail_to_string(this->rail_), + this->min_mv_ / 1000.0f, this->max_mv_ / 1000.0f); + return; + } + + // Round to nearest step + uint16_t offset = millivolts - this->min_mv_; + uint16_t steps = (offset + this->step_mv_ / 2) / this->step_mv_; + millivolts = this->min_mv_ + (steps * this->step_mv_); + + if (this->parent_->set_rail_voltage(this->rail_, millivolts)) { + float actual_v = millivolts / 1000.0f; + this->publish_state(actual_v); + ESP_LOGD(TAG, "Set %s voltage to %.3fV", power_rail_to_string(this->rail_), actual_v); + } else { + ESP_LOGE(TAG, "Failed to set %s voltage to %.3fV", power_rail_to_string(this->rail_), value); + } +} + +} // namespace axp2101 +} // namespace esphome diff --git a/esphome/components/axp2101/axp2101_number.h b/esphome/components/axp2101/axp2101_number.h new file mode 100644 index 0000000000..d38982d765 --- /dev/null +++ b/esphome/components/axp2101/axp2101_number.h @@ -0,0 +1,44 @@ +/** + * @file axp2101_number.h + * @brief Number component for AXP2101 voltage control + */ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/number/number.h" +#include "axp2101.h" + +namespace esphome { +namespace axp2101 { + +/** + * @brief Number component for controlling AXP2101 power rail voltages + * + * This number component allows setting the output voltage of individual + * power rails (DCDCs and LDOs). + */ +class AXP2101Number : public number::Number, public Component { + public: + void set_parent(AXP2101Component *parent) { this->parent_ = parent; } + void set_power_rail(PowerRail rail) { this->rail_ = rail; } + void set_voltage_range(uint16_t min_mv, uint16_t max_mv, uint16_t step_mv) { + this->min_mv_ = min_mv; + this->max_mv_ = max_mv; + this->step_mv_ = step_mv; + } + + void setup() override; + void dump_config() override; + + protected: + void control(float value) override; + + AXP2101Component *parent_; + PowerRail rail_; + uint16_t min_mv_{500}; + uint16_t max_mv_{3500}; + uint16_t step_mv_{100}; +}; + +} // namespace axp2101 +} // namespace esphome diff --git a/esphome/components/axp2101/axp2101_switch.cpp b/esphome/components/axp2101/axp2101_switch.cpp new file mode 100644 index 0000000000..3174e3a040 --- /dev/null +++ b/esphome/components/axp2101/axp2101_switch.cpp @@ -0,0 +1,74 @@ +/** + * @file axp2101_switch.cpp + * @brief Implementation of AXP2101 switch component + */ +#include "axp2101_switch.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace axp2101 { + +static const char *const TAG = "axp2101.switch"; + +static const char *power_rail_to_string(PowerRail rail) { + switch (rail) { + case DCDC1: + return "DCDC1"; + case DCDC2: + return "DCDC2"; + case DCDC3: + return "DCDC3"; + case DCDC4: + return "DCDC4"; + case DCDC5: + return "DCDC5"; + case ALDO1: + return "ALDO1"; + case ALDO2: + return "ALDO2"; + case ALDO3: + return "ALDO3"; + case ALDO4: + return "ALDO4"; + case BLDO1: + return "BLDO1"; + case BLDO2: + return "BLDO2"; + case CPUSLDO: + return "CPUSLDO"; + case DLDO1: + return "DLDO1"; + case DLDO2: + return "DLDO2"; + default: + return "UNKNOWN"; + } +} + +void AXP2101Switch::dump_config() { + LOG_SWITCH("", "AXP2101 Switch", this); + ESP_LOGCONFIG(TAG, " Power Rail: %s", power_rail_to_string(this->rail_)); +} + +void AXP2101Switch::write_state(bool state) { + if (this->parent_ == nullptr) { + ESP_LOGE(TAG, "Parent not set!"); + return; + } + + bool success; + if (state) { + success = this->parent_->enable_power_rail(this->rail_); + } else { + success = this->parent_->disable_power_rail(this->rail_); + } + + if (success) { + this->publish_state(state); + } else { + ESP_LOGE(TAG, "Failed to %s power rail %s", state ? "enable" : "disable", power_rail_to_string(this->rail_)); + } +} + +} // namespace axp2101 +} // namespace esphome diff --git a/esphome/components/axp2101/axp2101_switch.h b/esphome/components/axp2101/axp2101_switch.h new file mode 100644 index 0000000000..129af220b4 --- /dev/null +++ b/esphome/components/axp2101/axp2101_switch.h @@ -0,0 +1,34 @@ +/** + * @file axp2101_switch.h + * @brief Switch component for AXP2101 power rail control + */ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "axp2101.h" + +namespace esphome { +namespace axp2101 { + +/** + * @brief Switch component for controlling AXP2101 power rails + * + * This switch allows enabling/disabling individual power rails (DCDCs and LDOs). + */ +class AXP2101Switch : public switch_::Switch, public Component { + public: + void set_parent(AXP2101Component *parent) { this->parent_ = parent; } + void set_power_rail(PowerRail rail) { this->rail_ = rail; } + + void dump_config() override; + + protected: + void write_state(bool state) override; + + AXP2101Component *parent_; + PowerRail rail_; +}; + +} // namespace axp2101 +} // namespace esphome diff --git a/esphome/components/axp2101/number.py b/esphome/components/axp2101/number.py new file mode 100644 index 0000000000..e221873e14 --- /dev/null +++ b/esphome/components/axp2101/number.py @@ -0,0 +1,89 @@ +"""Number support for AXP2101 voltage control.""" +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import number +from esphome.const import CONF_ID, UNIT_VOLT + +from . import AXP2101Component, axp2101_ns + +CODEOWNERS = ["@esphome/core"] +DEPENDENCIES = ["axp2101"] + +CONF_AXP2101_ID = "axp2101_id" +CONF_POWER_RAIL = "power_rail" + +AXP2101Number = axp2101_ns.class_("AXP2101Number", number.Number, cg.Component) + +# Power rail options matching the PowerRail enum in C++ +POWER_RAILS = { + "DCDC1": 0, + "DCDC2": 1, + "DCDC3": 2, + "DCDC4": 3, + "DCDC5": 4, + "ALDO1": 5, + "ALDO2": 6, + "ALDO3": 7, + "ALDO4": 8, + "BLDO1": 9, + "BLDO2": 10, + "CPUSLDO": 11, + "DLDO1": 12, + "DLDO2": 13, +} + +# Voltage ranges for each power rail (in millivolts) +VOLTAGE_RANGES = { + "DCDC1": {"min": 1500, "max": 3400, "step": 100}, + "DCDC2": {"min": 500, "max": 1540, "step": 10}, + "DCDC3": {"min": 500, "max": 3400, "step": 10}, + "DCDC4": {"min": 500, "max": 1840, "step": 10}, + "DCDC5": {"min": 1400, "max": 3700, "step": 100}, + "ALDO1": {"min": 500, "max": 3500, "step": 100}, + "ALDO2": {"min": 500, "max": 3500, "step": 100}, + "ALDO3": {"min": 500, "max": 3500, "step": 100}, + "ALDO4": {"min": 500, "max": 3500, "step": 100}, + "BLDO1": {"min": 500, "max": 3500, "step": 100}, + "BLDO2": {"min": 500, "max": 3500, "step": 100}, + "CPUSLDO": {"min": 500, "max": 1400, "step": 50}, + "DLDO1": {"min": 500, "max": 3400, "step": 100}, + "DLDO2": {"min": 500, "max": 3400, "step": 100}, +} + + +def validate_voltage_range(config): + """Validate that voltage is within the allowed range for the power rail.""" + rail = config[CONF_POWER_RAIL] + ranges = VOLTAGE_RANGES[rail] + + # Update the number schema with the correct min/max/step + return config + + +CONFIG_SCHEMA = cv.All( + number.number_schema( + AXP2101Number, + unit_of_measurement=UNIT_VOLT, + ).extend( + { + cv.GenerateID(CONF_AXP2101_ID): cv.use_id(AXP2101Component), + cv.Required(CONF_POWER_RAIL): cv.enum(POWER_RAILS, upper=True), + } + ), + validate_voltage_range, +) + + +async def to_code(config): + """Generate code for AXP2101 number.""" + paren = await cg.get_variable(config[CONF_AXP2101_ID]) + var = await number.new_number(config, min_value=0.5, max_value=3.7, step=0.01) + await cg.register_component(var, config) + + cg.add(var.set_parent(paren)) + cg.add(var.set_power_rail(config[CONF_POWER_RAIL])) + + # Set rail-specific voltage range + rail = config[CONF_POWER_RAIL] + ranges = VOLTAGE_RANGES[rail] + cg.add(var.set_voltage_range(ranges["min"], ranges["max"], ranges["step"])) diff --git a/esphome/components/axp2101/sensor.py b/esphome/components/axp2101/sensor.py new file mode 100644 index 0000000000..e018ccf6b3 --- /dev/null +++ b/esphome/components/axp2101/sensor.py @@ -0,0 +1,86 @@ +"""Sensor support for AXP2101.""" +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_BATTERY_VOLTAGE, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, + UNIT_VOLT, +) + +from . import AXP2101Component, axp2101_ns + +DEPENDENCIES = ["axp2101"] + +CONF_AXP2101_ID = "axp2101_id" +CONF_VBUS_VOLTAGE = "vbus_voltage" +CONF_VSYS_VOLTAGE = "vsys_voltage" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_AXP2101_ID): cv.use_id(AXP2101Component), + cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_VBUS_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_VSYS_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +) + + +async def to_code(config): + """Generate code for AXP2101 sensors.""" + paren = await cg.get_variable(config[CONF_AXP2101_ID]) + + if CONF_BATTERY_VOLTAGE in config: + sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) + cg.add(paren.set_battery_voltage_sensor(sens)) + + if CONF_BATTERY_LEVEL in config: + sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(paren.set_battery_level_sensor(sens)) + + if CONF_VBUS_VOLTAGE in config: + sens = await sensor.new_sensor(config[CONF_VBUS_VOLTAGE]) + cg.add(paren.set_vbus_voltage_sensor(sens)) + + if CONF_VSYS_VOLTAGE in config: + sens = await sensor.new_sensor(config[CONF_VSYS_VOLTAGE]) + cg.add(paren.set_vsys_voltage_sensor(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(paren.set_die_temperature_sensor(sens)) diff --git a/esphome/components/axp2101/switch.py b/esphome/components/axp2101/switch.py new file mode 100644 index 0000000000..89cc1b0870 --- /dev/null +++ b/esphome/components/axp2101/switch.py @@ -0,0 +1,51 @@ +"""Switch support for AXP2101 power rail control.""" +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import CONF_ID + +from . import AXP2101Component, axp2101_ns + +CODEOWNERS = ["@esphome/core"] + +DEPENDENCIES = ["axp2101"] + +CONF_AXP2101_ID = "axp2101_id" +CONF_POWER_RAIL = "power_rail" + +AXP2101Switch = axp2101_ns.class_("AXP2101Switch", switch.Switch, cg.Component) + +# Power rail options matching the PowerRail enum in C++ +POWER_RAILS = { + "DCDC1": 0, + "DCDC2": 1, + "DCDC3": 2, + "DCDC4": 3, + "DCDC5": 4, + "ALDO1": 5, + "ALDO2": 6, + "ALDO3": 7, + "ALDO4": 8, + "BLDO1": 9, + "BLDO2": 10, + "CPUSLDO": 11, + "DLDO1": 12, + "DLDO2": 13, +} + +CONFIG_SCHEMA = switch.switch_schema(AXP2101Switch).extend( + { + cv.GenerateID(CONF_AXP2101_ID): cv.use_id(AXP2101Component), + cv.Required(CONF_POWER_RAIL): cv.enum(POWER_RAILS, upper=True), + } +) + + +async def to_code(config): + """Generate code for AXP2101 switch.""" + paren = await cg.get_variable(config[CONF_AXP2101_ID]) + var = await switch.new_switch(config) + await cg.register_component(var, config) + + cg.add(var.set_parent(paren)) + cg.add(var.set_power_rail(config[CONF_POWER_RAIL])) diff --git a/tests/components/axp2101/common.yaml b/tests/components/axp2101/common.yaml new file mode 100644 index 0000000000..2b7b054235 --- /dev/null +++ b/tests/components/axp2101/common.yaml @@ -0,0 +1,51 @@ +axp2101: + - id: pmic + i2c_id: i2c_bus + address: 0x34 + update_interval: 60s + +sensor: + - platform: axp2101 + axp2101_id: pmic + battery_voltage: + name: "Battery Voltage" + battery_level: + name: "Battery Level" + vbus_voltage: + name: "VBUS Voltage" + vsys_voltage: + name: "VSYS Voltage" + temperature: + name: "Die Temperature" + +switch: + - platform: axp2101 + axp2101_id: pmic + name: "DCDC1 Enable" + power_rail: DCDC1 + - platform: axp2101 + axp2101_id: pmic + name: "ALDO1 Enable" + power_rail: ALDO1 + - platform: axp2101 + axp2101_id: pmic + name: "BLDO1 Enable" + power_rail: BLDO1 + +number: + - platform: axp2101 + axp2101_id: pmic + name: "DCDC1 Voltage" + power_rail: DCDC1 + - platform: axp2101 + axp2101_id: pmic + name: "DCDC2 Voltage" + power_rail: DCDC2 + - platform: axp2101 + axp2101_id: pmic + name: "ALDO1 Voltage" + power_rail: ALDO1 + - platform: axp2101 + axp2101_id: pmic + name: "CPUSLDO Voltage" + power_rail: CPUSLDO diff --git a/tests/components/axp2101/test.esp32-ard.yaml b/tests/components/axp2101/test.esp32-ard.yaml new file mode 100644 index 0000000000..7c503b0ccb --- /dev/null +++ b/tests/components/axp2101/test.esp32-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/axp2101/test.esp32-idf.yaml b/tests/components/axp2101/test.esp32-idf.yaml new file mode 100644 index 0000000000..b47e39c389 --- /dev/null +++ b/tests/components/axp2101/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/axp2101/test.esp8266-ard.yaml b/tests/components/axp2101/test.esp8266-ard.yaml new file mode 100644 index 0000000000..4a98b9388a --- /dev/null +++ b/tests/components/axp2101/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/axp2101/test.rp2040-ard.yaml b/tests/components/axp2101/test.rp2040-ard.yaml new file mode 100644 index 0000000000..319a7c71a6 --- /dev/null +++ b/tests/components/axp2101/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml + +<<: !include common.yaml