| /* |
| * Copyright (c) 2023 Nordic Semiconductor ASA |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT adi_adp5360_regulator |
| |
| #include <errno.h> |
| |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/regulator.h> |
| #include <zephyr/dt-bindings/regulator/adp5360.h> |
| #include <zephyr/sys/linear_range.h> |
| #include <zephyr/sys/util.h> |
| |
| /* 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) |