SPIM00 SPI transfers return all zeros on nRF54L15 with NCS v3.3.0

Board: Custom board based on nRF54L15
NCS Version: v3.3.0
SoC: nRF54L15, revision 2
Toolchain: nRF Connect SDK v3.3.0 bundled toolchain


Problem Description

SPIM00 on nRF54L15 is not working. All spi_transceive calls complete without returning an error but all received bytes are 0x00. Logic analyser confirms CSN never asserts low during a transfer. Without CSN asserting the target device never responds.

All SPI pins including CSN are on P2 which is the correct port for SPIM00 (global domain). P2 has no GPIOTE support. We suspect the Zephyr SPI driver cs-gpios mechanism requires GPIOTE to assert CSN and is silently failing on P2.


Hardware

SPI bus connected to an external SPI NOR flash (FM25W32) and LR1110 LoRa module. All SPI pins are on P2 which is the correct port for SPIM00.

SCK : P2.6
MOSI : P2.2
MISO : P2.4
CSN : P2.5


Configuration

pinctrl (minew_mwc05_nrf54l15-pinctrl.dtsi):

spi00_default: spi00_default {
    group1 {
        psels = <NRF_PSEL(SPIM_SCK,  2, 6)>,
                <NRF_PSEL(SPIM_MOSI, 2, 2)>,
                <NRF_PSEL(SPIM_MISO, 2, 4)>;
        nordic,drive-mode = <NRF_DRIVE_E0E1>;
    };
};

spi00_sleep: spi00_sleep {
    group1 {
        psels = <NRF_PSEL(SPIM_SCK,  2, 6)>,
                <NRF_PSEL(SPIM_MOSI, 2, 2)>,
                <NRF_PSEL(SPIM_MISO, 2, 4)>;
        low-power-enable;
    };
};


DTS node (minew_mwc05_nrf54l15_cpuapp.dts):

&spi00 {
    status = "okay";
    cs-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>;
    pinctrl-0 = <&spi00_default>;
    pinctrl-1 = <&spi00_sleep>;
    pinctrl-names = "default", "sleep";

    flash: flash@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <4000000>;
        jedec-id = [a1 28 16];
    };
};

board.defconfig

CONFIG_SPI=y
CONFIG_SPI_NOR=y


application
#define MY_SPI_MASTER DT_NODELABEL(spi00)
const struct device *spi_dev = DEVICE_DT_GET(MY_SPI_MASTER);

/***************************************************************************/
ret_code_t SD_InitSpiDriver(void)
{	
	ret_code_t err = NRF_SUCCESS;

	if(!device_is_ready(spi_dev))
	{
		NRF_LOG_ERROR("SPI master device not ready!");
	}
	else
	{
		NRF_LOG_INFO("SPI master init successfully!");
	}



	return err;
}

/***************************************************************************/
ret_code_t SD_UninitSpiDriver(SD_SPI_INSTANCE_TYPE instance)
{
	struct spi_cs_control cs_ctrl = (struct spi_cs_control)
	{
		.gpio = GPIO_DT_SPEC_GET(MY_SPI_MASTER, cs_gpios),
		.delay = 0u,
	};

	const struct spi_config spi_cfg = 
	{
		.operation = SPI_WORD_SET(8) | SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB,
		.frequency = 4000000,
		.slave = 0,
		.cs = cs_ctrl,
	};

    spi_release(spi_dev ,&spi_cfg);
    return NRF_SUCCESS;
}

/***************************************************************************/
ret_code_t SD_Transfer(SD_SPI_INSTANCE_TYPE instance,
                       uint8_t const *pTxBuffer, uint8_t txBufferLength,
                       uint8_t       *pRxBuffer, uint8_t rxBufferLength,
                       SD_TRANSFER_DONE_TYPE transferDoneCb)
{
    ret_code_t errCode = NRF_SUCCESS;

    struct spi_cs_control cs_ctrl;
    struct spi_config spi_cfg;

    if (instance == SD_SPI_INSTANCE_1)
    {
        LOG_INF("Using SPI instance 1");
        cs_ctrl = (struct spi_cs_control){
            .gpio = GPIO_DT_SPEC_GET(MY_SPI_MASTER, cs_gpios),
            .delay = 0u,
        };
        spi_cfg = (struct spi_config){
            .operation = SPI_WORD_SET(8) | SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB,
            .frequency = 4000000,
            .slave     = 0,
            .cs        = cs_ctrl,
        };
    }

    const struct spi_buf tx_buf = { .buf = (void *)pTxBuffer, .len = txBufferLength };
    const struct spi_buf_set tx  = { .buffers = &tx_buf, .count = 1 };

    struct spi_buf rx_buf        = { .buf = pRxBuffer, .len = rxBufferLength };
    const struct spi_buf_set rx  = { .buffers = &rx_buf, .count = 1 };

    LOG_DBG("SPI Transfer: TX(%d bytes) RX(%d bytes)", txBufferLength, rxBufferLength);
    if (txBufferLength > 0 && txBufferLength <= 8) {
        for (uint8_t i = 0; i < txBufferLength; i++) {
            LOG_DBG("  TX[%d]: 0x%02X", i, pTxBuffer[i]);
        }
    }

    if (rxBufferLength == 0) {
        errCode = spi_transceive(spi_dev, &spi_cfg, &tx, NULL);
    } else {
        errCode = spi_transceive(spi_dev, &spi_cfg, &tx, &rx);
    }

    if (errCode) {
        LOG_ERR("SPI transfer error: %d", errCode);
    } else if (rxBufferLength > 0 && rxBufferLength <= 8) {
        for (uint8_t i = 0; i < rxBufferLength; i++) {
            LOG_DBG("  RX[%d]: 0x%02X", i, pRxBuffer[i]);
        }
    }

    return errCode;
}


Observed Behaviour

- device_is_ready returns true for spi00
- spi_transceive returns 0 (success)
- All RX bytes are 0x00
- Logic analyser shows CSN (P2.5) stays high throughout the entire transfer and never asserts low
- Without CSN asserting the flash device never responds

Expected Behaviour

- CSN asserts low at the start of each transfer
- CSN deasserts high after transfer completes
- Flash JEDEC ID reads back A1 28 16

What We Have Tried

- Verified pinctrl psel encoding in generated zephyr.dts — correct
- Set drive mode to NRF_DRIVE_E0E1 as required for P2
- Confirmed CSN pin P2.5 is correctly defined in cs-gpios
- Confirmed no other DTS nodes conflict with P2.5
- Confirmed device_is_ready returns true

Questions

1. Does the Zephyr SPI driver cs-gpios mechanism require GPIOTE to assert CSN? If so this would explain why CSN never asserts on P2 which has no GPIOTE on nRF54L15.
2. Is there a known issue with using cs-gpios on P2 pins with SPIM00 on nRF54L15 in NCS v3.3.0?
3. Is there a fix or recommended workaround for using SPIM00 with a CSN pin on P2 on nRF54L15?
4. Is there a fix available in a later NCS version?

Related