mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v11 0/2] regulator: add the Will Semiconductor WL2868C camera PMIC
@ 2026-11-13  8:19 Qianhui Xu
  2026-11-13  8:19 ` [PATCH v11 1/2] regulator: dt-bindings: willsemi,wl2868c: add the " Qianhui Xu
  2026-11-13  8:19 ` [PATCH v11 2/2] regulator: willsemi,wl2868c: add the WL2868C camera PMIC driver Qianhui Xu
  0 siblings, 2 replies; 3+ messages in thread
From: Qianhui Xu @ 2026-11-13  8:19 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel,
	devicetree, linux-arm-msm

*** AI-assistance disclosure ***
This cover letter and the two patches below were prepared with the help
of an LLM coding assistant.  The author reviewed every line, ran every
gate quoted in this letter himself, and takes full responsibility for
the content.

This is v11 of the WL2868C binding+driver series.  It supersedes v10
(<20261111111132.1296892-1-xiaqinghuan@mail.ustc.edu.cn>) and differs
from it in one place only: the LDOx_SEQ handling Mark asked about.

What changed in v11
===================

Mark Brown asked on v10 2/2
(<20261111111132.1296892-3-xiaqinghuan@mail.ustc.edu.cn>):

> Should the driver not also override the LDOx_SEQ registers to make sure
> we have software control of them, or hide the enable/disable ops if
> they're non-zero?

Taken the first way: the four slot registers are cleared in probe.

The datasheet (WL2868C, Will Semiconductor, Rev 1.4, April 2022,
"0x0A, 0x0B, 0x0C and 0x0D: LDOx_SEQ", p.35) defines LDOx_SEQ[3:0] as
the power-up sequencer slot of each output:

  - 0000, the reset value: the output is controlled by its LDOx_EN bit
    in 0x0E;
  - x001..x111: the output is controlled by the sequencer, which brings
    the LDO up once SEQ_ON in 0x0F is set and SEQ_COUNT[2:0] reaches
    the slot; writes to the LDOx_EN bit have no effect on it.

So a non-zero slot silently defeats enable()/disable(): the register
write succeeds, is_enabled() reports the LDOx_EN bit, and the output
still follows the sequencer.  The binding describes no sequencing at
all, so clearing the slots and keeping 0x0E as the only control path is
the way to make the enable ops truthful.  The other option, hiding
enable/disable when a slot is non-zero, would leave the outputs
uncontrollable on exactly the boards where software needs them.

Measured on the reference board (Lenovo Legion Y700 Gen4, SM8750;
Linux 7.3.1, driver bound, read-only through regmap debugfs):
0x0a..0x0d = 00 00 00 00, 0x0e = 0x80, 0x0f = 0x00.  The firmware left
every slot at its reset value and the sequencer disabled, so the clear
is a no-op on this board today; it is there so that a board whose
firmware does program the slots does not end up with silently
uncontrolled outputs.  Clearing a non-zero slot switches off the outputs
it had turned on; at probe time no consumer is up yet, and the core
re-enables whatever it needs through the enable register.

Everything else is unchanged from v10, so the v10 changelog (sashiko's
three Medium findings and the two Low binding points) still applies as
written.

Gates for v11
=============

- git am of the two patches onto pristine v7.2 (25f7927e6ccb) leaves no
  local changes after the series.
- checkpatch --strict on the driver change: 0 errors, 0 warnings,
  0 checks.
- W=1 build of drivers/regulator/wl2868c-regulator.o with
  REGULATOR_WL2868C=y and its dependencies: rc=0, no warnings.
- dt_binding_check: unchanged and passing; the binding is not touched
  in v11.


Qianhui Xu (2):
  regulator: dt-bindings: willsemi,wl2868c: add the WL2868C camera PMIC
  regulator: willsemi,wl2868c: add the WL2868C camera PMIC driver

 .../bindings/regulator/willsemi,wl2868c.yaml  |  99 ++++++
 MAINTAINERS                                   |   7 +
 drivers/regulator/Kconfig                     |  11 +
 drivers/regulator/Makefile                    |   1 +
 drivers/regulator/wl2868c-regulator.c         | 301 ++++++++++++++++++
 5 files changed, 419 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/regulator/willsemi,wl2868c.yaml
 create mode 100644 drivers/regulator/wl2868c-regulator.c

-- 
2.53.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v11 1/2] regulator: dt-bindings: willsemi,wl2868c: add the WL2868C camera PMIC
  2026-11-13  8:19 [PATCH v11 0/2] regulator: add the Will Semiconductor WL2868C camera PMIC Qianhui Xu
@ 2026-11-13  8:19 ` Qianhui Xu
  2026-11-13  8:19 ` [PATCH v11 2/2] regulator: willsemi,wl2868c: add the WL2868C camera PMIC driver Qianhui Xu
  1 sibling, 0 replies; 3+ messages in thread
From: Qianhui Xu @ 2026-11-13  8:19 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel,
	devicetree, linux-arm-msm

The WL2868C is an I2C-controlled camera power management IC from Will
Semiconductor. It provides seven programmable LDOs: LDO1 and LDO2 are fed
from a shared VIN12 input and cover 0.496 V to 1.512 V, LDO3 and LDO4 share
VIN34, and LDO5 to LDO7 each have their own input, covering 1.504 V to
3.544 V. All outputs are programmable in 8 mV steps and are gated by
per-output enable bits with a chip-wide SYS_EN master. The chip also
exposes an optional active-low RST_N input.

VIN7 and VSYS are both inputs of the internal control logic, which is
powered from the higher of the two voltages, so a device tree has to
describe at least one of them. The remaining inputs only feed LDOs, so
they stay optional: an input can be left unconnected when the outputs it
feeds are unused on a given board.

The example enables ldo1, which is fed from VIN12, so it also describes
vin12-supply; the binding says an input may only be left out when the
outputs it feeds are unused on a given board.

Signed-off-by: Qianhui Xu <xiaqinghuan@mail.ustc.edu.cn>
---
 .../bindings/regulator/willsemi,wl2868c.yaml  | 99 +++++++++++++++++++
 MAINTAINERS                                   |  6 ++
 2 files changed, 105 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/regulator/willsemi,wl2868c.yaml

diff --git a/Documentation/devicetree/bindings/regulator/willsemi,wl2868c.yaml b/Documentation/devicetree/bindings/regulator/willsemi,wl2868c.yaml
new file mode 100644
index 000000000..972813fbd
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/willsemi,wl2868c.yaml
@@ -0,0 +1,99 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/regulator/willsemi,wl2868c.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Will Semiconductor WL2868C camera power management IC
+
+maintainers:
+  - Qianhui Xu <xiaqinghuan@mail.ustc.edu.cn>
+
+description: >
+  The WL2868C is a camera power management IC with seven programmable LDOs.
+  LDO1 and LDO2 share the VIN12 input and cover 0.496 V to 1.512 V; LDO3 and
+  LDO4 share VIN34, and LDO5 to LDO7 have one input each, covering 1.504 V to
+  3.544 V. All outputs are programmable in 8 mV steps.
+
+  VIN7 and VSYS feed the internal control logic, which is powered from the
+  higher of the two, so at least one of them has to be connected. The other
+  inputs only feed LDOs, so an input may be left unconnected on a board that
+  does not use the outputs it feeds, which is why they are not mandatory.
+
+properties:
+  compatible:
+    const: willsemi,wl2868c
+
+  reg:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+    description: Active-low RST_N input.
+
+  vin12-supply:
+    description: Input supply for LDO1 and LDO2.
+
+  vin34-supply:
+    description: Input supply for LDO3 and LDO4.
+
+  vin5-supply:
+    description: Input supply for LDO5.
+
+  vin6-supply:
+    description: Input supply for LDO6.
+
+  vin7-supply:
+    description: >
+      Input supply for LDO7 and one of the two supplies of the internal
+      control logic.
+
+  vsys-supply:
+    description: >
+      System supply, the other supply of the internal control logic. The
+      control logic is powered from the higher of VIN7 and VSYS.
+
+  regulators:
+    type: object
+    additionalProperties: false
+
+    patternProperties:
+      "^ldo[1-7]$":
+        type: object
+        $ref: regulator.yaml#
+        unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - regulators
+
+anyOf:
+  - required:
+      - vin7-supply
+  - required:
+      - vsys-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        pmic@2f {
+            compatible = "willsemi,wl2868c";
+            reg = <0x2f>;
+            vsys-supply = <&vreg_vsys>;
+            vin12-supply = <&vreg_vin12>;
+
+            regulators {
+                ldo1 {
+                    regulator-max-microvolt = <1128000>;
+                    regulator-min-microvolt = <1128000>;
+                };
+            };
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 8014b9f82..435beaf95 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29058,6 +29058,12 @@ L:	linux-input@vger.kernel.org
 S:	Maintained
 F:	drivers/hid/hid-wiimote*
 
+WILLSEMI WL2868C CAMERA PMIC
+M:	Qianhui Xu <xiaqinghuan@mail.ustc.edu.cn>
+L:	linux-kernel@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/regulator/willsemi,wl2868c.yaml
+
 WILOCITY WIL6210 WIRELESS DRIVER
 L:	linux-wireless@vger.kernel.org
 S:	Orphan
-- 
2.53.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v11 2/2] regulator: willsemi,wl2868c: add the WL2868C camera PMIC driver
  2026-11-13  8:19 [PATCH v11 0/2] regulator: add the Will Semiconductor WL2868C camera PMIC Qianhui Xu
  2026-11-13  8:19 ` [PATCH v11 1/2] regulator: dt-bindings: willsemi,wl2868c: add the " Qianhui Xu
@ 2026-11-13  8:19 ` Qianhui Xu
  1 sibling, 0 replies; 3+ messages in thread
From: Qianhui Xu @ 2026-11-13  8:19 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel,
	devicetree, linux-arm-msm

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 starts asserted, is released only after
the control supply is up, followed by a delay for the device to come
up, and is asserted again before the supply is disabled on unbind.

The enable register is written as a whole rather than updated bit by
bit: the per-LDO bits can be preset by the firmware while SYS_EN is
clear, and setting SYS_EN together with a single LDO bit would then
switch the preset outputs on as well. The driver therefore tracks the
set of LDOs enabled by consumers and writes exactly those bits, so
enabling one output cannot power another one. That set is seeded from
the register during probe, but only when SYS_EN is already set: with
SYS_EN clear the per-LDO bits are presets for outputs that are off,
and seeding them would make the first enable() of one output switch
all of them on. disable() commits the tracked state only after the
register write went through, so a failed write cannot make a later
enable() switch off an output that the core still believes is on.

The control logic and the I2C interface are powered from the higher of
VIN7 and VSYS. The driver enables VSYS, falling back to VIN7 on boards
that do not describe VSYS, and keeps it enabled for as long as it is
bound: without it the device does not answer on the bus. The device ID
is read only after the control supply is up and RST_N has been
released.

The LDOx_SEQ fields also have to be taken over: a non-zero slot moves
that output from the LDOx_EN bit to the power-up sequencer, and writes
to the enable register then have no effect on it (datasheet WL2868C
Rev 1.4, "0x0A, 0x0B, 0x0C and 0x0D: LDOx_SEQ", p.35). The binding
describes no sequencing, so the driver clears the four slot registers
during probe and keeps the enable register as the only control path;
otherwise enable() and disable() would silently do nothing for an
output a slot had claimed. The reset value is 0 and no known board
programs it, but firmware runs before us and may.

Signed-off-by: Qianhui Xu <xiaqinghuan@mail.ustc.edu.cn>
---
 MAINTAINERS                           |   1 +
 drivers/regulator/Kconfig             |  11 +
 drivers/regulator/Makefile            |   1 +
 drivers/regulator/wl2868c-regulator.c | 301 ++++++++++++++++++++++++++
 4 files changed, 314 insertions(+)
 create mode 100644 drivers/regulator/wl2868c-regulator.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 435beaf95..5a3913c45 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29063,6 +29063,7 @@ M:	Qianhui Xu <xiaqinghuan@mail.ustc.edu.cn>
 L:	linux-kernel@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/regulator/willsemi,wl2868c.yaml
+F:	drivers/regulator/wl2868c-regulator.c
 
 WILOCITY WIL6210 WIRELESS DRIVER
 L:	linux-wireless@vger.kernel.org
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index a54a54919..79bdba077 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -1851,6 +1851,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 134eee274..662f31a14 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -211,6 +211,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 000000000..9ffb08dcb
--- /dev/null
+++ b/drivers/regulator/wl2868c-regulator.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Will Semiconductor WL2868C seven-channel camera regulator driver.
+ *
+ * Copyright (C) 2026 Qianhui Xu
+ */
+
+#include <linux/cleanup.h>
+#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
+/* 0x0a..0x0d: seven 4-bit LDOx_SEQ slots; 0 = driven by LDOx_EN in 0x0e. */
+#define WL2868C_LDO_SEQ		0x0a
+#define WL2868C_LDO_SEQ_LEN		4
+#define WL2868C_LDO_ENABLE		0x0e
+#define WL2868C_SYS_ENABLE		BIT(7)
+#define WL2868C_LDO_ENABLE_MASK		GENMASK(6, 0)
+
+/* RST_N must be released after the control supply is up, not before. */
+#define WL2868C_RESET_RELEASE_US	1000
+/* Time the device needs after RST_N is released before it answers on I2C. */
+#define WL2868C_STARTUP_US		1000
+
+struct wl2868c {
+	struct regmap *regmap;
+	/* LDO enable bits owned by consumers, in WL2868C_LDO_ENABLE layout. */
+	unsigned int enabled;
+	/* Protects enabled. */
+	struct mutex lock;
+};
+
+static int wl2868c_enable(struct regulator_dev *rdev)
+{
+	struct wl2868c *chip = rdev_get_drvdata(rdev);
+	unsigned int bits = rdev->desc->enable_mask;
+	unsigned int ldos;
+	int ret;
+
+	guard(mutex)(&chip->lock);
+
+	/*
+	 * The enable register is written as a whole on purpose. SYS_EN gates
+	 * every output of the chip, and the per-LDO bits may be preset by the
+	 * firmware while SYS_EN is still clear: setting only SYS_EN and our own
+	 * bit would then bring the preset LDOs up as well. Writing the register
+	 * with the bits of the LDOs consumers have enabled, and only those,
+	 * keeps the other outputs off. chip->enabled was seeded from the
+	 * register in probe() when SYS_EN was set, so an output the firmware
+	 * left on is carried over instead of being cleared by the first
+	 * enable; when SYS_EN was clear no output is on at all.
+	 */
+	ldos = (chip->enabled | bits) & WL2868C_LDO_ENABLE_MASK;
+	ret = regmap_write(chip->regmap, WL2868C_LDO_ENABLE,
+			   WL2868C_SYS_ENABLE | ldos);
+	if (ret)
+		return ret;
+
+	chip->enabled = ldos;
+
+	return 0;
+}
+
+static int wl2868c_disable(struct regulator_dev *rdev)
+{
+	struct wl2868c *chip = rdev_get_drvdata(rdev);
+	unsigned int bits = rdev->desc->enable_mask;
+	unsigned int ldos;
+	int ret;
+
+	guard(mutex)(&chip->lock);
+
+	/*
+	 * SYS_EN stays set while at least one output is enabled; the per-LDO
+	 * bits of the other outputs stay clear so that setting it cannot
+	 * switch anything else on.
+	 */
+	ldos = (chip->enabled & ~bits) & WL2868C_LDO_ENABLE_MASK;
+
+	/*
+	 * The tracked state is committed only once the register write went
+	 * through: a failed write leaves the output on, and the regulator core
+	 * keeps its own use count unchanged, so dropping the bit here would
+	 * make the next enable() of another output switch this one off.
+	 */
+	ret = regmap_write(chip->regmap, WL2868C_LDO_ENABLE,
+			   ldos ? WL2868C_SYS_ENABLE | ldos : 0);
+	if (ret)
+		return ret;
+
+	chip->enabled = ldos;
+
+	return 0;
+}
+
+static int wl2868c_is_enabled(struct regulator_dev *rdev)
+{
+	struct wl2868c *chip = rdev_get_drvdata(rdev);
+	unsigned int mask = WL2868C_SYS_ENABLE | rdev->desc->enable_mask;
+	unsigned int value;
+	int ret;
+
+	ret = regmap_read(chip->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 = wl2868c_disable,
+	.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_assert(void *data)
+{
+	gpiod_set_value_cansleep(data, 1);
+}
+
+/*
+ * VIN7 and VSYS both feed the control logic and the I2C interface, which run
+ * from the higher of the two. The binding requires at least one of them, and
+ * whichever is present has to stay up for as long as the driver is bound:
+ * without it the device does not answer on the bus at all.
+ */
+static int wl2868c_enable_control_supply(struct device *dev)
+{
+	int ret;
+
+	ret = devm_regulator_get_enable_optional(dev, "vsys");
+	if (ret != -ENODEV)
+		return ret;
+
+	ret = devm_regulator_get_enable_optional(dev, "vin7");
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable the control supply\n");
+
+	return 0;
+}
+
+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;
+	struct wl2868c *chip;
+	unsigned int id, value;
+	static const u8 no_seq[WL2868C_LDO_SEQ_LEN];
+	int ret, i;
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	mutex_init(&chip->lock);
+	config.driver_data = chip;
+
+	/*
+	 * RST_N starts asserted and is released only once the control supply is
+	 * up. The reset action is registered after the supply action on
+	 * purpose: devres unwinds in reverse order, so RST_N is asserted again
+	 * before the supply is disabled on unbind, not after.
+	 */
+	reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(reset))
+		return dev_err_probe(dev, PTR_ERR(reset), "Failed to get reset GPIO\n");
+
+	chip->regmap = devm_regmap_init_i2c(client, &wl2868c_regmap_config);
+	config.regmap = chip->regmap;
+	if (IS_ERR(config.regmap))
+		return dev_err_probe(dev, PTR_ERR(config.regmap),
+				     "Failed to create regmap\n");
+
+	ret = wl2868c_enable_control_supply(dev);
+	if (ret)
+		return ret;
+
+	if (reset) {
+		fsleep(WL2868C_RESET_RELEASE_US);
+		gpiod_set_value_cansleep(reset, 0);
+		ret = devm_add_action_or_reset(dev, wl2868c_reset_assert, reset);
+		if (ret)
+			return ret;
+		fsleep(WL2868C_STARTUP_US);
+	}
+
+	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);
+
+	/*
+	 * LDOx_SEQ[3:0] (0x0a..0x0d) assigns an output to one of the seven
+	 * power-up sequencer slots; any value but 0 moves that LDO from the
+	 * LDOx_EN bit in 0x0e to the sequencer, where writes to 0x0e have no
+	 * effect (datasheet WL2868C Rev 1.4, "0x0A, 0x0B, 0x0C and 0x0D:
+	 * LDOx_SEQ", p.35). The binding describes no sequencing, so clear the
+	 * slots: every output then stays under the enable register this driver
+	 * owns. The reset value is 0 and no board is known to program it, but
+	 * firmware can, and a stale slot would make enable() silently inert.
+	 * Outputs a slot had turned on are switched off here; no consumer is
+	 * up yet at probe time.
+	 */
+	ret = regmap_bulk_write(config.regmap, WL2868C_LDO_SEQ, no_seq,
+				WL2868C_LDO_SEQ_LEN);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to clear the sequencer slots\n");
+
+	/*
+	 * Seed the tracked set with the outputs the firmware left enabled, so
+	 * that the first enable() or disable() of any output preserves them.
+	 * SYS_EN gates the whole chip: while it is clear no output is up, the
+	 * per-LDO bits are only presets and is_enabled() reports every output
+	 * as off. Seeding them in that state would make the first enable() of
+	 * any single output switch all the presets on.
+	 */
+	ret = regmap_read(config.regmap, WL2868C_LDO_ENABLE, &value);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to read the enable register\n");
+
+	if (value & WL2868C_SYS_ENABLE)
+		chip->enabled = value & WL2868C_LDO_ENABLE_MASK;
+
+	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


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-11-13  8:19 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-13  8:19 [PATCH v11 0/2] regulator: add the Will Semiconductor WL2868C camera PMIC Qianhui Xu
2026-11-13  8:19 ` [PATCH v11 1/2] regulator: dt-bindings: willsemi,wl2868c: add the " Qianhui Xu
2026-11-13  8:19 ` [PATCH v11 2/2] regulator: willsemi,wl2868c: add the WL2868C camera PMIC driver Qianhui Xu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®