[PATCH 2/2] regulator: Add MPS MPQ4210 buck-boost regulator driver
From: Tapio Reijonen
Date: Thu Sep 10 2026 - 05:00:53 EST
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@xxxxxxxxxxx>
---
MAINTAINERS | 1 +
drivers/regulator/Kconfig | 11 ++
drivers/regulator/Makefile | 1 +
drivers/regulator/mpq4210.c | 243 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 256 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index eb552bff791bed27b86d5e2a398d0d96253efc6f..78d28c103eb14d90e2566a00a089e66e34d0839b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18376,6 +18376,7 @@ MONOLITHIC POWER SYSTEM MPQ4210 REGULATOR DRIVER
M: Tapio Reijonen <tapio.reijonen@xxxxxxxxxxx>
S: Maintained
F: Documentation/devicetree/bindings/regulator/mps,mpq4210.yaml
+F: drivers/regulator/mpq4210.c
MONOLITHIC POWER SYSTEM PMIC DRIVER
M: Saravanan Sekar <sravanhome@xxxxxxxxx>
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 89789ac7a786c8c4c4a750cdcaa27a205762ac18..df7742533f22cacd4b012259f6e10d1e663c87c4 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 5a764cec8df834de6f7775574e4bbd1d4dd8f6ef..af69a0ee877d8afaf80b3b4c293661b5900ac78a 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 0000000000000000000000000000000000000000..227136d8225805a759d2d6545637858ea7122b89
--- /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@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
--
2.47.3