From e05df8faf1e4c474014261d849ba41830416b61b Mon Sep 17 00:00:00 2001 From: Gerard Marull-Paretas Date: Mon, 22 May 2023 19:07:23 +0200 Subject: [PATCH] drivers: regulator: adp5360: initial version Add a new regulator driver for Analog Devices ADP5360. While it is a MFD device, only support for BUCK/BUCKBOOST regulators is added in this patch. Signed-off-by: Gerard Marull-Paretas --- drivers/regulator/CMakeLists.txt | 1 + drivers/regulator/Kconfig | 1 + drivers/regulator/Kconfig.adp5360 | 28 ++ drivers/regulator/regulator_adp5360.c | 308 ++++++++++++++++++ dts/bindings/mfd/adi,adp5360.yaml | 12 + .../regulator/adi,adp5360-regulator.yaml | 85 +++++ .../zephyr/dt-bindings/regulator/adp5360.h | 28 ++ tests/drivers/build_all/regulator/i2c.dtsi | 12 + 8 files changed, 475 insertions(+) create mode 100644 drivers/regulator/Kconfig.adp5360 create mode 100644 drivers/regulator/regulator_adp5360.c create mode 100644 dts/bindings/mfd/adi,adp5360.yaml create mode 100644 dts/bindings/regulator/adi,adp5360-regulator.yaml create mode 100644 include/zephyr/dt-bindings/regulator/adp5360.h diff --git a/drivers/regulator/CMakeLists.txt b/drivers/regulator/CMakeLists.txt index 8368d9f37ac..1f5f9f8b345 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_ADP5360 regulator_adp5360.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_FAKE regulator_fake.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_FIXED regulator_fixed.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_NPM1100 regulator_npm1100.c) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index b69e52023a3..c51d55b4de4 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.adp5360" source "drivers/regulator/Kconfig.fake" source "drivers/regulator/Kconfig.fixed" source "drivers/regulator/Kconfig.npm1100" diff --git a/drivers/regulator/Kconfig.adp5360 b/drivers/regulator/Kconfig.adp5360 new file mode 100644 index 00000000000..30f4d566254 --- /dev/null +++ b/drivers/regulator/Kconfig.adp5360 @@ -0,0 +1,28 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX -License-Identifier: Apache-2.0 + +config REGULATOR_ADP5360 + bool "ADP5360 PMIC regulator driver" + default y + depends on DT_HAS_ADI_ADP5360_REGULATOR_ENABLED + select I2C + help + Enable the Analog Devices ADP5360 PMIC regulator driver + +if REGULATOR_ADP5360 + +config REGULATOR_ADP5360_COMMON_INIT_PRIORITY + int "ADP5360 regulator driver init priority (common part)" + default 75 + help + Init priority for the Analog Devices ADP5360 regulator driver (common + part). It must be greater than I2C init priority. + +config REGULATOR_ADP5360_INIT_PRIORITY + int "ADP5360 regulator driver init priority" + default 76 + help + Init priority for the Analog Devices ADP5360 regulator driver. It must + be greater than REGULATOR_ADP5360_COMMON_INIT_PRIORITY. + +endif diff --git a/drivers/regulator/regulator_adp5360.c b/drivers/regulator/regulator_adp5360.c new file mode 100644 index 00000000000..c417b6e5985 --- /dev/null +++ b/drivers/regulator/regulator_adp5360.c @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT adi_adp5360_regulator + +#include + +#include +#include +#include +#include +#include + +/* ADP5360 regulator related registers */ +#define ADP5360_BUCK_CFG 0x29U +#define ADP5360_BUCK_OUTPUT 0x2AU +#define ADP5360_BUCKBST_CFG 0x2BU +#define ADP5360_BUCKBST_OUTPUT 0x2CU + +/* Buck/boost configure register. */ +#define ADP5360_BUCK_CFG_SS_MSK GENMASK(7, 6) +#define ADP5360_BUCK_CFG_SS_POS 6U +#define ADP5360_BUCK_CFG_BST_ILIM_MSK GENMASK(5, 3) +#define ADP5360_BUCK_CFG_BST_ILIM_POS 3U +#define ADP5360_BUCK_CFG_BUCK_ILIM_MSK GENMASK(5, 3) +#define ADP5360_BUCK_CFG_BUCK_ILIM_POS 3U +#define ADP5360_BUCK_CFG_BUCK_MODE_MSK BIT(3) +#define ADP5360_BUCK_CFG_BUCK_MODE_POS 3U +#define ADP5360_BUCK_CFG_STP_MSK BIT(2) +#define ADP5360_BUCK_CFG_DISCHG_MSK BIT(1) +#define ADP5360_BUCK_CFG_EN_MSK BIT(0) + +/* Buck/boost output voltage setting register. */ +#define ADP5360_BUCK_OUTPUT_VOUT_MSK GENMASK(5, 0) +#define ADP5360_BUCK_OUTPUT_VOUT_POS 0U +#define ADP5360_BUCK_OUTPUT_DLY_MSK GENMASK(7, 6) +#define ADP5360_BUCK_OUTPUT_DLY_POS 6U + +struct regulator_adp5360_desc { + uint8_t cfg_reg; + uint8_t out_reg; + bool has_modes; + const struct linear_range *ranges; + uint8_t nranges; +}; + +static const struct linear_range buck_ranges[] = { + LINEAR_RANGE_INIT(600000, 50000U, 0x0U, 0x3FU), +}; + +static const struct regulator_adp5360_desc buck_desc = { + .cfg_reg = ADP5360_BUCK_CFG, + .out_reg = ADP5360_BUCK_OUTPUT, + .has_modes = true, + .ranges = buck_ranges, + .nranges = ARRAY_SIZE(buck_ranges), +}; + +static const struct linear_range buckboost_ranges[] = { + LINEAR_RANGE_INIT(1800000, 100000U, 0x0U, 0x0BU), + LINEAR_RANGE_INIT(2950000, 50000U, 0xCU, 0x3FU), +}; + +static const struct regulator_adp5360_desc buckboost_desc = { + .cfg_reg = ADP5360_BUCKBST_CFG, + .out_reg = ADP5360_BUCKBST_OUTPUT, + .has_modes = false, + .ranges = buckboost_ranges, + .nranges = ARRAY_SIZE(buckboost_ranges), +}; + +struct regulator_adp5360_config { + struct regulator_common_config common; + struct i2c_dt_spec i2c; + const struct regulator_adp5360_desc *desc; + int8_t dly_idx; + int8_t ss_idx; + int8_t ilim_idx; + bool stp_en; + bool dis_en; +}; + +struct regulator_adp5360_data { + struct regulator_common_data data; +}; + +static unsigned int regulator_adp5360_count_voltages(const struct device *dev) +{ + const struct regulator_adp5360_config *config = dev->config; + + return linear_range_group_values_count(config->desc->ranges, config->desc->nranges); +} + +static int regulator_adp5360_list_voltage(const struct device *dev, unsigned int idx, + int32_t *volt_uv) +{ + const struct regulator_adp5360_config *config = dev->config; + + return linear_range_group_get_value(config->desc->ranges, config->desc->nranges, idx, + volt_uv); +} + +static int regulator_adp5360_set_voltage(const struct device *dev, int32_t min_uv, int32_t max_uv) +{ + const struct regulator_adp5360_config *config = dev->config; + uint16_t idx; + int ret; + + ret = linear_range_group_get_win_index(config->desc->ranges, config->desc->nranges, min_uv, + max_uv, &idx); + if (ret == -EINVAL) { + return ret; + } + + return i2c_reg_update_byte_dt(&config->i2c, config->desc->out_reg, + ADP5360_BUCK_OUTPUT_VOUT_MSK, + (uint8_t)idx << ADP5360_BUCK_OUTPUT_VOUT_POS); +} + +static int regulator_adp5360_get_voltage(const struct device *dev, int32_t *volt_uv) +{ + const struct regulator_adp5360_config *config = dev->config; + int ret; + uint8_t raw_reg; + + ret = i2c_reg_read_byte_dt(&config->i2c, config->desc->out_reg, &raw_reg); + if (ret < 0) { + return ret; + } + + raw_reg = (raw_reg & ADP5360_BUCK_OUTPUT_VOUT_MSK) >> ADP5360_BUCK_OUTPUT_VOUT_POS; + + return linear_range_group_get_value(config->desc->ranges, config->desc->nranges, raw_reg, + volt_uv); +} + +static int regulator_adp5360_set_mode(const struct device *dev, regulator_mode_t mode) +{ + const struct regulator_adp5360_config *config = dev->config; + + if (!config->desc->has_modes || (mode > ADP5360_MODE_PWM)) { + return -ENOTSUP; + } + + return i2c_reg_update_byte_dt(&config->i2c, config->desc->cfg_reg, + ADP5360_BUCK_CFG_BUCK_MODE_MSK, + mode << ADP5360_BUCK_CFG_BUCK_MODE_POS); +} + +static int regulator_adp5360_get_mode(const struct device *dev, regulator_mode_t *mode) +{ + const struct regulator_adp5360_config *config = dev->config; + uint8_t val; + int ret; + + if (!config->desc->has_modes) { + return -ENOTSUP; + } + + ret = i2c_reg_read_byte_dt(&config->i2c, config->desc->cfg_reg, &val); + if (ret < 0) { + return ret; + } + + *mode = (val & ADP5360_BUCK_CFG_BUCK_MODE_MSK) >> ADP5360_BUCK_CFG_BUCK_MODE_POS; + + return 0; +} + +static int regulator_adp5360_enable(const struct device *dev) +{ + const struct regulator_adp5360_config *config = dev->config; + + return i2c_reg_update_byte_dt(&config->i2c, config->desc->cfg_reg, ADP5360_BUCK_CFG_EN_MSK, + 1U); +} + +static int regulator_adp5360_disable(const struct device *dev) +{ + const struct regulator_adp5360_config *config = dev->config; + + return i2c_reg_update_byte_dt(&config->i2c, config->desc->cfg_reg, ADP5360_BUCK_CFG_EN_MSK, + 0U); +} + +static int regulator_adp5360_init(const struct device *dev) +{ + const struct regulator_adp5360_config *config = dev->config; + int ret; + uint8_t val, nval, msk; + + regulator_common_data_init(dev); + + if (!i2c_is_ready_dt(&config->i2c)) { + return -ENODEV; + } + + /* apply optional delay */ + msk = 0U; + nval = 0U; + + ret = i2c_reg_read_byte_dt(&config->i2c, config->desc->out_reg, &val); + if (ret < 0) { + return ret; + } + + if (config->dly_idx >= 0) { + msk |= ADP5360_BUCK_OUTPUT_DLY_MSK; + nval |= ((uint8_t)config->dly_idx << ADP5360_BUCK_OUTPUT_DLY_POS) & + ADP5360_BUCK_OUTPUT_DLY_MSK; + } + + if (msk != 0U) { + ret = i2c_reg_write_byte_dt(&config->i2c, config->desc->out_reg, + (val & ~msk) | nval); + if (ret < 0) { + return ret; + } + } + + /* apply optional initial configuration */ + msk = 0U; + nval = 0U; + + ret = i2c_reg_read_byte_dt(&config->i2c, config->desc->cfg_reg, &val); + if (ret < 0) { + return ret; + } + + if (config->ss_idx >= 0) { + msk |= ADP5360_BUCK_CFG_SS_MSK; + nval |= ((uint8_t)config->ss_idx << ADP5360_BUCK_CFG_SS_POS) & + ADP5360_BUCK_CFG_SS_MSK; + } + + if (config->ilim_idx >= 0) { + if (config->desc->has_modes) { + msk |= ADP5360_BUCK_CFG_BUCK_ILIM_MSK; + nval |= ((uint8_t)config->ilim_idx << ADP5360_BUCK_CFG_BUCK_ILIM_POS) & + ADP5360_BUCK_CFG_BUCK_ILIM_MSK; + } else { + msk |= ADP5360_BUCK_CFG_BST_ILIM_MSK; + nval |= ((uint8_t)config->ilim_idx << ADP5360_BUCK_CFG_BST_ILIM_POS) & + ADP5360_BUCK_CFG_BST_ILIM_MSK; + } + } + + if (config->stp_en) { + msk |= ADP5360_BUCK_CFG_STP_MSK; + nval |= ADP5360_BUCK_CFG_STP_MSK; + } + + if (config->dis_en) { + msk |= ADP5360_BUCK_CFG_DISCHG_MSK; + nval |= ADP5360_BUCK_CFG_DISCHG_MSK; + } + + if (msk != 0U) { + ret = i2c_reg_write_byte_dt(&config->i2c, config->desc->cfg_reg, + (val & ~msk) | nval); + if (ret < 0) { + return ret; + } + } + + return regulator_common_init(dev, (val & ADP5360_BUCK_CFG_EN_MSK) != 0U); +} + +static const struct regulator_driver_api api = { + .enable = regulator_adp5360_enable, + .disable = regulator_adp5360_disable, + .count_voltages = regulator_adp5360_count_voltages, + .list_voltage = regulator_adp5360_list_voltage, + .set_voltage = regulator_adp5360_set_voltage, + .get_voltage = regulator_adp5360_get_voltage, + .set_mode = regulator_adp5360_set_mode, + .get_mode = regulator_adp5360_get_mode, +}; + +#define REGULATOR_ADP5360_DEFINE(node_id, id, name) \ + static struct regulator_adp5360_data data_##id; \ + \ + static const struct regulator_adp5360_config config_##id = { \ + .common = REGULATOR_DT_COMMON_CONFIG_INIT(node_id), \ + .i2c = I2C_DT_SPEC_GET(DT_GPARENT(node_id)), \ + .desc = &name##_desc, \ + .dly_idx = DT_ENUM_IDX_OR(node_id, adi_switch_delay_us, -1), \ + .ss_idx = DT_ENUM_IDX_OR(node_id, adi_soft_start_ms, -1), \ + .ilim_idx = DT_ENUM_IDX_OR(node_id, adi_ilim_milliamp, -1), \ + .stp_en = DT_PROP(node_id, adi_enable_stop_pulse), \ + .dis_en = DT_PROP(node_id, adi_enable_output_discharge), \ + }; \ + \ + DEVICE_DT_DEFINE(node_id, regulator_adp5360_init, NULL, &data_##id, &config_##id, \ + POST_KERNEL, CONFIG_REGULATOR_ADP5360_INIT_PRIORITY, &api); + +#define REGULATOR_ADP5360_DEFINE_COND(inst, child) \ + COND_CODE_1(DT_NODE_EXISTS(DT_INST_CHILD(inst, child)), \ + (REGULATOR_ADP5360_DEFINE(DT_INST_CHILD(inst, child), child##inst, child)), \ + ()) + +#define REGULATOR_ADP5360_DEFINE_ALL(inst) \ + REGULATOR_ADP5360_DEFINE_COND(inst, buck) \ + REGULATOR_ADP5360_DEFINE_COND(inst, buckboost) + +DT_INST_FOREACH_STATUS_OKAY(REGULATOR_ADP5360_DEFINE_ALL) diff --git a/dts/bindings/mfd/adi,adp5360.yaml b/dts/bindings/mfd/adi,adp5360.yaml new file mode 100644 index 00000000000..982acf66949 --- /dev/null +++ b/dts/bindings/mfd/adi,adp5360.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2023, Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: Analog Devices ADP5360 + +compatible: "adi,adp5360" + +include: i2c-device.yaml + +properties: + reg: + required: true diff --git a/dts/bindings/regulator/adi,adp5360-regulator.yaml b/dts/bindings/regulator/adi,adp5360-regulator.yaml new file mode 100644 index 00000000000..d683bd694ef --- /dev/null +++ b/dts/bindings/regulator/adi,adp5360-regulator.yaml @@ -0,0 +1,85 @@ +# Copyright (c), 2023 Nordic Semiconductor ASA +# SPDX -License-Identifier: Apache-2.0 + +description: | + Analog Devices ADP3560 PMIC + + The PMIC has one buck converter and one buck-boost converter. Both need to be + defined as children nodes, strictly following the BUCK and BUCKBOOST node + names. For example: + + pmic@46 { + compatible = "adi,adp5360"; + reg = <0x46>; + ... + regulators { + compatible = "adi,adp5360-regulator"; + + BUCK { + /* all properties for BUCK */ + }; + BUCKBOOST { + /* all properties for BUCKBOOST */ + }; + }; + }; + +compatible: "adi,adp5360-regulator" + +include: base.yaml + +child-binding: + include: + - name: regulator.yaml + property-allowlist: + - regulator-always-on + - regulator-boot-on + - regulator-init-microvolt + - regulator-min-microvolt + - regulator-max-microvolt + - regulator-allowed-modes + - regulator-initial-mode + + properties: + adi,switch-delay-us: + type: int + enum: + - 0 + - 5 + - 10 + - 20 + description: Switch delay time in hysteresis. + + adi,soft-start-ms: + type: int + enum: + - 1 + - 8 + - 64 + - 512 + description: Soft start time in milliseconds + + adi,ilim-milliamp: + type: int + enum: + - 100 + - 200 + - 300 + - 400 + - 500 + - 600 + - 700 + - 800 + description: | + Peak current limit, in milliamperes. Values above 400mA are only + applicable to buck boost. + + adi,enable-stop-pulse: + type: boolean + description: | + With this option selected and the buck/boost enabled, the buck/boost + regulator can be stopped using the STP pin. + + adi,enable-output-discharge: + type: boolean + description: Enable output discharge functionality diff --git a/include/zephyr/dt-bindings/regulator/adp5360.h b/include/zephyr/dt-bindings/regulator/adp5360.h new file mode 100644 index 00000000000..d3aecacaa72 --- /dev/null +++ b/include/zephyr/dt-bindings/regulator/adp5360.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_ADP5360_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_ADP5360_H_ + +/** + * @defgroup regulator_adp5360 ADP5360 Devicetree helpers. + * @ingroup regulator_interface + * @{ + */ + +/** + * @name ADP5360 Regulator modes + * @{ + */ +/** Hysteresis mode */ +#define ADP5360_MODE_HYS 0 +/** PWM mode */ +#define ADP5360_MODE_PWM 1 +/** @} */ + +/** @} */ + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_REGULATOR_ADP5360_H_*/ diff --git a/tests/drivers/build_all/regulator/i2c.dtsi b/tests/drivers/build_all/regulator/i2c.dtsi index 2597ab4781f..36818b03e8f 100644 --- a/tests/drivers/build_all/regulator/i2c.dtsi +++ b/tests/drivers/build_all/regulator/i2c.dtsi @@ -46,3 +46,15 @@ npm1300@2 { LDO2 {}; }; }; + +apd356x@3 { + compatible = "adi,adp5360"; + reg = <0x3>; + + regulators { + compatible = "adi,adp5360-regulator"; + + BUCK {}; + BUCKBOOST {}; + }; +};