mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v5 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support
@ 2026-09-29  9:59 Aniruddha Rao
  2026-09-29  9:59 ` [PATCH v5 1/5] firmware: tegra: bpmp: Move channel initialization to helper Aniruddha Rao
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Aniruddha Rao @ 2026-09-29  9:59 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

This series adds ACPI support to the Tegra BPMP driver and support for
Memory Bandwidth Throttler (MBWT) controls exposed by BPMP firmware of
some Tegra platforms.

The current BPMP driver depends on device tree bindings. This series
keeps the existing DT path unchanged while adding an ACPI transport path
for BPMP firmware requests.

MBWT controls are accessed through BPMP firmware requests. This series
defines the MBWT BPMP ABI, adds MBWT helper functions, and exposes a
narrow sysfs interface when firmware reports support for the required
MBWT requests.

Aniruddha Rao (5):
  firmware: tegra: bpmp: Move channel initialization to helper
  firmware: tegra: bpmp: Add ACPI support
  firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI
    definitions
  firmware: tegra: bpmp: Add MBWT BPMP helpers
  firmware: tegra: bpmp: Add MBWT sysfs interface

 .../ABI/testing/sysfs-platform-tegra-bpmp     |  48 ++
 drivers/firmware/tegra/Makefile               |   1 +
 drivers/firmware/tegra/bpmp-private.h         |  39 ++
 drivers/firmware/tegra/bpmp-sysfs.c           | 207 +++++++++
 drivers/firmware/tegra/bpmp-tegra186.c        |  14 +
 drivers/firmware/tegra/bpmp-tegra210.c        |  13 +-
 drivers/firmware/tegra/bpmp.c                 | 415 +++++++++++++++---
 include/soc/tegra/bpmp-abi.h                  | 161 ++++++-
 include/soc/tegra/bpmp.h                      |   2 +
 9 files changed, 849 insertions(+), 51 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-platform-tegra-bpmp
 create mode 100644 drivers/firmware/tegra/bpmp-sysfs.c

Changes since v4:
- Move BPMP channel allocation and ping setup/teardown into the
  SoC-specific init/deinit operations.
- Add an optional init_providers operation for DT child and provider
  registration after BPMP communication is established and the firmware
  tag is read.
- Add an ACPI BPMP init operation for the BMRQ ping path.
- Remove ACPI-specific setup and teardown conditionals from the common
  probe path.
- Drop the redundant NULL check after device_get_match_data().
- Explain the transport and provider initialization phases in the ACPI
  support commit message.
- Explain why DT registers an incoming MRQ_PING handler while ACPI only
  sends an outgoing ping request.
- Describe MBWT bandwidth groups and traffic types with platform data so
  firmware instance and VC type values can vary between platforms.
- Document the sysfs hierarchy using platform-defined bandwidth-group and
  traffic-type names.

Changes since v3:
- Update the sysfs ABI Documentation date to September 2026 and target
  release to 7.4.

Changes since v2:
- Rename the non-ACPI transfer helper to tegra_bpmp_transfer_channel().
- Keep the common BPMP message validation in tegra_bpmp_transfer().
- Remove duplicate message validation from the ACPI transfer helper.
- Move the MBWT helpers near probe and document why they are needed.
- Rename bpmp-tegra-sysfs.c to bpmp-sysfs.c.

Changes since v1:
- Dropped the SoC Kconfig patch.
- Kept the existing DT path unchanged while adding ACPI support.
- Reworked ACPI matching to avoid a dummy ops table.
- Added explicit error handling for short ACPI BMRQ responses.
- Folded MBWT helpers into bpmp.c.
- Reworked MBWT sysfs into per-traffic bandwidth attributes.
- Removed software bandwidth range checks.
- Use NVCLINK consistently in commit messages and documentation.
- Avoid platform-specific or transport-specific MBWT wording.

-- 
2.43.0


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

* [PATCH v5 1/5] firmware: tegra: bpmp: Move channel initialization to helper
  2026-09-29  9:59 [PATCH v5 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
@ 2026-09-29  9:59 ` Aniruddha Rao
  2026-09-29  9:59 ` [PATCH v5 2/5] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Aniruddha Rao @ 2026-09-29  9:59 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

Move BPMP channel allocation and initialization into a helper.

This keeps the probe function focused on sequencing.

It also lets later patches decide whether mailbox/IVC setup is needed.

No functional change is intended.

Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
Changes since v4:
- No change.

Changes since v3:
- No change.

Changes since v2:
- No change.

Changes since v1:
- Keep OF resource initialization in tegra_bpmp_probe().
- Move only BPMP channel allocation and initialization into a helper.

 drivers/firmware/tegra/bpmp.c | 44 +++++++++++++++++++++--------------
 1 file changed, 27 insertions(+), 17 deletions(-)

diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index 753472b53bd8..0eb99b1c5068 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -733,19 +733,9 @@ void tegra_bpmp_handle_rx(struct tegra_bpmp *bpmp)
 	spin_unlock(&bpmp->lock);
 }
 
-static int tegra_bpmp_probe(struct platform_device *pdev)
+static int tegra_bpmp_init_channels(struct tegra_bpmp *bpmp)
 {
-	struct tegra_bpmp *bpmp;
-	char tag[TAG_SZ];
 	size_t size;
-	int err;
-
-	bpmp = devm_kzalloc(&pdev->dev, sizeof(*bpmp), GFP_KERNEL);
-	if (!bpmp)
-		return -ENOMEM;
-
-	bpmp->soc = of_device_get_match_data(&pdev->dev);
-	bpmp->dev = &pdev->dev;
 
 	INIT_LIST_HEAD(&bpmp->mrqs);
 	spin_lock_init(&bpmp->lock);
@@ -755,31 +745,51 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
 
 	size = BITS_TO_LONGS(bpmp->threaded.count) * sizeof(long);
 
-	bpmp->threaded.allocated = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+	bpmp->threaded.allocated = devm_kzalloc(bpmp->dev, size, GFP_KERNEL);
 	if (!bpmp->threaded.allocated)
 		return -ENOMEM;
 
-	bpmp->threaded.busy = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+	bpmp->threaded.busy = devm_kzalloc(bpmp->dev, size, GFP_KERNEL);
 	if (!bpmp->threaded.busy)
 		return -ENOMEM;
 
 	spin_lock_init(&bpmp->atomic_tx_lock);
-	bpmp->tx_channel = devm_kzalloc(&pdev->dev, sizeof(*bpmp->tx_channel),
+	bpmp->tx_channel = devm_kzalloc(bpmp->dev, sizeof(*bpmp->tx_channel),
 					GFP_KERNEL);
 	if (!bpmp->tx_channel)
 		return -ENOMEM;
 
-	bpmp->rx_channel = devm_kzalloc(&pdev->dev, sizeof(*bpmp->rx_channel),
-	                                GFP_KERNEL);
+	bpmp->rx_channel = devm_kzalloc(bpmp->dev, sizeof(*bpmp->rx_channel),
+					GFP_KERNEL);
 	if (!bpmp->rx_channel)
 		return -ENOMEM;
 
-	bpmp->threaded_channels = devm_kcalloc(&pdev->dev, bpmp->threaded.count,
+	bpmp->threaded_channels = devm_kcalloc(bpmp->dev, bpmp->threaded.count,
 					       sizeof(*bpmp->threaded_channels),
 					       GFP_KERNEL);
 	if (!bpmp->threaded_channels)
 		return -ENOMEM;
 
+	return 0;
+}
+
+static int tegra_bpmp_probe(struct platform_device *pdev)
+{
+	struct tegra_bpmp *bpmp;
+	char tag[TAG_SZ];
+	int err;
+
+	bpmp = devm_kzalloc(&pdev->dev, sizeof(*bpmp), GFP_KERNEL);
+	if (!bpmp)
+		return -ENOMEM;
+
+	bpmp->soc = of_device_get_match_data(&pdev->dev);
+	bpmp->dev = &pdev->dev;
+
+	err = tegra_bpmp_init_channels(bpmp);
+	if (err < 0)
+		return err;
+
 	platform_set_drvdata(pdev, bpmp);
 
 	err = bpmp->soc->ops->init(bpmp);
-- 
2.43.0


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

* [PATCH v5 2/5] firmware: tegra: bpmp: Add ACPI support
  2026-09-29  9:59 [PATCH v5 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
  2026-09-29  9:59 ` [PATCH v5 1/5] firmware: tegra: bpmp: Move channel initialization to helper Aniruddha Rao
@ 2026-09-29  9:59 ` Aniruddha Rao
  2026-09-29  9:59 ` [PATCH v5 3/5] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions Aniruddha Rao
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Aniruddha Rao @ 2026-09-29  9:59 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

Add required changes in the Tegra BPMP driver to make it compatible with
ACPI-based platforms.

On ACPI systems, IPC is handled through an AML method instead of the
kernel mailbox and IVC frameworks.

Clock, reset and power-domain providers are not controlled by the Linux
BPMP driver on ACPI-based systems, so do not initialize them there.

Keep transport initialization in the SoC-specific init operation. Use an
optional init_providers operation for DT child and provider registration
after BPMP communication has been established and the firmware tag has
been read. ACPI platforms leave this operation unset.

DT transports register an MRQ_PING handler for requests initiated by
BPMP firmware. This request direction was used only by a diagnostic
command in older firmware and is not provided by firmware on ACPI-based
systems. The ACPI transport therefore does not register an incoming
handler; its init operation only sends the outgoing MRQ_PING request used
to verify communication with firmware.

Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
Changes since v4:
- Move BPMP channel allocation and ping setup/teardown into the
  SoC-specific init/deinit operations.
- Add an optional init_providers operation for DT child and provider
  registration after BPMP communication is established and the firmware
  tag is read.
- Add an ACPI BPMP init operation for the BMRQ ping path.
- Remove ACPI-specific setup and teardown conditionals from the common
  probe path.
- Drop the redundant NULL check after device_get_match_data().
- Explain the transport and provider initialization phases in the commit
  message.
- Explain why DT registers an incoming MRQ_PING handler while ACPI only
  sends an outgoing ping request.

Changes since v3:
- No change.

Changes since v2:
- Rename the non-ACPI transfer helper to tegra_bpmp_transfer_channel().
- Keep the common BPMP message validation in tegra_bpmp_transfer().
- Remove duplicate message validation from the ACPI transfer helper.

Changes since v1:
- Use acpi_extract_package() and a common cleanup path for BMRQ replies.
- Treat short BMRQ responses as errors.
- Rename the mailbox transport helper.
- Avoid a dummy ACPI ops table and call SoC ops only when present.
- Keep OF resource setup guarded by the OF node in probe.

 drivers/firmware/tegra/bpmp-private.h  |   6 +
 drivers/firmware/tegra/bpmp-tegra186.c |  14 ++
 drivers/firmware/tegra/bpmp-tegra210.c |  13 +-
 drivers/firmware/tegra/bpmp.c          | 265 ++++++++++++++++++++-----
 4 files changed, 246 insertions(+), 52 deletions(-)

diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
index 07c3d46abb87..163b1c82d445 100644
--- a/drivers/firmware/tegra/bpmp-private.h
+++ b/drivers/firmware/tegra/bpmp-private.h
@@ -10,6 +10,7 @@
 
 struct tegra_bpmp_ops {
 	int (*init)(struct tegra_bpmp *bpmp);
+	int (*init_providers)(struct tegra_bpmp *bpmp);
 	void (*deinit)(struct tegra_bpmp *bpmp);
 	bool (*is_response_ready)(struct tegra_bpmp_channel *channel);
 	bool (*is_request_ready)(struct tegra_bpmp_channel *channel);
@@ -26,4 +27,9 @@ struct tegra_bpmp_ops {
 extern const struct tegra_bpmp_ops tegra186_bpmp_ops;
 extern const struct tegra_bpmp_ops tegra210_bpmp_ops;
 
+int tegra_bpmp_init_channels(struct tegra_bpmp *bpmp);
+int tegra_bpmp_init_ping(struct tegra_bpmp *bpmp);
+void tegra_bpmp_deinit_ping(struct tegra_bpmp *bpmp);
+int tegra_bpmp_init_dt_providers(struct tegra_bpmp *bpmp);
+
 #endif
diff --git a/drivers/firmware/tegra/bpmp-tegra186.c b/drivers/firmware/tegra/bpmp-tegra186.c
index 64863db7a715..c34f6d2d275c 100644
--- a/drivers/firmware/tegra/bpmp-tegra186.c
+++ b/drivers/firmware/tegra/bpmp-tegra186.c
@@ -325,6 +325,10 @@ static int tegra186_bpmp_init(struct tegra_bpmp *bpmp)
 	struct tegra186_bpmp *priv;
 	int err;
 
+	err = tegra_bpmp_init_channels(bpmp);
+	if (err < 0)
+		return err;
+
 	priv = devm_kzalloc(bpmp->dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
 		return -ENOMEM;
@@ -352,6 +356,13 @@ static int tegra186_bpmp_init(struct tegra_bpmp *bpmp)
 
 	tegra186_bpmp_reset_channels(bpmp);
 
+	err = tegra_bpmp_init_ping(bpmp);
+	if (err < 0) {
+		mbox_free_channel(priv->mbox.channel);
+		tegra186_bpmp_teardown_channels(bpmp);
+		return err;
+	}
+
 	return 0;
 }
 
@@ -359,6 +370,8 @@ static void tegra186_bpmp_deinit(struct tegra_bpmp *bpmp)
 {
 	struct tegra186_bpmp *priv = bpmp->priv;
 
+	tegra_bpmp_deinit_ping(bpmp);
+
 	mbox_free_channel(priv->mbox.channel);
 
 	tegra186_bpmp_teardown_channels(bpmp);
@@ -373,6 +386,7 @@ static int tegra186_bpmp_resume(struct tegra_bpmp *bpmp)
 
 const struct tegra_bpmp_ops tegra186_bpmp_ops = {
 	.init = tegra186_bpmp_init,
+	.init_providers = tegra_bpmp_init_dt_providers,
 	.deinit = tegra186_bpmp_deinit,
 	.is_response_ready = tegra186_bpmp_is_message_ready,
 	.is_request_ready = tegra186_bpmp_is_message_ready,
diff --git a/drivers/firmware/tegra/bpmp-tegra210.c b/drivers/firmware/tegra/bpmp-tegra210.c
index 6295f5640c15..dbd02f750c2a 100644
--- a/drivers/firmware/tegra/bpmp-tegra210.c
+++ b/drivers/firmware/tegra/bpmp-tegra210.c
@@ -166,6 +166,10 @@ static int tegra210_bpmp_init(struct tegra_bpmp *bpmp)
 	unsigned int i;
 	int err;
 
+	err = tegra_bpmp_init_channels(bpmp);
+	if (err < 0)
+		return err;
+
 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
 		return -ENOMEM;
@@ -220,11 +224,18 @@ static int tegra210_bpmp_init(struct tegra_bpmp *bpmp)
 		return err;
 	}
 
-	return 0;
+	return tegra_bpmp_init_ping(bpmp);
+}
+
+static void tegra210_bpmp_deinit(struct tegra_bpmp *bpmp)
+{
+	tegra_bpmp_deinit_ping(bpmp);
 }
 
 const struct tegra_bpmp_ops tegra210_bpmp_ops = {
 	.init = tegra210_bpmp_init,
+	.init_providers = tegra_bpmp_init_dt_providers,
+	.deinit = tegra210_bpmp_deinit,
 	.is_response_ready = tegra210_bpmp_is_response_ready,
 	.is_request_ready = tegra210_bpmp_is_request_ready,
 	.ack_response = tegra210_bpmp_ack_response,
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index 0eb99b1c5068..bd8564865638 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -3,6 +3,7 @@
  * Copyright (c) 2016, NVIDIA CORPORATION.  All rights reserved.
  */
 
+#include <linux/acpi.h>
 #include <linux/clk/tegra.h>
 #include <linux/genalloc.h>
 #include <linux/mailbox_client.h>
@@ -11,8 +12,10 @@
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/pm.h>
+#include <linux/property.h>
 #include <linux/semaphore.h>
 #include <linux/sched/clock.h>
+#include <linux/slab.h>
 
 #include <soc/tegra/bpmp.h>
 #include <soc/tegra/bpmp-abi.h>
@@ -23,6 +26,12 @@
 #define MSG_ACK		BIT(0)
 #define MSG_RING	BIT(1)
 #define TAG_SZ		32
+#define TEGRA_BPMP_ACPI_BMRQ_DATA_SZ	3960U
+
+struct tegra_bpmp_acpi_message {
+	u64 status;
+	u8 *data;
+};
 
 static inline const struct tegra_bpmp_ops *
 channel_to_ops(struct tegra_bpmp_channel *channel)
@@ -343,12 +352,96 @@ static ssize_t tegra_bpmp_channel_write(struct tegra_bpmp_channel *channel,
 
 static int __maybe_unused tegra_bpmp_resume(struct device *dev);
 
+#ifdef CONFIG_ACPI
+static int tegra_bpmp_transfer_acpi(struct tegra_bpmp *bpmp,
+				    struct tegra_bpmp_message *msg)
+{
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer format = { sizeof("NB"), "NB" };
+	struct acpi_object_list param_list;
+	union acpi_object params[2];
+	struct tegra_bpmp_acpi_message *reply;
+	acpi_status status;
+	size_t data_len;
+	int err = 0;
+
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = msg->mrq;
+
+	params[1].type = ACPI_TYPE_BUFFER;
+	params[1].buffer.length = msg->tx.size;
+	params[1].buffer.pointer = (u8 *)msg->tx.data;
+
+	param_list.count = 2;
+	param_list.pointer = params;
+
+	status = acpi_evaluate_object(ACPI_HANDLE(bpmp->dev), "BMRQ",
+				      &param_list, &output);
+	if (ACPI_FAILURE(status)) {
+		acpi_evaluation_failure_warn(ACPI_HANDLE(bpmp->dev), "BMRQ",
+					     status);
+		return -ENODEV;
+	}
+
+	status = acpi_extract_package(output.pointer, &format, &response);
+	if (ACPI_FAILURE(status)) {
+		dev_err(bpmp->dev, "BMRQ: invalid response package: %s\n",
+			acpi_format_exception(status));
+		err = -ENODATA;
+		goto out;
+	}
+
+	if (response.length < sizeof(*reply)) {
+		dev_err(bpmp->dev, "BMRQ: response too short\n");
+		err = -ENODATA;
+		goto out;
+	}
+
+	reply = response.pointer;
+	data_len = response.length - sizeof(*reply);
+	if (data_len > TEGRA_BPMP_ACPI_BMRQ_DATA_SZ) {
+		dev_err(bpmp->dev, "BMRQ: reply buffer too large (%zu)\n",
+			data_len);
+		err = -EINVAL;
+		goto out;
+	}
+
+	msg->rx.ret = (int)reply->status;
+
+	if (msg->rx.data && msg->rx.size) {
+		if (data_len < msg->rx.size) {
+			dev_err(bpmp->dev, "BMRQ: response data too short\n");
+			err = -ENODATA;
+			goto out;
+		}
+
+		memcpy(msg->rx.data, reply->data, msg->rx.size);
+	}
+
+out:
+	kfree(response.pointer);
+	kfree(output.pointer);
+
+	return err;
+}
+#else
+static int tegra_bpmp_transfer_acpi(struct tegra_bpmp *bpmp,
+				    struct tegra_bpmp_message *msg)
+{
+	return -EOPNOTSUPP;
+}
+#endif
+
 int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp,
 			       struct tegra_bpmp_message *msg)
 {
 	struct tegra_bpmp_channel *channel;
 	int err;
 
+	if (WARN_ON(ACPI_HANDLE(bpmp->dev)))
+		return -EOPNOTSUPP;
+
 	if (WARN_ON(!irqs_disabled()))
 		return -EPERM;
 
@@ -389,19 +482,13 @@ int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp,
 }
 EXPORT_SYMBOL_GPL(tegra_bpmp_transfer_atomic);
 
-int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
-			struct tegra_bpmp_message *msg)
+static int tegra_bpmp_transfer_channel(struct tegra_bpmp *bpmp,
+				       struct tegra_bpmp_message *msg)
 {
 	struct tegra_bpmp_channel *channel;
 	unsigned long timeout;
 	int err;
 
-	if (WARN_ON(irqs_disabled()))
-		return -EPERM;
-
-	if (!tegra_bpmp_message_valid(msg))
-		return -EINVAL;
-
 	if (bpmp->suspended) {
 		/* Reset BPMP IPC channels during resume based on flags passed */
 		if (msg->flags & TEGRA_BPMP_MESSAGE_RESET)
@@ -428,6 +515,21 @@ int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
 	return tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size,
 				       &msg->rx.ret);
 }
+
+int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
+			struct tegra_bpmp_message *msg)
+{
+	if (WARN_ON(irqs_disabled()))
+		return -EPERM;
+
+	if (!tegra_bpmp_message_valid(msg))
+		return -EINVAL;
+
+	if (ACPI_HANDLE(bpmp->dev))
+		return tegra_bpmp_transfer_acpi(bpmp, msg);
+
+	return tegra_bpmp_transfer_channel(bpmp, msg);
+}
 EXPORT_SYMBOL_GPL(tegra_bpmp_transfer);
 
 static struct tegra_bpmp_mrq *tegra_bpmp_find_mrq(struct tegra_bpmp *bpmp,
@@ -606,11 +708,17 @@ static int tegra_bpmp_ping(struct tegra_bpmp *bpmp)
 	msg.rx.data = &response;
 	msg.rx.size = sizeof(response);
 
-	local_irq_save(flags);
 	start = ktime_get();
-	err = tegra_bpmp_transfer_atomic(bpmp, &msg);
+
+	if (ACPI_HANDLE(bpmp->dev)) {
+		err = tegra_bpmp_transfer(bpmp, &msg);
+	} else {
+		local_irq_save(flags);
+		err = tegra_bpmp_transfer_atomic(bpmp, &msg);
+		local_irq_restore(flags);
+	}
+
 	end = ktime_get();
-	local_irq_restore(flags);
 
 	if (!err)
 		dev_dbg(bpmp->dev,
@@ -632,6 +740,9 @@ static int tegra_bpmp_get_firmware_tag_old(struct tegra_bpmp *bpmp, char *tag,
 	void *virt;
 	int err;
 
+	if (ACPI_HANDLE(bpmp->dev))
+		return -EOPNOTSUPP;
+
 	if (size != TAG_SZ)
 		return -EINVAL;
 
@@ -733,7 +844,8 @@ void tegra_bpmp_handle_rx(struct tegra_bpmp *bpmp)
 	spin_unlock(&bpmp->lock);
 }
 
-static int tegra_bpmp_init_channels(struct tegra_bpmp *bpmp)
+int tegra_bpmp_init_channels(struct tegra_bpmp *bpmp)
+
 {
 	size_t size;
 
@@ -773,68 +885,95 @@ static int tegra_bpmp_init_channels(struct tegra_bpmp *bpmp)
 	return 0;
 }
 
-static int tegra_bpmp_probe(struct platform_device *pdev)
+int tegra_bpmp_init_ping(struct tegra_bpmp *bpmp)
 {
-	struct tegra_bpmp *bpmp;
-	char tag[TAG_SZ];
 	int err;
 
-	bpmp = devm_kzalloc(&pdev->dev, sizeof(*bpmp), GFP_KERNEL);
-	if (!bpmp)
-		return -ENOMEM;
-
-	bpmp->soc = of_device_get_match_data(&pdev->dev);
-	bpmp->dev = &pdev->dev;
-
-	err = tegra_bpmp_init_channels(bpmp);
-	if (err < 0)
-		return err;
-
-	platform_set_drvdata(pdev, bpmp);
-
-	err = bpmp->soc->ops->init(bpmp);
-	if (err < 0)
-		return err;
-
 	err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
 				     tegra_bpmp_mrq_handle_ping, bpmp);
 	if (err < 0)
-		goto deinit;
+		return err;
 
 	err = tegra_bpmp_ping(bpmp);
 	if (err < 0) {
-		dev_err(&pdev->dev, "failed to ping BPMP: %d\n", err);
+		dev_err(bpmp->dev, "failed to ping BPMP: %d\n", err);
 		goto free_mrq;
 	}
 
-	err = tegra_bpmp_get_firmware_tag(bpmp, tag, sizeof(tag));
-	if (err < 0) {
-		dev_err(&pdev->dev, "failed to get firmware tag: %d\n", err);
-		goto free_mrq;
-	}
+	return 0;
 
-	dev_info(&pdev->dev, "firmware: %.*s\n", (int)sizeof(tag), tag);
+free_mrq:
+	tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp);
+
+	return err;
+}
+
+void tegra_bpmp_deinit_ping(struct tegra_bpmp *bpmp)
+{
+	tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp);
+}
+
+int tegra_bpmp_init_dt_providers(struct tegra_bpmp *bpmp)
+{
+	struct device *dev = bpmp->dev;
+	int err;
 
-	err = of_platform_default_populate(pdev->dev.of_node, NULL, &pdev->dev);
+	err = of_platform_default_populate(dev->of_node, NULL, dev);
 	if (err < 0)
-		goto free_mrq;
+		return err;
 
-	if (of_property_present(pdev->dev.of_node, "#clock-cells")) {
+	if (of_property_present(dev->of_node, "#clock-cells")) {
 		err = tegra_bpmp_init_clocks(bpmp);
 		if (err < 0)
-			goto free_mrq;
+			return err;
 	}
 
-	if (of_property_present(pdev->dev.of_node, "#reset-cells")) {
+	if (of_property_present(dev->of_node, "#reset-cells")) {
 		err = tegra_bpmp_init_resets(bpmp);
 		if (err < 0)
-			goto free_mrq;
+			return err;
 	}
 
-	if (of_property_present(pdev->dev.of_node, "#power-domain-cells")) {
+	if (of_property_present(dev->of_node, "#power-domain-cells")) {
 		err = tegra_bpmp_init_powergates(bpmp);
 		if (err < 0)
-			goto free_mrq;
+			return err;
+	}
+
+	return 0;
+}
+
+static int tegra_bpmp_probe(struct platform_device *pdev)
+{
+	struct tegra_bpmp *bpmp;
+	char tag[TAG_SZ];
+	int err;
+
+	bpmp = devm_kzalloc(&pdev->dev, sizeof(*bpmp), GFP_KERNEL);
+	if (!bpmp)
+		return -ENOMEM;
+
+	bpmp->soc = device_get_match_data(&pdev->dev);
+	bpmp->dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, bpmp);
+
+	err = bpmp->soc->ops->init(bpmp);
+	if (err < 0)
+		return err;
+
+	err = tegra_bpmp_get_firmware_tag(bpmp, tag, sizeof(tag));
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to get firmware tag: %d\n", err);
+		goto deinit;
+	}
+
+	dev_info(&pdev->dev, "firmware: %.*s\n", (int)sizeof(tag), tag);
+
+	if (bpmp->soc->ops->init_providers) {
+		err = bpmp->soc->ops->init_providers(bpmp);
+		if (err < 0)
+			goto deinit;
 	}
 
 	err = tegra_bpmp_init_debugfs(bpmp);
@@ -843,8 +982,6 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
 
 	return 0;
 
-free_mrq:
-	tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp);
 deinit:
 	if (bpmp->soc->ops->deinit)
 		bpmp->soc->ops->deinit(bpmp);
@@ -867,7 +1004,7 @@ static int __maybe_unused tegra_bpmp_resume(struct device *dev)
 
 	bpmp->suspended = false;
 
-	if (bpmp->soc->ops->resume)
+	if (bpmp->soc->ops && bpmp->soc->ops->resume)
 		return bpmp->soc->ops->resume(bpmp);
 	else
 		return 0;
@@ -939,10 +1076,36 @@ static const struct of_device_id tegra_bpmp_match[] = {
 	{ }
 };
 
+#ifdef CONFIG_ACPI
+static int tegra_bpmp_acpi_init(struct tegra_bpmp *bpmp)
+{
+	return tegra_bpmp_ping(bpmp);
+}
+
+static const struct tegra_bpmp_ops tegra_bpmp_acpi_ops = {
+	.init = tegra_bpmp_acpi_init,
+};
+
+static const struct tegra_bpmp_soc tegra_bpmp_acpi_soc = {
+	.ops = &tegra_bpmp_acpi_ops,
+};
+
+static const struct acpi_device_id tegra_bpmp_acpi_match[] = {
+	{
+		.id = "NVDA3001",
+		.driver_data = (kernel_ulong_t)&tegra_bpmp_acpi_soc,
+	},
+	{ }
+};
+#endif
+
 static struct platform_driver tegra_bpmp_driver = {
 	.driver = {
 		.name = "tegra-bpmp",
 		.of_match_table = tegra_bpmp_match,
+#ifdef CONFIG_ACPI
+		.acpi_match_table = tegra_bpmp_acpi_match,
+#endif
 		.pm = &tegra_bpmp_pm_ops,
 		.suppress_bind_attrs = true,
 	},
-- 
2.43.0


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

* [PATCH v5 3/5] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions
  2026-09-29  9:59 [PATCH v5 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
  2026-09-29  9:59 ` [PATCH v5 1/5] firmware: tegra: bpmp: Move channel initialization to helper Aniruddha Rao
  2026-09-29  9:59 ` [PATCH v5 2/5] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
@ 2026-09-29  9:59 ` Aniruddha Rao
  2026-09-29  9:59 ` [PATCH v5 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers Aniruddha Rao
  2026-09-29  9:59 ` [PATCH v5 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface Aniruddha Rao
  4 siblings, 0 replies; 6+ messages in thread
From: Aniruddha Rao @ 2026-09-29  9:59 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

Different workloads can place different memory-bandwidth demands on the
system. BPMP firmware of some Tegra platforms provides Memory Bandwidth
Throttler (MBWT) controls for PCIe and GPU connected over chip-to-chip
link (NVCLINK) traffic on the path to DRAM so software can query and
program those bandwidth caps.

The MBWT controls are not exposed through ordinary host MMIO and are
accessible only through BPMP firmware requests.

Add the ABI definitions for the MBWT message request (MRQ), including
the MRQ ID, sub-command IDs, and request/response payloads needed to
issue the MBWT control requests defined by BPMP firmware.

Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
Changes since v4:
- No change.

Changes since v3:
- No change.

Changes since v2:
- No change.

Changes since v1:
- Use NVCLINK spelling consistently.

 include/soc/tegra/bpmp-abi.h | 161 ++++++++++++++++++++++++++++++++++-
 1 file changed, 160 insertions(+), 1 deletion(-)

diff --git a/include/soc/tegra/bpmp-abi.h b/include/soc/tegra/bpmp-abi.h
index 5c64b3e02211..6f25126f5fb8 100644
--- a/include/soc/tegra/bpmp-abi.h
+++ b/include/soc/tegra/bpmp-abi.h
@@ -587,12 +587,13 @@ struct mrq_response {
 #define MRQ_HWPM		93U
 #define MRQ_DVFS		94U
 #define MRQ_PPP_PROFILE		95U
+#define MRQ_SOCHUB_MBWT		96U
 
 /**
  * @brief Maximum MRQ code to be sent by CPU software to
  * BPMP. Subject to change in future
  */
-#define MAX_CPU_MRQ_ID		95U
+#define MAX_CPU_MRQ_ID		96U
 
 /** @} */
 
@@ -6686,6 +6687,164 @@ struct mrq_ppp_profile_response {
 /** @} PPP */
 /** @endcond */
 
+/** @cond (bpmp_tb500)
+ * @ingroup MRQ_Codes
+ * @def MRQ_SOCHUB_MBWT
+ * @brief Configure per-virtual-channel bandwidth caps for a bandwidth group using
+ *        Memory Bandwidth Throttler (MBWT).
+ *
+ * * Initiators: Any
+ * * Targets: BPMP
+ * * Request Payload: @ref mrq_sochub_mbwt_request
+ * * Response Payload: @ref mrq_sochub_mbwt_response
+ *
+ * @addtogroup SOCHUB_MBWT
+ * @{
+ */
+
+/**
+ * @brief Sub-command identifiers for #MRQ_SOCHUB_MBWT.
+ */
+enum mrq_sochub_mbwt_cmd {
+	/**
+	 * @brief Check whether the BPMP-FW supports the specified
+	 * #MRQ_SOCHUB_MBWT sub-command.
+	 *
+	 * mrq_response::err is 0 if the specified request is
+	 * supported and -#BPMP_ENODEV otherwise.
+	 */
+	CMD_SOCHUB_MBWT_QUERY_ABI = 0,
+	/**
+	 * @brief Get bandwidth cap for a virtual channel
+	 *
+	 * mrq_response::err is defined as:
+	 *
+	 * | Value          | Description                                    |
+	 * |----------------|------------------------------------------------|
+	 * | 0              | Success                                        |
+	 * | -#BPMP_ENOTSUP | #MRQ_SOCHUB_MBWT is not supported by BPMP-FW.  |
+	 * | -#BPMP_EINVAL  | Invalid request parameters.                    |
+	 * | -#BPMP_EIO     | Failed to retrieve the bandwidth.              |
+	 */
+	CMD_SOCHUB_MBWT_GET_BW = 1,
+	/**
+	 * @brief Set bandwidth cap for a virtual channel.
+	 *
+	 * mrq_response::err is defined as:
+	 *
+	 * | Value          | Description                                    |
+	 * |----------------|------------------------------------------------|
+	 * | 0              | Success                                        |
+	 * | -#BPMP_ENOTSUP | #MRQ_SOCHUB_MBWT is not supported by BPMP-FW.  |
+	 * | -#BPMP_EINVAL  | Invalid request parameters.                    |
+	 * | -#BPMP_EIO     | Failed to set the bandwidth.                   |
+	 */
+	CMD_SOCHUB_MBWT_SET_BW = 2,
+};
+
+/**
+ * @brief Request data for #MRQ_SOCHUB_MBWT sub-command
+ *        #CMD_SOCHUB_MBWT_QUERY_ABI
+ */
+struct cmd_sochub_mbwt_query_abi_req {
+	/** @brief Sub-command identifier from @ref mrq_sochub_mbwt_cmd */
+	uint32_t cmd_code;
+} BPMP_ABI_PACKED;
+
+/**
+ * @brief Request data for #MRQ_SOCHUB_MBWT sub-command
+ *        #CMD_SOCHUB_MBWT_GET_BW
+ */
+struct cmd_sochub_mbwt_get_bw_req {
+	/**
+	 * @brief Instance ID for the bandwidth group
+	 *
+	 * Valid range is [0, 5]
+	 */
+	uint32_t instance;
+	/**
+	 * @brief Type of the virtual channel/traffic
+	 *
+	 * Valid values:
+	 *
+	 * * Value 0: PCIe read
+	 * * Value 1: PCIe write
+	 * * Value 2: GPU connected over the chip-to-chip link (NVCLINK)
+	 */
+	uint32_t vc_type;
+} BPMP_ABI_PACKED;
+
+/**
+ * @brief Request data for #MRQ_SOCHUB_MBWT sub-command
+ *        #CMD_SOCHUB_MBWT_SET_BW
+ */
+struct cmd_sochub_mbwt_set_bw_req {
+	/**
+	 * @brief Instance ID for the bandwidth group
+	 *
+	 * Valid range is [0, 5]
+	 */
+	uint32_t instance;
+	/**
+	 * @brief Type of the virtual channel/traffic
+	 *
+	 * Valid values:
+	 *
+	 * * Value 0: PCIe read
+	 * * Value 1: PCIe write
+	 * * Value 2: GPU connected over the chip-to-chip link (NVCLINK)
+	 */
+	uint32_t vc_type;
+	/** @brief Bandwidth cap in GB/s */
+	uint32_t bw;
+} BPMP_ABI_PACKED;
+
+/**
+ * @brief Response data for #MRQ_SOCHUB_MBWT sub-command
+ *        #CMD_SOCHUB_MBWT_GET_BW
+ */
+struct cmd_sochub_mbwt_get_bw_resp {
+	/** @brief Bandwidth cap in GB/s */
+	uint32_t bw;
+} BPMP_ABI_PACKED;
+
+/**
+ * @brief Request payload for the #MRQ_SOCHUB_MBWT -command
+ *
+ * | Sub-command                   | Request payload                        |
+ * |-------------------------------|----------------------------------------|
+ * | #CMD_SOCHUB_MBWT_QUERY_ABI    | #cmd_sochub_mbwt_query_abi_req         |
+ * | #CMD_SOCHUB_MBWT_GET_BW       | #cmd_sochub_mbwt_get_bw_req            |
+ * | #CMD_SOCHUB_MBWT_SET_BW       | #cmd_sochub_mbwt_set_bw_req            |
+ */
+struct mrq_sochub_mbwt_request {
+	/** @brief Sub-command ID from @ref mrq_sochub_mbwt_cmd. */
+	uint32_t cmd;
+	union {
+		struct cmd_sochub_mbwt_query_abi_req query_abi;
+		struct cmd_sochub_mbwt_get_bw_req get_bw;
+		struct cmd_sochub_mbwt_set_bw_req set_bw;
+	} BPMP_UNION_ANON;
+} BPMP_ABI_PACKED;
+
+/**
+ * @brief Response payload for the #MRQ_SOCHUB_MBWT -command.
+ *
+ * | Sub-command                   | Response payload                       |
+ * |-------------------------------|----------------------------------------|
+ * | #CMD_SOCHUB_MBWT_QUERY_ABI    | -                                      |
+ * | #CMD_SOCHUB_MBWT_GET_BW       | #cmd_sochub_mbwt_get_bw_resp           |
+ * | #CMD_SOCHUB_MBWT_SET_BW       | -                                      |
+ */
+struct mrq_sochub_mbwt_response {
+	union {
+		struct cmd_sochub_mbwt_get_bw_resp get_bw;
+	} BPMP_UNION_ANON;
+} BPMP_ABI_PACKED;
+
+/** @} SOCHUB_MBWT */
+/** @endcond */
+
 /**
  * @addtogroup Error_Codes
  * Negative values for mrq_response::err generally indicate some
-- 
2.43.0


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

* [PATCH v5 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers
  2026-09-29  9:59 [PATCH v5 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
                   ` (2 preceding siblings ...)
  2026-09-29  9:59 ` [PATCH v5 3/5] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions Aniruddha Rao
@ 2026-09-29  9:59 ` Aniruddha Rao
  2026-09-29  9:59 ` [PATCH v5 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface Aniruddha Rao
  4 siblings, 0 replies; 6+ messages in thread
From: Aniruddha Rao @ 2026-09-29  9:59 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

Add helper functions that send MBWT requests to BPMP firmware.

The helpers implement the GET_BW and SET_BW operations.

They also provide a QUERY_ABI-based probe for command availability.

Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
Changes since v4:
- No change.

Changes since v3:
- No change.

Changes since v2:
- Move the MBWT helpers near probe.
- Explain why the MBWT helpers are needed in the commit message.

Changes since v1:
- Use generic MBWT helper names.
- Fold the MBWT helpers into bpmp.c.
- Drop MBWT Kconfig dependencies.
- Add firmware error handling for MBWT requests.

 drivers/firmware/tegra/bpmp-private.h |   7 ++
 drivers/firmware/tegra/bpmp.c         | 111 +++++++++++++++++++++++++-
 2 files changed, 117 insertions(+), 1 deletion(-)

diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
index 163b1c82d445..f0a7d6254bdc 100644
--- a/drivers/firmware/tegra/bpmp-private.h
+++ b/drivers/firmware/tegra/bpmp-private.h
@@ -32,4 +32,11 @@ int tegra_bpmp_init_ping(struct tegra_bpmp *bpmp);
 void tegra_bpmp_deinit_ping(struct tegra_bpmp *bpmp);
 int tegra_bpmp_init_dt_providers(struct tegra_bpmp *bpmp);
 
+bool tegra_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
+				      unsigned int cmd_code);
+int tegra_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
+			unsigned int vc_type, unsigned int *bandwidth);
+int tegra_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
+			unsigned int vc_type, unsigned int bandwidth);
+
 #endif
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index bd8564865638..f2b739977f13 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -844,8 +844,117 @@ void tegra_bpmp_handle_rx(struct tegra_bpmp *bpmp)
 	spin_unlock(&bpmp->lock);
 }
 
-int tegra_bpmp_init_channels(struct tegra_bpmp *bpmp)
 
+/*
+ * MBWT controls are not exposed through ordinary host MMIO and are accessed
+ * through BPMP firmware requests. These helpers provide the request wrappers
+ * used by bandwidth control callers.
+ */
+bool tegra_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
+				      unsigned int cmd_code)
+{
+	struct mrq_sochub_mbwt_request request = {
+		.cmd = CMD_SOCHUB_MBWT_QUERY_ABI,
+		.query_abi.cmd_code = cmd_code,
+	};
+	struct tegra_bpmp_message msg = {
+		.mrq = MRQ_SOCHUB_MBWT,
+		.tx = {
+			.data = &request,
+			.size = sizeof(request),
+		},
+	};
+	int err;
+
+	err = tegra_bpmp_transfer(bpmp, &msg);
+	if (err || msg.rx.ret)
+		return false;
+
+	return true;
+}
+
+int tegra_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
+			unsigned int vc_type, unsigned int *bandwidth)
+{
+	struct mrq_sochub_mbwt_request request = {
+		.cmd = CMD_SOCHUB_MBWT_GET_BW,
+		.get_bw = {
+			.instance = instance,
+			.vc_type = vc_type,
+		},
+	};
+	struct mrq_sochub_mbwt_response response = {};
+	struct tegra_bpmp_message msg = {
+		.mrq = MRQ_SOCHUB_MBWT,
+		.tx = {
+			.data = &request,
+			.size = sizeof(request),
+		},
+		.rx = {
+			.data = &response,
+			.size = sizeof(response),
+		},
+	};
+	int err;
+
+	if (!bandwidth)
+		return -EINVAL;
+
+	err = tegra_bpmp_transfer(bpmp, &msg);
+	if (err) {
+		dev_err(bpmp->dev, "MBWT get bandwidth transfer failed: %d\n",
+			err);
+		return err;
+	}
+
+	if (msg.rx.ret) {
+		dev_err(bpmp->dev, "MBWT get bandwidth failed: BPMP error %d\n",
+			msg.rx.ret);
+		return -EIO;
+	}
+
+	*bandwidth = response.get_bw.bw;
+
+	return 0;
+}
+
+int tegra_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
+			unsigned int vc_type, unsigned int bandwidth)
+{
+	struct mrq_sochub_mbwt_request request = {
+		.cmd = CMD_SOCHUB_MBWT_SET_BW,
+		.set_bw = {
+			.instance = instance,
+			.vc_type = vc_type,
+			.bw = bandwidth,
+		},
+	};
+	struct tegra_bpmp_message msg = {
+		.mrq = MRQ_SOCHUB_MBWT,
+		.tx = {
+			.data = &request,
+			.size = sizeof(request),
+		},
+	};
+	int err;
+
+	err = tegra_bpmp_transfer(bpmp, &msg);
+	if (err) {
+		dev_err(bpmp->dev, "MBWT set bandwidth transfer failed: %d\n",
+			err);
+		return err;
+	}
+
+	if (msg.rx.ret) {
+		dev_err(bpmp->dev, "MBWT set bandwidth failed: BPMP error %d\n",
+			msg.rx.ret);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int tegra_bpmp_init_channels(struct tegra_bpmp *bpmp)
 {
 	size_t size;
 
-- 
2.43.0


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

* [PATCH v5 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface
  2026-09-29  9:59 [PATCH v5 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
                   ` (3 preceding siblings ...)
  2026-09-29  9:59 ` [PATCH v5 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers Aniruddha Rao
@ 2026-09-29  9:59 ` Aniruddha Rao
  4 siblings, 0 replies; 6+ messages in thread
From: Aniruddha Rao @ 2026-09-29  9:59 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

Different workloads can place different memory-bandwidth demands on the
system. Selecting an appropriate bandwidth limit depends on the runtime
workload mix and on the devices carrying that traffic, such as PCIe
traffic or GPU traffic connected over the chip-to-chip link (NVCLINK).
That information is not available to the kernel.

BPMP firmware of some Tegra platforms provides Memory Bandwidth
Throttler (MBWT) controls for PCIe and GPU traffic connected over
NVCLINK on the path to DRAM. Each PCIe bandwidth group has a single
shared cap for all traffic in that group. Bandwidth for a group can be
set per traffic type.

Describe the available bandwidth groups and traffic types in platform
data. Each entry maps a sysfs name to the corresponding firmware
instance and VC type so that these values can vary between platforms.

Add sysfs attributes on the tegra-bpmp platform device to expose a
narrow userspace interface for MBWT control. The attributes are arranged
as mbwt/<bandwidth-group>/<traffic-type>/bandwidth. The directory names
come from platform data. The current platform description uses pcie0
through pcie5 for bandwidth groups and pcie_read, pcie_write and
nvclink for traffic types.

Reading a bandwidth attribute queries firmware for the selected bandwidth
group and traffic type. Writing an integer programs the target bandwidth
cap in GB/s for that bandwidth group and traffic type.

Register the attributes only when BPMP firmware reports support for the
MBWT GET_BW and SET_BW requests through its query ABI.

Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
Changes since v4:
- Describe MBWT bandwidth groups and traffic types with platform data so
  firmware instance and VC type values can vary between platforms.
- Document the sysfs hierarchy using platform-defined bandwidth-group and
  traffic-type names.

Changes since v3:
- Update the sysfs ABI Documentation date to September 2026 and target
  release to 7.4.

Changes since v2:
- Rename bpmp-tegra-sysfs.c to bpmp-sysfs.c.

Changes since v1:
- Register the interface only when firmware supports GET_BW and SET_BW.
- Expose per-traffic bandwidth attributes.
- Remove software bandwidth range checks.
- Guard sysfs registration with CONFIG_SYSFS.
- Rename the sysfs initialization helper.
- Use generic MBWT wording.

 .../ABI/testing/sysfs-platform-tegra-bpmp     |  48 ++++
 drivers/firmware/tegra/Makefile               |   1 +
 drivers/firmware/tegra/bpmp-private.h         |  26 +++
 drivers/firmware/tegra/bpmp-sysfs.c           | 207 ++++++++++++++++++
 drivers/firmware/tegra/bpmp.c                 |  35 +++
 include/soc/tegra/bpmp.h                      |   2 +
 6 files changed, 319 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-platform-tegra-bpmp
 create mode 100644 drivers/firmware/tegra/bpmp-sysfs.c

diff --git a/Documentation/ABI/testing/sysfs-platform-tegra-bpmp b/Documentation/ABI/testing/sysfs-platform-tegra-bpmp
new file mode 100644
index 000000000000..5d0b525bd72a
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-tegra-bpmp
@@ -0,0 +1,48 @@
+What:		/sys/bus/platform/devices/<bpmp-device>/mbwt/<bandwidth-group>/<traffic-type>/bandwidth
+Date:		September 2026
+KernelVersion:	7.4
+Contact:	Aniruddha TVS Rao <anrao@nvidia.com>
+Description:
+		Provides access to Memory Bandwidth Throttler (MBWT)
+		controls exposed by BPMP firmware for PCIe traffic and GPU
+		traffic connected over the chip-to-chip link (NVCLINK) on
+		the path to DRAM.
+
+		The attributes are present only when BPMP firmware reports
+		support for the MBWT GET_BW and SET_BW requests through the
+		MBWT query ABI.
+
+		Each <bandwidth-group> directory identifies one PCIe bandwidth
+		group. The available groups and their directory names are
+		platform-specific. Current platforms use pcie0 through pcie5.
+		Each bandwidth group has a single shared cap for all traffic
+		in that group. A group may contain only PCIe devices, only a
+		GPU connected over NVCLINK, or both PCIe and GPU traffic in a
+		bifurcated topology.
+
+		The <traffic-type> directory selects the traffic type for the
+		selected group. The following traffic types may be available:
+
+		pcie_read
+			PCIe read traffic
+
+		pcie_write
+			PCIe write traffic
+
+		nvclink
+			GPU traffic connected over NVCLINK
+
+		Reading a bandwidth attribute returns the bandwidth cap in GB/s
+		reported by firmware for that bandwidth group and traffic
+		type.
+
+		Writing an integer to a bandwidth attribute programs the target
+		bandwidth cap in GB/s for that bandwidth group and traffic
+		type.
+
+		Examples:
+			cat .../mbwt/pcie0/pcie_write/bandwidth
+			echo 100 > .../mbwt/pcie0/pcie_write/bandwidth
+
+Users:		Platform integration and bandwidth tuning on systems with BPMP
+		firmware MBWT support.
diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile
index 41e2e4dc31d6..97f7a5db9d0a 100644
--- a/drivers/firmware/tegra/Makefile
+++ b/drivers/firmware/tegra/Makefile
@@ -6,5 +6,6 @@ tegra-bpmp-$(CONFIG_ARCH_TEGRA_194_SOC)	+= bpmp-tegra186.o
 tegra-bpmp-$(CONFIG_ARCH_TEGRA_234_SOC)	+= bpmp-tegra186.o
 tegra-bpmp-$(CONFIG_ARCH_TEGRA_264_SOC)	+= bpmp-tegra186.o
 tegra-bpmp-$(CONFIG_DEBUG_FS)	+= bpmp-debugfs.o
+tegra-bpmp-$(CONFIG_SYSFS)	+= bpmp-sysfs.o
 obj-$(CONFIG_TEGRA_BPMP)	+= tegra-bpmp.o
 obj-$(CONFIG_TEGRA_IVC)		+= ivc.o
diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
index f0a7d6254bdc..39cf31771836 100644
--- a/drivers/firmware/tegra/bpmp-private.h
+++ b/drivers/firmware/tegra/bpmp-private.h
@@ -24,6 +24,23 @@ struct tegra_bpmp_ops {
 	int (*resume)(struct tegra_bpmp *bpmp);
 };
 
+struct tegra_bpmp_mbwt_vc {
+	const char *name;
+	unsigned int type;
+};
+
+struct tegra_bpmp_mbwt_group {
+	const char *name;
+	unsigned int id;
+	const struct tegra_bpmp_mbwt_vc *vcs;
+	unsigned int num_vcs;
+};
+
+struct tegra_bpmp_mbwt_soc {
+	const struct tegra_bpmp_mbwt_group *groups;
+	unsigned int num_groups;
+};
+
 extern const struct tegra_bpmp_ops tegra186_bpmp_ops;
 extern const struct tegra_bpmp_ops tegra210_bpmp_ops;
 
@@ -39,4 +56,13 @@ int tegra_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
 int tegra_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
 			unsigned int vc_type, unsigned int bandwidth);
 
+#ifdef CONFIG_SYSFS
+int tegra_bpmp_init_sysfs(struct tegra_bpmp *bpmp);
+#else
+static inline int tegra_bpmp_init_sysfs(struct tegra_bpmp *bpmp)
+{
+	return 0;
+}
+#endif
+
 #endif
diff --git a/drivers/firmware/tegra/bpmp-sysfs.c b/drivers/firmware/tegra/bpmp-sysfs.c
new file mode 100644
index 000000000000..c7bf7f13f5d7
--- /dev/null
+++ b/drivers/firmware/tegra/bpmp-sysfs.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION.
+ */
+
+#include <linux/device.h>
+#include <linux/kobject.h>
+#include <linux/kstrtox.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#include <soc/tegra/bpmp.h>
+#include <soc/tegra/bpmp-abi.h>
+
+#include "bpmp-private.h"
+
+struct tegra_bpmp_mbwt_attr {
+	struct kobj_attribute attr;
+	struct tegra_bpmp_mbwt_sysfs *mbwt;
+	struct kobject *kobj;
+	unsigned int instance;
+	unsigned int vc_type;
+};
+
+struct tegra_bpmp_mbwt_sysfs {
+	struct tegra_bpmp *bpmp;
+	struct kobject *root;
+	struct kobject **groups;
+	struct tegra_bpmp_mbwt_attr *attrs;
+	unsigned int num_groups;
+	unsigned int num_attrs;
+	/* Serializes bandwidth requests to firmware. */
+	struct mutex lock;
+};
+
+static struct tegra_bpmp_mbwt_attr *
+tegra_bpmp_mbwt_attr_from_kobj_attr(struct kobj_attribute *attr)
+{
+	return container_of(attr, struct tegra_bpmp_mbwt_attr, attr);
+}
+
+static ssize_t tegra_bpmp_mbwt_show(struct kobject *kobj,
+				    struct kobj_attribute *attr, char *buf)
+{
+	struct tegra_bpmp_mbwt_attr *mbwt_attr;
+	struct tegra_bpmp_mbwt_sysfs *mbwt;
+	unsigned int bandwidth;
+	int err;
+
+	mbwt_attr = tegra_bpmp_mbwt_attr_from_kobj_attr(attr);
+	mbwt = mbwt_attr->mbwt;
+
+	mutex_lock(&mbwt->lock);
+	err = tegra_bpmp_mbwt_get(mbwt->bpmp, mbwt_attr->instance,
+				  mbwt_attr->vc_type, &bandwidth);
+	mutex_unlock(&mbwt->lock);
+	if (err)
+		return err;
+
+	return sysfs_emit(buf, "%u\n", bandwidth);
+}
+
+static ssize_t tegra_bpmp_mbwt_store(struct kobject *kobj,
+				     struct kobj_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct tegra_bpmp_mbwt_attr *mbwt_attr;
+	struct tegra_bpmp_mbwt_sysfs *mbwt;
+	unsigned int bandwidth;
+	int err;
+
+	err = kstrtou32(buf, 0, &bandwidth);
+	if (err)
+		return err;
+
+	mbwt_attr = tegra_bpmp_mbwt_attr_from_kobj_attr(attr);
+	mbwt = mbwt_attr->mbwt;
+
+	mutex_lock(&mbwt->lock);
+	err = tegra_bpmp_mbwt_set(mbwt->bpmp, mbwt_attr->instance,
+				  mbwt_attr->vc_type, bandwidth);
+	mutex_unlock(&mbwt->lock);
+	if (err)
+		return err;
+
+	return count;
+}
+
+static void tegra_bpmp_mbwt_sysfs_teardown(void *data)
+{
+	struct tegra_bpmp_mbwt_sysfs *mbwt = data;
+	unsigned int i;
+
+	for (i = 0; i < mbwt->num_attrs; i++) {
+		sysfs_remove_file(mbwt->attrs[i].kobj,
+				  &mbwt->attrs[i].attr.attr);
+		kobject_put(mbwt->attrs[i].kobj);
+	}
+
+	for (i = 0; i < mbwt->num_groups; i++)
+		kobject_put(mbwt->groups[i]);
+
+	kobject_put(mbwt->root);
+}
+
+static int tegra_bpmp_mbwt_sysfs_add_group(struct tegra_bpmp_mbwt_sysfs *mbwt,
+					   const struct tegra_bpmp_mbwt_group *group)
+{
+	struct tegra_bpmp_mbwt_attr *attr;
+	struct kobject *group_kobj;
+	unsigned int i;
+	int err;
+
+	group_kobj = kobject_create_and_add(group->name, mbwt->root);
+	if (!group_kobj)
+		return -ENOMEM;
+
+	mbwt->groups[mbwt->num_groups++] = group_kobj;
+
+	for (i = 0; i < group->num_vcs; i++) {
+		attr = &mbwt->attrs[mbwt->num_attrs];
+		attr->kobj = kobject_create_and_add(group->vcs[i].name,
+						    group_kobj);
+		if (!attr->kobj)
+			return -ENOMEM;
+
+		sysfs_attr_init(&attr->attr.attr);
+		attr->attr.attr.name = "bandwidth";
+		attr->attr.attr.mode = 0644;
+		attr->attr.show = tegra_bpmp_mbwt_show;
+		attr->attr.store = tegra_bpmp_mbwt_store;
+		attr->mbwt = mbwt;
+		attr->instance = group->id;
+		attr->vc_type = group->vcs[i].type;
+
+		err = sysfs_create_file(attr->kobj, &attr->attr.attr);
+		if (err) {
+			kobject_put(attr->kobj);
+			return err;
+		}
+
+		mbwt->num_attrs++;
+	}
+
+	return 0;
+}
+
+int tegra_bpmp_init_sysfs(struct tegra_bpmp *bpmp)
+{
+	const struct tegra_bpmp_mbwt_soc *soc = bpmp->soc->mbwt;
+	struct tegra_bpmp_mbwt_sysfs *mbwt;
+	unsigned int i, num_attrs = 0;
+	int err;
+
+	if (!soc)
+		return 0;
+
+	if (!tegra_bpmp_mrq_is_supported(bpmp, MRQ_SOCHUB_MBWT))
+		return 0;
+
+	if (!tegra_bpmp_mbwt_cmd_is_supported(bpmp, CMD_SOCHUB_MBWT_GET_BW) ||
+	    !tegra_bpmp_mbwt_cmd_is_supported(bpmp, CMD_SOCHUB_MBWT_SET_BW))
+		return 0;
+
+	mbwt = devm_kzalloc(bpmp->dev, sizeof(*mbwt), GFP_KERNEL);
+	if (!mbwt)
+		return -ENOMEM;
+
+	mbwt->bpmp = bpmp;
+	mutex_init(&mbwt->lock);
+
+	mbwt->groups = devm_kcalloc(bpmp->dev, soc->num_groups,
+				    sizeof(*mbwt->groups), GFP_KERNEL);
+	if (!mbwt->groups)
+		return -ENOMEM;
+
+	for (i = 0; i < soc->num_groups; i++)
+		num_attrs += soc->groups[i].num_vcs;
+
+	mbwt->attrs = devm_kcalloc(bpmp->dev, num_attrs,
+				   sizeof(*mbwt->attrs), GFP_KERNEL);
+	if (!mbwt->attrs)
+		return -ENOMEM;
+
+	mbwt->root = kobject_create_and_add("mbwt", &bpmp->dev->kobj);
+	if (!mbwt->root)
+		return -ENOMEM;
+
+	for (i = 0; i < soc->num_groups; i++) {
+		err = tegra_bpmp_mbwt_sysfs_add_group(mbwt, &soc->groups[i]);
+		if (err)
+			goto remove_sysfs;
+	}
+
+	err = devm_add_action_or_reset(bpmp->dev,
+				       tegra_bpmp_mbwt_sysfs_teardown, mbwt);
+	if (err)
+		return err;
+
+	return 0;
+
+remove_sysfs:
+	tegra_bpmp_mbwt_sysfs_teardown(mbwt);
+
+	return err;
+}
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index f2b739977f13..d91e833120b1 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -1085,6 +1085,10 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
 			goto deinit;
 	}
 
+	err = tegra_bpmp_init_sysfs(bpmp);
+	if (err < 0)
+		dev_err(&pdev->dev, "failed to initialize sysfs: %d\n", err);
+
 	err = tegra_bpmp_init_debugfs(bpmp);
 	if (err < 0)
 		dev_err(&pdev->dev, "debugfs initialization failed: %d\n", err);
@@ -1186,6 +1190,36 @@ static const struct of_device_id tegra_bpmp_match[] = {
 };
 
 #ifdef CONFIG_ACPI
+#define TEGRA_BPMP_MBWT_VC_PCIE_READ	0
+#define TEGRA_BPMP_MBWT_VC_PCIE_WRITE	1
+#define TEGRA_BPMP_MBWT_VC_NVCLINK	2
+
+static const struct tegra_bpmp_mbwt_vc tegra_bpmp_acpi_mbwt_vcs[] = {
+	{ .name = "pcie_read", .type = TEGRA_BPMP_MBWT_VC_PCIE_READ },
+	{ .name = "pcie_write", .type = TEGRA_BPMP_MBWT_VC_PCIE_WRITE },
+	{ .name = "nvclink", .type = TEGRA_BPMP_MBWT_VC_NVCLINK },
+};
+
+static const struct tegra_bpmp_mbwt_group tegra_bpmp_acpi_mbwt_groups[] = {
+	{ .name = "pcie0", .id = 0, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+	  .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+	{ .name = "pcie1", .id = 1, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+	  .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+	{ .name = "pcie2", .id = 2, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+	  .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+	{ .name = "pcie3", .id = 3, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+	  .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+	{ .name = "pcie4", .id = 4, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+	  .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+	{ .name = "pcie5", .id = 5, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+	  .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+};
+
+static const struct tegra_bpmp_mbwt_soc tegra_bpmp_acpi_mbwt_soc = {
+	.groups = tegra_bpmp_acpi_mbwt_groups,
+	.num_groups = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_groups),
+};
+
 static int tegra_bpmp_acpi_init(struct tegra_bpmp *bpmp)
 {
 	return tegra_bpmp_ping(bpmp);
@@ -1197,6 +1231,7 @@ static const struct tegra_bpmp_ops tegra_bpmp_acpi_ops = {
 
 static const struct tegra_bpmp_soc tegra_bpmp_acpi_soc = {
 	.ops = &tegra_bpmp_acpi_ops,
+	.mbwt = &tegra_bpmp_acpi_mbwt_soc,
 };
 
 static const struct acpi_device_id tegra_bpmp_acpi_match[] = {
diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h
index a33582590a3b..f55db918ccb1 100644
--- a/include/soc/tegra/bpmp.h
+++ b/include/soc/tegra/bpmp.h
@@ -16,6 +16,7 @@
 #include <soc/tegra/bpmp-abi.h>
 
 struct tegra_bpmp_clk;
+struct tegra_bpmp_mbwt_soc;
 struct tegra_bpmp_ops;
 
 struct tegra_bpmp_soc {
@@ -28,6 +29,7 @@ struct tegra_bpmp_soc {
 	} channels;
 
 	const struct tegra_bpmp_ops *ops;
+	const struct tegra_bpmp_mbwt_soc *mbwt;
 	unsigned int num_resets;
 };
 
-- 
2.43.0


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

end of thread, other threads:[~2026-09-29 10:00 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-29  9:59 [PATCH v5 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
2026-09-29  9:59 ` [PATCH v5 1/5] firmware: tegra: bpmp: Move channel initialization to helper Aniruddha Rao
2026-09-29  9:59 ` [PATCH v5 2/5] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
2026-09-29  9:59 ` [PATCH v5 3/5] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions Aniruddha Rao
2026-09-29  9:59 ` [PATCH v5 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers Aniruddha Rao
2026-09-29  9:59 ` [PATCH v5 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface Aniruddha Rao

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®