[PATCH v3 2/3] regulator: willsemi,wl2868c: add the WL2868C camera PMIC driver
From: Qianhui Xu
Date: Fri Oct 30 2026 - 23:43:24 EST
The WL2868C is an I2C camera power management IC with seven LDOs.
LDO1 and LDO2 are fed from VIN12 and cover 0.496 V to 1.512 V with a
7-bit voltage selector; LDO3 and LDO4 share VIN34, and LDO5 to LDO7
have one input each, covering 1.504 V to 3.544 V with an 8-bit
selector. All outputs step by 8 mV.
Every output is gated by a per-LDO bit in the enable register, and the
whole chip is additionally gated by the SYS_EN bit in the same
register. The driver sets SYS_EN together with the LDO bit on enable
and requires both to read back before reporting the regulator as
enabled, because no output comes up while SYS_EN is clear. An
optional active-low RST_N GPIO is released during probe, followed by
a 1 ms delay for the device to come up, and is asserted again when
the driver is unbound.
The device is used on the Lenovo Legion Tab (TB322FC, SM8750): the
rear camera (samsung,s5kjns) takes DVDD from LDO1, AVDD from LDO4 and
DOVDD from LDO7, the front camera (galaxycore,gc08a8) takes DVDD from
LDO2, AVDD from LDO3 and DOVDD from LDO7, and the rear VCM
(giantec,gt9764) takes VDD from LDO5 and VDDIO from LDO7. The next
patch wires these supplies up in the board file.
Signed-off-by: Qianhui Xu <xiaqinghuan@xxxxxxxxxxxxxxxx>
---
MAINTAINERS | 1 +
drivers/regulator/Kconfig | 11 ++
drivers/regulator/Makefile | 1 +
drivers/regulator/wl2868c-regulator.c | 155 ++++++++++++++++++++++++++
4 files changed, 168 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 632b17b3ea..a334308798 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29418,6 +29418,7 @@ M: Qianhui Xu <xiaqinghuan@xxxxxxxxxxxxxxxx>
L: linux-kernel@xxxxxxxxxxxxxxx
S: Maintained
F: Documentation/devicetree/bindings/regulator/willsemi,wl2868c.yaml
+F: drivers/regulator/wl2868c-regulator.c
WILOCITY WIL6210 WIRELESS DRIVER
L: linux-wireless@xxxxxxxxxxxxxxx
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 89789ac7a7..53f5b1527f 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -1858,6 +1858,17 @@ config REGULATOR_VQMMC_IPQ4019
This driver provides support for the VQMMC LDO I/0
voltage regulator of the IPQ4019 SD/EMMC controller.
+config REGULATOR_WL2868C
+ tristate "Will Semiconductor WL2868C regulator"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Support the seven LDO outputs of the Will Semiconductor WL2868C
+ camera power management IC.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wl2868c-regulator.
+
config REGULATOR_WM831X
tristate "Wolfson Microelectronics WM831x PMIC regulators"
depends on MFD_WM831X
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 5a764cec8d..d706959588 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -212,6 +212,7 @@ obj-$(CONFIG_REGULATOR_RZG2L_VBCTRL) += renesas-usb-vbus-regulator.o
obj-$(CONFIG_REGULATOR_VCTRL) += vctrl-regulator.o
obj-$(CONFIG_REGULATOR_VEXPRESS) += vexpress-regulator.o
obj-$(CONFIG_REGULATOR_VQMMC_IPQ4019) += vqmmc-ipq4019-regulator.o
+obj-$(CONFIG_REGULATOR_WL2868C) += wl2868c-regulator.o
obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o
obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o
obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o
diff --git a/drivers/regulator/wl2868c-regulator.c b/drivers/regulator/wl2868c-regulator.c
new file mode 100644
index 0000000000..9c4f4240b1
--- /dev/null
+++ b/drivers/regulator/wl2868c-regulator.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Will Semiconductor WL2868C seven-channel camera regulator driver.
+ *
+ * Copyright (C) 2026 Qianhui Xu
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+
+#define WL2868C_DEVICE_ID 0x00
+#define WL2868C_DEVICE_ID_VALUE 0x82
+#define WL2868C_LDO_ENABLE 0x0e
+#define WL2868C_SYS_ENABLE BIT(7)
+
+static int wl2868c_enable(struct regulator_dev *rdev)
+{
+ /*
+ * SYS_EN is the master enable of the chip: no output comes up unless
+ * it is set together with the per-LDO bit.
+ */
+ return regmap_update_bits(rdev->regmap, WL2868C_LDO_ENABLE,
+ WL2868C_SYS_ENABLE | rdev->desc->enable_mask,
+ WL2868C_SYS_ENABLE | rdev->desc->enable_mask);
+}
+
+static int wl2868c_is_enabled(struct regulator_dev *rdev)
+{
+ unsigned int mask = WL2868C_SYS_ENABLE | rdev->desc->enable_mask;
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(rdev->regmap, WL2868C_LDO_ENABLE, &value);
+ if (ret)
+ return ret;
+
+ return (value & mask) == mask;
+}
+
+static const struct regulator_ops wl2868c_ops = {
+ .list_voltage = regulator_list_voltage_linear,
+ .map_voltage = regulator_map_voltage_linear,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .enable = wl2868c_enable,
+ .disable = regulator_disable_regmap,
+ .is_enabled = wl2868c_is_enabled,
+};
+
+/*
+ * LDO1 and LDO2 have a 7-bit voltage selector, the other five have an 8-bit
+ * one; all of them step by 8 mV.
+ */
+#define WL2868C_LDO(_num, _supply, _min, _n_voltages, _vsel_reg, _mask, _en) { \
+ .name = "ldo" #_num, \
+ .of_match = "ldo" #_num, \
+ .regulators_node = "regulators", \
+ .id = (_num) - 1, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+ .ops = &wl2868c_ops, \
+ .supply_name = _supply, \
+ .min_uV = _min, \
+ .uV_step = 8000, \
+ .n_voltages = _n_voltages, \
+ .vsel_reg = _vsel_reg, \
+ .vsel_mask = _mask, \
+ .enable_reg = WL2868C_LDO_ENABLE, \
+ .enable_mask = _en, \
+ .enable_time = 150, \
+}
+
+static const struct regulator_desc wl2868c_regulators[] = {
+ WL2868C_LDO(1, "vin12", 496000, 128, 0x03, 0x7f, BIT(0)),
+ WL2868C_LDO(2, "vin12", 496000, 128, 0x04, 0x7f, BIT(1)),
+ WL2868C_LDO(3, "vin34", 1504000, 256, 0x05, 0xff, BIT(2)),
+ WL2868C_LDO(4, "vin34", 1504000, 256, 0x06, 0xff, BIT(3)),
+ WL2868C_LDO(5, "vin5", 1504000, 256, 0x07, 0xff, BIT(4)),
+ WL2868C_LDO(6, "vin6", 1504000, 256, 0x08, 0xff, BIT(5)),
+ WL2868C_LDO(7, "vin7", 1504000, 256, 0x09, 0xff, BIT(6)),
+};
+
+static const struct regmap_config wl2868c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x25,
+};
+
+static void wl2868c_reset(void *data)
+{
+ gpiod_set_value_cansleep(data, 1);
+}
+
+static int wl2868c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct regulator_config config = { .dev = dev };
+ struct regulator_dev *rdev;
+ struct gpio_desc *reset;
+ unsigned int id;
+ int ret, i;
+
+ reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(reset))
+ return dev_err_probe(dev, PTR_ERR(reset), "Failed to get reset GPIO\n");
+
+ if (reset) {
+ ret = devm_add_action_or_reset(dev, wl2868c_reset, reset);
+ if (ret)
+ return ret;
+ fsleep(1000);
+ }
+
+ config.regmap = devm_regmap_init_i2c(client, &wl2868c_regmap_config);
+ if (IS_ERR(config.regmap))
+ return dev_err_probe(dev, PTR_ERR(config.regmap),
+ "Failed to create regmap\n");
+
+ ret = regmap_read(config.regmap, WL2868C_DEVICE_ID, &id);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read device ID\n");
+ if (id != WL2868C_DEVICE_ID_VALUE)
+ return dev_err_probe(dev, -ENODEV, "Unexpected device ID: %#x\n", id);
+
+ for (i = 0; i < ARRAY_SIZE(wl2868c_regulators); i++) {
+ rdev = devm_regulator_register(dev, &wl2868c_regulators[i], &config);
+ if (IS_ERR(rdev))
+ return dev_err_probe(dev, PTR_ERR(rdev),
+ "Failed to register LDO%d\n", i + 1);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id wl2868c_of_match[] = {
+ { .compatible = "willsemi,wl2868c" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, wl2868c_of_match);
+
+static struct i2c_driver wl2868c_driver = {
+ .driver = {
+ .name = "wl2868c",
+ .of_match_table = wl2868c_of_match,
+ },
+ .probe = wl2868c_probe,
+};
+module_i2c_driver(wl2868c_driver);
+
+MODULE_DESCRIPTION("Will Semiconductor WL2868C regulator driver");
+MODULE_LICENSE("GPL");
--
2.53.0