]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
i2c: qcom-geni: Avoid extra TX DMA TRE for single read message in GPI mode
authorAniket Randive <aniket.randive@oss.qualcomm.com>
Fri, 10 Apr 2026 10:19:49 +0000 (15:49 +0530)
committerAndi Shyti <andi.shyti@kernel.org>
Fri, 10 Apr 2026 20:27:10 +0000 (22:27 +0200)
In GPI mode, the I2C GENI driver programs an extra TX DMA transfer
descriptor (TRE) on the TX channel when handling a single read message.
This results in an unintended write phase being issued on the I2C bus,
even though a read transaction does not require any TX data.

For a single-byte read, the correct hardware sequence consists of the
CONFIG and GO commands followed by a single RX DMA TRE. Programming an
additional TX DMA TRE is redundant, causes unnecessary DMA buffer
mapping on the TX channel, and may lead to incorrect bus behavior.

Update the transfer logic to avoid programming a TX DMA TRE for single
read messages in GPI mode.

Co-developed-by: Maramaina Naresh <naresh.maramaina@oss.qualcomm.com>
Signed-off-by: Maramaina Naresh <naresh.maramaina@oss.qualcomm.com>
Signed-off-by: Aniket Randive <aniket.randive@oss.qualcomm.com>
Reviewed-by: Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20260410101949.2315058-1-aniket.randive@oss.qualcomm.com
drivers/i2c/busses/i2c-qcom-geni.c

index a4acb78fafb669f49e71f739764b9779617f0cb5..a482a4c60744a5dece929da8a5e3a8e7219c8566 100644 (file)
@@ -625,8 +625,8 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
 {
        struct gpi_i2c_config *peripheral;
        unsigned int flags;
-       void *dma_buf;
-       dma_addr_t addr;
+       void *dma_buf = NULL;
+       dma_addr_t addr = 0;
        enum dma_data_direction map_dirn;
        enum dma_transfer_direction dma_dirn;
        struct dma_async_tx_descriptor *desc;
@@ -639,6 +639,16 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
        gi2c_gpi_xfer = &gi2c->i2c_multi_desc_config;
        msg_idx = gi2c_gpi_xfer->msg_idx_cnt;
 
+       /*
+        * Skip TX DMA mapping for a read message (I2C_M_RD) to avoid
+        * programming an extra TX DMA TRE that would cause an unintended
+        * write cycle on the I2C bus before the actual read operation.
+        */
+       if (op == I2C_WRITE && msgs[msg_idx].flags & I2C_M_RD) {
+               peripheral->multi_msg = true;
+               goto skip_tx_dma_map;
+       }
+
        dma_buf = i2c_get_dma_safe_msg_buf(&msgs[msg_idx], 1);
        if (!dma_buf) {
                ret = -ENOMEM;
@@ -658,6 +668,7 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
                goto out;
        }
 
+skip_tx_dma_map:
        if (gi2c->is_tx_multi_desc_xfer) {
                flags = DMA_CTRL_ACK;
 
@@ -740,9 +751,12 @@ static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
        return 0;
 
 err_config:
-       dma_unmap_single(gi2c->se.dev->parent, addr,
-                        msgs[msg_idx].len, map_dirn);
-       i2c_put_dma_safe_msg_buf(dma_buf, &msgs[msg_idx], false);
+       /* Avoid DMA unmap as the write operation skipped DMA mapping */
+       if (dma_buf) {
+               dma_unmap_single(gi2c->se.dev->parent, addr,
+                                msgs[msg_idx].len, map_dirn);
+               i2c_put_dma_safe_msg_buf(dma_buf, &msgs[msg_idx], false);
+       }
 
 out:
        gi2c->err = ret;