/**************************************************************************//**
 * @file     sd.c
 * @brief    SdLoader source code for SD driver
 *
 * SPDX-License-Identifier: Apache-2.0
 * @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved.
*****************************************************************************/

#include <string.h>
#include "wblib.h"
#include "turbowriter.h"

//#define DEBUG
//#define OPT_FPGA_DEBUG

int volatile _fmi_uFMIReferenceClock;

#define SD_FREQ     24000
#define SDHC_FREQ   24000
#define MMC_FREQ    20000

FMI_SD_INFO_T SDInfo, *pSD;
extern UINT8 dummy_buffer[];

//--- define type of SD card or MMC
#define FMI_TYPE_UNKNOWN                0
#define FMI_TYPE_SD_HIGH                1
#define FMI_TYPE_SD_LOW                 2
#define FMI_TYPE_MMC                    3   // MMC access mode: Byte mode for capacity <= 2GB
#define FMI_TYPE_MMC_SECTOR_MODE        4   // MMC access mode: Sector mode for capacity > 2GB

// global variables
UINT32 _fmi_uR3_CMD=0;
UINT32 _fmi_uR7_CMD=0;

void fmiCheckRB()
{
    while(1)
    {
        outpw(REG_SDCR, inpw(REG_SDCR)|SDCR_8CLK_OE);
        while(inpw(REG_SDCR) & SDCR_8CLK_OE);
        if (inpw(REG_SDISR) & SDISR_SD_DATA0)
            break;
    }
}

INT fmiSDCommand(UINT8 ucCmd, UINT32 uArg)
{
    outpw(REG_SDARG, uArg);
    outpw(REG_SDCR, (inpw(REG_SDCR)&0xffffc080)|(ucCmd << 8)|(0x01));

    while(inpw(REG_SDCR) & 0x01)
    {
        if (pSD->bIsCardInsert == FALSE)
            return -1;
    }

    return Successful;
}


INT fmiSDCmdAndRsp(UINT8 ucCmd, UINT32 uArg, INT ntickCount)
{
    unsigned int reg;
    int volatile count=0;

    outpw(REG_SDARG, uArg);
    reg = inpw(REG_SDCR) & 0xffffc080;
    outpw(REG_SDCR, reg | (ucCmd << 8)|(0x03));

    if (ntickCount > 0)
    {
        while(inpw(REG_SDCR) & 0x02)
        {
            if (count++ > ntickCount)
            {
                outpw(REG_SDCR, inpw(REG_SDCR)|0x4000); // reset SD engine
                return 2;
            }
            if (pSD->bIsCardInsert == FALSE)
                return -1;
        }
    }
    else
    {
        while(inpw(REG_SDCR) & 0x02)
        {
            if (pSD->bIsCardInsert == FALSE)
                return -1;
        }
    }

    if (_fmi_uR7_CMD)
    {
        if (((inpw(REG_SDRSP1) & 0xff) != 0x55) && ((inpw(REG_SDRSP0) & 0xf) != 0x01))
        {
            _fmi_uR7_CMD = 0;
            return -1;
        }
    }

    if (!_fmi_uR3_CMD)
    {
        if (inpw(REG_SDISR) & 0x04)     // check CRC7
            return Successful;
        else
        {
#ifdef DEBUG
            printf("response error [%d]!\n", ucCmd);
#endif
            return -1;
        }
    }
    else
    {
        _fmi_uR3_CMD = 0;
        outpw(REG_SDISR, 0x02);
        return Successful;
    }
}


// Get 16 bytes CID or CSD
INT fmiSDCmdAndRsp2(UINT8 ucCmd, UINT32 uArg, UINT *puR2ptr)
{
    unsigned int i;
    unsigned int tmpBuf[5];
    unsigned int reg;

    outpw(REG_SDARG, uArg);
    reg = inpw(REG_SDCR) & 0xffffc080;
    outpw(REG_SDCR, reg | (ucCmd << 8)|(0x11));

    while(inpw(REG_SDCR) & 0x10)
    {
        if (pSD->bIsCardInsert == FALSE)
            return -1;
    }

    if (inpw(REG_SDISR) & 0x04)
    {
        for (i=0; i<5; i++)
            tmpBuf[i] = Swap32(inpw(REG_FB_0+i*4));

        for (i=0; i<4; i++)
            *puR2ptr++ = ((tmpBuf[i] & 0x00ffffff)<<8) | ((tmpBuf[i+1] & 0xff000000)>>24);
        return Successful;
    }
    else
        return -1;
}


INT fmiSDCmdAndRspDataIn(UINT8 ucCmd, UINT32 uArg)
{
    unsigned int reg;

    outpw(REG_SDARG, uArg);
    reg = inpw(REG_SDCR) & 0xffffc080;
    outpw(REG_SDCR, reg | (ucCmd << 8)|(0x07));

    while (inpw(REG_SDCR) & 0x02)
    {
        if (pSD->bIsCardInsert == FALSE)
            return -1;
    }

    while (inpw(REG_SDCR) & 0x04)
    {
        if (pSD->bIsCardInsert == FALSE)
            return -1;
    }

    /* According to comment in Linux driver mmc_sd_init_card() in core/sd.c, 
       "some SDHC cards are not able to provide valid CRCs for non-512-byte blocks",
       SDLoader don't check CRC-16 to improve compatibility.
     */
    if (inpw(REG_SDISR) & 0x04)     // check CRC7
        return Successful;
    else
    {
#ifdef DEBUG
        printf("fmiSDCmdAndRspDataIn: response error [%d]!\n", ucCmd);
#endif
        return -1;
    }

//    if (!(inpw(REG_SDISR) & 0x08))      // check CRC16
//    {
//#ifdef DEBUG
//        printf("fmiSDCmdAndRspDataIn: read data error!\n");
//#endif
//        return -1;
//    }
//    return Successful;
}


// Initial
INT fmiSD_Init()
{
    int volatile i, status, rate;
    unsigned int resp;
    unsigned int CIDBuffer[4];
    UINT32 div0;

#if 1
    // set the clock to 200KHz
    /* divider */
    rate = _fmi_uFMIReferenceClock / 200;
    if ((_fmi_uFMIReferenceClock % 200) == 0)
        rate = rate - 1;
#else
    // set the clock to 400KHz
    /* divider */
    rate = _fmi_uFMIReferenceClock / 400;
    if ((_fmi_uFMIReferenceClock % 400) == 0)
        rate = rate - 1;
#endif

    for(i=0; i<100; i++);
    outpw(REG_CLKDIV2, (inpw(REG_CLKDIV2) & ~SD_S) | (0x03 << 19));     // SD clock from UPLL

#ifdef OPT_FPGA_DEBUG
    // div0 always is 1 for FPGA board since FPGA board don't support CLKDIV2[SD_N0]
    div0 = 1;
#else
    div0 = 8;
#endif

    outpw(REG_CLKDIV2, (inpw(REG_CLKDIV2) & ~SD_N0) | ((div0-1) << 16));    // SD clock divided by 8
    rate /= div0;
    rate &= 0xFF;
    if (rate) rate--;
    outpw(REG_CLKDIV2, (inpw(REG_CLKDIV2) & ~SD_N1) | (rate << 24));    // SD clock divider

    for(i=0; i<1000; i++);

    // power ON 74 clock
    outpw(REG_SDCR, inpw(REG_SDCR) | SDCR_74CLK_OE);

    while(inpw(REG_SDCR) & SDCR_74CLK_OE)
    {
        if (pSD->bIsCardInsert == FALSE)
            return -1;
    }

    fmiSDCommand(0, 0);     // reset all cards
    for (i=0x100; i>0; i--);

    // initial SDHC
    _fmi_uR7_CMD = 1;
    i = fmiSDCmdAndRsp(8, 0x00000155, 5000);
    _fmi_uR7_CMD = 0;   // Disable R7 checking for commands that are not CMD8
    if (i == Successful)
    {
        // SD 2.0
        fmiSDCmdAndRsp(55, 0x00, 0);
        _fmi_uR3_CMD = 1;
        fmiSDCmdAndRsp(41, 0x40ff8000, 0);  // 2.7v-3.6v
        resp = inpw(REG_SDRSP0);
        while (!(resp & 0x00800000))        // check if card is ready
        {
            fmiSDCmdAndRsp(55, 0x00, 0);
            _fmi_uR3_CMD = 1;
            fmiSDCmdAndRsp(41, 0x40ff8000, 0);  // 3.0v-3.4v
            resp = inpw(REG_SDRSP0);
        }
        if (resp & 0x00400000)
            pSD->uCardType = FMI_TYPE_SD_HIGH;
        else
            pSD->uCardType = FMI_TYPE_SD_LOW;
    }
    else
    {
        // SD 1.1
        i = fmiSDCmdAndRsp(55, 0x00, 6000);
        if (i == 2)     // MMC memory
        {
            fmiSDCommand(0, 0);     // reset
            for (i=0x100; i>0; i--);

            _fmi_uR3_CMD = 1;
            // 2014/8/6, to support eMMC v4.4, the argument of CMD1 should be 0x40ff8000 to support both MMC plus and eMMC cards.
            if (fmiSDCmdAndRsp(1, 0x40ff8000, 5000) != 2)   // MMC memory
            {
                resp = inpw(REG_SDRSP0);
                while (!(resp & 0x00800000))    // check if card is ready
                {
                    _fmi_uR3_CMD = 1;
                    fmiSDCmdAndRsp(1, 0x40ff8000, 0);   // high voltage
                    resp = inpw(REG_SDRSP0);
                }
                // MMC card is ready. Check the access mode of MMC card.
                if (resp & 0x00400000)
                {
                    pSD->uCardType = FMI_TYPE_MMC_SECTOR_MODE;
                    //printf("--> MMC card report Ready and Sector Mode (>2GB) !\n");
                }
                else
                {
                    pSD->uCardType = FMI_TYPE_MMC;
                    //printf("--> MMC card report Ready and Byte Mode (<=2GB) !\n");
                }
            }
            else
            {
                pSD->uCardType = FMI_TYPE_UNKNOWN;
                return -1;
            }
        }
        else if (i == 0)    // SD Memory
        {
            _fmi_uR3_CMD = 1;
            fmiSDCmdAndRsp(41, 0x00ff8000, 0);  // 3.0v-3.4v
            resp = inpw(REG_SDRSP0);
            while (!(resp & 0x00800000))        // check if card is ready
            {
                fmiSDCmdAndRsp(55, 0x00, 0);
                _fmi_uR3_CMD = 1;
                fmiSDCmdAndRsp(41, 0x00ff8000, 0);  // 3.0v-3.4v
                resp = inpw(REG_SDRSP0);
            }
            pSD->uCardType = FMI_TYPE_SD_LOW;
        }
        else
        {
            pSD->uCardType = FMI_TYPE_UNKNOWN;
#ifdef DEBUG
            printf("CMD55 CRC error !!\n");
#endif
            return -1;
        }
    }

    // CMD2, CMD3
    if (pSD->uCardType != FMI_TYPE_UNKNOWN)
    {
        fmiSDCmdAndRsp2(2, 0x00, CIDBuffer);
        if (pSD->uCardType == FMI_TYPE_MMC)
        {
            if ((status = fmiSDCmdAndRsp(3, 0x10000, 0)) != Successful)     // set RCA
                return status;
            pSD->uRCA = 0x10000;
        }
        else
        {
            if ((status = fmiSDCmdAndRsp(3, 0x00, 0)) != Successful)        // get RCA
                return status;
            else
                pSD->uRCA = (inpw(REG_SDRSP0) << 8) & 0xffff0000;
        }
    }

#ifdef DEBUG
    switch (pSD->uCardType)
    {
        case FMI_TYPE_SD_HIGH:
            sysprintf("This is high capacity SD memory card\n");       break;
        case FMI_TYPE_SD_LOW:
            sysprintf("This is standard capacity SD memory card\n");   break;
        case FMI_TYPE_MMC:
            sysprintf("This is standard capacity MMC memory card\n");  break;
        case FMI_TYPE_MMC_SECTOR_MODE:
            sysprintf("This is high capacity MMC memory card\n");      break;
        default:
            sysprintf("ERROR: Unknown card type !!\n");                break;
    }
#endif

    // set data transfer clock
    return Successful;
}
/*
INT fmiSwitchToHighSpeed()
{
    UINT8 *Info;
    int volatile status=0;
    UINT16 current_comsumption, fun1_info, switch_status, busy_status0, busy_status1;

    Info = (UINT8 *)((UINT32)dummy_buffer | 0x80000000);
    outpw(REG_DMACSAR, (UINT32)Info);   // set DMA transfer starting address
    outpw(REG_SDBLEN, 63);  // 512 bit

    if ((status = fmiSDCmdAndRspDataIn(6, 0x00ffff01)) != Successful)
        return Fail;

    current_comsumption = dummy_buffer[0]<<8 | dummy_buffer[1];
    if (!current_comsumption)
        return Fail;

    fun1_info =  dummy_buffer[12]<<8 | dummy_buffer[13];

    switch_status =  dummy_buffer[16] & 0xf;

    busy_status0 = dummy_buffer[28]<<8 | dummy_buffer[29];

    if (!busy_status0)  // function ready
    {
        outpw(REG_DMACSAR, (UINT32)Info);   // set DMA transfer starting address
        outpw(REG_SDBLEN, 63);  // 512 bit

        if ((status = fmiSDCmdAndRspDataIn(6, 0x80ffff01)) != Successful)
            return Fail;

        // function change timing: 8 clocks
        outpw(REG_SDCR, inpw(REG_SDCR)|0x40);
        while(inpw(REG_SDCR) & 0x40);

        current_comsumption = dummy_buffer[0]<<8 | dummy_buffer[1];
        if (!current_comsumption)
            return Fail;

        busy_status1 = dummy_buffer[28]<<8 | dummy_buffer[29];
#ifdef DEBUG
        if (!busy_status1)
            printf("switch into high speed mode !!!\n");
#endif
        return Successful;
    }
    else
        return Fail;
}
*/

INT fmiSelectCard()
{
    UINT8 *Info;
    int volatile status=0;
    UINT32 arg;
//  UINT32 div0;

    if ((status = fmiSDCmdAndRsp(7, pSD->uRCA, 0)) != Successful)
        return status;
    fmiCheckRB();

    // if SD card set 4bit
#if 0
    if (pSD->uCardType == FMI_TYPE_SD_HIGH)
    {
        Info = (UINT8 *)((UINT32)dummy_buffer | 0x80000000);
        outpw(REG_DMACSAR, (UINT32)Info);   // set DMA transfer starting address
        outpw(REG_SDBLEN, 7);   // 64 bit

        if ((status = fmiSDCmdAndRsp(55, pSD->uRCA, 0)) != Successful)
            return status;
        if ((status = fmiSDCmdAndRspDataIn(51, 0x00)) != Successful)
            return status;

        {
            int volatile rate, i;
            if ((dummy_buffer[0] & 0xf) == 0x2)
            {
                status = fmiSwitchToHighSpeed();
                if (status == Successful)
                {
                    /* divider */
                    rate = _fmi_uFMIReferenceClock / SDHC_FREQ;
            //      if ((_fmi_uFMIReferenceClock % SDHC_FREQ) == 0)
            //          rate = rate - 1;
                    if ((_fmi_uFMIReferenceClock % SDHC_FREQ) > 0)
                        rate ++;

                    for(i=0; i<100; i++);

                    outpw(REG_CLKDIV2, (inpw(REG_CLKDIV2) & ~SD_S) | (0x03 << 19));     // SD clock from UPLL
                #ifdef OPT_FPGA_DEBUG
                    // div0 always is 1 for FPGA board since FPGA board don't support CLKDIV2[SD_N0]
                    div0 = 1;
                #else
                    div0 = 2;
                #endif
                    outpw(REG_CLKDIV2, (inpw(REG_CLKDIV2) & ~SD_N0) | ((div0-1) << 16));    // SD clock divided by 8

                    if (rate % div0)
                    {
                        rate /= div0;
                        rate &= 0xFF;
                    }
                    else
                    {
                        rate /= div0;
                        rate &= 0xFF;
                        rate--;
                    }

                    outpw(REG_CLKDIV2, (inpw(REG_CLKDIV2) & ~SD_N1) | (rate << 24));    // SD clock divider
                    for(i=0; i<1000; i++);
                }
            }
        }

        if ((status = fmiSDCmdAndRsp(55, pSD->uRCA, 0)) != Successful)
            return status;
        if ((status = fmiSDCmdAndRsp(6, 0x02, 0)) != Successful)    // set bus width
            return status;

        outpw(REG_SDCR, inpw(REG_SDCR)|0x8000);
    }
#endif
    //else if (pSD->uCardType == FMI_TYPE_SD_LOW)
    if ((pSD->uCardType == FMI_TYPE_SD_LOW) || (pSD->uCardType == FMI_TYPE_SD_HIGH))
    {
        Info = (UINT8 *)((UINT32)dummy_buffer | 0x80000000);
        outpw(REG_DMACSAR, (UINT32)Info);   // set DMA transfer starting address
        outpw(REG_SDBLEN, 7);   // 64 bit
#if 0
        if ((status = fmiSDCmdAndRsp(55, pSD->uRCA, 0)) != Successful)
            return status;
        if ((status = fmiSDCmdAndRspDataIn(51, 0x00)) != Successful)
            return status;
#endif
        if ((status = fmiSDCmdAndRsp(55, pSD->uRCA, 0)) != Successful)
            return status;
        if ((status = fmiSDCmdAndRsp(6, 0x02, 0)) != Successful)    // set bus width
            return status;

        outpw(REG_SDCR, inpw(REG_SDCR)|0x8000);
    }
    else if ((pSD->uCardType == FMI_TYPE_MMC) || (pSD->uCardType == FMI_TYPE_MMC_SECTOR_MODE))
    {
#if 0
        //--- MMC card bus width is 1 bit mode by default
        outpw(REG_SDCR, inpw(REG_SDCR) & (~0x00008000); // set SD controller to 1 bit mode
#else
        //--- sent CMD6 to MMC card to set bus width to 4 bits mode
        // set CMD6 argument Access field to 3, Index to 183, Value to 1 (4-bit mode)
        arg = (3 << 24) | (183 << 16) | (1 << 8);
        if ((status = fmiSDCmdAndRsp(6, arg, 0)) != Successful)
            return status;
        fmiCheckRB();

        outpw(REG_SDCR, inpw(REG_SDCR)|0x00008000); // set bus width to 4-bit mode for SD host controller
#endif
    }

    if ((status = fmiSDCmdAndRsp(16, 512, 0)) != Successful)    // set block length
        return status;

    fmiSDCommand(7, 0);

    outpw(REG_SDISR, 0xffff);
    outpw(REG_SDIER, inpw(REG_SDIER)|0x01);

    return Successful;
}


INT fmiSD_Read_in(UINT32 uSector, UINT32 uBufcnt, UINT32 uDAddr)
{
    BOOL volatile bIsSendCmd=FALSE;
    unsigned int volatile reg;
    int volatile i, loop, status;

    if ((status = fmiSDCmdAndRsp(7, pSD->uRCA, 0)) != Successful)
        return status;
    fmiCheckRB();

    outpw(REG_SDBLEN, 0x1ff);   // 512 bytes
    if ((pSD->uCardType == FMI_TYPE_SD_HIGH) || (pSD->uCardType == FMI_TYPE_MMC_SECTOR_MODE))
        outpw(REG_SDARG, uSector);
    else
        outpw(REG_SDARG, uSector * 512);

    outpw(REG_DMACSAR, uDAddr);

    loop = uBufcnt / 255;
    for (i=0; i<loop; i++)
    {
        reg = inpw(REG_SDCR) & 0xff00c080;
        reg = reg | 0xff0000;
        if (!bIsSendCmd)
        {
            outpw(REG_SDCR, reg|(18<<8)|0x07);
            bIsSendCmd = TRUE;
        }
        else
            outpw(REG_SDCR, reg | 0x04);

        while(1)
        {
            if ((inpw(REG_SDISR) & 0x01) && (!(inpw(REG_SDCR) & 0x04)))
            {
                outpw(REG_SDISR, 0x01);
                break;
            }
            if (pSD->bIsCardInsert == FALSE)
                return -1;
        }

        if (!(inpw(REG_SDISR) & 0x04))      // check CRC7
        {
#ifdef DEBUG
            printf("fmiSD_Read: response error!\n");
#endif
            return -1;
        }

        if (!(inpw(REG_SDISR) & 0x08))      // check CRC16
        {
#ifdef DEBUG
            printf("fmiSD_Read_in:read data error!\n");
#endif
            return -1;
        }
    }

    loop = uBufcnt % 255;
    if (loop != 0)
    {
        reg = (inpw(REG_SDCR) & 0xff00c080) | (loop << 16);
        if (!bIsSendCmd)
        {
            outpw(REG_SDCR, reg|(18<<8)|0x07);
            bIsSendCmd = TRUE;
        }
        else
            outpw(REG_SDCR, reg | 0x04);

        while(1)
        {
            if ((inpw(REG_SDISR) & 0x01) && (!(inpw(REG_SDCR) & 0x04)))
            {
                outpw(REG_SDISR, 0x01);
                break;
            }
            if (pSD->bIsCardInsert == FALSE)
                return -1;
        }

        if (!(inpw(REG_SDISR) & 0x04))      // check CRC7
        {
#ifdef DEBUG
            printf("fmiSD_Read: response error!\n");
#endif
            return -1;
        }

        if (!(inpw(REG_SDISR) & 0x08))      // check CRC16
        {
#ifdef DEBUG
            printf("fmiSD_Read_in:read data error!\n");
#endif
            return -1;
        }
    }

    if (fmiSDCmdAndRsp(12, 0, 0))       // stop command
    {
#ifdef DEBUG
        printf("stop command fail !!\n");
#endif
        return -1;
    }
    fmiCheckRB();

    fmiSDCommand(7, 0);

    // According to SD spec v2.0 chapter 4.4,
    // "After the last SD Memory Card bus transaction, the host is required,
    //  to provide 8 (eight) clock cycles for the card to complete the
    //  operation before shutting down the clock."
    outpw(REG_SDCR, inpw(REG_SDCR)|SDCR_8CLK_OE);

    return Successful;
}


VOID fmiGet_SD_info()
{
    unsigned int R_LEN, C_Size, MULT, size;
    unsigned int Buffer[4];

    fmiSDCmdAndRsp2(9, pSD->uRCA, Buffer);

#ifdef DEBUG
    printf("max. data transfer rate [%x][%08x]\n", Buffer[0]&0xff, Buffer[0]);
#endif

    if ((Buffer[0] & 0xc0000000) && (pSD->uCardType != FMI_TYPE_MMC))
    {
        C_Size = ((Buffer[1] & 0x0000003f) << 16) | ((Buffer[2] & 0xffff0000) >> 16);
        size = (C_Size+1) * 512;    // Kbytes

        pSD->totalSectorN = size << 1;
    }
    else
    {
        R_LEN = (Buffer[1] & 0x000f0000) >> 16;
        C_Size = ((Buffer[1] & 0x000003ff) << 2) | ((Buffer[2] & 0xc0000000) >> 30);
        MULT = (Buffer[2] & 0x00038000) >> 15;
        size = (C_Size+1) * (1<<(MULT+2)) * (1<<R_LEN);

        pSD->totalSectorN = size / 512;
    }
}


INT  fmiInitSDDevice(int cardSel)
{
    int volatile rate, i;
    UINT32 div0;

    //Reset FMI
    outpw(REG_FMICR, FMI_SWRST);        // Start reset FMI controller.
    while(inpw(REG_FMICR)&FMI_SWRST);

    // enable SD
    outpw(REG_FMICR, FMI_SD_EN);
    outpw(REG_SDCR, inpw(REG_SDCR) & ~0xFF);        // disable SD clock ouput

    // reset SD engine
    outpw(REG_SDCR, inpw(REG_SDCR) | SDCR_SWRST);   // SD software reset
    while(inpw(REG_SDCR) & SDCR_SWRST);

    //Enable SD I/O pins (SD-0/1/2 port)
    if (cardSel==0)
    {
        outpw(REG_GPEFUN0, (inpw(REG_GPEFUN0) & (~0xFFFFFF00)) | 0x22222200);   // SD0_CLK/CMD/DAT0_3 pins selected
        outpw(REG_SDCR, inpw(REG_SDCR) & (~SDCR_SDPORT));                       // SD_0 port selected
    }

    else if (cardSel==1)
    {
        outpw(REG_GPBFUN0, (inpw(REG_GPBFUN0) & (~0x00FFFFFF)) | 0x00222222);   // SD1_CLK/CMD/DAT0_3 pins selected
        outpw(REG_SDCR, inpw(REG_SDCR) & (~SDCR_SDPORT) | SDCR_SDPORT_1);       // SD_1 port selected
    }
    else if (cardSel==2)
    {
        outpw(REG_GPDFUN0, (inpw(REG_GPDFUN0) & (~0xF0F00000)) | 0x10100000);   // SD2_DAT2/SD2_CLK pins selected
        outpw(REG_GPDFUN1, (inpw(REG_GPDFUN1) & (~0x0000000F)) | 0x00000001);   // SD2_CMD pins selected
        outpw(REG_GPEFUN1, (inpw(REG_GPEFUN1) & (~0x000FFF00)) | 0x00011100);   // SD2_DAT0_1_3 pins selected
        outpw(REG_SDCR, inpw(REG_SDCR) & (~SDCR_SDPORT) | SDCR_SDPORT_2);   // SD_2 port selected
    }

    // Disable FMI/SD host interrupt
    outpw(REG_FMIIER, 0);

    // restore default register value
    outpw(REG_SDIER, 0x00000A00);
    outpw(REG_SDISR, 0x0000FFFF);
    outpw(REG_SDTMOUT, 0x00FFFFFF);

    pSD = &SDInfo;
    memset((char *)pSD, 0, sizeof(FMI_SD_INFO_T));

    pSD->bIsCardInsert = TRUE;

    if (fmiSD_Init() < 0)
        return -1;

    /* divider */
    if (pSD->uCardType == 3)
        rate = _fmi_uFMIReferenceClock / MMC_FREQ;
    else
        rate = _fmi_uFMIReferenceClock / SD_FREQ;

    if (pSD->uCardType == 3)
    {
        if ((_fmi_uFMIReferenceClock % MMC_FREQ) > 0)
            rate ++;
    }
    else
    {
        if ((_fmi_uFMIReferenceClock % SD_FREQ) > 0)
            rate ++;
    }

    for(i=0; i<100; i++);
    outpw(REG_CLKDIV2, (inpw(REG_CLKDIV2) & ~SD_S) | (0x03 << 19));     // SD clock from UPLL
#ifdef OPT_FPGA_DEBUG
    // div0 always is 1 for FPGA board since FPGA board don't support CLKDIV2[SD_N0]
    div0 = 1;
#else
    div0 = 2;
#endif
    outpw(REG_CLKDIV2, (inpw(REG_CLKDIV2) & ~SD_N0) | ((div0-1) << 16));    // SD clock divided by 2
    if (rate % div0)
    {
        rate /= div0;
        rate &= 0xFF;
    }
    else
    {
        rate /= div0;
        rate &= 0xFF;
        rate--;
    }
    outpw(REG_CLKDIV2, (inpw(REG_CLKDIV2) & ~SD_N1) | (rate << 24));    // SD clock divider
    for(i=0; i<1000; i++);

    /* init SD interface */
    fmiGet_SD_info();

    if (fmiSelectCard())
        return -1;

    return 0;
}   /* end InitIDEDevice */

INT  fmiSD_Read(UINT32 uSector, UINT32 uBufcnt, UINT32 uDAddr)
{
    int status=0;

    // enable SD
    outpw(REG_FMICR, 0x02);
    status = fmiSD_Read_in(uSector, uBufcnt, uDAddr);

    return status;
}

VOID fmiInitDevice()
{
    // Enable SD Card Host Controller operation and driving clock.
    outpw(REG_AHBCLK, inpw(REG_AHBCLK) | SIC_CKE);
    outpw(REG_AHBCLK, inpw(REG_AHBCLK) | SD_CKE);

    // DMAC Initial
    outpw(REG_DMACCSR, DMAC_EN | DMAC_SWRST);
    outpw(REG_DMACCSR, DMAC_EN);
    outpw(REG_DMACISR, TABORT_IF);
    outpw(REG_DMACIER, 0x00000000);

    outpw(REG_FMICR, FMI_SWRST);        // reset FMI engine

    // assign UPLL to reference clock of SD engine.
#ifdef OPT_FPGA_DEBUG
    _fmi_uFMIReferenceClock = 27000;    // 27MHz for FPGA board
#else
    _fmi_uFMIReferenceClock = sysGetPLLOutputHz(eSYS_UPLL, sysGetExternalClock()) / 1000;
#endif
}
