From nobody Tue Dec 2 00:46:46 2025 Received: from smtpout-03.galae.net (smtpout-03.galae.net [185.246.85.4]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4B42D26F2AD for ; Mon, 24 Nov 2025 14:48:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.85.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763995738; cv=none; b=C7ZctuD+Ys5FDuZMMBQ6xXYjCyCqOhWqGdyAym2kMAzuXwwdS3HYhcL8QjoexphGq09XSBdJSmrU0LEdjgTILjwYSwt4mysT7XItBCcd0x9zyvFpPrhD38meZOqKS6rFYJJosD9vXCxfGjTKvnKfvigmDxPtcZzc15X+LWxBIFc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763995738; c=relaxed/simple; bh=oep1bdWFRbFW7rlKaUYj86LsnLto/9YhZ3F1OTo86zk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=JE+Qrq52x8kdZdDlxCycojQ8fq23aHgEgeVaHAIvIJF+3PcQbOnH8FckPn6jzihzQdRHfS/w8I0RTl2Z9UA1es6pp+RpkJRQO9hM2A5REYz/Fom/gKLu8Xz5nWlfp7cG+AERpJgK7EmwL4xOL/aXeYbZWyMgjJP+GwUds1/z5Mw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=a+Ore6GW; arc=none smtp.client-ip=185.246.85.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="a+Ore6GW" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id C11C54E418A5 for ; Mon, 24 Nov 2025 14:48:54 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 938EA606FC; Mon, 24 Nov 2025 14:48:54 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id D8CDF10371DB5; Mon, 24 Nov 2025 15:48:52 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1763995733; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=E65SZmiDoU6jgob3AdJWifHU8CJmVyqhN7mABheg6DU=; b=a+Ore6GWJnT6PuoOWGUyrIBBpwetMqj+niV1fB//nICmsG/TAk48IonEARWIqoEr36/QDP 7PmsZJ1+uNEmW8eEmbbMYb4qBZlppqSRacrqbWBDMuvNlhQWMBhLD0Ec9RMt7N3DLjgpfW zpGjoI4MXS3Ux3FiyxujIRBr1mUM5x4+ynhslX8YQ9RMP0ApmGnY9PwGrawSv85UVuD4uy 3VQDklA2fekypOyObmxxGkB0ljKkbc/GXCtvGsFNs/+DIY0zvAP9TngW/zkxirAL4Gt4yl rz6dL+3QOdaKfGznYOEyhKWNdVRDMoMCV3UVdq6iXpawf71gsD0QJfCtBXRWIA== From: Romain Gantois Date: Mon, 24 Nov 2025 15:48:10 +0100 Subject: [PATCH v4 6/6] regulator: ltm8054: Support output current limit control Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20251124-ltm8054-driver-v4-6-107a8a814abe@bootlin.com> References: <20251124-ltm8054-driver-v4-0-107a8a814abe@bootlin.com> In-Reply-To: <20251124-ltm8054-driver-v4-0-107a8a814abe@bootlin.com> To: Liam Girdwood , Mark Brown , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Jonathan Cameron , David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko Cc: Thomas Petazzoni , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-iio@vger.kernel.org, Romain Gantois X-Mailer: b4 0.14.3 X-Last-TLS-Session-Version: TLSv1.3 The LTM8054 supports setting a fixed output current limit using a sense resistor connected to a dedicated pin. This limit can then be lowered dynamically by varying the voltage level of the CTL pin. Support controlling the LTM8054's output current limit. Signed-off-by: Romain Gantois --- drivers/regulator/Kconfig | 1 + drivers/regulator/ltm8054-regulator.c | 260 ++++++++++++++++++++++++++++++= +++- 2 files changed, 259 insertions(+), 2 deletions(-) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index ab97025c8f94..7b70f640ba25 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -597,6 +597,7 @@ config REGULATOR_LTC3676 =20 config REGULATOR_LTM8054 tristate "LTM8054 Buck-Boost voltage regulator" + depends on IIO help This driver provides support for the Analog Devices LTM8054 Buck-Boost micromodule regulator. The LTM8054 has an adjustable diff --git a/drivers/regulator/ltm8054-regulator.c b/drivers/regulator/ltm8= 054-regulator.c index c432b00d75a4..4613f3f57860 100644 --- a/drivers/regulator/ltm8054-regulator.c +++ b/drivers/regulator/ltm8054-regulator.c @@ -6,6 +6,7 @@ */ =20 #include +#include #include #include #include @@ -13,12 +14,20 @@ #include #include #include +#include +#include +#include #include +#include #include +#include #include #include #include +#include #include +#include +#include =20 #include #include @@ -27,7 +36,36 @@ /* The LTM8054 regulates its FB pin to 1.2V */ #define LTM8054_FB_uV 1200000 =20 +/* Threshold voltage between the Vout and Iout pins which triggers current + * limiting. + */ +#define LTM8054_VOUT_IOUT_MAX_uV 58000 + +#define LTM8054_MAX_CTL_uV 1200000 +#define LTM8054_MIN_CTL_uV 50000 + +#define LTM8054_CTL_RW_TIMEOUT msecs_to_jiffies(500) + +/* CTL pin read/write transaction */ +struct ltm8054_ctl_pin_work { + struct work_struct work; + unsigned int ctl_val; + int ret; + bool write; +}; + struct ltm8054_priv { + struct device *dev; + + struct iio_channel *ctl_dac; + struct ltm8054_ctl_pin_work ctl_work; + /* Lock for ctl_work. */ + struct mutex ctl_work_lock; + struct completion ctl_rw_done; + + int min_uA; + int max_uA; + struct regulator_desc rdesc; }; =20 @@ -41,14 +79,198 @@ static int ltm8054_scale(unsigned int uV, u32 r1, u32 = r2) return uV + tmp; } =20 -static const struct regulator_ops ltm8054_regulator_ops =3D { }; +static void ltm8054_do_ctl_work(struct work_struct *work) +{ + struct ltm8054_ctl_pin_work *ctl_work =3D + container_of_const(work, struct ltm8054_ctl_pin_work, work); + struct ltm8054_priv *priv =3D + container_of_const(ctl_work, struct ltm8054_priv, ctl_work); + unsigned int val; + bool write; + int ret; + + lockdep_assert_not_held(&priv->ctl_work_lock); + + scoped_guard(mutex, &priv->ctl_work_lock) { + val =3D ctl_work->ctl_val; + write =3D ctl_work->write; + } + + /* Standard IIO voltage unit is mV, scale accordingly. */ + if (write) + ret =3D iio_write_channel_processed_scale(priv->ctl_dac, val, KILO); + else + ret =3D iio_read_channel_processed_scale(priv->ctl_dac, &val, KILO); + + dev_dbg(priv->dev, "%s CTL IO channel, val: %duV\n", str_write_read(write= ), val); + + scoped_guard(mutex, &priv->ctl_work_lock) { + ctl_work->ret =3D ret; + ctl_work->ctl_val =3D val; + } + + complete(&priv->ctl_rw_done); +} + +static int ltm8054_ctl_pin_rw(struct ltm8054_priv *priv, bool write, unsig= ned int *ctl_val) +{ + struct ltm8054_ctl_pin_work *ctl_work =3D &priv->ctl_work; + int ret; + + lockdep_assert_not_held(&priv->ctl_work_lock); + + /* + * The get/set_current_limit() callbacks have an active regulator core + * reservation ID (obtained with ww_acquire_init()). + * + * Or, the IO channel driver may call something like + * regulator_enable(), meaning this thread would acquire a new + * regulator core reservation ID before the current one is dropped + * (using ww_acquire_fini()). This is forbidden. + * + * Thus, perform the IO channel read/write in a different thread, and + * wait for it to complete, with a timeout to avoid deadlocking. + */ + + scoped_guard(mutex, &priv->ctl_work_lock) { + if (work_busy(&ctl_work->work)) + return -EBUSY; + + if (write) { + ctl_work->ctl_val =3D *ctl_val; + ctl_work->write =3D 1; + } else { + ctl_work->write =3D 0; + } + + schedule_work(&ctl_work->work); + } + + ret =3D wait_for_completion_timeout(&priv->ctl_rw_done, LTM8054_CTL_RW_TI= MEOUT); + reinit_completion(&priv->ctl_rw_done); + + if (unlikely(!ret)) + return -ETIMEDOUT; + + scoped_guard(mutex, &priv->ctl_work_lock) { + ret =3D ctl_work->ret; + + if (ret) + return ret; + + if (!write) + *ctl_val =3D ctl_work->ctl_val; + } + + return 0; +} + +static int ltm8054_set_current_limit(struct regulator_dev *rdev, int min_u= A, int max_uA) +{ + struct ltm8054_priv *priv =3D rdev_get_drvdata(rdev); + unsigned int ctl_val; + u64 vdac_uV; + + min_uA =3D clamp_t(int, min_uA, priv->min_uA, priv->max_uA); + + /* adjusted current limit =3D Rsense current limit * CTL pin voltage / ma= x CTL pin voltage */ + vdac_uV =3D (u64)min_uA * LTM8054_MAX_CTL_uV; + do_div(vdac_uV, priv->max_uA); + + dev_dbg(&rdev->dev, + "Setting current limit to %duA, CTL pin to %lluuV\n", min_uA, vdac_uV); + + ctl_val =3D vdac_uV; + + return ltm8054_ctl_pin_rw(priv, 1, &ctl_val); +} + +static int ltm8054_get_current_limit(struct regulator_dev *rdev) +{ + struct ltm8054_priv *priv =3D rdev_get_drvdata(rdev); + unsigned int ctl_val; + int ret; + u64 uA; + + ret =3D ltm8054_ctl_pin_rw(priv, 0, &ctl_val); + if (ret) + return ret; + + uA =3D (u64)ctl_val * priv->max_uA; + do_div(uA, LTM8054_MAX_CTL_uV); + + return uA; +} + +static const struct regulator_ops ltm8054_no_ctl_ops =3D { }; + +static const struct regulator_ops ltm8054_ctl_ops =3D { + .set_current_limit =3D ltm8054_set_current_limit, + .get_current_limit =3D ltm8054_get_current_limit, +}; + +static struct iio_channel *ltm8054_init_ctl_dac(struct platform_device *pd= ev) +{ + struct iio_channel *ctl_dac; + enum iio_chan_type type; + int ret; + + ctl_dac =3D devm_iio_channel_get(&pdev->dev, "ctl"); + if (IS_ERR(ctl_dac)) { + /* + * If the IO channel is not available yet, request probe retry. + * + * devm_iio_channel_get() will return ENODEV if it can't find + * the channel, which will happen if the channel's driver + * hasn't probed yet. If it returned any other error code, + * then something went wrong with the IO channel, don't retry. + */ + if (PTR_ERR(ctl_dac) =3D=3D -ENODEV) + return ERR_PTR(-EPROBE_DEFER); + + return ctl_dac; + } + + ret =3D iio_get_channel_type(ctl_dac, &type); + if (ret) + return ERR_PTR(ret); + + if (type !=3D IIO_VOLTAGE) + return ERR_PTR(-EINVAL); + + return ctl_dac; +} =20 static int ltm8054_of_parse(struct device *dev, struct ltm8054_priv *priv, struct regulator_config *config) { + u32 rsense; u32 r[2]; + u64 tmp; int ret; =20 + ret =3D device_property_read_u32(dev, "adi,iout-rsense-micro-ohms", &rsen= se); + if (ret) + return ret; + + if (rsense =3D=3D 0) + return -EINVAL; + + /* The maximum output current limit is the one set by the Rsense resistor= */ + tmp =3D (u64)LTM8054_VOUT_IOUT_MAX_uV * MICRO; + do_div(tmp, rsense); + priv->max_uA =3D tmp; + + /* + * Applying a voltage below LTM8054_MAX_CTL_uV on the CTL pin reduces + * the output current limit. If this level drops below + * LTM8054_MIN_CTL_uV the regulator stops switching. + */ + + tmp =3D (u64)priv->max_uA * LTM8054_MIN_CTL_uV; + do_div(tmp, LTM8054_MAX_CTL_uV); + priv->min_uA =3D tmp; + ret =3D device_property_read_u32_array(dev, "regulator-fb-voltage-divider= -ohms", r, ARRAY_SIZE(r)); if (ret) @@ -58,6 +280,9 @@ static int ltm8054_of_parse(struct device *dev, struct l= tm8054_priv *priv, priv->rdesc.min_uV =3D priv->rdesc.fixed_uV; priv->rdesc.n_voltages =3D 1; =20 + dev_dbg(dev, "max_uA: %d min_uA: %d fixed_uV: %d\n", + priv->max_uA, priv->min_uA, priv->rdesc.fixed_uV); + config->of_node =3D dev_of_node(dev); config->init_data =3D of_get_regulator_init_data(dev, config->of_node, @@ -72,23 +297,53 @@ static int ltm8054_of_parse(struct device *dev, struct= ltm8054_priv *priv, return 0; } =20 +static void ltm8054_cancel_ctl_work(void *ctl_work) +{ + cancel_work_sync(ctl_work); +} + static int ltm8054_probe(struct platform_device *pdev) { struct regulator_config config =3D { }; + struct iio_channel *ctl_dac =3D NULL; struct device *dev =3D &pdev->dev; struct regulator_dev *rdev; struct ltm8054_priv *priv; int ret; =20 + /* Do this first, as it might defer. */ + if (device_property_match_string(dev, "io-channel-names", "ctl") >=3D 0) { + ctl_dac =3D ltm8054_init_ctl_dac(pdev); + if (IS_ERR(ctl_dac)) + return dev_err_probe(dev, PTR_ERR(ctl_dac), + "failed to initialize CTL DAC\n"); + } + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; =20 + priv->dev =3D dev; priv->rdesc.name =3D "ltm8054-regulator"; - priv->rdesc.ops =3D <m8054_regulator_ops; + priv->rdesc.ops =3D <m8054_no_ctl_ops; priv->rdesc.type =3D REGULATOR_VOLTAGE; priv->rdesc.owner =3D THIS_MODULE; =20 + if (ctl_dac) { + priv->ctl_dac =3D ctl_dac; + + INIT_WORK(&priv->ctl_work.work, ltm8054_do_ctl_work); + init_completion(&priv->ctl_rw_done); + + devm_add_action_or_reset(priv->dev, ltm8054_cancel_ctl_work, &priv->ctl_= work.work); + + ret =3D devm_mutex_init(priv->dev, &priv->ctl_work_lock); + if (ret) + return ret; + + priv->rdesc.ops =3D <m8054_ctl_ops; + } + config.dev =3D dev; config.driver_data =3D priv; =20 @@ -121,3 +376,4 @@ module_platform_driver(ltm8054_driver); MODULE_DESCRIPTION("LTM8054 regulator driver"); MODULE_AUTHOR("Romain Gantois "); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_CONSUMER"); --=20 2.51.2