Add ADI LTC7871 buck-boost controller driver support. Signed-off-by: Celine Joy A. Capua <celinejoy.capua@xxxxxxxxxx> --- drivers/regulator/Kconfig | 11 + drivers/regulator/Makefile | 1 + drivers/regulator/ltc7871-regulator.c | 405 ++++++++++++++++++++++++++++++++++ 3 files changed, 417 insertions(+) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 39297f7d8177193e51c99bc2b360c6d9936e62fe..500e8d7a198c597a479f80866a9733ebf945e15f 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -566,6 +566,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 3d5a803dce8a0556ba9557fa069c6e37593b3c69..47e26fcf39db39da542a7bc4df05b214c4b7cc0f 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_REGULATOR_LP8788) += lp8788-ldo.o obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o obj-$(CONFIG_REGULATOR_LTC3589) += ltc3589.o obj-$(CONFIG_REGULATOR_LTC3676) += ltc3676.o +obj-$(CONFIG_REGULATOR_LTC7871) += ltc7871-regulator.o obj-$(CONFIG_REGULATOR_MAX14577) += max14577-regulator.o obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o obj-$(CONFIG_REGULATOR_MAX5970) += max5970-regulator.o diff --git a/drivers/regulator/ltc7871-regulator.c b/drivers/regulator/ltc7871-regulator.c new file mode 100644 index 0000000000000000000000000000000000000000..eeea952f9362e48e0f6b85309a0c273f77776cb0 --- /dev/null +++ b/drivers/regulator/ltc7871-regulator.c @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices LTC7871 Voltage Regulator Driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include <linux/bitfield.h> +#include <linux/crc8.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/spi/spi.h> + +#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) +{ + s64 tmp; + + tmp = 1200 * (1000 + (div_s64(ltc7871->r2 * 1000, ltc7871->r1))); + tmp = tmp - dac_val * ltc7871->r2; + + return tmp; +} + +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 datasheet and 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) + return -EINVAL; + } + + ret = device_property_read_u32(&chip->spi->dev, "adi,rb-external-ohms", + &chip->rb_ext); + if (!ret) { + if (!chip->rb_ext) + return -EINVAL; + } + + ret = device_property_read_u32(&chip->spi->dev, "adi,rc-external-ohms", + &chip->rc_ext); + if (!ret) { + if (!chip->rc_ext) + return -EINVAL; + } + + ret = device_property_read_u32(&chip->spi->dev, "adi,rd-external-ohms", + &chip->rd_ext); + if (!ret) { + if (!chip->rd_ext) + 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) { + return -EINVAL; + } + + ret = ltc7871_reg_write(chip->spi, LTC7871_REG_SETCUR, + chip->idac_setcur_uA); + if (ret) + return ret; + } + 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,switching-freq-divider", + &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 <celinejoy.capua@xxxxxxxxxx>"); +MODULE_LICENSE("GPL"); -- 2.34.1