/*
 *  driver/mmc/nvt_ivot_mmc.c
 *
 *  Author:	Howard Chang
 *  Created:	Feb 01, 2019
 *  Copyright:	Novatek Inc.
 *
 */

#include <asm/io.h>
#include "common.h"
#include <errno.h>
#include <dm.h>
#include <command.h>
#include <mmc.h>
#include <malloc.h>
#include <asm/gpio.h>
#include "nvt_ivot_mmc.h"
#include <asm/arch/hardware.h>

#define NVT_MMC_DRIVER_VERSION  "1"

#ifdef CONFIG_TARGET_NA51055
#include <asm/arch/na51055_regs.h>
#else
#ifdef CONFIG_TARGET_NA51089
#include <asm/arch/na51089_regs.h>
#else
#ifdef CONFIG_TARGET_NS02301
#include <asm/arch/ns02301_regs.h>
#else
#if (defined(CONFIG_TARGET_NA51090) || defined(CONFIG_TARGET_NA51090_A64))
#include <asm/arch/na51090_regs.h>
#elif (defined(CONFIG_TARGET_NA51102) || defined(CONFIG_TARGET_NA51102_A64))
#include <asm/arch/na51102_regs.h>
#elif (defined(CONFIG_TARGET_NA51103) || defined(CONFIG_TARGET_NA51103_A64))
#include <asm/arch/na51103_regs.h>
#else
#include <asm/arch/na51000_regs.h>
#endif
#endif
#endif
#endif

#include <asm/nvt-common/nvt_types.h>
#include <asm/nvt-common/nvt_common.h>
#include <linux/libfdt.h>
//#define __MMC_DEBUG
//668 emmc HAL APIs
//#define FPGA
#define mmc_resp_type (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC|MMC_RSP_BUSY|MMC_RSP_OPCODE)
#if (defined(CONFIG_TARGET_NA51000) || defined(CONFIG_TARGET_NA51090) || defined(CONFIG_TARGET_NA51090_A64) || defined(CONFIG_TARGET_NA51102) || defined(CONFIG_TARGET_NA51102_A64) || defined(CONFIG_TARGET_NA51103) || defined(CONFIG_TARGET_NA51103_A64))
#define PLL_SYS_CR_REG_OFS	0xF0020040
#else
#define PLL_SYS_CR_REG_OFS	0xF002003C
#endif

#if (defined(CONFIG_TARGET_NA51090) || defined(CONFIG_TARGET_NA51090_A64) || defined(CONFIG_TARGET_NA51102) || defined(CONFIG_TARGET_NA51102_A64) || defined(CONFIG_TARGET_NA51103) || defined(CONFIG_TARGET_NA51103_A64))
#define SDIO_MASK		0x7FF
#define SDIO2_MASK		0x7FF0000
#else
#define SDIO_MASK		0x7FF
#define SDIO2_MASK		0x7FF0000
#endif

#ifdef CONFIG_NVT_FPGA_EMULATION
#define FPGA_SDIO_SRCCLK	12000000
#else
#define FPGA_SDIO_SRCCLK	192000000
#endif

int mmc_nvt_start_command(struct mmc_nvt_host *host);

#ifdef CONFIG_DM_MMC
/* Novatek IVOT MMC board definitions */
struct nvt_mmc_plat
{
	struct mmc_config cfg;
	struct mmc mmc;
};
#endif

#if (SDIO_OLD_DRIVING_FLOW == 1)
static u32 default_pad_driving[SDIO_MAX_MODE_DRIVING] = {20, 15, 15, 20, 15, 15, 30, 25, 25, 40, 30, 30};
#else
static u32 default_pad_driving[SDIO_MAX_MODE_DRIVING] = {2, 1, 1, 2, 1, 1, 3, 2, 2, 3, 2, 2};
#endif

static void mmc_nvt_parse_driving(struct mmc_nvt_host *host)
{
	/* Enable it after dts parsing ready*/
	int nodeoffset;
	u32 *cell = NULL;
	char path[20] = {0};
	int i;
	int len;

	sprintf(path,"mmc%d", host->id);

	nodeoffset = fdt_path_offset((const void*)nvt_fdt_buffer, path);

	cell = (u32*)fdt_getprop((const void*)nvt_fdt_buffer, nodeoffset, "max-frequency", NULL);

	if (cell > 0) {
		host->mmc_default_clk = __be32_to_cpu(cell[0]);
	}

	cell = (u32*)fdt_getprop((const void*)nvt_fdt_buffer, nodeoffset, "driving", NULL);

	if (cell < 0) {
		for (i = 0; i < SDIO_MAX_MODE_DRIVING; i++) {
			host->pad_driving[i] = default_pad_driving[i];
		}
		printf("\n%s: use default driving table\n", __func__);
	} else {
		for (i = 0; i < SDIO_MAX_MODE_DRIVING; i++) {
			host->pad_driving[i] = __be32_to_cpu(cell[i]);
		}
	}

	cell = (u32*)fdt_getprop((const void*)nvt_fdt_buffer, nodeoffset, "mmc-hs200-1_8v", NULL);
	if (cell > 0)
		host->ext_caps |= MMC_MODE_HS200;

	cell = (u32*)fdt_getprop((const void*)nvt_fdt_buffer, nodeoffset, "indly_sel", NULL);
	if (cell > 0) {
		host->indly_sel = __be32_to_cpu(cell[0]);
	} else {
		host->indly_sel = -1;
	}

	cell = (u32*)fdt_getprop((const void*)nvt_fdt_buffer, nodeoffset, "neg-sample-edge", NULL);
	if (cell > 0) {
		host->neg_sample_edge = __be32_to_cpu(cell[0]);
	} else {
		host->neg_sample_edge = -1;
	}

	if (host->id == SDIO_HOST_ID_3) {
		sprintf(path,"/top@%x/sdio3",IOADDR_TOP_REG_BASE);
		nodeoffset = fdt_path_offset((const void*)nvt_fdt_buffer, path);
		cell = (u32*)fdt_getprop((const void*)nvt_fdt_buffer, nodeoffset, "pinmux", NULL);
		host->pinmux_value = __be32_to_cpu(cell[0]);

		if (host->pinmux_value & PIN_SDIO_CFG_8BITS)
			host->enable_8bits = 1;
		else
			host->enable_8bits = 0;
	}

	/* Config SDIO pinmux*/
	do {
		host->pinmux_value = 0x1; //Default value
		nodeoffset = fdt_path_offset((const void*)nvt_fdt_buffer, "/top/sdio");
		if (nodeoffset < 0) {
			printf("%s(%d) nodeoffset < 0\n",__func__, __LINE__);
			printf("%s: path %s not found\n", __func__, "/top/sdio");
			break;
		}

		cell = (u32*)fdt_getprop((const void*)nvt_fdt_buffer, nodeoffset, "pinmux", &len);
		if (len == 0) {
			printf("%s(%d) len = 0\n",__func__, __LINE__);
			break;
		}
		host->pinmux_value = __be32_to_cpu(cell[0]);
		printf("%s: sdio pinmux 0x%x\r\n", __func__, host->pinmux_value);
	} while (0);

#ifdef CONFIG_TARGET_NA51089
	if (host->id == SDIO_HOST_ID_2) {
		sprintf(path,"/top@%x/sdio2",IOADDR_TOP_REG_BASE);
		nodeoffset = fdt_path_offset((const void*)nvt_fdt_buffer, path);
		cell = (u32*)fdt_getprop((const void*)nvt_fdt_buffer, nodeoffset, "pinmux", NULL);
		host->pinmux_value = __be32_to_cpu(cell[0]);
	}
#endif
}

//=============================================================================================================

/*
	Get SDIO controller register.

	@param[in] host			host data structure
	@param[in] offset       register offset in SDIO controller (word alignment)

	@return register value
*/
static REGVALUE sdiohost_getreg(struct mmc_nvt_host *host, u32 offset)
{
	return readl(host->base + offset);
}

/*
	Set SDIO controller register.

	@param[in] host		host data structure
	@param[in] offset   offset in SDIO controller (word alignment)
	@param[in] value    register value

	@return void
*/
static void sdiohost_setreg(struct mmc_nvt_host *host, u32 offset, REGVALUE value)
{
	writel(value, host->base + offset);
}

static void sdiohost_setblkfifoen(struct mmc_nvt_host *host, bool enable)
{
	union SDIO_PHY_REG phyreg;

	phyreg.reg = sdiohost_getreg(host, SDIO_PHY_REG_OFS);
	phyreg.bit.BLK_FIFO_EN = enable;
	sdiohost_setreg(host, SDIO_PHY_REG_OFS, phyreg.reg);
}

#define RESET_TIMEOUT	10000
static void sdiohost_setphyrst(struct mmc_nvt_host *host)
{
	union SDIO_PHY_REG phyreg, phyreg_read;
	u8 i;

	phyreg.reg = sdiohost_getreg(host, SDIO_PHY_REG_OFS);
	phyreg.bit.PHY_SW_RST = 1;
	sdiohost_setreg(host, SDIO_PHY_REG_OFS, phyreg.reg);
	while (1)
	{
		phyreg_read.reg = sdiohost_getreg(host, SDIO_PHY_REG_OFS);
		if ((phyreg_read.bit.PHY_SW_RST == 0) || (i == RESET_TIMEOUT))
			break;

		i++;
	}

	if (i == RESET_TIMEOUT)
		printf("phy reset timeout\n");
}

static void sdiohost_setphysample(struct mmc_nvt_host *host, BOOL internal, BOOL before)
{
	union SDIO_DLY0_REG dly0_reg;

	dly0_reg.reg = sdiohost_getreg(host, SDIO_DLY0_REG_OFS);
	if (host->neg_sample_edge >= 0) {
		dly0_reg.bit.SAMPLE_CLK_EDGE = host->neg_sample_edge;
	}
	dly0_reg.bit.SRC_CLK_SEL = internal;    // 0: from pad, 1: from internal
	dly0_reg.bit.PAD_CLK_SEL = before;      // 0: after pad, 1: before pad

	sdiohost_setreg(host, SDIO_DLY0_REG_OFS, dly0_reg.reg);
}


static void sdiohost_setinvoutdly(struct mmc_nvt_host *host, BOOL data_outdly_inv, BOOL cmd_outdly_inv)
{
	union SDIO_DLY5_REG dly5_reg;

	dly5_reg.reg = sdiohost_getreg(host, SDIO_DLY5_REG_OFS);
	dly5_reg.bit.DATA_OUT_DLY_INV = data_outdly_inv;
	dly5_reg.bit.CMD_OUT_DLY_INV = cmd_outdly_inv;

	sdiohost_setreg(host, SDIO_DLY5_REG_OFS, dly5_reg.reg);
}

#if 0
static void sdiohost_setphyclkoutdly(struct mmc_nvt_host *host, u32 dly_setting)
{
	union SDIO_CLOCK_CTRL2_REG clk_crtl_reg;

	clk_crtl_reg.reg = sdiohost_getreg(host, SDIO_CLOCK_CTRL2_REG_OFS);

	clk_crtl_reg.bit.OUTDLY_SEL = dly_setting;

	sdiohost_setreg(host, SDIO_CLOCK_CTRL2_REG_OFS, clk_crtl_reg.reg);
}
#endif

static void sdiohost_setphyclkindly(struct mmc_nvt_host *host, u32 dly_setting)
{
	union SDIO_CLOCK_CTRL2_REG clk_crtl_reg;

	clk_crtl_reg.reg = sdiohost_getreg(host, SDIO_CLOCK_CTRL2_REG_OFS);

	clk_crtl_reg.bit.INDLY_SEL = dly_setting;

	sdiohost_setreg(host, SDIO_CLOCK_CTRL2_REG_OFS, clk_crtl_reg.reg);
}

/*
	Set SDIO bus width.

	@param[in] host		host data structure
	@param[in] Width    SDIO controller bus width
			- @b SDIO_BUS_WIDTH1: 1 bit data bus
			- @b SDIO_BUS_WIDTH4: 4 bits data bus
			- @b SDIO_BUS_WIDTH8: 8 bits data bus

	@return void
*/
void sdiohost_setbuswidth(struct mmc_nvt_host *host, u32 width)
{
	union SDIO_BUS_WIDTH_REG widthreg;

	widthreg.reg = 0;
	widthreg.bit.BUS_WIDTH = width;
	sdiohost_setreg(host, SDIO_BUS_WIDTH_REG_OFS, widthreg.reg);
}

/*
	Set SDIO clock enable or disable

	When set to TRUE, SD controller will output SD clock to SD card.
	When set to FALSE, SD controller will not output SD clock to SD card.

	@param[in] host data structure
	@param[in] enableflag   enable clock output
				- @b TRUE: enable SD clock output
				- @b FALSE: disable SD clock output

	@return void
*/
void sdiohost_enclkout(struct mmc_nvt_host *host, BOOL enableflag)
{
	union SDIO_CLOCK_CTRL_REG clkctrlreg;

	clkctrlreg.reg = sdiohost_getreg(host, SDIO_CLOCK_CTRL_REG_OFS);

	if (enableflag == TRUE) {
		/* enabke SDIO CLK */
		clkctrlreg.bit.CLK_DIS = 0;
	} else {
		/* disable SDIO CLK */
		clkctrlreg.bit.CLK_DIS = 1;
	}

	sdiohost_setreg(host, SDIO_CLOCK_CTRL_REG_OFS, clkctrlreg.reg);
}

/*
	Set SDIO CLK card type.

	@param[in] host data structure
	@param[in] brisingsample    SDIO controller input sampling timing
			- @b TRUE: sample at rising edge
			- @b FALSE :sample at falling edge

	@return void
*/
void sdiohost_setclktype(struct mmc_nvt_host *host, BOOL brisingsample)
{
	union SDIO_CLOCK_CTRL_REG clkctrlreg;

	clkctrlreg.reg = sdiohost_getreg(host, SDIO_CLOCK_CTRL_REG_OFS);

	if (brisingsample)
		clkctrlreg.bit.CLK_SD = 1;
	else
		clkctrlreg.bit.CLK_SD = 0;

	sdiohost_setreg(host, SDIO_CLOCK_CTRL_REG_OFS, clkctrlreg.reg);
}

/*
	Set SDIO CLK cmd type.

	@param[in] host data sturcture
	@param[in] brisingsample    SDIO controller input sampling timing
			- @b TRUE: sample at rising edge
			- @b FALSE :sample at falling edge

	@return void
*/
void sdiohost_setclkcmdtype(struct mmc_nvt_host *host, BOOL brisingsample)
{
	union SDIO_CLOCK_CTRL_REG clkctrlreg;

	clkctrlreg.reg = sdiohost_getreg(host, SDIO_CLOCK_CTRL_REG_OFS);

	if (brisingsample)
		clkctrlreg.bit.CLK_SD_CMD = 1;
	else
		clkctrlreg.bit.CLK_SD_CMD = 0;

	sdiohost_setreg(host, SDIO_CLOCK_CTRL_REG_OFS, clkctrlreg.reg);
}

/**
    Get module clock rate

    Get module clock rate, one module at a time.

    @param[in] num      Module ID(PLL_CLKSEL_*), one module at a time.
                          Please refer to pll.h

    @return Moudle clock rate(PLL_CLKSEL_*_*), please refer to pll.h
*/
u32 pll_get_sdioclock_rate(int id)
{
	REGVALUE    regdata;

	if (id != SDIO_HOST_ID_3)
		HAL_READ_UINT32(PLL_SYS_CR_REG_OFS, regdata);
	else
		HAL_READ_UINT32(PLL_SYS_CR_REG_OFS + 0x4, regdata);

	if (id != SDIO_HOST_ID_2)
		regdata &= SDIO_MASK;
	else
		regdata &= SDIO2_MASK;

	return (u32)regdata;
}

/**
    Set module clock rate

    Set module clock rate, one module at a time.

	@param[in] id		SDIO channel
    @param[in] num      Module ID(PLL_CLKSEL_*), one module at a time.
                          Please refer to pll.h
    @param[in] value    Moudle clock rate(PLL_CLKSEL_*_*), please refer to pll.h

    @return void
*/
void pll_set_sdioclock_rate(int id, u32 value)
{
	REGVALUE regdata;

	if (id != SDIO_HOST_ID_3)
		HAL_READ_UINT32(PLL_SYS_CR_REG_OFS, regdata);
	else
		HAL_READ_UINT32(PLL_SYS_CR_REG_OFS + 0x4, regdata);

	if (id != SDIO_HOST_ID_2) {
		regdata &= ~SDIO_MASK;
		regdata |= value;
	} else {
		regdata &= ~SDIO2_MASK;
		regdata |= value << 16;
	}

	if (id != SDIO_HOST_ID_3)
		HAL_WRITE_UINT32(PLL_SYS_CR_REG_OFS, regdata);
	else
		HAL_WRITE_UINT32(PLL_SYS_CR_REG_OFS + 0x4, regdata);
}

/*
	Set bus clock.

	@param[in] id		SDIO channel
	@param[in] uiclock  SD bus clock in Hz

	@return void
*/
void nvt_clk_set_rate(int id, u32 uiclock)
{
	u32 divider, src_clk = FPGA_SDIO_SRCCLK;

	divider = (src_clk + uiclock-1)/uiclock;
	if (!divider)
		divider = 1;

	pll_set_sdioclock_rate(id, divider-1);
}

/*
	Set SDIO bus clock.

	@param[in] host data structure
	@param[in] uiclock  SD bus clock in Hz

	@return void
*/
void sdiohost_setbusclk(struct mmc_nvt_host *host, u32 uiclock, u32 *ns)
{
	union SDIO_CLOCK_CTRL_REG clkctrlreg;

	/* Disable SDIO clk */
	sdiohost_enclkout(host, FALSE);

	if (uiclock == 0) {
		return;
	}

	nvt_clk_set_rate(host->id, uiclock);

	if (ns)
		*ns = (1000000) / (uiclock/1000);

	/* Enable SDIO clk */
	sdiohost_enclkout(host, TRUE);

	clkctrlreg.reg = sdiohost_getreg(host, SDIO_CLOCK_CTRL_REG_OFS);

	clkctrlreg.bit.DLY_ACT = 1;

	sdiohost_setreg(host, SDIO_CLOCK_CTRL_REG_OFS, clkctrlreg.reg);

	sdiohost_setclktype(host, TRUE);
	sdiohost_setclkcmdtype(host, TRUE);
}

/*
	Get SDIO bus clock.

	@return unit of Hz
*/
static u32 sdiohost_getbusclk(int id)
{
	u32 uisourceclock;
	u32 uiclockdivider;

	uisourceclock = FPGA_SDIO_SRCCLK;

	uiclockdivider = pll_get_sdioclock_rate(id);

	return uisourceclock / (uiclockdivider + 1);
}

/*
	Get SDIO Busy or not

	@return TRUE: ready
		FALSE: busy
*/
BOOL sdiohost_getrdy(struct mmc_nvt_host *host)
{
	union SDIO_BUS_STATUS_REG stsreg;

	stsreg.reg = sdiohost_getreg(host, SDIO_BUS_STATUS_REG_OFS);

	return stsreg.bit.CARD_BUSY;
}

/*
	Reset SDIO host controller.

	@return void
*/
void sdiohost_reset(struct mmc_nvt_host *host)
{
	union SDIO_CMD_REG cmdreg;

	cmdreg.reg = sdiohost_getreg(host, SDIO_CMD_REG_OFS);
	cmdreg.bit.SDC_RST = 1;
	sdiohost_setreg(host, SDIO_CMD_REG_OFS, cmdreg.reg);

	while (1) {
		cmdreg.reg = sdiohost_getreg(host, SDIO_CMD_REG_OFS);

		if (cmdreg.bit.SDC_RST == 0)
			break;
	}

	if (host->mmc_input_clk > 1000000)
		udelay(1);
	else
		udelay((1000000/ host->mmc_input_clk) + 1);
}

/*
	Reset SDIO controller data state machine.

	@return void
*/
void sdiohost_resetdata(struct mmc_nvt_host *host)
{
	union SDIO_DATA_CTRL_REG    datactrlreg;
	union SDIO_FIFO_CONTROL_REG fifoctrlreg;
	union SDIO_DLY1_REG dlyreg;
	union SDIO_PHY_REG  phyreg;
	union SDIO_PHY_REG  phyreg_read;
	union SDIO_FIFO_SWITCH_REG  fifoswitch;

	/* //#NT#Fix SDIO data state machine abnormal when DATA CRC/Timeout
	occurs before FIFO count complete */
	union SDIO_STATUS_REG	stsreg;

	/* SDIO bug: force to clear data end status to exit
	waiting data end state*/
	stsreg.reg          = 0;
	stsreg.bit.DATA_END = 1;
	sdiohost_setreg(host, SDIO_STATUS_REG_OFS, stsreg.reg);


	fifoctrlreg.reg = 0;
	sdiohost_setreg(host, SDIO_FIFO_CONTROL_REG_OFS, fifoctrlreg.reg);

	while (1) {
		fifoctrlreg.reg = sdiohost_getreg(host, SDIO_FIFO_CONTROL_REG_OFS);
		if (fifoctrlreg.bit.FIFO_EN == 0)
			break;
	}

	datactrlreg.reg = sdiohost_getreg(host, SDIO_DATA_CTRL_REG_OFS);
	datactrlreg.bit.DATA_EN = 0;
	sdiohost_setreg(host, SDIO_DATA_CTRL_REG_OFS, datactrlreg.reg);

	/* Fix SDIO data state machine abnormal when DATA
	CRC/Timeout occurs before FIFO count complete */
	/* Do software reset to reset SD state machine */
	sdiohost_reset(host);

	/* patch begin for sd write hang-up or write byte access error */
	fifoswitch.reg = sdiohost_getreg(host, SDIO_FIFO_SWITCH_REG_OFS);
	fifoswitch.bit.FIFO_SWITCH_DLY = 1;
	sdiohost_setreg(host, SDIO_FIFO_SWITCH_REG_OFS, fifoswitch.reg);
	/* patch end for sd write hang-up or write byte access error */

	phyreg.reg = sdiohost_getreg(host, SDIO_PHY_REG_OFS);
	phyreg.bit.PHY_SW_RST = 1;
	sdiohost_setreg(host, SDIO_PHY_REG_OFS, phyreg.reg);
	while (1)
	{
		phyreg_read.reg = sdiohost_getreg(host, SDIO_PHY_REG_OFS);
		if (phyreg_read.bit.PHY_SW_RST == 0)
			break;
	}

	dlyreg.reg = sdiohost_getreg(host, SDIO_DLY1_REG_OFS);
	dlyreg.bit.DATA_READ_DLY = 2;
	dlyreg.bit.DET_READ_DLY = 2;
	sdiohost_setreg(host, SDIO_DLY1_REG_OFS, dlyreg.reg);
}

/*
	Set SDIO controller data timeout.

	@param[in] timeout  time out value between data blocks (unit: SD clock)

	@return void
*/
void sdiohost_setdatatimeout(struct mmc_nvt_host *host, u32 timeout)
{
	union SDIO_DATA_TIMER_REG timerreg;

	timerreg.bit.TIMEOUT = timeout;
	sdiohost_setreg(host, SDIO_DATA_TIMER_REG_OFS, timerreg.reg);
}

#if (SDIO_OLD_DRIVING_FLOW == 1)
/*
	Set PAD driving Sink

	@param[in] name driving sink name
	@param[in] paddriving driving current

	@return
		- @b E_OK: sucess
		- @b Else: fail
*/

static int pad_setdrivingsink(u32 name, u32 paddriving)
{
	u32 reg_data;
	u32 dw_offset = 0x0, bit_offset = 0x0, bit_mask = 0x0;
	u32 driving = paddriving;

	if (nvt_get_chip_id() == CHIP_NA51055) {
		if(name & PAD_DS_GROUP_40) {
			/* 40MA GROUP */
			dw_offset  = (PAD_DS10_REG_OFS - PAD_DS_REG_OFS);
			dw_offset += (((name & PAD_DS_GPIO_BASE_MASK)>>5)<<2);
			bit_offset = name & 0x1F;
			bit_mask   = 0x07;
			driving &= PAD_DS_GROUP_40_MSK;

			if (driving & PAD_DRIVINGSINK_5MA) {
				driving = 0;
			} else if (driving & PAD_DRIVINGSINK_10MA) {
				driving = 1;
			} else if (driving & PAD_DRIVINGSINK_15MA) {
				driving = 2;
			} else if (driving & PAD_DRIVINGSINK_20MA) {
				driving = 3;
			} else if (driving & PAD_DRIVINGSINK_25MA) {
				driving = 4;
			} else if (driving & PAD_DRIVINGSINK_30MA) {
				driving = 5;
			} else if (driving & PAD_DRIVINGSINK_35MA) {
				driving = 6;
			} else if (driving & PAD_DRIVINGSINK_40MA) {
				driving = 7;
			} else {
				printf("No Valid DS value [name(0x%08x) driving(0x%x)], PAD_DS_GROUP_40(51055)\r\n", name, driving);
				return E_PAR;
			}

			//printf("[name(0x%08x) driving(0x%x) dw_ofs(0x%x) bit_ofs(%02d) bit_mask(0x%x)], PAD_DS_GROUP_40(51055)\r\n", name, driving, dw_offset, bit_offset, bit_mask);
		} else if (name & PAD_DS_GROUP_16) {
			/* 16MA GROUP */
			dw_offset  = (((name & PAD_DS_GPIO_BASE_MASK)>>5)<<2);
			bit_offset = name & 0x1F;
			bit_mask   = 0x01;
			driving &= PAD_DS_GROUP_16_MSK;

			if (driving & PAD_DRIVINGSINK_6MA) {
				driving = 0;
			} else if (driving & PAD_DRIVINGSINK_16MA) {
				driving = 1;
			} else {
				printf("No Valid DS value [name(0x%08x) driving(0x%x)], PAD_DS_GROUP_16(51055)\r\n", name, driving);
				return E_PAR;
			}

			//printf("[name(0x%08x) driving(0x%x) dw_ofs(0x%x) bit_ofs(%02d) bit_mask(0x%x)], PAD_DS_GROUP_16(51055)\r\n", name, driving, dw_offset, bit_offset, bit_mask);
		} else {
			/* 10MA GROUP */
			if (name >= PAD_DS_HSIGPIO0) {
				dw_offset  = (((name & PAD_DS_GPIO_BASE_MASK)>>5)<<2) + 0x28;
			} else {
				dw_offset  = (((name & PAD_DS_GPIO_BASE_MASK)>>5)<<2);
			}
			bit_offset = name & 0x1F;
			bit_mask   = 0x01;
			driving &= PAD_DS_GROUP_10_MSK;

			if (driving & PAD_DRIVINGSINK_4MA) {
				driving = 0;
			} else if (driving & PAD_DRIVINGSINK_10MA) {
				driving = 1;
			} else {
				printf("No Valid DS value [name(0x%08x) driving(0x%x)], ELSE(51055)\r\n", name, driving);
				return E_PAR;
			}

			//printf("[name(0x%08x) driving(0x%x) dw_ofs(0x%x) bit_ofs(%02d) bit_mask(0x%x)], ELSE(51055)\r\n", name, driving, dw_offset, bit_offset, bit_mask);
		}
	} else {
		if (name & PAD_DS_GROUP_40) {
			/* 24MA GROUP or 40MA GROUP */
			dw_offset  = (PAD_DS10_REG_OFS - PAD_DS_REG_OFS);
			dw_offset += (((name & PAD_DS_GPIO_BASE_MASK)>>5)<<2);
			bit_offset = name & 0x1F;
			bit_mask   = 0x07;
			//driving &= PAD_DS_GROUP_24_MSK;

			if ((driving & PAD_DRIVINGSINK_3MA) || (driving & PAD_DRIVINGSINK_5MA)) {
				driving = 0;
			} else if ((driving & PAD_DRIVINGSINK_6MA) || (driving & PAD_DRIVINGSINK_10MA)) {
				driving = 1;
			} else if ((driving & PAD_DRIVINGSINK_9MA) || (driving & PAD_DRIVINGSINK_15MA)) {
				driving = 2;
			} else if ((driving & PAD_DRIVINGSINK_12MA) || (driving & PAD_DRIVINGSINK_20MA)) {
				driving = 3;
			} else if ((driving & PAD_DRIVINGSINK_15MA) || (driving & PAD_DRIVINGSINK_25MA)) {
				driving = 4;
			} else if ((driving & PAD_DRIVINGSINK_18MA) || (driving & PAD_DRIVINGSINK_30MA)) {
				driving = 5;
			} else if ((driving & PAD_DRIVINGSINK_21MA) || (driving & PAD_DRIVINGSINK_35MA)) {
				driving = 6;
			} else if ((driving & PAD_DRIVINGSINK_24MA) || (driving & PAD_DRIVINGSINK_40MA)) {
				driving = 7;
			} else {
				printf("No Valid DS value [name(0x%08x) driving(0x%x)], PAD_DS_GROUP_40\r\n", name, driving);
				return E_PAR;
			}

			//printf("[name(0x%08x) driving(0x%x) dw_ofs(0x%x) bit_ofs(%02d) bit_mask(0x%x)], PAD_DS_GROUP_40\r\n", name, driving, dw_offset, bit_offset, bit_mask);
		} else if (((name >= PAD_DS_CGPIO18) && (name <= PAD_DS_CGPIO22)) ||
		           ((name >= PAD_DS_CGPIO0) && (name <= PAD_DS_CGPIO7)) ||
		           (name == PAD_DS_CGPIO8) ||
		           (name == PAD_DS_CGPIO9)) {
			dw_offset  = (((name & PAD_DS_GPIO_BASE_MASK)>>5)<<2);
			bit_offset = name & 0x1F;
			bit_mask   = 0x3;

			if ((driving == PAD_DRIVINGSINK_4MA) || (driving == PAD_DRIVINGSINK_6MA)) {
				driving = 0;
			} else if ((driving == PAD_DRIVINGSINK_8MA) || (driving == PAD_DRIVINGSINK_10MA)) {
				driving = 1;
			} else if (driving == PAD_DRIVINGSINK_12MA) {
				driving = 2;
			} else if (driving == PAD_DRIVINGSINK_16MA) {
				driving = 3;
			} else {
				printf("No Valid DS value [name(0x%08x) driving(0x%x)], CGPIO0-9 & CGPIO18-22\r\n", name, driving);
				return E_PAR;
			}

			//printf("[name(0x%08x) driving(0x%x) dw_ofs(0x%x) bit_ofs(%02d) bit_mask(0x%x)], CGPIO0-9 & CGPIO18-22\r\n", name, driving, dw_offset, bit_offset, bit_mask);
		} else {
			if ((name >= PAD_DS_HSIGPIO0) && !(name & PAD_DS_GROUP_16)) {
				dw_offset  = (((name & PAD_DS_GPIO_BASE_MASK)>>5)<<2) + 0x28;
			} else {
				dw_offset  = (((name & PAD_DS_GPIO_BASE_MASK)>>5)<<2);
			}
			bit_offset = name & 0x1F;
			bit_mask   = 0x07;

			if (name & PAD_DS_GROUP_16)
				driving &= PAD_DS_GROUP_COMBO_MSK;

			if ((driving == PAD_DRIVINGSINK_4MA) || (driving == PAD_DRIVINGSINK_6MA)) {
				driving = 0;
			} else if ((driving == PAD_DRIVINGSINK_8MA) || (driving == PAD_DRIVINGSINK_10MA)) {
				driving = 1;
			} else if (driving == PAD_DRIVINGSINK_12MA) {
				driving = 2;
			} else if (driving == PAD_DRIVINGSINK_16MA) {
				driving = 3;
			} else {
				printf("No Valid DS value [name(0x%08x) driving(0x%x)], ELSE\r\n", name, driving);
				return E_PAR;
			}

			//printf("[name(0x%08x) driving(0x%x) dw_ofs(0x%x) bit_ofs(%02d) bit_mask(0x%x)], ELSE\r\n", name, driving, dw_offset, bit_offset, bit_mask);
		}
	}

	HAL_READ_UINT32(IOADDR_PAD_REG_BASE + PAD_DS_REG_OFS + dw_offset, reg_data);
	reg_data &= ~(bit_mask << bit_offset);
	reg_data |=  (driving << bit_offset);
	HAL_WRITE_UINT32(IOADDR_PAD_REG_BASE + PAD_DS_REG_OFS + dw_offset, reg_data);

	return E_OK;
}

static u32 driving_xfer(u32 driving)
{
	u32 pad_driving;

	if (driving == 40)
		pad_driving = PAD_DRIVINGSINK_40MA;
	else if (driving == 10)
		pad_driving = PAD_DRIVINGSINK_10MA;
	else if (driving == 15)
		pad_driving = PAD_DRIVINGSINK_15MA;
	else if (driving == 20)
		pad_driving = PAD_DRIVINGSINK_20MA;
	else if (driving == 25)
		pad_driving = PAD_DRIVINGSINK_25MA;
	else if (driving == 30)
		pad_driving = PAD_DRIVINGSINK_30MA;
	else if (driving == 35)
		pad_driving = PAD_DRIVINGSINK_35MA;
	else if (driving == 6)
		pad_driving = PAD_DRIVINGSINK_6MA;
	else if (driving == 16)
		pad_driving = PAD_DRIVINGSINK_16MA;
	else if (driving == 4)
		pad_driving = PAD_DRIVINGSINK_4MA;
	else if (driving == 8)
		pad_driving = PAD_DRIVINGSINK_8MA;
	else if (driving == 12)
		pad_driving = PAD_DRIVINGSINK_12MA;
	else if (driving == 3)
		pad_driving = PAD_DRIVINGSINK_3MA;
	else if (driving == 9)
		pad_driving = PAD_DRIVINGSINK_9MA;
	else if (driving == 18)
		pad_driving = PAD_DRIVINGSINK_18MA;
	else if (driving == 21)
		pad_driving = PAD_DRIVINGSINK_21MA;
	else if (driving == 24)
		pad_driving = PAD_DRIVINGSINK_24MA;
	else
		pad_driving = PAD_DRIVINGSINK_5MA;

	return pad_driving;
}

/*
	Set PAD drive/sink of clock pin for specified SDIO channel.

	@param[in] id       SDIO channel ID
			- @b SDIO_HOST_ID_1: SDIO1
			- @b SDIO_HOST_ID_2: SDIO2
	@param[in] driving  desired driving value * 10, unit: mA
				valid value: 50 ~ 200

	@return
		- @b E_OK: sucess
		- @b Else: fail
*/
static int sdiohost_setpaddriving(struct mmc_nvt_host *host, SDIO_SPEED_MODE mode)
{
	UINT32 data_uidriving, cmd_uidriving, clk_uidriving;

#ifdef CONFIG_NVT_FPGA_EMULATION
	return 0;
#endif

	if (mode == SDIO_MODE_DS) {
		data_uidriving = driving_xfer(host->pad_driving[SDIO_DS_MODE_DATA]);
		cmd_uidriving = driving_xfer(host->pad_driving[SDIO_DS_MODE_CMD]);
		clk_uidriving = driving_xfer(host->pad_driving[SDIO_DS_MODE_CLK]);
	} else if (mode == SDIO_MODE_HS) {
		data_uidriving = driving_xfer(host->pad_driving[SDIO_HS_MODE_DATA]);
		cmd_uidriving = driving_xfer(host->pad_driving[SDIO_HS_MODE_CMD]);
		clk_uidriving = driving_xfer(host->pad_driving[SDIO_HS_MODE_CLK]);
	} else if (mode == SDIO_MODE_SDR50) {
		data_uidriving = driving_xfer(host->pad_driving[SDIO_SDR50_MODE_DATA]);
		cmd_uidriving = driving_xfer(host->pad_driving[SDIO_SDR50_MODE_CMD]);
		clk_uidriving = driving_xfer(host->pad_driving[SDIO_SDR50_MODE_CLK]);
	} else {
		data_uidriving = driving_xfer(host->pad_driving[SDIO_SDR104_MODE_DATA]);
		cmd_uidriving = driving_xfer(host->pad_driving[SDIO_SDR104_MODE_CMD]);
		clk_uidriving = driving_xfer(host->pad_driving[SDIO_SDR104_MODE_CLK]);
	}

	if (host->id == SDIO_HOST_ID_1) {
		pad_setdrivingsink(PAD_DS_CGPIO12, cmd_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO13, data_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO14, data_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO15, data_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO16, data_uidriving);
		return pad_setdrivingsink(PAD_DS_CGPIO11, clk_uidriving);
	} else if (host->id == SDIO_HOST_ID_2) {
		pad_setdrivingsink(PAD_DS_CGPIO18, cmd_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO19, data_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO20, data_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO21, data_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO22, data_uidriving);
		return pad_setdrivingsink(PAD_DS_CGPIO17, clk_uidriving);
	} else {
		if (host->enable_8bits) {
			pad_setdrivingsink(PAD_DS_CGPIO4, data_uidriving);
			pad_setdrivingsink(PAD_DS_CGPIO5, data_uidriving);
			pad_setdrivingsink(PAD_DS_CGPIO6, data_uidriving);
			pad_setdrivingsink(PAD_DS_CGPIO7, data_uidriving);
		}
		pad_setdrivingsink(PAD_DS_CGPIO0, data_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO1, data_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO2, data_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO3, data_uidriving);
		pad_setdrivingsink(PAD_DS_CGPIO9, cmd_uidriving);
		return pad_setdrivingsink(PAD_DS_CGPIO8, clk_uidriving);
	}

	return 0;
}
#else
/*
	Set PAD driving Sink

	@param[in] name driving sink name
	@param[in] paddriving driving current

	@return
		- @b E_OK: sucess
		- @b Else: fail
*/

static int pad_setdrivingsink(u32 name, u32 paddriving)
{
	unsigned long padreg;
	unsigned long dwoffset = 0x0, bitoffset = 0x0;
	unsigned long driving = paddriving;

	bitoffset = name & 0x1F;
	dwoffset = (name >> 5);

	HAL_READ_UINT32(IOADDR_PAD_REG_BASE + PAD_PUPD0_REG_OFS + (dwoffset << 2), padreg);
	padreg &= ~(0xf << bitoffset);
	padreg |=  (driving << bitoffset);
	HAL_WRITE_UINT32(IOADDR_PAD_REG_BASE + PAD_PUPD0_REG_OFS + (dwoffset << 2), padreg);

	return E_OK;
}

/*
	Set PAD drive/sink of clock pin for specified SDIO channel.

	@param[in] id       SDIO channel ID
			- @b SDIO_HOST_ID_1: SDIO1
			- @b SDIO_HOST_ID_2: SDIO2
	@param[in] driving  desired driving value * 10, unit: mA
				valid value: 50 ~ 200

	@return
		- @b E_OK: sucess
		- @b Else: fail
*/
static int sdiohost_setpaddriving(struct mmc_nvt_host *host, SDIO_SPEED_MODE mode)
{
	UINT32 data_uidriving, cmd_uidriving, clk_uidriving;

	if (mode == SDIO_MODE_DS) {
		data_uidriving = host->pad_driving[SDIO_DS_MODE_DATA];
		cmd_uidriving = host->pad_driving[SDIO_DS_MODE_CMD];
		clk_uidriving = host->pad_driving[SDIO_DS_MODE_CLK];
	} else if (mode == SDIO_MODE_HS) {
		data_uidriving = host->pad_driving[SDIO_HS_MODE_DATA];
		cmd_uidriving = host->pad_driving[SDIO_HS_MODE_CMD];
		clk_uidriving = host->pad_driving[SDIO_HS_MODE_CLK];
	} else if (mode == SDIO_MODE_SDR50) {
		data_uidriving = host->pad_driving[SDIO_SDR50_MODE_DATA];
		cmd_uidriving = host->pad_driving[SDIO_SDR50_MODE_CMD];
		clk_uidriving = host->pad_driving[SDIO_SDR50_MODE_CLK];
	} else {
		data_uidriving = host->pad_driving[SDIO_SDR104_MODE_DATA];
		cmd_uidriving = host->pad_driving[SDIO_SDR104_MODE_CMD];
		clk_uidriving = host->pad_driving[SDIO_SDR104_MODE_CLK];
	}

	if (host->id == SDIO_HOST_ID_1) {
		pad_setdrivingsink(PAD_DS_SDIO_CMD, cmd_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO_D0, data_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO_D1, data_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO_D2, data_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO_D3, data_uidriving);
		return pad_setdrivingsink(PAD_DS_SDIO_CLK, clk_uidriving);
	}
#if (SDIO_HOST_ID_COUNT > 1)
	else if (host->id == SDIO_HOST_ID_2) {

#if defined(TOP_SDIO2_WIDTH_REG)
		if (host->enable_8bits) {
			pad_setdrivingsink(PAD_DS_SDIO2_D4, data_uidriving);
			pad_setdrivingsink(PAD_DS_SDIO2_D5, data_uidriving);
			pad_setdrivingsink(PAD_DS_SDIO2_D6, data_uidriving);
			pad_setdrivingsink(PAD_DS_SDIO2_D7, data_uidriving);
		}
#endif

		pad_setdrivingsink(PAD_DS_SDIO2_CMD, cmd_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO2_D0, data_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO2_D1, data_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO2_D2, data_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO2_D3, data_uidriving);
		return pad_setdrivingsink(PAD_DS_SDIO2_CLK, clk_uidriving);
	}
#endif
#if (SDIO_HOST_ID_COUNT > 2)
	else if (host->id == SDIO_HOST_ID_3) {

#if defined(TOP_SDIO3_WIDTH_REG)
		if (host->enable_8bits) {
			pad_setdrivingsink(PAD_DS_SDIO3_D4, data_uidriving);
			pad_setdrivingsink(PAD_DS_SDIO3_D5, data_uidriving);
			pad_setdrivingsink(PAD_DS_SDIO3_D6, data_uidriving);
			pad_setdrivingsink(PAD_DS_SDIO3_D7, data_uidriving);
		}
#endif

		pad_setdrivingsink(PAD_DS_SDIO3_CMD, cmd_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO3_D0, data_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO3_D1, data_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO3_D2, data_uidriving);
		pad_setdrivingsink(PAD_DS_SDIO3_D3, data_uidriving);
		return pad_setdrivingsink(PAD_DS_SDIO3_CLK, clk_uidriving);
	}
#endif

	return 0;
}
#endif

/*
	Delay for SDIO module

	@param[in] uid the count for dummy read

	@return void
*/
void sdiohost_delayd(struct mmc_nvt_host *host, u32 uid)
{
	u32 i;

	for (i = uid; i; i--)
		sdiohost_getreg(host, SDIO_CMD_REG_OFS);
}

/*
	Set SDIO controller block size.

	@param[in] host data structure

	@return void
*/
void sdiohost_setblksize(struct mmc_nvt_host *host)
{
	union SDIO_DATA_CTRL_REG datactrlreg;

	datactrlreg.reg = sdiohost_getreg(host, SDIO_DATA_CTRL_REG_OFS);
	datactrlreg.bit.BLK_SIZE = host->data->blocksize;
	sdiohost_setreg(host, SDIO_DATA_CTRL_REG_OFS, datactrlreg.reg);
}

/*
	Setup SDIO controller data transfer in DMA mode.

	@param[in] uidmaaddress buffer DRAM address
			- possible value: 0x000_0000 ~ 0xFFF_FFFF
	@param[in] uidatalength total transfer length
			- possible value: 0x000_0001 ~ 0x3FF_FFFF
	@param[in] bisread      read/write mode
			- @b TRUE: indicate data read transfer
			- @b FALSE: indicate data write transfer

	@return void
*/
void sdiohost_setupdatatransferdma(struct mmc_nvt_host *host ,
u32 uidmaaddress, u32 uidatalength, BOOL bisread)
{
	union SDIO_DATA_CTRL_REG datactrlreg;
	union SDIO_DATA_LENGTH_REG datalenreg;
	union SDIO_FIFO_CONTROL_REG fifoctrlreg;
	union SDIO_DMA_START_ADDR_REG dmaaddrreg;
	u32 uibusclk;

	/* dummy read for patch */
	sdiohost_delayd(host, 2);

	uibusclk = sdiohost_getbusclk(host->id);

	if (uibusclk >= 48000000)
		sdiohost_delayd(host, 3);
	else if ((uibusclk >= 24000000) && (uibusclk < 48000000))
		sdiohost_delayd(host, 6);
	else if ((uibusclk >= 12000000) && (uibusclk < 24000000))
		sdiohost_delayd(host, 9);
	else
		sdiohost_delayd(host, 21);

	/* patch for sd fifo bug end */

	datactrlreg.reg = sdiohost_getreg(host, SDIO_DATA_CTRL_REG_OFS);

#if 0
	/* multiple read => disable SDIO INT detection after transfer end */
	if (bisread && (uidatalength > datactrlreg.bit.BLK_SIZE))
		datactrlreg.bit.DIS_SDIO_INT_PERIOD = 1;
	else
		datactrlreg.bit.DIS_SDIO_INT_PERIOD = 0;
#else
	/*
	 * The DIS_SDIO_INT_PERIOD is designed for reading infinite blocks.
	 * But there are some compatibility and performance issues, so it should not be used.
	 */
	datactrlreg.bit.DIS_SDIO_INT_PERIOD = 0;
#endif

	/*move data en after fifo en*/
	/*datactrlreg.bit.DATA_EN = 1;*/
	sdiohost_setreg(host, SDIO_DATA_CTRL_REG_OFS, datactrlreg.reg);

	dmaaddrreg.reg = 0;
	dmaaddrreg.bit.DRAM_ADDR = uidmaaddress;
	sdiohost_setreg(host, SDIO_DMA_START_ADDR_REG_OFS, dmaaddrreg.reg);

	datalenreg.reg = 0;
	datalenreg.bit.LENGTH = uidatalength;
	sdiohost_setreg(host, SDIO_DATA_LENGTH_REG_OFS, datalenreg.reg);

	fifoctrlreg.reg = 0;

	/* Flush cache in DMA mode*/
	if (!bisread)
		fifoctrlreg.bit.FIFO_DIR = 1;
	else
		fifoctrlreg.bit.FIFO_DIR = 0;

	fifoctrlreg.bit.FIFO_MODE = 1;
	sdiohost_setreg(host, SDIO_FIFO_CONTROL_REG_OFS, fifoctrlreg.reg);

	datactrlreg.reg = sdiohost_getreg(host, SDIO_DATA_CTRL_REG_OFS);
	datactrlreg.bit.DATA_EN = 1;
	sdiohost_setreg(host, SDIO_DATA_CTRL_REG_OFS, datactrlreg.reg);

	fifoctrlreg.bit.FIFO_EN = 1;
	sdiohost_setreg(host, SDIO_FIFO_CONTROL_REG_OFS, fifoctrlreg.reg);
}

/*
	Setup SDIO controller data transfer in PIO mode.

	@param[in] uidatalength total transfer length
			- possible value: 0x000_0001 ~ 0x3FF_FFFF
	@param[in] bisread      read/write mode
			- @b TRUE: indicate data read transfer
			- @b FALSE: indicate data write transfer

	@return void
*/
void sdiohost_setupdatatransferpio(struct mmc_nvt_host *host,  u32 uidatalength, BOOL bisread)
{
	union SDIO_DATA_CTRL_REG datactrlreg;
	union SDIO_DATA_LENGTH_REG datalenreg;
	union SDIO_FIFO_CONTROL_REG fifoctrlreg;
	u32 uibusclk;

	/* dummy read for patch */
	sdiohost_delayd(host, 2);

	uibusclk = sdiohost_getbusclk(host->id);

	if (uibusclk >= 48000000)
		sdiohost_delayd(host, 3);
	else if ((uibusclk >= 24000000) && (uibusclk < 48000000))
		sdiohost_delayd(host, 6);
	else if ((uibusclk >= 12000000) && (uibusclk < 24000000))
		sdiohost_delayd(host, 9);
	else
		sdiohost_delayd(host, 21);

	/* patch for sd fifo bug end */


	datactrlreg.reg = sdiohost_getreg(host, SDIO_DATA_CTRL_REG_OFS);
	/* multiple read => disable SDIO INT detection after transfer end */
	if (bisread && (uidatalength > datactrlreg.bit.BLK_SIZE))
		datactrlreg.bit.DIS_SDIO_INT_PERIOD = 1;
	else
		datactrlreg.bit.DIS_SDIO_INT_PERIOD = 0;

	datactrlreg.bit.DATA_EN = 1;
	sdiohost_setreg(host, SDIO_DATA_CTRL_REG_OFS, datactrlreg.reg);

	datalenreg.reg = 0;
	datalenreg.bit.LENGTH = uidatalength;
	sdiohost_setreg(host, SDIO_DATA_LENGTH_REG_OFS, datalenreg.reg);

	fifoctrlreg.reg = 0;

	if (!bisread)
		fifoctrlreg.bit.FIFO_DIR = 1;
	else
		fifoctrlreg.bit.FIFO_DIR = 0;

	sdiohost_setreg(host, SDIO_FIFO_CONTROL_REG_OFS, fifoctrlreg.reg);

	fifoctrlreg.bit.FIFO_EN = 1;
	sdiohost_setreg(host, SDIO_FIFO_CONTROL_REG_OFS, fifoctrlreg.reg);

}

/*
	Write SDIO data blocks.


	@note This function should only be called in PIO mode.

	@param[in] pbuf         buffer DRAM address
	@param[in] uiLength     total length (block alignment)

	@return
			- @b E_OK: success
			- @b E_SYS: data CRC or data timeout error
*/
ER sdiohost_writeblock(struct mmc_nvt_host *host, u8 *pbuf, u32 uilength)
{
	u32  uiwordcount, i, *pbufword;
	u32  uifullcount, uiremaincount;
	BOOL    bwordalignment;
	union SDIO_DATA_PORT_REG    datareg;
	union SDIO_FIFO_STATUS_REG  fifostsreg;
	union SDIO_STATUS_REG       stsreg;

	uiwordcount     = (uilength + sizeof(u32) - 1) / sizeof(u32);
	uifullcount     = uiwordcount / SDIO_HOST_DATA_FIFO_DEPTH;
	uiremaincount   = uiwordcount % SDIO_HOST_DATA_FIFO_DEPTH;
	pbufword        = (u32 *)pbuf;

	if ((u32)pbuf & 0x3)
		bwordalignment = FALSE;
	else
		bwordalignment = TRUE;

	while (uifullcount) {
		fifostsreg.reg = sdiohost_getreg(host, SDIO_FIFO_STATUS_REG_OFS);

		if (fifostsreg.bit.FIFO_EMPTY) {
			if (bwordalignment == TRUE) {
				/* Word alignment*/
				for (i = SDIO_HOST_DATA_FIFO_DEPTH; i; i--) {
					sdiohost_setreg(host, SDIO_DATA_PORT_REG_OFS, \
						*pbufword++);
				}
			} else {
				/* Not word alignment*/
				for (i = SDIO_HOST_DATA_FIFO_DEPTH; i; i--) {
					datareg.reg = *pbuf++;
					datareg.reg |= (*pbuf++) << 8;
					datareg.reg |= (*pbuf++) << 16;
					datareg.reg |= (*pbuf++) << 24;

					sdiohost_setreg(host, SDIO_DATA_PORT_REG_OFS, \
						datareg.reg);
				}
			}

			uifullcount--;
		}

		stsreg.reg = sdiohost_getreg(host, SDIO_STATUS_REG_OFS);

		if (stsreg.bit.DATA_CRC_FAIL || stsreg.bit.DATA_TIMEOUT) {
			printf("write block fail\n");
			return E_SYS;
		}
	}

	if (uiremaincount) {
		while (1) {
			fifostsreg.reg = \
				sdiohost_getreg(host, SDIO_FIFO_STATUS_REG_OFS);

			if (fifostsreg.bit.FIFO_EMPTY)
				break;

			stsreg.reg = sdiohost_getreg(host, SDIO_STATUS_REG_OFS);

			if (stsreg.bit.DATA_CRC_FAIL || stsreg.bit.DATA_TIMEOUT)
				return E_SYS;
		}

		if (bwordalignment == TRUE) {
			/* Word alignment*/
			for (i = uiremaincount; i; i--) {
				sdiohost_setreg(host, SDIO_DATA_PORT_REG_OFS, \
					*pbufword++);
			}
		} else {
			/* Not word alignment*/
			for (i = uiremaincount; i; i--) {
				datareg.reg = *pbuf++;
				datareg.reg |= (*pbuf++) << 8;
				datareg.reg |= (*pbuf++) << 16;
				datareg.reg |= (*pbuf++) << 24;

				sdiohost_setreg(host, SDIO_DATA_PORT_REG_OFS, \
					datareg.reg);
			}
		}
	}

	return E_OK;
}

/*
	Read SDIO data blocks.

	@note This function should only be called in PIO mode.

	@param[out] pbuf        buffer DRAM address
	@param[in] uiLength     total length (block alignment)

	@return
		- @b E_OK: success
		- @b E_SYS: data CRC or data timeout error
*/
ER sdiohost_readblock(struct mmc_nvt_host *host, u8 *pbuf, u32 uilength)
{
	u32  uiwordcount, i, *pbufword;
	u32  uifullcount, uiremaincount;
	BOOL    bwordalignment;
	union SDIO_DATA_PORT_REG    datareg;
	union SDIO_FIFO_STATUS_REG  fifostsreg;
	union SDIO_STATUS_REG       stsreg;

	uiwordcount     = (uilength + sizeof(u32) - 1) / sizeof(u32);
	uifullcount     = uiwordcount / SDIO_HOST_DATA_FIFO_DEPTH;
	uiremaincount   = uiwordcount % SDIO_HOST_DATA_FIFO_DEPTH;
	pbufword        = (u32 *)pbuf;

	if ((u32)pbuf & 0x3)
		bwordalignment = FALSE;
	else
		bwordalignment = TRUE;

	while (uifullcount) {
		fifostsreg.reg = sdiohost_getreg(host, SDIO_FIFO_STATUS_REG_OFS);

		if (fifostsreg.bit.FIFO_FULL) {
			if (bwordalignment == TRUE) {
				/* Word alignment*/
				for (i = SDIO_HOST_DATA_FIFO_DEPTH; i; i--) {
					*pbufword++ = \
					sdiohost_getreg(host, SDIO_DATA_PORT_REG_OFS);
				}
			} else {
				/* Not word alignment*/
				for (i = SDIO_HOST_DATA_FIFO_DEPTH; i; i--) {
					datareg.reg = \
					sdiohost_getreg(host, SDIO_DATA_PORT_REG_OFS);

					*pbuf++ = datareg.reg & 0xFF;
					*pbuf++ = (datareg.reg>>8) & 0xFF;
					*pbuf++ = (datareg.reg>>16) & 0xFF;
					*pbuf++ = (datareg.reg>>24) & 0xFF;
				}
			}

			uifullcount--;
		}

		stsreg.reg = sdiohost_getreg(host, SDIO_STATUS_REG_OFS);

		if (stsreg.bit.DATA_CRC_FAIL || stsreg.bit.DATA_TIMEOUT)
			return E_SYS;
	}

	if (uiremaincount) {
		while (1) {
			fifostsreg.reg = \
				sdiohost_getreg(host, SDIO_FIFO_STATUS_REG_OFS);

			if (fifostsreg.bit.FIFO_CNT == uiremaincount)
				break;

			stsreg.reg = sdiohost_getreg(host, SDIO_STATUS_REG_OFS);
			if (stsreg.bit.DATA_CRC_FAIL || stsreg.bit.DATA_TIMEOUT)
				return E_SYS;
		}

		if (bwordalignment == TRUE) {
			/* Word alignment*/
			for (i = uiremaincount; i; i--) {
				*pbufword++ = \
					sdiohost_getreg(host, SDIO_DATA_PORT_REG_OFS);
			}
		} else {
			/* Not word alignment*/
			for (i = uiremaincount; i; i--) {
				datareg.reg = \
					sdiohost_getreg(host, SDIO_DATA_PORT_REG_OFS);

				*pbuf++ = datareg.reg & 0xFF;
				*pbuf++ = (datareg.reg>>8) & 0xFF;
				*pbuf++ = (datareg.reg>>16) & 0xFF;
				*pbuf++ = (datareg.reg>>24) & 0xFF;
			}
		}
	}

	return E_OK;
}

u32 sdiohost_getstatus(struct mmc_nvt_host *host)
{
	return sdiohost_getreg(host, SDIO_STATUS_REG_OFS);
}

void sdiohost_setstatus(struct mmc_nvt_host *host, u32 status)
{
	sdiohost_setreg(host, SDIO_STATUS_REG_OFS, status);
}

static void sdiohost_fifo_data_trans(struct mmc_nvt_host *host,
					unsigned int n)
{
	if (host->data_dir == SDIO_HOST_WRITE_DATA) {
		host->buffer = (u8*) host->data->src;
		sdiohost_writeblock(host, (u8 *)host->buffer, \
			host->buffer_bytes_left);
		host->bytes_left -= host->buffer_bytes_left;
	} else {
		host->buffer = (u8*) host->data->dest;
		sdiohost_readblock(host, (u8 *)host->buffer, \
			host->buffer_bytes_left);
		host->bytes_left -= host->buffer_bytes_left;
	}
}

void sdiohost_clrfifoen(struct mmc_nvt_host *host)
{
	union SDIO_FIFO_CONTROL_REG fifoctrlreg;

	fifoctrlreg.reg = sdiohost_getreg(host, SDIO_FIFO_CONTROL_REG_OFS);
	fifoctrlreg.bit.FIFO_EN = 0;
	sdiohost_setreg(host, SDIO_FIFO_CONTROL_REG_OFS, fifoctrlreg.reg);
}

u32 sdiohost_getfifodir(struct mmc_nvt_host *host)
{
	union SDIO_FIFO_CONTROL_REG fifoctrlreg;

	fifoctrlreg.reg = sdiohost_getreg(host, SDIO_FIFO_CONTROL_REG_OFS);

	return fifoctrlreg.bit.FIFO_DIR;
}

void sdiohost_waitfifoempty(struct mmc_nvt_host *host)
{
	union SDIO_FIFO_STATUS_REG fifostsreg;
	u32 read0, read1;

	read0 = 0;
	while (1) {
		fifostsreg.reg = sdiohost_getreg(host, SDIO_FIFO_STATUS_REG_OFS);
		read1 = fifostsreg.bit.FIFO_EMPTY;
		if (read0 & read1)
			break;
		else
			read0 = read1;
	}
}

void sdiohost_getlongrsp(struct mmc_nvt_host *host, u32 *prsp3, u32 *prsp2, \
					u32 *prsp1, u32 *prsp0)
{
	union SDIO_RSP0_REG rsp0reg;
	union SDIO_RSP1_REG rsp1reg;
	union SDIO_RSP2_REG rsp2reg;
	union SDIO_RSP3_REG rsp3reg;

	rsp0reg.reg = sdiohost_getreg(host, SDIO_RSP0_REG_OFS);
	*prsp0 = (u32) rsp0reg.reg;
	rsp1reg.reg = sdiohost_getreg(host, SDIO_RSP1_REG_OFS);
	*prsp1 = (u32) rsp1reg.reg;
	rsp2reg.reg = sdiohost_getreg(host, SDIO_RSP2_REG_OFS);
	*prsp2 = (u32) rsp2reg.reg;
	rsp3reg.reg = sdiohost_getreg(host, SDIO_RSP3_REG_OFS);
	*prsp3 = (u32) rsp3reg.reg;
}

void sdiohost_getshortrsp(struct mmc_nvt_host *host, u32 *prsp)
{
	union SDIO_RSP0_REG rspreg;

	rspreg.reg = sdiohost_getreg(host, SDIO_RSP0_REG_OFS);
	*prsp = (u32) rspreg.reg;
}

static void mmc_nvt_cmd_done(struct mmc_nvt_host *host)
{
	struct mmc_cmd *cmd = host->cmd;
	host->cmd = NULL;

	if (cmd->resp_type & MMC_RSP_PRESENT) {
		if (cmd->resp_type & MMC_RSP_136) {
			/* response type 2 */
			sdiohost_getlongrsp(host, \
			(u32 *)&cmd->response[0], (u32 *)&cmd->response[1],
			(u32 *)&cmd->response[2], (u32 *)&cmd->response[3]);
			debug("lrsp 0x%x 0x%x 0x%x 0x%x\n", cmd->response[0], cmd->response[1], cmd->response[2], cmd->response[3]);
		} else {
			/* response types 1, 1b, 3, 4, 5, 6 */
			sdiohost_getshortrsp(host, (u32 *)&cmd->response[0]);
			debug("rsp 0x%x\n", cmd->response[0]);
		}
	}
}

static ER sdiohost_transfer(struct mmc_nvt_host *host)
{
	u32 status, qstatus = 0;
	int end_command = 0;
	int end_transfer = 0;
	int err_sts = 1, timeout = 0;
	struct mmc_data *data = host->data;
	ulong time_start, time_now;

	time_start = get_timer(0);
	while (1) {
		status = sdiohost_getstatus(host);
		//printf("cmd status is 0x%x\n", status);
		if (status & SDIO_STATUS_REG_CMD_SEND) {
			qstatus = status;
			sdiohost_setstatus(host, SDIO_STATUS_REG_CMD_SEND);
			break;
		}

		time_now = get_timer(0);
		if ((time_now - time_start) > 1000) {
			printk("[TOUT] CMD_SENT timeout [start(0x%lx) now(0x%lx), status(0x%x), qsts(0x%x)]\n", time_start, time_now, status, qstatus);
			break;
		}
	}

	time_start = get_timer(0);
	if (data && host->do_dma) {
		while (1) {
			status = sdiohost_getstatus(host);
			//printf("data status is 0x%x\n", status);
			if ((status & SDIO_STATUS_REG_DATA_END) || \
				(status & SDIO_STATUS_REG_DATA_TIMEOUT)) {
				qstatus = status;
				sdiohost_setstatus(host, SDIO_STATUS_REG_DATA_END);
				break;
			} else if ((status & SDIO_STATUS_REG_RSP_CRC_FAIL) || \
				(status & SDIO_STATUS_REG_DATA_CRC_FAIL)) {
				qstatus = status;
				//printf("crc status is 0x%x\n", status);
				break;
			}

			time_now = get_timer(0);
			if ((time_now - time_start) > 1000) {
				printk("[TOUT] DATA_EN timeout [start(0x%lx) now(0x%lx), status(0x%x), qsts(0x%x)]\n", time_start, time_now, status, qstatus);
				break;
			}
		}
	}


	if ((qstatus & SDIO_STATUS_REG_RSP_CRC_FAIL) ||
		(qstatus & SDIO_STATUS_REG_DATA_CRC_FAIL) ||
		(qstatus & SDIO_STATUS_REG_RSP_TIMEOUT) ||
		(qstatus & SDIO_STATUS_REG_DATA_TIMEOUT)) {
		err_sts = 1;
	} else {
		err_sts = 0;
	}
	//printf("qstatus is 0x%x, err_sts %d\n", qstatus, err_sts);

	if (qstatus & MMCST_RSP_TIMEOUT) {
		/* Command timeout */
		if (host->cmd) {
			//printf("CMD%d timeout, status %x\n",host->cmd->cmdidx, qstatus);
			if (host->data)
				sdiohost_resetdata(host);
		}
		sdiohost_setstatus(host, MMCST_RSP_TIMEOUT);
		timeout = 1;
	}

	if (qstatus & MMCST_DATA_TIMEOUT) {
		printf("data timeout (CMD%d), status 0x%x\n", host->cmd->cmdidx, qstatus);
		if (host->data)
			sdiohost_resetdata(host);

		sdiohost_setstatus(host, MMCST_DATA_TIMEOUT);
	}

	if (qstatus & MMCST_RSP_CRC_FAIL) {
		/* Command CRC error */
		if (host->cmd) {
			printf("Command CRC error (CMD%d), status 0x%x\n", host->cmd->cmdidx, qstatus);
		} else {
			printf("Command CRC error without CMD, status 0x%x\n", qstatus);
		}
		sdiohost_setstatus(host, MMCST_RSP_CRC_FAIL);
	}

	if (qstatus & MMCST_DATA_CRC_FAIL) {
		/* Data CRC error */
		printf("data %s CRC error (CMD%d), status 0x%x\n", \
			(host->data->flags & MMC_DATA_WRITE) ? "write" : "read", host->cmd->cmdidx, qstatus);
		sdiohost_resetdata(host);
		sdiohost_setstatus(host, MMCST_DATA_CRC_FAIL);
	}

	if (((qstatus & MMCST_RSP_CRC_OK) || (qstatus & MMCST_CMD_SENT)) && !timeout) {
		/* End of command phase */
		if (data == NULL)
			end_command = (int) host->cmd;
		else {
			if ((host->bytes_left > 0) && (host->do_dma == 0)) {
				/* if datasize < rw_threshold
				 * no RX ints are generated
				 */
				sdiohost_fifo_data_trans(host, host->bytes_left);
			}
		}
		if (qstatus & MMCST_RSP_CRC_OK)
			sdiohost_setstatus(host, MMCST_RSP_CRC_OK);

		if (qstatus & MMCST_CMD_SENT)
			sdiohost_setstatus(host, MMCST_CMD_SENT);
	}

	if ((qstatus & MMCST_RSP_CRC_OK))
		end_command = (int) host->cmd;

	if (data && !host->do_dma && !timeout) {
		while (1) {
			status = sdiohost_getstatus(host);
			if ((status & SDIO_STATUS_REG_DATA_END) || \
				(status & SDIO_STATUS_REG_DATA_TIMEOUT)) {
				qstatus = status;
				sdiohost_setstatus(host, SDIO_STATUS_REG_DATA_END);
				break;
			} else if ((status & SDIO_STATUS_REG_RSP_CRC_FAIL) || (status & SDIO_STATUS_REG_RSP_TIMEOUT) || \
				(status & SDIO_STATUS_REG_DATA_CRC_FAIL)) {
				qstatus = status;
				break;
			}
		}
	}

	if ((qstatus & MMCST_DATA_END) || (qstatus & MMCST_DATA_CRC_OK)) {
		end_transfer = 1;
		data->dest += data->blocksize;
		if (!sdiohost_getfifodir(host)) {
			if (qstatus & MMCST_DATA_END) {
				if ((qstatus & MMCST_DMA_ERROR)
					!= MMCST_DMA_ERROR) {
					sdiohost_waitfifoempty(host);
				}
				sdiohost_clrfifoen(host);
			}
		}
		if (qstatus & MMCST_DATA_END)
			sdiohost_setstatus(host, MMCST_DATA_END);

		if (qstatus & MMCST_DATA_CRC_OK)
			sdiohost_setstatus(host, MMCST_DATA_CRC_OK);
	}

	if (end_command)
		mmc_nvt_cmd_done(host);

	if (end_transfer && (!host->cmd)) {
		host->data = NULL;
		host->cmd = NULL;
	}

	if (err_sts) {
		if (timeout)
			return -ETIMEDOUT;

		status = sdiohost_getstatus(host);
		if(status)
			printf("end status is 0x%x\n", status);
		return -ECOMM;
	}

	return SDIO_HOST_CMD_OK;
}

/*
	Send SD command to SD bus.

	@param[in] cmd      command value
	@param[in] rsptype  response type
			- @b SDIO_HOST_RSP_NONE: no response is required
			- @b SDIO_HOST_RSP_SHORT: need short (32 bits) response
			- @b SDIO_HOST_RSP_LONG: need long (128 bits) response
	@param[in] beniointdetect enable SDIO INT detect after command end
			- @b TRUE: enable SDIO INT detection
			- @b FALSE: keep SDIO INT detection

	@return command result
	- @b SDIO_HOST_CMD_OK: command execution success
	- @b SDIO_HOST_RSP_TIMEOUT: response timeout. no response got from card.
	- @b SDIO_HOST_RSP_CRCFAIL: response CRC fail.
	- @b SDIO_HOST_CMD_FAIL: other fail.
*/
ER sdiohost_sendcmd(struct mmc_nvt_host *host, \
	u32 cmd, SDIO_HOST_RESPONSE rsptype, BOOL beniointdetect)
{
	union SDIO_CMD_REG cmdreg;
	u32 status;

	/*cmdreg.reg = 0;*/
	cmdreg.reg = sdiohost_getreg(host, SDIO_CMD_REG_OFS);
	cmdreg.bit.CMD_IDX = 0;
	cmdreg.bit.NEED_RSP = 0;
	cmdreg.bit.LONG_RSP = 0;
	cmdreg.bit.RSP_TIMEOUT_TYPE = 0;
	cmdreg.bit.ENABLE_SDIO_INT_DETECT = beniointdetect;

	if (rsptype != SDIO_HOST_RSP_NONE) {
		/* Need response */
		cmdreg.bit.NEED_RSP = 1;

		switch (rsptype) {

		default:
			break;

		case SDIO_HOST_RSP_LONG:
			cmdreg.bit.LONG_RSP = 1;
			break;

		case SDIO_HOST_RSP_SHORT_TYPE2:
			cmdreg.bit.RSP_TIMEOUT_TYPE = 1;
			break;

		case SDIO_HOST_RSP_LONG_TYPE2:
			cmdreg.bit.RSP_TIMEOUT_TYPE = 1;
			cmdreg.bit.LONG_RSP = 1;
			break;
		}
	}

	cmdreg.bit.CMD_IDX = cmd;
	sdiohost_setreg(host, SDIO_CMD_REG_OFS, cmdreg.reg);

	/*Clear all status*/
	status = sdiohost_getstatus(host);
	sdiohost_setstatus(host, status);

	/* Start command/data transmits */
	cmdreg.bit.CMD_EN = 1;
	sdiohost_setreg(host, SDIO_CMD_REG_OFS, cmdreg.reg);

	return sdiohost_transfer(host);
}

ER sdiohost_sendsdcmd(struct mmc_nvt_host *host, u32 cmdpart)
{
	BOOL benintdetect = FALSE;
	SDIO_HOST_RESPONSE rsptype = SDIO_HOST_RSP_NONE;
	u32 param = host->cmd->cmdarg;

	if ((cmdpart & SDIO_CMD_REG_LONG_RSP) == SDIO_CMD_REG_LONG_RSP) {
		if (cmdpart & SDIO_CMD_REG_RSP_TYPE2)
			rsptype = SDIO_HOST_RSP_LONG_TYPE2;
		else
			rsptype = SDIO_HOST_RSP_LONG;
	} else if (cmdpart & SDIO_CMD_REG_VOLTAGE_SWITCH_DETECT) {
		rsptype = SDIO_HOST_RSP_VOLT_DETECT;
	} else if (cmdpart & SDIO_CMD_REG_NEED_RSP) {

		if (cmdpart & SDIO_CMD_REG_RSP_TYPE2)
			rsptype = SDIO_HOST_RSP_SHORT_TYPE2;
		else
			rsptype = SDIO_HOST_RSP_SHORT;
	}

	if (cmdpart & SDIO_CMD_REG_ABORT)
		benintdetect = TRUE;

	sdiohost_setreg(host, SDIO_ARGU_REG_OFS, param);
	//printf("bEnIntDetect %d\r\n",benintdetect);

	return sdiohost_sendcmd(host, cmdpart & SDIO_CMD_REG_INDEX, \
			rsptype, benintdetect);
}

static void mmc_nvt_prepare_data(struct mmc_nvt_host *host)
{
	u32 size;

	if (host->data == NULL) {
		return;
	}

	sdiohost_setblksize(host);

	host->buffer = (u8*)(host->data->dest);
	host->bytes_left = host->data->blocks * host->data->blocksize;
	host->data_dir = ((host->data->flags & MMC_DATA_WRITE) ?
		SDIO_HOST_WRITE_DATA : SDIO_HOST_READ_DATA);

#if (defined(CONFIG_TARGET_NA51090) || defined(CONFIG_TARGET_NA51090_A64))
	if ((host->bytes_left % ARCH_DMA_MINALIGN) || ((u32)host->buffer % ARCH_DMA_MINALIGN) || (host->mmc_input_clk < 24000000)) {
		if (host->mmc_input_clk < 24000000) {
			debug("clk(%dHz) < 24MHz, force to PIO\n", host->mmc_input_clk);
		}
#else
	if ((host->bytes_left % ARCH_DMA_MINALIGN) || ((u32)host->buffer % ARCH_DMA_MINALIGN)) {
#endif
		host->do_dma = 0;
		host->buffer_bytes_left = host->bytes_left;
	} else {
		host->do_dma = 1;
	}

	if ((host->mmc_input_clk >= 50000000) && host->do_dma) {
		if (host->data_dir == SDIO_HOST_WRITE_DATA) {
			debug("over 50MHz and cmd write, blkfifoen = 0\n");
			sdiohost_setblkfifoen(host, false);
		} else {
			debug("blkfifoen = 1\n");
			sdiohost_setblkfifoen(host, true);
		}
	}

	if (host->do_dma) {
		size = (u32)(host->buffer) + host->bytes_left;

		if (host->data_dir == SDIO_HOST_WRITE_DATA)
			flush_dcache_range(ALIGN_FLOOR((u32)host->buffer, ARCH_DMA_MINALIGN), (u32)roundup(size,ARCH_DMA_MINALIGN));
		else
			invalidate_dcache_range(ALIGN_FLOOR((u32)host->buffer, ARCH_DMA_MINALIGN), (u32)roundup(size,ARCH_DMA_MINALIGN));

		sdiohost_setupdatatransferdma(host, (u32)host->buffer, \
			host->bytes_left, host->data_dir);
		/* zero this to ensure we take no PIO paths */
		host->bytes_left = 0;
	} else {
		sdiohost_setupdatatransferpio(host, \
			host->buffer_bytes_left, \
			host->data_dir);
	}
}

int mmc_nvt_start_command(struct mmc_nvt_host *host)
{
	u32 cmd_reg = 0;
	char *s;

	switch (host->cmd->resp_type & mmc_resp_type) {
	case MMC_RSP_R1: /* 48 bits, CRC */
		s = ", R1";
		cmd_reg |= SDIO_CMD_REG_NEED_RSP;
		break;
	case MMC_RSP_R1b:
		s = ", R1b";
		/* There's some spec confusion about when R1B is
		 * allowed, but if the card doesn't issue a BUSY
		 * then it's harmless for us to allow it.
		 */
		/*need to check card busy CARD_BUSY2READY bit or
		 *send _SDIO_SD_SEND_STATUS to check
		*/
		cmd_reg |= SDIO_CMD_REG_NEED_RSP;
		/* FALLTHROUGH */
		break;
	case MMC_RSP_R2: /* 136 bits, CRC */
		s = ", R2";
		cmd_reg |= SDIO_CMD_REG_LONG_RSP;
		break;
	case MMC_RSP_R3: /* 48 bits, no CRC */
		s = ", R3/R4";
		cmd_reg |= SDIO_CMD_REG_NEED_RSP;
		break;
	default:
		s = ", Rx";
		cmd_reg |= 0;
		break;
	};

	debug("CMD%d, arg 0x%08x %s\n", host->cmd->cmdidx, host->cmd->cmdarg, s);

	/* Set command index */
	cmd_reg |= host->cmd->cmdidx;

	return sdiohost_sendsdcmd(host, cmd_reg);
}

#if !CONFIG_IS_ENABLED(DM_MMC)
static int host_request(struct mmc *dev,
			struct mmc_cmd *cmd,
			struct mmc_data *data)
{
	struct mmc_nvt_host *host = dev->priv;
#else
static int host_request(struct udevice *udev,
			struct mmc_cmd *cmd,
			struct mmc_data *data)
{
	struct mmc_nvt_host *host = dev_get_priv(udev);
	struct mmc *dev = mmc_get_mmc_dev(udev);
#endif

	int result;
	unsigned int time_start, time_now, mmcst = 0;
	host->data = data;
	host->cmd = cmd;
	static int cur_clk = 0;

#ifdef CONFIG_NVT_IVOT_EMMC
	if (data || (host->id == SDIO_HOST_ID_3)) {
#else
	if (data) {
#endif
		time_start = get_timer (0);
		while (1) {
			mmcst = sdiohost_getrdy(host);
			if (mmcst == true)
				break;
#ifdef CONFIG_NVT_IVOT_EMMC
			if (host->id != SDIO_HOST_ID_3) {
				time_now = get_timer (0);
				if ((time_now - time_start) > 1000)
					break;
			}
#else
			time_now = get_timer (0);
			if ((time_now - time_start) > 1000)
				break;
#endif
		}

		if (mmcst == false) {
			printf("still busy\n");
			return -ETIMEDOUT;
		}
	}

	if(dev->clock != cur_clk) {
		sdiohost_setdatatimeout(host, (dev->clock/1000)*300);
	}

	cur_clk = dev->clock;

	mmc_nvt_prepare_data(host);
	result = mmc_nvt_start_command(host);

	return result;
}

#if !CONFIG_IS_ENABLED(DM_MMC)
/* MMC uses open drain drivers in the enumeration phase */
static int mmc_host_reset(struct mmc *dev)
{
	return 0;
}
#endif

static int nvt_emmc_arch_host_preinit(struct mmc_nvt_host *host)
{
	if (host->id == SDIO_HOST_ID_1) {
#ifdef CONFIG_TARGET_NA51000
		*(uint32_t*) 0xF0010004 |= 0x4000;
		udelay(10);
		*(uint32_t*) 0xF00100A0 &= ~(0x3F0000);
		udelay(10);
		*(uint32_t*) 0xF00200A4 &= ~(0x4);
		udelay(10);
		*(uint32_t*) 0xF00200A4 |= 0x4;
		udelay(10);
		*(uint32_t*) 0xF0020020 &= ~0x30;
		udelay(10);
		*(uint32_t*) 0xF0020040 &= ~0x7FF;
		udelay(10);
		*(uint32_t*) 0xF0020040 |= 0x1F0;
		udelay(10);
		*(uint32_t*) 0xF0020084 |= 0x4;
#elif (defined(CONFIG_TARGET_NA51090) || defined(CONFIG_TARGET_NA51090_A64) || defined(CONFIG_TARGET_NA51103) || defined(CONFIG_TARGET_NA51103_A64))
#if (defined(CONFIG_TARGET_NA51103) || defined(CONFIG_TARGET_NA51103_A64))
		if (host->pinmux_value & PIN_SDIO_CFG_SDIO_2) {
			*(uint32_t*) 0xF0010004 &= ~0x30;          // SDIO_EN
			udelay(10);
			*(uint32_t*) 0xF0010004 |= 0x20;
			udelay(10);
			*(uint32_t*) 0xF00100A8 &= ~(0x300000);    // PGPIO20~21
			udelay(10);
			*(uint32_t*) 0xF00100B4 &= ~(0x3C0);       // DGPIO6~9
			udelay(10);

			*(uint32_t*) 0xF003000C &= ~(0xF00);       // PGPIO20~21 PU
			*(uint32_t*) 0xF003000C |= 0xA00;
			udelay(10);
			*(uint32_t*) 0xF0030018 &= ~(0xFF000);     // DGPIO6, DGPIO8~9 PU and DGPIO7 PD
			*(uint32_t*) 0xF0030018 |= 0xA6000;
			udelay(10);

			printf("use SDIO_2\n");
		} else {
			*(uint32_t*) 0xF0010004 &= ~0x30;          // SDIO_EN
			udelay(10);
			*(uint32_t*) 0xF0010004 |= 0x10;
			udelay(10);
			*(uint32_t*) 0xF00100A8 &= ~(0x8EC000);    // PGPIO14~15, PGPIO17~19, PGPIO23
			udelay(10);

			*(uint32_t*) 0xF0030008 &= ~(0xF0000000);  // PGPIO14~15 PU
			*(uint32_t*) 0xF0030008 |= 0xA0000000;
			*(uint32_t*) 0xF003000C &= ~(0xC0FC);      // PGPIO17, PGPIO19, PGPIO23 PU and PGPIO18 PD
			*(uint32_t*) 0xF003000C |= 0x8098;
			udelay(10);

			printf("use SDIO\n");
		}
#elif (defined(CONFIG_TARGET_NA51090) || defined(CONFIG_TARGET_NA51090_A64))
		*(uint32_t*) 0xF0010004 &= ~0x10;        // SDIO_EN
		udelay(10);
		*(uint32_t*) 0xF0010004 |= 0x10;
		udelay(10);
		*(uint32_t*) 0xF00100A8 &= ~(0x7B000);   // PGPIO12~13, PGPIO15~18
		udelay(10);
#endif
		*(uint32_t*) 0xF0020098 &= ~(0x4);       // SDIO_RSTN
		udelay(10);
		*(uint32_t*) 0xF0020098 |= 0x4;
		udelay(10);
		*(uint32_t*) 0xF0020020 &= ~0x3;         // SDIO_CLKSEL
		udelay(10);
		*(uint32_t*) 0xF0020040 &= ~0x7FF;       // SDIO_CLKDIV
		udelay(10);
		*(uint32_t*) 0xF0020040 |= 0x1F0;
		udelay(10);
		*(uint32_t*) 0xF0020074 |= 0x800;        // SDIO_CLKEN
		udelay(10);
#elif (defined(CONFIG_TARGET_NA51102) || defined(CONFIG_TARGET_NA51102_A64))
		*(uint32_t*) 0xF0010004 &= ~0x10;        // SDIO_EN
		udelay(10);
		*(uint32_t*) 0xF0010004 |= 0x10;
		udelay(10);
		*(uint32_t*) 0xF00100A0 &= ~(0x3F << 11);// PGPIO12~13, PGPIO15~18
		udelay(10);
		*(uint32_t*) 0xF0020094 &= ~(0x1 << 11); // SDIO_RSTN
		udelay(10);
		*(uint32_t*) 0xF0020094 |= (0x1 << 11);
		udelay(10);
		*(uint32_t*) 0xF0020020 &= ~0x3;         // SDIO_CLKSEL
		udelay(10);
		*(uint32_t*) 0xF0020040 &= ~0x7FF;       // SDIO_CLKDIV
		udelay(10);
		*(uint32_t*) 0xF0020040 |= 0x1F0;
		udelay(10);
		*(uint32_t*) 0xF0020074 |= 0x800;        // SDIO_CLKEN
		udelay(10);
#else
		*(uint32_t*) 0xF0010004 |= 0x4000;
		udelay(10);
		*(uint32_t*) 0xF00100A0 &= ~(0x1F800);
		udelay(10);
		*(uint32_t*) 0xF0020084 &= ~(0x4);
		udelay(10);
		*(uint32_t*) 0xF0020084 |= 0x4;
		udelay(10);
		*(uint32_t*) 0xF0020020 &= ~0x30;
		udelay(10);
		*(uint32_t*) 0xF002003C &= ~0x7FF;
		udelay(10);
		*(uint32_t*) 0xF002003C |= 0x1F0;
		udelay(10);
		*(uint32_t*) 0xF0020074 |= 0x4;
		udelay(10);
		*(uint32_t*) 0xF0030068 &= ~0xFFFFFF;
		udelay(10);
		*(uint32_t*) 0xF0030068 |= 0x111111;
#endif
	} else if (host->id == SDIO_HOST_ID_2) {
#ifdef CONFIG_TARGET_NA51089
		if (host->pinmux_value & PIN_SDIO_CFG_2ND_PINMUX) {
			*(uint32_t*) 0xF0010004 &= ~0xC0000;
			*(uint32_t*) 0xF0010004 |= 0x80000;
			udelay(10);
			*(uint32_t*) 0xF00100B0 &= ~(0x18F);
			udelay(10);
			*(uint32_t*) 0xF003004C &= ~0x3C0FF;
			udelay(10);
			*(uint32_t*) 0xF003004C |= 0x1;
			udelay(10);
			*(uint32_t*) 0xF003000C &= ~0x3C0FC;
			*(uint32_t*) 0xF003000C |= 0x280A8;
			udelay(10);
		} else if (host->pinmux_value & PIN_SDIO_CFG_3RD_PINMUX) {
			*(uint32_t*) 0xF0010004 &= ~0xC0000;
			*(uint32_t*) 0xF0010004 |= 0xC0000;
			udelay(10);
			*(uint32_t*) 0xF00100E8 &= ~(0xFC);
			udelay(10);
			*(uint32_t*) 0xF0030098 &= ~0xFFF0;
			udelay(10);
			*(uint32_t*) 0xF0030098 |= 0x10;
			udelay(10);
			*(uint32_t*) 0xF0030020 &= ~0x3FF0;
			*(uint32_t*) 0xF0030020 |= 0x2AA0;
			udelay(10);
		} else {
			*(uint32_t*) 0xF0010004 &= ~0xC0000;
			*(uint32_t*) 0xF0010004 |= 0x40000;
			udelay(10);
			*(uint32_t*) 0xF00100A0 &= ~(0x7E0000);
			udelay(10);
			*(uint32_t*) 0xF0030068 &= ~0xF000000;
			udelay(10);
			*(uint32_t*) 0xF0030068 |= 0x1000000;
			udelay(10);
			*(uint32_t*) 0xF0030044 = 0x0;
			udelay(10);
		}
#else
		*(uint32_t*) 0xF0010004 |= 0x8000;
		udelay(10);
		*(uint32_t*) 0xF00100A0 &= ~(0x7E0000);
		udelay(10);
		*(uint32_t*) 0xF0030068 &= ~0xF000000;
		udelay(10);
		*(uint32_t*) 0xF0030068 |= 0x1000000;
		udelay(10);
		*(uint32_t*) 0xF0030044 = 0x0;
		udelay(10);
#endif
		*(uint32_t*) 0xF0020084 &= ~(0x8);
		udelay(10);
		*(uint32_t*) 0xF0020084 |= 0x8;
		udelay(10);
		*(uint32_t*) 0xF0020020 &= ~0x300;
		udelay(10);
		*(uint32_t*) 0xF002003C &= ~0x7FF0000;
		udelay(10);
		*(uint32_t*) 0xF002003C |= 0x1F00000;
		udelay(10);
		*(uint32_t*) 0xF0020074 |= 0x8;
	} else {
		*(uint32_t*) 0xF0010004 &= ~(0x80003000);
		udelay(10);
		if (host->enable_8bits) {
			*(uint32_t*) 0xF0010004 |= 0x80020000;
			//udelay(10);
			*(uint32_t*) 0xF00100A0 &= ~(0x3FF);
		} else {
			*(uint32_t*) 0xF0010004 |= 0x20000;
			udelay(10);
			*(uint32_t*) 0xF00100A0 &= ~(0x30F);
		}

		udelay(10);
		*(uint32_t*) 0xF0020084 &= ~(0x4000);
		udelay(10);
		*(uint32_t*) 0xF0020084 |= 0x4000;
		udelay(10);
		*(uint32_t*) 0xF0020074 &= ~0x1;
		udelay(10);
		*(uint32_t*) 0xF0020024 &= ~0x3;
		udelay(10);
		*(uint32_t*) 0xF0020040 &= ~0x7FF;
		udelay(10);
		*(uint32_t*) 0xF0020040 |= 0x1F0;
		udelay(10);
		*(uint32_t*) 0xF0020074 |= 0x4000;
	}

	sdiohost_enclkout(host, TRUE);

	sdiohost_setpaddriving(host, SDIO_MODE_DS);

	#if (defined(CONFIG_TARGET_NA51000) || defined(CONFIG_TARGET_NA51055))
	if (nvt_get_chip_id() == CHIP_NA51084) {
		sdiohost_setphysample(host, FALSE, TRUE);
	} else {
		sdiohost_setphysample(host, TRUE, FALSE);
	}
	#else
	sdiohost_setphysample(host, FALSE, TRUE);
	#endif

	sdiohost_resetdata(host);

	/* Delay 1 ms (SD spec) after clock is outputted. */
	/* (Delay 1024 us to reduce code size) */
	udelay(1024);

	sdiohost_setreg(host, SDIO_INT_MASK_REG_OFS, 0xFF);
	sdiohost_setdatatimeout(host, 0x10000000);

	sdiohost_setreg(host, 0x1FC, 0x1);

	return E_OK;
}

#if !CONFIG_IS_ENABLED(DM_MMC)
static int host_set_ios(struct mmc *dev)
{
	struct mmc_nvt_host *host = dev->priv;
#else
static int host_set_ios(struct udevice *udev)
{
	struct mmc *dev = mmc_get_mmc_dev(udev);
	struct mmc_nvt_host *host = dev_get_priv(udev);
#endif
	//kwinyee debug
	//printf("%s called !,clk = %d, bwidth = %d\n",__func__, dev->clock, dev->bus_width);

	if (host->id >= 3) {
		printf("invalid host id %d\n", host->id);
		return -1;
	}

	if (dev->bus_width) {
		switch (dev->bus_width) {
		case 8:
			sdiohost_setbuswidth(host, SDIO_BUS_WIDTH8);
			break;
		case 4:
			sdiohost_setbuswidth(host, SDIO_BUS_WIDTH4);
			break;
		case 1:
			sdiohost_setbuswidth(host, SDIO_BUS_WIDTH1);
			break;
		}
	}

	if (dev->clock) {
		static int cur_clk[3] = {0};
		if(dev->clock != cur_clk[host->id]) {
			sdiohost_setbusclk(host, dev->clock, (u32*)&host->ns_in_one_cycle);

			/*Set host sampling strategy according to different speeds and modes*/
			if (dev->clock >= 192000000) {
				//sdiohost_setphyclkindly(host, 0x9);
				sdiohost_setpaddriving(host, SDIO_MODE_SDR104);
				sdiohost_setblkfifoen(host, true);
			} else if (dev->clock >= 160000000) {
				//sdiohost_setphyclkindly(host, 0x3);
				sdiohost_setpaddriving(host, SDIO_MODE_SDR104);
				sdiohost_setblkfifoen(host, true);
			} else if (dev->clock >= 120000000) {
				//sdiohost_setphyclkindly(host, 0x1f);
				sdiohost_setpaddriving(host, SDIO_MODE_SDR104);
				sdiohost_setblkfifoen(host, true);
			} else if (dev->clock >= 96000000) {
				//sdiohost_setphyclkindly(host, 0x1b);
				sdiohost_setpaddriving(host, SDIO_MODE_SDR50);
				sdiohost_setblkfifoen(host, true);
			} else if (dev->clock >= 64000000) {
				//sdiohost_setphyclkindly(host, 0x0);
				sdiohost_setpaddriving(host, SDIO_MODE_SDR50);
				sdiohost_setblkfifoen(host, true);
			} else if (dev->clock >= 25000000) {
				//sdiohost_setphyclkindly(host, 0x0);
				sdiohost_setpaddriving(host, SDIO_MODE_HS);
				sdiohost_setblkfifoen(host, false);
			} else {
				//sdiohost_setphyclkindly(host, 0x0);
				sdiohost_setpaddriving(host, SDIO_MODE_DS);
				sdiohost_setblkfifoen(host, false);
			}

			if (host->indly_sel >= 0) {
				sdiohost_setphyclkindly(host, host->indly_sel);
				printf("use dts indly(0x%x)\n", host->indly_sel);
			}

			sdiohost_setinvoutdly(host, false, false);

			/*Reset phy to take effect*/
			sdiohost_setphyrst(host);
			sdiohost_reset(host);
		}

		cur_clk[host->id] = dev->clock;
		host->mmc_input_clk = dev->clock;
	}

	return 0;
}

#if !CONFIG_IS_ENABLED(DM_MMC)
static int mmc_host_getcd(struct mmc *mmc)
#else
static int mmc_host_getcd(struct udevice *dev)
#endif
{
	return 1;
}

#define NVT_MMC0_NAME  "V" NVT_MMC_DRIVER_VERSION "_NVT_MMC0"
#define NVT_MMC1_NAME  "V" NVT_MMC_DRIVER_VERSION "_NVT_MMC1"
#define NVT_MMC2_NAME  "V" NVT_MMC_DRIVER_VERSION "_NVT_MMC2"

static char dev_name_0[] = NVT_MMC0_NAME;
static char dev_name_1[] = NVT_MMC1_NAME;
static char dev_name_2[] = NVT_MMC2_NAME;

#if !CONFIG_IS_ENABLED(DM_MMC)
static const struct mmc_ops nvt_hsmmc_ops = {
	.send_cmd = host_request,
	.set_ios = host_set_ios,
	.init = mmc_host_reset,
	.getcd = mmc_host_getcd,
};
/*
 * mmc_host_init - initialize the mmc controller.
 * Set initial clock and power for mmc slot.
 * Initialize mmc struct and register with mmc framework.
 */
int nvt_mmc_init(int id)
{
	struct mmc *dev;
	struct mmc_nvt_host *host;
	struct mmc_config *cfg;

	host = malloc(sizeof(struct mmc_nvt_host));
	if (!host)
		return -ENOMEM;

	memset(host, 0, sizeof(struct mmc_nvt_host));

	cfg = malloc(sizeof(struct mmc_config));
	if (!cfg)
		return -ENOMEM;

	memset(cfg, 0, sizeof(struct mmc_config));


	//init host controler
	host->id = id;
	if (id == SDIO_HOST_ID_1) {
		host->base = (u32*)IOADDR_SDIO_REG_BASE;
		cfg->name = dev_name_0;
	} else if (id == SDIO_HOST_ID_2) {
		host->base = (u32*)IOADDR_SDIO2_REG_BASE;
		cfg->name = dev_name_1;
	} else {
		host->base = (u32*)IOADDR_SDIO3_REG_BASE;
		cfg->name = dev_name_2;
	}

	host->mmc_input_clk = 312500;

	host->mmc_default_clk = 48000000;

	mmc_nvt_parse_driving(host);

	nvt_emmc_arch_host_preinit(host);

	cfg->voltages = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 \
			| MMC_VDD_30_31 | MMC_VDD_31_32 | MMC_VDD_32_33 \
			| MMC_VDD_33_34 | MMC_VDD_34_35 | MMC_VDD_35_36;
	cfg->f_min = 312500;
#ifdef CONFIG_NVT_FPGA_EMULATION
	cfg->f_max = 6000000;
#else
	cfg->f_max = host->mmc_default_clk;
#endif
	cfg->b_max = (32*1024);
	cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS;

	if(host->enable_8bits)
		cfg->host_caps |= MMC_MODE_8BIT;

	cfg->ops = &nvt_hsmmc_ops;

	dev = mmc_create(cfg, NULL);
	if (dev == NULL) {
		free(cfg);
		return -1;
	}

	dev->priv = host;

	debug("MMC DEBUG : %s done \n", __FUNCTION__);

	return 0;
}
#else

#ifdef MMC_SUPPORTS_TUNING
static void sdiohost_set_detmode(struct mmc_nvt_host *host, bool det_mode)
{
	union SDIO_DLY0_REG dly0_reg;

	dly0_reg.reg = sdiohost_getreg(host, SDIO_DLY0_REG_OFS);
	dly0_reg.bit.DET_MODE = det_mode;
	sdiohost_setreg(host, SDIO_DLY0_REG_OFS, dly0_reg.reg);
}

static void sdiohost_set_detdata(struct mmc_nvt_host *host, bool data_is_8bit)
{
	union SDIO_DLY0_REG dly0_reg;

	dly0_reg.reg = sdiohost_getreg(host, SDIO_DLY0_REG_OFS);
	dly0_reg.bit.DET_DATA = data_is_8bit;
	sdiohost_setreg(host, SDIO_DLY0_REG_OFS, dly0_reg.reg);
}

static void sdiohost_set_phasecmd(struct mmc_nvt_host *host, u32 sel)
{
	union SDIO_DLY0_REG dly0_reg;

	dly0_reg.reg = sdiohost_getreg(host, SDIO_DLY0_REG_OFS);
	dly0_reg.bit.DLY_PHASE_SEL = sel;
	sdiohost_setreg(host, SDIO_DLY0_REG_OFS, dly0_reg.reg);
}

static void sdiohost_set_phasedata(struct mmc_nvt_host *host, u32 sel)
{
	union SDIO_DLY0_REG dly0_reg;

	dly0_reg.reg = sdiohost_getreg(host, SDIO_DLY0_REG_OFS);
	dly0_reg.bit.DATA_DLY_PHASE_SEL = sel;
	sdiohost_setreg(host, SDIO_DLY0_REG_OFS, dly0_reg.reg);
}

static void sdiohost_set_phaseunit(struct mmc_nvt_host *host, u32 sel)
{
	union SDIO_DLY0_REG dly0_reg;

	dly0_reg.reg = sdiohost_getreg(host, SDIO_DLY0_REG_OFS);
	dly0_reg.bit.DLY_SEL = sel;
	sdiohost_setreg(host, SDIO_DLY0_REG_OFS, dly0_reg.reg);
}

static void sdiohost_set_detclr(struct mmc_nvt_host *host)
{
	union SDIO_DLY3_REG dly3_reg;

	dly3_reg.reg = sdiohost_getreg(host, SDIO_DLY3_REG_OFS);

	dly3_reg.bit.DET_CLR = 1;
	sdiohost_setreg(host, SDIO_DLY3_REG_OFS, dly3_reg.reg);

	dly3_reg.bit.DET_CLR = 0;
	sdiohost_setreg(host, SDIO_DLY3_REG_OFS, dly3_reg.reg);
}

static uint32_t sdiohost_get_detcmdout(struct mmc_nvt_host *host)
{
	union SDIO_DLY6_REG dly6_reg;

	dly6_reg.reg = sdiohost_getreg(host, SDIO_DLY6_REG_OFS);

	return dly6_reg.bit.DET_CMD_OUT;
}

static uint32_t sdiohost_get_detdataout(struct mmc_nvt_host *host)
{
	union SDIO_DLY2_REG dly2_reg;

	dly2_reg.reg = sdiohost_getreg(host, SDIO_DLY2_REG_OFS);

	return dly2_reg.bit.DET_DATA_OUT;
}

static void nvt_mmc_check_edge(u32 edge_result, u32 sample_clk_edge, u32 *zeropos, u32 *zerolength)
{
	u32 max_length = 0;
	u32 max_pos = 0;
	u32 this_length = 0;
	u32 this_pos = 0;
	u32 pos;

    if (sample_clk_edge == SDIO_HOST_SAMPLE_CLK_EDGE_NEG) {
        edge_result = (edge_result >> SDIO_DELAY_PHASE_SEL_MAX);
    } else if (sample_clk_edge == SDIO_HOST_SAMPLE_CLK_EDGE_BOTH) {
        edge_result |= (edge_result >> SDIO_DELAY_PHASE_SEL_MAX);
    }

	for (pos = 0; pos < SDIO_DELAY_PHASE_SEL_MAX; pos++) {
		if ((edge_result & (1 << pos)) == 0) {
			if (this_length == 0) {
				this_pos = pos;
			}
			this_length++;
		} else {
			if (this_length > max_length) {
				max_length = this_length;
				max_pos = this_pos;
			}
			this_length = 0;
		}
	}

	if ((max_length == 0) || (this_length > max_length)) {
		max_pos = this_pos;
		max_length = this_length;
	}

    // move BestPos to the center of zero bit
    if (max_length) {
        max_pos += (max_length >> 1);
    }

	*zeropos = max_pos;
	*zerolength = max_length;
}

static int nvt_mmc_auto_tuning(struct udevice *dev, uint opcode)
{
    struct nvt_mmc_plat *plat = dev_get_platdata(dev);
    struct mmc *mmc = plat ? &plat->mmc : NULL;
    struct mmc_nvt_host *host = dev_get_priv(dev);
    SDIO_HOST_SAMPLE_CLK_EDGE sample_clk_edge;
    u32 det_cmd_out, det_data_out;
    u32 cmd_phase_sel, data_phase_sel, zero_len;
    u32 i, j, result;

    // check tuning requirement
    if (mmc == NULL) {
        printf("cannot find mmc param\n");
        return E_PAR;
    }

    if (mmc->signal_voltage != MMC_SIGNAL_VOLTAGE_180) {
        printf("signal_voltage %d not 1.8V, tuning cmd is illegal\n",
               mmc->signal_voltage);
        return E_SYS;
    }

    if (mmc->clock <= 48000000) {
        printf("clk %d is less/equal than 48MHz, skip tuning\n",
               mmc->clock);
        return E_OK;
    }

    if (host->ext_caps & MMC_MODE_HS400) {
        printf("skip tuning on HS400\n");
        return E_OK;
    }

    sdiohost_set_detmode(host, SDIO_HOST_DET_MODE_AUTO);

    sdiohost_set_detdata(host, (mmc->bus_width == 8));

    // TODO: use SDIO_HOST_SAMPLE_CLK_EDGE_BOTH in DDR speed mode(e.x. DDR50, HS400)
    sample_clk_edge = host->neg_sample_edge;

    for (i = (SDIO_DELAY_PHASE_UNIT_MAX-1); i < SDIO_DELAY_PHASE_UNIT_MAX; i--) {
        det_cmd_out = 0;
        det_data_out= 0;
        sdiohost_set_phaseunit(host, i);
        sdiohost_setphyrst(host);

        for (j = 0; j < SDIO_AUTO_TUNE_REPEAT; j++) {
            sdiohost_set_detclr(host);

            result = mmc_send_tuning(mmc, opcode, (int *)&result);
            if(result) {
                // use HW default
                sdiohost_set_phaseunit(host, 0);
                sdiohost_setphyrst(host);

                return E_SYS;
            }

            det_cmd_out |= sdiohost_get_detcmdout(host);
            det_data_out |= sdiohost_get_detdataout(host);
        }

        if (det_cmd_out && det_data_out) {
            nvt_mmc_check_edge(det_cmd_out, sample_clk_edge, &cmd_phase_sel, &zero_len);
            if (zero_len < SDIO_EDGE_RESULT_THD) {
                continue;
            }

            nvt_mmc_check_edge(det_data_out, sample_clk_edge, &data_phase_sel, &zero_len);
            if (zero_len < SDIO_EDGE_RESULT_THD) {
                continue;
            }

            sdiohost_set_phasecmd(host, cmd_phase_sel);
            sdiohost_set_phasedata(host, data_phase_sel);
            sdiohost_setphyrst(host);

            break;
        }
    }

	return E_OK;
}
#endif

static const struct dm_mmc_ops nvt_hsmmc_ops = {
	.send_cmd = host_request,
	.set_ios = host_set_ios,
	.get_cd = mmc_host_getcd,
#ifdef MMC_SUPPORTS_TUNING
	.execute_tuning = nvt_mmc_auto_tuning,
#endif
};

static int nvt_mmc_probe(struct udevice *dev)
{
	struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
	struct nvt_mmc_plat *plat = dev_get_platdata(dev);
	struct mmc_nvt_host *host = dev_get_priv(dev);
	struct mmc_config *cfg = &plat->cfg;
	u32 base_addr;

	host->base = (void*)dev_read_addr(dev);
	base_addr = (u32) host->base;

	if (base_addr == IOADDR_SDIO_REG_BASE) {
		host->id = SDIO_HOST_ID_1;
		cfg->name = dev_name_0;
	} else if (base_addr == IOADDR_SDIO2_REG_BASE) {
		host->id = SDIO_HOST_ID_2;
		cfg->name = dev_name_1;
	} else if (base_addr == IOADDR_SDIO3_REG_BASE) {
		host->id = SDIO_HOST_ID_3;
		cfg->name = dev_name_2;
	}

	host->mmc_input_clk = 312500;

	host->mmc_default_clk = 48000000;

	host->ext_caps = 0;

	host->do_dma = 1;

	mmc_nvt_parse_driving(host);

	nvt_emmc_arch_host_preinit(host);

	cfg->voltages = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 \
			| MMC_VDD_30_31 | MMC_VDD_31_32 | MMC_VDD_32_33 \
			| MMC_VDD_33_34 | MMC_VDD_34_35 | MMC_VDD_35_36 \
			| MMC_VDD_165_195;
#ifdef CONFIG_NVT_FPGA_EMULATION
	cfg->f_min = 312500;
	cfg->f_max = 6000000;
#else
	cfg->f_min = 312500;
	cfg->f_max = host->mmc_default_clk;
#endif
	cfg->b_max = (32*1024);
	cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS;

	if (host->enable_8bits)
		cfg->host_caps |= MMC_MODE_8BIT;

	if (host->ext_caps) {
		cfg->host_caps |= host->ext_caps;
	}

	upriv->mmc = &plat->mmc;

	return 0;
}

#if CONFIG_BLK
static int nvt_mmc_bind(struct udevice *dev)
{
	struct nvt_mmc_plat *plat = dev_get_platdata(dev);

	return mmc_bind(dev, &plat->mmc, &plat->cfg);
}
#endif

static int nvt_mmc_remove(struct udevice *dev)
{
	struct mmc_nvt_host *host = dev_get_priv(dev);

	if (host->id == SDIO_HOST_ID_1) {
#ifdef CONFIG_SD_CARD1_POWER_PIN
		u32 reg_val;

		gpio_request(CONFIG_SD_CARD1_POWER_PIN, "sdio1_power");
		gpio_request(C_GPIO(11), "sdio1_clk");
		gpio_request(C_GPIO(12), "sdio1_cmd");
		gpio_request(C_GPIO(13), "sdio1_d0");
		gpio_request(C_GPIO(14), "sdio1_d1");
		gpio_request(C_GPIO(15), "sdio1_d2");
		gpio_request(C_GPIO(16), "sdio1_d3");

		if (CONFIG_SD_CARD1_ON_STATE)
			gpio_direction_output(CONFIG_SD_CARD1_POWER_PIN, 0);
		else
			gpio_direction_output(CONFIG_SD_CARD1_POWER_PIN, 1);

		reg_val = readl(IOADDR_TOP_REG_BASE + 0xA0);
		reg_val |= 0x1F800;
		writel(reg_val, IOADDR_TOP_REG_BASE + 0xA0);

		reg_val = readl(IOADDR_PAD_REG_BASE);
		reg_val &= ~0xFFC00000;
		reg_val |= 0x55400000;
		writel(reg_val, IOADDR_PAD_REG_BASE);

		reg_val = readl(IOADDR_PAD_REG_BASE + 0x4);
		reg_val &= ~0x3;
		reg_val |= 0x1;
		writel(reg_val, IOADDR_PAD_REG_BASE + 0x4);

		gpio_direction_output(C_GPIO(11), 0);
		gpio_direction_output(C_GPIO(12), 0);
		gpio_direction_output(C_GPIO(13), 0);
		gpio_direction_output(C_GPIO(14), 0);
		gpio_direction_output(C_GPIO(15), 0);
		gpio_direction_output(C_GPIO(16), 0);

		/*Disable SDIO CLK*/
		reg_val = readl(IOADDR_CG_REG_BASE + 0x74);
		reg_val &= ~(0x1 << 2);
		writel(reg_val, IOADDR_CG_REG_BASE + 0x74);
#endif
	} else if (host->id == SDIO_HOST_ID_2) {
#ifdef CONFIG_SD_CARD2_POWER_PIN
		u32 reg_val;
		int ret;

		gpio_request(CONFIG_SD_CARD2_POWER_PIN, "sdio2_power");
		gpio_request(C_GPIO(17), "sdio2_clk");
		gpio_request(C_GPIO(18), "sdio2_cmd");
		gpio_request(C_GPIO(19), "sdio2_d0");
		gpio_request(C_GPIO(20), "sdio2_d1");
		gpio_request(C_GPIO(21), "sdio2_d2");
		gpio_request(C_GPIO(22), "sdio2_d3");

		if (CONFIG_SD_CARD2_ON_STATE)
			gpio_direction_output(CONFIG_SD_CARD2_POWER_PIN, 0);
		else
			gpio_direction_output(CONFIG_SD_CARD2_POWER_PIN, 1);

		reg_val = readl(IOADDR_TOP_REG_BASE + 0xA0);
		reg_val |= 0x7E0000;
		writel(reg_val, IOADDR_TOP_REG_BASE + 0xA0);

		reg_val = readl(IOADDR_PAD_REG_BASE + 0x4);
		reg_val &= ~0x3FFC;
		reg_val |= 0x1554;
		writel(reg_val, IOADDR_PAD_REG_BASE + 0x4);

		gpio_direction_output(C_GPIO(17), 0);
		gpio_direction_output(C_GPIO(18), 0);
		gpio_direction_output(C_GPIO(19), 0);
		gpio_direction_output(C_GPIO(20), 0);
		gpio_direction_output(C_GPIO(21), 0);
		gpio_direction_output(C_GPIO(22), 0);

		/*Disable SDIO CLK*/
		reg_val = readl(IOADDR_CG_REG_BASE + 0x74);
		reg_val &= ~(0x1 << 3);
		writel(reg_val, IOADDR_CG_REG_BASE + 0x74);
#endif
	}
	return 0;
}

static const struct udevice_id nvt_mmc_ids[] = {
	{
		.compatible = "nvt,nvt_mmc",
	},
	{
		.compatible = "nvt,nvt_mmc2",
	},
	{
		.compatible = "nvt,nvt_mmc3",
	},
	{},
};

U_BOOT_DRIVER(nvt_mmc_drv) = {
	.name = "nvtivot_mmc",
	.id		= UCLASS_MMC,
	.of_match	= nvt_mmc_ids,
#if CONFIG_BLK
	.bind		= nvt_mmc_bind,
#endif
	.probe = nvt_mmc_probe,
	.ops = &nvt_hsmmc_ops,
	.platdata_auto_alloc_size = sizeof(struct nvt_mmc_plat),
	.priv_auto_alloc_size = sizeof(struct mmc_nvt_host),
	.remove		= nvt_mmc_remove,
	.flags		= DM_FLAG_OS_PREPARE,
};
#endif
