[PATCH] mfd: lm8502: add core MFD driver for TI LM8502

Herman van Hazendonk posted 1 patch 5 days, 1 hour ago
There is a newer version of this series
MAINTAINERS                |   9 +
drivers/mfd/Kconfig        |  18 ++
drivers/mfd/Makefile       |   1 +
drivers/mfd/lm8502.c       | 511 +++++++++++++++++++++++++++++++++++++
include/linux/mfd/lm8502.h | 130 ++++++++++
5 files changed, 669 insertions(+)
create mode 100644 drivers/mfd/lm8502.c
create mode 100644 include/linux/mfd/lm8502.h
[PATCH] mfd: lm8502: add core MFD driver for TI LM8502
Posted by Herman van Hazendonk 5 days, 1 hour ago
The TI LM8502 is an I2C-attached combo device with ten constant-
current LED outputs (D1..D10) and an internal H-bridge that drives a
vibrator motor. Pin D10 is shared between the tenth LED channel and
the haptic output; the haptic child driver mux's D10 to the H-bridge
path at each FF_RUMBLE start.

This commit adds the MFD core: i2c probe, regmap, optional vcc
regulator (with High Power Mode handshake for RPM-managed LDOs that
carry "regulator-allow-set-load"), optional chip-enable GPIO, software
reset, and the post-reset configuration sequence. Chip readiness is
verified inside probe using an LP55xx-style write-CHIP_EN-then-
readback loop bounded at 250 ms, so children only probe once the chip
is known to be ACKing the bus.

Two children are spawned via devm_mfd_add_devices():

  ti,lm8502-leds   - LED-class outputs D1..D10
  ti,lm8502-haptic - EV_FF / FF_RUMBLE input device

The corresponding child drivers land in subsequent patches.

Also include MAINTAINERS entry covering all four driver files plus
the DT binding from the preceding patch.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 MAINTAINERS                |   9 +
 drivers/mfd/Kconfig        |  18 ++
 drivers/mfd/Makefile       |   1 +
 drivers/mfd/lm8502.c       | 511 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/lm8502.h | 130 ++++++++++
 5 files changed, 669 insertions(+)
 create mode 100644 drivers/mfd/lm8502.c
 create mode 100644 include/linux/mfd/lm8502.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..3d83c03ff7ed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26770,6 +26770,15 @@ S:	Maintained
 F:	sound/soc/codecs/isabelle*
 F:	sound/soc/codecs/lm49453*
 
+TI LM8502 MFD DRIVER (LED + HAPTIC)
+M:	Herman van Hazendonk <github.com@herrie.org>
+S:	Maintained
+F:	Documentation/devicetree/bindings/mfd/ti,lm8502.yaml
+F:	drivers/input/misc/lm8502-haptic.c
+F:	drivers/leds/leds-lm8502.c
+F:	drivers/mfd/lm8502.c
+F:	include/linux/mfd/lm8502.h
+
 TI LMP92064 ADC DRIVER
 M:	Leonard Göhrs <l.goehrs@pengutronix.de>
 R:	kernel@pengutronix.de
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 7192c9d1d268..93018e5d2bd0 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2027,6 +2027,24 @@ config MFD_LM3533
 	  additional drivers must be enabled in order to use the LED,
 	  backlight or ambient-light-sensor functionality of the device.
 
+config MFD_LM8502
+	tristate "TI LM8502 combo LED + haptic controller"
+	depends on I2C && OF
+	select MFD_CORE
+	select REGMAP_I2C
+	help
+	  Say yes here to enable support for the TI LM8502 combo LED and
+	  haptic-feedback controller. The chip is used in the HP TouchPad
+	  tablet (Topaz and Opal).
+
+	  This driver provides the chip-wide MFD core (i2c probe, regmap,
+	  regulator High Power Mode handshake, enable GPIO, reset and
+	  configuration sequence, and PM suspend/resume). Per-subsystem
+	  drivers must be enabled separately:
+
+	    LEDS_LM8502         - LED-class outputs D1..D10
+	    INPUT_LM8502_HAPTIC - FF_RUMBLE vibrator on the internal H-bridge
+
 config MFD_TIMBERDALE
 	tristate "Timberdale FPGA"
 	select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e75e8045c28a..6d23c4dd6870 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -241,6 +241,7 @@ obj-$(CONFIG_MFD_SEC_ACPM)	+= sec-acpm.o
 obj-$(CONFIG_MFD_SEC_I2C)	+= sec-i2c.o
 obj-$(CONFIG_MFD_SYSCON)	+= syscon.o
 obj-$(CONFIG_MFD_LM3533)	+= lm3533-core.o lm3533-ctrlbank.o
+obj-$(CONFIG_MFD_LM8502)	+= lm8502.o
 obj-$(CONFIG_MFD_VEXPRESS_SYSREG)	+= vexpress-sysreg.o
 obj-$(CONFIG_MFD_RETU)		+= retu-mfd.o
 obj-$(CONFIG_MFD_AS3711)	+= as3711.o
diff --git a/drivers/mfd/lm8502.c b/drivers/mfd/lm8502.c
new file mode 100644
index 000000000000..4562f05b12bc
--- /dev/null
+++ b/drivers/mfd/lm8502.c
@@ -0,0 +1,511 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * TI LM8502 Multi-Function Device core
+ *
+ * Copyright (C) 2008 Palm Inc.
+ * Copyright (C) 2024 Christophe Chapuis <chris.music.music@gmail.com>
+ * Copyright (C) 2024-2026 Herman van Hazendonk <github.com@herrie.org>
+ *
+ * The LM8502 is a combo LED + haptic controller on a single I2C address
+ * (0x33 on the HP TouchPad). This MFD core handles everything chip-wide:
+ *
+ *   - the i2c_client and regmap
+ *   - the chip-enable GPIO (a TLMM line on tenderloin)
+ *   - the vcc regulator and the High Power Mode (HPM) handshake with
+ *     RPM-managed PMIC LDOs (PM8058_L16 on tenderloin)
+ *   - the post-reset chip configuration sequence (software reset,
+ *     ENGINE_CNTRL1/2, MISC) with explicit write-then-readback so
+ *     I2C controllers that do not surface NAKs (notably MSM8x60 QUP
+ *     in the immediate post-power-up window) fail fast in probe
+ *     rather than silently leaving the chip unconfigured
+ *   - PM suspend/resume of the whole chip via the CHIP_EN bit
+ *
+ * Per-subsystem code lives in child drivers:
+ *
+ *   drivers/leds/leds-lm8502.c         - LED class for outputs D1..D10
+ *   drivers/input/misc/lm8502-haptic.c - EV_FF / FF_RUMBLE for the
+ *                                        internal H-bridge vibrator
+ *
+ * Both children are spawned via devm_mfd_add_devices() with explicit
+ * .of_compatible matches so their DT subnodes drive per-subsystem
+ * configuration (the LED children, ti,invert-direction, etc).
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/lm8502.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+/* Maximum time to wait for the chip to ACK reads/writes after power-up. */
+#define LM8502_DETECT_TIMEOUT_MS	250
+#define LM8502_DETECT_POLL_MS		5
+
+/* Time the chip needs after RESET=0xFF before responding again. */
+#define LM8502_RESET_SETTLE_MS		50
+
+/*
+ * After raising enable_gpio the chip's internal reset deasserts and
+ * the I2C front-end becomes responsive; datasheet calls out ~1 ms.
+ */
+#define LM8502_ENABLE_SETTLE_US		1000
+
+/*
+ * regulator_set_load() on RPM-managed PMIC LDOs (PM8058_L16) only
+ * sends an IPC; the rail change is asynchronous. 5 ms is the worst-
+ * case RPM commit window measured on MSM8x60 silicon -- no polling
+ * API exposes commit completion.
+ */
+#define LM8502_RPM_HPM_SETTLE_US	5000
+
+/* Driver-private wrapper around the shared struct lm8502. */
+struct lm8502_core {
+	struct lm8502 chip;		/* shared with children via parent drvdata */
+
+	/* Resources only the core touches: */
+	struct regulator *vcc;
+	struct gpio_desc *enable_gpio;
+};
+
+static bool lm8502_volatile_reg(struct device *dev, unsigned int reg)
+{
+	return true;			/* every read goes to the bus */
+}
+
+static const struct regmap_config lm8502_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0xF0,
+	.volatile_reg = lm8502_volatile_reg,
+	.cache_type = REGCACHE_NONE,
+};
+
+/* MFD children spawned by this core. */
+static const struct mfd_cell lm8502_devs[] = {
+	{
+		.name = "lm8502-leds",
+		.of_compatible = "ti,lm8502-leds",
+	},
+	{
+		.name = "lm8502-haptic",
+		.of_compatible = "ti,lm8502-haptic",
+	},
+};
+
+/*
+ * Bring the chip out of reset and verify it is ACKing the bus.
+ *
+ * The post-power-up window is tricky on MSM8x60 QUP: writes complete
+ * without -ENXIO even when the chip is not actually responding, so a
+ * blind probe-time init silently writes into the void. We use the
+ * LP55xx-family pattern: write CHIP_EN, read it back, retry until the
+ * read matches or we time out.
+ */
+static int lm8502_wait_for_chip(struct lm8502 *chip)
+{
+	unsigned long deadline = jiffies +
+				 msecs_to_jiffies(LM8502_DETECT_TIMEOUT_MS);
+	unsigned int val;
+	int ret;
+
+	/*
+	 * Use a wall-clock deadline rather than counting only sleep time:
+	 * regmap_{write,read}() round trips and any NAK/timeout cost on a
+	 * slow I2C bus can otherwise extend the wall-clock probe time
+	 * beyond LM8502_DETECT_TIMEOUT_MS without the loop noticing.
+	 */
+	do {
+		ret = regmap_write(chip->regmap, LM8502_ENGINE_CNTRL1,
+				   LM8502_CHIP_EN);
+		if (!ret) {
+			ret = regmap_read(chip->regmap, LM8502_ENGINE_CNTRL1,
+					  &val);
+			if (ret == 0 && (val & LM8502_CHIP_EN))
+				return 0;
+		}
+
+		fsleep(LM8502_DETECT_POLL_MS * USEC_PER_MSEC);
+	} while (time_before(jiffies, deadline));
+
+	return -ENODEV;
+}
+
+/*
+ * Bring the chip up: software reset, prove the bus, then program the
+ * baseline ENGINE_CNTRL2 / MISC / per-channel D<n>_CONTROL registers.
+ *
+ * Errors are reported with plain dev_err() rather than dev_err_probe()
+ * because this is also called from the resume path -- using
+ * dev_err_probe() there would pollute the deferred-probe last-failure
+ * tracker for a device that is already bound. Probe callers wrap the
+ * return with dev_err_probe() themselves.
+ */
+static int lm8502_chip_init(struct lm8502_core *core)
+{
+	struct lm8502 *chip = &core->chip;
+	struct device *dev = chip->dev;
+	int ret, i;
+
+	/*
+	 * Software reset. Writes through regmap; the chip may briefly
+	 * NAK reads in this window so we do not bother checking the
+	 * return value -- lm8502_wait_for_chip() proves bidirectional
+	 * communication below.
+	 */
+	regmap_write(chip->regmap, LM8502_RESET, LM8502_RESET_MAGIC);
+	fsleep(LM8502_RESET_SETTLE_MS * USEC_PER_MSEC);
+
+	ret = lm8502_wait_for_chip(chip);
+	if (ret) {
+		dev_err(dev, "chip did not respond: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(chip->regmap, LM8502_ENGINE_CNTRL2,
+			   LM8502_ENGINE_CNTRL2_INIT);
+	if (ret) {
+		dev_err(dev, "ENGINE_CNTRL2 write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(chip->regmap, LM8502_MISC, LM8502_MISC_INIT);
+	if (ret) {
+		dev_err(dev, "MISC write failed: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Program every D<n>_CONTROL register so the LED child can issue
+	 * brightness writes to D<n>_CURRENT_CTRL immediately. Without
+	 * this the channels stay at the post-reset default and ignore
+	 * current writes until the user manually configures them.
+	 */
+	for (i = 0; i < LM8502_MAX_LEDS; i++) {
+		ret = regmap_write(chip->regmap, LM8502_D1_CONTROL + i,
+				   LM8502_LED_CONTROL_INIT);
+		if (ret) {
+			dev_err(dev, "D%d_CONTROL write failed: %d\n",
+				i + 1, ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * devm-registered cleanup callback. Runs at unbind / probe-failure
+ * time AFTER the MFD child platform devices spawned by
+ * devm_mfd_add_devices() have been torn down (LIFO devres ordering --
+ * see lm8502_i2c_probe for the order in which actions and child
+ * devices are registered). This is what makes the children's remove
+ * callbacks issue their final regmap writes against a still-powered
+ * chip rather than a clamped/disabled one.
+ */
+static void lm8502_power_off(void *data)
+{
+	struct lm8502_core *core = data;
+
+	if (core->enable_gpio)
+		gpiod_set_value_cansleep(core->enable_gpio, 0);
+
+	if (core->vcc) {
+		regulator_set_load(core->vcc, 0);
+		regulator_disable(core->vcc);
+	}
+}
+
+static int lm8502_i2c_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct lm8502_core *core;
+	struct lm8502 *chip;
+	int ret;
+
+	core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
+	if (!core)
+		return -ENOMEM;
+
+	chip = &core->chip;
+	chip->dev = dev;
+	mutex_init(&chip->lock);
+
+	chip->regmap = devm_regmap_init_i2c(client, &lm8502_regmap_config);
+	if (IS_ERR(chip->regmap))
+		return dev_err_probe(dev, PTR_ERR(chip->regmap),
+				     "failed to allocate regmap\n");
+
+	/*
+	 * Optional vcc regulator. On the HP TouchPad this is PM8058_L16,
+	 * a 1.8 V always-on RPM-managed LDO with "regulator-allow-set-load"
+	 * so we can ask for High Power Mode below.
+	 */
+	core->vcc = devm_regulator_get_optional(dev, "vcc");
+	if (IS_ERR(core->vcc)) {
+		ret = PTR_ERR(core->vcc);
+		if (ret == -ENODEV) {
+			core->vcc = NULL;
+		} else {
+			return dev_err_probe(dev, ret,
+					     "failed to get vcc supply\n");
+		}
+	}
+
+	if (core->vcc) {
+		ret = regulator_enable(core->vcc);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "failed to enable vcc supply\n");
+
+		/*
+		 * Ask for High Power Mode. The LM8502 internal boost converter
+		 * plus all ten LED outputs at up to 9 mA each draw ~100 mA
+		 * worst case. On RPM-managed LDOs (PM8058_L16) the default Low
+		 * Power Mode caps the regulator at ~1 mA, leaving the chip
+		 * current-starved.
+		 *
+		 * regulator_set_load() is a no-op on consumers whose supply
+		 * does not carry "regulator-allow-set-load", so this is safe
+		 * on platforms with simpler regulator topology.
+		 */
+		ret = regulator_set_load(core->vcc, 100000);
+		if (ret) {
+			dev_err_probe(dev, ret,
+				      "regulator_set_load(100mA) failed\n");
+			goto err_disable_vcc;
+		}
+
+		fsleep(LM8502_RPM_HPM_SETTLE_US);
+	}
+
+	core->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+						    GPIOD_OUT_LOW);
+	if (IS_ERR(core->enable_gpio)) {
+		ret = PTR_ERR(core->enable_gpio);
+		goto err_disable_vcc;
+	}
+
+	if (core->enable_gpio) {
+		gpiod_set_value_cansleep(core->enable_gpio, 1);
+		fsleep(LM8502_ENABLE_SETTLE_US);
+	}
+
+	dev_set_drvdata(dev, chip);
+
+	ret = lm8502_chip_init(core);
+	if (ret) {
+		dev_err_probe(dev, ret, "chip init failed\n");
+		goto err_disable_chip;
+	}
+
+	/*
+	 * Register the power-off callback BEFORE devm_mfd_add_devices() so
+	 * devres unwinds LIFO: child platform devices teardown FIRST (their
+	 * .remove callbacks issue final regmap writes against a still-
+	 * powered chip), and lm8502_power_off() runs SECOND. Without this
+	 * ordering, lm8502_i2c_remove() / err_disable_chip would cut
+	 * CHIP_EN and the vcc rail before the children's cleanup writes
+	 * landed -- on MSM8x60 QUP the controller does not surface NAKs in
+	 * that window (see lm8502_wait_for_chip), so the writes are either
+	 * silently dropped or fail with -EREMOTEIO and the haptic motor
+	 * can be left energised.
+	 */
+	ret = devm_add_action_or_reset(dev, lm8502_power_off, core);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to register power-off action\n");
+
+	/*
+	 * Spawn LED and haptic child platform devices. Children retrieve
+	 * the shared struct lm8502 via dev_get_drvdata(pdev->dev.parent);
+	 * since chip_init above already completed, children can issue
+	 * register writes from their own probe paths immediately.
+	 */
+	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, lm8502_devs,
+				   ARRAY_SIZE(lm8502_devs), NULL, 0, NULL);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to add child devices\n");
+
+	return 0;
+
+	/*
+	 * Failure paths below run BEFORE lm8502_power_off() is registered
+	 * via devm, so they must still tear the rail down manually.
+	 */
+err_disable_chip:
+	if (core->enable_gpio)
+		gpiod_set_value_cansleep(core->enable_gpio, 0);
+err_disable_vcc:
+	if (core->vcc) {
+		regulator_set_load(core->vcc, 0);
+		regulator_disable(core->vcc);
+	}
+	return ret;
+}
+
+static int lm8502_suspend(struct device *dev)
+{
+	struct lm8502 *chip = dev_get_drvdata(dev);
+	struct lm8502_core *core = container_of(chip, struct lm8502_core, chip);
+	int ret;
+
+	mutex_lock(&chip->lock);
+	if (chip->suspended) {
+		mutex_unlock(&chip->lock);
+		return 0;
+	}
+
+	/*
+	 * Mark the chip as suspended UP FRONT, before any register write
+	 * that can fail. The semantic is "system PM committed to suspend
+	 * us"; even if a midway regmap write returns -ENXIO and the rest
+	 * of system suspend continues without us, the resume path must
+	 * still run the full re-init sequence because the rails / GPIO
+	 * may have been power-cycled across S3. Without this, a single
+	 * I2C error during suspend would leave chip->suspended == false,
+	 * lm8502_resume() would early-out, and the children's first
+	 * register access after resume would hit an unconfigured chip.
+	 */
+	chip->suspended = true;
+
+	/* Stop the haptic output in case a child left it running. */
+	ret = regmap_write(chip->regmap, LM8502_HAPTIC_FEEDBACK_CTRL, 0);
+	if (ret) {
+		dev_err(dev, "suspend: HAPTIC_FEEDBACK_CTRL write failed: %d\n",
+			ret);
+		goto unlock;
+	}
+
+	/* Drop CHIP_EN to power-gate the analog side. */
+	ret = regmap_update_bits(chip->regmap, LM8502_ENGINE_CNTRL1,
+				 LM8502_CHIP_EN, 0);
+	if (ret) {
+		dev_err(dev, "suspend: CHIP_EN clear failed: %d\n", ret);
+		goto unlock;
+	}
+
+	if (core->enable_gpio)
+		gpiod_set_value_cansleep(core->enable_gpio, 0);
+
+	/*
+	 * Drop the regulator to Low Power Mode now that the chip is
+	 * off; HPM is only needed while the analog stage draws ~100 mA,
+	 * and leaving it set wastes power across system suspend on
+	 * RPM-managed PMIC LDOs. Restored in resume.
+	 */
+	if (core->vcc)
+		regulator_set_load(core->vcc, 0);
+
+	mutex_unlock(&chip->lock);
+
+	return 0;
+
+unlock:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static int lm8502_resume(struct device *dev)
+{
+	struct lm8502 *chip = dev_get_drvdata(dev);
+	struct lm8502_core *core = container_of(chip, struct lm8502_core, chip);
+	int ret;
+
+	mutex_lock(&chip->lock);
+	if (!chip->suspended) {
+		mutex_unlock(&chip->lock);
+		return 0;
+	}
+
+	/*
+	 * Restore High Power Mode so the regulator can supply the
+	 * ~100 mA the analog stage draws under load. Symmetric with the
+	 * drop in suspend; safe no-op on consumers without
+	 * regulator-allow-set-load.
+	 */
+	if (core->vcc) {
+		ret = regulator_set_load(core->vcc, 100000);
+		if (ret) {
+			dev_err(dev, "resume: regulator_set_load(HPM) failed: %d\n",
+				ret);
+			goto unlock;
+		}
+		fsleep(LM8502_RPM_HPM_SETTLE_US);
+	}
+
+	if (core->enable_gpio) {
+		gpiod_set_value_cansleep(core->enable_gpio, 1);
+		fsleep(LM8502_ENABLE_SETTLE_US);
+	}
+
+	/*
+	 * Suspend de-asserts the enable_gpio, which on most boards power-
+	 * gates the chip and clears its register state. Re-run the full
+	 * init sequence so ENGINE_CNTRL1/2, MISC and the per-channel
+	 * D<n>_CONTROL registers are restored before any child driver
+	 * touches the chip. lm8502_chip_init() also sets CHIP_EN as a
+	 * side-effect of programming ENGINE_CNTRL1. Errors are already
+	 * logged with dev_err() inside lm8502_chip_init().
+	 */
+	ret = lm8502_chip_init(core);
+	if (ret)
+		goto err_disable;
+
+	chip->suspended = false;
+	mutex_unlock(&chip->lock);
+
+	return 0;
+
+err_disable:
+	/*
+	 * chip_init failed; we already raised enable_gpio and committed
+	 * HPM. Undo what resume did: de-assert enable_gpio and drop
+	 * the regulator back to Low Power Mode. The vcc regulator is
+	 * intentionally NOT disabled -- it was never disabled during
+	 * the preceding suspend either, so the device returns to the
+	 * same suspended state it was in before resume started rather
+	 * than a fully-off state.
+	 */
+	if (core->enable_gpio)
+		gpiod_set_value_cansleep(core->enable_gpio, 0);
+	if (core->vcc)
+		regulator_set_load(core->vcc, 0);
+unlock:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(lm8502_pm_ops, lm8502_suspend, lm8502_resume);
+
+static const struct of_device_id lm8502_of_match[] = {
+	{ .compatible = "ti,lm8502" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, lm8502_of_match);
+
+static const struct i2c_device_id lm8502_i2c_id[] = {
+	{ "lm8502" },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, lm8502_i2c_id);
+
+static struct i2c_driver lm8502_i2c_driver = {
+	.driver = {
+		.name = "lm8502",
+		.of_match_table = lm8502_of_match,
+		.pm = pm_sleep_ptr(&lm8502_pm_ops),
+	},
+	.probe = lm8502_i2c_probe,
+	.id_table = lm8502_i2c_id,
+};
+module_i2c_driver(lm8502_i2c_driver);
+
+MODULE_DESCRIPTION("TI LM8502 combo LED + haptic controller (MFD core)");
+MODULE_AUTHOR("Herman van Hazendonk <github.com@herrie.org>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/lm8502.h b/include/linux/mfd/lm8502.h
new file mode 100644
index 000000000000..9c7c90cdd273
--- /dev/null
+++ b/include/linux/mfd/lm8502.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * TI LM8502 Multi-Function Device
+ *
+ * The LM8502 is a combo LED + haptic controller exposed over I2C. The
+ * MFD core handles the i2c_client, regmap, regulator and chip-wide
+ * initialization; per-subsystem child platform drivers in drivers/leds/
+ * and drivers/input/misc/ bind to the spawned children and use the
+ * parent's regmap.
+ *
+ * Children retrieve the parent's lm8502 state via
+ *     struct lm8502 *chip = dev_get_drvdata(pdev->dev.parent);
+ * and serialise register access on chip->lock.
+ */
+#ifndef __LINUX_MFD_LM8502_H
+#define __LINUX_MFD_LM8502_H
+
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+/* --- Register map --------------------------------------------------- */
+
+#define LM8502_ENGINE_CNTRL1		0x00
+#define LM8502_ENGINE_CNTRL2		0x01
+#define LM8502_GROUP_FADING1		0x02
+#define LM8502_GROUP_FADING2		0x03
+#define LM8502_GROUP_FADING3		0x04
+
+#define LM8502_D1_CONTROL		0x06
+#define LM8502_D2_CONTROL		0x07
+#define LM8502_D3_CONTROL		0x08
+#define LM8502_D4_CONTROL		0x09
+#define LM8502_D5_CONTROL		0x0A
+#define LM8502_D6_CONTROL		0x0B
+#define LM8502_D7_CONTROL		0x0C
+#define LM8502_D8_CONTROL		0x0D
+#define LM8502_D9_CONTROL		0x0E
+#define LM8502_D10_CONTROL		0x0F
+
+#define LM8502_HAPTIC_CONTROL		0x10
+#define LM8502_HAPTIC_FEEDBACK_CTRL	0x21
+#define LM8502_HAPTIC_PWM_DUTY_CYCLE	0x22
+
+#define LM8502_D1_CURRENT_CTRL		0x26
+#define LM8502_D2_CURRENT_CTRL		0x27
+#define LM8502_D3_CURRENT_CTRL		0x28
+#define LM8502_D4_CURRENT_CTRL		0x29
+#define LM8502_D5_CURRENT_CTRL		0x2A
+#define LM8502_D6_CURRENT_CTRL		0x2B
+#define LM8502_D7_CURRENT_CTRL		0x2C
+#define LM8502_D8_CURRENT_CTRL		0x2D
+#define LM8502_D9_CURRENT_CTRL		0x2E
+#define LM8502_D10_CURRENT_CTRL		0x2F
+
+#define LM8502_MISC			0x36
+#define LM8502_ENGINE1_PC		0x37
+#define LM8502_ENGINE2_PC		0x38
+#define LM8502_STATUS			0x3A
+#define LM8502_INT			0x3B
+#define LM8502_RESET			0x3D
+
+/* ENGINE_CNTRL1 bits */
+#define LM8502_CHIP_EN			BIT(6)
+
+/* ENGINE_CNTRL2 — value applied at chip_init: charge-pump CP_MODE = 1x.
+ * Bit 5 selects the boost-converter compare mode; the value the legacy
+ * legacy vendor driver writes matches the LP55xx-family ENGINE_CNTRL2 "all
+ * engines in HOLD" default plus CP_MODE_1X.
+ */
+#define LM8502_ENGINE_CNTRL2_INIT	0x20
+
+/* MISC (0x36) bits applied at chip_init */
+#define LM8502_MISC_PWM_INPUT		BIT(1)	/* PWM input mux on D10 */
+/* BOOST_EN: internal boost converter -- REQUIRED for LED current. */
+#define LM8502_MISC_BOOST_EN		BIT(3)
+#define LM8502_MISC_POWER_SAVE		BIT(5)	/* clock gate idle blocks */
+#define LM8502_MISC_INIT		(LM8502_MISC_POWER_SAVE | \
+					 LM8502_MISC_BOOST_EN | \
+					 LM8502_MISC_PWM_INPUT)
+
+/* D<n>_CONTROL bits */
+#define LM8502_LED_MAPPING_MASK		0x03
+#define LM8502_LED_MAPPING_DIRECT	0x00	/* drive from current reg */
+#define LM8502_LED_MAX_CURRENT_MASK	0x18
+#define LM8502_LED_MAX_CURRENT_SHIFT	3
+#define LM8502_LED_MAX_CURRENT_9MA	0x02	/* field = 2 -> 9 mA cap */
+/*
+ * Initial D<n>_CONTROL value: MAX_CURRENT field = 2 (9 mA), DIRECT
+ * mapping. Programmed for every channel during chip_init so a child
+ * brightness write to D<n>_CURRENT_CTRL is honoured immediately.
+ */
+#define LM8502_LED_CONTROL_INIT		(LM8502_LED_MAX_CURRENT_9MA << \
+					 LM8502_LED_MAX_CURRENT_SHIFT)
+
+/* HAPTIC_FEEDBACK_CTRL bits */
+#define LM8502_HAPTIC_FB_INVERT_DIR	BIT(0)	/* H-bridge polarity flip */
+#define LM8502_HAPTIC_FB_ENABLE		BIT(1)	/* drive the motor */
+
+#define LM8502_MAX_LEDS			10
+
+/* RESET (0x3D) magic write value (LP55xx-family software reset). */
+#define LM8502_RESET_MAGIC		0xFF
+
+/* --- Shared parent state -------------------------------------------- */
+
+/**
+ * struct lm8502 - parent (MFD core) state shared with child drivers
+ * @dev:        the I2C client's struct device (parent of every child pdev)
+ * @regmap:     the chip's 8-bit register map (no cache; every read goes
+ *              to the bus)
+ * @lock:       serialises register sequences across children and against
+ *              the parent's PM callbacks
+ * @suspended:  set true while the chip is power-gated by parent suspend;
+ *              children must check it under @lock before touching
+ *              registers
+ *
+ * Chip readiness is implicit: child platform devices only probe after
+ * the MFD parent's probe has completed lm8502_chip_init() successfully,
+ * so by the time a child runs the chip is fully configured and ACKing
+ * I2C. There is no separate "initialized" flag.
+ */
+struct lm8502 {
+	struct device *dev;
+	struct regmap *regmap;
+	struct mutex lock;
+	bool suspended;
+};
+
+#endif /* __LINUX_MFD_LM8502_H */

base-commit: 944125b4c454b58d2fe6e35f1087a932b2050dff
-- 
2.43.0

[PATCH v2 0/1] mfd: lm8502: add core MFD driver for TI LM8502
Posted by Herman van Hazendonk 5 days, 1 hour ago
Apologies for the noise — v1 (Message-ID:
<20260603035411.396383-1-github.com@herrie.org>) was sent as a bare
[PATCH] without a cover letter. This v2 resends with the correct
format. No code changes relative to v1.

---

The TI LM8502 is an I2C-attached combo device combining ten constant-
current LED outputs (D1..D10) with an internal H-bridge for driving a
vibrator motor. Pin D10 is shared between the tenth LED channel and
the haptic output.

This patch adds the MFD core driver:

  - i2c probe with optional vcc regulator (High Power Mode handshake
    for RPM-managed LDOs carrying "regulator-allow-set-load") and
    optional chip-enable GPIO
  - Software reset and post-reset configuration sequence
  - Chip readiness verification via an LP55xx-style write-CHIP_EN-
    then-readback loop (250 ms bound); children only probe once the
    chip is known to be ACKing the bus
  - Devres-ordered teardown: lm8502_power_off() registered via
    devm_add_action_or_reset() *before* devm_mfd_add_devices() so
    devres LIFO unwinds children first (their .remove callbacks issue
    final regmap writes against a still-powered chip), then powers
    off the rail
  - MAINTAINERS entry covering all four driver files

Two children are spawned:

  ti,lm8502-leds   - LED-class outputs D1..D10
  ti,lm8502-haptic - EV_FF / FF_RUMBLE input device

The corresponding child drivers land in subsequent patches.

The DT binding for this device was submitted separately:
  Subject: [PATCH] dt-bindings: mfd: add ti,lm8502 combo LED + haptic controller

Changes since v1:
  - Resend with cover letter; no code changes

Herman van Hazendonk (1):
  mfd: lm8502: add core MFD driver for TI LM8502

 MAINTAINERS                |   9 +
 drivers/mfd/Kconfig        |  18 ++
 drivers/mfd/Makefile       |   1 +
 drivers/mfd/lm8502.c       | 511 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/lm8502.h | 130 ++++++++++
 5 files changed, 669 insertions(+)
 create mode 100644 drivers/mfd/lm8502.c
 create mode 100644 include/linux/mfd/lm8502.h


base-commit: 944125b4c454b58d2fe6e35f1087a932b2050dff
--
2.43.0

[PATCH v2 1/1] mfd: lm8502: add core MFD driver for TI LM8502
Posted by Herman van Hazendonk 5 days, 1 hour ago
The TI LM8502 is an I2C-attached combo device with ten constant-
current LED outputs (D1..D10) and an internal H-bridge that drives a
vibrator motor. Pin D10 is shared between the tenth LED channel and
the haptic output; the haptic child driver mux's D10 to the H-bridge
path at each FF_RUMBLE start.

This commit adds the MFD core: i2c probe, regmap, optional vcc
regulator (with High Power Mode handshake for RPM-managed LDOs that
carry "regulator-allow-set-load"), optional chip-enable GPIO, software
reset, and the post-reset configuration sequence. Chip readiness is
verified inside probe using an LP55xx-style write-CHIP_EN-then-
readback loop bounded at 250 ms, so children only probe once the chip
is known to be ACKing the bus.

Two children are spawned via devm_mfd_add_devices():

  ti,lm8502-leds   - LED-class outputs D1..D10
  ti,lm8502-haptic - EV_FF / FF_RUMBLE input device

The corresponding child drivers land in subsequent patches.

Also include MAINTAINERS entry covering all four driver files plus
the DT binding from the preceding patch.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 MAINTAINERS                |   9 +
 drivers/mfd/Kconfig        |  18 ++
 drivers/mfd/Makefile       |   1 +
 drivers/mfd/lm8502.c       | 511 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/lm8502.h | 130 ++++++++++
 5 files changed, 669 insertions(+)
 create mode 100644 drivers/mfd/lm8502.c
 create mode 100644 include/linux/mfd/lm8502.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..3d83c03ff7ed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26770,6 +26770,15 @@ S:	Maintained
 F:	sound/soc/codecs/isabelle*
 F:	sound/soc/codecs/lm49453*
 
+TI LM8502 MFD DRIVER (LED + HAPTIC)
+M:	Herman van Hazendonk <github.com@herrie.org>
+S:	Maintained
+F:	Documentation/devicetree/bindings/mfd/ti,lm8502.yaml
+F:	drivers/input/misc/lm8502-haptic.c
+F:	drivers/leds/leds-lm8502.c
+F:	drivers/mfd/lm8502.c
+F:	include/linux/mfd/lm8502.h
+
 TI LMP92064 ADC DRIVER
 M:	Leonard Göhrs <l.goehrs@pengutronix.de>
 R:	kernel@pengutronix.de
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 7192c9d1d268..93018e5d2bd0 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2027,6 +2027,24 @@ config MFD_LM3533
 	  additional drivers must be enabled in order to use the LED,
 	  backlight or ambient-light-sensor functionality of the device.
 
+config MFD_LM8502
+	tristate "TI LM8502 combo LED + haptic controller"
+	depends on I2C && OF
+	select MFD_CORE
+	select REGMAP_I2C
+	help
+	  Say yes here to enable support for the TI LM8502 combo LED and
+	  haptic-feedback controller. The chip is used in the HP TouchPad
+	  tablet (Topaz and Opal).
+
+	  This driver provides the chip-wide MFD core (i2c probe, regmap,
+	  regulator High Power Mode handshake, enable GPIO, reset and
+	  configuration sequence, and PM suspend/resume). Per-subsystem
+	  drivers must be enabled separately:
+
+	    LEDS_LM8502         - LED-class outputs D1..D10
+	    INPUT_LM8502_HAPTIC - FF_RUMBLE vibrator on the internal H-bridge
+
 config MFD_TIMBERDALE
 	tristate "Timberdale FPGA"
 	select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e75e8045c28a..6d23c4dd6870 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -241,6 +241,7 @@ obj-$(CONFIG_MFD_SEC_ACPM)	+= sec-acpm.o
 obj-$(CONFIG_MFD_SEC_I2C)	+= sec-i2c.o
 obj-$(CONFIG_MFD_SYSCON)	+= syscon.o
 obj-$(CONFIG_MFD_LM3533)	+= lm3533-core.o lm3533-ctrlbank.o
+obj-$(CONFIG_MFD_LM8502)	+= lm8502.o
 obj-$(CONFIG_MFD_VEXPRESS_SYSREG)	+= vexpress-sysreg.o
 obj-$(CONFIG_MFD_RETU)		+= retu-mfd.o
 obj-$(CONFIG_MFD_AS3711)	+= as3711.o
diff --git a/drivers/mfd/lm8502.c b/drivers/mfd/lm8502.c
new file mode 100644
index 000000000000..4562f05b12bc
--- /dev/null
+++ b/drivers/mfd/lm8502.c
@@ -0,0 +1,511 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * TI LM8502 Multi-Function Device core
+ *
+ * Copyright (C) 2008 Palm Inc.
+ * Copyright (C) 2024 Christophe Chapuis <chris.music.music@gmail.com>
+ * Copyright (C) 2024-2026 Herman van Hazendonk <github.com@herrie.org>
+ *
+ * The LM8502 is a combo LED + haptic controller on a single I2C address
+ * (0x33 on the HP TouchPad). This MFD core handles everything chip-wide:
+ *
+ *   - the i2c_client and regmap
+ *   - the chip-enable GPIO (a TLMM line on tenderloin)
+ *   - the vcc regulator and the High Power Mode (HPM) handshake with
+ *     RPM-managed PMIC LDOs (PM8058_L16 on tenderloin)
+ *   - the post-reset chip configuration sequence (software reset,
+ *     ENGINE_CNTRL1/2, MISC) with explicit write-then-readback so
+ *     I2C controllers that do not surface NAKs (notably MSM8x60 QUP
+ *     in the immediate post-power-up window) fail fast in probe
+ *     rather than silently leaving the chip unconfigured
+ *   - PM suspend/resume of the whole chip via the CHIP_EN bit
+ *
+ * Per-subsystem code lives in child drivers:
+ *
+ *   drivers/leds/leds-lm8502.c         - LED class for outputs D1..D10
+ *   drivers/input/misc/lm8502-haptic.c - EV_FF / FF_RUMBLE for the
+ *                                        internal H-bridge vibrator
+ *
+ * Both children are spawned via devm_mfd_add_devices() with explicit
+ * .of_compatible matches so their DT subnodes drive per-subsystem
+ * configuration (the LED children, ti,invert-direction, etc).
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/lm8502.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+/* Maximum time to wait for the chip to ACK reads/writes after power-up. */
+#define LM8502_DETECT_TIMEOUT_MS	250
+#define LM8502_DETECT_POLL_MS		5
+
+/* Time the chip needs after RESET=0xFF before responding again. */
+#define LM8502_RESET_SETTLE_MS		50
+
+/*
+ * After raising enable_gpio the chip's internal reset deasserts and
+ * the I2C front-end becomes responsive; datasheet calls out ~1 ms.
+ */
+#define LM8502_ENABLE_SETTLE_US		1000
+
+/*
+ * regulator_set_load() on RPM-managed PMIC LDOs (PM8058_L16) only
+ * sends an IPC; the rail change is asynchronous. 5 ms is the worst-
+ * case RPM commit window measured on MSM8x60 silicon -- no polling
+ * API exposes commit completion.
+ */
+#define LM8502_RPM_HPM_SETTLE_US	5000
+
+/* Driver-private wrapper around the shared struct lm8502. */
+struct lm8502_core {
+	struct lm8502 chip;		/* shared with children via parent drvdata */
+
+	/* Resources only the core touches: */
+	struct regulator *vcc;
+	struct gpio_desc *enable_gpio;
+};
+
+static bool lm8502_volatile_reg(struct device *dev, unsigned int reg)
+{
+	return true;			/* every read goes to the bus */
+}
+
+static const struct regmap_config lm8502_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0xF0,
+	.volatile_reg = lm8502_volatile_reg,
+	.cache_type = REGCACHE_NONE,
+};
+
+/* MFD children spawned by this core. */
+static const struct mfd_cell lm8502_devs[] = {
+	{
+		.name = "lm8502-leds",
+		.of_compatible = "ti,lm8502-leds",
+	},
+	{
+		.name = "lm8502-haptic",
+		.of_compatible = "ti,lm8502-haptic",
+	},
+};
+
+/*
+ * Bring the chip out of reset and verify it is ACKing the bus.
+ *
+ * The post-power-up window is tricky on MSM8x60 QUP: writes complete
+ * without -ENXIO even when the chip is not actually responding, so a
+ * blind probe-time init silently writes into the void. We use the
+ * LP55xx-family pattern: write CHIP_EN, read it back, retry until the
+ * read matches or we time out.
+ */
+static int lm8502_wait_for_chip(struct lm8502 *chip)
+{
+	unsigned long deadline = jiffies +
+				 msecs_to_jiffies(LM8502_DETECT_TIMEOUT_MS);
+	unsigned int val;
+	int ret;
+
+	/*
+	 * Use a wall-clock deadline rather than counting only sleep time:
+	 * regmap_{write,read}() round trips and any NAK/timeout cost on a
+	 * slow I2C bus can otherwise extend the wall-clock probe time
+	 * beyond LM8502_DETECT_TIMEOUT_MS without the loop noticing.
+	 */
+	do {
+		ret = regmap_write(chip->regmap, LM8502_ENGINE_CNTRL1,
+				   LM8502_CHIP_EN);
+		if (!ret) {
+			ret = regmap_read(chip->regmap, LM8502_ENGINE_CNTRL1,
+					  &val);
+			if (ret == 0 && (val & LM8502_CHIP_EN))
+				return 0;
+		}
+
+		fsleep(LM8502_DETECT_POLL_MS * USEC_PER_MSEC);
+	} while (time_before(jiffies, deadline));
+
+	return -ENODEV;
+}
+
+/*
+ * Bring the chip up: software reset, prove the bus, then program the
+ * baseline ENGINE_CNTRL2 / MISC / per-channel D<n>_CONTROL registers.
+ *
+ * Errors are reported with plain dev_err() rather than dev_err_probe()
+ * because this is also called from the resume path -- using
+ * dev_err_probe() there would pollute the deferred-probe last-failure
+ * tracker for a device that is already bound. Probe callers wrap the
+ * return with dev_err_probe() themselves.
+ */
+static int lm8502_chip_init(struct lm8502_core *core)
+{
+	struct lm8502 *chip = &core->chip;
+	struct device *dev = chip->dev;
+	int ret, i;
+
+	/*
+	 * Software reset. Writes through regmap; the chip may briefly
+	 * NAK reads in this window so we do not bother checking the
+	 * return value -- lm8502_wait_for_chip() proves bidirectional
+	 * communication below.
+	 */
+	regmap_write(chip->regmap, LM8502_RESET, LM8502_RESET_MAGIC);
+	fsleep(LM8502_RESET_SETTLE_MS * USEC_PER_MSEC);
+
+	ret = lm8502_wait_for_chip(chip);
+	if (ret) {
+		dev_err(dev, "chip did not respond: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(chip->regmap, LM8502_ENGINE_CNTRL2,
+			   LM8502_ENGINE_CNTRL2_INIT);
+	if (ret) {
+		dev_err(dev, "ENGINE_CNTRL2 write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(chip->regmap, LM8502_MISC, LM8502_MISC_INIT);
+	if (ret) {
+		dev_err(dev, "MISC write failed: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Program every D<n>_CONTROL register so the LED child can issue
+	 * brightness writes to D<n>_CURRENT_CTRL immediately. Without
+	 * this the channels stay at the post-reset default and ignore
+	 * current writes until the user manually configures them.
+	 */
+	for (i = 0; i < LM8502_MAX_LEDS; i++) {
+		ret = regmap_write(chip->regmap, LM8502_D1_CONTROL + i,
+				   LM8502_LED_CONTROL_INIT);
+		if (ret) {
+			dev_err(dev, "D%d_CONTROL write failed: %d\n",
+				i + 1, ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * devm-registered cleanup callback. Runs at unbind / probe-failure
+ * time AFTER the MFD child platform devices spawned by
+ * devm_mfd_add_devices() have been torn down (LIFO devres ordering --
+ * see lm8502_i2c_probe for the order in which actions and child
+ * devices are registered). This is what makes the children's remove
+ * callbacks issue their final regmap writes against a still-powered
+ * chip rather than a clamped/disabled one.
+ */
+static void lm8502_power_off(void *data)
+{
+	struct lm8502_core *core = data;
+
+	if (core->enable_gpio)
+		gpiod_set_value_cansleep(core->enable_gpio, 0);
+
+	if (core->vcc) {
+		regulator_set_load(core->vcc, 0);
+		regulator_disable(core->vcc);
+	}
+}
+
+static int lm8502_i2c_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct lm8502_core *core;
+	struct lm8502 *chip;
+	int ret;
+
+	core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
+	if (!core)
+		return -ENOMEM;
+
+	chip = &core->chip;
+	chip->dev = dev;
+	mutex_init(&chip->lock);
+
+	chip->regmap = devm_regmap_init_i2c(client, &lm8502_regmap_config);
+	if (IS_ERR(chip->regmap))
+		return dev_err_probe(dev, PTR_ERR(chip->regmap),
+				     "failed to allocate regmap\n");
+
+	/*
+	 * Optional vcc regulator. On the HP TouchPad this is PM8058_L16,
+	 * a 1.8 V always-on RPM-managed LDO with "regulator-allow-set-load"
+	 * so we can ask for High Power Mode below.
+	 */
+	core->vcc = devm_regulator_get_optional(dev, "vcc");
+	if (IS_ERR(core->vcc)) {
+		ret = PTR_ERR(core->vcc);
+		if (ret == -ENODEV) {
+			core->vcc = NULL;
+		} else {
+			return dev_err_probe(dev, ret,
+					     "failed to get vcc supply\n");
+		}
+	}
+
+	if (core->vcc) {
+		ret = regulator_enable(core->vcc);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "failed to enable vcc supply\n");
+
+		/*
+		 * Ask for High Power Mode. The LM8502 internal boost converter
+		 * plus all ten LED outputs at up to 9 mA each draw ~100 mA
+		 * worst case. On RPM-managed LDOs (PM8058_L16) the default Low
+		 * Power Mode caps the regulator at ~1 mA, leaving the chip
+		 * current-starved.
+		 *
+		 * regulator_set_load() is a no-op on consumers whose supply
+		 * does not carry "regulator-allow-set-load", so this is safe
+		 * on platforms with simpler regulator topology.
+		 */
+		ret = regulator_set_load(core->vcc, 100000);
+		if (ret) {
+			dev_err_probe(dev, ret,
+				      "regulator_set_load(100mA) failed\n");
+			goto err_disable_vcc;
+		}
+
+		fsleep(LM8502_RPM_HPM_SETTLE_US);
+	}
+
+	core->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+						    GPIOD_OUT_LOW);
+	if (IS_ERR(core->enable_gpio)) {
+		ret = PTR_ERR(core->enable_gpio);
+		goto err_disable_vcc;
+	}
+
+	if (core->enable_gpio) {
+		gpiod_set_value_cansleep(core->enable_gpio, 1);
+		fsleep(LM8502_ENABLE_SETTLE_US);
+	}
+
+	dev_set_drvdata(dev, chip);
+
+	ret = lm8502_chip_init(core);
+	if (ret) {
+		dev_err_probe(dev, ret, "chip init failed\n");
+		goto err_disable_chip;
+	}
+
+	/*
+	 * Register the power-off callback BEFORE devm_mfd_add_devices() so
+	 * devres unwinds LIFO: child platform devices teardown FIRST (their
+	 * .remove callbacks issue final regmap writes against a still-
+	 * powered chip), and lm8502_power_off() runs SECOND. Without this
+	 * ordering, lm8502_i2c_remove() / err_disable_chip would cut
+	 * CHIP_EN and the vcc rail before the children's cleanup writes
+	 * landed -- on MSM8x60 QUP the controller does not surface NAKs in
+	 * that window (see lm8502_wait_for_chip), so the writes are either
+	 * silently dropped or fail with -EREMOTEIO and the haptic motor
+	 * can be left energised.
+	 */
+	ret = devm_add_action_or_reset(dev, lm8502_power_off, core);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to register power-off action\n");
+
+	/*
+	 * Spawn LED and haptic child platform devices. Children retrieve
+	 * the shared struct lm8502 via dev_get_drvdata(pdev->dev.parent);
+	 * since chip_init above already completed, children can issue
+	 * register writes from their own probe paths immediately.
+	 */
+	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, lm8502_devs,
+				   ARRAY_SIZE(lm8502_devs), NULL, 0, NULL);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to add child devices\n");
+
+	return 0;
+
+	/*
+	 * Failure paths below run BEFORE lm8502_power_off() is registered
+	 * via devm, so they must still tear the rail down manually.
+	 */
+err_disable_chip:
+	if (core->enable_gpio)
+		gpiod_set_value_cansleep(core->enable_gpio, 0);
+err_disable_vcc:
+	if (core->vcc) {
+		regulator_set_load(core->vcc, 0);
+		regulator_disable(core->vcc);
+	}
+	return ret;
+}
+
+static int lm8502_suspend(struct device *dev)
+{
+	struct lm8502 *chip = dev_get_drvdata(dev);
+	struct lm8502_core *core = container_of(chip, struct lm8502_core, chip);
+	int ret;
+
+	mutex_lock(&chip->lock);
+	if (chip->suspended) {
+		mutex_unlock(&chip->lock);
+		return 0;
+	}
+
+	/*
+	 * Mark the chip as suspended UP FRONT, before any register write
+	 * that can fail. The semantic is "system PM committed to suspend
+	 * us"; even if a midway regmap write returns -ENXIO and the rest
+	 * of system suspend continues without us, the resume path must
+	 * still run the full re-init sequence because the rails / GPIO
+	 * may have been power-cycled across S3. Without this, a single
+	 * I2C error during suspend would leave chip->suspended == false,
+	 * lm8502_resume() would early-out, and the children's first
+	 * register access after resume would hit an unconfigured chip.
+	 */
+	chip->suspended = true;
+
+	/* Stop the haptic output in case a child left it running. */
+	ret = regmap_write(chip->regmap, LM8502_HAPTIC_FEEDBACK_CTRL, 0);
+	if (ret) {
+		dev_err(dev, "suspend: HAPTIC_FEEDBACK_CTRL write failed: %d\n",
+			ret);
+		goto unlock;
+	}
+
+	/* Drop CHIP_EN to power-gate the analog side. */
+	ret = regmap_update_bits(chip->regmap, LM8502_ENGINE_CNTRL1,
+				 LM8502_CHIP_EN, 0);
+	if (ret) {
+		dev_err(dev, "suspend: CHIP_EN clear failed: %d\n", ret);
+		goto unlock;
+	}
+
+	if (core->enable_gpio)
+		gpiod_set_value_cansleep(core->enable_gpio, 0);
+
+	/*
+	 * Drop the regulator to Low Power Mode now that the chip is
+	 * off; HPM is only needed while the analog stage draws ~100 mA,
+	 * and leaving it set wastes power across system suspend on
+	 * RPM-managed PMIC LDOs. Restored in resume.
+	 */
+	if (core->vcc)
+		regulator_set_load(core->vcc, 0);
+
+	mutex_unlock(&chip->lock);
+
+	return 0;
+
+unlock:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static int lm8502_resume(struct device *dev)
+{
+	struct lm8502 *chip = dev_get_drvdata(dev);
+	struct lm8502_core *core = container_of(chip, struct lm8502_core, chip);
+	int ret;
+
+	mutex_lock(&chip->lock);
+	if (!chip->suspended) {
+		mutex_unlock(&chip->lock);
+		return 0;
+	}
+
+	/*
+	 * Restore High Power Mode so the regulator can supply the
+	 * ~100 mA the analog stage draws under load. Symmetric with the
+	 * drop in suspend; safe no-op on consumers without
+	 * regulator-allow-set-load.
+	 */
+	if (core->vcc) {
+		ret = regulator_set_load(core->vcc, 100000);
+		if (ret) {
+			dev_err(dev, "resume: regulator_set_load(HPM) failed: %d\n",
+				ret);
+			goto unlock;
+		}
+		fsleep(LM8502_RPM_HPM_SETTLE_US);
+	}
+
+	if (core->enable_gpio) {
+		gpiod_set_value_cansleep(core->enable_gpio, 1);
+		fsleep(LM8502_ENABLE_SETTLE_US);
+	}
+
+	/*
+	 * Suspend de-asserts the enable_gpio, which on most boards power-
+	 * gates the chip and clears its register state. Re-run the full
+	 * init sequence so ENGINE_CNTRL1/2, MISC and the per-channel
+	 * D<n>_CONTROL registers are restored before any child driver
+	 * touches the chip. lm8502_chip_init() also sets CHIP_EN as a
+	 * side-effect of programming ENGINE_CNTRL1. Errors are already
+	 * logged with dev_err() inside lm8502_chip_init().
+	 */
+	ret = lm8502_chip_init(core);
+	if (ret)
+		goto err_disable;
+
+	chip->suspended = false;
+	mutex_unlock(&chip->lock);
+
+	return 0;
+
+err_disable:
+	/*
+	 * chip_init failed; we already raised enable_gpio and committed
+	 * HPM. Undo what resume did: de-assert enable_gpio and drop
+	 * the regulator back to Low Power Mode. The vcc regulator is
+	 * intentionally NOT disabled -- it was never disabled during
+	 * the preceding suspend either, so the device returns to the
+	 * same suspended state it was in before resume started rather
+	 * than a fully-off state.
+	 */
+	if (core->enable_gpio)
+		gpiod_set_value_cansleep(core->enable_gpio, 0);
+	if (core->vcc)
+		regulator_set_load(core->vcc, 0);
+unlock:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(lm8502_pm_ops, lm8502_suspend, lm8502_resume);
+
+static const struct of_device_id lm8502_of_match[] = {
+	{ .compatible = "ti,lm8502" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, lm8502_of_match);
+
+static const struct i2c_device_id lm8502_i2c_id[] = {
+	{ "lm8502" },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, lm8502_i2c_id);
+
+static struct i2c_driver lm8502_i2c_driver = {
+	.driver = {
+		.name = "lm8502",
+		.of_match_table = lm8502_of_match,
+		.pm = pm_sleep_ptr(&lm8502_pm_ops),
+	},
+	.probe = lm8502_i2c_probe,
+	.id_table = lm8502_i2c_id,
+};
+module_i2c_driver(lm8502_i2c_driver);
+
+MODULE_DESCRIPTION("TI LM8502 combo LED + haptic controller (MFD core)");
+MODULE_AUTHOR("Herman van Hazendonk <github.com@herrie.org>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/lm8502.h b/include/linux/mfd/lm8502.h
new file mode 100644
index 000000000000..9c7c90cdd273
--- /dev/null
+++ b/include/linux/mfd/lm8502.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * TI LM8502 Multi-Function Device
+ *
+ * The LM8502 is a combo LED + haptic controller exposed over I2C. The
+ * MFD core handles the i2c_client, regmap, regulator and chip-wide
+ * initialization; per-subsystem child platform drivers in drivers/leds/
+ * and drivers/input/misc/ bind to the spawned children and use the
+ * parent's regmap.
+ *
+ * Children retrieve the parent's lm8502 state via
+ *     struct lm8502 *chip = dev_get_drvdata(pdev->dev.parent);
+ * and serialise register access on chip->lock.
+ */
+#ifndef __LINUX_MFD_LM8502_H
+#define __LINUX_MFD_LM8502_H
+
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+/* --- Register map --------------------------------------------------- */
+
+#define LM8502_ENGINE_CNTRL1		0x00
+#define LM8502_ENGINE_CNTRL2		0x01
+#define LM8502_GROUP_FADING1		0x02
+#define LM8502_GROUP_FADING2		0x03
+#define LM8502_GROUP_FADING3		0x04
+
+#define LM8502_D1_CONTROL		0x06
+#define LM8502_D2_CONTROL		0x07
+#define LM8502_D3_CONTROL		0x08
+#define LM8502_D4_CONTROL		0x09
+#define LM8502_D5_CONTROL		0x0A
+#define LM8502_D6_CONTROL		0x0B
+#define LM8502_D7_CONTROL		0x0C
+#define LM8502_D8_CONTROL		0x0D
+#define LM8502_D9_CONTROL		0x0E
+#define LM8502_D10_CONTROL		0x0F
+
+#define LM8502_HAPTIC_CONTROL		0x10
+#define LM8502_HAPTIC_FEEDBACK_CTRL	0x21
+#define LM8502_HAPTIC_PWM_DUTY_CYCLE	0x22
+
+#define LM8502_D1_CURRENT_CTRL		0x26
+#define LM8502_D2_CURRENT_CTRL		0x27
+#define LM8502_D3_CURRENT_CTRL		0x28
+#define LM8502_D4_CURRENT_CTRL		0x29
+#define LM8502_D5_CURRENT_CTRL		0x2A
+#define LM8502_D6_CURRENT_CTRL		0x2B
+#define LM8502_D7_CURRENT_CTRL		0x2C
+#define LM8502_D8_CURRENT_CTRL		0x2D
+#define LM8502_D9_CURRENT_CTRL		0x2E
+#define LM8502_D10_CURRENT_CTRL		0x2F
+
+#define LM8502_MISC			0x36
+#define LM8502_ENGINE1_PC		0x37
+#define LM8502_ENGINE2_PC		0x38
+#define LM8502_STATUS			0x3A
+#define LM8502_INT			0x3B
+#define LM8502_RESET			0x3D
+
+/* ENGINE_CNTRL1 bits */
+#define LM8502_CHIP_EN			BIT(6)
+
+/* ENGINE_CNTRL2 — value applied at chip_init: charge-pump CP_MODE = 1x.
+ * Bit 5 selects the boost-converter compare mode; the value the legacy
+ * legacy vendor driver writes matches the LP55xx-family ENGINE_CNTRL2 "all
+ * engines in HOLD" default plus CP_MODE_1X.
+ */
+#define LM8502_ENGINE_CNTRL2_INIT	0x20
+
+/* MISC (0x36) bits applied at chip_init */
+#define LM8502_MISC_PWM_INPUT		BIT(1)	/* PWM input mux on D10 */
+/* BOOST_EN: internal boost converter -- REQUIRED for LED current. */
+#define LM8502_MISC_BOOST_EN		BIT(3)
+#define LM8502_MISC_POWER_SAVE		BIT(5)	/* clock gate idle blocks */
+#define LM8502_MISC_INIT		(LM8502_MISC_POWER_SAVE | \
+					 LM8502_MISC_BOOST_EN | \
+					 LM8502_MISC_PWM_INPUT)
+
+/* D<n>_CONTROL bits */
+#define LM8502_LED_MAPPING_MASK		0x03
+#define LM8502_LED_MAPPING_DIRECT	0x00	/* drive from current reg */
+#define LM8502_LED_MAX_CURRENT_MASK	0x18
+#define LM8502_LED_MAX_CURRENT_SHIFT	3
+#define LM8502_LED_MAX_CURRENT_9MA	0x02	/* field = 2 -> 9 mA cap */
+/*
+ * Initial D<n>_CONTROL value: MAX_CURRENT field = 2 (9 mA), DIRECT
+ * mapping. Programmed for every channel during chip_init so a child
+ * brightness write to D<n>_CURRENT_CTRL is honoured immediately.
+ */
+#define LM8502_LED_CONTROL_INIT		(LM8502_LED_MAX_CURRENT_9MA << \
+					 LM8502_LED_MAX_CURRENT_SHIFT)
+
+/* HAPTIC_FEEDBACK_CTRL bits */
+#define LM8502_HAPTIC_FB_INVERT_DIR	BIT(0)	/* H-bridge polarity flip */
+#define LM8502_HAPTIC_FB_ENABLE		BIT(1)	/* drive the motor */
+
+#define LM8502_MAX_LEDS			10
+
+/* RESET (0x3D) magic write value (LP55xx-family software reset). */
+#define LM8502_RESET_MAGIC		0xFF
+
+/* --- Shared parent state -------------------------------------------- */
+
+/**
+ * struct lm8502 - parent (MFD core) state shared with child drivers
+ * @dev:        the I2C client's struct device (parent of every child pdev)
+ * @regmap:     the chip's 8-bit register map (no cache; every read goes
+ *              to the bus)
+ * @lock:       serialises register sequences across children and against
+ *              the parent's PM callbacks
+ * @suspended:  set true while the chip is power-gated by parent suspend;
+ *              children must check it under @lock before touching
+ *              registers
+ *
+ * Chip readiness is implicit: child platform devices only probe after
+ * the MFD parent's probe has completed lm8502_chip_init() successfully,
+ * so by the time a child runs the chip is fully configured and ACKing
+ * I2C. There is no separate "initialized" flag.
+ */
+struct lm8502 {
+	struct device *dev;
+	struct regmap *regmap;
+	struct mutex lock;
+	bool suspended;
+};
+
+#endif /* __LINUX_MFD_LM8502_H */
-- 
2.43.0