[PATCH v2 1/3] i3c: Add helpers to get the maximum transfer size for a device

From: Shubham Patil

Date: Thu Nov 05 2026 - 03:55:17 EST


A target's MRL and MWL, retrieved with GETMRL and GETMWL, describe only
what that target can accept. They say nothing about the controller
driving it, which may impose a smaller limit of its own: the DesignWare
controller, for example, rejects any private SDR transfer that does not
fit its data buffer. A device driver sizing a transfer from MRL alone
can therefore build one the controller has to refuse with -EOPNOTSUPP.

Add tx_fifo_bytes and rx_fifo_bytes to struct i3c_master_controller, to
be set by a controller driver before i3c_master_register() when it has
such a limit, together with two helpers returning the smaller of the
target and controller values for each direction.

Both inputs use 0 to mean "no limit reported", so the helpers combine
them with min_not_zero() and return 0 only when neither side reports
one, leaving each caller to apply its own default. struct
i3c_device_info keeps the raw target values untouched.

The returned value applies to a single message. A controller is free to
budget its buffer across every message in one i3c_device_do_xfers()
call, so a caller batching several messages in the same direction may
still be rejected.

Signed-off-by: Shubham Patil <shubhamsanjay.patil@xxxxxxx>
---
drivers/i3c/device.c | 49 ++++++++++++++++++++++++++++++++++++++
include/linux/i3c/device.h | 3 +++
include/linux/i3c/master.h | 13 +++++++---
3 files changed, 62 insertions(+), 3 deletions(-)

diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c
index 4df4682af150..588f66b3af6d 100644
--- a/drivers/i3c/device.c
+++ b/drivers/i3c/device.c
@@ -9,6 +9,7 @@
#include <linux/bug.h>
#include <linux/completion.h>
#include <linux/device.h>
+#include <linux/minmax.h>
#include <linux/mutex.h>
#include <linux/slab.h>

@@ -106,6 +107,54 @@ void i3c_device_get_info(const struct i3c_device *dev,
}
EXPORT_SYMBOL_GPL(i3c_device_get_info);

+/**
+ * i3c_device_get_max_read_xfer_bytes() - get the maximum readable message size
+ *
+ * @dev: device the transfers would be directed to
+ *
+ * Return: the smaller of @dev's MRL and the controller's receive limit for a
+ * single message, or 0 if neither reports a limit.
+ */
+u16 i3c_device_get_max_read_xfer_bytes(const struct i3c_device *dev)
+{
+ u16 mrl = 0, rx_fifo_bytes = 0;
+
+ i3c_bus_normaluse_lock(dev->bus);
+ if (dev->desc) {
+ mrl = dev->desc->info.max_read_len;
+ rx_fifo_bytes = i3c_dev_get_master(dev->desc)->rx_fifo_bytes;
+ }
+ i3c_bus_normaluse_unlock(dev->bus);
+
+ /* 0 means "no limit reported" on either side. */
+ return min_not_zero(mrl, rx_fifo_bytes);
+}
+EXPORT_SYMBOL_GPL(i3c_device_get_max_read_xfer_bytes);
+
+/**
+ * i3c_device_get_max_write_xfer_bytes() - get the maximum writable message size
+ *
+ * @dev: device the transfers would be directed to
+ *
+ * Return: the smaller of @dev's MWL and the controller's transmit limit for a
+ * single message, or 0 if neither reports a limit.
+ */
+u16 i3c_device_get_max_write_xfer_bytes(const struct i3c_device *dev)
+{
+ u16 mwl = 0, tx_fifo_bytes = 0;
+
+ i3c_bus_normaluse_lock(dev->bus);
+ if (dev->desc) {
+ mwl = dev->desc->info.max_write_len;
+ tx_fifo_bytes = i3c_dev_get_master(dev->desc)->tx_fifo_bytes;
+ }
+ i3c_bus_normaluse_unlock(dev->bus);
+
+ /* 0 means "no limit reported" on either side. */
+ return min_not_zero(mwl, tx_fifo_bytes);
+}
+EXPORT_SYMBOL_GPL(i3c_device_get_max_write_xfer_bytes);
+
/**
* i3c_device_disable_ibi() - Disable IBIs coming from a specific device
* @dev: device on which IBIs should be disabled
diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h
index e30ea2cb13cb..cb3374e4f9fa 100644
--- a/include/linux/i3c/device.h
+++ b/include/linux/i3c/device.h
@@ -331,6 +331,9 @@ int i3c_device_do_setdasa(struct i3c_device *dev);

void i3c_device_get_info(const struct i3c_device *dev, struct i3c_device_info *info);

+u16 i3c_device_get_max_read_xfer_bytes(const struct i3c_device *dev);
+u16 i3c_device_get_max_write_xfer_bytes(const struct i3c_device *dev);
+
struct i3c_ibi_payload {
unsigned int len;
const void *data;
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index d58070a72a49..3084cc7a21be 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -551,11 +551,16 @@ struct i3c_master_controller_ops {
* context (notably the resume path), and is skipped if the
* controller is shutting down
* @dev_nack_retry_count: retry count when slave device nack
+ * @tx_fifo_bytes: maximum number of bytes this controller can transmit in a
+ * single message, or 0 if it has no such limit
+ * @rx_fifo_bytes: maximum number of bytes this controller can receive in a
+ * single message, or 0 if it has no such limit
*
* A &struct i3c_master_controller has to be registered to the I3C subsystem
- * through i3c_master_register(). None of &struct i3c_master_controller fields
- * should be set manually, just pass appropriate values to
- * i3c_master_register().
+ * through i3c_master_register(). Except for @tx_fifo_bytes and
+ * @rx_fifo_bytes, which a controller driver may set before registering, none
+ * of &struct i3c_master_controller fields should be set manually, just pass
+ * appropriate values to i3c_master_register().
*/
struct i3c_master_controller {
struct device dev;
@@ -580,6 +585,8 @@ struct i3c_master_controller {
struct work_struct hj_work;
struct work_struct reg_work;
unsigned int dev_nack_retry_count;
+ u16 tx_fifo_bytes;
+ u16 rx_fifo_bytes;
};

/**
--
2.34.1