[PATCH v2 7/9] i3c: mipi-i3c-hci: Program AST2700 IBI termination threshold

From: Billy Tsai

Date: Tue Nov 10 2026 - 22:21:49 EST


For an IBI with payload, it's normally the device that decides when to
stop sending payload bytes. A misbehaving device that never terminates
can leave the controller stuck waiting on the transfer. AST2700 has an
in-house IBI payload length threshold that, once enabled, forces
termination once the received payload reaches it.

When payload IBIs are requested, grow the AST2700 termination
threshold to at least the requested payload length and enable
termination on that value. The register is controller-wide while IBI
requests for different devices can run in parallel, so the
read-modify-write is serialized under the controller lock. Gate this
behind HCI_QUIRK_IBI_PAYLOAD_THLD; controllers without the quirk keep
the existing behavior.

Signed-off-by: Billy Tsai <billy_tsai@xxxxxxxxxxxxxx>
---
drivers/i3c/master/mipi-i3c-hci/aspeed.c | 20 ++++++++++++++++++++
drivers/i3c/master/mipi-i3c-hci/aspeed.h | 5 +++++
drivers/i3c/master/mipi-i3c-hci/core.c | 17 ++++++++++++-----
3 files changed, 37 insertions(+), 5 deletions(-)

diff --git a/drivers/i3c/master/mipi-i3c-hci/aspeed.c b/drivers/i3c/master/mipi-i3c-hci/aspeed.c
index 3aa910e786f8..4a73e0180a10 100644
--- a/drivers/i3c/master/mipi-i3c-hci/aspeed.c
+++ b/drivers/i3c/master/mipi-i3c-hci/aspeed.c
@@ -136,6 +136,26 @@ void aspeed_i3c_write(struct i3c_hci *hci, u32 reg, u32 val)
writel(val, to_aspeed_vendor_data(hci)->inhouse_regs + reg);
}

+void aspeed_i3c_ibi_grow_threshold(struct i3c_hci *hci, unsigned int ibi_max_len)
+{
+ u32 mrl, cur_len;
+
+ /*
+ * The termination threshold is controller-wide while IBI requests
+ * for different devices can run in parallel, so serialize the
+ * read-modify-write.
+ */
+ guard(spinlock_irqsave)(&hci->lock);
+
+ mrl = aspeed_i3c_read(hci, ASPEED_I3C_MST_MRL);
+ cur_len = FIELD_GET(ASPEED_I3C_IBI_TERMINATE_LEN, mrl);
+
+ ibi_max_len = max(ibi_max_len, cur_len);
+ aspeed_i3c_write(hci, ASPEED_I3C_MST_MRL,
+ ASPEED_I3C_IBI_TERMINATE_EN |
+ FIELD_PREP(ASPEED_I3C_IBI_TERMINATE_LEN, ibi_max_len));
+}
+
static void aspeed_i3c_phy_write(struct i3c_hci *hci, u32 reg, u32 val)
{
writel(val, to_aspeed_vendor_data(hci)->phy_regs + reg);
diff --git a/drivers/i3c/master/mipi-i3c-hci/aspeed.h b/drivers/i3c/master/mipi-i3c-hci/aspeed.h
index 7ff1f66b67cd..7aecce04bba7 100644
--- a/drivers/i3c/master/mipi-i3c-hci/aspeed.h
+++ b/drivers/i3c/master/mipi-i3c-hci/aspeed.h
@@ -37,6 +37,7 @@ void aspeed_i3c_set_daa_index(struct i3c_hci *hci, unsigned int dat_idx);
u32 aspeed_i3c_read(struct i3c_hci *hci, u32 reg);
void aspeed_i3c_write(struct i3c_hci *hci, u32 reg, u32 val);
int aspeed_i3c_phy_init(struct i3c_hci *hci);
+void aspeed_i3c_ibi_grow_threshold(struct i3c_hci *hci, unsigned int ibi_max_len);

#define ASPEED_I3C_CTRL 0x00
#define ASPEED_I3C_CTRL_CLOCK_STALL_EN BIT(14)
@@ -44,6 +45,10 @@ int aspeed_i3c_phy_init(struct i3c_hci *hci);
#define ASPEED_I3C_CTRL_INIT_MODE GENMASK(1, 0)
#define ASPEED_I3C_INIT_MST_MODE 0

+#define ASPEED_I3C_MST_MRL 0x08
+#define ASPEED_I3C_IBI_TERMINATE_EN BIT(16)
+#define ASPEED_I3C_IBI_TERMINATE_LEN GENMASK(15, 0)
+
#define ASPEED_I3C_DAA_INDEX0 0x10
#define ASPEED_I3C_DAA_INDEX1 0x14
#define ASPEED_I3C_DAA_INDEX2 0x18
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index 759892a3785b..31fab1fe614c 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -722,15 +722,21 @@ static void i3c_hci_detach_i2c_dev(struct i2c_dev_desc *dev)
static int i3c_hci_request_ibi(struct i3c_dev_desc *dev,
const struct i3c_ibi_setup *req)
{
- struct i3c_master_controller *m = i3c_dev_get_master(dev);
- struct i3c_hci *hci = to_i3c_hci(m);
struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev);
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ unsigned int ibi_max_len = req->max_payload_len;
unsigned int dat_idx = dev_data->dat_idx;
+ struct i3c_hci *hci = to_i3c_hci(m);

- if (req->max_payload_len != 0)
+ if (req->max_payload_len != 0) {
mipi_i3c_hci_dat_v1.set_flags(hci, dat_idx, DAT_0_IBI_PAYLOAD, 0);
- else
+
+ if (hci->quirks & HCI_QUIRK_IBI_PAYLOAD_THLD)
+ aspeed_i3c_ibi_grow_threshold(hci, ibi_max_len);
+ } else {
mipi_i3c_hci_dat_v1.clear_flags(hci, dat_idx, DAT_0_IBI_PAYLOAD, 0);
+ }
+
return hci->io->request_ibi(hci, dev, req);
}

@@ -1408,7 +1414,8 @@ static const __maybe_unused struct of_device_id i3c_hci_of_match[] = {
HCI_QUIRK_BOARD_PHY_TIMINGS |
HCI_QUIRK_VENDOR_INTR_SUMMARY |
HCI_QUIRK_VENDOR_RESOURCES |
- HCI_QUIRK_VENDOR_DAA_INDEX) },
+ HCI_QUIRK_VENDOR_DAA_INDEX |
+ HCI_QUIRK_IBI_PAYLOAD_THLD) },
{},
};
MODULE_DEVICE_TABLE(of, i3c_hci_of_match);

--
2.34.1