]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
spi: qcom-geni: Fix cs_change handling on the last transfer
authorViken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
Tue, 9 Jun 2026 08:43:09 +0000 (14:13 +0530)
committerMark Brown <broonie@kernel.org>
Tue, 9 Jun 2026 21:40:47 +0000 (22:40 +0100)
TPM TIS SPI probe fails with:

   tpm_tis_spi: probe of spi11.0 failed with error -110

TPM TIS SPI sets cs_change=1 on single-transfer messages to keep CS
asserted across the header, wait-state, and data phases of a transaction.
CS deassertion between these phases violates the TCG SPI flow control
specification.

This bug was introduced by commit b99181cdf9fa ("spi-geni-qcom: remove
manual CS control"), which replaced manual CS control with automatic CS
control via the FRAGMENTATION bit. The FRAGMENTATION bit controls CS
behavior after a transfer: when set to 1, CS remains asserted; when
cleared to 0, CS is deasserted.

The commit correctly sets FRAGMENTATION for non-last transfers with
cs_change=0 to keep CS asserted between chained transfers, but misses the
case where cs_change=1 is set on the last transfer. When cs_change=1 on
the last transfer, the client requests CS to remain asserted after the
message completes, so FRAGMENTATION must be set to 1 in this case as well.

Fix setup_se_xfer() to set FRAGMENTATION when cs_change=1 on the last
transfer.

Also fix the same missing case in setup_gsi_xfer() and correct it to
write 1 instead of the raw bitmask FRAGMENTATION (value 4) to
peripheral.fragmentation. This field is a 1-bit boolean consumed by
gpi_create_spi_tre() via u32_encode_bits(..., TRE_SPI_GO_FRAG). Writing 4
to a 1-bit field causes u32_encode_bits() to mask it to 0, silently
disabling the FRAGMENTATION bit in the GPI TRE regardless of the
cs_change logic.

Fixes: b99181cdf9fa ("spi-geni-qcom: remove manual CS control")
Cc: stable@vger.kernel.org
Reviewed-by: Jonathan Marek <jonathan@marek.ca>
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
Link: https://patch.msgid.link/20260609-fix-spi-fragmentation-bit-logic-v2-1-e18efc255563@oss.qualcomm.com
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/spi/spi-geni-qcom.c

index d5fb0edc8e0c8b24f24f21ffa9afcdaee2350af0..23c6d3a37341d232a5bc46ff0a748c8152121073 100644 (file)
@@ -440,10 +440,15 @@ static int setup_gsi_xfer(struct spi_transfer *xfer, struct spi_geni_master *mas
                return ret;
        }
 
-       if (!xfer->cs_change) {
-               if (!list_is_last(&xfer->transfer_list, &spi->cur_msg->transfers))
-                       peripheral.fragmentation = FRAGMENTATION;
-       }
+       /*
+        * Set fragmentation to keep CS asserted after this transfer when:
+        *  - non-last transfer with cs_change=0: keep CS asserted between chained transfers
+        *  - last transfer with cs_change=1: keep CS asserted after the message
+        *    (e.g. TPM TIS SPI uses cs_change=1 on single-transfer messages to
+        *     keep CS asserted across header, wait-state and data phases)
+        */
+       peripheral.fragmentation = list_is_last(&xfer->transfer_list, &spi->cur_msg->transfers) ?
+                                  xfer->cs_change : !xfer->cs_change;
 
        if (peripheral.cmd & SPI_RX) {
                dmaengine_slave_config(mas->rx, &config);
@@ -849,10 +854,16 @@ static int setup_se_xfer(struct spi_transfer *xfer,
                mas->cur_xfer_mode = GENI_SE_DMA;
        geni_se_select_mode(se, mas->cur_xfer_mode);
 
-       if (!xfer->cs_change) {
-               if (!list_is_last(&xfer->transfer_list, &spi->cur_msg->transfers))
-                       m_params = FRAGMENTATION;
-       }
+       /*
+        * Set FRAGMENTATION to keep CS asserted after this transfer when:
+        *  - non-last transfer with cs_change=0: keep CS asserted between chained transfers
+        *  - last transfer with cs_change=1: keep CS asserted after the message
+        *    (e.g. TPM TIS SPI uses cs_change=1 on single-transfer messages to
+        *     keep CS asserted across header, wait-state and data phases)
+        */
+       if (list_is_last(&xfer->transfer_list, &spi->cur_msg->transfers) ?
+           xfer->cs_change : !xfer->cs_change)
+               m_params = FRAGMENTATION;
 
        /*
         * Lock around right before we start the transfer since our