From 8a3ff25427acba26f5e2814b497a8a2c03358c91 Mon Sep 17 00:00:00 2001 From: "Celine Joy A. Capua" Date: Fri, 10 Jan 2025 13:12:31 +0800 Subject: [PATCH 1/3] dt-bindings: regulator: Document the ltc7871 regulator The LTC7871 is a bidirectional buck or boost switching regulator controller that operates in either buck or boost mode on demand. Add corresponding DT bindings. Signed-off-by: Celine Joy A. Capua --- .../regulator/adi,ltc7871-regulator.yaml | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/adi,ltc7871-regulator.yaml diff --git a/Documentation/devicetree/bindings/regulator/adi,ltc7871-regulator.yaml b/Documentation/devicetree/bindings/regulator/adi,ltc7871-regulator.yaml new file mode 100644 index 00000000000000..b1ae8b16e19c11 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/adi,ltc7871-regulator.yaml @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright (c) 2025 Analog Devices, Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/adi,ltc7871-regulator.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices LTC7871 Buck-Boost Voltage Regulator +maintainers: + - Celine Joy Capua + +description: + The LTC7871 is a high performance bidirectional buck or boost switching + regulator controller that operates in either buck or boost mode on demand. + It regulates in buck mode from VHIGH-to-VLOW and boost mode from + VLOW-to-VHIGH depending on a control signal, making it ideal for 48V/12V + automotive dual battery systems +properties: + compatible: + enum: + - adi,ltc7871 + - adi,ltc7872 + + reg: + maxItems: 1 + + adi,enable-chip-ctrl-wp: + description: If present, this indicates the use of regulator's + internal write protection. + type: boolean + + adi,ra-external-ohms: + description: External Resistor used to compute the value of + programmable VLOW in buck mode. + default: 10000 + + adi,rb-external-ohms: + description: External Resistor used to compute the value of + programmable VLOW in buck mode. + default: 107000 + + adi,rc-external-ohms: + description: External Resistor used to compute the value of + programmable VHIGH in boost mode. + default: 12700 + + adi,rd-external-ohms: + description: External Resistor used to compute the value of + programmable VHIGH in boost mode. + default: 499000 + + adi,idac-setcur-microamp: + description: Adjusts the IDAC_SETCUR to program SETCUR + pin's sourcing current. + minimum: -63 + maximum: 64 + default: 0 + + adi,freq-spread-percentage: + description: Sets the range of modulation with respect to the + switching frequency. + enum: [+-12%, +-15%, +-10%, +-8%] + default: +-12% + + adi,switching-freq-divider: + description: The factor at which the switching frequency is + divided to get the modulation frequency. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [512, 1024, 2048, 4096, 256, 128, 64] + default: 512 + +required: + - compatible + - reg + +allOf: + - $ref: regulator.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + regulator@0 { + compatible = "adi,ltc7871"; + reg = <0>; + adi,ra-external-ohm = <10000>; + adi,rb-external-ohm = <107000>; + adi,rc-external-ohm = <12700>; + adi,rd-external-ohm = <499000>; + adi,idac-setcur-microamp = <0>; + adi,freq-spread-percentage = "+-12%"; + adi,switching-freq-divider = <512>; + }; + }; From f7c83e8e737539f5c4eaa7c2d08ce6de259697ca Mon Sep 17 00:00:00 2001 From: "Celine Joy A. Capua" Date: Fri, 10 Jan 2025 13:15:59 +0800 Subject: [PATCH 2/3] regulator: ltc7871: Add driver for LTC7871 Add ADI LTC7871 buck-boost controller driver support. Signed-off-by: Celine Joy A. Capua --- drivers/regulator/Kconfig | 11 + drivers/regulator/Makefile | 1 + drivers/regulator/ltc7871-regulator.c | 402 ++++++++++++++++++++++++++ 3 files changed, 414 insertions(+) create mode 100644 drivers/regulator/ltc7871-regulator.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 41291800200422..8f1b7f74acf1dc 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -538,6 +538,17 @@ config REGULATOR_LTC3676 This enables support for the LTC3676 8-output regulators controlled via I2C. +config REGULATOR_LTC7871 + tristate "LTC7871 six-phase buck-boost voltage regulator driver with SPI" + depends on SPI && OF + help + This driver controls an Analog Devices LTC7871 high performance + bidirectional buck or boost switching regulator controller + that operates in either buck or boost mode on demand. + + Say M here if you want to include support for the regulator as a + module. + config REGULATOR_MAX14577 tristate "Maxim 14577/77836 regulator" depends on MFD_MAX14577 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index b2b059b5ee565a..5a183286079e35 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_REGULATOR_LP8788) += lp8788-buck.o obj-$(CONFIG_REGULATOR_LP8788) += lp8788-ldo.o obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o obj-$(CONFIG_REGULATOR_LTC3589) += ltc3589.o +obj-$(CONFIG_REGULATOR_LTC7871) += ltc7871-regulator.o obj-$(CONFIG_REGULATOR_LTC3676) += ltc3676.o obj-$(CONFIG_REGULATOR_MAX14577) += max14577-regulator.o obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o diff --git a/drivers/regulator/ltc7871-regulator.c b/drivers/regulator/ltc7871-regulator.c new file mode 100644 index 00000000000000..59c3b856fcb825 --- /dev/null +++ b/drivers/regulator/ltc7871-regulator.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices LTC7871 Voltage Regulator Driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC7871_REG_FAULT 0x01 +#define LTC7871_REG_CONFIG2 0x06 +#define LTC7871_REG_CHIP_CTRL 0x07 +#define LTC7871_REG_IDAC_VLOW 0x08 +#define LTC7871_REG_IDAC_VHIGH 0x09 +#define LTC7871_REG_SETCUR 0x0A +#define LTC7871_REG_SSFM 0x0B + +#define LTC7871_FAULT_OVER_TEMP BIT(0) +#define LTC7871_FAULT_VHIGH_UV BIT(4) +#define LTC7871_FAULT_VHIGH_OV BIT(5) +#define LTC7871_FAULT_VLOW_OV BIT(6) + +#define LTC7871_MASK_CONFIG2_BUCK_BOOST BIT(0) + +#define LTC7871_MASK_CHIP_CTRL_WP BIT(0) + +#define LTC7871_MASK_SSFM_FREQ_SPREAD GENMASK(4, 3) +#define LTC7871_MASK_SSFM_MOD_SIG_FREQ GENMASK(2, 0) + +#define LTC7871_CRC_INIT 0x41 +#define LTC7871_CRC8_POLY 0x7 +#define LTC7871_DATA_POS 1 +#define LTC7871_CRC_POS 2 +#define LTC7871_FRAME_SIZE 3 + +#define LTC7871_IDAC_MAX 63 +#define LTC7871_IDAC_MIN -64 + +DECLARE_CRC8_TABLE(ltc7871_crc8_table); + +struct ltc7871 { + struct spi_device *spi; + struct regulator_dev *rdev; + bool enable_chip_ctrl_wp; + bool regulator_mode; + u32 ra_ext; + u32 rb_ext; + u32 rc_ext; + u32 rd_ext; + u32 r1; + u32 r2; + u32 max_vol; + u32 min_vol; + s32 idac_setcur_uA; + const char *freq_spread_percentage; + u32 switching_freq_divider; +}; + +static const char * const ltc7871_freq_spread_percentage[] = { + "+-12%", + "+-15%", + "+-10%", + "+-8%", +}; + +static const unsigned int ltc7871_switching_freq_divider[] = { + 512, + 1024, + 2048, + 4096, + 256, + 128, + 64, +}; + +static int ltc7871_reg_read(struct spi_device *spi, u8 reg, int *val) +{ + int ret; + struct spi_transfer t; + u8 crc; + u8 rx_buf[LTC7871_FRAME_SIZE] = {0}; + u8 tx_buf[LTC7871_FRAME_SIZE] = {0}; + + tx_buf[0] = reg << 1 | 1; + + t.tx_buf = tx_buf; + t.rx_buf = rx_buf; + t.len = LTC7871_FRAME_SIZE; + + crc = crc8(ltc7871_crc8_table, rx_buf, LTC7871_CRC_POS, + LTC7871_CRC_INIT); + + ret = spi_sync_transfer(spi, &t, 1); + if (ret < 0) + return ret; + + if (rx_buf[LTC7871_CRC_POS] != crc) + return -EIO; + + return 0; +} + +static int ltc7871_reg_write(struct spi_device *spi, u8 reg, int val) +{ + struct spi_transfer t; + u8 rx_buf[LTC7871_FRAME_SIZE] = {0}; + u8 tx_buf[LTC7871_FRAME_SIZE] = {0}; + + tx_buf[0] = reg << 1; + tx_buf[1] = val; + tx_buf[2] = crc8(ltc7871_crc8_table, tx_buf, LTC7871_CRC_POS, LTC7871_CRC_INIT); + + t.tx_buf = tx_buf; + t.rx_buf = rx_buf; + t.len = LTC7871_FRAME_SIZE; + + return spi_sync_transfer(spi, &t, 1); +} + +static int ltc7871_get_error_flags(struct regulator_dev *rdev, + unsigned int *flags) +{ + u32 val; + int ret; + struct ltc7871 *ltc7871 = rdev_get_drvdata(rdev); + + ret = ltc7871_reg_read(ltc7871->spi, LTC7871_REG_FAULT, &val); + if (ret) + return ret; + + *flags = 0; + + if (FIELD_GET(LTC7871_FAULT_VHIGH_OV, val) || + FIELD_GET(LTC7871_FAULT_VLOW_OV, val)) + *flags |= REGULATOR_ERROR_OVER_VOLTAGE_WARN; + + if (FIELD_GET(LTC7871_FAULT_VHIGH_UV, val)) + *flags |= REGULATOR_ERROR_UNDER_VOLTAGE; + + if (FIELD_GET(LTC7871_FAULT_OVER_TEMP, val)) + *flags |= REGULATOR_ERROR_OVER_TEMP; + + return 0; +} + +static s64 _ltc7871_dac_to_uV(struct ltc7871 *ltc7871, u32 dac_val) +{ + return 1200 * (1000 + (div_s64(ltc7871->r2 * 1000, ltc7871->r1))) - + dac_val * ltc7871->r2; +} + +static s64 _ltc7871_uV_to_dac(struct ltc7871 *ltc7871, s32 uV) +{ + s64 tmp; + + tmp = 1200 * (1000 + (div_s64(ltc7871->r2 * 1000, ltc7871->r1))) - uV; + tmp = div_s64(tmp, ltc7871->r2); + + return tmp; +} + +static int ltc7871_set_voltage_sel(struct regulator_dev *rdev, + unsigned int sel) +{ + int reg; + int addr; + struct ltc7871 *ltc7871 = rdev_get_drvdata(rdev); + + if (sel < ltc7871->min_vol || sel > ltc7871->max_vol) + return -EINVAL; + + if (ltc7871->regulator_mode) + addr = LTC7871_REG_IDAC_VLOW; + else + addr = LTC7871_REG_IDAC_VHIGH; + + reg = _ltc7871_uV_to_dac(ltc7871, sel); + + return ltc7871_reg_write(ltc7871->spi, addr, reg); +} + +static int ltc7871_get_voltage_sel(struct regulator_dev *rdev) +{ + int reg, ret; + int addr; + struct ltc7871 *ltc7871 = rdev_get_drvdata(rdev); + + if (ltc7871->regulator_mode) + addr = LTC7871_REG_IDAC_VLOW; + else + addr = LTC7871_REG_IDAC_VHIGH; + + ret = ltc7871_reg_read(ltc7871->spi, addr, ®); + if (ret < 0) + return ret; + + return _ltc7871_dac_to_uV(ltc7871, reg); +} + +static int ltc7871_get_prop_index(const u32 *table, size_t table_size, u32 value) +{ + int i; + + for (i = 0; i < table_size; i++) + if (table[i] == value) + return i; + + return -EINVAL; +} + +static int ltc7871_parse_fw(struct ltc7871 *chip) +{ + int reg, ret; + int val1, val2; + + /* Setting default values based on LTC7871 Datasheet and Demo Circuit DC2886A Schematic */ + chip->idac_setcur_uA = 0; + chip->freq_spread_percentage = "+-12%"; + chip->switching_freq_divider = 512; + chip->enable_chip_ctrl_wp = 0; + chip->ra_ext = 10000; + chip->rb_ext = 107000; + chip->rc_ext = 12700; + chip->rd_ext = 499000; + + ret = device_property_read_u32(&chip->spi->dev, "adi,ra-external-ohms", + &chip->ra_ext); + if (!ret) { + if (chip->ra_ext <= 0) + return -EINVAL; + } + + ret = device_property_read_u32(&chip->spi->dev, "adi,rb-external-ohms", + &chip->rb_ext); + if (!ret) { + if (chip->rb_ext <= 0) + return -EINVAL; + } + + ret = device_property_read_u32(&chip->spi->dev, "adi,rc-external-ohms", + &chip->rc_ext); + if (!ret) { + if (chip->rc_ext <= 0) + return -EINVAL; + } + + ret = device_property_read_u32(&chip->spi->dev, "adi,rd-external-ohms", + &chip->rd_ext); + if (!ret) { + if (chip->rd_ext <= 0) + return -EINVAL; + } + + ret = ltc7871_reg_read(chip->spi, LTC7871_REG_CONFIG2, ®); + if (ret < 0) + return ret; + + chip->regulator_mode = FIELD_GET(LTC7871_MASK_CONFIG2_BUCK_BOOST, reg); + + if (chip->regulator_mode) { + chip->r1 = chip->ra_ext; + chip->r2 = chip->rb_ext; + } else { + chip->r1 = chip->rc_ext; + chip->r2 = chip->rd_ext; + } + chip->min_vol = _ltc7871_dac_to_uV(chip, LTC7871_IDAC_MAX); + chip->max_vol = _ltc7871_dac_to_uV(chip, LTC7871_IDAC_MIN); + + ret = ltc7871_reg_read(chip->spi, LTC7871_REG_CHIP_CTRL, ®); + if (ret < 0) + return ret; + + chip->enable_chip_ctrl_wp = device_property_read_bool(&chip->spi->dev, + "adi,enable-chip-ctrl-wp"); + val1 = FIELD_PREP(LTC7871_MASK_CHIP_CTRL_WP, chip->enable_chip_ctrl_wp) | reg; + ret = ltc7871_reg_write(chip->spi, LTC7871_REG_CHIP_CTRL, val1); + if (ret) + return ret; + + ret = device_property_read_u32(&chip->spi->dev, "adi,idac-setcur-microamp", + &chip->idac_setcur_uA); + if (!ret) { + if (chip->idac_setcur_uA >= LTC7871_IDAC_MIN && + chip->idac_setcur_uA <= LTC7871_IDAC_MAX) { + ret = ltc7871_reg_write(chip->spi, + LTC7871_REG_SETCUR, + chip->idac_setcur_uA); + if (ret) + return ret; + } else { + return -EINVAL; + } + } + ret = device_property_match_property_string(&chip->spi->dev, + "adi,freq-spread-percentage", + ltc7871_freq_spread_percentage, + ARRAY_SIZE(ltc7871_freq_spread_percentage)); + + if (ret >= 0) + val1 = FIELD_PREP(LTC7871_MASK_SSFM_FREQ_SPREAD, ret); + else + val1 = 0; + + ret = device_property_read_u32(&chip->spi->dev, + "adi,modulation-signal-freq", + &chip->switching_freq_divider); + if (!ret) { + ret = ltc7871_get_prop_index(ltc7871_switching_freq_divider, + ARRAY_SIZE(ltc7871_switching_freq_divider), + chip->switching_freq_divider); + if (ret < 0) + return ret; + + val2 = FIELD_PREP(LTC7871_MASK_SSFM_MOD_SIG_FREQ, ret); + } + + return ltc7871_reg_write(chip->spi, LTC7871_REG_SSFM, val1 | val2); +} + +static const struct regulator_ops ltc7871_regulator_ops = { + .set_voltage_sel = ltc7871_set_voltage_sel, + .get_voltage_sel = ltc7871_get_voltage_sel, + .get_error_flags = ltc7871_get_error_flags, +}; + +static const struct regulator_desc ltc7871_regulator_desc = { + .ops = <c7871_regulator_ops, + .name = "ltc7871", + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; + +static int ltc7871_probe(struct spi_device *spi) +{ + int ret; + struct regulator_init_data *init_data; + struct device *dev = &spi->dev; + struct regulator_config config = { }; + struct ltc7871 *chip; + + init_data = of_get_regulator_init_data(dev, spi->dev.of_node, + <c7871_regulator_desc); + if (!init_data) + return -EINVAL; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + crc8_populate_msb(ltc7871_crc8_table, LTC7871_CRC8_POLY); + + chip->spi = spi; + + ret = ltc7871_parse_fw(chip); + if (ret < 0) + return ret; + + config.dev = dev; + config.init_data = init_data; + config.driver_data = chip; + + chip->rdev = devm_regulator_register(dev, <c7871_regulator_desc, + &config); + + return PTR_ERR_OR_ZERO(chip->rdev); +} + +static const struct of_device_id ltc7871_of_match[] = { + { .compatible = "adi,ltc7871", }, + { .compatible = "adi,ltc7872", }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc7871_of_match); + +static const struct spi_device_id ltc7871_id[] = { + {"ltc7871" }, + {"ltc7872" }, + { }, +}; +MODULE_DEVICE_TABLE(spi, ltc7871_id); + +static struct spi_driver ltc7871_driver = { + .driver = { + .name = "ltc7871", + .of_match_table = ltc7871_of_match, + }, + .probe = ltc7871_probe, + .id_table = ltc7871_id, +}; +module_spi_driver(ltc7871_driver); + +MODULE_DESCRIPTION("LTC7871 Voltage Regulator Driver"); +MODULE_AUTHOR("Celine Joy Capua "); +MODULE_LICENSE("GPL"); From a2967e7b43a02d93f71eb593bc11aa28847488a9 Mon Sep 17 00:00:00 2001 From: "Celine Joy A. Capua" Date: Fri, 10 Jan 2025 13:17:15 +0800 Subject: [PATCH 3/3] kconfig.adi: Add LTC7871 imply REGULATOR_LTC7871. Signed-off-by: Celine Joy A. Capua --- Kconfig.adi | 1 + 1 file changed, 1 insertion(+) diff --git a/Kconfig.adi b/Kconfig.adi index 66eb89473a6cd2..82f16d4b10375a 100644 --- a/Kconfig.adi +++ b/Kconfig.adi @@ -95,6 +95,7 @@ config KERNEL_ALL_ADI_DRIVERS imply REGULATOR_MAX77857 imply REGULATOR_MAX77541 imply MFD_MAX77541 + imply REGULATOR_LTC7871 source "drivers/clk/Kconfig.adi" source "drivers/hwmon/Kconfig.adi"