[PATCH 1/1] i3c: master: svc: add support for read len bigger than 255

From: Frank Li
Date: Wed Jan 17 2024 - 17:50:38 EST


RDTERM is 8bit. Only support max 255 read transfer for auto terminate. Use
manual terminate when read len bigger than 255.

When left data length is FIFO size + 1, issue terminate
(RDTERM(1) | REQUEST_NONE). So hardware will stop fetch data after next
data.
│ ◄────────── buff length ────────►│
│ │
│ ┌─┬─────────────┤
│ │ │ FIFO SIZE │
│ └─┴─────────────┘


Wait FIFO Full and Issue read termniate here!!

Signed-off-by: Frank Li <Frank.Li@xxxxxxx>
---
drivers/i3c/master/svc-i3c-master.c | 76 ++++++++++++++++++++++++-----
1 file changed, 64 insertions(+), 12 deletions(-)

diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c
index 5ee4db68988e2..58047ad357791 100644
--- a/drivers/i3c/master/svc-i3c-master.c
+++ b/drivers/i3c/master/svc-i3c-master.c
@@ -977,7 +977,7 @@ static int svc_i3c_master_do_daa(struct i3c_master_controller *m)
}

static int svc_i3c_master_read(struct svc_i3c_master *master,
- u8 *in, unsigned int len)
+ u8 *in, unsigned int len, bool auto_term)
{
int offset = 0, i;
u32 mdctrl, mstatus;
@@ -995,16 +995,60 @@ static int svc_i3c_master_read(struct svc_i3c_master *master,
return -ETIMEDOUT;
}

- mdctrl = readl(master->regs + SVC_I3C_MDATACTRL);
- count = SVC_I3C_MDATACTRL_RXCOUNT(mdctrl);
- if (offset + count > len) {
- dev_err(master->dev, "I3C receive length too long!\n");
- return -EINVAL;
- }
- for (i = 0; i < count; i++)
- in[offset + i] = readl(master->regs + SVC_I3C_MRDATAB);
+ if (auto_term || completed) {
+ /* auto termate or early termate by target */
+ mdctrl = readl(master->regs + SVC_I3C_MDATACTRL);
+ count = SVC_I3C_MDATACTRL_RXCOUNT(mdctrl);
+ if (offset + count > len) {
+ dev_err(master->dev, "I3C receive length too long!\n");
+ return -EINVAL;
+ }
+ for (i = 0; i < count; i++)
+ in[offset + i] = readl(master->regs + SVC_I3C_MRDATAB);
+
+ offset += count;

- offset += count;
+ } else {
+ /*
+ * Controller will fill whole RX FIFO in manual mode. FIFO full can prevent
+ * controller continue fetch data from target.
+ *
+ * When left data length is FIFO size + 1, issue terminate
+ * (RDTERM(1) | REQUEST_NONE). So hardware will stop fetch data after next
+ * data.
+ *
+ * │ ◄────────── buff length ────────►│
+ * │ │
+ * │ ┌─┬─────────────┤
+ * │ │ │ FIFO SIZE │
+ * │ └─┴─────────────┘
+ * ▲
+ * │
+ * Wait FIFO Full and Issue read termniate here!!
+ */
+ mdctrl = readl_relaxed(master->regs + SVC_I3C_MDATACTRL);
+ count = SVC_I3C_MDATACTRL_RXCOUNT(mdctrl);
+
+ if (offset + count + SVC_I3C_FIFO_SIZE < len) {
+ for (i = 0; i < count; i++) {
+ in[offset] = readl_relaxed(master->regs + SVC_I3C_MRDATAB);
+ offset++;
+ }
+ } else {
+ if (count != SVC_I3C_FIFO_SIZE)
+ continue;
+
+ /* Issue manual read terminate at next data */
+ if (offset + SVC_I3C_FIFO_SIZE == len - 1)
+ writel_relaxed(SVC_I3C_MCTRL_REQUEST_NONE |
+ SVC_I3C_MCTRL_DIR(1) |
+ SVC_I3C_MCTRL_RDTERM(1),
+ master->regs + SVC_I3C_MCTRL);
+
+ in[offset] = readl_relaxed(master->regs + SVC_I3C_MRDATAB);
+ offset++;
+ }
+ }
}

return offset;
@@ -1042,9 +1086,17 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
u8 *in, const u8 *out, unsigned int xfer_len,
unsigned int *actual_len, bool continued)
{
+ int rdterm = 0;
u32 reg;
int ret;

+ if (rnw)
+ rdterm = xfer_len;
+
+ /* If read length > max RDTERM in MCTRL, using manual terminate */
+ if (xfer_len > 255)
+ rdterm = 0;
+
/* clean SVC_I3C_MINT_IBIWON w1c bits */
writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS);

@@ -1053,7 +1105,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
SVC_I3C_MCTRL_IBIRESP_NACK |
SVC_I3C_MCTRL_DIR(rnw) |
SVC_I3C_MCTRL_ADDR(addr) |
- SVC_I3C_MCTRL_RDTERM(*actual_len),
+ SVC_I3C_MCTRL_RDTERM(rdterm),
master->regs + SVC_I3C_MCTRL);

ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
@@ -1086,7 +1138,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
}

if (rnw)
- ret = svc_i3c_master_read(master, in, xfer_len);
+ ret = svc_i3c_master_read(master, in, xfer_len, !!rdterm);
else
ret = svc_i3c_master_write(master, out, xfer_len);
if (ret < 0)
--
2.34.1