]> Gentwo Git Trees - linux/.git/commitdiff
i3c: master: svc: Add basic HDR mode support
authorFrank Li <Frank.Li@nxp.com>
Thu, 6 Nov 2025 17:36:03 +0000 (12:36 -0500)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Fri, 28 Nov 2025 23:39:08 +0000 (00:39 +0100)
Add basic HDR mode support for the svs I3C master driver.

Only support for private transfers and does not support sending CCC
commands in HDR mode.

Key differences:
- HDR uses commands (0x00-0x7F for write, 0x80-0xFF for read) to
distinguish transfer direction.
- HDR read/write commands must be written to FIFO before issuing the I3C
address command. The hardware automatically sends the standard CCC command
to enter HDR mode.
- HDR exit pattern must be sent instead of send a stop after transfer
completion.
- Read/write data size must be an even number.

Co-developed-by: Carlos Song <carlos.song@nxp.com>
Signed-off-by: Carlos Song <carlos.song@nxp.com>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
Link: https://patch.msgid.link/20251106-i3c_ddr-v11-4-33a6a66ed095@nxp.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
drivers/i3c/master/svc-i3c-master.c

index 0543f06b9fba704b3ea542c0ea1a13aa94e445b0..a62f22ff8b5762e1d6f2147b09e5e329aaf4afa0 100644 (file)
 #define   SVC_I3C_MCTRL_REQUEST_NONE 0
 #define   SVC_I3C_MCTRL_REQUEST_START_ADDR 1
 #define   SVC_I3C_MCTRL_REQUEST_STOP 2
+#define   SVC_I3C_MCTRL_REQUEST_FORCE_EXIT 6
 #define   SVC_I3C_MCTRL_REQUEST_IBI_ACKNACK 3
 #define   SVC_I3C_MCTRL_REQUEST_PROC_DAA 4
 #define   SVC_I3C_MCTRL_REQUEST_AUTO_IBI 7
 #define   SVC_I3C_MCTRL_TYPE_I3C 0
 #define   SVC_I3C_MCTRL_TYPE_I2C BIT(4)
+#define   SVC_I3C_MCTRL_TYPE_DDR BIT(5)
 #define   SVC_I3C_MCTRL_IBIRESP_AUTO 0
 #define   SVC_I3C_MCTRL_IBIRESP_ACK_WITHOUT_BYTE 0
 #define   SVC_I3C_MCTRL_IBIRESP_ACK_WITH_BYTE BIT(7)
@@ -95,6 +97,7 @@
 #define SVC_I3C_MINTMASKED   0x098
 #define SVC_I3C_MERRWARN     0x09C
 #define   SVC_I3C_MERRWARN_NACK BIT(2)
+#define   SVC_I3C_MERRWARN_CRC BIT(10)
 #define   SVC_I3C_MERRWARN_TIMEOUT BIT(20)
 #define SVC_I3C_MDMACTRL     0x0A0
 #define SVC_I3C_MDATACTRL    0x0AC
@@ -174,7 +177,7 @@ struct svc_i3c_cmd {
        const void *out;
        unsigned int len;
        unsigned int actual_len;
-       struct i3c_priv_xfer *xfer;
+       struct i3c_xfer *xfer;
        bool continued;
 };
 
@@ -389,7 +392,32 @@ svc_i3c_master_dev_from_addr(struct svc_i3c_master *master,
 
 static bool svc_cmd_is_read(u32 rnw_cmd, u32 type)
 {
-       return rnw_cmd;
+       return (type == SVC_I3C_MCTRL_TYPE_DDR) ? (rnw_cmd & 0x80) : rnw_cmd;
+}
+
+static void svc_i3c_master_emit_force_exit(struct svc_i3c_master *master)
+{
+       u32 reg;
+
+       writel(SVC_I3C_MCTRL_REQUEST_FORCE_EXIT, master->regs + SVC_I3C_MCTRL);
+
+       /*
+        * Not need check error here because it is never happen at hardware.
+        * IP just wait for few fclk cycle to complete DDR exit pattern. Even
+        * though fclk stop, timeout happen here, the whole data actually
+        * already finish transfer. The next command will be timeout because
+        * wrong hardware state.
+        */
+       readl_poll_timeout_atomic(master->regs + SVC_I3C_MSTATUS, reg,
+                                 SVC_I3C_MSTATUS_MCTRLDONE(reg), 0, 1000);
+
+       /*
+        * This delay is necessary after the emission of a stop, otherwise eg.
+        * repeating IBIs do not get detected. There is a note in the manual
+        * about it, stating that the stop condition might not be settled
+        * correctly if a start condition follows too rapidly.
+        */
+       udelay(1);
 }
 
 static void svc_i3c_master_emit_stop(struct svc_i3c_master *master)
@@ -527,7 +555,7 @@ static void svc_i3c_master_ibi_isr(struct svc_i3c_master *master)
         * cycle, leading to missed client IBI handlers.
         *
         * A typical scenario is when IBIWON occurs and bus arbitration is lost
-        * at svc_i3c_master_priv_xfers().
+        * at svc_i3c_master_i3c_xfers().
         *
         * Clear SVC_I3C_MINT_IBIWON before sending SVC_I3C_MCTRL_REQUEST_AUTO_IBI.
         */
@@ -807,6 +835,8 @@ static int svc_i3c_master_bus_init(struct i3c_master_controller *m)
 
        info.dyn_addr = ret;
 
+       info.hdr_cap = I3C_CCC_HDR_MODE(I3C_HDR_DDR);
+
        writel(SVC_MDYNADDR_VALID | SVC_MDYNADDR_ADDR(info.dyn_addr),
               master->regs + SVC_I3C_MDYNADDR);
 
@@ -1320,6 +1350,16 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
        /* clean SVC_I3C_MINT_IBIWON w1c bits */
        writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS);
 
+       if (xfer_type == SVC_I3C_MCTRL_TYPE_DDR) {
+               /* DDR command need prefill into FIFO */
+               writel(rnw_cmd, master->regs + SVC_I3C_MWDATAB);
+               if (!rnw) {
+                       /* write data also need prefill into FIFO */
+                       ret = svc_i3c_master_write(master, out, xfer_len);
+                       if (ret)
+                               goto emit_stop;
+               }
+       }
 
        while (retry--) {
                writel(SVC_I3C_MCTRL_REQUEST_START_ADDR |
@@ -1413,7 +1453,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
 
        if (rnw)
                ret = svc_i3c_master_read(master, in, xfer_len);
-       else
+       else if (xfer_type != SVC_I3C_MCTRL_TYPE_DDR)
                ret = svc_i3c_master_write(master, out, xfer_len);
        if (ret < 0)
                goto emit_stop;
@@ -1426,10 +1466,19 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
        if (ret)
                goto emit_stop;
 
+       if (xfer_type == SVC_I3C_MCTRL_TYPE_DDR &&
+           (readl(master->regs + SVC_I3C_MERRWARN) & SVC_I3C_MERRWARN_CRC)) {
+               ret = -ENXIO;
+               goto emit_stop;
+       }
+
        writel(SVC_I3C_MINT_COMPLETE, master->regs + SVC_I3C_MSTATUS);
 
        if (!continued) {
-               svc_i3c_master_emit_stop(master);
+               if (xfer_type != SVC_I3C_MCTRL_TYPE_DDR)
+                       svc_i3c_master_emit_stop(master);
+               else
+                       svc_i3c_master_emit_force_exit(master);
 
                /* Wait idle if stop is sent. */
                readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
@@ -1439,7 +1488,11 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
        return 0;
 
 emit_stop:
-       svc_i3c_master_emit_stop(master);
+       if (xfer_type != SVC_I3C_MCTRL_TYPE_DDR)
+               svc_i3c_master_emit_stop(master);
+       else
+               svc_i3c_master_emit_force_exit(master);
+
        svc_i3c_master_clear_merrwarn(master);
        svc_i3c_master_flush_fifo(master);
 
@@ -1486,6 +1539,11 @@ static void svc_i3c_master_dequeue_xfer(struct svc_i3c_master *master,
        spin_unlock_irqrestore(&master->xferqueue.lock, flags);
 }
 
+static int i3c_mode_to_svc_type(enum i3c_xfer_mode mode)
+{
+       return (mode == I3C_SDR) ? SVC_I3C_MCTRL_TYPE_I3C : SVC_I3C_MCTRL_TYPE_DDR;
+}
+
 static void svc_i3c_master_start_xfer_locked(struct svc_i3c_master *master)
 {
        struct svc_i3c_xfer *xfer = master->xferqueue.cur;
@@ -1675,9 +1733,8 @@ static int svc_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
        return ret;
 }
 
-static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
-                                    struct i3c_priv_xfer *xfers,
-                                    int nxfers)
+static int svc_i3c_master_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *xfers,
+                                   int nxfers, enum i3c_xfer_mode mode)
 {
        struct i3c_master_controller *m = i3c_dev_get_master(dev);
        struct svc_i3c_master *master = to_svc_i3c_master(m);
@@ -1685,19 +1742,32 @@ static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
        struct svc_i3c_xfer *xfer;
        int ret, i;
 
+       if (mode != I3C_SDR) {
+               /*
+                * Only support data size less than FIFO SIZE when using DDR
+                * mode. First entry is cmd in FIFO, so actual available FIFO
+                * for data is SVC_I3C_FIFO_SIZE - 2 since DDR only supports
+                * even length.
+                */
+               for (i = 0; i < nxfers; i++)
+                       if (xfers[i].len > SVC_I3C_FIFO_SIZE - 2)
+                               return -EINVAL;
+       }
+
        xfer = svc_i3c_master_alloc_xfer(master, nxfers);
        if (!xfer)
                return -ENOMEM;
 
-       xfer->type = SVC_I3C_MCTRL_TYPE_I3C;
+       xfer->type = i3c_mode_to_svc_type(mode);
 
        for (i = 0; i < nxfers; i++) {
+               u32 rnw_cmd = (mode == I3C_SDR) ? xfers[i].rnw : xfers[i].cmd;
+               bool rnw = svc_cmd_is_read(rnw_cmd, xfer->type);
                struct svc_i3c_cmd *cmd = &xfer->cmds[i];
-               bool rnw = xfers[i].rnw;
 
                cmd->xfer = &xfers[i];
                cmd->addr = master->addrs[data->index];
-               cmd->rnw = rnw;
+               cmd->rnw_cmd = rnw_cmd;
                cmd->in = rnw ? xfers[i].data.in : NULL;
                cmd->out = rnw ? NULL : xfers[i].data.out;
                cmd->len = xfers[i].len;
@@ -1896,7 +1966,7 @@ static const struct i3c_master_controller_ops svc_i3c_master_ops = {
        .do_daa = svc_i3c_master_do_daa,
        .supports_ccc_cmd = svc_i3c_master_supports_ccc_cmd,
        .send_ccc_cmd = svc_i3c_master_send_ccc_cmd,
-       .priv_xfers = svc_i3c_master_priv_xfers,
+       .i3c_xfers = svc_i3c_master_i3c_xfers,
        .i2c_xfers = svc_i3c_master_i2c_xfers,
        .request_ibi = svc_i3c_master_request_ibi,
        .free_ibi = svc_i3c_master_free_ibi,