SD over SPI on nRF54L15: CMD8 sometimes OK (“a11d”), other times “Legacy card detected, no CMD8”, then CMD58 “No OCR” → disk_access_init('SD') -> -134

MCU/Board: nRF54L15 (custom, cpuapp)
SDK/Zephyr: nRF Connect SDK v3.0.2 (*** Using Zephyr OS v4.0.99-f791c49f492c ***)
FS stack: FATFS (Elm) over SD SPI slot (zephyr,sdhc-spi-slot)
Interface: SPI via spi21 to micro-SD socket, 3.0 V rail
Goal: mount /SD: reliably at boot; write WAV/CSV


Symptom

Cold boot frequently fails SD card identification:

  • Sometimes CMD8 returns a normal R7 echo (printed as a11d / 0x1AA) and then later fails with CMD58 No OCR.

  • Other times CMD8 times out and the driver prints “Legacy card detected, no CMD8 support”.

  • Ultimately disk_access_init('SD') -> -134.

Warm reset occasionally makes it succeed (hence my suspicion about power/CS timing).

Representative logs:

[00:00:00.022] <dbg> sd: sd_init_io: Resetting power to card
[00:00:02.024] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd0 arg 0x0
[00:00:02.025] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd8 a11d          <-- R7 looks OK
...
[00:00:53.608] <dbg> sd: sdmmc_spi_send_ocr: No OCR detected
[00:00:53.608] <inf> sdstore: disk_access_init('SD') -> -134

-- on other boots --

[00:10:15.728] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd8 arg 0x1aa
[00:10:15.728] <dbg> sd: sd_send_interface_condition: Legacy card detected, no CMD8 support
(repeats cmd0/cmd8/legacy...)

Also noticed:

  • The stack requests 400 kHz during identification; actual on-wire is ~250 kHz (expected: nRF SPIM can’t do 400 kHz exactly).

  • Log prints “Host controller support 3.3V max”; my card VDD is 3.0 V (within 2.7–3.6 V SD spec).


Hardware

  • micro-SD socket wired to spi21: SCK=P2.06, MOSI=P2.09, MISO=P2.xx (not shown below), CS=P2.10

  • Card VDD 3.0 V controlled by a load switch from P2.07

  • Pull-ups present on CS; MISO pull-up available if needed

  • Level shifters not used (direct 3.0 V domain)


&spi21 {
    status = "okay";
    pinctrl-0 = <&spi21_default>;
    pinctrl-1 = <&spi21_sleep>;
    pinctrl-names = "default", "sleep";

    sdhc0: sdhc@0 {
        compatible = "zephyr,sdhc-spi-slot";
        reg = <0>;
        status = "okay";

        /* I have tested both ACTIVE_HIGH and ACTIVE_LOW here */
        pwr-gpios = <&gpio2 7 GPIO_ACTIVE_HIGH>;
        power-delay-ms = <300>;                 /* tried 300..3000 */

        /* I have tried cs-gpios both on the bus and here on the device.
           Currently placing it here to let the slot driver own CS: */
        cs-gpios = <&gpio2 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
        spi-hold-cs;
        spi-cs-setup-delay-us = <10>;
        spi-cs-hold-delay-us  = <10>;

        /* Start conservative; later will raise to 12–24 MHz */
        spi-max-frequency = <400000>;

        mmc: mmc {
            compatible = "zephyr,sdmmc-disk";
            status = "okay";
            disk-name = "SD";
        };
    };
};


Kconfig (storage/logging relevant bits)

# =========================
# Kernel / stacks
# =========================
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_ISR_STACK_SIZE=2048
CONFIG_THREAD_NAME=y

# =========================
# Logging (RTT console)
# =========================
CONFIG_LOG=y
CONFIG_PRINTK=y
CONFIG_CONSOLE=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_LOG_BACKEND_RTT=y
CONFIG_RTT_CONSOLE=y

# Per-subsystem log levels (turn up only what we care about)
CONFIG_SD_LOG_LEVEL_DBG=y
CONFIG_SDHC_LOG_LEVEL_DBG=y
CONFIG_DISK_LOG_LEVEL_DBG=y
# CONFIG_SPI_LOG_LEVEL_DBG=y

# Optional: bigger RTT buffers while debugging SD spam
# CONFIG_LOG_BACKEND_RTT_OUTPUT_BUFFER_SIZE=2048
# CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=8192
# Global override when diagnosing:
# CONFIG_LOG_OVERRIDE_LEVEL=4

# =========================
# Drivers / peripherals
# =========================
CONFIG_GPIO=y
CONFIG_GPIO_NRFX=y
CONFIG_I2C=y
CONFIG_SPI=y

# IO expander
CONFIG_GPIO_PCAL64XXA=y

# SAADC & timers (nrfx direct)
CONFIG_ADC=n
CONFIG_NRFX_SAADC=y
CONFIG_NRFX_TIMER22=y
CONFIG_NRFX_GPPI=y

# =========================
# Filesystem & SD over SPI/SDMMC
# =========================
CONFIG_DISK_ACCESS=y
CONFIG_FILE_SYSTEM=y
CONFIG_FAT_FILESYSTEM_ELM=y
CONFIG_DISK_DRIVER_SDMMC=y
CONFIG_SPI_NRFX_RAM_BUFFER_SIZE=64
CONFIG_SDMMC_SUBSYS=y
CONFIG_DEBUG_OPTIMIZATIONS=y 
# CONFIG_SDMMC_VOLUME_NAME="SD"

# Demonstrate filesystem time integration
# CONFIG_FS_FATFS_HAS_RTC=y

# Optional FAT features:
# CONFIG_FAT_FILESYSTEM_ELM_EXFAT=y
# CONFIG_FS_FATFS_LFN=y
# CONFIG_FS_FATFS_MAX_LFN=255

# =========================
# SD stack timeouts/retries (numbers ONLY) — enable as needed
# =========================
# CONFIG_SD_INIT_TIMEOUT=5000
# CONFIG_SD_RETRY_COUNT=20
# CONFIG_SD_OCR_RETRY_COUNT=5000
# CONFIG_SD_CMD_TIMEOUT=500
# CONFIG_SD_DATA_TIMEOUT=15000
# CONFIG_SD_CMD_RETRIES=3
# CONFIG_SD_DATA_RETRIES=5

When it hits disk_access_init() returns -134. Running SDK V3.0.2

  • Hi!

    micro-SD socket wired to spi21: SCK=P2.06, MOSI=P2.09, MISO=P2.xx (not shown below), CS=P2.10

    The P2 pins are a bit special ,ref https://docs.nordicsemi.com/bundle/ps_nrf54L15/page/chapters/pin.html#d379e188 and https://docs.nordicsemi.com/bundle/ps_nrf54L15/page/chapters/pin.html#ariaid-title2

    Try using constant latency mode. Add CONFIG_NRFX_POWER=y to prj.conf and enable constant latency using nrfx_power_constlat_mode_request()

  • Many thanks   for bringing attention to the specificity of port 2. I switched to using the default SPIM00 on the nRF54L15 QFN48 package.

    My question now is: this does not require cross-domain, right? According to the documentation it just needs to be configured for strong drive.

    However, I noticed that MISO and MOSI are swapped in the hardware. From the binder it seems they should be:

    • P2.08 = MOSI (SDO)
    • P2.09 = MISO (SDI)

    But in the board they are wired the other way around. I thought using PSEL would swap them, but I could be wrong. Can they be swapped in the pmux?

  • Hi!

    MuffinBreak said:
    • P2.08 = MOSI (SDO)
    • P2.09 = MISO (SDI)

    Yes, that is correct.

    MuffinBreak said:
    Can they be swapped in the pmux?

    P2 pins are not as flexible, as e.g. P1 pins, so these cannot be swapped.

    SPIM00 SDO can be either P2.02 or P2.08

    SPIM00 SDI can be either P2.04 or P2.09

    See also

     Essential pin planning guidelines for the nRF54L Series 

    and https://hlord2000.github.io/

  • I have now swapped the lines according to the datasheet and switched to SPI21 instead of SPIM00, since SPIM00 apparently cannot go as low as 400 kHz with a 128 MHz peripheral clock and a ÷126 divider. The device tree and relevant configuration have been updated accordingly.

    &pinctrl {
    
        uart30_default: uart30_default {
            group1 { psels = <NRF_PSEL(UART_TX, 0, 3)>; };
            group2 { psels = <NRF_PSEL(UART_RX, 0, 2)>; bias-pull-up; };
        };
        uart30_sleep: uart30_sleep {
            group1 { psels = <NRF_PSEL(UART_TX, 0, 3)>; low-power-enable; };
            group2 { psels = <NRF_PSEL(UART_RX, 0, 2)>; bias-pull-up; low-power-enable; };
        };
    
    	
    	i2c22_default: i2c22_default {
    		group1 {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 12)>, <NRF_PSEL(TWIM_SDA, 1, 11)>;
    		};
    	};
    
    	i2c22_sleep: i2c22_sleep {
    		group1 {
    			psels = <NRF_PSEL(TWIM_SCL, 1, 12)>, <NRF_PSEL(TWIM_SDA, 1, 11)>;
    			low-power-enable;
    
    		};
    	};
    
    	spi20_default: spi20_default {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 2, 1)>,
    					<NRF_PSEL(SPIM_MOSI, 2, 2)>,
    					<NRF_PSEL(SPIM_MISO, 2, 4)>;
    				    
    
    		};
    	};
    
    	spi20_sleep: spi20_sleep{
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 2, 1)>,
    					<NRF_PSEL(SPIM_MOSI, 2, 2)>,
    					<NRF_PSEL(SPIM_MISO, 2, 4)>;
    					low-power-enable;
    		};
    	};
    
    	spi21_default: spi21_default {
    
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 2, 6)>,
    					<NRF_PSEL(SPIM_MOSI, 2, 8)>,
    					<NRF_PSEL(SPIM_MISO, 2, 9)>;
    					//nordic,drive-mode = <NRF_DRIVE_H0H1>;
    			// bias-pull-up;
    		};
        };
    
        spi21_sleep: spi21_sleep 
    	{
    
    		group2 {
    			psels = <NRF_PSEL(SPIM_SCK, 2, 6)>,
    					<NRF_PSEL(SPIM_MOSI, 2, 8)>,
    					<NRF_PSEL(SPIM_MISO, 2, 9)>;
    					//nordic,drive-mode = <NRF_DRIVE_H0H1>;
    			// bias-pull-up;
    			low-power-enable;
    		};
        };
    
    	
    };
    

    # =========================
    # Drivers / peripherals
    # =========================
    CONFIG_GPIO=y
    CONFIG_GPIO_NRFX=y
    CONFIG_I2C=y
    CONFIG_SPI=y
    CONFIG_SPI_NRFX_RAM_BUFFER_SIZE=64
    
    #FOR THE EXTERNAL mx25r64 FLASH
    CONFIG_SPI_NOR=y
    CONFIG_SPI_NOR_SFDP_DEVICETREE=y
    CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096
    
    # required by SPI driver FOR mcu BOOT MAINLY 
    # CONFIG_MULTITHREADING=y
    
    # IO expander
    CONFIG_GPIO_PCAL64XXA=y
    
    # SAADC & timers (nrfx direct)
    CONFIG_ADC=n
    CONFIG_NRFX_SAADC=y
    CONFIG_NRFX_TIMER22=y
    CONFIG_NRFX_GPPI=y
    
    # =========================
    # Filesystem & SD over SPI/SDMMC
    # =========================
    CONFIG_DISK_ACCESS=y
    CONFIG_FILE_SYSTEM=y
    CONFIG_FAT_FILESYSTEM_ELM=y
    # CONFIG_SDHC=y #did not change anythign enabling it unless we have to disable mmc too?
    CONFIG_DISK_DRIVERS=y
    # CONFIG_SDMMC_SUBSYS=y #does not seem to help
    # CONFIG_FAT_FILESYSTEM_ELM_MKFS=y
    CONFIG_MAIN_STACK_SIZE=8192
    
    
    #new
    CONFIG_FS_FATFS_LFN=y
    CONFIG_DISK_DRIVER_SDMMC=y
    
    
    # You call nrfx_power_constlat_mode_request()
    # CONFIG_NRFX_POWER=y
    #new for power mgmt
    CONFIG_PM=y
    CONFIG_PM_DEVICE=y
    CONFIG_PM_POLICY_LATENCY_STANDALONE=y  
    CONFIG_NRFX_POWER=y 
    

    and still getting same errors

    [00:00:15.040,264] <dbg> sd: sd_send_interface_condition: Legacy card detected, no CMD8 support
    [00:00:15.040,278] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd0 arg 0x0
    [00:00:15.040,774] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd8 arg 0x1aa
    [00:00:15.041,260] <dbg> sd: sd_send_interface_condition: Legacy card detected, no CMD8 support
    [00:00:15.041,273] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd0 arg 0x0
    [00:00:15.041,766] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd8 arg 0x1aa
    [00:00:15.042,250] <dbg> sd: sd_send_interface_condition: Legacy card detected, no CMD8 support
    [00:00:15.042,265] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd0 arg 0x0
    [00:00:15.042,753] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd8 arg 0x1aa
    [00:00:15.043,237] <dbg> sd: sd_send_interface_condition: Legacy card detected, no CMD8 support
    [00:00:15.043,252] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd0 arg 0x0
    [00:00:15.043,739] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd8 arg 0x1aa
    [00:00:15.044,217] <dbg> sd: sd_send_interface_condition: Legacy card detected, no CMD8 support
    [00:00:15.044,230] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd0 arg 0x0
    [00:00:15.044,710] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd8 arg 0x1aa

    /* Initialize controller and query geometry; we de-init again so fs_mount owns lifecycle */
    static int sd_init_comms(const char *disk)
    {
        uint32_t block_count = 0U, block_size = 0U;
    
        (void)disk_access_ioctl(disk, DISK_IOCTL_CTRL_INIT, NULL);
    
        int rc = disk_access_init(disk); // fails this!!!!!!!
    
        if (rc) 
        {
            (void)disk_access_ioctl(disk, DISK_IOCTL_CTRL_DEINIT, NULL);
            return rc;
        }
    
        rc = disk_access_ioctl(disk, DISK_IOCTL_GET_SECTOR_COUNT, &block_count);
        if (rc) goto out;
    
        rc = disk_access_ioctl(disk, DISK_IOCTL_GET_SECTOR_SIZE, &block_size);
        if (rc) goto out;
    
        uint64_t cap_mb = ((uint64_t)block_count * (uint64_t)block_size) >> 20;
    
        LOG_INF("SD geometry: blocks=%u, size=%u (~%u MB)", block_count, block_size, (uint32_t)cap_mb);
    out:
        (void)disk_access_ioctl(disk, DISK_IOCTL_CTRL_DEINIT, NULL);
        return rc;
    }
    
    
    /* Optional boot/VBR inspector to hint at exFAT formatting, etc. */
    static int sdcard_inspect_boot(const char *disk_name)
    {
        (void)disk_access_ioctl(disk_name, DISK_IOCTL_CTRL_INIT, NULL);
    
        uint32_t ssize = 0U;
        int ret = disk_access_ioctl(disk_name, DISK_IOCTL_GET_SECTOR_SIZE, &ssize);
        if (ret != 0 || ssize == 0U)
        {
            LOG_WRN("Sector size query ret=%d size=%u; assuming %u", ret, ssize, SDCARD_SECTOR_SIZE);
            ssize = SDCARD_SECTOR_SIZE;
        }
    
        uint8_t buf[SDCARD_SECTOR_SIZE];
    
        if ( ssize > sizeof(buf) ) 
        {
            LOG_WRN("Sector size %u > %u buffer", ssize, (unsigned)sizeof(buf));
            return -E2BIG;
            goto out;
        }
    
        uint32_t vbr_lba = 0U;
    
        ret = disk_access_read(disk_name, buf, 0, 1);
        if (ret != 0)
        {
            LOG_ERR("disk_access_read('%s', LBA0) ret=%d", disk_name, ret);
            goto out;
        }
    
        uint8_t sig0 = buf[ssize - 2];
        uint8_t sig1 = buf[ssize - 1];
    
        if ((sig0 == 0x55U) && (sig1 == 0xAAU))
        {
            const uint8_t *p0 = &buf[0x1BE];
            uint32_t start =
                ((uint32_t)p0[8])       |
                ((uint32_t)p0[9]  << 8) |
                ((uint32_t)p0[10] << 16)|
                ((uint32_t)p0[11] << 24);
            vbr_lba = start;
            LOG_INF("MBR ok: P1 start LBA=%u (type=0x%02x)", vbr_lba, p0[4]);
        }
        else
        {
            vbr_lba = 0U;
            LOG_WRN("No 0x55AA at LBA0; assuming superfloppy VBR");
        }
    
        ret = disk_access_read(disk_name, buf, vbr_lba, 1);
        if (ret != 0)
        {
            LOG_ERR("read VBR@%u ret=%d", vbr_lba, ret);
            goto out;
        }
    
        sig0 = buf[ssize - 2];
        sig1 = buf[ssize - 1];
    
        char oem[9];
        memcpy(oem, &buf[3], 8);
        oem[8] = '\0';
        LOG_INF("VBR@%u sig=%02x %02x OEM='%s'", vbr_lba, sig0, sig1, oem);
    
        if (memcmp(&buf[3], "EXFAT   ", 8) == 0)
        {
            LOG_WRN("exFAT detected → enable CONFIG_FAT_FILESYSTEM_ELM_EXFAT or reformat FAT32");
            ret = -EOPNOTSUPP;
            goto out;
        }
    
        if (!((sig0 == 0x55U) && (sig1 == 0xAAU)))
        {
            LOG_WRN("VBR missing 0x55AA; volume not FAT-formatted");
            ret = -EINVAL;
            goto out;
        }
    
    out:
        (void)disk_access_ioctl(disk_name, DISK_IOCTL_CTRL_DEINIT, NULL);
        return ret;
    }
    
    /* -------------------- Mount / Unmount API -------------------- */
    static void enable_constant_latency(void)
    {
        // nrf_power_task_trigger(NRF_POWER, NRF_POWER_TASK_CONSTLAT);
    
        
        // Request constant latency mode
        nrfx_power_constlat_mode_request();
    }
    
    
    /* Mount FATFS on the SD card */
    int sdcard_mount(void)
    {
        enable_constant_latency();
    
        if (s_mounted)
        {
            return 0;
        }
    
        if (!device_is_ready(spiSDCard_dev)) 
        {
            LOG_ERR("SPI00 not ready!");
            return -ENODEV;
        }
    
        LOG_INF("SD card SPI00 bus is ready!");
    
    
        // dout_set(DOUT_SD_PWR, true);
        k_busy_wait(1000000);   /* let power stabilize */
    
        int rc = sd_init_comms(SD_DISK_NAME);
        if (rc != 0)
        {
            return rc;
        }
    
        s_mount.storage_dev = (void *)SD_DISK_NAME;
    
        rc = fs_mount(&s_mount);
        if (rc != 0)
        {
            LOG_ERR("fs_mount(%s) failed: %d (disk='%s')",
                    SDCARD_MOUNT_POINT, rc, SD_DISK_NAME);
            (void)sdcard_inspect_boot(SD_DISK_NAME);
            return rc;
        }
    
        s_mounted = true;
        LOG_INF("Mounted FAT at %s (disk='%s')", SDCARD_MOUNT_POINT, SD_DISK_NAME);
        return 0;
    }

    Minor update: The issue is still ongoing. I have a level shifter translating 1.8 V to 3.0 V between the MCU and the SD card, but currently there are no pull-ups on any of the lines—except for a pull-up on the CS line on the nRF side. I added a pull-up resistor on the SD card’s MISO line (on the SD card side), and now I’m getting a different error. So there’s still a problem, but it’s slightly changed.

    [00:00:12.955,020] <dbg> sdhc_spi: sdhc_spi_send_cmd: cmd0 arg 0x0
    [00:00:13.157,927] <err> sd: Card error on CMD0

  • Issue has now been resolved and this can be closed. Has been resolved by misconfiguration of the CS pin, enabling SPI21 instead of SPI00 and using constant power latency mode

Related