// Driver for accessing SD card in SDIO mode on RP2040.

#include "ZuluSCSI_platform.h"

#include <stdint.h>
#include <string.h>

//
// Hardware
//
#include <hardware/dma.h>
#include <hardware/gpio.h>
#include <hardware/clocks.h>
#include <hardware/pio.h>

//
// Platform
//
#include "pico/stdlib.h"

//
// Project
//
#include "diskio.h"
#include "my_debug.h"
#include "delays.h"
#include "rp2040_sdio.h"
#include "rp2040_sdio.pio.h"  // build\build\rp2040_sdio.pio.h
#include "sd_card_constants.h"
#include "sd_card.h"
#include "sd_timeouts.h"
#include "SdioCard.h"
#include "util.h"

#define STATE sd_card_p->sdio_if_p->state

static char const *errstr(sdio_status_t error) {
    switch (error) {
        case SDIO_OK:
            return "SDIO: OK";
        case SDIO_BUSY:
            return "SDIO: busy";
        case SDIO_ERR_RESPONSE_TIMEOUT:
            return "SDIO: Timed out waiting for response from card";
        case SDIO_ERR_RESPONSE_CRC:
            return "SDIO: Response CRC is wrong";
        case SDIO_ERR_RESPONSE_CODE:
            return "SDIO: Response command code does not match what was sent";
        case SDIO_ERR_DATA_TIMEOUT:
            return "SDIO: Timed out waiting for data block";
        case SDIO_ERR_DATA_CRC:
            return "SDIO: CRC for data packet is wrong";
        case SDIO_ERR_WRITE_CRC:
            return "SDIO: Card reports bad CRC for write";
        case SDIO_ERR_WRITE_FAIL:
            return "SDIO: Card reports write failure";
    }
    return "Unknown error";
}

//FIXME
#define azdbg(arg1, ...) {\
    DBG_PRINTF("%s,%d: %s\n", __func__, __LINE__, arg1); \
}
#define TRACE_PRINTF(fmt, args...)
//#define TRACE_PRINTF DBG_PRINTF

#define checkReturnOk(call) ((STATE.error = (call)) == SDIO_OK ? true : logSDError(sd_card_p, __LINE__))

static bool logSDError(sd_card_t *sd_card_p, int line)
{
    STATE.error_line = line;
    EMSG_PRINTF("%s at line %d; error code %d\n", 
        errstr(STATE.error), line, (int)STATE.error);
    return false;
}

/*
    CLKDIV is from sd_driver\SDIO\rp2040_sdio.pio

    baud = clk_sys / (CLKDIV * clk_div) 
    baud * CLKDIV * clk_div = clk_sys;
    clk_div = clk_sys / (CLKDIV * baud)
*/
static float calculate_clk_div(uint baud) {
    float div = (float)clock_get_hz(clk_sys) / (CLKDIV * baud);
    /* Baud rate cannot exceed clk_sys frequency divided by CLKDIV! */
    DBG_PRINTF("clk_div = %f\n", div);
    myASSERT(div >= 1 && div <= 65536);
    return div;
}

bool sd_sdio_begin(sd_card_t *sd_card_p)
{
    uint32_t reply;
    sdio_status_t status;
    
    // Initialize at 400 kHz clock speed
    if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(400 * 1000)))
        return false; 

    // Establish initial connection with the card
    for (int retries = 0; retries < 5; retries++)
    {
        delay_ms(1);
        reply = 0;
        rp2040_sdio_command_R1(sd_card_p, CMD0_GO_IDLE_STATE, 0, NULL); // GO_IDLE_STATE
        status = rp2040_sdio_command_R1(sd_card_p, CMD8_SEND_IF_COND, 0x1AA, &reply); // SEND_IF_COND

        if (status == SDIO_OK && reply == 0x1AA)
        {
            break;
        }
    }

    if (reply != 0x1AA || status != SDIO_OK)
    {
        // azdbg("SDIO not responding to CMD8 SEND_IF_COND, status ", (int)status, " reply ", reply);
        EMSG_PRINTF("%s,%d SDIO not responding to CMD8 SEND_IF_COND, status 0x%x reply 0x%lx\n", 
            __func__, __LINE__, status, reply);
        return false;
    }

    // Send ACMD41 to begin card initialization and wait for it to complete
    uint32_t start = millis();
    do {
        if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, 0, &reply)) || // APP_CMD
            !checkReturnOk(rp2040_sdio_command_R3(sd_card_p, ACMD41_SD_SEND_OP_COND, 0xD0040000, &STATE.ocr))) // 3.0V voltage
            // !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD41, 0xC0100000, &STATE.ocr)))
        {
            return false;
        }

        if ((uint32_t)(millis() - start) > sd_timeouts.sd_sdio_begin)
        {
            EMSG_PRINTF("SDIO card initialization timeout\n");
            return false;
        }
    } while (!(STATE.ocr & (1 << 31)));

    // Get CID
    // CMD2 is valid only in "ready" state;
    // Transitions to "ident" state
    // Note: CMD10 is valid only in "stby" state
    if (!checkReturnOk(rp2040_sdio_command_R2(sd_card_p, CMD2_ALL_SEND_CID, 0, (uint8_t *)&sd_card_p->state.CID)))
    {
        azdbg("SDIO failed to read CID");
        return false;
    }

    // Get relative card address
    // Valid in "ident" or "stby" state; transitions to "stby"
    // Transitions from "card-identification-mode" to "data-transfer-mode"
    if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD3_SEND_RELATIVE_ADDR, 0, &STATE.rca)))
    {
        azdbg("SDIO failed to get RCA");
        return false;
    }

    // Get CSD
    // Valid in "stby" state; stays in "stby" state
    if (!checkReturnOk(rp2040_sdio_command_R2(sd_card_p, CMD9_SEND_CSD, STATE.rca, sd_card_p->state.CSD)))
    {
        azdbg("SDIO failed to read CSD");
        return false;
    }
    sd_card_p->state.sectors = CSD_sectors(sd_card_p->state.CSD);

    // Select card
    // Valid in "stby" state; 
    // If card is addressed, transitions to "tran" state
    if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD7_SELECT_CARD, STATE.rca, &reply)))
    {
        azdbg("SDIO failed to select card");
        return false;
    }

    /* At power up CD/DAT3 has a 50KOhm pull up enabled in the card. 
    This resistor serves two functions Card detection and Mode Selection. 
    For Mode Selection, the host can drive the line high or let it be pulled high to select SD mode. 
    If the host wants to select SPI mode it should drive the line low. 
    For Card detection, the host detects that the line is pulled high. 
    This pull-up should be disconnected by the user, during regular data transfer, 
    with SET_CLR_CARD_DETECT (ACMD42) command. */
    // Disconnect the 50 KOhm pull-up resistor on CD/DAT3
    // Valid in "tran" state; stays in "tran" state
    if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) ||
        !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD42_SET_CLR_CARD_DETECT, 0, &reply)))
    {
        azdbg("SDIO failed to disconnect pull-up");
        return false;
    }

    // Set 4-bit bus mode
    // Valid in "tran" state; stays in "tran" state
    if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) ||
        !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD6_SET_BUS_WIDTH, 2, &reply)))
    {
        azdbg("SDIO failed to set bus width");
        return false;
    }
    if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply))) // SET_BLOCKLEN
    {
        EMSG_PRINTF("%s,%d SDIO failed to set BLOCKLEN\n", __func__, __LINE__);
        return false;
    }
    // Increase to high clock rate
    if (!sd_card_p->sdio_if_p->baud_rate)
        sd_card_p->sdio_if_p->baud_rate = clock_get_hz(clk_sys) / 12; // Default
    if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(sd_card_p->sdio_if_p->baud_rate)))
        return false; 

    return true;
}

uint8_t sd_sdio_errorCode(sd_card_t *sd_card_p) // const
{
    return STATE.error;
}

uint32_t sd_sdio_errorData() // const
{
    return 0;
}

uint32_t sd_sdio_errorLine(sd_card_t *sd_card_p) // const
{
    return STATE.error_line;
}

bool sd_sdio_isBusy(sd_card_t *sd_card_p) 
{
    // return (sio_hw->gpio_in & (1 << SDIO_D0)) == 0;
    return (sio_hw->gpio_in & (1 << sd_card_p->sdio_if_p->D0_gpio)) == 0;
}

bool sd_sdio_readOCR(sd_card_t *sd_card_p, uint32_t* ocr)
{
    // SDIO mode does not have CMD58, but main program uses this to
    // poll for card presence. Return status register instead.
    return checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, ocr));
}

uint32_t sd_sdio_status(sd_card_t *sd_card_p)
{
    uint32_t reply;
    if (checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, &reply)))
        return reply;
    else
        return 0;
}

bool sd_sdio_stopTransmission(sd_card_t *sd_card_p, bool blocking)
{

    STATE.ongoing_wr_mlt_blk = false;

    uint32_t reply;
    if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD12_STOP_TRANSMISSION, 0, &reply)))
    {
        return false;
    }

    if (!blocking)
    {
        return true;
    }
    else
    {
        uint32_t start = millis();
        while (millis() - start < 200 && sd_sdio_isBusy(sd_card_p));
        if (sd_sdio_isBusy(sd_card_p))
        {
            EMSG_PRINTF("sd_sdio_stopTransmission() timeout\n");
            return false;
        }
        else
        {
            return true;
        }
    }
}

uint8_t sd_sdio_type(sd_card_t *sd_card_p) // const
{
    if (STATE.ocr & (1 << 30))
        return SDCARD_V2HC;
    else
        return SDCARD_V2;
}


/* Writing and reading */

bool sd_sdio_writeSector(sd_card_t *sd_card_p, uint32_t sector, const uint8_t* src)
{
    if (STATE.ongoing_wr_mlt_blk)
        // Stop any ongoing write transmission
        if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;

    if (((uint32_t)src & 3) != 0) {
        // Buffer is not aligned, need to memcpy() the data to a temporary buffer.
        memcpy(STATE.dma_buf, src, sizeof(STATE.dma_buf));
        src = (uint8_t*)STATE.dma_buf;
    }

    uint32_t reply;
    if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */
        !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD24_WRITE_BLOCK, sector, &reply)) || // WRITE_BLOCK
        !checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, 1))) // Start transmission
    {
        return false;
    }

    do {
        uint32_t bytes_done;
        STATE.error = rp2040_sdio_tx_poll(sd_card_p, &bytes_done);
    } while (STATE.error == SDIO_BUSY);

    if (STATE.error != SDIO_OK)
    {
        EMSG_PRINTF("sd_sdio_writeSector(%lu) failed: %s (%d)\n", 
            sector, errstr(STATE.error), (int)STATE.error);
    }

    return STATE.error == SDIO_OK;
}

bool sd_sdio_writeSectors(sd_card_t *sd_card_p, uint32_t sector, const uint8_t *src, size_t n) {
    if (((uint32_t)src & 3) != 0) {
        // Unaligned write, execute sector-by-sector
        for (size_t i = 0; i < n; i++) {
            if (!sd_sdio_writeSector(sd_card_p, sector + i, src + 512 * i)) {
                return false;
            }
        }
        return true;
    }
    if (STATE.ongoing_wr_mlt_blk && sector == STATE.wr_mlt_blk_cnt_sector) {
        /* Continue a multiblock write */
        if (!checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, n)))  // Start transmission
            return false;
    } else {
        // Stop any previous transmission
        if (STATE.ongoing_wr_mlt_blk) {
            if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;
        }
        uint32_t reply;
        if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD25_WRITE_MULTIPLE_BLOCK, sector, &reply)) ||
            !checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, n)))  // Start transmission
        {
            return false;
        }
    }

    do {
        uint32_t bytes_done;
        STATE.error = rp2040_sdio_tx_poll(sd_card_p, &bytes_done);
    } while (STATE.error == SDIO_BUSY);

    if (STATE.error != SDIO_OK) {
        EMSG_PRINTF("sd_sdio_writeSectors(,%lu,,%zu) failed: %s (%d)\n", sector, n, errstr(STATE.error), (int)STATE.error);
        sd_sdio_stopTransmission(sd_card_p, true);
        return false;
    } else {
        STATE.wr_mlt_blk_cnt_sector = sector + n;
        STATE.ongoing_wr_mlt_blk = true;
        return true;
    }
    /* Optimization:
    To optimize large contiguous writes,
    postpone stopping transmission until it is
    clear that the next operation is not a continuation.

    Any transactions other than a `sd_sdio_writeSectors`
    continuation must stop any ongoing transmission
    before proceding.
    */
}

bool sd_sdio_readSector(sd_card_t *sd_card_p, uint32_t sector, uint8_t* dst)
{
    if (STATE.ongoing_wr_mlt_blk)
        // Stop any ongoing transmission
        if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;

    uint8_t *real_dst = dst;
    if (((uint32_t)dst & 3) != 0)
    {
        // Buffer is not aligned, need to memcpy() the data from a temporary buffer.
        dst = (uint8_t*)STATE.dma_buf;
    }
    uint32_t reply;
    if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */
        !checkReturnOk(rp2040_sdio_rx_start(sd_card_p, dst, 1, SDIO_BLOCK_SIZE)) || // Prepare for reception
        !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD17_READ_SINGLE_BLOCK, sector, &reply))) // READ_SINGLE_BLOCK
    {
        return false;
    }

    do {
        STATE.error = rp2040_sdio_rx_poll(sd_card_p, SDIO_WORDS_PER_BLOCK);
    } while (STATE.error == SDIO_BUSY);

    if (STATE.error != SDIO_OK)
    {
        EMSG_PRINTF("sd_sdio_readSector(,%lu,) failed: %s (%d)\n", 
            sector, errstr(STATE.error), (int)STATE.error);
    }

    if (dst != real_dst)
    {
        memcpy(real_dst, STATE.dma_buf, sizeof(STATE.dma_buf));
    }

    return STATE.error == SDIO_OK;
}

bool sd_sdio_readSectors(sd_card_t *sd_card_p, uint32_t sector, uint8_t* dst, size_t n)
{
    if (STATE.ongoing_wr_mlt_blk)
        // Stop any ongoing transmission
        if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;
        
    if (((uint32_t)dst & 3) != 0 || sector + n >= sd_card_p->state.sectors)
    {
        // Unaligned read or end-of-drive read, execute sector-by-sector
        for (size_t i = 0; i < n; i++)
        {
            if (!sd_sdio_readSector(sd_card_p, sector + i, dst + 512 * i))
            {
                return false;
            }
        }
        return true;
    }

    uint32_t reply;
    if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */
        !checkReturnOk(rp2040_sdio_rx_start(sd_card_p, dst, n, SDIO_BLOCK_SIZE)) || // Prepare for reception
        !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD18_READ_MULTIPLE_BLOCK, sector, &reply))) // READ_MULTIPLE_BLOCK
    {
        return false;
    }

    do {
        STATE.error = rp2040_sdio_rx_poll(sd_card_p, SDIO_WORDS_PER_BLOCK);
    } while (STATE.error == SDIO_BUSY);

    if (STATE.error != SDIO_OK)
    {
        EMSG_PRINTF("sd_sdio_readSectors(%ld,...,%d)  failed: %s (%d)\n", 
            sector, n, errstr(STATE.error), STATE.error);
        sd_sdio_stopTransmission(sd_card_p, true);
        return false;
    }
    else
    {
        return sd_sdio_stopTransmission(sd_card_p, true);
    }
}

// Get 512 bit (64 byte) SD Status
bool rp2040_sdio_get_sd_status(sd_card_t *sd_card_p, uint8_t response[64]) {
    uint32_t reply;
    if (!checkReturnOk(rp2040_sdio_rx_start(sd_card_p, response, 1, 64)) || // Prepare for reception
        !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) ||  // APP_CMD
        !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD13_SD_STATUS, 0, &reply))) // SD Status
    {
        EMSG_PRINTF("ACMD13 failed\n");
        return false;
    }
    // Read 512 bit block on DAT bus (not CMD)
    do {
        STATE.error = rp2040_sdio_rx_poll(sd_card_p, 64 / 4);
    } while (STATE.error == SDIO_BUSY);

    if (STATE.error != SDIO_OK)
    {
        EMSG_PRINTF("ACMD13 failed: %s (%d)\n", errstr(STATE.error), (int)STATE.error);
    }
    return STATE.error == SDIO_OK;

}

static bool sd_sdio_test_com(sd_card_t *sd_card_p) {
    bool success = false;

    if (!(sd_card_p->state.m_Status & STA_NOINIT)) {
        // SD card is currently initialized

        // Get status
        uint32_t reply = 0;
        sdio_status_t status = rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, &reply);

        // Only care that communication succeeded
        success = (status == SDIO_OK);

        if (!success) {
            // Card no longer sensed - ensure card is initialized once re-attached
            sd_card_p->state.m_Status |= STA_NOINIT;
        }
    } else {
        // Do a "light" version of init, just enough to test com

        // Initialize at 400 kHz clock speed
        if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(400 * 1000)))
            return false; 

        // Establish initial connection with the card
        rp2040_sdio_command_R1(sd_card_p, CMD0_GO_IDLE_STATE, 0, NULL); // GO_IDLE_STATE
        uint32_t reply = 0;
        sdio_status_t status = rp2040_sdio_command_R1(sd_card_p, CMD8_SEND_IF_COND, 0x1AA, &reply); // SEND_IF_COND

        success = (reply == 0x1AA && status == SDIO_OK);
    }

    return success;
}

#if PICO_SDK_VERSION_MAJOR < 2
    typedef enum gpio_function gpio_function_t;
#endif

// Helper function to configure whole GPIO in one line
static void gpio_conf(uint gpio, gpio_function_t fn, bool pullup, bool pulldown, bool output, bool initial_state)
{
    gpio_put(gpio, initial_state);
    gpio_set_dir(gpio, output);
    gpio_set_pulls(gpio, pullup, pulldown);
    gpio_set_function(gpio, fn);

    // See rp2040_sdio_init
}

static DSTATUS sd_sdio_init(sd_card_t *sd_card_p) {
    sd_lock(sd_card_p);

    // Make sure there's a card in the socket before proceeding
    sd_card_detect(sd_card_p);
    if (sd_card_p->state.m_Status & STA_NODISK) {
        sd_unlock(sd_card_p);
        return sd_card_p->state.m_Status;
    }
    // Make sure we're not already initialized before proceeding
    if (!(sd_card_p->state.m_Status & STA_NOINIT)) {
        sd_unlock(sd_card_p);
        return sd_card_p->state.m_Status;
    }
    // Initialize the member variables
    sd_card_p->state.card_type = SDCARD_NONE;

    //        pin                             function        pup   pdown  out    state
    gpio_conf(sd_card_p->sdio_if_p->CLK_gpio, GPIO_FUNC_PIO1, true, false, true,  true);
    gpio_conf(sd_card_p->sdio_if_p->CMD_gpio, GPIO_FUNC_PIO1, true, false, true,  true);
    gpio_conf(sd_card_p->sdio_if_p->D0_gpio,  GPIO_FUNC_PIO1, true, false, false, true);
    gpio_conf(sd_card_p->sdio_if_p->D1_gpio,  GPIO_FUNC_PIO1, true, false, false, true);
    gpio_conf(sd_card_p->sdio_if_p->D2_gpio,  GPIO_FUNC_PIO1, true, false, false, true);
    gpio_conf(sd_card_p->sdio_if_p->D3_gpio,  GPIO_FUNC_PIO1, true, false, false, true);

    bool ok = sd_sdio_begin(sd_card_p);
    if (ok) {
        // The card is now initialized
        sd_card_p->state.m_Status &= ~STA_NOINIT;
    }
    sd_unlock(sd_card_p);
    return sd_card_p->state.m_Status;
}
static void sd_sdio_deinit(sd_card_t *sd_card_p) {
    sd_lock(sd_card_p);

    sd_card_p->state.m_Status |= STA_NOINIT;
    sd_card_p->state.card_type = SDCARD_NONE;

    //        pin                             function        pup   pdown   out    state
    gpio_conf(sd_card_p->sdio_if_p->CLK_gpio, GPIO_FUNC_NULL, false, false, false, false);
    gpio_conf(sd_card_p->sdio_if_p->CMD_gpio, GPIO_FUNC_NULL, false, false, false, false);
    gpio_conf(sd_card_p->sdio_if_p->D0_gpio,  GPIO_FUNC_NULL, false, false, false, false);
    gpio_conf(sd_card_p->sdio_if_p->D1_gpio,  GPIO_FUNC_NULL, false, false, false, false);
    gpio_conf(sd_card_p->sdio_if_p->D2_gpio,  GPIO_FUNC_NULL, false, false, false, false);
    gpio_conf(sd_card_p->sdio_if_p->D3_gpio,  GPIO_FUNC_NULL, false, false, false, false);

    //TODO: free other resources: PIO, SMs, etc.

    sd_unlock(sd_card_p);    
}

uint32_t sd_sdio_sectorCount(sd_card_t *sd_card_p) {
    myASSERT(!(sd_card_p->state.m_Status & STA_NOINIT));
    return CSD_sectors(sd_card_p->state.CSD);
}

static block_dev_err_t sd_sdio_write_blocks(sd_card_t *sd_card_p, const uint8_t *buffer, uint32_t ulSectorNumber,
                                            uint32_t blockCnt) {
    TRACE_PRINTF("%s(,,,%zu)\n", __func__, blockCnt);
    bool ok = true;

    sd_lock(sd_card_p);

    if (1 == blockCnt)
        ok = sd_sdio_writeSector(sd_card_p, ulSectorNumber, buffer);
    else
        ok = sd_sdio_writeSectors(sd_card_p, ulSectorNumber, buffer, blockCnt);

    sd_unlock(sd_card_p);

    if (ok)
        return SD_BLOCK_DEVICE_ERROR_NONE;
    else
        return SD_BLOCK_DEVICE_ERROR_WRITE;
}
static block_dev_err_t sd_sdio_read_blocks(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t ulSectorNumber,
                                           uint32_t ulSectorCount) {
    bool ok = true;

    sd_lock(sd_card_p);

    if (1 == ulSectorCount)
        ok = sd_sdio_readSector(sd_card_p, ulSectorNumber, buffer);
    else
        ok = sd_sdio_readSectors(sd_card_p, ulSectorNumber, buffer, ulSectorCount);

    sd_unlock(sd_card_p);

    if (ok)
        return SD_BLOCK_DEVICE_ERROR_NONE;
    else
        return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
}
static block_dev_err_t sd_sync(sd_card_t *sd_card_p) {
    sd_lock(sd_card_p);
    block_dev_err_t err = SD_BLOCK_DEVICE_ERROR_NONE;
    if (STATE.ongoing_wr_mlt_blk)
        if (!sd_sdio_stopTransmission(sd_card_p, true))
            err = SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
    sd_unlock(sd_card_p);
    return err;
}
void sd_sdio_ctor(sd_card_t *sd_card_p) {
    myASSERT(sd_card_p->sdio_if_p); // Must have an interface object
    /*
    Pins CLK_gpio, D1_gpio, D2_gpio, and D3_gpio are at offsets from pin D0_gpio.
    The offsets are determined by sd_driver\SDIO\rp2040_sdio.pio.
    */
    myASSERT(!sd_card_p->sdio_if_p->CLK_gpio);
    myASSERT(!sd_card_p->sdio_if_p->D1_gpio);
    myASSERT(!sd_card_p->sdio_if_p->D2_gpio);
    myASSERT(!sd_card_p->sdio_if_p->D3_gpio);

    sd_card_p->sdio_if_p->CLK_gpio = (sd_card_p->sdio_if_p->D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32;
    sd_card_p->sdio_if_p->D1_gpio = sd_card_p->sdio_if_p->D0_gpio + 1;
    sd_card_p->sdio_if_p->D2_gpio = sd_card_p->sdio_if_p->D0_gpio + 2;
    sd_card_p->sdio_if_p->D3_gpio = sd_card_p->sdio_if_p->D0_gpio + 3;

    sd_card_p->state.m_Status = STA_NOINIT;

    sd_card_p->init = sd_sdio_init;
    sd_card_p->deinit = sd_sdio_deinit;
    sd_card_p->write_blocks = sd_sdio_write_blocks;
    sd_card_p->read_blocks = sd_sdio_read_blocks;
    sd_card_p->sync = sd_sync;
    sd_card_p->get_num_sectors = sd_sdio_sectorCount;
    sd_card_p->sd_test_com = sd_sdio_test_com;
}
