/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is written based on the information obtained from:
 *     http://elm-chan.org/docs/mmc/mmc_e.html
 *     http://www.rjhcoding.com/avrc-sd-interface-4.php
 *     https://www.cl.cam.ac.uk/teaching/1112/P31/code/diskio.c
 *     https://www.sdcard.org/downloads/pls/index.html
 *     https://elinux.org/images/d/d3/Mmc_spec.pdf
 * ----------------------------------------------------------------------------------
 */


#include <eCxx/Platform/Common/Application.h>
#include <eCxx/Platform/RawMemoryCard.h>


namespace eCxx {


#if !defined(__x86_64__) && !defined(__i386__)


//
// Command constants
//
static constexpr uint8_t CMD0   = 0x40 +  0; // GO_IDLE_STATE
static constexpr uint8_t CMD1   = 0x40 +  1; // SEND_OP_COND           (MMC)
static constexpr uint8_t ACMD41 = 0xC0 + 41; // SEND_OP_COND           (SDC)
static constexpr uint8_t CMD8   = 0x40 +  8; // SEND_IF_COND
static constexpr uint8_t CMD9   = 0x40 +  9; // SEND_CSD
static constexpr uint8_t CMD10  = 0x40 + 10; // SEND_CID
static constexpr uint8_t CMD12  = 0x40 + 12; // STOP_TRANSMISSION
static constexpr uint8_t ACMD13 = 0xC0 + 13; // SD_STATUS              (SDC)
static constexpr uint8_t CMD16  = 0x40 + 16; // SET_BLOCKLEN
static constexpr uint8_t CMD17  = 0x40 + 17; // READ_SINGLE_BLOCK
static constexpr uint8_t CMD18  = 0x40 + 18; // READ_MULTIPLE_BLOCK
static constexpr uint8_t CMD23  = 0x40 + 23; // SET_BLOCK_COUNT        (MMC)
static constexpr uint8_t ACMD23 = 0xC0 + 23; // SET_WR_BLK_ERASE_COUNT (SDC)
static constexpr uint8_t CMD24  = 0x40 + 24; // WRITE_BLOCK
static constexpr uint8_t CMD25  = 0x40 + 25; // WRITE_MULTIPLE_BLOCK
static constexpr uint8_t CMD55  = 0x40 + 55; // APP_CMD
static constexpr uint8_t CMD58  = 0x40 + 58; // READ_OCR


//
// Token constants
//
static constexpr uint8_t R1_IDLE                      = 0b00000001;
static constexpr uint8_t R1_SUCCESS                   = 0b00000000;

static constexpr uint8_t CMD17_CMD18_CMD24_DATA_TOKEN = 0b11111110;

static constexpr uint8_t CMD25_DATA_TOKEN             = 0b11111100;
static constexpr uint8_t CMD25_STOP_TOKEN             = 0b11111101;

static constexpr uint8_t DATA_WRITE_MASK              = 0b00011111;
static constexpr uint8_t DATA_WRITE_ACCEPTED          = 0b00000101;
static constexpr uint8_t DATA_WRITE_ERROR             = 0b00001101;
static constexpr uint8_t DATA_WRITE_CRC_ERROR         = 0b00001011;


//
// Helper functions
//
static inline uint8_t waitReady()
{
    // For convenience
    auto& spi = Application::spi();

    // Wait until ready with timeout of 500 mS
    const uint32_t startMillis = Application::millis();

    uint8_t res = spi.ntmReadXFF();
    do {
        Application::yield();
        res = spi.ntmReadXFF();
    }
    while( (res != 0xFF) && (Application::millis() - startMillis) < 500 );

    return res;
}


static inline bool selectCard()
{
    // For convenience
    auto& spi = Application::spi();

    // Select the card
    spi.ntmSelectSlave();
    spi.ntmWrite(0xFF);

    // Wait until it is ready
    return waitReady() == 0xFF;
}


static inline void deselectCard()
{
    // For convenience
    auto& spi = Application::spi();

    // Deselect the card
    spi.ntmWrite(0xFF);
    spi.ntmDeselectSlave();
}


static inline uint8_t selectCardAndSendCmd(uint8_t cmd, uint32_t arg) // Always return the R1 part of the response
{
    // For convenience
    auto& spi = Application::spi();

    // ACMD<N> is the command sequense of CMD55-CMD<N>
    uint8_t r1;

    if(cmd & 0x80) {
        cmd &= 0x7F;
        r1 = selectCardAndSendCmd(CMD55, 0);
        if(r1 > 1) return r1;
    }

    // Select the card
    if( !selectCard() ) return 0xFF;

    // Prepare the CRC
    uint8_t crc = 0xFF;              // Dummy CRC
         if(cmd == CMD0) crc = 0x95; // Valid CRC for CMD0 (arg = 0x00000000)
    else if(cmd == CMD8) crc = 0x87; // Valid CRC for CMD8 (arg = 0x000001AA)

    // Send command packet
    spi.ntmWrite( cmd                             ); // Send command
    spi.ntmWrite( static_cast<uint8_t>(arg >> 24) ); // Send argument [31:24]
    spi.ntmWrite( static_cast<uint8_t>(arg >> 16) ); // Send argument [23:16]
    spi.ntmWrite( static_cast<uint8_t>(arg >>  8) ); // Send argument [15:08]
    spi.ntmWrite( static_cast<uint8_t>(arg      ) ); // Send argument [07:00]
    spi.ntmWrite( crc                             ); // Send CRC

    // Receive command response
    for(uint8_t i = 0; i < 10; ++i) {
        r1 = spi.ntmReadXFF();
        if( !(r1 & 0x08) ) break; // A response can be recognized by a zero start bit
    }

    if(r1 & 0x08) return r1; // Response timeout

    if(cmd == CMD12) {
        // Skip trailing zeroes for R1b
        while( spi.ntmReadXFF() == 0x00 ) Application::yield();
    }

    // Return the response
    return r1;
}


static inline RawMemoryCard::RMCResultCode initializeCard(uint8_t& cardType)
{
    // For convenience
    using RMCResultCode = RawMemoryCard::RMCResultCode;

    auto& spi = Application::spi();

    // Initialize using slow clock frequency
    spi.ntmBegin(RawMemoryCard::SPIFrequency_Init);

    // Set an invalid value to the card type
    cardType = 0;

    // Reset the memory card
    uint8_t resetTry = 3;

    while(resetTry) {
        // Send 80 dummy clocks
        deselectCard();
        for(uint8_t i = 0; i < 10; ++i) spi.ntmWrite(0xFF);
        // Enter idle state
        if( selectCardAndSendCmd(CMD0, 0) == R1_IDLE ) {
            // Reset success
            resetTry = 0xFF;
            break;
        }
        // Try to get out from error state
        deselectCard();
        selectCard  ();
        for(uint16_t i = 0; i < RawMemoryCard::SectorSize * 2; ++i) spi.ntmWrite(0xFF);
        // Decrement the counter
        --resetTry;
    }

    if(resetTry != 0xFF) return RMCResultCode::ResetFailed;

    // Check for SD version 1 or MMC
    if( selectCardAndSendCmd(CMD8, 0x000001AA) != R1_IDLE ) {
        // Check for SD version 1
        uint32_t startMillis = Application::millis();
        do {
            const uint8_t r1 = selectCardAndSendCmd(ACMD41, 0);
            if(r1 == R1_SUCCESS) {
                cardType = RawMemoryCard::CT_SD1;
                break;
            }
        } while( (Application::millis() - startMillis) < 1000 );
        // Check for MMC
        if(!cardType) {
            startMillis = Application::millis();
            do {
                const uint8_t r1 = selectCardAndSendCmd(CMD1, 0);
                if(r1 == R1_SUCCESS) {
                    cardType = RawMemoryCard::CT_MMC;
                    break;
                }
            } while( (Application::millis() - startMillis) < 1000 );
        }
        // Check for unknown card
        if(!cardType) return RMCResultCode::UnsupportedCard;
    }

    // Check for SD version 2
    else {
        // Get the trailing values of R7
        uint8_t res4[4];
        for(uint8_t i = 0; i < 4; ++i) res4[i] = spi.ntmReadXFF();
        // Check if the lower 12 bits matched 0x01AA
        if(res4[2] != 0x01 || res4[3] != 0xAA) return RMCResultCode::UnsupportedCard;
        // Wait until the card leaves idle state
        const uint32_t startMillis = Application::millis();
        do {
            const uint8_t r1 = selectCardAndSendCmd(ACMD41, 0x40000000);
            if(r1 == R1_SUCCESS) {
                cardType = RawMemoryCard::CT_SD2;
                break;
            }
        } while( (Application::millis() - startMillis) < 1000 );
        // Check for unknown card
        if(!cardType) return RMCResultCode::UnsupportedCard;
        // Read OCR
        if( selectCardAndSendCmd(CMD58, 0) == R1_SUCCESS ) {
            // Get the trailing values of R3
            for(uint8_t i = 0; i < 4; ++i) res4[i] = spi.ntmReadXFF();
            // Check the CCS bit (bit 30 of OCR)
            if(res4[0] & 0x40) cardType |= RawMemoryCard::CT_BLOCK;
        }
    }

    // Set R/W block size (sector size)
    if( selectCardAndSendCmd(CMD16, RawMemoryCard::SectorSize) != R1_SUCCESS ) {
        cardType = 0;
        return RMCResultCode::UnsupportedCard;
    }

    // Deselect the card
    deselectCard();

    // Change to fast clock frequency
    spi.ntmBegin(RawMemoryCard::SPIFrequency_Work);

    // Done
    return RMCResultCode::NoError;
}


static inline bool waitRecvReady()
{
    // For convenience
    auto& spi = Application::spi();

    // Wait for data packet with timeout of 200 mS
    const uint32_t startMillis = Application::millis();
          uint8_t  token;
    do {
        token = spi.ntmReadXFF();
    } while( (token == 0xFF) && (Application::millis() - startMillis) < 200 );

    if(token != CMD17_CMD18_CMD24_DATA_TOKEN) return false;

    // Done
    return true;
}


static inline bool recvOneDataBlockMO4(uint8_t* buff, uint16_t count)
{
    // For convenience
    auto& spi = Application::spi();

    // Wait until the card is ready to send data
    if( !waitRecvReady() ) return false;

    // Receive the data block into buffer
    do {
        *buff++ = spi.ntmReadXFF();
        *buff++ = spi.ntmReadXFF();
        *buff++ = spi.ntmReadXFF();
        *buff++ = spi.ntmReadXFF();
    } while(count -= 4);

    // Discard the CRC
    spi.ntmReadXFF();
    spi.ntmReadXFF();

    // Done
    return true;
}


static inline bool recvOneDataBlockVar(uint8_t* buff, uint16_t count)
{
    // For convenience
    auto& spi = Application::spi();

    // Wait until the card is ready to send data
    if( !waitRecvReady() ) return false;

    // Receive the data block into buffer
    const uint16_t len = eCxx::min(count, RawMemoryCard::SectorSize);

    for(uint16_t i = 0; i < len; ++i) *buff++ = spi.ntmReadXFF();

    // Discard the remaining bytes as needed
    if(len < RawMemoryCard::SectorSize) {
        for(uint16_t i = len; i < RawMemoryCard::SectorSize; ++i) spi.ntmReadXFF();
    }

    // Discard the CRC
    spi.ntmReadXFF();
    spi.ntmReadXFF();

    // Fill the rest of the buffer with zeroes as needed
    if(count > len) {
        for(uint16_t i = len; i < count; ++i) *buff++ = 0;
    }

    // Done
    return true;
}


static inline bool sendOneDataBlockVar(const uint8_t* buff, uint16_t count, uint8_t token)
{
    // For convenience
    auto& spi = Application::spi();

    // Wait until the card is ready
    if(waitReady() != 0xFF) return false;

    // Send data token
    spi.ntmWrite(token);

    // Send the buffer data
    const uint16_t len = eCxx::min(count, RawMemoryCard::SectorSize);

    for(uint16_t i = 0; i < len; ++i) spi.ntmWrite(*buff++);

    // Send zeroes as needed
    if(len < RawMemoryCard::SectorSize) {
        for(uint16_t i = len; i < RawMemoryCard::SectorSize; ++i) spi.ntmWrite(0);
    }

    // Send dummy CRC
    spi.ntmWrite(0xFF);
    spi.ntmWrite(0xFF);

    // Check if the operation was not accepted
    if( (spi.ntmReadXFF() & DATA_WRITE_MASK) != DATA_WRITE_ACCEPTED ) return false;

    // Done
    return true;
}


//
// A simple raw-mode memory card reader and writer
//
RawMemoryCard::RawMemoryCard()
: _cardType     (0)
, _sBlockTotal  (0)
, _sBlockCounter(0)
, _sByteCounter (0)
{}


RawMemoryCard::~RawMemoryCard()
{ end(); }


RawMemoryCard::RMCResultCode RawMemoryCard::begin()
{
    // For convenience
    auto& spi = Application::spi();

    // Simply exit if it has been initialized
    if(_cardType) return RMCResultCode::NoError;

    // Initialize SPI
    spi.begin(SPIMode::Master);

    // Initialize card
    const RMCResultCode res = initializeCard(_cardType);

    // Shutdown SPI if initialization failed
    if(res != RMCResultCode::NoError) spi.end();

    // Return the result code
    return res;
}


void RawMemoryCard::end()
{
    // Simply exit if it has not been initialized
    if(!_cardType) return;

    // Select and deselect the card
    selectCard();
    deselectCard();

    // Uninitialize SPI
    Application::spi().end();

    // Reset variables
    _cardType      = 0;
    _sBlockTotal   = 0;
    _sBlockCounter = 0;
    _sByteCounter  = 0;
}


RawMemoryCard::RMCResultCode RawMemoryCard::getSectorSize(uint16_t& ret) const
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // Currently the sector size is fixed
    ret = RawMemoryCard::SectorSize;

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::getSectorCount(uint32_t& ret) const
{
    // Read CSD
          uint8_t       csd[16];
    const RMCResultCode res = getCSD(csd);

    if(res != RMCResultCode::NoError) return res;

    // Calculate the number of sectors
    switch(csd[0] >> 6) {
        // CSD version 1.0 (SD and MMC)
        //  0 = 127:120
        //  1 = 119:112
        //  2 = 111:104
        //  3 = 103: 96
        //  4 =  95: 88
        //  5 =  87: 80 0000BBBB
        //  6 =  79: 72 000000CC
        //  7 =  71: 64 CCCCCCCC
        //  8 =  63: 56 CC000000
        //  9 =  55: 48 000000MM
        // 10 =  47: 40 M0000000
        // 11 =  39: 32
        // 12 =  31: 24
        // 13 =  23: 16
        // 14 =  15:  8
        // 15 =   7:  0
        case 0 : {
                const uint16_t csize = ( static_cast<uint16_t>(csd[ 6] & 0x03) << 10 )
                                     | ( static_cast<uint16_t>(csd[ 7]       ) <<  2 )
                                     | ( static_cast<uint16_t>(csd[ 8] & 0xC0) >>  6 );
                const uint16_t cmult = ( static_cast<uint16_t>(csd[ 9] & 0x03) <<  1 )
                                     | ( static_cast<uint16_t>(csd[10] & 0x80) >>  7 );
                const uint16_t rdbln = (                       csd[ 5] & 0x0F        );
                ret = static_cast<uint32_t>(csize + 1) << ( (cmult + 2) + rdbln - 9 );
            }
            break;

        // CSD version 2.0 (SD only)
        //  0 = 127:120
        //  1 = 119:112
        //  2 = 111:104
        //  3 = 103: 96
        //  4 =  95: 88
        //  5 =  87: 80
        //  6 =  79: 72
        //  7 =  71: 64 00CCCCCC
        //  8 =  63: 56 CCCCCCCC
        //  9 =  55: 48 CCCCCCCC
        // 10 =  47: 40
        // 11 =  39: 32
        // 12 =  31: 24
        // 13 =  23: 16
        // 14 =  15:  8
        // 15 =   7:  0
        case 1 : {
                const uint32_t csize = ( static_cast<uint32_t>(csd[7] & 0x3F) << 16 )
                                     | ( static_cast<uint32_t>(csd[8]       ) <<  8 )
                                     | ( static_cast<uint32_t>(csd[9]       )       );
                ret = (csize + 1) << 10;
            }
            break;

        // CSD version 3.0 (SD only)
        //  0 = 127:120
        //  1 = 119:112
        //  2 = 111:104
        //  3 = 103: 96
        //  4 =  95: 88
        //  5 =  87: 80
        //  6 =  79: 72 0000CCCC
        //  7 =  71: 64 CCCCCCCC
        //  8 =  63: 56 CCCCCCCC
        //  9 =  55: 48 CCCCCCCC
        // 10 =  47: 40
        // 11 =  39: 32
        // 12 =  31: 24
        // 13 =  23: 16
        // 14 =  15:  8
        // 15 =   7:  0
        case 2 : {
                const uint32_t csize = ( static_cast<uint32_t>(csd[6] & 0x0F) << 24 )
                                     | ( static_cast<uint32_t>(csd[7]       ) << 16 )
                                     | ( static_cast<uint32_t>(csd[8]       ) <<  8 )
                                     | ( static_cast<uint32_t>(csd[9]       )       );
                ret = (csize + 1) << 10;
            }
            break;

        // Unknown CSD version
        default:
            ret = 0;
    }

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::getEraseBlockSize(uint32_t& ret) const
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // SD version 2
    if(_cardType & CT_SD2) {
        // Read status
              uint8_t       status[64];
        const RMCResultCode res = getStatus(status);
        if(res != RMCResultCode::NoError) return res;
        // Get the erase block size
        //  0 = 511:504
        //  1 = 503:496
        //  2 = 495:488
        //  3 = 487:480
        //  4 = 479:472
        //  5 = 471:464
        //  6 = 463:456
        //  7 = 455:448
        //  8 = 447:440
        //  9 = 439:432
        // 10 = 431:424 AAAA0000
        // 11 = 423:416
        // 12 = 415:408
        // 13 = 407:400
        // 14 = 399:392
        // 15 = 391:384
        // ... the remaining bytes are not used ...
        ret = (1024UL * 8UL) << (status[10] >> 4);
    }

    // SD version 1 and MMC
    else {
        // Read CSD
               uint8_t      csd[16];
        const RMCResultCode res = getCSD(csd);
        if(res != RMCResultCode::NoError) return res;
        // SD version 1
        if(_cardType & CT_SD1) {
            // SD CSD all versions
            //  0 = 127:120
            //  1 = 119:112
            //  2 = 111:104
            //  3 = 103: 96
            //  4 =  95: 88
            //  5 =  87: 80
            //  6 =  79: 72
            //  7 =  71: 64
            //  8 =  63: 56
            //  9 =  55: 48
            // 10 =  47: 40 0ESSSSSS
            // 11 =  39: 32 S0000000
            // 12 =  31: 24 000000WW
            // 13 =  23: 16 WW000000
            // 14 =  15:  8
            // 15 =   7:  0
            const bool     bm512 = csd[10] & 0x40;
            const uint32_t ssize = ( static_cast<uint32_t>(csd[10] & 0x3F) << 1 )
                                 | ( static_cast<uint32_t>(csd[11] & 0x80) >> 7 );
            const uint32_t wsize = ( static_cast<uint32_t>(csd[12] & 0x03) << 2 )
                                 | ( static_cast<uint32_t>(csd[13] & 0xC0) >> 6 );
            ret = bm512 ? 512 : ( (ssize + 1) * eCxx::pow(2, wsize) );
        }

        // MMC
        else {
            // MMC CSD
            //  0 = 127:120
            //  1 = 119:112
            //  2 = 111:104
            //  3 = 103: 96
            //  4 =  95: 88
            //  5 =  87: 80
            //  6 =  79: 72
            //  7 =  71: 64
            //  8 =  63: 56
            //  9 =  55: 48
            // 10 =  47: 40 0SSSSSGG
            // 11 =  39: 32 GGG00000
            // 12 =  31: 24 000000WW
            // 13 =  23: 16 WW000000
            // 14 =  15:  8
            // 15 =   7:  0
            const uint32_t ssize = ( static_cast<uint32_t>(csd[10] & 0x7C) >> 2 );
            const uint32_t esize = ( static_cast<uint32_t>(csd[10] & 0x03) << 3 )
                                 | ( static_cast<uint32_t>(csd[11] & 0xE0) >> 5 );
            const uint32_t wsize = ( static_cast<uint32_t>(csd[12] & 0x03) << 2 )
                                 | ( static_cast<uint32_t>(csd[13] & 0xC0) >> 6 );
            ret = (ssize + 1) * (esize + 1) * eCxx::pow(2, wsize);
        }
    }

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::getCardType(uint8_t& ret) const
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // Copy the card type
    ret = _cardType;

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::getCSD(uint8_t (&ret)[16]) const
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // Read CSD
    const uint8_t r1 = selectCardAndSendCmd(CMD9, 0);
    if(r1 != 0) return RMCResultCode::ReadCSDFailed;

    recvOneDataBlockMO4(ret, 16);

    // Deselect the card
    deselectCard();

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::getCID(uint8_t (&ret)[16]) const
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // Read CID
    const uint8_t r1 = selectCardAndSendCmd(CMD10, 0);
    if(r1 != 0) return RMCResultCode::ReadCIDFailed;

    recvOneDataBlockMO4(ret, 16);

    // Deselect the card
    deselectCard();

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::getOCR(uint8_t (&ret)[4]) const
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // Read CID
    const uint8_t r1 = selectCardAndSendCmd(CMD58, 0);
    if(r1 != 0) return RMCResultCode::ReadOCRFailed;

    recvOneDataBlockMO4(ret, 4);

    // Deselect the card
    deselectCard();

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::getStatus(uint8_t (&ret)[64]) const
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // MMC does not support this function
    if(_cardType & RawMemoryCard::CT_MMC) return RMCResultCode::InvalidOperation;

    // Read status
    const uint8_t r1 = selectCardAndSendCmd(ACMD13, 0);
    if(r1 != 0) return RMCResultCode::ReadSDStatusFailed;

    recvOneDataBlockMO4(ret, 64);

    // Deselect the card
    deselectCard();

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::readOneBlock(uint32_t sector, uint8_t* buff, uint16_t buffSize) const
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // Check the parameters
    if(!buff || !buffSize) return RMCResultCode::InvalidParameter;

    // Convert to byte address as needed
    if( !(_cardType & RawMemoryCard::CT_BLOCK) ) sector *= RawMemoryCard::SectorSize;

    // Read one block
    const uint8_t r1 = selectCardAndSendCmd(CMD17, sector);
    if(r1 != 0) return RMCResultCode::ReadSBlockFailed;

    if( !recvOneDataBlockVar(buff, buffSize) ) return RMCResultCode::ReadSBlockFailed;

    // Deselect the card
    deselectCard();

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::writeOneBlock(uint32_t sector, const uint8_t* buff, uint16_t buffSize)
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // Check the parameters
    if(!buff || !buffSize) return RMCResultCode::InvalidParameter;

    // Convert to byte address as needed
    if( !(_cardType & RawMemoryCard::CT_BLOCK) ) sector *= RawMemoryCard::SectorSize;

    // Write one block
    const uint8_t r1 = selectCardAndSendCmd(CMD24, sector);
    if(r1 != 0) return RMCResultCode::WriteSBlockFailed;

    if( !sendOneDataBlockVar(buff, buffSize, CMD17_CMD18_CMD24_DATA_TOKEN) ) return RMCResultCode::WriteSBlockFailed;

    // Deselect the card
    deselectCard();

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::streamingReadBegin(uint32_t sector, uint8_t blockCount) const
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // Error if a streaming operation is currently in progress
    if(_sBlockTotal) return RMCResultCode::InvalidOperation;

    // Check the parameter
    if(!blockCount) return RMCResultCode::InvalidParameter;

    // Convert to byte address as needed
    if( !(_cardType & RawMemoryCard::CT_BLOCK) ) sector *= RawMemoryCard::SectorSize;

    // Read one block
    if(blockCount == 1) {
        const uint8_t r1 = selectCardAndSendCmd(CMD17, sector);
        if(r1 != 0) return RMCResultCode::ReadSBlockFailed;
    }

    // Read multiple blocks
    else {
        // MMC only
        if(_cardType & RawMemoryCard::CT_MMC) {
            const uint8_t r1mmc = selectCardAndSendCmd(CMD23, blockCount);
            if(r1mmc != 0) return RMCResultCode::ReadMBlockFailed;
        }
        // All card types
        const uint8_t r1 = selectCardAndSendCmd(CMD18, sector);
        if(r1 != 0) return RMCResultCode::ReadMBlockFailed;
    }

    // Wait until the card is ready to send the data block
    if( !waitRecvReady() ) return RMCResultCode::ReadMBlockFailed;

    // Initialize variables
    _sBlockTotal   = blockCount;
    _sBlockCounter = 0;
    _sByteCounter  = 0;

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::streamingReadEnd() const
{
    // Error if a streaming operation is not currently in progress
    if(!_sBlockTotal) return RMCResultCode::InvalidOperation;

    // Stop transmission
    uint8_t r1 = 0;
    if( (_sBlockCounter != _sBlockTotal) || !(_cardType & RawMemoryCard::CT_MMC) ) {
        r1 = selectCardAndSendCmd(CMD12, 0);
    }

    // Deselect the card
    deselectCard();

    // Reset variables
    _sBlockTotal   = 0;
    _sBlockCounter = 0;
    _sByteCounter  = 0;

    // Done
    return (r1 == 0) ? RMCResultCode::NoError : RMCResultCode::StopMBlockFailed;
}


uint16_t RawMemoryCard::streamingReadGet(uint8_t* buff, uint16_t buffSize) const
{
    // For convenience
    auto& spi = Application::spi();

    // Simply return zero if a streaming operation is not currently in progress
    if(!_sBlockTotal) return 0;

    // Check the parameter
    if(buffSize > RawMemoryCard::SectorSize) return 0xFFFF;
    if(!buffSize) return 0;

    // Receive the data block into buffer
    const uint16_t len = eCxx::min<uint16_t>(buffSize, RawMemoryCard::SectorSize - _sByteCounter);

    if(buff) {
        // Read bytes into the buffer
        for(uint16_t i = 0; i < len; ++i) *buff++ = spi.ntmReadXFF();
    }
    else {
        // Discard bytes
        for(uint16_t i = 0; i < len; ++i) spi.ntmReadXFF();
    }

    _sByteCounter += len;

    // Check if the total number of bytes read has reached the sector size
    if(_sByteCounter == RawMemoryCard::SectorSize) {
        // Discard the CRC
        spi.ntmReadXFF();
        spi.ntmReadXFF();
        // Update variables
        ++_sBlockCounter;
        _sByteCounter = 0;
        // If there are more block(s) to read, wait until the card is ready to send the next data block
        if(_sBlockCounter < _sBlockTotal) {
            if( !waitRecvReady() ) {
                // End the process and return zero on error
                streamingReadEnd();
                return 0;
            }
        }
    }

    // If there is no more block to read, end the process
    if(_sBlockCounter == _sBlockTotal) {
        // Return zero on error
        if(streamingReadEnd() != RMCResultCode::NoError) return 0;
    }

    // Repeat the read as needed
    if(len < buffSize) {
        // Read the next block to fill the buffer
        const uint16_t next = streamingReadGet(buff, buffSize - len);
        // Return zero on error
        if(!next) return 0;
        // Return the adjusted the number of bytes read
        return len + next;
    }

    // Return the original number of bytes read
    return len;
}


RawMemoryCard::RMCResultCode RawMemoryCard::streamingWriteBegin(uint32_t sector, uint8_t blockCount)
{
    // Error if it has not been initialized
    if(!_cardType) return RMCResultCode::NotInitialized;

    // Error if a streaming operation is currently in progress
    if(_sBlockTotal) return RMCResultCode::InvalidOperation;

    // Check the parameter
    if(!blockCount) return RMCResultCode::InvalidParameter;

    // Convert to byte address as needed
    if( !(_cardType & RawMemoryCard::CT_BLOCK) ) sector *= RawMemoryCard::SectorSize;

    // Write one block
    if(blockCount == 1) {
        const uint8_t r1 = selectCardAndSendCmd(CMD24, sector);
        if(r1 != 0) return RMCResultCode::WriteSBlockFailed;
    }

    // Write multiple blocks
    else {
        // MMC only
        if(_cardType & RawMemoryCard::CT_MMC) {
            const uint8_t r1mmc = selectCardAndSendCmd(CMD23, blockCount);
            if(r1mmc != 0) return RMCResultCode::WriteMBlockFailed;
        }
        // SD only
        else {
            const uint8_t r1sd = selectCardAndSendCmd(ACMD23, blockCount);
            if(r1sd != 0) return RMCResultCode::WriteMBlockFailed;
        }
        // All card types
        const uint8_t r1 = selectCardAndSendCmd(CMD25, sector);
        if(r1 != 0) return RMCResultCode::WriteMBlockFailed;
    }

    // Initialize variables
    _sBlockTotal   = blockCount;
    _sBlockCounter = 0;
    _sByteCounter  = 0;

    // Done
    return RMCResultCode::NoError;
}


RawMemoryCard::RMCResultCode RawMemoryCard::streamingWriteEnd()
{
    // For convenience
    auto& spi = Application::spi();

    // Error if a streaming operation is not currently in progress
    if(!_sBlockTotal) return RMCResultCode::InvalidOperation;

    // Error flag
    bool error = false;

    // Fill with zeroes as needed
    if(_sBlockCounter != _sBlockTotal) {
        // Check if the current block has been fully written
        if(_sByteCounter && _sByteCounter < RawMemoryCard::SectorSize) {
            // Write zeroes
            const uint16_t len = RawMemoryCard::SectorSize - _sByteCounter;
            for(uint16_t i = 0; i < len; ++i) spi.ntmWrite(0);
            // Send dummy CRC
            spi.ntmWrite(0xFF);
            spi.ntmWrite(0xFF);
            // Update variables
            ++_sBlockCounter;
            _sByteCounter = 0;
            // Check if the operation was accepted
            if( (spi.ntmReadXFF() & DATA_WRITE_MASK) != DATA_WRITE_ACCEPTED ) {
                // Set flag on error
                error = true;
            }
        }
        // Check if the remaining block(s) has/have been fully written
        if(!error && _sBlockCounter < _sBlockTotal) {
            for(uint16_t i = _sBlockCounter; i < _sBlockTotal; ++i) {
                // Send one block of zeroes and check for error
                if( !sendOneDataBlockVar(nullptr, 0, (_sBlockTotal > 1) ? CMD25_DATA_TOKEN : CMD17_CMD18_CMD24_DATA_TOKEN ) ) {
                    // Set flag on error
                    error = true;
                    break;
                }
            }
        }
    }

    // Wait until the card is ready
    if(waitReady() != 0xFF) {
        error = true;
    }
    else {
        // Send stop token as needed
        if( (_sBlockTotal > 1) && !(_cardType & RawMemoryCard::CT_MMC) ) spi.ntmWrite(CMD25_STOP_TOKEN);
    }

    // Wait until the card is no longer busy
    waitReady();

    // Deselect the card
    deselectCard();

    // Reset variables
    _sBlockTotal   = 0;
    _sBlockCounter = 0;
    _sByteCounter  = 0;

    // Done
    return error ? RMCResultCode::StopMBlockFailed : RMCResultCode::NoError;
}


uint16_t RawMemoryCard::streamingWritePut(const uint8_t* buff, uint16_t buffSize)
{
    // For convenience
    auto& spi = Application::spi();

    // Simply return zero if a streaming operation is not currently in progress
    if(!_sBlockTotal) return 0;

    // Check the parameter
    if(buffSize > RawMemoryCard::SectorSize) return 0xFFFF;
    if(!buffSize) return 0;

    // Special operation is needed if on the beginning of a new block
    if( !_sByteCounter ) {
        // Wait until the card is ready as needed
        if(waitReady() != 0xFF) {
            // End the process and return zero on error
            streamingWriteEnd();
            return 0;
        }
        // Send data token as needed
        spi.ntmWrite( (_sBlockTotal > 1) ? CMD25_DATA_TOKEN : CMD17_CMD18_CMD24_DATA_TOKEN );
    }

    // Send the buffer data
    const uint16_t len = eCxx::min<uint16_t>(buffSize, RawMemoryCard::SectorSize - _sByteCounter);

    if(buff) {
        // Write bytes from the buffer
        for(uint16_t i = 0; i < len; ++i) spi.ntmWrite(*buff++);
    }
    else {
        // Write zeroes
        for(uint16_t i = 0; i < len; ++i) spi.ntmWrite(0);
    }

    _sByteCounter += len;

    // Check if the total number of bytes written has reached the sector size
    if(_sByteCounter == RawMemoryCard::SectorSize) {
        // Send dummy CRC
        spi.ntmWrite(0xFF);
        spi.ntmWrite(0xFF);
        // Update variables
        ++_sBlockCounter;
        _sByteCounter = 0;
        // Check if the operation was accepted
        if( (spi.ntmReadXFF() & DATA_WRITE_MASK) != DATA_WRITE_ACCEPTED ) {
            // End the process and return zero on error
            streamingWriteEnd();
            return 0;
        }
    }

    // If there is no more block to write, end the process
    if(_sBlockCounter == _sBlockTotal) {
        // Return zero on error
        if(streamingWriteEnd() != RMCResultCode::NoError) return 0;
    }

    // Repeat the write as needed
    if(len < buffSize) {
        // Send the buffer to write the block
        const uint16_t next = streamingWritePut(buff, buffSize - len);
        // Return zero on error
        if(!next) return 0;
        // Return the adjusted the number of bytes written
        return len + next;
    }

    // Return the original number of bytes written
    return len;
}


PGMCharPointer RawMemoryCard::resultCodeStr(RMCResultCode rc)
{
    static const char PROGMEM rcNotInitialized    [] = "Not Initialized";
    static const char PROGMEM rcResetFailed       [] = "Reset Failed";
    static const char PROGMEM rcReadCSDFailed     [] = "Read CSD Failed";
    static const char PROGMEM rcReadCIDFailed     [] = "Read CID Failed";
    static const char PROGMEM rcReadOCRFailed     [] = "Read OCR Failed";
    static const char PROGMEM rcReadSDStatusFailed[] = "Read SD Status Failed";
    static const char PROGMEM rcReadSBlockFailed  [] = "Read Single Block Failed";
    static const char PROGMEM rcReadMBlockFailed  [] = "Read Multiple Block Failed";
    static const char PROGMEM rcWriteSBlockFailed [] = "Write Single Block Failed";
    static const char PROGMEM rcWriteMBlockFailed [] = "Write Multiple Block Failed";
    static const char PROGMEM rcStopMBlockFailed  [] = "Stop Multiple Block Read/Write Failed";
    static const char PROGMEM rcInvalidOperation  [] = "Invalid Operation";
    static const char PROGMEM rcInvalidParameter  [] = "Invalid Parameter";
    static const char PROGMEM rcUnsupportedCard   [] = "Unsupported Card";

    switch(rc) {
        case RMCResultCode::NoError            : return eCxx::PGM_PTR(Application::PGMStrNoError); break;
        case RMCResultCode::NotInitialized     : return eCxx::PGM_PTR(rcNotInitialized          ); break;
        case RMCResultCode::ResetFailed        : return eCxx::PGM_PTR(rcResetFailed             ); break;
        case RMCResultCode::ReadCSDFailed      : return eCxx::PGM_PTR(rcReadCSDFailed           ); break;
        case RMCResultCode::ReadCIDFailed      : return eCxx::PGM_PTR(rcReadCIDFailed           ); break;
        case RMCResultCode::ReadOCRFailed      : return eCxx::PGM_PTR(rcReadOCRFailed           ); break;
        case RMCResultCode::ReadSDStatusFailed : return eCxx::PGM_PTR(rcReadSDStatusFailed      ); break;
        case RMCResultCode::ReadSBlockFailed   : return eCxx::PGM_PTR(rcReadSBlockFailed        ); break;
        case RMCResultCode::ReadMBlockFailed   : return eCxx::PGM_PTR(rcReadMBlockFailed        ); break;
        case RMCResultCode::WriteSBlockFailed  : return eCxx::PGM_PTR(rcWriteSBlockFailed       ); break;
        case RMCResultCode::WriteMBlockFailed  : return eCxx::PGM_PTR(rcWriteMBlockFailed       ); break;
        case RMCResultCode::StopMBlockFailed   : return eCxx::PGM_PTR(rcStopMBlockFailed        ); break;
        case RMCResultCode::InvalidOperation   : return eCxx::PGM_PTR(rcInvalidOperation        ); break;
        case RMCResultCode::InvalidParameter   : return eCxx::PGM_PTR(rcInvalidParameter        ); break;
        case RMCResultCode::UnsupportedCard    : return eCxx::PGM_PTR(rcUnsupportedCard         ); break;
        default                                : return eCxx::PGM_PTR(Application::PGMStrUnknown); break;
    }
}


PGMCharPointer RawMemoryCard::cardTypeStr(uint8_t ct)
{
    static const char PROGMEM rcMMC     [] = "MMC";
    static const char PROGMEM rcSD1     [] = "SD1";
    static const char PROGMEM rcSD2     [] = "SD2 (Byte Addressed)";
    static const char PROGMEM rcSD2Block[] = "SD2 (Block Addressed)";

    switch(ct) {
        case CT_MMC            : return eCxx::PGM_PTR(rcMMC                     ); break;
        case CT_SD1            : return eCxx::PGM_PTR(rcSD1                     ); break;
        case CT_SD2            : return eCxx::PGM_PTR(rcSD2                     ); break;
        case CT_SD2 | CT_BLOCK : return eCxx::PGM_PTR(rcSD2Block                ); break;
        default                : return eCxx::PGM_PTR(Application::PGMStrUnknown); break;
    }
}


#else


//
// A simple raw-mode memory card reader and writer
// (dummy version for testing other parts of the library in the host PC)
//
RawMemoryCard::RawMemoryCard()
: _cardType     (0)
, _sBlockTotal  (0)
, _sBlockCounter(0)
, _sByteCounter (0)
{}


RawMemoryCard::~RawMemoryCard()
{}


RawMemoryCard::RMCResultCode RawMemoryCard::begin()
{ return RMCResultCode::InvalidOperation; }


void RawMemoryCard::end()
{}


RawMemoryCard::RMCResultCode RawMemoryCard::getSectorSize(uint16_t&) const
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::getSectorCount(uint32_t&) const
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::getCardType(uint8_t&) const
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::getCSD(uint8_t (&)[16]) const
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::getCID(uint8_t (&)[16]) const
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::getOCR(uint8_t (&)[4]) const
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::getStatus(uint8_t (&)[64]) const
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::readOneBlock(uint32_t, uint8_t*, uint16_t) const
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::writeOneBlock(uint32_t, const uint8_t*, uint16_t)
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::streamingReadBegin(uint32_t, uint8_t) const
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::streamingReadEnd() const
{ return RMCResultCode::InvalidOperation; }


uint16_t RawMemoryCard::streamingReadGet(uint8_t*, uint16_t) const
{ return 0; }


RawMemoryCard::RMCResultCode RawMemoryCard::streamingWriteBegin(uint32_t, uint8_t)
{ return RMCResultCode::InvalidOperation; }


RawMemoryCard::RMCResultCode RawMemoryCard::streamingWriteEnd()
{ return RMCResultCode::InvalidOperation; }


uint16_t RawMemoryCard::streamingWritePut(const uint8_t*, uint16_t)
{ return 0; }


PGMCharPointer RawMemoryCard::resultCodeStr(RMCResultCode)
{ return PGM_STR(""); }


PGMCharPointer RawMemoryCard::cardTypeStr(uint8_t)
{ return PGM_STR(""); }


#endif


} // namespace eCxx
