/// MMCv3/SDv1/SDv2 (in SPI mode) control module
/// Copyright (C) 2014, ChaN, all right reserved.
/// This software is a free software and there is NO WARRANTY.
/// No restriction on use. You can use, modify and redistribute it for
/// personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY.
/// Redistributions of source code must retain the above copyright notice.
#include <string.h> /// memset()
#include "cwgsys.h"
#include "pic24.h"
#include "iodisk.h"
#include "timers.h"
#include "iodigital.h"
#include "iospi.h"
/// Definitions for MMC/SDC command 
#define CMD0   (0)			/// GO_IDLE_STATE 
#define CMD1   (1)			/// SEND_OP_COND 
#define ACMD41 (41|0x80)	/// SEND_OP_COND (SDC) 
#define CMD8   (8)			/// SEND_IF_COND 
#define CMD9   (9)			/// SEND_CSD 
#define CMD10  (10)			/// SEND_CID 
#define CMD12  (12)			/// STOP_TRANSMISSION 
#define ACMD13 (13|0x80)	/// SD_STATUS (SDC) 
#define CMD16  (16)			/// SET_BLOCKLEN 
#define CMD17  (17)			/// READ_SINGLE_BLOCK 
#define CMD18  (18)			/// READ_MULTIPLE_BLOCK 
#define CMD23  (23)			/// SET_BLOCK_COUNT 
#define ACMD23 (23|0x80)	/// SET_WR_BLK_ERASE_COUNT (SDC) 
#define CMD24  (24)			/// WRITE_BLOCK 
#define CMD25  (25)			/// WRITE_MULTIPLE_BLOCK 
#define CMD41  (41)			/// SEND_OP_COND (ACMD) 
#define CMD55  (55)			/// APP_CMD 
#define CMD58  (58)			/// READ_OCR 
/// LOCAL DATA                                                            
volatile DSTATUS Stat = STA_NOINIT; /// Disk status 
UINT CardType;
Timer Tmr1;
/// Receive a data packet from MMC                                        *
/// Output:  1:OK, 0:Failed
/// Input:   Data buffer to store received data, Byte count (must be multiple of 4)
static int rcvr_datablock(BYTE *buff, UINT btr) {
    BYTE token;

    tkTmrSet(&Tmr1, 100);
    tkTmrStart(&Tmr1);

    do { // Wait for data packet in timeout of 100ms 
        token = spiExchange(0xFF);
    } while ((token == 0xFF) && tkTmrUpdate(&Tmr1));
    if (token != 0xFE) return 0; // If not valid data token, retutn with error 

    spiRcvrMulti(buff, btr); // Receive the data block into buffer 
    spiExchange(0xFF); // Discard CRC 
    spiExchange(0xFF);

    return 1; // Return with success 
}


#if _USE_WRITE
/// Send a data packet to MMC                                             *
/// Output:  1:OK, 0:Failed
/// Input:   512 byte data block to be transmitted, Data token
static int xmit_datablock(const BYTE *buff, BYTE token) {
    BYTE resp;

    if (!spiWaitReady()) return 0;

    spiExchange(token); // Xmit a token 
    if (token != 0xFD) { // Not StopTran token 
        spiXmitMulti(buff, 512); // Xmit the data block to the MMC 
        spiExchange(0xFF); // CRC (Dummy) 
        spiExchange(0xFF);
        resp = spiExchange(0xFF); // Receive a data response 
        if ((resp & 0x1F) != 0x05) // If not accepted, return with error 
            return 0;
    }

    return 1;
}

#endif
/// Send a command packet to MMC
/// Output:  the response value
/// Input:   Command byte, Argument
static BYTE send_cmd(BYTE cmd, DWORD arg) {
    BYTE n, res;

    if (cmd & 0x80) { // ACMD<n> is the command sequense of CMD55-CMD<n> 
        cmd &= 0x7F;
        res = send_cmd(CMD55, 0);
        if (res > 1){
            return res;
        }
    }
 
    // Select the card and wait for ready except to stop multiple block read 
    if (cmd != CMD12) {
        spiDeselectSD();
        if (!spiSelectSD()){
            return 0xFF;
        }
    }

    // Send command packet 
    spiExchange(0x40 | cmd); // Start + Command index 
    spiExchange((BYTE) (arg >> 24)); // Argument[31..24] 
    spiExchange((BYTE) (arg >> 16)); // Argument[23..16] 
    spiExchange((BYTE) (arg >> 8)); // Argument[15..8] 
    spiExchange((BYTE) arg); // Argument[7..0] 
    n = 0x01; // Dummy CRC + Stop 
    if (cmd == CMD0) n = 0x95; // Valid CRC for CMD0(0) + Stop 
    if (cmd == CMD8) n = 0x87; // Valid CRC for CMD8(0x1AA) + Stop 
    spiExchange(n);

    // Receive command response 
    if (cmd == CMD12){
        spiExchange(0xFF); // Skip a stuff byte on stop to read 
    }
    
    n = 10; // Wait for a valid response in timeout of 10 attempts 
    do{
        res = spiExchange(0xFF); 
    }while ((res & 0x80) && --n);

    return res; // Return with the response value 
}


/// Public Functions
/// Get Disk Status                                                       *
/// Output: status
/// Input: Physical drive nmuber (0)
DSTATUS disk_status(BYTE pdrv) {
    if (pdrv != 0) return STA_NOINIT; // Supports only single drive 

    return Stat;
}


/// Initialize Disk Drive                                                 *
/// f_mount() should be called instead, it uses this function
/// Output: status
/// Input:  Physical drive nmuber (0)
DSTATUS disk_initialize(BYTE pdrv) {
    BYTE n, cmd, ty, ocr[4];

    if (pdrv != 0) return STA_NOINIT; // Supports only single drive 
    if (Stat & STA_NODISK) return Stat; // No card in the socket 

    spiSlow();  //leave CS High
    for (n = 10; n; n--) spiExchange(0xFF); // 80 dummy clocks of high 
    ty = 0;
    if (send_cmd(CMD0, 0) == 1) { // Enter Idle state 
        tkTmrSet(&Tmr1, 1000); // Initialization timeout of 1000 msec 
        tkTmrStart(&Tmr1);
        if (send_cmd(CMD8, 0x1AA) == 1) { // SDv2? 
            for (n = 0; n < 4; n++) ocr[n] = spiExchange(0xFF); // Get trailing return value of R7 resp 
            if (ocr[2] == 0x01 && ocr[3] == 0xAA) { // The card can work at vdd range of 2.7-3.6V 
                while (tkTmrUpdate(&Tmr1) && send_cmd(ACMD41, 0x40000000)); // Wait for leaving idle state (ACMD41 with HCS bit) 
                if (tkTmrUpdate(&Tmr1) && send_cmd(CMD58, 0) == 0) { // Check CCS bit in the OCR 
                    for (n = 0; n < 4; n++) ocr[n] = spiExchange(0xFF);
                    ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2; // SDv2 
                }
            }
        } else { // SDv1 or MMCv3 
            if (send_cmd(ACMD41, 0) <= 1) {
                ty = CT_SD1;
                cmd = ACMD41; // SDv1 
            } else {
                ty = CT_MMC;
                cmd = CMD1; // MMCv3 
            }
            while (tkTmrUpdate(&Tmr1) && send_cmd(cmd, 0)); // Wait for leaving idle state 
            if (0 == tkTmrUpdate(&Tmr1) || send_cmd(CMD16, 512) != 0) // Set read/write block length to 512 
                ty = 0;
        }
    }
    CardType = ty;
    spiDeselectSD();

    if (ty) { // Function succeded 
        Stat &= ~STA_NOINIT; // Clear STA_NOINIT 
        //        FCLK_FAST();
        spiFast();
    } else { // Function failed 
        //        power_off(); /* Deinitialize interface */
#ifdef WITH_SPI
//        CloseSPI1();
#endif
    }

    return Stat;
}

#include "iodigital.h"
/// Read Sector(s)                                                        *
/// Output:  result
/// Input:   Physical drive number (0), Pointer to a data buffer for read data,
/// Start sector number (LBA), Sector count (1..128)
DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count) {

    if (pdrv || !count) return RES_PARERR;

    if (Stat & STA_NOINIT) return RES_NOTRDY;

    
    if (!(CardType & CT_BLOCK)) sector *= 512; // Convert to byte address if needed 
      
    if (count == 1) { // Single block read 
         if ((send_cmd(CMD17, sector) == 0) && // READ_SINGLE_BLOCK 
               rcvr_datablock(buff, 512)){
                   count = 0;
        }

    } else { // Multiple block read 
        if (send_cmd(CMD18, sector) == 0) { // READ_MULTIPLE_BLOCK 
            do {
                if (!rcvr_datablock(buff, 512)) break;
                buff += 512;
            } while (--count);
            send_cmd(CMD12, 0); // STOP_TRANSMISSION 
        }
    }
    spiDeselectSD();

    return (count ? RES_ERROR : RES_OK);
}


#if _USE_WRITE
/// Write Sector(s)                                                       *
/// Output: result
/// Input:  Physical drive nmuber (0), Pointer to the data to be written
/// Start sector number (LBA), Sector count (1..128)
DRESULT disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) {
    if (pdrv || !count) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;
    if (Stat & STA_PROTECT) return RES_WRPRT;

    if (!(CardType & CT_BLOCK)) sector *= 512; // Convert to byte address if needed 

    if (count == 1) { // Single block write 
        if ((send_cmd(CMD24, sector) == 0) // WRITE_BLOCK 
                && xmit_datablock(buff, 0xFE))
            count = 0;
    } else { // Multiple block write 
        if (CardType & CT_SDC) send_cmd(ACMD23, count);
        if (send_cmd(CMD25, sector) == 0) { // WRITE_MULTIPLE_BLOCK 
            do {
                if (!xmit_datablock(buff, 0xFC)) break;
                buff += 512;
            } while (--count);
            if (!xmit_datablock(0, 0xFD)) // STOP_TRAN token 
                count = 1;
        }
    }
    spiDeselectSD();

    return count ? RES_ERROR : RES_OK;
}


#endif
/// Miscellaneous Functions                                               
#if _USE_IOCTL
/// Disk IO control
/// Output: result
/// Input:  Physical drive nmuber (0), Control code,
/// Buffer to send/receive data block
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) {
    DRESULT res;
    BYTE n, csd[16], *ptr = buff;
    DWORD csz;

    if (pdrv) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;

    res = RES_ERROR;
    switch (cmd) {
        case CTRL_SYNC: // Flush write-back cache, Wait for end of internal process 
            if (spiSelectSD()) res = RES_OK;
            break;

        case GET_SECTOR_COUNT: // Get number of sectors on the disk (WORD) 
            if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) {
                if ((csd[0] >> 6) == 1) { // SDv2? 
                    csz = csd[9] + ((WORD) csd[8] << 8) + ((DWORD) (csd[7] & 63) << 16) + 1;
                    *(DWORD*) buff = csz << 10;
                } else { // SDv1 or MMCv3 
                    n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
                    csz = (csd[8] >> 6) + ((WORD) csd[7] << 2) + ((WORD) (csd[6] & 3) << 10) + 1;
                    *(DWORD*) buff = csz << (n - 9);
                }
                res = RES_OK;
            }
            break;

        case GET_BLOCK_SIZE: // Get erase block size in unit of sectors (DWORD) 
            if (CardType & CT_SD2) { // SDv2? 
                if (send_cmd(ACMD13, 0) == 0) { // Read SD status 
                    spiExchange(0xFF);
                    if (rcvr_datablock(csd, 16)) { // Read partial block 
                        for (n = 64 - 16; n; n--) spiExchange(0xFF); // Purge trailing data 
                        *(DWORD*) buff = 16UL << (csd[10] >> 4);
                        res = RES_OK;
                    }
                }
            } else { // SDv1 or MMCv3 
                if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) { // Read CSD 
                    if (CardType & CT_SD1) { // SDv1 
                        *(DWORD*) buff = (((csd[10] & 63) << 1) + ((WORD) (csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
                    } else { // MMCv3 
                        *(DWORD*) buff = ((WORD) ((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
                    }
                    res = RES_OK;
                }
            }
            break;

        case MMC_GET_TYPE: // Get card type flags (1 byte) 
            *ptr = CardType;
            res = RES_OK;
            break;

        case MMC_GET_CSD: // Receive CSD as a data block (16 bytes) 
            if ((send_cmd(CMD9, 0) == 0) // READ_CSD 
                    && rcvr_datablock(buff, 16))
                res = RES_OK;
            break;

        case MMC_GET_CID: // Receive CID as a data block (16 bytes) 
            if ((send_cmd(CMD10, 0) == 0) // READ_CID 
                    && rcvr_datablock(buff, 16))
                res = RES_OK;
            break;

        case MMC_GET_OCR: // Receive OCR as an R3 resp (4 bytes) 
            if (send_cmd(CMD58, 0) == 0) { // READ_OCR 
                for (n = 0; n < 4; n++)
                    *((BYTE*) buff + n) = spiExchange(0xFF);
                res = RES_OK;
            }
            break;

        case MMC_GET_SDSTAT: // Receive SD statsu as a data block (64 bytes) 
            if ((CardType & CT_SD2) && send_cmd(ACMD13, 0) == 0) { // SD_STATUS 
                spiExchange(0xFF);
                if (rcvr_datablock(buff, 64))
                    res = RES_OK;
            }
            break;

        case CTRL_POWER_OFF: // Power off 
//             power_off();
#ifdef WITH_SPI
//            CloseSPI1();
#endif
            Stat |= STA_NOINIT;
            res = RES_OK;
            break;

        default:
            res = RES_PARERR;
    }

    spiDeselectSD();

    return res;
}


#endif
/// Device Timer Driven Procedure                                         
/// This function must be called by timer interrupt in period of 1ms      
void disk_timerproc(void) {
    BYTE s;

    // Update socket status 

    s = Stat;

    if (0) s |= STA_PROTECT;//no write protection
    else s &= ~STA_PROTECT;

    if (doIsCardThere()) {
        s &= ~STA_NODISK;
    } else {
        s |= (STA_NODISK | STA_NOINIT);
    }
    Stat = s;
}

