From nobody Fri Dec 19 17:18:10 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 55D3433EB0E for ; Thu, 6 Nov 2025 14:12:03 +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=1762438325; cv=none; b=oA1nfsJ/Kpf+wV7/JGpf+uBqR/FAFqnmiUmCiGlzAS1HJARpOkYKPxGdfq9SFe3W52qDoft3dddH1RKXHvoWj+FzSzL/dl/rM1OUS6z4/Liy82LUm1zIpELzIgOtXNb4Da2UfqIjmh3I4Go9YdVzvTMaM1Gbh++zeCKcxYVlkPQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1762438325; c=relaxed/simple; bh=tBrLnoN6fk+Xqnm3U9nOXSYuDRrAPCj1BStgPuNDt7Y=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Fbd4JossXWlrgx/mH9uKbAAKl541y+9ZwBdFDuCDHwSeh1DTbosIPoyB74SYQFJyNX1rYayT5fgFEFo20RTIz87LUUsSGBy1TqtyR8eJWcowuf+96BED8VoBYLDGB1dSBKbwVzedrKitrfnR1aPIC/lceZprklFDZ7Y69G12NeY= 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=LI9HEaQ1; 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="LI9HEaQ1" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id CC56D4E4156D; Thu, 6 Nov 2025 14:12:01 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id A24A96068C; Thu, 6 Nov 2025 14:12:01 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id B3D721185115B; Thu, 6 Nov 2025 15:11:59 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1762438320; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=ZKeWFZxA04Nf3/9eL/Uw741y2vR3pCplWxqb3hcOC3s=; b=LI9HEaQ1i0wvEe68W/tfHeEZ/AHtRXpsEbeYhliUApXxyy47PLgV1dQcYu1x/8xCwx4oj+ nMACmlmkv0j2aRLaR1CsAWJFUjcR6ewc69wIz+D0hslv/GDWGtOEFCEYUbfPIDtdZh8KT5 jYjQZZ/mcAG4ToTMk+3ybnw3ogU5zg06bJf1Irwre8wknpb+Y3XX1RvEC5dc1zJpg/MhMZ A7juBUx4D4TpNO1ZmiKKCq8eKaOU9iv9NFo2fJWr+EOWqKmjdzHAuHpHDv1rDNc6LKlLak Z8Q606K7qiJHuCs9tLWYSxQg+wvSOPKIu+CamLVd2esyrbXMvOUjhvWhjR4mdA== From: Romain Gantois Date: Thu, 06 Nov 2025 15:11:50 +0100 Subject: [PATCH v3 5/5] 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: <20251106-ltm8054-driver-v3-5-fd1feae0f65a@bootlin.com> References: <20251106-ltm8054-driver-v3-0-fd1feae0f65a@bootlin.com> In-Reply-To: <20251106-ltm8054-driver-v3-0-fd1feae0f65a@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: Hans de Goede , 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 | 273 ++++++++++++++++++++++++++++++= +++- 2 files changed, 268 insertions(+), 6 deletions(-) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index f5c6d4a21a88..aad8c523420a 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -587,6 +587,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 b5783f6629e3..38072231b8e4 100644 --- a/drivers/regulator/ltm8054-regulator.c +++ b/drivers/regulator/ltm8054-regulator.c @@ -6,6 +6,7 @@ */ =20 #include +#include #include #include #include @@ -15,7 +16,11 @@ #include =20 #include +#include +#include +#include #include +#include #include #include #include @@ -26,10 +31,42 @@ #include #include =20 +#include +#include + /* 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; + bool write; + int ret; +}; + 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 @@ -43,14 +80,190 @@ 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); + + mutex_lock(&priv->ctl_work_lock); + val =3D ctl_work->ctl_val; + write =3D ctl_work->write; + mutex_unlock(&priv->ctl_work_lock); + + /* Standard IIO voltage unit is mV, scale accordingly. */ + if (write) + ret =3D iio_write_channel_processed_scale(priv->ctl_dac, + val, 1000); + else + ret =3D iio_read_channel_processed_scale(priv->ctl_dac, + &val, 1000); + + pr_debug("LTM8054: %s CTL IO channel, val: %duV\n", write ? "wrote" : "re= ading", val); + + mutex_lock(&priv->ctl_work_lock); + ctl_work->ret =3D ret; + ctl_work->ctl_val =3D val; + mutex_unlock(&priv->ctl_work_lock); + + 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 =3D 0; + + 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 && !write) + *ctl_val =3D ctl_work->ctl_val; + } + + return ret; +} + +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 (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) @@ -60,6 +273,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, @@ -77,32 +293,76 @@ static int ltm8054_of_parse(struct device *dev, struct= ltm8054_priv *priv, 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 + platform_set_drvdata(pdev, priv); + + 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); + mutex_init(&priv->ctl_work_lock); + + priv->rdesc.ops =3D <m8054_ctl_ops; + } + config.dev =3D dev; config.driver_data =3D priv; =20 ret =3D ltm8054_of_parse(dev, priv, &config); - if (ret) - return dev_err_probe(dev, ret, "failed to parse device tree\n"); + if (ret) { + ret =3D dev_err_probe(dev, ret, "failed to parse device tree\n"); + goto out_err; + } =20 rdev =3D devm_regulator_register(dev, &priv->rdesc, &config); - if (IS_ERR(rdev)) - return dev_err_probe(dev, PTR_ERR(rdev), "failed to register regulator\n= "); + if (IS_ERR(rdev)) { + ret =3D dev_err_probe(dev, PTR_ERR(rdev), "failed to register regulator\= n"); + goto out_err; + } =20 return 0; + +out_err: + if (ctl_dac) { + cancel_work_sync(&priv->ctl_work.work); + mutex_destroy(&priv->ctl_work_lock); + } + + return ret; +} + +static void ltm8054_remove(struct platform_device *pdev) +{ + struct ltm8054_priv *priv =3D platform_get_drvdata(pdev); + + if (priv->ctl_dac) { + cancel_work_sync(&priv->ctl_work.work); + mutex_destroy(&priv->ctl_work_lock); + } } =20 static const struct of_device_id ltm8054_of_match[] =3D { @@ -113,6 +373,7 @@ MODULE_DEVICE_TABLE(of, ltm8054_of_match); =20 static struct platform_driver ltm8054_driver =3D { .probe =3D ltm8054_probe, + .remove =3D ltm8054_remove, .driver =3D { .name =3D "ltm8054", .of_match_table =3D ltm8054_of_match, --=20 2.51.2