/* SPDX-License-Identifier: LGPL-2.1-only OR BSD-3-Clause */
/******************************************************************************
 *
 * This file is provided under a dual license.  When you use or
 * distribute this software, you may choose to be licensed under
 * version 2.1 of the GNU Lesser General Public License
 * ("LGPLv2.1 License") or BSD License.
 *
 * LGPLv2.1 License
 *
 * Copyright(C) 2019 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html for more
 * details.
 *
 * BSD LICENSE
 *
 * Copyright(C) 2019 MediaTek Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************/


#if defined(CC_EMMC_BOOT)

#include <common.h>
#include "x_bim.h"
#include "x_timer.h"
#include <config.h>
#include <malloc.h>
#include <asm/errno.h>
#include <linux/mtd/mtd.h>
#include "mmc.h"
#include "msdc_basic.h"
#include "msdc_host_hw.h"
#include "msdc_slave_hw.h"
#include "msdc_drv.h"
#include "x_ldr_env.h"
//#include "x_hal_5381.h"
#include "x_hal_arm.h"
#include "drvcust_if.h"
#include "dmx_drm_if.h"
#include "msdc_mmc_protocal.h"
#include "msdc_ett_setting.h"
#if defined(CC_MTD_ENCRYPT_SUPPORT)
#include "msdc_ext.h"
#endif


#define MSDC_EMMC_INIT_UNIFY_EN             (1)

int ch = 1;
/* specify default data transfer mode */
int dataMode = BASIC_DMA_DATA_MODE;
uint speedmode = SPEED_MODE_DS;
#ifdef CC_PARTITION_WP_SUPPORT
uint wp_config = 0;
#endif
/* the global variable related to descriptor dma */
#if !MSDC_USE_NORMAL_READ_LZO
volatile static uint _u4MsdcAsyncVect = 0x00;
#endif


#define MSDC_AUTOK_PARAM_NUM 17
uint szAutoK_CmdLine[MSDC_AUTOK_PARAM_NUM] = {0};
static volatile BOOL fg_autok_apply_done = FALSE;

#ifdef CC_SUPPORT_MULTI_EMMC_SIZE
    UINT64 msdc_capacity = 0x0;
    UINT32 tmpsize1, tmpsize2;
#endif
/* the global variable related to clcok */
UINT32 msdcClk[][MSDC_CLK_IDX_MAX] = {{MSDC_CLK_TARGET},
                                      {MSDC_CLK_SRC_VAL},
                                      {MSDC_CLK_MODE_VAL},
                                      {MSDC_CLK_DIV_VAL},
                                      {MSDC_CLK_DRV_VAL}};

struct mmc *emmc_dev = NULL;




g_msdc_info  g_msdc_pub_info = {
	.RCA = MMC_DEF_RCA,
	.g_msdc_runing_autok = 0,
	.p_hs200_tuning = NULL,
	.p_hs400_tuning = NULL,
	.p_sd_tuning = NULL
	};

//-----------------------------------------------------------------------------
// Partition encypt information.
//-----------------------------------------------------------------------------
static void MsdcUbootUpdateResponse(struct mmc_cmd* PCmd, msdc_cmd* new_cmd)
{
	PCmd->response[0] = new_cmd->resp[0];//MSDC_READ32(SDC_RESP0);
	PCmd->response[1] = new_cmd->resp[1];//MSDC_READ32(SDC_RESP1);
	PCmd->response[2] = new_cmd->resp[2];//MSDC_READ32(SDC_RESP2);
	PCmd->response[3] = new_cmd->resp[3];//MSDC_READ32(SDC_RESP3);
}

void static MsdcUbootCreateCmd(struct mmc *mmc,struct mmc_cmd *old_Cmd, struct mmc_data *data, msdc_cmd* new_cmd)
{
	memset(new_cmd, 0, sizeof(msdc_cmd));
	new_cmd->parg = (void *)mmc;
	new_cmd->cmd_idx = old_Cmd->cmdidx;
	new_cmd->cmd_arg = old_Cmd->cmdarg;
	new_cmd->speed_mode = speedmode;
	new_cmd->BusWidth = mmc->bus_width;

	//setup flags
	new_cmd->flags = 0;//data mode, r/w,etc.

	//data mode
	if (PIO_DATA_MODE == (old_Cmd->flags & 0x0F))
		new_cmd->flags |= DATA_MODE_PIO;
	else if (BASIC_DMA_DATA_MODE == (old_Cmd->flags & 0x0F))
		new_cmd->flags |= DATA_MODE_BASIC_DMA;
	else if (DESC_DMA_DATA_MODE == (old_Cmd->flags & 0x0F))
		new_cmd->flags |= DATA_MODE_DESC_DMA;
	else if (ENHANCED_DMA_DATA_MODE == (old_Cmd->flags & 0x0F))
		new_cmd->flags |= DATA_MODE_ENHANCED_DMA;
	else
		new_cmd->flags |= DATA_MODE_NULL;


	//data
	if (data)
		new_cmd->flags |= CMD_WITH_DATA;

	//auto cmd12
	if (old_Cmd->flags & (0x1 << 4))
	{
		MSDC_LOG(MSG_LVL_ERR, "##== cmd auto flags 0x%x \n",old_Cmd->flags);
		new_cmd->flags |= AUTO_CMD_12_MASK;
	}


	new_cmd->host_type = (mmc->version & MMC_VERSION_MMC) ? (HOST_EMMC):(HOST_SD);//sd/mmc

	if (data)
	{
		new_cmd->pBuff = ((data->flags & MMC_DATA_READ)?(data->dest):(data->src));

		//r/w
		if (data->flags & MMC_DATA_READ)
		{
			new_cmd->flags |= CMD_READ_OPERATION;
			#if MSDC_FLOW_DEBUG
			MSDC_LOG(MSG_LVL_ERR, "read: cmd %d, buffer 0x%x ,flags 0x%x \n",new_cmd->cmd_idx,new_cmd->pBuff,new_cmd->flags);
			#endif
		}
		else
		{
			new_cmd->flags |= CMD_WRITE_OPERATION;
			#if MSDC_FLOW_DEBUG
			MSDC_LOG(MSG_LVL_ERR, "write: cmd %d, buffer 0x%x ,flags 0x%x \n",new_cmd->cmd_idx,new_cmd->pBuff,new_cmd->flags);
			#endif
		}

		new_cmd->blocks = data->blocks;
		new_cmd->block_size = data->blocksize;

		if (0 == new_cmd->blocks)
		{
			new_cmd->partial_blk_len = data->blocksize;
			if (CMD21_SEND_TUNING_BLOCK != new_cmd->cmd_idx)
			{
				MSDC_LOG(MSG_LVL_ERR, "Warning %s : the data blocks is 0 !!", __FUNCTION__);
				MSDC_LOG(MSG_LVL_ERR, "cmd idx %d, arg 0x%x,flags 0x%x ,blk size %d\n",
						new_cmd->cmd_idx,new_cmd->cmd_arg,new_cmd->flags,new_cmd->block_size);
			}

		}
		else
		{
			new_cmd->partial_blk_len = 0;
		}
	}


}



VOID MSDC_ApplyAutoK_Param(msdc_autok_t *p_env, uint u4clk)
{
    UINT32 i = 0;
	szAutoK_CmdLine[i++] = p_env->K_IOCON;
	szAutoK_CmdLine[i++] = p_env->K_PAD_TUNE0;
	szAutoK_CmdLine[i++] = p_env->K_PAD_TUNE1;
	szAutoK_CmdLine[i++] = p_env->K_PATCH_BIT0;
	szAutoK_CmdLine[i++] = p_env->K_PATCH_BIT1;
	szAutoK_CmdLine[i++] = p_env->K_PATCH_BIT2;
	szAutoK_CmdLine[i++] = p_env->K_EMMC50_CFG0;
	szAutoK_CmdLine[i++] = p_env->K_DAT_RD_DLY0;
	szAutoK_CmdLine[i++] = p_env->K_DAT_RD_DLY1;
	szAutoK_CmdLine[i++] = p_env->K_DAT_RD_DLY2;
	szAutoK_CmdLine[i++] = p_env->K_DAT_RD_DLY3;
	szAutoK_CmdLine[i++] = p_env->K_EMMC50_PAD_DS_TUNE;
	szAutoK_CmdLine[i++] = p_env->K_EMMC50_PAD_CMD_TUNE;
	szAutoK_CmdLine[i++] = p_env->K_EMMC50_PAD_DAT01_TUNE;
	szAutoK_CmdLine[i++] = p_env->K_EMMC50_PAD_DAT23_TUNE;
	szAutoK_CmdLine[i++] = p_env->K_EMMC50_PAD_DAT45_TUNE;
	szAutoK_CmdLine[i++] = p_env->K_EMMC50_PAD_DAT67_TUNE;


    if ((u4clk >= 200) &&
    	((speedmode == SPEED_MODE_HS400) || (speedmode == SPEED_MODE_HS200))
    	)
	{

		MSDC_LOG(MSG_LVL_ERR,"Uboot Apply Param to register ! \n");
		MSDC_WRITE32(MSDC_IOCON,p_env->K_IOCON);
		MSDC_WRITE32(PAD_TUNE,p_env->K_PAD_TUNE0);
		MSDC_WRITE32(PAD_TUNE1,p_env->K_PAD_TUNE1);
		MSDC_WRITE32(PATCH_BIT0,p_env->K_PATCH_BIT0);
		MSDC_WRITE32(PATCH_BIT1,p_env->K_PATCH_BIT1);
		MSDC_WRITE32(PATCH_BIT2,p_env->K_PATCH_BIT2);
		MSDC_WRITE32(EMMC50_CFG0,p_env->K_EMMC50_CFG0);

		MSDC_WRITE32(DAT_RD_DLY0,p_env->K_DAT_RD_DLY0);
		MSDC_WRITE32(DAT_RD_DLY1,p_env->K_DAT_RD_DLY1);
		MSDC_WRITE32(DAT_RD_DLY2,p_env->K_DAT_RD_DLY2);
		MSDC_WRITE32(DAT_RD_DLY3,p_env->K_DAT_RD_DLY3);

		MSDC_WRITE32(EMMC50_PAD_DS_TUNE,p_env->K_EMMC50_PAD_DS_TUNE);
		MSDC_WRITE32(EMMC50_PAD_CMD_TUNE,p_env->K_EMMC50_PAD_CMD_TUNE);
		MSDC_WRITE32(EMMC50_PAD_DAT01_TUNE,p_env->K_EMMC50_PAD_DAT01_TUNE);
		MSDC_WRITE32(EMMC50_PAD_DAT23_TUNE,p_env->K_EMMC50_PAD_DAT23_TUNE);
		MSDC_WRITE32(EMMC50_PAD_DAT45_TUNE,p_env->K_EMMC50_PAD_DAT45_TUNE);
		MSDC_WRITE32(EMMC50_PAD_DAT67_TUNE,p_env->K_EMMC50_PAD_DAT67_TUNE);
	}




	//dump
	MSDC_LOG(MSG_LVL_ERR,"Uboot Apply Param: SpeedMode: 0x%x \n",p_env->SpeedMode);
	MSDC_LOG(MSG_LVL_ERR,"K_IOCON: 0x%x \n",p_env->K_IOCON);
	MSDC_LOG(MSG_LVL_ERR,"K_PAD_TUNE0: 0x%x \n",p_env->K_PAD_TUNE0);
	MSDC_LOG(MSG_LVL_ERR,"K_PAD_TUNE1: 0x%x \n",p_env->K_PAD_TUNE1);
	MSDC_LOG(MSG_LVL_ERR,"K_PATCH_BIT0: 0x%x \n",p_env->K_PATCH_BIT0);
	MSDC_LOG(MSG_LVL_ERR,"K_PATCH_BIT1: 0x%x \n",p_env->K_PATCH_BIT1);
	MSDC_LOG(MSG_LVL_ERR,"K_PATCH_BIT2: 0x%x \n",p_env->K_PATCH_BIT2);
	MSDC_LOG(MSG_LVL_ERR,"K_EMMC50_CFG0: 0x%x \n",p_env->K_EMMC50_CFG0);
	MSDC_LOG(MSG_LVL_ERR,"K_DAT_RD_DLY0: 0x%x \n",p_env->K_DAT_RD_DLY0);
	MSDC_LOG(MSG_LVL_ERR,"K_DAT_RD_DLY1: 0x%x \n",p_env->K_DAT_RD_DLY1);
	MSDC_LOG(MSG_LVL_ERR,"K_DAT_RD_DLY2: 0x%x \n",p_env->K_DAT_RD_DLY2);
	MSDC_LOG(MSG_LVL_ERR,"K_DAT_RD_DLY3: 0x%x \n",p_env->K_DAT_RD_DLY3);
	MSDC_LOG(MSG_LVL_ERR,"K_EMMC50_PAD_DS_TUNE: 0x%x \n",p_env->K_EMMC50_PAD_DS_TUNE);
	MSDC_LOG(MSG_LVL_ERR,"K_EMMC50_PAD_CMD_TUNE: 0x%x \n",p_env->K_EMMC50_PAD_CMD_TUNE);
	MSDC_LOG(MSG_LVL_ERR,"K_EMMC50_PAD_DAT01_TUNE: 0x%x \n",p_env->K_EMMC50_PAD_DAT01_TUNE);
	MSDC_LOG(MSG_LVL_ERR,"K_EMMC50_PAD_DAT23_TUNE: 0x%x \n",p_env->K_EMMC50_PAD_DAT23_TUNE);
	MSDC_LOG(MSG_LVL_ERR,"K_EMMC50_PAD_DAT45_TUNE: 0x%x \n",p_env->K_EMMC50_PAD_DAT45_TUNE);
	MSDC_LOG(MSG_LVL_ERR,"K_EMMC50_PAD_DAT67_TUNE: 0x%x \n",p_env->K_EMMC50_PAD_DAT67_TUNE);

	fg_autok_apply_done = TRUE;

}


void uboot_print_setting()
{
    MSDC_LOG(MSG_LVL_ERR,"uboot pinmux  0x%x\n",
			(*(volatile unsigned int *)(EMMC_PAD_PINMUX))
			);
}




#ifdef CC_PARTITION_WP_SUPPORT
uint MsdcPartitionWp(uint blkindx)
{
    uint partId;
    uint size = 0, offset = 0, wp_en = 0;

    for (partId = 0; partId < MAX_MTD_DEVICES; partId++)
    {
        offset = DRVCUST_InitGet((QUERY_TYPE_T)(eNANDFlashPartOffset0 + partId));
        size = DRVCUST_InitGet((QUERY_TYPE_T)(eNANDFlashPartSize0 + partId));
        wp_en = DRVCUST_InitGet((QUERY_TYPE_T)(eNANDFlashPartWp0 + partId));

        if((wp_en != 0) &&
        	 ((uint)(offset>>9) <= blkindx) &&
        	 ((uint)((offset+size)>>9) > blkindx))
        {
        	  MSDC_LOG(MSG_LVL_INFO, " This partition is write protect!\n");
            return 1;
        }
    }

    MSDC_LOG(MSG_LVL_INFO, " This partition is not write protect!\n");
    return 0;
}

uint MsdcPartitionWpConfig(uint wp)
{
    if(wp)
    {
      wp_config = 1;
      MSDC_LOG(MSG_LVL_INFO, "partition write protect function was enabled!\n");
    }
    else
    {
      wp_config = 0;
      MSDC_LOG(MSG_LVL_INFO, "partition write protect function was disabled!\n");
    }
}

#endif





void MsdcSetSpeedMode(uint speedMode)
{
    MSDC_LOG(MSG_LVL_ERR,"uboot set speed mode to %d \n",speedMode);
    speedmode = speedMode;
	MsdcSampleEdge(speedmode);
	MsdcSmapleDelay(speedmode);
}

void MsdcClrTiming(void)
{
    // Clear Sample Edge
    MSDC_CLRBIT(MSDC_IOCON, (((UINT32)0x3) << 1));

    // Clear Pad Tune
    MSDC_WRITE32(PAD_TUNE, 0x00000000);

    // Clear
    MSDC_WRITE32(DAT_RD_DLY0, 0x00000000);
    MSDC_WRITE32(DAT_RD_DLY1, 0x00000000);

}



void MsdSetDataMode(uint datamode)
{
	  //Assert((NULL_DATA_MODE < datamode) && (datamode <= ENHANCED_DMA_DATA_MODE));

    dataMode = datamode;

    if(datamode < BASIC_DMA_DATA_MODE)
    {
        MSDC_SETBIT(MSDC_CFG, MSDC_CFG_PIO_MODE);
        MSDC_LOG(MSG_LVL_ERR, "Set PIO Mode!\r\n");
    }
    else
    {
        MSDC_CLRBIT(MSDC_CFG, MSDC_CFG_PIO_MODE);
        MSDC_LOG(MSG_LVL_ERR, "Set DMA Mode!\r\n");
    }
}

int MsdcInit (struct mmc *mmc)
{
    // Reset MSDC
    MsdcReset();

    // SD/MMC Mode
    MSDC_SETBIT(MSDC_CFG, MSDC_CFG_PIO_MODE | MSDC_CFG_SD);
	// Reset MSDC
    MsdcReset();
    MsdSetDataMode(dataMode);

    // Disable sdio & Set bus to 1 bit mode
    MSDC_CLRBIT(SDC_CFG, SDC_CFG_SDIO | SDC_CFG_BW_MASK);

    // set clock mode (DIV mode)
    MSDC_CLRBIT(MSDC_CFG, (((uint)0x03) << 20));

    // Wait until clock is stable
    if (MSDC_FAILED == MsdcWaitClkStable())
    {
        return MSDC_FAILED;
    }

    // Set default RISC_SIZE for DWRD pio mode
    MSDC_WRITE32(MSDC_IOCON, (MSDC_READ32(MSDC_IOCON) & ~MSDC_IOCON_RISC_SIZE_MASK) | MSDC_IOCON_RISC_SIZE_DWRD);

    // Set Data timeout setting => Maximum setting
    MSDC_WRITE32(SDC_CFG, (MSDC_READ32(SDC_CFG) & ~(((uint)0xFF) << SDC_CFG_DTOC_SHIFT)) | (((uint)0xFF) << SDC_CFG_DTOC_SHIFT));

    // Clear Timing to default value
    MsdcClrTiming();


    return MSDC_SUCCESS;
}

void MsdcContinueClock (int i4ContinueClock)
{
    if (i4ContinueClock)
    {
       // Set clock continuous even if no command
       MSDC_SETBIT(MSDC_CFG, (((UINT32)0x01) << 1));
    }
    else
    {
       // Set clock power down if no command
       MSDC_CLRBIT(MSDC_CFG, (((UINT32)0x01) << 1));
    }
}




void MsdcSetBuffBits(uint *pBuff, uint bits)
{
    (*pBuff) |= bits;
}

void MsdcClrBuffBits(uint *pBuff, uint bits)
{
    (*pBuff) &= (~bits);
}

int MsdcCalCheckSum(uchar *pBuf, uint len)
{
    uint i, checksum = 0;
    for(i = 0;i<len;i++)
    {
        checksum += pBuf[i];
    }

    return checksum;
}



int MsdcSendCmd (struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
{
    int i4Ret = MSDC_SUCCESS;

	msdc_cmd new_cmd;
	msdc_cmd *pNewCmd = &new_cmd;
	int u4Encrypted = 0;

	MsdcUbootCreateCmd(mmc, cmd, data, pNewCmd);

	#if defined(CC_MTD_ENCRYPT_SUPPORT) && \
		defined(MSDC_AES_SUPPORT)
	if ((MMC_CMD_READ_MULTIPLE_BLOCK == cmd->cmdidx) || \
			(MMC_CMD_READ_SINGLE_BLOCK == cmd->cmdidx)) {
		if (MsdcPartitionEncrypted(cmd->cmdarg)) {
			u4Encrypted = 1;
			Msdc_AES_Open();
			Msdc_AES_DEC_Switch();
		}
	} else if((MMC_CMD_WRITE_MULTIPLE_BLOCK == cmd->cmdidx) || \
			(MMC_CMD_WRITE_SINGLE_BLOCK == cmd->cmdidx)) {
		if (MsdcPartitionEncrypted(cmd->cmdarg)) {
			u4Encrypted = 1;
			Msdc_AES_Open();
			Msdc_AES_ENC_Switch();
		}
	}
	#endif
	i4Ret = MsdcRequest(pNewCmd);

    if ((i4Ret != MSDC_SUCCESS) && data && (i4Ret == ERR_DAT_FAILED)) {
        #if MSDC_NEW_FIFO_PATH
		MSDC_LOG(MSG_LVL_ERR, "UBOOT cmd %d fail, flags 0x%x ,%d,%d\n",
				pNewCmd->cmd_idx, pNewCmd->flags,
				(pNewCmd->flags & CMD_READ_OPERATION),
				(pNewCmd->flags & CMD_WRITE_OPERATION));
		i4Ret =  MsdcOnlineAutoKTune(FALSE, (pNewCmd->flags & CMD_READ_OPERATION),
				(pNewCmd->flags & CMD_WRITE_OPERATION),pNewCmd);
		if (MSDC_SUCCESS != i4Ret) {
			goto ERROR_END;
		}
		#else
        i4Ret = MsdcReqDataTune(pNewCmd);
        if (MSDC_SUCCESS != i4Ret) {
            goto ERROR_END;
        }
		#endif
    }

ERROR_END:
	if (u4Encrypted) {
		u4Encrypted = 0;
		Msdc_AES_Close();
	}

	MsdcUbootUpdateResponse(cmd, pNewCmd);

    return i4Ret;
}

#if !MSDC_USE_NORMAL_READ_LZO

int MsdcAsyncWaitIntr (uint vector, uint timeoutCnt, uint fgMode)
{
    uint i, retVector;

    // Clear Vector variable
    _u4MsdcAsyncVect = 0;
    retVector = INT_SD_DATA_CRCERR;

    for(i = 0; i<timeoutCnt; i++)
    {
        // Status match any bit
        if((fgMode == 0) && ((MSDC_READ32(MSDC_INT) & vector) != 0))
        {
            _u4MsdcAsyncVect |= MSDC_READ32(MSDC_INT);
            MSDC_WRITE32(MSDC_INT, _u4MsdcAsyncVect & vector);
            return MSDC_SUCCESS;
        }
        else if((fgMode == 1) && ((MSDC_READ32(MSDC_INT) & vector) == vector))
        {
            _u4MsdcAsyncVect |= MSDC_READ32(MSDC_INT);
            MSDC_WRITE32(MSDC_INT, _u4MsdcAsyncVect & vector);
            return MSDC_SUCCESS;
        }

        if((MSDC_READ32(MSDC_INT) & retVector) != 0)
        {
            // return directly
            return MSDC_FAILED;
        }

        msdc_delay(1000);
    }

    // Timeout case
    return MSDC_FAILED;
}


int MsdcDMAWaitIntr(struct mmc_cmd *cmd, uint cur_off, uint bytes, uint total_sz)
{
    uint fgAutoCmd12, IntrWaitVect = 0, IntrCheckVect = 0;

    fgAutoCmd12 = ((cmd->flags>>4)?1:0);

    if(fgAutoCmd12)
    {
        IntrWaitVect = INT_SD_XFER_COMPLETE | INT_DMA_XFER_DONE | INT_SD_AUTOCMD_RDY;
        IntrCheckVect = INT_SD_XFER_COMPLETE | INT_DMA_XFER_DONE | INT_SD_AUTOCMD_RDY;
    }
    else
    {
        IntrWaitVect = INT_SD_XFER_COMPLETE | INT_DMA_XFER_DONE;
        IntrCheckVect = INT_SD_XFER_COMPLETE | INT_DMA_XFER_DONE;
    }


    if(total_sz <= (cur_off + bytes))
    {
        // Wait for sd xfer complete
        if (MSDC_SUCCESS != MsdcAsyncWaitIntr(IntrWaitVect, MSDC_WAIT_DATA_COMPLETE_TIMEOUT_LIMIT_COUNT, 1))
        {
            MSDC_LOG(MSG_LVL_ERR, "(L)%s: Failed to send/receive data (AccuVect 0x%08X INTR 0x%08X). %s line %d\r\n", __FUNCTION__, _u4MsdcAsyncVect, MSDC_READ32(MSDC_INT), __FILE__, __LINE__);
            return MSDC_FAILED;
        }
        if ((_u4MsdcAsyncVect & ~(IntrCheckVect)) || (0 != MSDC_READ32(MSDC_INT)))
        {
            MSDC_LOG(MSG_LVL_ERR, "%s: Unexpected status (AccuVect 0x%08X INTR 0x%08X). %s line %d\r\n", __FUNCTION__, _u4MsdcAsyncVect, MSDC_READ32(MSDC_INT), __FILE__, __LINE__);
            return MSDC_FAILED;
        }

        // Check DMA status
        if (0 != (MSDC_READ32(DMA_CFG) & (DMA_CFG_DMA_STATUS)))
        {
            MSDC_LOG(MSG_LVL_ERR, "%s: Incorrect DMA status. DMA_CFG: 0x%08X\r\n", __FUNCTION__, MSDC_READ32(DMA_CFG));
            return MSDC_FAILED;
        }

        return MSDC_SUCCESS;
    }
    else
    {
        if (MSDC_SUCCESS != MsdcAsyncWaitIntr(INT_DMA_XFER_DONE, MSDC_WAIT_DATA_COMPLETE_TIMEOUT_LIMIT_COUNT, 1))
        {
            MSDC_LOG(MSG_LVL_ERR, "(N)%s: Failed to send/receive data (AccuVect 0x%08X INTR 0x%08X). %s line %d\r\n", __FUNCTION__, _u4MsdcAsyncVect, MSDC_READ32(MSDC_INT), __FILE__, __LINE__);

			uboot_print_setting();
            return MSDC_FAILED;
        }

        if ((_u4MsdcAsyncVect & ~(INT_DMA_XFER_DONE)) || (0 != MSDC_READ32(MSDC_INT)))
        {
            MSDC_LOG(MSG_LVL_ERR, "%s: Unexpected status (AccuVect 0x%08X INTR 0x%08X). %s line %d\r\n", __FUNCTION__, _u4MsdcAsyncVect, MSDC_READ32(MSDC_INT), __FILE__, __LINE__);
            return MSDC_FAILED;
        }

        return MSDC_SUCCESS;
    }
}


int MsdcDMAStart(uint cur_pos, uint cur_off, uint bytes, uint total_sz)
{
    MSDC_WRITE32(DMA_SA, cur_pos);

    if (total_sz <= cur_off + bytes)
    {
        MSDC_LOG(MSG_LVL_INFO, "->Last: AccLen = %08X\r\n", (total_sz - cur_off));
        MSDC_WRITE32(DMA_LENGTH, (total_sz - cur_off));
        MSDC_WRITE32(DMA_CTRL, (DMA_BST_64 << DMA_CTRL_BST_SHIFT) | DMA_CTRL_LAST_BUF | DMA_CTRL_START);
    }
    else
    {
	      MSDC_LOG(MSG_LVL_INFO, "->AccLen = %08X\r\n", bytes);
        MSDC_WRITE32(DMA_LENGTH, (bytes));
        MSDC_WRITE32(DMA_CTRL, (DMA_BST_64 << DMA_CTRL_BST_SHIFT) | DMA_CTRL_START);
    }

    return MSDC_SUCCESS;
}

int MsdcDMATernmial(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
{
    if(MsdcErrorHandling(mmc, cmd, data))
    {
        return MSDC_FAILED;
    }

    return MSDC_SUCCESS;
}

#endif

/* different clock needs different max driving strength
*/
void MsdcDrivingStrength(uint driving)
{
    MSDC_LOG(MSG_LVL_ERR,"MT5895 uboot Set driving[ch %d] \n",ch);
	if (ch == 1) {
		//emmc setting, 3.3V [clk=3,cmd=3,dat=3]  1.8V [clk=6,cmd=6,dat=6],we assume to use 1.8V settings
		//CLK,CMD,DAT0,DAT1
		MSDC_CLRBIT(EMMC_PAD_DRIVING_HIGH, 0xFFFFF << 12);
		MSDC_CLRBIT(EMMC_PAD_DRIVING_LOW_CFG1, 0xFFF << 18);
		MSDC_SETBIT(EMMC_PAD_DRIVING_LOW_CFG1, ((0x5 & 0x7) << 18) | ((0x5 & 0x7) << 21) |
				((0x5 & 0x7) << 24) | ((0x5 & 0x7) << 27));

		//DAT2,DAT3,DAT4,DAT5,DAT6,DAT7
		MSDC_CLRBIT(EMMC_PAD_DRIVING_LOW_CFG2, 0x3FFFF << 0);
		MSDC_SETBIT(EMMC_PAD_DRIVING_LOW_CFG2, ((0x5 & 0x7) << 0) | ((0x5 & 0x7) << 3) |
				((0x5 & 0x7) << 6) | ((0x5 & 0x7) << 9) | ((0x5 & 0x7) << 12) |
				((0x5 & 0x7) << 15));


        MSDC_CLRBIT(EMMC_PAD_R1R0, 0xfffff000);
		MSDC_SETBIT(EMMC_PAD_R1R0, 0x55556000);

        MSDC_CLRBIT(EMMC_PAD_IES, 0x0000ffc0);
		MSDC_SETBIT(EMMC_PAD_IES, 0xffc0);

        MSDC_CLRBIT(EMMC_PAD_PUPD, 0x0000ffc0);
		MSDC_SETBIT(EMMC_PAD_PUPD, 0x00000040);

        MSDC_CLRBIT(EMMC_PAD_DS, 0x000e007f);
		MSDC_SETBIT(EMMC_PAD_DS, 0x000a0065);

        MSDC_CLRBIT(EMMC_PAD_PINMUX, 0x003000f0);
		MSDC_SETBIT(EMMC_PAD_PINMUX, 0x100000);

        MSDC_CLRBIT(EMMC_RESET_PAD_PU, 0x1000000);
		MSDC_SETBIT(EMMC_RESET_PAD_PU, 0x1000000);

        MSDC_CLRBIT(EMMC_RESET_PAD_PD, 0x1000000);
		MSDC_SETBIT(EMMC_RESET_PAD_PD, 0x1000000);

        MSDC_CLRBIT(EMMC_RESET_PAD_SMT, 0x1000000);
		MSDC_SETBIT(EMMC_RESET_PAD_SMT, 0x1000000);

        MSDC_CLRBIT(EMMC_RESET_PAD_IES, 0x1000000);
		MSDC_SETBIT(EMMC_RESET_PAD_IES, 0x1000000);
	} else {
		MSDC_CLRBIT(EMMC_PAD_DRIVING_HIGH, 0x00000fff);
		MSDC_SETBIT(EMMC_PAD_DRIVING_HIGH, 0);

        MSDC_CLRBIT(EMMC_PAD_DRIVING_LOW_CFG1, 0x0003ffff);
		MSDC_SETBIT(EMMC_PAD_DRIVING_LOW_CFG1, 0x00024924);

        MSDC_CLRBIT(EMMC_PAD_R1R0, 0x00000fff);
		MSDC_SETBIT(EMMC_PAD_R1R0, 0x00000556);

        MSDC_CLRBIT(EMMC_PAD_PUPD, 0x0000003f);
		MSDC_SETBIT(EMMC_PAD_PUPD, 0x1);

        MSDC_CLRBIT(SD_PAD_PINMUX, 0x1c000000);
		MSDC_SETBIT(SD_PAD_PINMUX, 0x04000000);
	}


}

int MsdcSetClkfreq(uint clkFreq)
{
    uint idx = 0, ddr = 0;

	clkFreq /= (1000000);
    ddr = (speedmode == SPEED_MODE_DDR50)?1:0;

	MSDC_LOG(MSG_LVL_ERR, "uboot set clock: %d, ch %d \n", clkFreq,ch);

	//Set src clock to 400MHZ,00[22]
	if (400 == clkFreq)
	{
		MSDC_CLRBIT(MSDC_CFG, MSDC_CFG_HS400_CK_MODE);
		MSDC_SETBIT(MSDC_CFG, (0x1 << MSDC_CFG_HS400_CK_MODE_SHIFT));
	}

	do
    {
        if((clkFreq < msdcClk[0][idx]) ||
		   (ddr && (msdcClk[2][idx] != 2)))
		    continue;
		else
			break;

	}while(++idx < MSDC_CLK_IDX_MAX);

	idx = (idx >= MSDC_CLK_IDX_MAX)? (MSDC_CLK_IDX_MAX - 1 ) : idx;

	// Enable msdc_src_clk gate
    if (ch == 1)
        MSDC_SETBIT(MSDC_CLK_S_REG1, MSDC_CLK_GATE_BIT);
    else
        MSDC_SETBIT(MSDC_CLK_S_REG0, MSDC_CLK_GATE_BIT);

    // Set clock source value
	if (ch == 1)
	{
        // Clr msdc_src_clk selection
        MSDC_CLRBIT(MSDC_CLK_S_REG1, MSDC_CLK_SEL_MASK);
        #if  defined(CC_EMMC_DDR50)
        MSDC_SETBIT(MSDC_CLK_S_REG1, 0x8<<0);
        #endif
        MSDC_SETBIT(MSDC_CLK_S_REG1, msdcClk[1][idx]<<0);
    }
    else
    {
        // Clr msdc_src_clk selection
        MSDC_CLRBIT(MSDC_CLK_S_REG0, MSDC_CLK_SEL_MASK);
        MSDC_SETBIT(MSDC_CLK_S_REG0, msdcClk[1][idx]<<0);
    }

	// Set clock mode value
	MSDC_CLRBIT(MSDC_CFG, (((UINT32)0x03) << 20));
    MSDC_SETBIT(MSDC_CFG, ((msdcClk[2][idx]) << 20));

    // Set clock divide value
    MSDC_CLRBIT(MSDC_CFG, (((UINT32)0xFFF) << 8));
    MSDC_SETBIT(MSDC_CFG, ((msdcClk[3][idx]) << 8));


    // Disable msdc_src_clk gate
    if (ch == 1)
        MSDC_CLRBIT(MSDC_CLK_S_REG1, MSDC_CLK_GATE_BIT);
    else
        MSDC_CLRBIT(MSDC_CLK_S_REG0, MSDC_CLK_GATE_BIT);


    // Wait until clock is stable
    if (MSDC_FAILED == MsdcWaitClkStable())
    {
        MSDC_LOG(MSG_LVL_ERR, "(%s)Set Bus Clock as %d(MHz) Failed!\n", ddr?"DDR":"SDR", msdcClk[0][idx]);
        return MSDC_FAILED;
    }

	MSDC_LOG(MSG_LVL_ERR, "uboot id %d: src %d: mode %d  div %d  driving %d\n", idx, msdcClk[1][idx], msdcClk[2][idx],
		                                                   msdcClk[3][idx], msdcClk[4][idx]);
    MSDC_LOG(MSG_LVL_ERR, "(%s)Set Bus Clock as %d(MHz) Success!\n", ddr?"DDR":"SDR", msdcClk[0][idx]);

    /*loader already set it. not need here*/
    //MsdcDrivingStrength(msdcClk[4][idx]);


	//Special for HS200/HS400,CLK,CMD,DAT
	if ((400 == clkFreq)|| (200 == clkFreq))
	{
		MsdcDrvSet(6,6,6);//To cover coner IC,DATA[6] for HS400 corner IC, HS200 can use DATA[5]
	}

	if (clkFreq >= 50)
	{
	    LDR_ENV_T *prLdrEnv = (LDR_ENV_T *)CC_LDR_ENV_OFFSET;
	    msdc_autok_t msdcenv;



#if defined(CC_KERNEL_HS200_SUPPORT) || defined(CC_KERNEL_HS400_SUPPORT)
		{
			if(sizeof(prLdrEnv->szMSDCAutoK) != sizeof(msdc_autok_t))
			{
				MSDC_LOG(MSG_LVL_ERR,"Loade the autok param fail! %d != %d\n", sizeof(prLdrEnv->szMSDCAutoK), sizeof(msdc_autok_t));
				BUG();
			}
			memcpy((void *)(&msdcenv), (void *)(prLdrEnv->szMSDCAutoK), sizeof(msdc_autok_t));
			MSDC_ApplyAutoK_Param(&msdcenv,clkFreq);
		}
#endif

	}

    return MSDC_SUCCESS;
}

int MsdcSetBusWidth (int busWidth)
{
    /* Modify MSDC Register Settings */
    if (0 == busWidth)
    {
        MSDC_WRITE32(SDC_CFG, (MSDC_READ32(SDC_CFG) & ~SDC_CFG_BW_MASK) | (0x00 <<  SDC_CFG_BW_SHIFT));
    }
    else if ((1 == busWidth) ||
		     (5 == busWidth))
    {
        MSDC_WRITE32(SDC_CFG, (MSDC_READ32(SDC_CFG) & ~SDC_CFG_BW_MASK) | (0x01 <<  SDC_CFG_BW_SHIFT));
    }
    else if ((2 == busWidth) ||
		     (6 == busWidth))
    {
        MSDC_WRITE32(SDC_CFG, (MSDC_READ32(SDC_CFG) & ~SDC_CFG_BW_MASK) | (0x02 <<  SDC_CFG_BW_SHIFT));
    }
	else
	{
        MSDC_LOG(MSG_LVL_INFO, "Invalid buswidth value: %d!\n", busWidth);
	}

    return MSDC_SUCCESS;
}


void MsdcSetIos(struct mmc *mmc)
{
    uint clock = mmc->clock;
    uint busWidth = mmc->bus_width;

    MsdcSetBusWidth(busWidth);
    MsdcSetClkfreq(0);
    MsdcSetClkfreq(clock);
}

void MSDC_PinMux(uint u4Ch)
{
    ch = u4Ch;

//MSDC Controller 0, Basic Address: 0xF0012000
//MSDC Controller 1, Basic Address: 0xF006D000

}

void mtk_mmc_init(void)
{
    emmc_dev = (struct mmc*)malloc(sizeof(struct mmc));
	if (NULL == emmc_dev)
	{
		MSDC_LOG(MSG_LVL_ERR,"uboot mmc alloc fail \n");
		return;
	}

	fg_autok_apply_done = FALSE;


    //Initial some structure element
    //sprintf(emmc_dev->name, "%s", "emmc0");
    emmc_dev->voltages = (MMC_VDD_30_31|MMC_VDD_31_32|MMC_VDD_32_33|MMC_VDD_33_34);
    emmc_dev->f_min = 397000;

#if defined(CC_EMMC_HS400)
    emmc_dev->f_max = 400000000;
    emmc_dev->host_caps = (MMC_MODE_HS|MMC_MODE_HS_52MHz|MMC_MODE_8BIT|MMC_MODE_DDR_18V|MMC_MODE_HS400_MASK|MMC_MODE_DDR_MASK);
	speedmode = SPEED_MODE_HS400;
#elif defined(CC_EMMC_HS200)
    emmc_dev->f_max = 200000000;
    emmc_dev->host_caps = (MMC_MODE_HS|MMC_MODE_HS_52MHz|MMC_MODE_8BIT|MMC_MODE_DDR_18V|MMC_MODE_HS200_18V);
	speedmode = SPEED_MODE_HS200;
#elif  defined(CC_EMMC_DDR50)
    emmc_dev->f_max = 50000000;
    emmc_dev->host_caps = (MMC_MODE_HS|MMC_MODE_HS_52MHz|MMC_MODE_8BIT|MMC_MODE_DDR_18V);
	MSDC_LOG(MSG_LVL_ERR,"Set max clk to 200MHZ in DDR50 mode \n");
#else
    emmc_dev->f_max = 52000000;
    emmc_dev->host_caps = (MMC_MODE_HS|MMC_MODE_HS_52MHz|MMC_MODE_8BIT);
	MSDC_LOG(MSG_LVL_ERR,"Set max clk to 50MHZ in default \n");
#endif

    emmc_dev->send_cmd = MsdcSendCmd;
    emmc_dev->set_ios = MsdcSetIos;
    emmc_dev->init = MsdcInit;
    mmc_register(emmc_dev);

	MSDC_LOG(MSG_LVL_ERR,"Uboot max clock %d \n",emmc_dev->f_max);

	MsdcInfoInit();

#ifdef CC_MTD_ENCRYPT_SUPPORT
    // aes init
    MSDCEXT_EncryptInit(128);
#endif
#ifdef CC_PARTITION_WP_SUPPORT
    // aes init
    MsdcPartitionWpConfig(1);
#endif
#if (MSDC_EMMC_INIT_UNIFY_EN == 1)
    LDR_ENV_T *prLdrEnv = (LDR_ENV_T *)CC_LDR_ENV_OFFSET;
    msdc_env_t msdcenv;

    if(sizeof(prLdrEnv->szMSDCenv1) != sizeof(msdc_env_t))
    {
        MSDC_LOG(MSG_LVL_ERR,"%d != %d\n", sizeof(prLdrEnv->szMSDCenv1), sizeof(msdc_env_t));
        BUG();
    }

	x_memset(szAutoK_CmdLine, 0, sizeof(szAutoK_CmdLine));
    memcpy((void *)(&msdcenv), (void *)(prLdrEnv->szMSDCenv1), sizeof(msdc_env_t));
    //memset(EXT_CSD, 0x00, 512);
    memcpy((void *)(&emmc_dev->ocr), (void *)(msdcenv.ocr), sizeof(uint));
    memcpy((void *)(&emmc_dev->rca), (void *)(msdcenv.rca), sizeof(ushort));
    memcpy((void *)(emmc_dev->cid), (void *)(msdcenv.cid), 4*sizeof(uint));
    memcpy((void *)(emmc_dev->csd), (void *)(msdcenv.csd), 4*sizeof(uint));
    //EXT_CSD[185] = msdcenv.ext_csd_185;
    //EXT_CSD[192] = msdcenv.ext_csd_192;
    //EXT_CSD[196] = msdcenv.ext_csd_196;
    //EXT_CSD[212] = msdcenv.ext_csd_212;
    //EXT_CSD[213] = msdcenv.ext_csd_213;
    //EXT_CSD[214] = msdcenv.ext_csd_214;
    //EXT_CSD[215] = msdcenv.ext_csd_215;
    emmc_dev->clock = msdcClk[0][MSDC_NORM_CLK_IDX]*1000000;
    //MsdcClrTiming();

    if(msdcenv.fgHost != 0)
    {
        MSDC_LOG(MSG_LVL_ERR,"HOST 1, rca 0x%x \n",emmc_dev->rca);
        emmc_dev->host_caps |= MMC_MODE_8BIT;
        emmc_dev->bus_width = 8;
    }
    else
    {
        MSDC_LOG(MSG_LVL_ERR,"HOST 0\n");
        emmc_dev->host_caps |= MMC_MODE_4BIT;
        emmc_dev->bus_width = 4;
    }

    //MSDC_PinMux(msdcenv.fgHost);
    MsdSetDataMode(dataMode);

	printf("----------speed mode---%d \n",speedmode);
	/*if loader open hs200/hs400, once enter into uboot, it's still running at hs200/hs400 timing, so not change any regs*/
	if (emmc_dev->f_max < 200000000)
	{
		MsdcFifoPathSelAndInit();
	}


    if (mmc_init_fast(emmc_dev))
    {
        MSDC_LOG(MSG_LVL_ERR, "MMC init failed\n");
        //BUG();
    }
#else
    emmc_dev->host_caps |= MMC_MODE_8BIT;
    //emmc_dev->host_caps |= MMC_MODE_4BIT;

    MSDC_PinMux(1);
    //MSDC_PinMux(0);
    if (mmc_init(emmc_dev))
    {
        MSDC_LOG(MSG_LVL_ERR, "MMC init failed\n");
       // BUG();
    }
#endif
#ifdef CC_SUPPORT_MULTI_EMMC_SIZE
        msdc_capacity = emmc_dev->capacity;
        tmpsize1 = (msdc_capacity & 0xffff0000)>>32;
        tmpsize2 = msdc_capacity & 0x0000ffff;
        MSDC_LOG(MSG_LVL_ERR,"[MSDC_MT5399_MMC] emmc capacity high: 0x%X, low: 0x%X~\n",tmpsize1, tmpsize2);
#endif


}

int board_mmc_init(bd_t *bis)
{
    mtk_mmc_init();
    return 0;
}

int cpu_mmc_init(bd_t *bis)
{
    return 0;
}

#endif // CC_EMMC_BOOT

