diff options
| author | Tapio Reijonen <tapio.reijonen@vaisala.com> | 2026-09-10 08:51:42 +0000 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-10 16:53:11 +0100 |
| commit | 61879d561e91c71502fef9f28f26b31e9a5b9fa2 (patch) | |
| tree | 7848e83b09a0ba8e2cb028734299412a2bcf4d91 | |
| parent | e3c05a881fc9f0d47e373998bef5b4cb7426c565 (diff) | |
| download | linux-next-61879d561e91c71502fef9f28f26b31e9a5b9fa2.tar.gz linux-next-61879d561e91c71502fef9f28f26b31e9a5b9fa2.zip | |
regulator: Add MPS MPQ4210 buck-boost regulator driver
The MPQ4210 is a 40V synchronous four-switch buck-boost controller with
an I2C interface. Add a driver exposing voltage control and enable
control through the regulator interface.
The output voltage is programmed by an 11-bit feedback reference DAC with
a 1mV step, split across REF_LSB[2:0] and REF_MSB[7:0], and is then
scaled by the external feedback divider described in the device tree.
Only 0.3V to 2.047V of that range is specified, so linear_min_sel holds
the driver to it and the lower selectors are not offered. Scaled by the
divider, that floor is not zero: 4.2V on a board with a gain of 14, and
the core narrows regulator-min-microvolt to it.
The same divider ratio applies to the slew rate, so the four reference
slew rates that the Control 1 SR field selects are scaled into a
per-device ramp_delay_table and the field is exposed through
regulator_set_ramp_delay_regmap(). ramp_delay is initialised from the
rate SR is currently programmed for, so the core waits in proportion to
the size of each change; a board that sets regulator-ramp-delay
reprograms SR and the core uses that value instead.
Enabling follows the start-up sequence in the datasheet: commit the
reference with the GO bit, wait 200ms, then set ENPWR. Control 1 bit 2 is
documented as reserved but has to be set before the controller starts up,
so probe sets it. The controller does not respond on the bus while EN is
deasserted, so the enable GPIO is claimed before the first register
access.
Link: https://www.monolithicpower.com/en/mpq4210.html
Signed-off-by: Tapio Reijonen <tapio.reijonen@vaisala.com>
Link: https://patch.msgid.link/20260910-mpq4210-regulator-v1-2-d37e208dfc8d@vaisala.com
Signed-off-by: Mark Brown <broonie@kernel.org>
| -rw-r--r-- | MAINTAINERS | 1 | ||||
| -rw-r--r-- | drivers/regulator/Kconfig | 11 | ||||
| -rw-r--r-- | drivers/regulator/Makefile | 1 | ||||
| -rw-r--r-- | drivers/regulator/mpq4210.c | 243 |
4 files changed, 256 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index eb552bff791b..78d28c103eb1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18376,6 +18376,7 @@ MONOLITHIC POWER SYSTEM MPQ4210 REGULATOR DRIVER M: Tapio Reijonen <tapio.reijonen@vaisala.com> S: Maintained F: Documentation/devicetree/bindings/regulator/mps,mpq4210.yaml +F: drivers/regulator/mpq4210.c MONOLITHIC POWER SYSTEM PMIC DRIVER M: Saravanan Sekar <sravanhome@gmail.com> diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 89789ac7a786..df7742533f22 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -870,6 +870,17 @@ config REGULATOR_MP886X help This driver supports the MP8869 voltage regulator. +config REGULATOR_MPQ4210 + tristate "MPS MPQ4210 regulator driver" + depends on I2C && OF + select REGMAP_I2C + help + This driver supports the MPQ4210 synchronous buck-boost controller. + It supports get/set voltage, enable/disable and ramp rate selection + through the regulator interface. + Say M here if you want to include support for the regulator as a + module. The module will be named "mpq4210". + config REGULATOR_MPQ7920 tristate "Monolithic MPQ7920 PMIC" depends on I2C && OF diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 5a764cec8df8..af69a0ee877d 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -104,6 +104,7 @@ obj-$(CONFIG_REGULATOR_MCP16502) += mcp16502.o obj-$(CONFIG_REGULATOR_MP5416) += mp5416.o obj-$(CONFIG_REGULATOR_MP8859) += mp8859.o obj-$(CONFIG_REGULATOR_MP886X) += mp886x.o +obj-$(CONFIG_REGULATOR_MPQ4210) += mpq4210.o obj-$(CONFIG_REGULATOR_MPQ7920) += mpq7920.o obj-$(CONFIG_REGULATOR_MT6311) += mt6311-regulator.o obj-$(CONFIG_REGULATOR_MT6315) += mt6315-regulator.o diff --git a/drivers/regulator/mpq4210.c b/drivers/regulator/mpq4210.c new file mode 100644 index 000000000000..227136d82258 --- /dev/null +++ b/drivers/regulator/mpq4210.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Monolithic Power Systems MPQ4210 buck-boost regulator + * + * Copyright (c) 2026 Vaisala Oyj + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/math.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> + +#define MPQ4210_REF_LSB 0x00 +#define MPQ4210_REF_LSB_MASK GENMASK(2, 0) +#define MPQ4210_REF_LSB_BITS 3 +#define MPQ4210_REF_MSB 0x01 +#define MPQ4210_CONTROL1 0x02 +#define MPQ4210_CONTROL1_SR GENMASK(7, 6) +#define MPQ4210_CONTROL1_RESERVED BIT(2) +#define MPQ4210_CONTROL1_GO BIT(1) +#define MPQ4210_CONTROL1_ENPWR BIT(0) +#define MPQ4210_INT_MASK 0x06 + +/* + * The feedback reference is an 11 bit value with a 1mV step. The datasheet + * specifies no reference below 0.3V, so those selectors are not offered. + */ +#define MPQ4210_REF_MIN 0x12c +#define MPQ4210_REF_MAX 0x7ff +#define MPQ4210_REF_STEP_UV 1000 + +#define MPQ4210_ENPWR_DELAY_MS 200 + +/* Feedback reference slew rate per MPQ4210_CONTROL1_SR value, in uV/us. */ +static const unsigned int mpq4210_ref_slew_rate[] = { 38, 50, 75, 150 }; + +static const struct regmap_config mpq4210_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MPQ4210_INT_MASK, +}; + +/* Scale a feedback reference value by the output voltage divider ratio. */ +static unsigned int mpq4210_scale(unsigned int val, u32 r1, u32 r2) +{ + u64 tmp = (u64)val * (r1 + r2); + + do_div(tmp, r2); + + return tmp; +} + +static int mpq4210_set_voltage_sel(struct regulator_dev *rdev, unsigned int sel) +{ + int ret; + + ret = regmap_write(rdev->regmap, MPQ4210_REF_LSB, + sel & MPQ4210_REF_LSB_MASK); + if (ret) + return ret; + + ret = regmap_write(rdev->regmap, MPQ4210_REF_MSB, + sel >> MPQ4210_REF_LSB_BITS); + if (ret) + return ret; + + return regmap_set_bits(rdev->regmap, MPQ4210_CONTROL1, + MPQ4210_CONTROL1_GO); +} + +static int mpq4210_get_voltage_sel(struct regulator_dev *rdev) +{ + unsigned int lsb, msb; + int ret; + + ret = regmap_read(rdev->regmap, MPQ4210_REF_MSB, &msb); + if (ret) + return ret; + + ret = regmap_read(rdev->regmap, MPQ4210_REF_LSB, &lsb); + if (ret) + return ret; + + return (msb << MPQ4210_REF_LSB_BITS) | (lsb & MPQ4210_REF_LSB_MASK); +} + +static int mpq4210_enable(struct regulator_dev *rdev) +{ + int ret; + + ret = regmap_set_bits(rdev->regmap, MPQ4210_CONTROL1, + MPQ4210_CONTROL1_GO); + if (ret) + return ret; + + /* The reference has to settle before power switching may start. */ + msleep(MPQ4210_ENPWR_DELAY_MS); + + return regmap_set_bits(rdev->regmap, MPQ4210_CONTROL1, + MPQ4210_CONTROL1_ENPWR); +} + +static const struct regulator_ops mpq4210_regulator_ops = { + .set_voltage_sel = mpq4210_set_voltage_sel, + .get_voltage_sel = mpq4210_get_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .set_ramp_delay = regulator_set_ramp_delay_regmap, + .enable = mpq4210_enable, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const struct regulator_desc mpq4210_regulator = { + .name = "mpq4210", + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &mpq4210_regulator_ops, + .n_voltages = MPQ4210_REF_MAX + 1, + .linear_min_sel = MPQ4210_REF_MIN, + .enable_reg = MPQ4210_CONTROL1, + .enable_mask = MPQ4210_CONTROL1_ENPWR, + .ramp_reg = MPQ4210_CONTROL1, + .ramp_mask = MPQ4210_CONTROL1_SR, + .n_ramp_values = ARRAY_SIZE(mpq4210_ref_slew_rate), +}; + +static int mpq4210_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct regulator_config config = { }; + struct regulator_desc *desc; + struct regulator_dev *rdev; + struct gpio_desc *enable; + struct regmap *regmap; + unsigned int *slew; + unsigned int i, val; + u32 r[2]; + int ret; + + ret = of_property_read_u32_array(dev->of_node, "mps,fb-voltage-divider", + r, ARRAY_SIZE(r)); + if (ret) + return dev_err_probe(dev, ret, + "failed to read mps,fb-voltage-divider\n"); + + if (!r[1]) + return dev_err_probe(dev, -EINVAL, + "feedback divider R2 must not be zero\n"); + + /* The controller does not answer on the bus while EN is deasserted. */ + enable = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(enable)) + return dev_err_probe(dev, PTR_ERR(enable), + "failed to get enable GPIO\n"); + + regmap = devm_regmap_init_i2c(client, &mpq4210_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "failed to init regmap\n"); + + desc = devm_kmemdup(dev, &mpq4210_regulator, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->min_uV = mpq4210_scale(MPQ4210_REF_MIN * MPQ4210_REF_STEP_UV, + r[0], r[1]); + desc->uV_step = mpq4210_scale(MPQ4210_REF_STEP_UV, r[0], r[1]); + + slew = devm_kcalloc(dev, ARRAY_SIZE(mpq4210_ref_slew_rate), + sizeof(*slew), GFP_KERNEL); + if (!slew) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(mpq4210_ref_slew_rate); i++) + slew[i] = mpq4210_scale(mpq4210_ref_slew_rate[i], r[0], r[1]); + + desc->ramp_delay_table = slew; + + ret = regmap_read(regmap, MPQ4210_CONTROL1, &val); + if (ret) + return dev_err_probe(dev, ret, "failed to read control 1\n"); + + /* The core overrides this if the board sets regulator-ramp-delay. */ + desc->ramp_delay = slew[FIELD_GET(MPQ4210_CONTROL1_SR, val)]; + + /* + * Documented as reserved, but the datasheet requires it to be set + * before the controller starts up. + */ + ret = regmap_set_bits(regmap, MPQ4210_CONTROL1, + MPQ4210_CONTROL1_RESERVED); + if (ret) + return dev_err_probe(dev, ret, "failed to write control 1\n"); + + config.dev = dev; + config.regmap = regmap; + config.of_node = dev->of_node; + config.init_data = of_get_regulator_init_data(dev, dev->of_node, desc); + if (!config.init_data) + return -ENOMEM; + + rdev = devm_regulator_register(dev, desc, &config); + if (IS_ERR(rdev)) + return dev_err_probe(dev, PTR_ERR(rdev), + "failed to register regulator\n"); + + return 0; +} + +static const struct of_device_id mpq4210_of_match[] = { + { .compatible = "mps,mpq4210" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpq4210_of_match); + +static const struct i2c_device_id mpq4210_i2c_id[] = { + { .name = "mpq4210" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpq4210_i2c_id); + +static struct i2c_driver mpq4210_regulator_driver = { + .driver = { + .name = "mpq4210", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = mpq4210_of_match, + }, + .probe = mpq4210_i2c_probe, + .id_table = mpq4210_i2c_id, +}; + +module_i2c_driver(mpq4210_regulator_driver); + +MODULE_DESCRIPTION("Monolithic Power Systems MPQ4210 voltage regulator driver"); +MODULE_AUTHOR("Tapio Reijonen <tapio.reijonen@vaisala.com>"); +MODULE_LICENSE("GPL"); |
