diff --git a/drivers/mfd/CMakeLists.txt b/drivers/mfd/CMakeLists.txt index d7071a6af95..0d27ef2c034 100644 --- a/drivers/mfd/CMakeLists.txt +++ b/drivers/mfd/CMakeLists.txt @@ -5,3 +5,4 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_MFD_NPM1300 mfd_npm1300.c) zephyr_library_sources_ifdef(CONFIG_MFD_NPM6001 mfd_npm6001.c) +zephyr_library_sources_ifdef(CONFIG_MFD_AXP192 mfd_axp192.c) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 64a166c7cc7..114b78ad587 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -18,6 +18,7 @@ config MFD_INIT_PRIORITY help Multi-function devices initialization priority. +source "drivers/mfd/Kconfig.axp192" source "drivers/mfd/Kconfig.npm1300" source "drivers/mfd/Kconfig.npm6001" diff --git a/drivers/mfd/Kconfig.axp192 b/drivers/mfd/Kconfig.axp192 new file mode 100644 index 00000000000..fed776d3656 --- /dev/null +++ b/drivers/mfd/Kconfig.axp192 @@ -0,0 +1,10 @@ +# Copyright (c) 2023 Martin Kiepfer +# SPDX -License-Identifier: Apache-2.0 + +config MFD_AXP192 + bool "AXP192 PMIC multi-function device driver" + default y + depends on DT_HAS_X_POWERS_AXP192_ENABLED + select I2C + help + Enable the X-Powers AXP192 PMIC multi-function device driver diff --git a/drivers/mfd/mfd_axp192.c b/drivers/mfd/mfd_axp192.c new file mode 100644 index 00000000000..ee144721db4 --- /dev/null +++ b/drivers/mfd/mfd_axp192.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 Martin Kiepfer + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT x_powers_axp192 + +#include + +#include +#include +#include + +LOG_MODULE_REGISTER(mfd_axp192, CONFIG_MFD_LOG_LEVEL); + +/* Chip ID value */ +#define AXP192_CHIP_ID 0x03U + +/* Registers definitions */ +#define AXP192_REG_CHIP_ID 0x03U + +struct mfd_axp192_config { + struct i2c_dt_spec i2c; +}; + +static int mfd_axp192_init(const struct device *dev) +{ + const struct mfd_axp192_config *config = dev->config; + uint8_t chip_id; + int ret; + + LOG_DBG("Initializing instance"); + + if (!i2c_is_ready_dt(&config->i2c)) { + LOG_ERR("I2C bus not ready"); + return -ENODEV; + } + + /* Check if axp192 chip is available */ + ret = i2c_reg_read_byte_dt(&config->i2c, AXP192_REG_CHIP_ID, &chip_id); + if (ret < 0) { + return ret; + } + + if (chip_id != AXP192_CHIP_ID) { + LOG_ERR("Invalid Chip detected (%d)", chip_id); + return -EINVAL; + } + + return 0; +} + +#define MFD_AXP192_DEFINE(inst) \ + static const struct mfd_axp192_config config##inst = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, mfd_axp192_init, NULL, NULL, &config##inst, POST_KERNEL, \ + CONFIG_MFD_INIT_PRIORITY, NULL); + +DT_INST_FOREACH_STATUS_OKAY(MFD_AXP192_DEFINE) diff --git a/drivers/regulator/CMakeLists.txt b/drivers/regulator/CMakeLists.txt index 1f5f9f8b345..c5c616f0d2a 100644 --- a/drivers/regulator/CMakeLists.txt +++ b/drivers/regulator/CMakeLists.txt @@ -4,6 +4,7 @@ zephyr_library() zephyr_library_sources(regulator_common.c) +zephyr_library_sources_ifdef(CONFIG_REGULATOR_AXP192 regulator_axp192.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_ADP5360 regulator_adp5360.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_FAKE regulator_fake.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_FIXED regulator_fixed.c) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index c51d55b4de4..dc745407ec6 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -27,6 +27,7 @@ module = REGULATOR module-str = regulator source "subsys/logging/Kconfig.template.log_config" +source "drivers/regulator/Kconfig.axp192" source "drivers/regulator/Kconfig.adp5360" source "drivers/regulator/Kconfig.fake" source "drivers/regulator/Kconfig.fixed" diff --git a/drivers/regulator/Kconfig.axp192 b/drivers/regulator/Kconfig.axp192 new file mode 100644 index 00000000000..c2ec403359e --- /dev/null +++ b/drivers/regulator/Kconfig.axp192 @@ -0,0 +1,23 @@ +# Copyright (c) 2023 Martin Kiepfer +# SPDX -License-Identifier: Apache-2.0 + +config REGULATOR_AXP192 + bool "X-Power AXP192 PMIC regulator driver" + default y + depends on DT_HAS_X_POWERS_AXP192_REGULATOR_ENABLED + depends on DT_HAS_X_POWERS_AXP192_ENABLED + select I2C + select MFD + help + Enable the AXP PMIC regulator driver + +if REGULATOR_AXP192 + +config REGULATOR_AXP192_INIT_PRIORITY + int "AXP192 regulator driver init priority" + default 76 + help + Init priority for the axp192 regulator driver. It must be + greater than MFD_INIT_PRIORITY. + +endif diff --git a/drivers/regulator/regulator_axp192.c b/drivers/regulator/regulator_axp192.c new file mode 100644 index 00000000000..a2513d6e5ae --- /dev/null +++ b/drivers/regulator/regulator_axp192.c @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2021 NXP + * Copyright (c) 2023 Martin Kiepfer + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT x_powers_axp192_regulator + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(regulator_axp192, CONFIG_REGULATOR_LOG_LEVEL); + +/* Output control registers */ +#define AXP192_REG_EXTEN_DCDC2_CONTROL 0x10U +#define AXP192_REG_DCDC123_LDO23_CONTROL 0x12U +#define AXP192_REG_DCDC2_VOLTAGE 0x23U +#define AXP192_REG_DCDC2_SLOPE 0x25U +#define AXP192_REG_DCDC1_VOLTAGE 0x26U +#define AXP192_REG_DCDC3_VOLTAGE 0x27U +#define AXP192_REG_LDO23_VOLTAGE 0x28U +#define AXP192_REG_DCDC123_WORKMODE 0x80U + +struct regulator_axp192_desc { + const uint8_t enable_reg; + const uint8_t enable_mask; + const uint8_t enable_val; + const uint8_t vsel_reg; + const uint8_t vsel_mask; + const uint8_t vsel_bitpos; + const int32_t max_ua; + const uint8_t workmode_reg; + const uint8_t workmode_mask; + const uint8_t workmode_pwm_val; + const uint8_t num_ranges; + const struct linear_range *ranges; +}; + +struct regulator_axp192_data { + struct regulator_common_data data; +}; + +struct regulator_axp192_config { + struct regulator_common_config common; + const struct regulator_axp192_desc *desc; + const struct device *mfd; + const struct i2c_dt_spec i2c; + + LOG_INSTANCE_PTR_DECLARE(log); +}; + +static const struct linear_range dcdc1_ranges[] = { + LINEAR_RANGE_INIT(700000U, 25000U, 0x00U, 0x7FU), +}; + +static const struct regulator_axp192_desc dcdc1_desc = { + .enable_reg = AXP192_REG_DCDC123_LDO23_CONTROL, + .enable_mask = 0x01U, + .enable_val = 0x01U, + .vsel_reg = AXP192_REG_DCDC1_VOLTAGE, + .vsel_mask = 0x7FU, + .vsel_bitpos = 0U, + .max_ua = 1200000U, + .workmode_reg = AXP192_REG_DCDC123_WORKMODE, + .workmode_mask = 0x08U, + .workmode_pwm_val = 0x08U, + .ranges = dcdc1_ranges, + .num_ranges = ARRAY_SIZE(dcdc1_ranges), +}; + +static const struct linear_range dcdc2_ranges[] = { + LINEAR_RANGE_INIT(700000U, 25000U, 0x00U, 0x3FU), +}; + +static const struct regulator_axp192_desc dcdc2_desc = { + .enable_reg = AXP192_REG_DCDC123_LDO23_CONTROL, + .enable_mask = 0x10U, + .enable_val = 0x10U, + .vsel_reg = AXP192_REG_DCDC2_VOLTAGE, + .vsel_mask = 0x3FU, + .vsel_bitpos = 0U, + .max_ua = 1600000U, + .workmode_reg = AXP192_REG_DCDC123_WORKMODE, + .workmode_mask = 0x04U, + .workmode_pwm_val = 0x04U, + .ranges = dcdc2_ranges, + .num_ranges = ARRAY_SIZE(dcdc2_ranges), +}; + +static const struct linear_range dcdc3_ranges[] = { + LINEAR_RANGE_INIT(700000U, 25000U, 0x00U, 0x7FU), +}; + +static const struct regulator_axp192_desc dcdc3_desc = { + .enable_reg = AXP192_REG_DCDC123_LDO23_CONTROL, + .enable_mask = 0x02U, + .enable_val = 0x02U, + .vsel_reg = AXP192_REG_DCDC3_VOLTAGE, + .vsel_mask = 0x7FU, + .vsel_bitpos = 0U, + .max_ua = 700000U, + .workmode_reg = AXP192_REG_DCDC123_WORKMODE, + .workmode_mask = 0x02U, + .workmode_pwm_val = 0x02U, + .ranges = dcdc3_ranges, + .num_ranges = ARRAY_SIZE(dcdc3_ranges), +}; + +static const struct linear_range ldo2_ranges[] = { + LINEAR_RANGE_INIT(1800000U, 100000U, 0x00U, 0x0FU), +}; + +static const struct regulator_axp192_desc ldo2_desc = { + .enable_reg = AXP192_REG_DCDC123_LDO23_CONTROL, + .enable_mask = 0x04U, + .enable_val = 0x04U, + .vsel_reg = AXP192_REG_LDO23_VOLTAGE, + .vsel_mask = 0xF0U, + .vsel_bitpos = 4U, + .max_ua = 200000U, + .workmode_reg = 0U, + .workmode_mask = 0U, + .ranges = ldo2_ranges, + .num_ranges = ARRAY_SIZE(ldo2_ranges), +}; + +static const struct linear_range ldo3_ranges[] = { + LINEAR_RANGE_INIT(1800000U, 100000U, 0x00U, 0x0FU), +}; + +static const struct regulator_axp192_desc ldo3_desc = { + .enable_reg = AXP192_REG_DCDC123_LDO23_CONTROL, + .enable_mask = 0x08U, + .enable_val = 0x08U, + .vsel_reg = AXP192_REG_LDO23_VOLTAGE, + .vsel_mask = 0x0FU, + .vsel_bitpos = 0U, + .max_ua = 200000U, + .workmode_reg = 0U, + .workmode_mask = 0U, + .ranges = ldo3_ranges, + .num_ranges = ARRAY_SIZE(ldo3_ranges), +}; + +static int axp192_enable(const struct device *dev) +{ + const struct regulator_axp192_config *config = dev->config; + int ret; + + LOG_INST_DBG(config->log, "Enabling regulator"); + LOG_INST_DBG(config->log, "[0x%02x]=0x%02x mask=0x%02x", config->desc->enable_reg, + config->desc->enable_val, config->desc->enable_mask); + + ret = i2c_reg_update_byte_dt(&config->i2c, config->desc->enable_reg, + config->desc->enable_mask, config->desc->enable_val); + if (ret != 0) { + LOG_INST_ERR(config->log, "Failed to enable regulator"); + } + + return ret; +} + +static int axp192_disable(const struct device *dev) +{ + const struct regulator_axp192_config *config = dev->config; + int ret; + + LOG_INST_DBG(config->log, "Disabling regulator"); + LOG_INST_DBG(config->log, "[0x%02x]=0 mask=0x%x", config->desc->enable_reg, + config->desc->enable_mask); + + ret = i2c_reg_update_byte_dt(&config->i2c, config->desc->enable_reg, + config->desc->enable_mask, 0u); + if (ret != 0) { + LOG_INST_ERR(config->log, "Failed to disable regulator"); + } + + return ret; +} + +static unsigned int axp192_count_voltages(const struct device *dev) +{ + const struct regulator_axp192_config *config = dev->config; + + return linear_range_group_values_count(config->desc->ranges, config->desc->num_ranges); +} + +static int axp192_list_voltage(const struct device *dev, unsigned int idx, int32_t *volt_uv) +{ + const struct regulator_axp192_config *config = dev->config; + + return linear_range_group_get_value(config->desc->ranges, config->desc->num_ranges, idx, + volt_uv); +} + +static int axp192_set_voltage(const struct device *dev, int32_t min_uv, int32_t max_uv) +{ + const struct regulator_axp192_config *config = dev->config; + uint16_t idx; + int ret; + + LOG_INST_DBG(config->log, "voltage = [min=%d, max=%d]", min_uv, max_uv); + + /* set voltage */ + ret = linear_range_group_get_win_index(config->desc->ranges, config->desc->num_ranges, + min_uv, max_uv, &idx); + if (ret != 0) { + LOG_INST_ERR(config->log, "No voltage range window could be detected"); + return ret; + } + + idx <<= config->desc->vsel_bitpos; + + LOG_INST_DBG(config->log, "[0x%x]=0x%x mask=0x%x", config->desc->vsel_reg, idx, + config->desc->vsel_mask); + ret = i2c_reg_update_byte_dt(&config->i2c, config->desc->vsel_reg, config->desc->vsel_mask, + (uint8_t)idx); + if (ret != 0) { + LOG_INST_ERR(config->log, "Failed to set regulator voltage"); + } + + return ret; +} + +static int axp192_get_voltage(const struct device *dev, int32_t *volt_uv) +{ + const struct regulator_axp192_config *config = dev->config; + int ret; + uint8_t raw_reg; + + /* read voltage */ + ret = i2c_reg_read_byte_dt(&config->i2c, config->desc->vsel_reg, &raw_reg); + if (ret != 0) { + return ret; + } + + raw_reg = (raw_reg & config->desc->vsel_mask) >> config->desc->vsel_bitpos; + + ret = linear_range_group_get_value(config->desc->ranges, config->desc->num_ranges, raw_reg, + volt_uv); + + return ret; +} + +static int axp192_set_mode(const struct device *dev, regulator_mode_t mode) +{ + const struct regulator_axp192_config *config = dev->config; + int ret; + + /* setting workmode is only possible for DCDC1-3 */ + if ((mode == AXP192_DCDC_MODE_PWM) && (config->desc->workmode_reg != 0)) { + + /* configure PWM mode */ + LOG_INST_DBG(config->log, "PWM mode enabled"); + ret = i2c_reg_update_byte_dt(&config->i2c, config->desc->workmode_reg, + config->desc->workmode_mask, + config->desc->workmode_pwm_val); + if (ret != 0) { + return ret; + } + } else if (mode == AXP192_DCDC_MODE_AUTO) { + + /* configure AUTO mode (default) */ + if (config->desc->workmode_reg != 0) { + ret = i2c_reg_update_byte_dt(&config->i2c, config->desc->workmode_reg, + config->desc->workmode_mask, 0u); + if (ret != 0) { + return ret; + } + } else { + + /* AUTO is default mode for LDOs that cannot be configured */ + return 0; + } + } else { + LOG_INST_ERR(config->log, "Setting DCDC workmode failed"); + return -ENOTSUP; + } + + return 0; +} + +static int axp192_get_current_limit(const struct device *dev, int32_t *curr_ua) +{ + const struct regulator_axp192_config *config = dev->config; + + *curr_ua = config->desc->max_ua; + + return 0; +} + +static struct regulator_driver_api api = { + .enable = axp192_enable, + .disable = axp192_disable, + .count_voltages = axp192_count_voltages, + .list_voltage = axp192_list_voltage, + .set_voltage = axp192_set_voltage, + .get_voltage = axp192_get_voltage, + .set_mode = axp192_set_mode, + .get_current_limit = axp192_get_current_limit, +}; + +static int regulator_axp192_init(const struct device *dev) +{ + const struct regulator_axp192_config *config = dev->config; + uint8_t enabled_val; + bool is_enabled; + int ret = 0; + + regulator_common_data_init(dev); + + if (!device_is_ready(config->mfd)) { + LOG_INST_ERR(config->log, "Parent instance not ready!"); + return -ENODEV; + } + + /* read regulator state */ + ret = i2c_reg_read_byte_dt(&config->i2c, config->desc->enable_reg, &enabled_val); + if (ret != 0) { + LOG_INST_ERR(config->log, "Reading enable status failed!"); + return ret; + } + is_enabled = ((enabled_val & config->desc->enable_mask) == config->desc->enable_val); + LOG_INST_DBG(config->log, "is_enabled: %d", is_enabled); + + return regulator_common_init(dev, is_enabled); +} + +#define REGULATOR_AXP192_DEFINE(node_id, id, name) \ + static struct regulator_axp192_data data_##id; \ + LOG_INSTANCE_REGISTER(name, node_id, CONFIG_REGULATOR_LOG_LEVEL); \ + static const struct regulator_axp192_config config_##id = { \ + .common = REGULATOR_DT_COMMON_CONFIG_INIT(node_id), \ + .desc = &name##_desc, \ + .mfd = DEVICE_DT_GET(DT_GPARENT(node_id)), \ + .i2c = I2C_DT_SPEC_GET(DT_GPARENT(node_id)), \ + LOG_INSTANCE_PTR_INIT(log, name, node_id)}; \ + DEVICE_DT_DEFINE(node_id, regulator_axp192_init, NULL, &data_##id, &config_##id, \ + POST_KERNEL, CONFIG_REGULATOR_AXP192_INIT_PRIORITY, &api); + +#define REGULATOR_AXP192_DEFINE_COND(inst, child) \ + COND_CODE_1(DT_NODE_EXISTS(DT_INST_CHILD(inst, child)), \ + (REGULATOR_AXP192_DEFINE(DT_INST_CHILD(inst, child), child##inst, child)), ()) + +#define REGULATOR_AXP192_DEFINE_ALL(inst) \ + REGULATOR_AXP192_DEFINE_COND(inst, dcdc1) \ + REGULATOR_AXP192_DEFINE_COND(inst, dcdc2) \ + REGULATOR_AXP192_DEFINE_COND(inst, dcdc3) \ + REGULATOR_AXP192_DEFINE_COND(inst, ldo2) \ + REGULATOR_AXP192_DEFINE_COND(inst, ldo3) + +DT_INST_FOREACH_STATUS_OKAY(REGULATOR_AXP192_DEFINE_ALL) diff --git a/dts/bindings/mfd/x-powers,axp192.yaml b/dts/bindings/mfd/x-powers,axp192.yaml new file mode 100644 index 00000000000..ae181715e88 --- /dev/null +++ b/dts/bindings/mfd/x-powers,axp192.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2023, Martin Kiepfer +# SPDX-License-Identifier: Apache-2.0 + +description: X-Powers AXP192 + +compatible: "x-powers,axp192" + +include: i2c-device.yaml + +properties: + reg: + required: true diff --git a/dts/bindings/regulator/x-powers,axp192-regulator.yaml b/dts/bindings/regulator/x-powers,axp192-regulator.yaml new file mode 100644 index 00000000000..6f8d8718357 --- /dev/null +++ b/dts/bindings/regulator/x-powers,axp192-regulator.yaml @@ -0,0 +1,64 @@ +# Copyright (c), 2021 NXP +# Copyright (c), 2023 Martin Kiepfer +# SPDX -License-Identifier: Apache-2.0 + +description: | + AXP192 PMIC + + The PMIC has three DCDC converters and two LDOs (LDO1 cannot be disabled). + All need to be defined as children nodes. + For example: + + i2c { + pmic@34 { + reg = <0x34>; + ... + regulators { + compatible = "x-powers,axp192-regulator"; + + DCDC1 { + /* all properties for DCDC1 */ + }; + DCDC2 { + /* all properties for DCDC2 */ + }; + DCDC3 { + /* all properties for DCDC3 */ + }; + LDO2 { + /* all properties for LDO2 */ + }; + LDO3 { + /* all properties for LDO3 */ + }; + }; + }; + }; + +compatible: "x-powers,axp192-regulator" + +include: base.yaml + +child-binding: + include: + - name: regulator.yaml + property-allowlist: + - regulator-init-microvolt + - regulator-min-microvolt + - regulator-max-microvolt + - regulator-always-on + - regulator-boot-on + - regulator-initial-mode + - regulator-allowed-modes + + properties: + regulator-initial-mode: + type: int + default: 0 + enum: + - 0 + - 1 + description: | + Initial operating mode. AXP192 supports 2 different power modes: + AXP192_DCDC_MODE_AUTO: Auto (0, default) + AXP192_DCDC_MODE_PWM: PWM diff --git a/include/zephyr/dt-bindings/regulator/axp192.h b/include/zephyr/dt-bindings/regulator/axp192.h new file mode 100644 index 00000000000..0ea2eedd3aa --- /dev/null +++ b/include/zephyr/dt-bindings/regulator/axp192.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Martin Kiepfer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_AXP192_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_AXP192_H_ + +/** + * @defgroup regulator_axp192 AXP192 Devicetree helpers. + * @ingroup regulator_interface + * @{ + */ + +/** + * @name AXP192 Regulator modes + * @{ + */ +/* DCDCs */ +#define AXP192_DCDC_MODE_AUTO 0x00U +#define AXP192_DCDC_MODE_PWM 0x01U + +/** @} */ + +/** @} */ + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_AXP192_H_ */ diff --git a/tests/drivers/build_all/regulator/i2c.dtsi b/tests/drivers/build_all/regulator/i2c.dtsi index 36818b03e8f..250c359f9c8 100644 --- a/tests/drivers/build_all/regulator/i2c.dtsi +++ b/tests/drivers/build_all/regulator/i2c.dtsi @@ -58,3 +58,19 @@ apd356x@3 { BUCKBOOST {}; }; }; + +axp192@4 { + compatible = "x-powers,axp192"; + reg = <0x4>; + + regulators { + compatible = "x-powers,axp192-regulator"; + + DCDC1 {}; + DCDC2 {}; + DCDC3 {}; + LDO1 {}; + LDO2 {}; + LDO3 {}; + }; +};