]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
spi: bcm63xx-hsspi: add support for 1-2-2 read ops
authorJonas Gorski <jonas.gorski@gmail.com>
Wed, 17 Dec 2025 21:10:26 +0000 (22:10 +0100)
committerMark Brown <broonie@kernel.org>
Tue, 23 Dec 2025 10:53:50 +0000 (10:53 +0000)
Add support for 1-2-2 read ops by separately calculating the switch from
single-bit to multi-bit, and then switching within the prepend data.

This allows us to support single-bit write followed by multi-bit write
followed by multi-bit read, and we do not need to reject 1-2-2 read
operations anymore.

Tested on BCM963268BU_P300 with custom fixup to allow 1-2-2 on the
non-SDFP capable s25fl129p1 attached (which it actually supports):

root@OpenWrt:~# cat /sys/kernel/debug/spi-nor/spi1.0/params
name            s25fl129p1
id              01 20 18 4d 01 01
size            16.0 MiB
write size      1
page size       256
address nbytes  3
flags           HAS_16BIT_SR | NO_READ_CR

opcodes
 read           0xbb
  dummy cycles  4
 erase          0xd8
 program        0x02
 8D extension   none

protocols
 read           1S-2S-2S
 write          1S-1S-1S
 register       1S-1S-1S

Reading from flash is still working as expected:

[    1.070000] parser_imagetag: rootfs: CFE image tag found at 0x0 with version 6, board type 963168VX
[    1.080000] parser_imagetag: Partition 0 is rootfs offset 100 and length 665000
[    1.090000] parser_imagetag: Partition 1 is kernel offset 665100 and length 136fa1
[    1.100000] parser_imagetag: Spare partition is offset 7b0000 and length 30000

Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
Acked-by: William Zhang <william.zhang@broadcom.com>
Tested-by: David Regan <dregan@broadcom.com>
Link: https://patch.msgid.link/20251217211026.173946-1-jonas.gorski@gmail.com
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/spi/spi-bcm63xx-hsspi.c

index 18261cbd413b496fc89909e0e0ecf5999203ea2d..d9e972ef2abd64a80372fce71e24517a250febe7 100644 (file)
@@ -142,6 +142,7 @@ struct bcm63xx_hsspi {
        u32 wait_mode;
        u32 xfer_mode;
        u32 prepend_cnt;
+       u32 md_start;
        u8 *prepend_buf;
 };
 
@@ -268,18 +269,20 @@ static bool bcm63xx_prepare_prepend_transfer(struct spi_controller *host,
 {
 
        struct bcm63xx_hsspi *bs = spi_controller_get_devdata(host);
-       bool tx_only = false;
+       bool tx_only = false, multidata = false;
        struct spi_transfer *t;
 
        /*
         * Multiple transfers within a message may be combined into one transfer
         * to the controller using its prepend feature. A SPI message is prependable
         * only if the following are all true:
-        *   1. One or more half duplex write transfer in single bit mode
-        *   2. Optional full duplex read/write at the end
-        *   3. No delay and cs_change between transfers
+        *   1. One or more half duplex write transfers at the start
+        *   2. Optional switch from single to dual bit within the write transfers
+        *   3. Optional full duplex read/write at the end if all single bit
+        *   4. No delay and cs_change between transfers
         */
        bs->prepend_cnt = 0;
+       bs->md_start = 0;
        list_for_each_entry(t, &msg->transfers, transfer_list) {
                if ((spi_delay_to_ns(&t->delay, t) > 0) || t->cs_change) {
                        bcm63xx_prepend_printk_on_checkfail(bs,
@@ -297,31 +300,44 @@ static bool bcm63xx_prepare_prepend_transfer(struct spi_controller *host,
                                return false;
                        }
 
-                       if (t->tx_nbits > SPI_NBITS_SINGLE &&
-                               !list_is_last(&t->transfer_list, &msg->transfers)) {
+                       if (t->tx_nbits == SPI_NBITS_SINGLE &&
+                           !list_is_last(&t->transfer_list, &msg->transfers) &&
+                           multidata) {
                                bcm63xx_prepend_printk_on_checkfail(bs,
-                                        "multi-bit prepend buf not supported!\n");
+                                        "single-bit after multi-bit not supported!\n");
                                return false;
                        }
 
-                       if (t->tx_nbits == SPI_NBITS_SINGLE) {
-                               memcpy(bs->prepend_buf + bs->prepend_cnt, t->tx_buf, t->len);
-                               bs->prepend_cnt += t->len;
-                       }
+                       if (t->tx_nbits > SPI_NBITS_SINGLE)
+                               multidata = true;
+
+                       memcpy(bs->prepend_buf + bs->prepend_cnt, t->tx_buf, t->len);
+                       bs->prepend_cnt += t->len;
+
+                       if (t->tx_nbits == SPI_NBITS_SINGLE)
+                               bs->md_start += t->len;
+
                } else {
                        if (!list_is_last(&t->transfer_list, &msg->transfers)) {
                                bcm63xx_prepend_printk_on_checkfail(bs,
                                         "rx/tx_rx transfer not supported when it is not last one!\n");
                                return false;
                        }
+
+                       if (t->rx_buf && t->rx_nbits == SPI_NBITS_SINGLE &&
+                           multidata) {
+                               bcm63xx_prepend_printk_on_checkfail(bs,
+                                        "single-bit after multi-bit not supported!\n");
+                               return false;
+                       }
                }
 
                if (list_is_last(&t->transfer_list, &msg->transfers)) {
                        memcpy(t_prepend, t, sizeof(struct spi_transfer));
 
-                       if (tx_only && t->tx_nbits == SPI_NBITS_SINGLE) {
+                       if (tx_only) {
                                /*
-                                * if the last one is also a single bit tx only transfer, merge
+                                * if the last one is also a tx only transfer, merge
                                 * all of them into one single tx transfer
                                 */
                                t_prepend->len = bs->prepend_cnt;
@@ -329,7 +345,7 @@ static bool bcm63xx_prepare_prepend_transfer(struct spi_controller *host,
                                bs->prepend_cnt = 0;
                        } else {
                                /*
-                                * if the last one is not a tx only transfer or dual tx xfer, all
+                                * if the last one is not a tx only transfer, all
                                 * the previous transfers are sent through prepend bytes and
                                 * make sure it does not exceed the max prepend len
                                 */
@@ -339,6 +355,15 @@ static bool bcm63xx_prepare_prepend_transfer(struct spi_controller *host,
                                        return false;
                                }
                        }
+                       /*
+                        * If switching from single-bit to multi-bit, make sure
+                        * the start offset does not exceed the maximum
+                        */
+                       if (multidata && bs->md_start > HSSPI_MAX_PREPEND_LEN) {
+                               bcm63xx_prepend_printk_on_checkfail(bs,
+                                       "exceed max multi-bit offset, abort prepending transfers!\n");
+                               return false;
+                       }
                }
        }
 
@@ -381,11 +406,11 @@ static int bcm63xx_hsspi_do_prepend_txrx(struct spi_device *spi,
 
                if (t->rx_nbits == SPI_NBITS_DUAL) {
                        reg |= 1 << MODE_CTRL_MULTIDATA_RD_SIZE_SHIFT;
-                       reg |= bs->prepend_cnt << MODE_CTRL_MULTIDATA_RD_STRT_SHIFT;
+                       reg |= bs->md_start << MODE_CTRL_MULTIDATA_RD_STRT_SHIFT;
                }
                if (t->tx_nbits == SPI_NBITS_DUAL) {
                        reg |= 1 << MODE_CTRL_MULTIDATA_WR_SIZE_SHIFT;
-                       reg |= bs->prepend_cnt << MODE_CTRL_MULTIDATA_WR_STRT_SHIFT;
+                       reg |= bs->md_start << MODE_CTRL_MULTIDATA_WR_STRT_SHIFT;
                }
        }
 
@@ -692,13 +717,6 @@ static bool bcm63xx_hsspi_mem_supports_op(struct spi_mem *mem,
        if (!spi_mem_default_supports_op(mem, op))
                return false;
 
-       /* Controller doesn't support spi mem dual io mode */
-       if ((op->cmd.opcode == SPINOR_OP_READ_1_2_2) ||
-               (op->cmd.opcode == SPINOR_OP_READ_1_2_2_4B) ||
-               (op->cmd.opcode == SPINOR_OP_READ_1_2_2_DTR) ||
-               (op->cmd.opcode == SPINOR_OP_READ_1_2_2_DTR_4B))
-               return false;
-
        return true;
 }