/*
 *  File: sd_io.c
 *  Author: Nelson Lombardo
 *  Year: 2015
 *  e-mail: nelson.lombardo@gmail.com
 *  License at the end of file.
 */

#include "sd_io.h"

#ifdef _M_IX86  // For use over x86
/*****************************************************************************/
/* Private Methods Prototypes - Direct work with PC file                     */
/*****************************************************************************/

/**
 * \brief Get the total numbers of sectors in SD card.
 * \param dev Device descriptor.
 * \return Quantity of sectors. Zero if fail.
 */
DWORD __SD_Sectors (SD_DEV* dev);

/*****************************************************************************/
/* Private Methods - Direct work with PC file                                */
/*****************************************************************************/

DWORD __SD_Sectors (SD_DEV *dev)
{
    if (dev->fp == NULL) return(0); // Fail
    else {
        fseek(dev->fp, 0L, SEEK_END);
        return (((DWORD)(ftell(dev->fp)))/((DWORD)512)-1);
    }
}
#else   // For use with uControllers
/******************************************************************************
 Private Methods Prototypes - Direct work with SD card
******************************************************************************/

/**
    \brief Simple function to calculate power of two.
    \param e Exponent.
    \return Math function result.
*/
DWORD __SD_Power_Of_Two(BYTE e);

/**
     \brief Assert the SD card (SPI CS low).
 */
inline void __SD_Assert (void);

/**
    \brief Deassert the SD (SPI CS high).
 */
inline void __SD_Deassert (void);

/**
    \brief Change to max the speed transfer.
    \param throttle
 */
void __SD_Speed_Transfer (BYTE throttle);

/**
    \brief Send SPI commands.
    \param cmd Command to send.
    \param arg Argument to send.
    \return R1 response.
 */
BYTE __SD_Send_Cmd(BYTE cmd, DWORD arg);

/**
    \brief Write a data block on SD card.
    \param dat Storage the data to transfer.
    \param token Inidicates the type of transfer (single or multiple).
 */
SDRESULTS __SD_Write_Block(SD_DEV *dev, void *dat, BYTE token);

/**
    \brief Get the total numbers of sectors in SD card.
    \param dev Device descriptor.
    \return Quantity of sectors. Zero if fail.
 */
DWORD __SD_Sectors (SD_DEV *dev);

/******************************************************************************
 Private Methods - Direct work with SD card
******************************************************************************/

DWORD __SD_Power_Of_Two(BYTE e)
{
    DWORD partial = 1;
    BYTE idx;
    for(idx=0; idx!=e; idx++) partial *= 2;
    return(partial);
}

inline void __attribute__((flatten, __section__(".text_bram"))) __SD_Assert(void){
    SPI_CS_Low();
}

inline void __attribute__((flatten, __section__(".text_bram"))) __SD_Deassert(void){
    SPI_CS_High();
}

void __SD_Speed_Transfer(BYTE throttle) {
    if(throttle == HIGH) SPI_Freq_High();
    else SPI_Freq_Low();
}

BYTE __attribute__((noinline, flatten, __section__(".text_bram"))) __SD_Send_Cmd(BYTE cmd, DWORD arg)
{
    BYTE wiredata[10];
    BYTE crc, res;
    int timer_set;

    //printf("Sending SD CMD 0x%x with arg 0x%x\n", cmd, arg);

    // ACMD«n» is the command sequense of CMD55-CMD«n»
    if(cmd & 0x80) {
        cmd &= 0x7F;
        res = __SD_Send_Cmd(CMD55, 0);
        if (res > 1) return (res);
    }

    // Select the card
    __SD_Deassert();
    SPI_R(NULL, 4);
    __SD_Assert();

    // Send complete command set
    wiredata[0] = cmd;                  // Start and command index
    wiredata[1] = (arg >> 24);          // Arg[31-24]
    wiredata[2] = (arg >> 16);          // Arg[23-16]
    wiredata[3] = (arg >> 8 );          // Arg[15-08]
    wiredata[4] = (arg >> 0 );          // Arg[07-00]

    // CRC?
    crc = 0x01;                         // Dummy CRC and stop
    if(cmd == CMD0) crc = 0x95;         // Valid CRC for CMD0(0)
    if(cmd == CMD8) crc = 0x87;         // Valid CRC for CMD8(0x1AA)
    wiredata[5] = crc;

    SPI_W(wiredata, 6);

    // Receive command response
    // Wait for a valid response in timeout of 5 milliseconds
    timer_set = SPI_Timer_On(5);
    do {
        SPI_R(&res, 1);
    } while((res & 0x80)&&(SPI_Timer_Status()==TRUE));
    if (timer_set == 0)
        SPI_Timer_Off();
    // Return with the response value
    //printf("CMD_res: %u\n", res);
    return(res);
}

SDRESULTS __SD_Write_Block(SD_DEV *dev, void *dat, BYTE token)
{
    WORD idx;
    BYTE line;
    // Send token (single or multiple)
    SPI_WW(token);
    // Single block write?
    if(token != 0xFD)
    {
        // Send block data
        for(idx=0; idx!=SD_BLK_SIZE; idx++) SPI_WW(*((BYTE*)dat + idx));
        /* Dummy CRC */
        SPI_WW(0xFF);
        SPI_WW(0xFF);
        // If not accepted, returns the reject error
        if((SPI_RW(0xFF) & 0x1F) != 0x05) return(SD_REJECT);
    }
#ifdef SD_IO_WRITE_WAIT_BLOCKER
    // Waits until finish of data programming (blocked)
    while(SPI_RW(0xFF)==0);
    return(SD_OK);
#else
    // Waits until finish of data programming with a timeout
    SPI_Timer_On(SD_IO_WRITE_TIMEOUT_WAIT);
    do {
        line = SPI_RW(0xFF);
    } while((line==0)&&(SPI_Timer_Status()==TRUE));
    SPI_Timer_Off();
#ifdef SD_IO_DBG_COUNT
    dev->debug.write++;
#endif
    if(line==0) return(SD_BUSY);
    else return(SD_OK);
#endif
}

DWORD __SD_Sectors (SD_DEV *dev)
{
    BYTE csd[18];
    BYTE tkn;
    DWORD ss = 0;
    WORD C_SIZE = 0;
    BYTE C_SIZE_MULT = 0;
    BYTE READ_BL_LEN = 0;
    int timer_set;

    if(__SD_Send_Cmd(CMD9, 0)==0)
    {
        // Wait for response
        timer_set = SPI_Timer_On(5);  // Wait for data packet (timeout of 5ms)
        do {
            SPI_R(&tkn, 1);
        } while((tkn==0xFF)&&(SPI_Timer_Status()==TRUE));
        if (timer_set == 0)
            SPI_Timer_Off();

        if(tkn!=0xFE)
            return 0;

        // TODO: CRC check
        SPI_R(csd, 18);

        if(dev->cardtype & SDCT_SD1)
        {
            ss = csd[0];
            // READ_BL_LEN[83:80]: max. read data block length
            READ_BL_LEN = (csd[5] & 0x0F);
            // C_SIZE [73:62]
            C_SIZE = (csd[6] & 0x03);
            C_SIZE <<= 8;
            C_SIZE |= (csd[7]);
            C_SIZE <<= 2;
            C_SIZE |= ((csd[8] >> 6) & 0x03);
            // C_SIZE_MULT [49:47]
            C_SIZE_MULT = (csd[9] & 0x03);
            C_SIZE_MULT <<= 1;
            C_SIZE_MULT |= ((csd[10] >> 7) & 0x01);
        }
        else if(dev->cardtype & SDCT_SD2)
        {
            // C_SIZE [69:48]
            C_SIZE = (csd[7] & 0x3F);
            C_SIZE <<= 8;
            C_SIZE |= (csd[8] & 0xFF);
            C_SIZE <<= 8;
            C_SIZE |= (csd[9] & 0xFF);
            // C_SIZE_MULT [--]. don't exits
            C_SIZE_MULT = 17;   //C_SIZE_MULT+2 = 19
            printf("csize: %u\n", C_SIZE);
        }
        ss = (C_SIZE + 1);
        // SD_BLK_SIZE = 2^9
        //ss *= __SD_Power_Of_Two(C_SIZE_MULT + 2 + READ_BL_LEN - 9);
        ss *= 1 << (C_SIZE_MULT + 2 + READ_BL_LEN - 9);
        //ss /= SD_BLK_SIZE;
        return (ss);
    } else return (0); // Error
}
#endif // Private methods for uC

/******************************************************************************
 Public Methods - Direct work with SD card
******************************************************************************/

SDRESULTS SD_Init(SD_DEV *dev)
{
    BYTE initdata[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
#if defined(_M_IX86)    // x86
    dev->fp = fopen(dev->fn, "r+");
    if (dev->fp == NULL)
        return (SD_ERROR);
    else
    {
        dev->last_sector = __SD_Sectors(dev);
#ifdef SD_IO_DBG_COUNT
        dev->debug.read = 0;
        dev->debug.write = 0;
#endif
        return (SD_OK);
    }
#else   // uControllers
    BYTE n, cmd, ct, ocr[4];
    BYTE idx;
    BYTE init_trys;
    ct = 0;
    for(init_trys=0; ((init_trys!=SD_INIT_TRYS)&&(!ct)); init_trys++)
    {
        // Initialize SPI for use with the memory card
        SPI_Init();

        __SD_Deassert();
        SPI_Freq_Low();

        // 80 dummy clocks
        //for(idx = 0; idx != 10; idx++) SPI_RW(0xFF);
        SPI_W(initdata, sizeof(initdata));

        /*SPI_Timer_On(500);
        while(SPI_Timer_Status()==TRUE);
        SPI_Timer_Off();*/

        dev->mount = FALSE;
        /*SPI_Timer_On(500);
        while ((__SD_Send_Cmd(CMD0, 0) != 1)&&(SPI_Timer_Status()==TRUE));
        SPI_Timer_Off();*/
        if (__SD_Send_Cmd(CMD0, 0) != 1)
            continue;
        // Idle state

        // SD version 2?
        if (__SD_Send_Cmd(CMD8, 0x1AA) == 1) {
            // Get trailing return value of R7 resp
            SPI_R(ocr, 4);
            // VDD range of 2.7-3.6V is OK?
            if ((ocr[2] == 0x01)&&(ocr[3] == 0xAA))
            {
                // Wait for leaving idle state (ACMD41 with HCS bit)...
                SPI_Timer_On(1000);
                while ((SPI_Timer_Status()==TRUE)&&(__SD_Send_Cmd(ACMD41, 1UL << 30)));
                // CCS in the OCR?
                if ((SPI_Timer_Status()==TRUE)&&(__SD_Send_Cmd(CMD58, 0) == 0))
                {
                    SPI_R(ocr, 4);
                    // SD version 2?
                    ct = (ocr[0] & 0x40) ? SDCT_SD2 | SDCT_BLOCK : SDCT_SD2;
                }
                SPI_Timer_Off();
            }
        } else {
            // SD version 1 or MMC?
            if (__SD_Send_Cmd(ACMD41, 0) <= 1)
            {
                // SD version 1
                ct = SDCT_SD1;
                cmd = ACMD41;
            } else {
                // MMC version 3
                ct = SDCT_MMC;
                cmd = CMD1;
            }
            // Wait for leaving idle state
            SPI_Timer_On(250);
            while((SPI_Timer_Status()==TRUE)&&(__SD_Send_Cmd(cmd, 0)));

            if(SPI_Timer_Status()==FALSE) ct = 0;
            SPI_Timer_Off();
            if(__SD_Send_Cmd(CMD59, 0))   ct = 0;   // Deactivate CRC check (default)
            if(__SD_Send_Cmd(CMD16, 512)) ct = 0;   // Set R/W block length to 512 bytes
        }
    }
    if(ct) {
        dev->cardtype = ct;
        dev->mount = TRUE;
        dev->last_sector = __SD_Sectors(dev) - 1;
        printf("lastsec %lu\n", dev->last_sector);
#ifdef SD_IO_DBG_COUNT
        dev->debug.read = 0;
        dev->debug.write = 0;
#endif
        __SD_Speed_Transfer(HIGH); // High speed transfer
    }
    SPI_Release();
    return (ct ? SD_OK : SD_NOINIT);
#endif
}

SDRESULTS __attribute__((noinline, flatten, __section__(".text_bram"))) SD_Read(SD_DEV *dev, void *dat, DWORD sector, WORD ofs, WORD cnt)
{
#if defined(_M_IX86)    // x86
    // Check the sector query
    if((sector > dev->last_sector)||(cnt == 0)) return(SD_PARERR);
    if(dev->fp!=NULL)
    {
        if (fseek(dev->fp, ((512 * sector) + ofs), SEEK_SET)!=0)
            return(SD_ERROR);
        else {
            if(fread(dat, 1, (cnt - ofs),dev->fp)==(cnt - ofs))
            {
#ifdef SD_IO_DBG_COUNT
                dev->debug.read++;
#endif
                return(SD_OK);
            }
            else return(SD_ERROR);
        }
    } else {
        return(SD_ERROR);
    }
#else   // uControllers
    SDRESULTS res;
    BYTE tkn;
    WORD remaining;
    res = SD_ERROR;
    if ((sector > dev->last_sector)||(cnt == 0)) return(SD_PARERR);
    // Convert sector number to byte address (sector * SD_BLK_SIZE) for SDC1
    if (!(dev->cardtype & SDCT_BLOCK))
        sector *= SD_BLK_SIZE;

    if (__SD_Send_Cmd(CMD17, sector) == 0) {
        SPI_Timer_On(100);  // Wait for data packet (timeout of 100ms)
        do {
            SPI_R(&tkn, 1);
        } while((tkn==0xFF)&&(SPI_Timer_Status()==TRUE));
        SPI_Timer_Off();
        // Token of single block?
        if(tkn==0xFE) {
            // Size block (512 bytes) + CRC (2 bytes) - offset - bytes to count
            remaining = SD_BLK_SIZE + 2 - ofs - cnt;
            // Skip offset
            if(ofs) {
                SPI_R(NULL, ofs);
            }
            // I receive the data and I write in user's buffer
            SPI_R((BYTE*)dat, cnt);
            // Skip remaining
            // TODO: CRC
            SPI_R(NULL, remaining);
            res = SD_OK;
        }
    }
    SPI_Release();
#ifdef SD_IO_DBG_COUNT
    dev->debug.read++;
#endif
    return(res);
#endif
}

#ifdef SD_IO_WRITE
SDRESULTS SD_Write(SD_DEV *dev, void *dat, DWORD sector)
{
#if defined(_M_IX86)    // x86
    // Query ok?
    if(sector > dev->last_sector) return(SD_PARERR);
    if(dev->fp != NULL)
    {
        if(fseek(dev->fp, SD_BLK_SIZE * sector, SEEK_SET)!=0)
            return(SD_ERROR);
        else {
            if(fwrite(dat, 1, SD_BLK_SIZE, dev->fp)==SD_BLK_SIZE)
            {
#ifdef SD_IO_DBG_COUNT
                dev->debug.write++;
#endif
                return(SD_OK);
            }
            else return(SD_ERROR);
        }
    } else return(SD_ERROR);
#else   // uControllers
    // Query ok?
    if(sector > dev->last_sector) return(SD_PARERR);
    // Convert sector number to byte address (sector * SD_BLK_SIZE) for SDC1
    if (!(dev->cardtype & SDCT_BLOCK))
        sector *= SD_BLK_SIZE;

    // Single block write (token <- 0xFE)
    // Convert sector number to bytes address (sector * SD_BLK_SIZE)
    if(__SD_Send_Cmd(CMD24, sector)==0)
        return(__SD_Write_Block(dev, dat, 0xFE));
    else
        return(SD_ERROR);
#endif
}
#endif

SDRESULTS SD_Status(SD_DEV *dev)
{
#if defined(_M_IX86)
    return((dev->fp == NULL) ? SD_OK : SD_NORESPONSE);
#else
    return(__SD_Send_Cmd(CMD0, 0) ? SD_OK : SD_NORESPONSE);
#endif
}

// «sd_io.c» is part of:
/*----------------------------------------------------------------------------/
/  ulibSD - Library for SD cards semantics            (C)Nelson Lombardo, 2015
/-----------------------------------------------------------------------------/
/ ulibSD library is a free software that opened under license policy of
/ following conditions.
/
/ Copyright (C) 2015, ChaN, all right reserved.
/
/ 1. Redistributions of source code must retain the above copyright notice,
/    this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/----------------------------------------------------------------------------*/

// Derived from Mister Chan works on FatFs code (http://elm-chan.org/fsw/ff/00index_e.html):
/*----------------------------------------------------------------------------/
/  FatFs - FAT file system module  R0.11                 (C)ChaN, 2015
/-----------------------------------------------------------------------------/
/ FatFs module is a free software that opened under license policy of
/ following conditions.
/
/ Copyright (C) 2015, ChaN, all right reserved.
/
/ 1. Redistributions of source code must retain the above copyright notice,
/    this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/----------------------------------------------------------------------------*/
