/*-----------------------------------------------------------------------*/
/* MMC/SDC (in SPI mode) control module  (C)ChaN, 2006                   */
/*-----------------------------------------------------------------------*/
/* Only rcvr_spi(), xmit_spi(), disk_timerproc(), disk_initialize () and */
/* some macros are platform dependent.                                   */
/*-----------------------------------------------------------------------*/


#include <avr/io.h>
#include "diskio.h"


/* Definitions for MMC/SDC command */
#define CMD0    (0x40+0)        /* GO_IDLE_STATE */
#define CMD1    (0x40+1)        /* SEND_OP_COND */
#define CMD8    (0x40+8)        /* SEND_IF_COND */
#define CMD9    (0x40+9)        /* SEND_CSD */
#define CMD10   (0x40+10)       /* SEND_CID */
#define CMD12   (0x40+12)       /* STOP_TRANSMISSION */
#define CMD16   (0x40+16)       /* SET_BLOCKLEN */
#define CMD17   (0x40+17)       /* READ_SINGLE_BLOCK */
#define CMD18   (0x40+18)       /* READ_MULTIPLE_BLOCK */
#define CMD23   (0x40+23)       /* SET_BLOCK_COUNT */
#define CMD24   (0x40+24)       /* WRITE_BLOCK */
#define CMD25   (0x40+25)       /* WRITE_MULTIPLE_BLOCK */
#define CMD41   (0x40+41)       /* SEND_OP_COND (ACMD) */
#define CMD55   (0x40+55)       /* APP_CMD */
#define CMD58   (0x40+58)       /* READ_OCR */


/* Control signals (Platform dependent) */
#define SELECT()        PORTB &= ~_BV(PB2)      /* MMC CS = L */
#define DESELECT()      PORTB |= _BV(PB2)       /* MMC CS = H */

#define SOCKPORT        PINB                    /* Socket contact port */
#define SOCKINS         0x01                    /* Card detect switch (PB0) */



/*--------------------------------------------------------------------------

   Module Private Functions

---------------------------------------------------------------------------*/

static volatile
DSTATUS Stat = STA_NOINIT;      /* Disk status */

static volatile
BYTE Timer1, Timer2;            /* 100Hz decrement timer */

static
BYTE CardType;                  /* b0:MMC, b1:SDC, b2:Block addressing */



/*-----------------------------------------------------------------------*/
/* Transmit a byte to MMC via SPI  (Platform dependent)                  */
/*-----------------------------------------------------------------------*/

#define xmit_spi(dat)   SPDR=(dat); loop_until_bit_is_set(SPSR,SPIF)



/*-----------------------------------------------------------------------*/
/* Receive a byte from MMC via SPI  (Platform dependent)                 */
/*-----------------------------------------------------------------------*/

static
BYTE rcvr_spi (void)
{
        SPDR = 0xFF;
        loop_until_bit_is_set(SPSR, SPIF);
        return SPDR;
}

/* Alternative macro to receive data fast */
#define rcvr_spi_m(dst) SPDR=0xFF; loop_until_bit_is_set(SPSR,SPIF); *(dst)=SPDR



/*-----------------------------------------------------------------------*/
/* Wait for card ready                                                   */
/*-----------------------------------------------------------------------*/

static
BYTE wait_ready (void)
{
        BYTE res;


        Timer2 = 50;    /* Wait for ready in timeout of 500ms */
        rcvr_spi();
        do
                res = rcvr_spi();
        while ((res != 0xFF) && Timer2);

        return res;
}



/*-----------------------------------------------------------------------*/
/* Receive a data packet from MMC                                        */
/*-----------------------------------------------------------------------*/

static
BOOL rcvr_datablock (
        BYTE *buff,                     /* Data buffer to store received data */
        UINT btr                        /* Byte count (must be even number) */
)
{
        BYTE token;


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

        do {                                                    /* Receive the data block into buffer */
                rcvr_spi_m(buff++);
                rcvr_spi_m(buff++);
        } while (btr -= 2);
        rcvr_spi();                                             /* Discard CRC */
        rcvr_spi();

        return TRUE;                                    /* Return with success */
}



/*-----------------------------------------------------------------------*/
/* Send a data packet to MMC                                             */
/*-----------------------------------------------------------------------*/

#if _READONLY == 0
static
BOOL xmit_datablock (
        const BYTE *buff,       /* 512 byte data block to be transmitted */
        BYTE token                      /* Data/Stop token */
)
{
        BYTE resp, wc;


        if (wait_ready() != 0xFF) return FALSE;

        xmit_spi(token);                                        /* Xmit data token */
        if (token != 0xFD) {    /* Is data token */
                wc = 0;
                do {                                                    /* Xmit the 512 byte data block to MMC */
                        xmit_spi(*buff++);
                        xmit_spi(*buff++);
                } while (--wc);
                xmit_spi(0xFF);                                 /* CRC (Dummy) */
                xmit_spi(0xFF);
                resp = rcvr_spi();                              /* Reveive data response */
                if ((resp & 0x1F) != 0x05)              /* If not accepted, return with error */
                        return FALSE;
        }

        return TRUE;
}
#endif /* _READONLY */



/*-----------------------------------------------------------------------*/
/* Send a command packet to MMC                                          */
/*-----------------------------------------------------------------------*/

static
BYTE send_cmd (
        BYTE cmd,               /* Command byte */
        DWORD arg               /* Argument */
)
{
        BYTE n, res;


        if (wait_ready() != 0xFF) return 0xFF;

        /* Send command packet */
        xmit_spi(cmd);                                          /* Command */
        xmit_spi((BYTE)(arg >> 24));            /* Argument[31..24] */
        xmit_spi((BYTE)(arg >> 16));            /* Argument[23..16] */
        xmit_spi((BYTE)(arg >> 8));                     /* Argument[15..8] */
        xmit_spi((BYTE)arg);                            /* Argument[7..0] */
        n = 0;
        if (cmd == CMD0) n = 0x95;                      /* CRC for CMD0(0) */
        if (cmd == CMD8) n = 0x87;                      /* CRC for CMD8(0x1AA) */
        xmit_spi(n);

        /* Receive command response */
        if (cmd == CMD12) rcvr_spi();           /* Skip a stuff byte when stop reading */
        n = 10;                                                         /* Wait for a valid response in timeout of 10 attempts */
        do
                res = rcvr_spi();
        while ((res & 0x80) && --n);

        return res;                     /* Return with the response value */
}




/*--------------------------------------------------------------------------

   Public Functions

---------------------------------------------------------------------------*/


/*-----------------------------------------------------------------------*/
/* Initialize Disk Drive                                                 */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (
        BYTE drv                /* Physical drive nmuber (0) */
)
{
        BYTE n, ty, ocr[4];


        if (drv) return STA_NOINIT;                     /* Supports only single drive */
        if (Stat & STA_NODISK) return Stat;     /* No card in the socket */

        for (n = 10; n; n--) rcvr_spi();        /* 80 dummy clocks */

        SELECT();                               /* CS = L */
        ty = 0;
        if (send_cmd(CMD0, 0) == 1) {                   /* Enter Idle state */
                Timer1 = 100;                                           /* Initialization timeout of 1000 msec */
                if (send_cmd(CMD8, 0x1AA) == 1) {       /* SDC Ver2+ */
                        for (n = 0; n < 4; n++) ocr[n] = rcvr_spi();
                        if (ocr[2] == 0x01 && ocr[3] == 0xAA) { /* The card can work at vdd range of 2.7-3.6V */
                                do {
                                        if (send_cmd(CMD55, 0) <= 1 && send_cmd(CMD41, 1UL << 30) == 0) break;  /* ACMD41 with HCS bit */
                                } while (Timer1);
                                if (Timer1 && send_cmd(CMD58, 0) == 0) {        /* Check CCS bit */
                                        for (n = 0; n < 4; n++) ocr[n] = rcvr_spi();
                                        ty = (ocr[0] & 0x40) ? 6 : 2;
                                }
                        }
                } else {                                                        /* SDC Ver1 or MMC */
                        ty = (send_cmd(CMD55, 0) <= 1 && send_cmd(CMD41, 0) <= 1) ? 2 : 1;      /* SDC : MMC */
                        do {
                                if (ty == 2) {
                                        if (send_cmd(CMD55, 0) <= 1 && send_cmd(CMD41, 0) == 0) break;  /* ACMD41 */
                                } else {
                                        if (send_cmd(CMD1, 0) == 0) break;                                                              /* CMD1 */
                                }
                        } while (Timer1);
                        if (!Timer1 || send_cmd(CMD16, 512) != 0)       /* Select R/W block length */
                                ty = 0;
                }
        }
        CardType = ty;
        DESELECT();                     /* CS = H */
        rcvr_spi();                     /* Idle (Release DO) */

        if (ty) {                       /* Initialization succeded */
                Stat &= ~STA_NOINIT;            /* Clear STA_NOINIT */
        } else {                        /* Initialization failed */
                Stat |=  STA_NOINIT;            /* Set STA_NOINIT */
        }

        return Stat;
}



/*-----------------------------------------------------------------------*/
/* Get Disk Status                                                       */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
        BYTE drv                /* Physical drive nmuber (0) */
)
{
        if (drv) return STA_NOINIT;             /* Supports only single drive */
        return Stat;
}



/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
        BYTE drv,                       /* Physical drive nmuber (0) */
        BYTE *buff,                     /* Pointer to the data buffer to store read data */
        DWORD sector,           /* Start sector number (LBA) */
        BYTE count                      /* Sector count (1..255) */
)
{
        if (drv || !count) return RES_PARERR;
        if (Stat & STA_NOINIT) return RES_NOTRDY;

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

        SELECT();                       /* CS = L */

        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 */
                }
        }

        DESELECT();                     /* CS = H */
        rcvr_spi();                     /* Idle (Release DO) */

        return count ? RES_ERROR : RES_OK;
}



/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if _READONLY == 0
DRESULT disk_write (
        BYTE drv,                       /* Physical drive nmuber (0) */
        const BYTE *buff,       /* Pointer to the data to be written */
        DWORD sector,           /* Start sector number (LBA) */
        BYTE count                      /* Sector count (1..255) */
)
{
        if (drv || !count) return RES_PARERR;
        if (Stat & STA_NOINIT) return RES_NOTRDY;
        if (Stat & STA_PROTECT) return RES_WRPRT;

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

        SELECT();                       /* CS = L */

        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 & 2) {
                        send_cmd(CMD55, 0); send_cmd(CMD23, count);     /* ACMD23 */
                }
                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;
                }
        }

        DESELECT();                     /* CS = H */
        rcvr_spi();                     /* Idle (Release DO) */

        return count ? RES_ERROR : RES_OK;
}
#endif /* _READONLY */



/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
        BYTE drv,               /* Physical drive nmuber (0) */
        BYTE ctrl,              /* Control code */
        void *buff              /* Buffer to send/receive data block */
)
{
        DRESULT res;
        BYTE n, csd[16], *ptr = buff;
        WORD csize;


        if (drv) return RES_PARERR;

        SELECT();               /* CS = L */

        res = RES_ERROR;
        switch (ctrl) {
                case GET_SECTOR_COUNT : /* Get number of sectors on the disk (DWORD) */
                        if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) {
                                if ((csd[0] >> 6) == 1) {       /* SDC ver 2.00 */
                                        csize = csd[9] + ((WORD)csd[8] << 8) + 1;
                                        *(DWORD*)buff = (DWORD)csize << 10;
                                } else {                                        /* MMC or SDC ver 1.XX */
                                        n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
                                        csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
                                        *(DWORD*)buff = (DWORD)csize << (n - 9);
                                }
                                res = RES_OK;
                        }
                        break;

                case GET_SECTOR_SIZE :  /* Get sectors on the disk (WORD) */
                        *(WORD*)buff = 512;
                        res = RES_OK;
                        break;

                case CTRL_SYNC :        /* Make sure that data has been written */
                        if (wait_ready() == 0xFF)
                                res = RES_OK;
                        break;

                case MMC_GET_CSD :      /* Receive CSD as a data block (16 bytes) */
                        if (Stat & STA_NOINIT) return RES_NOTRDY;
                        if ((send_cmd(CMD9, 0) == 0)    /* READ_CSD */
                                && rcvr_datablock(ptr, 16))
                                res = RES_OK;
                        break;

                case MMC_GET_CID :      /* Receive CID as a data block (16 bytes) */
                        if (Stat & STA_NOINIT) return RES_NOTRDY;
                        if ((send_cmd(CMD10, 0) == 0)   /* READ_CID */
                                && rcvr_datablock(ptr, 16))
                                res = RES_OK;
                        break;

                case MMC_GET_OCR :      /* Receive OCR as an R3 resp (4 bytes) */
                        if (Stat & STA_NOINIT) return RES_NOTRDY;
                        if (send_cmd(CMD58, 0) == 0) {  /* READ_OCR */
                                for (n = 0; n < 4; n++)
                                        *ptr++ = rcvr_spi();
                                res = RES_OK;
                        }
                        break;

                default:
                        res = RES_PARERR;
        }

        DESELECT();                     /* CS = H */
        rcvr_spi();                     /* Idle (Release DO) */

        return res;
}



/*---------------------------------------*/
/* Device timer interrupt procedure      */
/* This must be called in period of 10ms */
/* (Platform dependent)                  */

void disk_timerproc (void)
{
        static BYTE pv;
        BYTE n, s;


        n = Timer1;                                             /* 100Hz decrement timer */
        if (n) Timer1 = --n;
        n = Timer2;
        if (n) Timer2 = --n;

        n = pv;
        pv = SOCKPORT & (SOCKINS);              /* Sample socket switch */

        if (n == pv) {                                  /* Have contacts stabled? */
                s = Stat;
                if (pv & SOCKINS)                       /* INS = H (Socket empty) */
                        s |= (STA_NODISK | STA_NOINIT);
                else                                            /* INS = L (Card inserted) */
                        s &= ~STA_NODISK;

                Stat = s;
        }
}