/*
 * Copyright (c) 2019 NXP
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT nxp_imx_usdhc

#include <sys/__assert.h>
#include <drivers/disk.h>
#include <drivers/gpio.h>
#include <sys/byteorder.h>
#include <soc.h>
#include <drivers/clock_control.h>

#include "sdmmc_sdhc.h"

#include <logging/log.h>
LOG_MODULE_REGISTER(usdhc, CONFIG_SDMMC_LOG_LEVEL);

enum usdhc_cmd_type {
	USDHC_CMD_TYPE_NORMAL = 0U,
	/*!< Normal command */
	USDHC_CMD_TYPE_SUSPEND = 1U,
	/*!< Suspend command */
	USDHC_CMD_TYPE_RESUME = 2U,
	/*!< Resume command */
	USDHC_CMD_TYPE_ABORT = 3U,
	/*!< Abort command */
	USDHC_CMD_TYPE_EMPTY = 4U,
	/*!< Empty command */
};

enum usdhc_status_flag {
	USDHC_CMD_INHIBIT_FLAG =
		USDHC_PRES_STATE_CIHB_MASK,
	/*!< Command inhibit */
	USDHC_DATA_INHIBIT_FLAG =
		USDHC_PRES_STATE_CDIHB_MASK,
	/*!< Data inhibit */
	USDHC_DATA_LINE_ACTIVE_FLAG =
		USDHC_PRES_STATE_DLA_MASK,
	/*!< Data line active */
	USDHC_SD_CLK_STATUS_FLAG =
		USDHC_PRES_STATE_SDSTB_MASK,
	/*!< SD bus clock stable */
	USDHC_WRITE_ACTIVE_FLAG =
		USDHC_PRES_STATE_WTA_MASK,
	/*!< Write transfer active */
	USDHC_READ_ACTIVE_FLAG =
		USDHC_PRES_STATE_RTA_MASK,
	/*!< Read transfer active */
	USDHC_BUF_WRITE_ENABLE_FLAG =
		USDHC_PRES_STATE_BWEN_MASK,
	/*!< Buffer write enable */
	USDHC_BUF_READ_ENABLE_FLAG =
		USDHC_PRES_STATE_BREN_MASK,
	/*!< Buffer read enable */
	USDHC_RETUNING_REQ_FLAG =
		USDHC_PRES_STATE_RTR_MASK,
	/*!< re-tuning request flag ,only used for SDR104 mode */
	USDHC_DELAY_SETTING_DONE_FLAG =
		USDHC_PRES_STATE_TSCD_MASK,
	/*!< delay setting finished flag */
	USDHC_CARD_INSERTED_FLAG =
		USDHC_PRES_STATE_CINST_MASK,
	/*!< Card inserted */
	USDHC_CMD_LINE_LEVEL_FLAG =
		USDHC_PRES_STATE_CLSL_MASK,
	/*!< Command line signal level */
	USDHC_DATA0_LINE_LEVEL_FLAG =
		1U << USDHC_PRES_STATE_DLSL_SHIFT,
	/*!< Data0 line signal level */
	USDHC_DATA1_LINE_LEVEL_FLAG =
		1U << (USDHC_PRES_STATE_DLSL_SHIFT + 1U),
	/*!< Data1 line signal level */
	USDHC_DATA2_LINE_LEVEL_FLAG =
		1U << (USDHC_PRES_STATE_DLSL_SHIFT + 2U),
	/*!< Data2 line signal level */
	USDHC_DATA3_LINE_LEVEL_FLAG =
		1U << (USDHC_PRES_STATE_DLSL_SHIFT + 3U),
	/*!< Data3 line signal level */
	USDHC_DATA4_LINE_LEVEL_FLAG =
		1U << (USDHC_PRES_STATE_DLSL_SHIFT + 4U),
	/*!< Data4 line signal level */
	USDHC_DATA5_LINE_LEVEL_FLAG =
		1U << (USDHC_PRES_STATE_DLSL_SHIFT + 5U),
	/*!< Data5 line signal level */
	USDHC_DATA6_LINE_LEVEL_FLAG =
		1U << (USDHC_PRES_STATE_DLSL_SHIFT + 6U),
	/*!< Data6 line signal level */
	USDHC_DATA7_LINE_LEVEL_FLAG =
		(int)(1U << (USDHC_PRES_STATE_DLSL_SHIFT + 7U)),
	/*!< Data7 line signal level */
};

enum usdhc_transfer_flag {
	USDHC_ENABLE_DMA_FLAG =
		USDHC_MIX_CTRL_DMAEN_MASK,
	/*!< Enable DMA */

	USDHC_CMD_TYPE_SUSPEND_FLAG =
		(USDHC_CMD_XFR_TYP_CMDTYP(1U)),
	/*!< Suspend command */
	USDHC_CMD_TYPE_RESUME_FLAG =
		(USDHC_CMD_XFR_TYP_CMDTYP(2U)),
	/*!< Resume command */
	USDHC_CMD_TYPE_ABORT_FLAG =
		(USDHC_CMD_XFR_TYP_CMDTYP(3U)),
	/*!< Abort command */

	USDHC_BLOCK_COUNT_FLAG =
		USDHC_MIX_CTRL_BCEN_MASK,
	/*!< Enable block count */
	USDHC_AUTO_CMD12_FLAG =
		USDHC_MIX_CTRL_AC12EN_MASK,
	/*!< Enable auto CMD12 */
	USDHC_DATA_READ_FLAG =
		USDHC_MIX_CTRL_DTDSEL_MASK,
	/*!< Enable data read */
	USDHC_MULTIPLE_BLOCK_FLAG =
		USDHC_MIX_CTRL_MSBSEL_MASK,
	/*!< Multiple block data read/write */
	USDHC_AUTO_CMD23FLAG =
		USDHC_MIX_CTRL_AC23EN_MASK,
	/*!< Enable auto CMD23 */
	USDHC_RSP_LEN_136_FLAG =
		USDHC_CMD_XFR_TYP_RSPTYP(1U),
	/*!< 136 bit response length */
	USDHC_RSP_LEN_48_FLAG =
		USDHC_CMD_XFR_TYP_RSPTYP(2U),
	/*!< 48 bit response length */
	USDHC_RSP_LEN_48_BUSY_FLAG =
		USDHC_CMD_XFR_TYP_RSPTYP(3U),
	/*!< 48 bit response length with busy status */

	USDHC_CRC_CHECK_FLAG =
		USDHC_CMD_XFR_TYP_CCCEN_MASK,
	/*!< Enable CRC check */
	USDHC_IDX_CHECK_FLAG =
		USDHC_CMD_XFR_TYP_CICEN_MASK,
	/*!< Enable index check */
	USDHC_DATA_PRESENT_FLAG =
		USDHC_CMD_XFR_TYP_DPSEL_MASK,
	/*!< Data present flag */
};

enum usdhc_int_status_flag {
	USDHC_INT_CMD_DONE_FLAG =
		USDHC_INT_STATUS_CC_MASK,
	/*!< Command complete */
	USDHC_INT_DATA_DONE_FLAG =
		USDHC_INT_STATUS_TC_MASK,
	/*!< Data complete */
	USDHC_INT_BLK_GAP_EVENT_FLAG =
		USDHC_INT_STATUS_BGE_MASK,
	/*!< Block gap event */
	USDHC_INT_DMA_DONE_FLAG =
		USDHC_INT_STATUS_DINT_MASK,
	/*!< DMA interrupt */
	USDHC_INT_BUF_WRITE_READY_FLAG =
		USDHC_INT_STATUS_BWR_MASK,
	/*!< Buffer write ready */
	USDHC_INT_BUF_READ_READY_FLAG =
		USDHC_INT_STATUS_BRR_MASK,
	/*!< Buffer read ready */
	USDHC_INT_CARD_INSERTED_FLAG =
		USDHC_INT_STATUS_CINS_MASK,
	/*!< Card inserted */
	USDHC_INT_CARD_REMOVED_FLAG =
		USDHC_INT_STATUS_CRM_MASK,
	/*!< Card removed */
	USDHC_INT_CARD_INTERRUPT_FLAG =
		USDHC_INT_STATUS_CINT_MASK,
	/*!< Card interrupt */

	USDHC_INT_RE_TUNING_EVENT_FLAG =
		USDHC_INT_STATUS_RTE_MASK,
	/*!< Re-Tuning event,only for SD3.0 SDR104 mode */
	USDHC_INT_TUNING_PASS_FLAG =
		USDHC_INT_STATUS_TP_MASK,
	/*!< SDR104 mode tuning pass flag */
	USDHC_INT_TUNING_ERR_FLAG =
		USDHC_INT_STATUS_TNE_MASK,
	/*!< SDR104 tuning error flag */

	USDHC_INT_CMD_TIMEOUT_FLAG =
		USDHC_INT_STATUS_CTOE_MASK,
	/*!< Command timeout error */
	USDHC_INT_CMD_CRC_ERR_FLAG =
		USDHC_INT_STATUS_CCE_MASK,
	/*!< Command CRC error */
	USDHC_INT_CMD_ENDBIT_ERR_FLAG =
		USDHC_INT_STATUS_CEBE_MASK,
	/*!< Command end bit error */
	USDHC_INT_CMD_IDX_ERR_FLAG =
		USDHC_INT_STATUS_CIE_MASK,
	/*!< Command index error */
	USDHC_INT_DATA_TIMEOUT_FLAG =
		USDHC_INT_STATUS_DTOE_MASK,
	/*!< Data timeout error */
	USDHC_INT_DATA_CRC_ERR_FLAG =
		USDHC_INT_STATUS_DCE_MASK,
	/*!< Data CRC error */
	USDHC_INT_DATA_ENDBIT_ERR_FLAG =
		USDHC_INT_STATUS_DEBE_MASK,
	/*!< Data end bit error */
	USDHC_INT_AUTO_CMD12_ERR_FLAG =
		USDHC_INT_STATUS_AC12E_MASK,
	/*!< Auto CMD12 error */
	USDHC_INT_DMA_ERR_FLAG =
		USDHC_INT_STATUS_DMAE_MASK,
	/*!< DMA error */

	USDHC_INT_CMD_ERR_FLAG =
		(USDHC_INT_CMD_TIMEOUT_FLAG |
		USDHC_INT_CMD_CRC_ERR_FLAG |
		USDHC_INT_CMD_ENDBIT_ERR_FLAG |
		USDHC_INT_CMD_IDX_ERR_FLAG),
	/*!< Command error */
	USDHC_INT_DATA_ERR_FLAG =
		(USDHC_INT_DATA_TIMEOUT_FLAG |
		USDHC_INT_DATA_CRC_ERR_FLAG |
		USDHC_INT_DATA_ENDBIT_ERR_FLAG |
		USDHC_INT_AUTO_CMD12_ERR_FLAG),
	/*!< Data error */
	USDHC_INT_ERR_FLAG =
		(USDHC_INT_CMD_ERR_FLAG |
		USDHC_INT_DATA_ERR_FLAG |
		USDHC_INT_DMA_ERR_FLAG),
	/*!< All error */
	USDHC_INT_DATA_FLAG =
		(USDHC_INT_DATA_DONE_FLAG |
		USDHC_INT_DMA_DONE_FLAG |
		USDHC_INT_BUF_WRITE_READY_FLAG |
		USDHC_INT_BUF_READ_READY_FLAG |
		USDHC_INT_DATA_ERR_FLAG |
		USDHC_INT_DMA_ERR_FLAG),
	/*!< Data interrupts */
	USDHC_INT_CMD_FLAG =
		(USDHC_INT_CMD_DONE_FLAG |
		USDHC_INT_CMD_ERR_FLAG),
	/*!< Command interrupts */
	USDHC_INT_CARD_DETECT_FLAG =
		(USDHC_INT_CARD_INSERTED_FLAG |
		USDHC_INT_CARD_REMOVED_FLAG),
	/*!< Card detection interrupts */
	USDHC_INT_SDR104_TUNING_FLAG =
		(USDHC_INT_RE_TUNING_EVENT_FLAG |
		USDHC_INT_TUNING_PASS_FLAG |
		USDHC_INT_TUNING_ERR_FLAG),

	USDHC_INT_ALL_FLAGS =
		(USDHC_INT_BLK_GAP_EVENT_FLAG |
		USDHC_INT_CARD_INTERRUPT_FLAG |
		USDHC_INT_CMD_FLAG |
		USDHC_INT_DATA_FLAG |
		USDHC_INT_ERR_FLAG |
		USDHC_INT_SDR104_TUNING_FLAG),
	/*!< All flags mask */
};

enum usdhc_data_bus_width {
	USDHC_DATA_BUS_WIDTH_1BIT = 0U,
	/*!< 1-bit mode */
	USDHC_DATA_BUS_WIDTH_4BIT = 1U,
	/*!< 4-bit mode */
	USDHC_DATA_BUS_WIDTH_8BIT = 2U,
	/*!< 8-bit mode */
};

#define USDHC_MAX_BLOCK_COUNT	\
	(USDHC_BLK_ATT_BLKCNT_MASK >>	\
	USDHC_BLK_ATT_BLKCNT_SHIFT)

struct usdhc_cmd {
	uint32_t index;	/*cmd idx*/
	uint32_t argument;	/*cmd arg*/
	enum usdhc_cmd_type cmd_type;
	enum sdhc_rsp_type rsp_type;
	uint32_t response[4U];
	uint32_t rsp_err_flags;
	uint32_t flags;
};

struct usdhc_data {
	bool cmd12;
	/* Enable auto CMD12 */
	bool cmd23;
	/* Enable auto CMD23 */
	bool ignore_err;
	/* Enable to ignore error event
	 * to read/write all the data
	 */
	bool data_enable;
	uint8_t data_type;
	/* this is used to distinguish
	 * the normal/tuning/boot data
	 */
	uint32_t block_size;
	/* Block size
	 */
	uint32_t block_count;
	/* Block count
	 */
	uint32_t *rx_data;
	/* Buffer to save data read
	 */
	const uint32_t *tx_data;
	/* Data buffer to write
	 */
};

enum usdhc_dma_mode {
	USDHC_DMA_SIMPLE = 0U,
	/* external DMA
	 */
	USDHC_DMA_ADMA1 = 1U,
	/* ADMA1 is selected
	 */
	USDHC_DMA_ADMA2 = 2U,
	/* ADMA2 is selected
	 */
	USDHC_EXT_DMA = 3U,
	/* external dma mode select
	 */
};

enum usdhc_burst_len {
	USDHC_INCR_BURST_LEN = 0x01U,
	/* enable burst len for INCR
	 */
	USDHC_INCR4816_BURST_LEN = 0x02U,
	/* enable burst len for INCR4/INCR8/INCR16
	 */
	USDHC_INCR4816_BURST_LEN_WRAP = 0x04U,
	/* enable burst len for INCR4/8/16 WRAP
	 */
};

struct usdhc_adma_config {
	enum usdhc_dma_mode dma_mode;
	/* DMA mode
	 */
	enum usdhc_burst_len burst_len;
	/* burst len config
	 */
	uint32_t *adma_table;
	/* ADMA table address,
	 * can't be null if transfer way is ADMA1/ADMA2
	 */
	uint32_t adma_table_words;
	/* ADMA table length united as words,
	 * can't be 0 if transfer way is ADMA1/ADMA2
	 */
};

struct usdhc_context {
	bool cmd_only;
	struct usdhc_cmd cmd;
	struct usdhc_data data;
	struct usdhc_adma_config dma_cfg;
};

enum usdhc_endian_mode {
	USDHC_BIG_ENDIAN = 0U,
	/* Big endian mode
	 */
	USDHC_HALF_WORD_BIG_ENDIAN = 1U,
	/* Half word big endian mode
	 */
	USDHC_LITTLE_ENDIAN = 2U,
	/* Little endian mode
	 */
};

struct usdhc_config {
	USDHC_Type *base;
	const struct device *clock_dev;
	clock_control_subsys_t clock_subsys;
	uint8_t nusdhc;

	char *pwr_name;
	uint8_t pwr_pin;
	gpio_dt_flags_t pwr_flags;

	char *detect_name;
	uint8_t detect_pin;
	gpio_dt_flags_t detect_flags;

	bool no_1_8_v;

	uint32_t data_timeout;
	/* Data timeout value
	 */
	enum usdhc_endian_mode endian;
	/* Endian mode
	 */
	uint8_t read_watermark;
	/* Watermark level for DMA read operation.
	 * Available range is 1 ~ 128.
	 */
	uint8_t write_watermark;
	/* Watermark level for DMA write operation.
	 * Available range is 1 ~ 128.
	 */
	uint8_t read_burst_len;
	/* Read burst len
	 */
	uint8_t write_burst_len;
	/* Write burst len
	 */
};

struct usdhc_capability {
	uint32_t max_blk_len;
	uint32_t max_blk_cnt;
	uint32_t host_flags;
};

enum host_detect_type {
	SD_DETECT_GPIO_CD,
	/* sd card detect by CD pin through GPIO
	 */
	SD_DETECT_HOST_CD,
	/* sd card detect by CD pin through host
	 */
	SD_DETECT_HOST_DATA3,
	/* sd card detect by DAT3 pin through host
	 */
};

struct usdhc_client_info {
	uint32_t busclk_hz;
	uint32_t relative_addr;
	uint32_t version;
	uint32_t card_flags;
	uint32_t raw_cid[4U];
	uint32_t raw_csd[4U];
	uint32_t raw_scr[2U];
	uint32_t raw_ocr;
	struct sd_cid cid;
	struct sd_csd csd;
	struct sd_scr scr;
	uint32_t sd_block_count;
	uint32_t sd_block_size;
	enum sd_timing_mode sd_timing;
	enum sd_driver_strength driver_strength;
	enum sd_max_current max_current;
	enum sd_voltage voltage;
};

struct usdhc_priv {
	bool host_ready;
	uint8_t status;

	const struct device *pwr_gpio;
	const struct device *detect_gpio;
	struct gpio_callback detect_cb;

	enum host_detect_type detect_type;
	bool inserted;

	uint32_t src_clk_hz;

	const struct usdhc_config *config;
	struct usdhc_capability host_capability;

	struct usdhc_client_info card_info;

	struct usdhc_context op_context;
};

enum usdhc_xfer_data_type {
	USDHC_XFER_NORMAL = 0U,
	/* transfer normal read/write data
	 */
	USDHC_XFER_TUNING = 1U,
	/* transfer tuning data
	 */
	USDHC_XFER_BOOT = 2U,
	/* transfer boot data
	 */
	USDHC_XFER_BOOT_CONTINUOUS = 3U,
	/* transfer boot data continuous
	 */
};

#define USDHC_ADMA1_ADDRESS_ALIGN (4096U)
#define USDHC_ADMA1_LENGTH_ALIGN (4096U)
#define USDHC_ADMA2_ADDRESS_ALIGN (4U)
#define USDHC_ADMA2_LENGTH_ALIGN (4U)

#define USDHC_ADMA2_DESCRIPTOR_LENGTH_SHIFT (16U)
#define USDHC_ADMA2_DESCRIPTOR_LENGTH_MASK (0xFFFFU)
#define USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY	\
	(USDHC_ADMA2_DESCRIPTOR_LENGTH_MASK - 3U)

#define SWAP_WORD_BYTE_SEQUENCE(x) (__REV(x))
#define SWAP_HALF_WROD_BYTE_SEQUENCE(x) (__REV16(x))

#define SDMMCHOST_NOT_SUPPORT 0U

#define CARD_BUS_FREQ_50MHZ (0U)
#define CARD_BUS_FREQ_100MHZ0 (1U)
#define CARD_BUS_FREQ_100MHZ1 (2U)
#define CARD_BUS_FREQ_200MHZ (3U)

#define CARD_BUS_STRENGTH_0 (0U)
#define CARD_BUS_STRENGTH_1 (1U)
#define CARD_BUS_STRENGTH_2 (2U)
#define CARD_BUS_STRENGTH_3 (3U)
#define CARD_BUS_STRENGTH_4 (4U)
#define CARD_BUS_STRENGTH_5 (5U)
#define CARD_BUS_STRENGTH_6 (6U)
#define CARD_BUS_STRENGTH_7 (7U)

enum usdhc_adma_flag {
	USDHC_ADMA_SINGLE_FLAG = 0U,
	USDHC_ADMA_MUTI_FLAG = 1U,
};

enum usdhc_adma2_descriptor_flag {
	USDHC_ADMA2_VALID_FLAG = (1U << 0U),
	/* Valid flag
	 */
	USDHC_ADMA2_END_FLAG = (1U << 1U),
	/* End flag
	 */
	USDHC_ADMA2_INT_FLAG = (1U << 2U),
	/* Interrupt flag
	 */
	USDHC_ADMA2_ACTIVITY1_FLAG = (1U << 4U),
	/* Activity 1 mask
	 */
	USDHC_ADMA2_ACTIVITY2_FLAG = (1U << 5U),
	/* Activity 2 mask
	 */

	USDHC_ADMA2_NOP_FLAG =
		(USDHC_ADMA2_VALID_FLAG),
	/* No operation
	 */
	USDHC_ADMA2_RESERVED_FLAG =
		(USDHC_ADMA2_ACTIVITY1_FLAG |
		USDHC_ADMA2_VALID_FLAG),
	/* Reserved
	 */
	USDHC_ADMA2_XFER_FLAG =
		(USDHC_ADMA2_ACTIVITY2_FLAG |
		USDHC_ADMA2_VALID_FLAG),
	/* Transfer type
	 */
	USDHC_ADMA2_LINK_FLAG =
		(USDHC_ADMA2_ACTIVITY1_FLAG |
		USDHC_ADMA2_ACTIVITY2_FLAG |
		USDHC_ADMA2_VALID_FLAG),
	/* Link type
	 */
};

struct usdhc_adma2_descriptor {
	uint32_t attribute;
	/*!< The control and status field */
	const uint32_t *address;
	/*!< The address field */
};

enum usdhc_card_flag {
	USDHC_HIGH_CAPACITY_FLAG =
		(1U << 1U),
	/* Support high capacity
	 */
	USDHC_4BIT_WIDTH_FLAG =
		(1U << 2U),
	/* Support 4-bit data width
	 */
	USDHC_SDHC_FLAG =
		(1U << 3U),
	/* Card is SDHC
	 */
	USDHC_SDXC_FLAG =
		(1U << 4U),
	/* Card is SDXC
	 */
	USDHC_VOL_1_8V_FLAG =
		(1U << 5U),
	/* card support 1.8v voltage
	 */
	USDHC_SET_BLK_CNT_CMD23_FLAG =
		(1U << 6U),
	/* card support cmd23 flag
	 */
	USDHC_SPEED_CLASS_CONTROL_CMD_FLAG =
		(1U << 7U),
	/* card support speed class control flag
	 */
};

enum usdhc_capability_flag {
	USDHC_SUPPORT_ADMA_FLAG =
		USDHC_HOST_CTRL_CAP_ADMAS_MASK,
	/*!< Support ADMA */
	USDHC_SUPPORT_HIGHSPEED_FLAG =
		USDHC_HOST_CTRL_CAP_HSS_MASK,
	/*!< Support high-speed */
	USDHC_SUPPORT_DMA_FLAG =
		USDHC_HOST_CTRL_CAP_DMAS_MASK,
	/*!< Support DMA */
	USDHC_SUPPORT_SUSPEND_RESUME_FLAG =
		USDHC_HOST_CTRL_CAP_SRS_MASK,
	/*!< Support suspend/resume */
	USDHC_SUPPORT_V330_FLAG =
		USDHC_HOST_CTRL_CAP_VS33_MASK,
	/*!< Support voltage 3.3V */
	USDHC_SUPPORT_V300_FLAG =
		USDHC_HOST_CTRL_CAP_VS30_MASK,
	/*!< Support voltage 3.0V */
	USDHC_SUPPORT_V180_FLAG =
		USDHC_HOST_CTRL_CAP_VS18_MASK,
	/*!< Support voltage 1.8V */
	/* Put additional two flags in
	 * HTCAPBLT_MBL's position.
	 */
	USDHC_SUPPORT_4BIT_FLAG =
		(USDHC_HOST_CTRL_CAP_MBL_SHIFT << 0U),
	/*!< Support 4 bit mode */
	USDHC_SUPPORT_8BIT_FLAG =
		(USDHC_HOST_CTRL_CAP_MBL_SHIFT << 1U),
	/*!< Support 8 bit mode */
	/* sd version 3.0 new feature */
	USDHC_SUPPORT_DDR50_FLAG =
		USDHC_HOST_CTRL_CAP_DDR50_SUPPORT_MASK,
	/*!< support DDR50 mode */

#if defined(FSL_FEATURE_USDHC_HAS_SDR104_MODE) &&\
	(!FSL_FEATURE_USDHC_HAS_SDR104_MODE)
	USDHC_SUPPORT_SDR104_FLAG = 0,
	/*!< not support SDR104 mode */
#else
	USDHC_SUPPORT_SDR104_FLAG =
		USDHC_HOST_CTRL_CAP_SDR104_SUPPORT_MASK,
	/*!< support SDR104 mode */
#endif
#if defined(FSL_FEATURE_USDHC_HAS_SDR50_MODE) &&\
	(!FSL_FEATURE_USDHC_HAS_SDR50_MODE)
	USDHC_SUPPORT_SDR50_FLAG = 0U,
	/*!< not support SDR50 mode */
#else
	USDHC_SUPPORT_SDR50_FLAG =
		USDHC_HOST_CTRL_CAP_SDR50_SUPPORT_MASK,
	/*!< support SDR50 mode */
#endif
};

#define NXP_SDMMC_MAX_VOLTAGE_RETRIES (1000U)

#define CARD_DATA0_STATUS_MASK USDHC_DATA0_LINE_LEVEL_FLAG
#define CARD_DATA1_STATUS_MASK USDHC_DATA1_LINE_LEVEL_FLAG
#define CARD_DATA2_STATUS_MASK USDHC_DATA2_LINE_LEVEL_FLAG
#define CARD_DATA3_STATUS_MASK USDHC_DATA3_LINE_LEVEL_FLAG
#define CARD_DATA0_NOT_BUSY USDHC_DATA0_LINE_LEVEL_FLAG

#define SDHC_STANDARD_TUNING_START (10U)
/*!< standard tuning start point */
#define SDHC_TUINIG_STEP (2U)
/*!< standard tuning step */
#define SDHC_RETUNING_TIMER_COUNT (0U)
/*!< Re-tuning timer */

#define USDHC_MAX_DVS	\
	((USDHC_SYS_CTRL_DVS_MASK >>	\
	USDHC_SYS_CTRL_DVS_SHIFT) + 1U)
#define USDHC_MAX_CLKFS	\
	((USDHC_SYS_CTRL_SDCLKFS_MASK >>	\
	USDHC_SYS_CTRL_SDCLKFS_SHIFT) + 1U)
#define USDHC_PREV_DVS(x) ((x) -= 1U)
#define USDHC_PREV_CLKFS(x, y) ((x) >>= (y))

#define SDMMCHOST_SUPPORT_SDR104_FREQ SD_CLOCK_208MHZ

#define USDHC_ADMA_TABLE_WORDS (8U)
#define USDHC_ADMA2_ADDR_ALIGN (4U)
#define USDHC_READ_BURST_LEN (8U)
#define USDHC_WRITE_BURST_LEN (8U)
#define USDHC_DATA_TIMEOUT (0xFU)

#define USDHC_READ_WATERMARK_LEVEL (0x80U)
#define USDHC_WRITE_WATERMARK_LEVEL (0x80U)

enum usdhc_reset {
	USDHC_RESET_ALL =
		USDHC_SYS_CTRL_RSTA_MASK,
	/*!< Reset all except card detection */
	USDHC_RESET_CMD =
		USDHC_SYS_CTRL_RSTC_MASK,
	/*!< Reset command line */
	USDHC_RESET_DATA =
		USDHC_SYS_CTRL_RSTD_MASK,
	/*!< Reset data line */

#if defined(FSL_FEATURE_USDHC_HAS_SDR50_MODE) &&\
	(!FSL_FEATURE_USDHC_HAS_SDR50_MODE)
	USDHC_RESET_TUNING = 0U,
	/*!< no reset tuning circuit bit */
#else
	USDHC_RESET_TUNING = USDHC_SYS_CTRL_RSTT_MASK,
	/*!< reset tuning circuit */
#endif

	USDHC_RESETS_All =
		(USDHC_RESET_ALL |
		USDHC_RESET_CMD |
		USDHC_RESET_DATA |
		USDHC_RESET_TUNING),
	/*!< All reset types */
};

static void usdhc_millsec_delay(unsigned int cycles_to_wait)
{
	unsigned int start = sys_clock_cycle_get_32();

	while (sys_clock_cycle_get_32() - start < (cycles_to_wait * 1000))
		;
}

uint32_t g_usdhc_boot_dummy __aligned(64);
uint32_t g_usdhc_rx_dummy[2048] __aligned(64);

static int usdhc_adma2_descriptor_cfg(
	uint32_t *adma_table, uint32_t adma_table_words,
	const uint32_t *data_addr, uint32_t data_size, uint32_t flags)
{
	uint32_t min_entries, start_entry = 0U;
	uint32_t max_entries = (adma_table_words * sizeof(uint32_t)) /
		sizeof(struct usdhc_adma2_descriptor);
	struct usdhc_adma2_descriptor *adma2_addr =
		(struct usdhc_adma2_descriptor *)(adma_table);
	uint32_t i, dma_buf_len = 0U;

	if ((uint32_t)data_addr % USDHC_ADMA2_ADDRESS_ALIGN) {
		return -EIO;
	}
	/* Add non aligned access support.
	 */
	if (data_size % sizeof(uint32_t)) {
		/* make the data length as word-aligned */
		data_size += sizeof(uint32_t) - (data_size % sizeof(uint32_t));
	}

	/* Check if ADMA descriptor's number is enough. */
	if (!(data_size % USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY)) {
		min_entries = data_size /
			USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY;
	} else {
		min_entries = ((data_size /
			USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY) + 1U);
	}
	/* calcucate the start entry for multiple descriptor mode,
	 * ADMA engine is not stop, so update the descriptor
	 * data address and data size is enough
	 */
	if (flags == USDHC_ADMA_MUTI_FLAG) {
		for (i = 0U; i < max_entries; i++) {
			if (!(adma2_addr[i].attribute & USDHC_ADMA2_VALID_FLAG))
				break;
		}
		start_entry = i;
		/* add one entry for dummy entry */
		min_entries += 1U;
	}

	if ((min_entries + start_entry) > max_entries) {
		return -EIO;
	}

	for (i = start_entry; i < (min_entries + start_entry); i++) {
		if (data_size > USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY) {
			dma_buf_len =
				USDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY;
		} else {
			dma_buf_len = (data_size == 0U ? sizeof(uint32_t) :
				data_size);
			/* adma don't support 0 data length transfer
			 * descriptor
			 */
		}

		/* Each descriptor for ADMA2 is 64-bit in length */
		adma2_addr[i].address = (data_size == 0U) ?
			&g_usdhc_boot_dummy : data_addr;
		adma2_addr[i].attribute = (dma_buf_len <<
			USDHC_ADMA2_DESCRIPTOR_LENGTH_SHIFT);
		adma2_addr[i].attribute |=
			(data_size == 0U) ? 0U :
				(USDHC_ADMA2_XFER_FLAG | USDHC_ADMA2_INT_FLAG);
		data_addr += (dma_buf_len / sizeof(uint32_t));

		if (data_size != 0U)
			data_size -= dma_buf_len;
	}

	/* add a dummy valid ADMA descriptor for multiple descriptor mode,
	 * this is useful when transfer boot data, the ADMA
	 * engine will not stop at block gap
	 */
	if (flags == USDHC_ADMA_MUTI_FLAG) {
		adma2_addr[start_entry + 1U].attribute |= USDHC_ADMA2_XFER_FLAG;
	} else {
		adma2_addr[i - 1U].attribute |= USDHC_ADMA2_END_FLAG;
		/* set the end bit */
	}

	return 0;
}

static int usdhc_Internal_dma_cfg(struct usdhc_priv *priv,
	struct usdhc_adma_config *dma_cfg,
	const uint32_t *data_addr)
{
	USDHC_Type *base = priv->config->base;
	bool cmd23 = priv->op_context.data.cmd23;

	if (dma_cfg->dma_mode == USDHC_DMA_SIMPLE) {
		/* check DMA data buffer address align or not */
		if (((uint32_t)data_addr % USDHC_ADMA2_ADDRESS_ALIGN) != 0U) {
			return -EIO;
		}
		/* in simple DMA mode if use auto CMD23,
		 * address should load to ADMA addr,
		 * and block count should load to DS_ADDR
		 */
		if (cmd23)
			base->ADMA_SYS_ADDR = (uint32_t)data_addr;
		else
			base->DS_ADDR = (uint32_t)data_addr;
	} else {
		/* When use ADMA, disable simple DMA */
		base->DS_ADDR = 0U;
		base->ADMA_SYS_ADDR = (uint32_t)(dma_cfg->adma_table);
	}

	/* select DMA mode and config the burst length */
	base->PROT_CTRL &= ~(USDHC_PROT_CTRL_DMASEL_MASK |
		USDHC_PROT_CTRL_BURST_LEN_EN_MASK);
	base->PROT_CTRL |= USDHC_PROT_CTRL_DMASEL(dma_cfg->dma_mode) |
		USDHC_PROT_CTRL_BURST_LEN_EN(dma_cfg->burst_len);
	/* enable DMA */
	base->MIX_CTRL |= USDHC_MIX_CTRL_DMAEN_MASK;

	return 0;
}


static int usdhc_adma_table_cfg(struct usdhc_priv *priv, uint32_t flags)
{
	int error = -EIO;
	struct usdhc_data *data = &priv->op_context.data;
	struct usdhc_adma_config *dma_cfg = &priv->op_context.dma_cfg;
	uint32_t boot_dummy_off = data->data_type == USDHC_XFER_BOOT_CONTINUOUS ?
		sizeof(uint32_t) : 0U;
	const uint32_t *data_addr = (const uint32_t *)((uint32_t)((!data->rx_data) ?
		data->tx_data : data->rx_data) + boot_dummy_off);
	uint32_t data_size = data->block_size * data->block_count - boot_dummy_off;

	switch (dma_cfg->dma_mode) {
	case USDHC_DMA_SIMPLE:
		error = 0;
	break;

	case USDHC_DMA_ADMA1:
		error = -EINVAL;
	break;

	case USDHC_DMA_ADMA2:
		error = usdhc_adma2_descriptor_cfg(dma_cfg->adma_table,
			dma_cfg->adma_table_words, data_addr, data_size, flags);
	break;
	default:
		return -EINVAL;
	}

	/* for internal dma, internal DMA configurations should not update
	 * the configurations when continuous transfer the
	 * boot data, only the DMA descriptor need update
	 */
	if ((!error) && (data->data_type != USDHC_XFER_BOOT_CONTINUOUS)) {
		error = usdhc_Internal_dma_cfg(priv, dma_cfg, data_addr);
	}

	return error;
}

static int usdhc_data_xfer_cfg(struct usdhc_priv *priv,
	bool en_dma)
{
	USDHC_Type *base = priv->config->base;
	uint32_t mix_ctrl = base->MIX_CTRL;
	struct usdhc_data *data = NULL;
	uint32_t *flag = &priv->op_context.cmd.flags;

	if (!priv->op_context.cmd_only)
		data = &priv->op_context.data;

	if (data != NULL) {
		if (data->data_type == USDHC_XFER_BOOT_CONTINUOUS) {
			/* clear stop at block gap request */
			base->PROT_CTRL &= ~USDHC_PROT_CTRL_SABGREQ_MASK;
			/* continuous transfer data */
			base->PROT_CTRL |= USDHC_PROT_CTRL_CREQ_MASK;
			return 0;
		}

		/* check data inhibit flag */
		if (base->PRES_STATE & USDHC_DATA_INHIBIT_FLAG)
			return -EBUSY;
		/* check transfer block count */
		if ((data->block_count > USDHC_MAX_BLOCK_COUNT) ||
			(!data->tx_data && !data->rx_data))
			return -EINVAL;

		/* config mix parameter */
		mix_ctrl &= ~(USDHC_MIX_CTRL_MSBSEL_MASK |
			USDHC_MIX_CTRL_BCEN_MASK |
			USDHC_MIX_CTRL_DTDSEL_MASK |
			USDHC_MIX_CTRL_AC12EN_MASK);

		if (data->rx_data) {
			mix_ctrl |= USDHC_MIX_CTRL_DTDSEL_MASK;
		}

		if (data->block_count > 1U) {
			mix_ctrl |= USDHC_MIX_CTRL_MSBSEL_MASK |
				USDHC_MIX_CTRL_BCEN_MASK;
			/* auto command 12 */
			if (data->cmd12) {
				mix_ctrl |= USDHC_MIX_CTRL_AC12EN_MASK;
			}
		}

		/* auto command 23, auto send set block count cmd before
		 * multiple read/write
		 */
		if ((data->cmd23)) {
			mix_ctrl |= USDHC_MIX_CTRL_AC23EN_MASK;
			base->VEND_SPEC2 |=
				USDHC_VEND_SPEC2_ACMD23_ARGU2_EN_MASK;
			/* config the block count to DS_ADDR */
			base->DS_ADDR = data->block_count;
		} else {
			mix_ctrl &= ~USDHC_MIX_CTRL_AC23EN_MASK;
			base->VEND_SPEC2 &=
				(~USDHC_VEND_SPEC2_ACMD23_ARGU2_EN_MASK);
		}

		if (data->data_type != USDHC_XFER_BOOT) {
			/* config data block size/block count */
			base->BLK_ATT =
				((base->BLK_ATT & ~(USDHC_BLK_ATT_BLKSIZE_MASK |
				USDHC_BLK_ATT_BLKCNT_MASK)) |
				(USDHC_BLK_ATT_BLKSIZE(data->block_size) |
				USDHC_BLK_ATT_BLKCNT(data->block_count)));
		} else {
			mix_ctrl |= USDHC_MIX_CTRL_MSBSEL_MASK |
				USDHC_MIX_CTRL_BCEN_MASK;
			base->PROT_CTRL |=
				USDHC_PROT_CTRL_RD_DONE_NO_8CLK_MASK;
		}

		/* data present flag */
		*flag |= USDHC_DATA_PRESENT_FLAG;
		/* Disable useless interrupt */
		if (en_dma) {
			base->INT_SIGNAL_EN &=
				~(USDHC_INT_BUF_WRITE_READY_FLAG |
				USDHC_INT_BUF_READ_READY_FLAG |
				USDHC_INT_DMA_DONE_FLAG);
			base->INT_STATUS_EN &=
				~(USDHC_INT_BUF_WRITE_READY_FLAG |
				USDHC_INT_BUF_READ_READY_FLAG |
				USDHC_INT_DMA_DONE_FLAG);
		} else {
			base->INT_SIGNAL_EN |=
				USDHC_INT_BUF_WRITE_READY_FLAG |
				USDHC_INT_BUF_READ_READY_FLAG;
			base->INT_STATUS_EN |=
				USDHC_INT_BUF_WRITE_READY_FLAG |
				USDHC_INT_BUF_READ_READY_FLAG;
		}
	} else {
		/* clear data flags */
		mix_ctrl &= ~(USDHC_MIX_CTRL_MSBSEL_MASK |
					USDHC_MIX_CTRL_BCEN_MASK |
					USDHC_MIX_CTRL_DTDSEL_MASK |
					USDHC_MIX_CTRL_AC12EN_MASK |
					USDHC_MIX_CTRL_AC23EN_MASK);

		if (base->PRES_STATE & USDHC_CMD_INHIBIT_FLAG)
			return -EBUSY;
	}

	/* config the mix parameter */
	base->MIX_CTRL = mix_ctrl;

	return 0;
}

static void usdhc_send_cmd(USDHC_Type *base, struct usdhc_cmd *command)
{
	uint32_t xfer_type = base->CMD_XFR_TYP;
	uint32_t flags = command->flags;

	if (!(base->PRES_STATE & USDHC_CMD_INHIBIT_FLAG)
		&& (command->cmd_type != USDHC_CMD_TYPE_EMPTY)) {
		/* Define the flag corresponding to each response type. */
		switch (command->rsp_type) {
		case SDHC_RSP_TYPE_NONE:
			break;
		case SDHC_RSP_TYPE_R1: /* Response 1 */
		case SDHC_RSP_TYPE_R5: /* Response 5 */
		case SDHC_RSP_TYPE_R6: /* Response 6 */
		case SDHC_RSP_TYPE_R7: /* Response 7 */
			flags |= (USDHC_RSP_LEN_48_FLAG |
					USDHC_CRC_CHECK_FLAG |
					USDHC_IDX_CHECK_FLAG);
		break;

		case SDHC_RSP_TYPE_R1b: /* Response 1 with busy */
		case SDHC_RSP_TYPE_R5b: /* Response 5 with busy */
			flags |= (USDHC_RSP_LEN_48_BUSY_FLAG |
					USDHC_CRC_CHECK_FLAG |
					USDHC_IDX_CHECK_FLAG);
		break;

		case SDHC_RSP_TYPE_R2: /* Response 2 */
			flags |= (USDHC_RSP_LEN_136_FLAG |
				USDHC_CRC_CHECK_FLAG);
		break;

		case SDHC_RSP_TYPE_R3: /* Response 3 */
		case SDHC_RSP_TYPE_R4: /* Response 4 */
			flags |= (USDHC_RSP_LEN_48_FLAG);
		break;

		default:
			break;
		}

		if (command->cmd_type == USDHC_CMD_TYPE_ABORT)
			flags |= USDHC_CMD_TYPE_ABORT_FLAG;

		/* config cmd index */
		xfer_type &= ~(USDHC_CMD_XFR_TYP_CMDINX_MASK |
					USDHC_CMD_XFR_TYP_CMDTYP_MASK |
					USDHC_CMD_XFR_TYP_CICEN_MASK |
					USDHC_CMD_XFR_TYP_CCCEN_MASK |
					USDHC_CMD_XFR_TYP_RSPTYP_MASK |
					USDHC_CMD_XFR_TYP_DPSEL_MASK);

		xfer_type |=
			(((command->index << USDHC_CMD_XFR_TYP_CMDINX_SHIFT) &
				USDHC_CMD_XFR_TYP_CMDINX_MASK) |
				((flags) & (USDHC_CMD_XFR_TYP_CMDTYP_MASK |
				USDHC_CMD_XFR_TYP_CICEN_MASK |
				USDHC_CMD_XFR_TYP_CCCEN_MASK |
				USDHC_CMD_XFR_TYP_RSPTYP_MASK |
				USDHC_CMD_XFR_TYP_DPSEL_MASK)));

		/* config the command xfertype and argument */
		base->CMD_ARG = command->argument;
		base->CMD_XFR_TYP = xfer_type;
	}

	if (command->cmd_type == USDHC_CMD_TYPE_EMPTY) {
		/* disable CMD done interrupt for empty command */
		base->INT_SIGNAL_EN &= ~USDHC_INT_SIGNAL_EN_CCIEN_MASK;
	}
}

static int usdhc_cmd_rsp(struct usdhc_priv *priv)
{
	uint32_t i;
	USDHC_Type *base = priv->config->base;
	struct usdhc_cmd *cmd = &priv->op_context.cmd;

	if (cmd->rsp_type != SDHC_RSP_TYPE_NONE) {
		cmd->response[0U] = base->CMD_RSP0;
		if (cmd->rsp_type == SDHC_RSP_TYPE_R2) {
			cmd->response[1U] = base->CMD_RSP1;
			cmd->response[2U] = base->CMD_RSP2;
			cmd->response[3U] = base->CMD_RSP3;

			i = 4U;
			/* R3-R2-R1-R0(lowest 8 bit is invalid bit)
			 * has the same format
			 * as R2 format in SD specification document
			 * after removed internal CRC7 and end bit.
			 */
			do {
				cmd->response[i - 1U] <<= 8U;
				if (i > 1U) {
					cmd->response[i - 1U] |=
						((cmd->response[i - 2U] &
						0xFF000000U) >> 24U);
				}
				i--;
			} while (i);
		}
	}
	/* check response error flag */
	if ((cmd->rsp_err_flags) &&
		((cmd->rsp_type == SDHC_RSP_TYPE_R1) ||
		(cmd->rsp_type == SDHC_RSP_TYPE_R1b) ||
		(cmd->rsp_type == SDHC_RSP_TYPE_R6) ||
		(cmd->rsp_type == SDHC_RSP_TYPE_R5))) {
		if (((cmd->rsp_err_flags) & (cmd->response[0U])))
			return -EIO;
	}

	return 0;
}

static int usdhc_wait_cmd_done(struct usdhc_priv *priv,
	bool poll_cmd)
{
	int error = 0;
	uint32_t int_status = 0U;
	USDHC_Type *base = priv->config->base;

	/* check if need polling command done or not */
	if (poll_cmd) {
		/* Wait command complete or USDHC encounters error. */
		while (!(int_status & (USDHC_INT_CMD_DONE_FLAG |
			USDHC_INT_CMD_ERR_FLAG))) {
			int_status = base->INT_STATUS;
		}

		if ((int_status & USDHC_INT_TUNING_ERR_FLAG) ||
			(int_status & USDHC_INT_CMD_ERR_FLAG)) {
			error = -EIO;
		}
		/* Receive response when command completes successfully. */
		if (!error) {
			error = usdhc_cmd_rsp(priv);
		} else {
			LOG_ERR("CMD%d Polling ERROR",
				priv->op_context.cmd.index);
		}

		base->INT_STATUS = (USDHC_INT_CMD_DONE_FLAG |
				USDHC_INT_CMD_ERR_FLAG |
				USDHC_INT_TUNING_ERR_FLAG);
	}

	return error;
}

static inline void usdhc_write_data(USDHC_Type *base, uint32_t data)
{
	base->DATA_BUFF_ACC_PORT = data;
}

static inline uint32_t usdhc_read_data(USDHC_Type *base)
{
	return base->DATA_BUFF_ACC_PORT;
}

static uint32_t usdhc_read_data_port(struct usdhc_priv *priv,
	uint32_t xfered_words)
{
	USDHC_Type *base = priv->config->base;
	struct usdhc_data *data = &priv->op_context.data;
	uint32_t i, total_words, remaing_words;
	/* The words can be read at this time. */
	uint32_t watermark = ((base->WTMK_LVL & USDHC_WTMK_LVL_RD_WML_MASK) >>
		USDHC_WTMK_LVL_RD_WML_SHIFT);

	/* If DMA is enable, do not need to polling data port */
	if (!(base->MIX_CTRL & USDHC_MIX_CTRL_DMAEN_MASK)) {
		/*Add non aligned access support.*/
		if (data->block_size % sizeof(uint32_t)) {
			data->block_size +=
				sizeof(uint32_t) -
				(data->block_size % sizeof(uint32_t));
			/* make the block size as word-aligned */
		}

		total_words = ((data->block_count * data->block_size) /
			sizeof(uint32_t));

		if (watermark >= total_words) {
			remaing_words = total_words;
		} else if ((watermark < total_words) &&
			((total_words - xfered_words) >= watermark)) {
			remaing_words = watermark;
		} else {
			remaing_words = (total_words - xfered_words);
		}

		i = 0U;
		while (i < remaing_words) {
			data->rx_data[xfered_words++] = usdhc_read_data(base);
			i++;
		}
	}

	return xfered_words;
}

static int usdhc_read_data_port_sync(struct usdhc_priv *priv)
{
	USDHC_Type *base = priv->config->base;
	struct usdhc_data *data = &priv->op_context.data;
	uint32_t total_words;
	uint32_t xfered_words = 0U, int_status = 0U;
	int error = 0;

	if (data->block_size % sizeof(uint32_t)) {
		data->block_size +=
			sizeof(uint32_t) -
			(data->block_size % sizeof(uint32_t));
	}

	total_words =
		((data->block_count * data->block_size) /
		sizeof(uint32_t));

	while ((!error) && (xfered_words < total_words)) {
		while (!(int_status & (USDHC_INT_BUF_READ_READY_FLAG |
			USDHC_INT_DATA_ERR_FLAG |
			USDHC_INT_TUNING_ERR_FLAG)))
			int_status = base->INT_STATUS;

		/* during std tuning process, software do not need to read data,
		 * but wait BRR is enough
		 */
		if ((data->data_type == USDHC_XFER_TUNING) &&
			(int_status & USDHC_INT_BUF_READ_READY_FLAG)) {
			base->INT_STATUS = USDHC_INT_BUF_READ_READY_FLAG |
				USDHC_INT_TUNING_PASS_FLAG;

			return 0;
		} else if ((int_status & USDHC_INT_TUNING_ERR_FLAG)) {
			base->INT_STATUS = USDHC_INT_TUNING_ERR_FLAG;
			/* if tuning error occur ,return directly */
			error = -EIO;
		} else if ((int_status & USDHC_INT_DATA_ERR_FLAG)) {
			if (!(data->ignore_err))
				error = -EIO;
			/* clear data error flag */
			base->INT_STATUS = USDHC_INT_DATA_ERR_FLAG;
		}

		if (!error) {
			xfered_words = usdhc_read_data_port(priv, xfered_words);
			/* clear buffer read ready */
			base->INT_STATUS = USDHC_INT_BUF_READ_READY_FLAG;
			int_status = 0U;
		}
	}

	/* Clear data complete flag after the last read operation. */
	base->INT_STATUS = USDHC_INT_DATA_DONE_FLAG;

	return error;
}

static uint32_t usdhc_write_data_port(struct usdhc_priv *priv,
	uint32_t xfered_words)
{
	USDHC_Type *base = priv->config->base;
	struct usdhc_data *data = &priv->op_context.data;
	uint32_t i, total_words, remaing_words;
	/* Words can be wrote at this time. */
	uint32_t watermark = ((base->WTMK_LVL & USDHC_WTMK_LVL_WR_WML_MASK) >>
		USDHC_WTMK_LVL_WR_WML_SHIFT);

	/* If DMA is enable, do not need to polling data port */
	if (!(base->MIX_CTRL & USDHC_MIX_CTRL_DMAEN_MASK)) {
		if (data->block_size % sizeof(uint32_t)) {
			data->block_size +=
				sizeof(uint32_t) -
				(data->block_size % sizeof(uint32_t));
		}

		total_words =
			((data->block_count * data->block_size) /
			sizeof(uint32_t));

		if (watermark >= total_words) {
			remaing_words = total_words;
		} else if ((watermark < total_words) &&
			((total_words - xfered_words) >= watermark)) {
			remaing_words = watermark;
		} else {
			remaing_words = (total_words - xfered_words);
		}

		i = 0U;
		while (i < remaing_words) {
			usdhc_write_data(base, data->tx_data[xfered_words++]);
			i++;
		}
	}

	return xfered_words;
}

static status_t usdhc_write_data_port_sync(struct usdhc_priv *priv)
{
	USDHC_Type *base = priv->config->base;
	struct usdhc_data *data = &priv->op_context.data;
	uint32_t total_words;
	uint32_t xfered_words = 0U, int_status = 0U;
	int error = 0;

	if (data->block_size % sizeof(uint32_t)) {
		data->block_size +=
			sizeof(uint32_t) - (data->block_size % sizeof(uint32_t));
	}

	total_words = (data->block_count * data->block_size) / sizeof(uint32_t);

	while ((!error) && (xfered_words < total_words)) {
		while (!(int_status & (USDHC_INT_BUF_WRITE_READY_FLAG |
				USDHC_INT_DATA_ERR_FLAG |
				USDHC_INT_TUNING_ERR_FLAG))) {
			int_status = base->INT_STATUS;
		}

		if (int_status & USDHC_INT_TUNING_ERR_FLAG) {
			base->INT_STATUS = USDHC_INT_TUNING_ERR_FLAG;
			/* if tuning error occur ,return directly */
			return -EIO;
		} else if (int_status & USDHC_INT_DATA_ERR_FLAG) {
			if (!(data->ignore_err))
				error = -EIO;
			/* clear data error flag */
			base->INT_STATUS = USDHC_INT_DATA_ERR_FLAG;
		}

		if (!error) {
			xfered_words = usdhc_write_data_port(priv,
				xfered_words);
			/* clear buffer write ready */
			base->INT_STATUS = USDHC_INT_BUF_WRITE_READY_FLAG;
			int_status = 0U;
		}
	}

	/* Wait write data complete or data transfer error
	 * after the last writing operation.
	 */
	while (!(int_status & (USDHC_INT_DATA_DONE_FLAG |
		USDHC_INT_DATA_ERR_FLAG))) {
		int_status = base->INT_STATUS;
	}

	if (int_status & USDHC_INT_DATA_ERR_FLAG) {
		if (!(data->ignore_err))
			error = -EIO;
	}
	base->INT_STATUS = USDHC_INT_DATA_DONE_FLAG |
		USDHC_INT_DATA_ERR_FLAG;

	return error;
}

static int usdhc_data_sync_xfer(struct usdhc_priv *priv, bool en_dma)
{
	int error = 0;
	uint32_t int_status = 0U;
	USDHC_Type *base = priv->config->base;
	struct usdhc_data *data = &priv->op_context.data;

	if (en_dma) {
		/* Wait data complete or USDHC encounters error. */
		while (!((int_status &
			(USDHC_INT_DATA_DONE_FLAG | USDHC_INT_DATA_ERR_FLAG |
			USDHC_INT_CMD_ERR_FLAG | USDHC_INT_TUNING_ERR_FLAG)))) {
			int_status = base->INT_STATUS;
		}

		if (int_status & USDHC_INT_TUNING_ERR_FLAG) {
			error = -EIO;
		} else if ((int_status & (USDHC_INT_DATA_ERR_FLAG |
			USDHC_INT_DMA_ERR_FLAG))) {
			if ((!(data->ignore_err)) ||
				(int_status &
				USDHC_INT_DATA_TIMEOUT_FLAG)) {
				error = -EIO;
			}
		}
		/* load dummy data */
		if ((data->data_type == USDHC_XFER_BOOT_CONTINUOUS) && (!error))
			*(data->rx_data) = g_usdhc_boot_dummy;

		base->INT_STATUS = (USDHC_INT_DATA_DONE_FLAG |
			USDHC_INT_DATA_ERR_FLAG |
			USDHC_INT_DMA_ERR_FLAG |
			USDHC_INT_TUNING_PASS_FLAG |
			USDHC_INT_TUNING_ERR_FLAG);
	} else {
		if (data->rx_data) {
			error = usdhc_read_data_port_sync(priv);
		} else {
			error = usdhc_write_data_port_sync(priv);
		}
	}
	return error;
}

static int usdhc_xfer(struct usdhc_priv *priv)
{
	int error = -EIO;
	struct usdhc_data *data = NULL;
	bool en_dma = true, execute_tuning;
	USDHC_Type *base = priv->config->base;

	if (!priv->op_context.cmd_only) {
		data = &priv->op_context.data;
		if (data->data_type == USDHC_XFER_TUNING)
			execute_tuning = true;
		else
			execute_tuning = false;
	} else {
		execute_tuning = false;
	}

	/*check re-tuning request*/
	if ((base->INT_STATUS & USDHC_INT_RE_TUNING_EVENT_FLAG)) {
		base->INT_STATUS = USDHC_INT_RE_TUNING_EVENT_FLAG;
		return -EAGAIN;
	}

	/* Update ADMA descriptor table according to different DMA mode
	 * (no DMA, ADMA1, ADMA2).
	 */

	if (data && (!execute_tuning) && priv->op_context.dma_cfg.adma_table)
		error = usdhc_adma_table_cfg(priv,
			(data->data_type & USDHC_XFER_BOOT) ?
			USDHC_ADMA_MUTI_FLAG : USDHC_ADMA_SINGLE_FLAG);

	/* if the DMA descriptor configure fail or not needed , disable it */
	if (error) {
		en_dma = false;
		/* disable DMA, using polling mode in this situation */
		base->MIX_CTRL &= ~USDHC_MIX_CTRL_DMAEN_MASK;
		base->PROT_CTRL &= ~USDHC_PROT_CTRL_DMASEL_MASK;
	}

	/* config the data transfer parameter */
	error = usdhc_data_xfer_cfg(priv, en_dma);
	if (error)
		return error;
	/* send command first */
	usdhc_send_cmd(base, &priv->op_context.cmd);
	/* wait command done */
	error = usdhc_wait_cmd_done(priv, (data == NULL) ||
		(data->data_type == USDHC_XFER_NORMAL));
	/* wait transfer data finish */
	if (data && (!error)) {
		return usdhc_data_sync_xfer(priv, en_dma);
	}

	return error;
}

static inline void usdhc_select_1_8_vol(USDHC_Type *base, bool en_1_8_v)
{
	if (en_1_8_v)
		base->VEND_SPEC |= USDHC_VEND_SPEC_VSELECT_MASK;
	else
		base->VEND_SPEC &= ~USDHC_VEND_SPEC_VSELECT_MASK;
}

static inline void usdhc_force_clk_on(USDHC_Type *base, bool on)
{
	if (on)
		base->VEND_SPEC |= USDHC_VEND_SPEC_FRC_SDCLK_ON_MASK;
	else
		base->VEND_SPEC &= ~USDHC_VEND_SPEC_FRC_SDCLK_ON_MASK;
}

static void usdhc_tuning(USDHC_Type *base, uint32_t start, uint32_t step, bool enable)
{
	uint32_t tuning_ctrl = 0U;

	if (enable) {
		/* feedback clock */
		base->MIX_CTRL |= USDHC_MIX_CTRL_FBCLK_SEL_MASK;
		/* config tuning start and step */
		tuning_ctrl = base->TUNING_CTRL;
		tuning_ctrl &= ~(USDHC_TUNING_CTRL_TUNING_START_TAP_MASK |
			USDHC_TUNING_CTRL_TUNING_STEP_MASK);
		tuning_ctrl |= (USDHC_TUNING_CTRL_TUNING_START_TAP(start) |
			USDHC_TUNING_CTRL_TUNING_STEP(step) |
			USDHC_TUNING_CTRL_STD_TUNING_EN_MASK);
		base->TUNING_CTRL = tuning_ctrl;

		/* excute tuning */
		base->AUTOCMD12_ERR_STATUS |=
			(USDHC_AUTOCMD12_ERR_STATUS_EXECUTE_TUNING_MASK |
			USDHC_AUTOCMD12_ERR_STATUS_SMP_CLK_SEL_MASK);
	} else {
		/* disable the standard tuning */
		base->TUNING_CTRL &= ~USDHC_TUNING_CTRL_STD_TUNING_EN_MASK;
		/* clear excute tuning */
		base->AUTOCMD12_ERR_STATUS &=
			~(USDHC_AUTOCMD12_ERR_STATUS_EXECUTE_TUNING_MASK |
			USDHC_AUTOCMD12_ERR_STATUS_SMP_CLK_SEL_MASK);
	}
}

int usdhc_adjust_tuning_timing(USDHC_Type *base, uint32_t delay)
{
	uint32_t clk_tune_ctrl = 0U;

	clk_tune_ctrl = base->CLK_TUNE_CTRL_STATUS;

	clk_tune_ctrl &= ~USDHC_CLK_TUNE_CTRL_STATUS_DLY_CELL_SET_PRE_MASK;

	clk_tune_ctrl |= USDHC_CLK_TUNE_CTRL_STATUS_DLY_CELL_SET_PRE(delay);

	/* load the delay setting */
	base->CLK_TUNE_CTRL_STATUS = clk_tune_ctrl;
	/* check delat setting error */
	if (base->CLK_TUNE_CTRL_STATUS &
		(USDHC_CLK_TUNE_CTRL_STATUS_PRE_ERR_MASK |
		USDHC_CLK_TUNE_CTRL_STATUS_NXT_ERR_MASK))
		return -EIO;

	return 0;
}

static inline void usdhc_set_retuning_timer(USDHC_Type *base, uint32_t counter)
{
	base->HOST_CTRL_CAP &= ~USDHC_HOST_CTRL_CAP_TIME_COUNT_RETUNING_MASK;
	base->HOST_CTRL_CAP |= USDHC_HOST_CTRL_CAP_TIME_COUNT_RETUNING(counter);
}

static inline void usdhc_set_bus_width(USDHC_Type *base,
	enum usdhc_data_bus_width width)
{
	base->PROT_CTRL = ((base->PROT_CTRL & ~USDHC_PROT_CTRL_DTW_MASK) |
		USDHC_PROT_CTRL_DTW(width));
}

static int usdhc_execute_tuning(struct usdhc_priv *priv)
{
	bool tuning_err = true;
	int ret;
	USDHC_Type *base = priv->config->base;

	/* enable the standard tuning */
	usdhc_tuning(base, SDHC_STANDARD_TUNING_START, SDHC_TUINIG_STEP, true);

	while (true) {
		/* send tuning block */
		ret = usdhc_xfer(priv);
		if (ret) {
			return ret;
		}
		usdhc_millsec_delay(10);

		/*wait excute tuning bit clear*/
		if ((base->AUTOCMD12_ERR_STATUS &
			USDHC_AUTOCMD12_ERR_STATUS_EXECUTE_TUNING_MASK)) {
			continue;
		}

		/* if tuning error , re-tuning again */
		if ((base->CLK_TUNE_CTRL_STATUS &
			(USDHC_CLK_TUNE_CTRL_STATUS_NXT_ERR_MASK |
			USDHC_CLK_TUNE_CTRL_STATUS_PRE_ERR_MASK)) &&
			tuning_err) {
			tuning_err = false;
			/* enable the standard tuning */
			usdhc_tuning(base, SDHC_STANDARD_TUNING_START,
				SDHC_TUINIG_STEP, true);
			usdhc_adjust_tuning_timing(base,
				SDHC_STANDARD_TUNING_START);
		} else {
			break;
		}
	}

	/* delay to wait the host controller stable */
	usdhc_millsec_delay(1000);

	/* check tuning result*/
	if (!(base->AUTOCMD12_ERR_STATUS &
		USDHC_AUTOCMD12_ERR_STATUS_SMP_CLK_SEL_MASK)) {
		return -EIO;
	}

	usdhc_set_retuning_timer(base, SDHC_RETUNING_TIMER_COUNT);

	return 0;
}

static int usdhc_vol_switch(struct usdhc_priv *priv)
{
	USDHC_Type *base = priv->config->base;
	int retry = 0xffff;

	while (base->PRES_STATE &
		(CARD_DATA1_STATUS_MASK | CARD_DATA2_STATUS_MASK |
		CARD_DATA3_STATUS_MASK | CARD_DATA0_NOT_BUSY)) {
		retry--;
		if (retry <= 0) {
			return -EACCES;
		}
	}

	/* host switch to 1.8V */
	usdhc_select_1_8_vol(base, true);

	usdhc_millsec_delay(20000U);

	/*enable force clock on*/
	usdhc_force_clk_on(base, true);
	/* dealy 1ms,not exactly correct when use while */
	usdhc_millsec_delay(20000U);
	/*disable force clock on*/
	usdhc_force_clk_on(base, false);

	/* check data line and cmd line status */
	retry = 0xffff;
	while (!(base->PRES_STATE &
		(CARD_DATA1_STATUS_MASK | CARD_DATA2_STATUS_MASK |
		CARD_DATA3_STATUS_MASK | CARD_DATA0_NOT_BUSY))) {
		retry--;
		if (retry <= 0) {
			return -EBUSY;
		}
	}

	return 0;
}

static inline void usdhc_op_ctx_init(struct usdhc_priv *priv,
	bool cmd_only, uint8_t cmd_idx, uint32_t arg, enum sdhc_rsp_type rsp_type)
{
	struct usdhc_cmd *cmd = &priv->op_context.cmd;
	struct usdhc_data *data = &priv->op_context.data;

	priv->op_context.cmd_only = cmd_only;

	memset((char *)cmd, 0, sizeof(struct usdhc_cmd));
	memset((char *)data, 0, sizeof(struct usdhc_data));

	cmd->index = cmd_idx;
	cmd->argument = arg;
	cmd->rsp_type = rsp_type;
}

static int usdhc_select_fun(struct usdhc_priv *priv,
	uint32_t group, uint32_t function)
{
	const struct usdhc_config *config = priv->config;
	uint32_t *fun_status;
	uint16_t fun_grp_info[6U] = {0};
	uint32_t current_fun_status = 0U, arg;
	struct usdhc_cmd *cmd = &priv->op_context.cmd;
	struct usdhc_data *data = &priv->op_context.data;
	int ret;

	/* check if card support CMD6 */
	if ((priv->card_info.version <= SD_SPEC_VER1_0) ||
		(!(priv->card_info.csd.cmd_class & SD_CMD_CLASS_SWITCH))) {
		return -EINVAL;
	}

	/* Check if card support high speed mode. */
	arg = (SD_SWITCH_CHECK << 31U | 0x00FFFFFFU);
	arg &= ~((uint32_t)(0xFU) << (group * 4U));
	arg |= (function << (group * 4U));
	usdhc_op_ctx_init(priv, 0, SDHC_SWITCH, arg, SDHC_RSP_TYPE_R1);

	data->block_size = 64U;
	data->block_count = 1U;
	data->rx_data = &g_usdhc_rx_dummy[0];
	ret = usdhc_xfer(priv);
	if (ret || (cmd->response[0U] & SDHC_R1ERR_All_FLAG))
		return -EIO;

	fun_status = data->rx_data;

	/* Switch function status byte sequence
	 * from card is big endian(MSB first).
	 */
	switch (config->endian) {
	case USDHC_LITTLE_ENDIAN:
		fun_status[0U] = SWAP_WORD_BYTE_SEQUENCE(fun_status[0U]);
		fun_status[1U] = SWAP_WORD_BYTE_SEQUENCE(fun_status[1U]);
		fun_status[2U] = SWAP_WORD_BYTE_SEQUENCE(fun_status[2U]);
		fun_status[3U] = SWAP_WORD_BYTE_SEQUENCE(fun_status[3U]);
		fun_status[4U] = SWAP_WORD_BYTE_SEQUENCE(fun_status[4U]);
		break;
	case USDHC_BIG_ENDIAN:
		break;
	case USDHC_HALF_WORD_BIG_ENDIAN:
		fun_status[0U] = SWAP_HALF_WROD_BYTE_SEQUENCE(fun_status[0U]);
		fun_status[1U] = SWAP_HALF_WROD_BYTE_SEQUENCE(fun_status[1U]);
		fun_status[2U] = SWAP_HALF_WROD_BYTE_SEQUENCE(fun_status[2U]);
		fun_status[3U] = SWAP_HALF_WROD_BYTE_SEQUENCE(fun_status[3U]);
		fun_status[4U] = SWAP_HALF_WROD_BYTE_SEQUENCE(fun_status[4U]);
		break;
	default:
		return -ENOTSUP;
	}

	fun_grp_info[5U] = (uint16_t)fun_status[0U];
	fun_grp_info[4U] = (uint16_t)(fun_status[1U] >> 16U);
	fun_grp_info[3U] = (uint16_t)(fun_status[1U]);
	fun_grp_info[2U] = (uint16_t)(fun_status[2U] >> 16U);
	fun_grp_info[1U] = (uint16_t)(fun_status[2U]);
	fun_grp_info[0U] = (uint16_t)(fun_status[3U] >> 16U);
	current_fun_status = ((fun_status[3U] & 0xFFU) << 8U) |
		(fun_status[4U] >> 24U);

	/* check if function is support */
	if (((fun_grp_info[group] & (1 << function)) == 0U) ||
		((current_fun_status >>
		(group * 4U)) & 0xFU) != function) {
		return -ENOTSUP;
	}

	/* Switch to high speed mode. */
	usdhc_op_ctx_init(priv, 0, SDHC_SWITCH, arg, SDHC_RSP_TYPE_R1);

	data->block_size = 64U;
	data->block_count = 1U;
	data->rx_data = &g_usdhc_rx_dummy[0];

	cmd->argument = (SD_SWITCH_SET << 31U | 0x00FFFFFFU);
	cmd->argument &= ~((uint32_t)(0xFU) << (group * 4U));
	cmd->argument |= (function << (group * 4U));

	ret = usdhc_xfer(priv);
	if (ret || (cmd->response[0U] & SDHC_R1ERR_All_FLAG))
		return -EIO;
	/* Switch function status byte sequence
	 * from card is big endian(MSB first).
	 */
	switch (config->endian) {
	case USDHC_LITTLE_ENDIAN:
		fun_status[3U] = SWAP_WORD_BYTE_SEQUENCE(fun_status[3U]);
		fun_status[4U] = SWAP_WORD_BYTE_SEQUENCE(fun_status[4U]);
		break;
	case USDHC_BIG_ENDIAN:
		break;
	case USDHC_HALF_WORD_BIG_ENDIAN:
		fun_status[3U] = SWAP_HALF_WROD_BYTE_SEQUENCE(fun_status[3U]);
		fun_status[4U] = SWAP_HALF_WROD_BYTE_SEQUENCE(fun_status[4U]);
		break;
	default:
		return -ENOTSUP;
	}
	/* According to the "switch function status[bits 511~0]" return
	 * by switch command in mode "set function":
	 * -check if group 1 is successfully changed to function 1 by checking
	 * if bits 379~376 equal value 1;
	 */
	current_fun_status = ((fun_status[3U] & 0xFFU) << 8U) |
		(fun_status[4U] >> 24U);

	if (((current_fun_status >>
		(group * 4U)) & 0xFU) != function) {
		return -EINVAL;
	}

	return 0;
}

uint32_t usdhc_set_sd_clk(USDHC_Type *base, uint32_t src_clk_hz, uint32_t sd_clk_hz)
{
	uint32_t total_div = 0U;
	uint32_t divisor = 0U;
	uint32_t prescaler = 0U;
	uint32_t sysctl = 0U;
	uint32_t nearest_freq = 0U;

	__ASSERT_NO_MSG(src_clk_hz != 0U);
	__ASSERT_NO_MSG((sd_clk_hz != 0U) && (sd_clk_hz <= src_clk_hz));

	/* calculate total divisor first */
	total_div = src_clk_hz / sd_clk_hz;
	if (total_div > (USDHC_MAX_CLKFS * USDHC_MAX_DVS)) {
		return 0U;
	}

	if (total_div) {
		/* calculate the divisor (src_clk_hz / divisor) <= sd_clk_hz */
		if ((src_clk_hz / total_div) > sd_clk_hz)
			total_div++;

		/* divide the total divisor to div and prescaler */
		if (total_div > USDHC_MAX_DVS) {
			prescaler = total_div / USDHC_MAX_DVS;
			/* prescaler must be a value which equal 2^n and
			 * smaller than SDHC_MAX_CLKFS
			 */
			while (((USDHC_MAX_CLKFS % prescaler) != 0U) ||
				(prescaler == 1U))
				prescaler++;
			/* calculate the divisor */
			divisor = total_div / prescaler;
			/* fine tuning the divisor until
			 * divisor * prescaler >= total_div
			 */
			while ((divisor * prescaler) < total_div) {
				divisor++;
				if (divisor > USDHC_MAX_DVS) {
					if ((prescaler <<= 1U) >
						USDHC_MAX_CLKFS) {
						return 0;
					}
				divisor = total_div / prescaler;
				}
			}
		} else {
			/* in this situation , divsior and SDCLKFS
			 * can generate same clock
			 * use SDCLKFS
			 */
			if (((total_div % 2U) != 0U) & (total_div != 1U)) {
				divisor = total_div;
				prescaler = 1U;
			} else {
				divisor = 1U;
				prescaler = total_div;
			}
		}
		nearest_freq = src_clk_hz / (divisor == 0U ? 1U : divisor) /
			prescaler;
	} else {
		/* in this condition , src_clk_hz = busClock_Hz, */
		/* in DDR mode , set SDCLKFS to 0, divisor = 0, actually the
		 * totoal divider = 2U
		 */
		divisor = 0U;
		prescaler = 0U;
		nearest_freq = src_clk_hz;
	}

	/* calculate the value write to register */
	if (divisor != 0U) {
		USDHC_PREV_DVS(divisor);
	}
	/* calculate the value write to register */
	if (prescaler != 0U) {
		USDHC_PREV_CLKFS(prescaler, 1U);
	}

	/* Set the SD clock frequency divisor, SD clock frequency select,
	 * data timeout counter value.
	 */
	sysctl = base->SYS_CTRL;
	sysctl &= ~(USDHC_SYS_CTRL_DVS_MASK |
		USDHC_SYS_CTRL_SDCLKFS_MASK);
	sysctl |= (USDHC_SYS_CTRL_DVS(divisor) |
		USDHC_SYS_CTRL_SDCLKFS(prescaler));
	base->SYS_CTRL = sysctl;

	/* Wait until the SD clock is stable. */
	while (!(base->PRES_STATE & USDHC_PRES_STATE_SDSTB_MASK)) {
		;
	}

	return nearest_freq;
}

static void usdhc_enable_ddr_mode(USDHC_Type *base,
	bool enable, uint32_t nibble_pos)
{
	uint32_t prescaler = (base->SYS_CTRL & USDHC_SYS_CTRL_SDCLKFS_MASK) >>
		USDHC_SYS_CTRL_SDCLKFS_SHIFT;

	if (enable) {
		base->MIX_CTRL &= ~USDHC_MIX_CTRL_NIBBLE_POS_MASK;
		base->MIX_CTRL |= (USDHC_MIX_CTRL_DDR_EN_MASK |
			USDHC_MIX_CTRL_NIBBLE_POS(nibble_pos));
		prescaler >>= 1U;
	} else {
		base->MIX_CTRL &= ~USDHC_MIX_CTRL_DDR_EN_MASK;

		if (prescaler == 0U) {
			prescaler += 1U;
		} else {
			prescaler <<= 1U;
		}
	}

	base->SYS_CTRL = (base->SYS_CTRL & (~USDHC_SYS_CTRL_SDCLKFS_MASK)) |
		USDHC_SYS_CTRL_SDCLKFS(prescaler);
}

static int usdhc_select_bus_timing(struct usdhc_priv *priv)
{
	const struct usdhc_config *config = priv->config;
	int error = -EIO;

	if (priv->card_info.voltage != SD_VOL_1_8_V) {
		/* Switch the card to high speed mode */
		if (priv->host_capability.host_flags &
			USDHC_SUPPORT_HIGHSPEED_FLAG) {
			/* group 1, function 1 ->high speed mode*/
			error = usdhc_select_fun(priv, SD_GRP_TIMING_MODE,
				SD_TIMING_SDR25_HIGH_SPEED_MODE);
			/* If the result isn't "switching to
			 * high speed mode(50MHZ)
			 * successfully or card doesn't support
			 * high speed
			 * mode". Return failed status.
			 */
			if (!error) {
				priv->card_info.sd_timing =
					SD_TIMING_SDR25_HIGH_SPEED_MODE;
				priv->card_info.busclk_hz =
					usdhc_set_sd_clk(config->base,
						priv->src_clk_hz,
						SD_CLOCK_50MHZ);
			} else if (error == -ENOTSUP) {
				/* if not support high speed,
				 * keep the card work at default mode
				 */
				return 0;
			}
		} else {
			/* if not support high speed,
			 * keep the card work at default mode
			 */
			return 0;
		}
	} else if ((USDHC_SUPPORT_SDR104_FLAG !=
		SDMMCHOST_NOT_SUPPORT) ||
		(USDHC_SUPPORT_SDR50_FLAG != SDMMCHOST_NOT_SUPPORT) ||
		(USDHC_SUPPORT_DDR50_FLAG != SDMMCHOST_NOT_SUPPORT)) {
		/* card is in UHS_I mode */
		switch (priv->card_info.sd_timing) {
			/* if not select timing mode,
			 * sdmmc will handle it automatically
			 */
		case SD_TIMING_SDR12_DFT_MODE:
		case SD_TIMING_SDR104_MODE:
			error = usdhc_select_fun(priv, SD_GRP_TIMING_MODE,
				SD_TIMING_SDR104_MODE);
			if (!error) {
				priv->card_info.sd_timing =
					SD_TIMING_SDR104_MODE;
				priv->card_info.busclk_hz =
					usdhc_set_sd_clk(config->base,
						priv->src_clk_hz,
						SDMMCHOST_SUPPORT_SDR104_FREQ);
				break;
			}
		case SD_TIMING_DDR50_MODE:
			error = usdhc_select_fun(priv, SD_GRP_TIMING_MODE,
				SD_TIMING_DDR50_MODE);
			if (!error) {
				priv->card_info.sd_timing =
					SD_TIMING_DDR50_MODE;
				priv->card_info.busclk_hz =
					usdhc_set_sd_clk(
						config->base,
						priv->src_clk_hz,
						SD_CLOCK_50MHZ);
				usdhc_enable_ddr_mode(config->base, true, 0U);
			}
			break;
		case SD_TIMING_SDR50_MODE:
			error = usdhc_select_fun(priv,
				SD_GRP_TIMING_MODE,
				SD_TIMING_SDR50_MODE);
			if (!error) {
				priv->card_info.sd_timing =
					SD_TIMING_SDR50_MODE;
				priv->card_info.busclk_hz =
					usdhc_set_sd_clk(
						config->base,
						priv->src_clk_hz,
						SD_CLOCK_100MHZ);
			}
			break;
		case SD_TIMING_SDR25_HIGH_SPEED_MODE:
			error = usdhc_select_fun(priv, SD_GRP_TIMING_MODE,
				SD_TIMING_SDR25_HIGH_SPEED_MODE);
			if (!error) {
				priv->card_info.sd_timing =
					SD_TIMING_SDR25_HIGH_SPEED_MODE;
				priv->card_info.busclk_hz =
					usdhc_set_sd_clk(
						config->base,
						priv->src_clk_hz,
						SD_CLOCK_50MHZ);
			}
			break;

		default:
			break;
		}
	}

	/* SDR50 and SDR104 mode need tuning */
	if ((priv->card_info.sd_timing == SD_TIMING_SDR50_MODE) ||
		(priv->card_info.sd_timing == SD_TIMING_SDR104_MODE)) {
		struct usdhc_cmd *cmd = &priv->op_context.cmd;
		struct usdhc_data *data = &priv->op_context.data;

		/* config IO strength in IOMUX*/
		if (priv->card_info.sd_timing == SD_TIMING_SDR50_MODE) {
			imxrt_usdhc_pinmux(config->nusdhc, false,
				CARD_BUS_FREQ_100MHZ1,
				CARD_BUS_STRENGTH_7);
		} else {
			imxrt_usdhc_pinmux(config->nusdhc, false,
				CARD_BUS_FREQ_200MHZ,
				CARD_BUS_STRENGTH_7);
		}
		/* execute tuning */
		priv->op_context.cmd_only = 0;

		memset((char *)cmd, 0, sizeof(struct usdhc_cmd));
		memset((char *)data, 0, sizeof(struct usdhc_data));

		cmd->index = SDHC_SEND_TUNING_BLOCK;
		cmd->rsp_type = SDHC_RSP_TYPE_R1;

		data->block_size = 64;
		data->block_count = 1U;
		data->rx_data = &g_usdhc_rx_dummy[0];
		data->data_type = USDHC_XFER_TUNING;
		error = usdhc_execute_tuning(priv);
		if (error)
			return error;
	} else {
		/* set default IO strength to 4 to cover card adapter driver
		 * strength difference
		 */
		imxrt_usdhc_pinmux(config->nusdhc, false,
			CARD_BUS_FREQ_100MHZ1,
			CARD_BUS_STRENGTH_4);
	}

	return error;
}

static int usdhc_write_sector(void *bus_data, const uint8_t *buf, uint32_t sector,
		     uint32_t count)
{
	struct usdhc_priv *priv = bus_data;
	struct usdhc_cmd *cmd = &priv->op_context.cmd;
	struct usdhc_data *data = &priv->op_context.data;

	memset((char *)cmd, 0, sizeof(struct usdhc_cmd));
	memset((char *)data, 0, sizeof(struct usdhc_data));

	priv->op_context.cmd_only = 0;
	cmd->index = SDHC_WRITE_MULTIPLE_BLOCK;
	data->block_size = priv->card_info.sd_block_size;
	data->block_count = count;
	data->tx_data = (const uint32_t *)buf;
	data->cmd12 = true;
	if (data->block_count == 1U) {
		cmd->index = SDHC_WRITE_BLOCK;
	}
	cmd->argument = sector;
	if (!(priv->card_info.card_flags & SDHC_HIGH_CAPACITY_FLAG)) {
		cmd->argument *= priv->card_info.sd_block_size;
	}
	cmd->rsp_type = SDHC_RSP_TYPE_R1;
	cmd->rsp_err_flags = SDHC_R1ERR_All_FLAG;

	return usdhc_xfer(priv);
}

static int usdhc_read_sector(void *bus_data, uint8_t *buf, uint32_t sector,
		     uint32_t count)
{
	struct usdhc_priv *priv = bus_data;
	struct usdhc_cmd *cmd = &priv->op_context.cmd;
	struct usdhc_data *data = &priv->op_context.data;

	memset((char *)cmd, 0, sizeof(struct usdhc_cmd));
	memset((char *)data, 0, sizeof(struct usdhc_data));

	priv->op_context.cmd_only = 0;
	cmd->index = SDHC_READ_MULTIPLE_BLOCK;
	data->block_size = priv->card_info.sd_block_size;
	data->block_count = count;
	data->rx_data = (uint32_t *)buf;
	data->cmd12 = true;

	if (data->block_count == 1U) {
		cmd->index = SDHC_READ_SINGLE_BLOCK;
	}

	cmd->argument = sector;
	if (!(priv->card_info.card_flags & SDHC_HIGH_CAPACITY_FLAG)) {
		cmd->argument *= priv->card_info.sd_block_size;
	}

	cmd->rsp_type = SDHC_RSP_TYPE_R1;
	cmd->rsp_err_flags = SDHC_R1ERR_All_FLAG;

	return usdhc_xfer(priv);
}

static bool usdhc_set_sd_active(USDHC_Type *base)
{
	uint32_t timeout = 0xffff;

	base->SYS_CTRL |= USDHC_SYS_CTRL_INITA_MASK;
	/* Delay some time to wait card become active state. */
	while ((base->SYS_CTRL & USDHC_SYS_CTRL_INITA_MASK) ==
		USDHC_SYS_CTRL_INITA_MASK) {
		if (!timeout) {
			break;
		}
		timeout--;
	}

	return ((!timeout) ? false : true);
}

static void usdhc_get_host_capability(USDHC_Type *base,
	struct usdhc_capability *capability)
{
	uint32_t host_cap;
	uint32_t max_blk_len;

	host_cap = base->HOST_CTRL_CAP;

	/* Get the capability of USDHC. */
	max_blk_len = ((host_cap & USDHC_HOST_CTRL_CAP_MBL_MASK) >>
		USDHC_HOST_CTRL_CAP_MBL_SHIFT);
	capability->max_blk_len = (512U << max_blk_len);
	/* Other attributes not in HTCAPBLT register. */
	capability->max_blk_cnt = USDHC_MAX_BLOCK_COUNT;
	capability->host_flags = (host_cap & (USDHC_SUPPORT_ADMA_FLAG |
		USDHC_SUPPORT_HIGHSPEED_FLAG | USDHC_SUPPORT_DMA_FLAG |
		USDHC_SUPPORT_SUSPEND_RESUME_FLAG | USDHC_SUPPORT_V330_FLAG));
	capability->host_flags |= (host_cap & USDHC_SUPPORT_V300_FLAG);
	capability->host_flags |= (host_cap & USDHC_SUPPORT_V180_FLAG);
	capability->host_flags |=
		(host_cap & (USDHC_SUPPORT_DDR50_FLAG |
			USDHC_SUPPORT_SDR104_FLAG |
			USDHC_SUPPORT_SDR50_FLAG));
	/* USDHC support 4/8 bit data bus width. */
	capability->host_flags |= (USDHC_SUPPORT_4BIT_FLAG |
		USDHC_SUPPORT_8BIT_FLAG);
}

static bool usdhc_hw_reset(USDHC_Type *base, uint32_t mask, uint32_t timeout)
{
	base->SYS_CTRL |= (mask & (USDHC_SYS_CTRL_RSTA_MASK |
		USDHC_SYS_CTRL_RSTC_MASK | USDHC_SYS_CTRL_RSTD_MASK));
	/* Delay some time to wait reset success. */
	while ((base->SYS_CTRL & mask)) {
		if (!timeout) {
			break;
		}
		timeout--;
	}

	return ((!timeout) ? false : true);
}

static void usdhc_host_hw_init(USDHC_Type *base,
	const struct usdhc_config *config)
{
	uint32_t proctl, sysctl, wml;
	uint32_t int_mask;

	__ASSERT_NO_MSG(config);
	__ASSERT_NO_MSG((config->write_watermark >= 1U) &&
			(config->write_watermark <= 128U));
	__ASSERT_NO_MSG((config->read_watermark >= 1U) &&
			(config->read_watermark <= 128U));
	__ASSERT_NO_MSG(config->write_burst_len <= 16U);

	/* Reset USDHC. */
	usdhc_hw_reset(base, USDHC_RESET_ALL, 100U);

	proctl = base->PROT_CTRL;
	wml = base->WTMK_LVL;
	sysctl = base->SYS_CTRL;

	proctl &= ~(USDHC_PROT_CTRL_EMODE_MASK | USDHC_PROT_CTRL_DMASEL_MASK);
	/* Endian mode*/
	proctl |= USDHC_PROT_CTRL_EMODE(config->endian);

	/* Watermark level */
	wml &= ~(USDHC_WTMK_LVL_RD_WML_MASK |
			USDHC_WTMK_LVL_WR_WML_MASK |
			USDHC_WTMK_LVL_RD_BRST_LEN_MASK |
			USDHC_WTMK_LVL_WR_BRST_LEN_MASK);
	wml |= (USDHC_WTMK_LVL_RD_WML(config->read_watermark) |
			USDHC_WTMK_LVL_WR_WML(config->write_watermark) |
			USDHC_WTMK_LVL_RD_BRST_LEN(config->read_burst_len) |
			USDHC_WTMK_LVL_WR_BRST_LEN(config->write_burst_len));

	/* config the data timeout value */
	sysctl &= ~USDHC_SYS_CTRL_DTOCV_MASK;
	sysctl |= USDHC_SYS_CTRL_DTOCV(config->data_timeout);

	base->SYS_CTRL = sysctl;
	base->WTMK_LVL = wml;
	base->PROT_CTRL = proctl;

	/* disable internal DMA and DDR mode */
	base->MIX_CTRL &= ~(USDHC_MIX_CTRL_DMAEN_MASK |
		USDHC_MIX_CTRL_DDR_EN_MASK);

	int_mask = (USDHC_INT_CMD_FLAG | USDHC_INT_CARD_DETECT_FLAG |
		USDHC_INT_DATA_FLAG | USDHC_INT_SDR104_TUNING_FLAG |
		USDHC_INT_BLK_GAP_EVENT_FLAG);

	base->INT_STATUS_EN |= int_mask;

}

static void usdhc_cd_gpio_cb(const struct device *dev,
				  struct gpio_callback *cb, uint32_t pins)
{
	struct usdhc_priv *priv =
		CONTAINER_OF(cb, struct usdhc_priv, detect_cb);
	const struct usdhc_config *config = priv->config;

	gpio_pin_interrupt_configure(dev, config->detect_pin, GPIO_INT_DISABLE);
}

static int usdhc_cd_gpio_init(const struct device *detect_gpio,
			      uint32_t pin, gpio_dt_flags_t flags,
			      struct gpio_callback *callback)
{
	int ret;

	ret = gpio_pin_configure(detect_gpio, pin, GPIO_INPUT | flags);
	if (ret)
		return ret;

	gpio_init_callback(callback, usdhc_cd_gpio_cb, BIT(pin));

	return gpio_add_callback(detect_gpio, callback);
}

static void usdhc_host_reset(struct usdhc_priv *priv)
{
	USDHC_Type *base = priv->config->base;

	usdhc_select_1_8_vol(base, false);
	usdhc_enable_ddr_mode(base, false, 0);
	usdhc_tuning(base, SDHC_STANDARD_TUNING_START, SDHC_TUINIG_STEP, false);
#if FSL_FEATURE_USDHC_HAS_HS400_MODE
	/* Disable HS400 mode */
	/* Disable DLL */
#endif
}

static int usdhc_app_host_cmd(struct usdhc_priv *priv, int retry,
	uint32_t arg, uint8_t app_cmd, uint32_t app_arg, enum sdhc_rsp_type rsp_type,
	enum sdhc_rsp_type app_rsp_type, bool app_cmd_only)
{
	struct usdhc_cmd *cmd = &priv->op_context.cmd;
	int ret;

APP_CMD_XFER_AGAIN:
	priv->op_context.cmd_only = 1;
	cmd->index = SDHC_APP_CMD;
	cmd->argument = arg;
	cmd->rsp_type = rsp_type;
	ret = usdhc_xfer(priv);
	retry--;
	if (ret && retry > 0) {
		goto APP_CMD_XFER_AGAIN;
	}

	priv->op_context.cmd_only = app_cmd_only;
	cmd->index = app_cmd;
	cmd->argument = app_arg;
	cmd->rsp_type = app_rsp_type;
	ret = usdhc_xfer(priv);
	if (ret && retry > 0) {
		goto APP_CMD_XFER_AGAIN;
	}

	return ret;
}

static int usdhc_sd_init(struct usdhc_priv *priv)
{
	const struct usdhc_config *config = priv->config;
	USDHC_Type *base = config->base;
	uint32_t app_cmd_41_arg = 0U;
	int ret, retry;
	struct usdhc_cmd *cmd = &priv->op_context.cmd;
	struct usdhc_data *data = &priv->op_context.data;

	if (!priv->host_ready) {
		return -ENODEV;
	}

	/* reset variables */
	priv->card_info.card_flags = 0U;
	/* set DATA bus width 1bit at beginning*/
	usdhc_set_bus_width(base, USDHC_DATA_BUS_WIDTH_1BIT);
	/*set card freq to 400KHZ at begging*/
	priv->card_info.busclk_hz =
		usdhc_set_sd_clk(base, priv->src_clk_hz,
			SDMMC_CLOCK_400KHZ);
	/* send card active */
	ret = usdhc_set_sd_active(base);
	if (ret == false) {
		return -EIO;
	}

	/* Get host capability. */
	usdhc_get_host_capability(base, &priv->host_capability);

	/* card go idle */
	usdhc_op_ctx_init(priv, 1, SDHC_GO_IDLE_STATE, 0, SDHC_RSP_TYPE_NONE);

	ret = usdhc_xfer(priv);
	if (ret) {
		return ret;
	}

	if (USDHC_SUPPORT_V330_FLAG != SDMMCHOST_NOT_SUPPORT) {
		app_cmd_41_arg |= (SD_OCR_VDD32_33FLAG | SD_OCR_VDD33_34FLAG);
		priv->card_info.voltage = SD_VOL_3_3_V;
	} else if (USDHC_SUPPORT_V300_FLAG != SDMMCHOST_NOT_SUPPORT) {
		app_cmd_41_arg |= SD_OCR_VDD29_30FLAG;
		priv->card_info.voltage = SD_VOL_3_3_V;
	}

	/* allow user select the work voltage, if not select,
	 * sdmmc will handle it automatically
	 */
	if (priv->config->no_1_8_v == false) {
		if (USDHC_SUPPORT_V180_FLAG != SDMMCHOST_NOT_SUPPORT) {
			app_cmd_41_arg |= SD_OCR_SWITCH_18_REQ_FLAG;
		}
	}

	/* Check card's supported interface condition. */
	usdhc_op_ctx_init(priv, 1, SDHC_SEND_IF_COND,
		SDHC_VHS_3V3 | SDHC_CHECK, SDHC_RSP_TYPE_R7);

	retry = 10;
	while (retry) {
		ret = usdhc_xfer(priv);
		if (!ret) {
			if ((cmd->response[0U] & 0xFFU) != SDHC_CHECK) {
				ret = -ENOTSUP;
			} else {
				break;
			}
		}
		retry--;
	}

	if (!ret) {
		/* SDHC or SDXC card */
		app_cmd_41_arg |= SD_OCR_HOST_CAP_FLAG;
		priv->card_info.card_flags |= USDHC_SDHC_FLAG;
	} else {
		/* SDSC card */
		LOG_ERR("USDHC SDSC not implemented yet!");
		return -ENOTSUP;
	}

	/* Set card interface condition according to SDHC capability and
	 * card's supported interface condition.
	 */
APP_SEND_OP_COND_AGAIN:
	usdhc_op_ctx_init(priv, 1, 0, 0, SDHC_RSP_TYPE_NONE);
	ret = usdhc_app_host_cmd(priv, NXP_SDMMC_MAX_VOLTAGE_RETRIES, 0,
		SDHC_APP_SEND_OP_COND, app_cmd_41_arg,
		SDHC_RSP_TYPE_R1, SDHC_RSP_TYPE_R3, 1);
	if (ret) {
		LOG_ERR("APP Condition CMD failed:%d", ret);
		return ret;
	}
	if (cmd->response[0U] & SD_OCR_PWR_BUSY_FLAG) {
		/* high capacity check */
		if (cmd->response[0U] & SD_OCR_CARD_CAP_FLAG) {
			priv->card_info.card_flags |= SDHC_HIGH_CAPACITY_FLAG;
		}

		if (priv->config->no_1_8_v == false) {
			/* 1.8V support */
			if (cmd->response[0U] & SD_OCR_SWITCH_18_ACCEPT_FLAG) {
				priv->card_info.card_flags |= SDHC_1800MV_FLAG;
			}
		}
		priv->card_info.raw_ocr = cmd->response[0U];
	} else {
		goto APP_SEND_OP_COND_AGAIN;
	}

	/* check if card support 1.8V */
	if ((priv->card_info.card_flags & USDHC_VOL_1_8V_FLAG)) {
		usdhc_op_ctx_init(priv, 1, SDHC_VOL_SWITCH,
			0, SDHC_RSP_TYPE_R1);

		ret = usdhc_xfer(priv);
		if (!ret) {
			ret = usdhc_vol_switch(priv);
		}
		if (ret) {
			LOG_ERR("Voltage switch failed: %d", ret);
			return ret;
		}
		priv->card_info.voltage = SD_VOL_1_8_V;
	}

	/* Initialize card if the card is SD card. */
	usdhc_op_ctx_init(priv, 1, SDHC_ALL_SEND_CID, 0, SDHC_RSP_TYPE_R2);

	ret = usdhc_xfer(priv);
	if (!ret) {
		memcpy(priv->card_info.raw_cid, cmd->response,
			sizeof(priv->card_info.raw_cid));
		sdhc_decode_cid(&priv->card_info.cid,
			priv->card_info.raw_cid);
	} else {
		LOG_ERR("All send CID CMD failed: %d", ret);
		return ret;
	}

	usdhc_op_ctx_init(priv, 1, SDHC_SEND_RELATIVE_ADDR,
		0, SDHC_RSP_TYPE_R6);

	ret = usdhc_xfer(priv);
	if (!ret) {
		priv->card_info.relative_addr = (cmd->response[0U] >> 16U);
	} else {
		LOG_ERR("Send relative address CMD failed: %d", ret);
		return ret;
	}

	usdhc_op_ctx_init(priv, 1, SDHC_SEND_CSD,
		(priv->card_info.relative_addr << 16U), SDHC_RSP_TYPE_R2);

	ret = usdhc_xfer(priv);
	if (!ret) {
		memcpy(priv->card_info.raw_csd, cmd->response,
			sizeof(priv->card_info.raw_csd));
		sdhc_decode_csd(&priv->card_info.csd, priv->card_info.raw_csd,
			&priv->card_info.sd_block_count,
			&priv->card_info.sd_block_size);
	} else {
		LOG_ERR("Send CSD CMD failed: %d", ret);
		return ret;
	}

	usdhc_op_ctx_init(priv, 1, SDHC_SELECT_CARD,
		priv->card_info.relative_addr << 16U,
		SDHC_RSP_TYPE_R1);

	ret = usdhc_xfer(priv);
	if (ret || (cmd->response[0U] & SDHC_R1ERR_All_FLAG)) {
		LOG_ERR("Select card CMD failed: %d", ret);
		return -EIO;
	}

	usdhc_op_ctx_init(priv, 0, 0, 0, SDHC_RSP_TYPE_NONE);
	data->block_size = 8;
	data->block_count = 1;
	data->rx_data = &priv->card_info.raw_scr[0];
	ret = usdhc_app_host_cmd(priv, 1, (priv->card_info.relative_addr << 16),
		SDHC_APP_SEND_SCR, 0,
		SDHC_RSP_TYPE_R1, SDHC_RSP_TYPE_R1, 0);

	if (ret) {
		LOG_ERR("Send SCR following APP CMD failed: %d", ret);
		return ret;
	}

	switch (config->endian) {
	case USDHC_LITTLE_ENDIAN:
		priv->card_info.raw_scr[0] =
			SWAP_WORD_BYTE_SEQUENCE(priv->card_info.raw_scr[0]);
		priv->card_info.raw_scr[1] =
			SWAP_WORD_BYTE_SEQUENCE(priv->card_info.raw_scr[1]);
	break;
	case USDHC_BIG_ENDIAN:
	break;
	case USDHC_HALF_WORD_BIG_ENDIAN:
		priv->card_info.raw_scr[0U] =
			SWAP_HALF_WROD_BYTE_SEQUENCE(
				priv->card_info.raw_scr[0U]);
		priv->card_info.raw_scr[1U] =
			SWAP_HALF_WROD_BYTE_SEQUENCE(
				priv->card_info.raw_scr[1U]);
	break;
	default:
		return -EINVAL;
	}

	sdhc_decode_scr(&priv->card_info.scr, priv->card_info.raw_scr,
		&priv->card_info.version);
	if (priv->card_info.scr.sd_width & 0x4U) {
		priv->card_info.card_flags |=
			USDHC_4BIT_WIDTH_FLAG;
	}
	/* speed class control cmd */
	if (priv->card_info.scr.cmd_support & 0x01U) {
		priv->card_info.card_flags |=
			USDHC_SPEED_CLASS_CONTROL_CMD_FLAG;
	}
	/* set block count cmd */
	if (priv->card_info.scr.cmd_support & 0x02U) {
		priv->card_info.card_flags |=
			USDHC_SET_BLK_CNT_CMD23_FLAG;
	}

	/* Set to max frequency in non-high speed mode. */
	priv->card_info.busclk_hz = usdhc_set_sd_clk(base,
		priv->src_clk_hz, SD_CLOCK_25MHZ);

	/* Set to 4-bit data bus mode. */
	if ((priv->host_capability.host_flags & USDHC_SUPPORT_4BIT_FLAG) &&
		(priv->card_info.card_flags & USDHC_4BIT_WIDTH_FLAG)) {
		usdhc_op_ctx_init(priv, 1, 0, 0, SDHC_RSP_TYPE_NONE);

		ret = usdhc_app_host_cmd(priv, 1,
				(priv->card_info.relative_addr << 16),
				SDHC_APP_SET_BUS_WIDTH, 2,
				SDHC_RSP_TYPE_R1, SDHC_RSP_TYPE_R1, 1);

		if (ret) {
			LOG_ERR("Set bus width failed: %d", ret);
			return ret;
		}
		usdhc_set_bus_width(base, USDHC_DATA_BUS_WIDTH_4BIT);
	}

	if (priv->card_info.version >= SD_SPEC_VER3_0) {
		/* set sd card driver strength */
		ret = usdhc_select_fun(priv, SD_GRP_DRIVER_STRENGTH_MODE,
			priv->card_info.driver_strength);
		if (ret) {
			LOG_ERR("Set SD driver strength failed: %d", ret);
			return ret;
		}

		/* set sd card current limit */
		ret = usdhc_select_fun(priv, SD_GRP_CURRENT_LIMIT_MODE,
			priv->card_info.max_current);
		if (ret) {
			LOG_ERR("Set SD current limit failed: %d", ret);
			return ret;
		}
	}

	/* set block size */
	usdhc_op_ctx_init(priv, 1, SDHC_SET_BLOCK_SIZE,
		priv->card_info.sd_block_size, SDHC_RSP_TYPE_R1);

	ret = usdhc_xfer(priv);
	if (ret || cmd->response[0U] & SDHC_R1ERR_All_FLAG) {
		LOG_ERR("Set block size failed: %d", ret);
		return -EIO;
	}

	if (priv->card_info.version > SD_SPEC_VER1_0) {
		/* select bus timing */
		ret = usdhc_select_bus_timing(priv);
		if (ret) {
			LOG_ERR("Select bus timing failed: %d", ret);
			return ret;
		}
	}

	retry = 10;
	ret = -EIO;
	while (ret && retry >= 0) {
		ret = usdhc_read_sector(priv, (uint8_t *)g_usdhc_rx_dummy, 0, 1);
		if (!ret) {
			break;
		}
		retry--;
	}

	if (ret) {
		LOG_ERR("USDHC bus device initalization failed!");
	}

	return ret;
}

static K_MUTEX_DEFINE(z_usdhc_init_lock);

static int usdhc_board_access_init(struct usdhc_priv *priv)
{
	const struct usdhc_config *config = priv->config;
	int ret = 0;
	uint32_t gpio_level;
	USDHC_Type *base = config->base;

	if (config->pwr_name) {
		priv->pwr_gpio = device_get_binding(config->pwr_name);
		if (!priv->pwr_gpio) {
			return -ENODEV;
		}
	}

	if (config->detect_name) {
		priv->detect_type = SD_DETECT_GPIO_CD;
		priv->detect_gpio = device_get_binding(config->detect_name);
		if (!priv->detect_gpio) {
			return -ENODEV;
		}
	}

	if (priv->pwr_gpio) {
		ret = gpio_pin_configure(priv->pwr_gpio,
				config->pwr_pin,
				GPIO_OUTPUT_ACTIVE |
				config->pwr_flags);
		if (ret) {
			return ret;
		}

		/* 100ms delay to make sure SD card is stable,
		 * maybe could be shorter
		 */
		k_busy_wait(100000);
	}

	if (!priv->detect_gpio) {
		LOG_INF("USDHC detection other than GPIO");
		/* DATA3 does not monitor card insertion */
		base->PROT_CTRL &= ~USDHC_PROT_CTRL_D3CD_MASK;
		if ((base->PRES_STATE & USDHC_PRES_STATE_CINST_MASK) != 0) {
			priv->inserted = true;
		} else {
			priv->inserted = false;
			return -ENODEV;
		}
	} else {
		ret = usdhc_cd_gpio_init(priv->detect_gpio,
				config->detect_pin,
				config->detect_flags,
				&priv->detect_cb);
		if (ret) {
			return ret;
		}
		ret = gpio_pin_get(priv->detect_gpio, config->detect_pin);
		if (ret < 0) {
			return ret;
		}

		gpio_level = ret;

		if (gpio_level == 0) {
			priv->inserted = false;
			LOG_ERR("NO SD inserted!");

			return -ENODEV;
		}

		priv->inserted = true;
		LOG_INF("SD inserted!");
	}
	return 0;
}

static int usdhc_access_init(const struct device *dev)
{
	const struct usdhc_config *config = dev->config;
	struct usdhc_priv *priv = dev->data;
	int ret;

	(void)k_mutex_lock(&z_usdhc_init_lock, K_FOREVER);

	memset((char *)priv, 0, sizeof(struct usdhc_priv));
	priv->config = config;

	if (!config->base) {
		k_mutex_unlock(&z_usdhc_init_lock);

		return -ENODEV;
	}

	if (clock_control_get_rate(config->clock_dev,
				   config->clock_subsys,
				   &priv->src_clk_hz)) {
		return -EINVAL;
	}

	ret = usdhc_board_access_init(priv);
	if (ret) {
		k_mutex_unlock(&z_usdhc_init_lock);

		return ret;
	}

	priv->op_context.dma_cfg.dma_mode = USDHC_DMA_ADMA2;
	priv->op_context.dma_cfg.burst_len = USDHC_INCR_BURST_LEN;
	/*No DMA used for this Version*/
	priv->op_context.dma_cfg.adma_table = 0;
	priv->op_context.dma_cfg.adma_table_words = USDHC_ADMA_TABLE_WORDS;
	usdhc_host_hw_init(config->base, config);
	priv->host_ready = 1;

	usdhc_host_reset(priv);
	ret = usdhc_sd_init(priv);
	k_mutex_unlock(&z_usdhc_init_lock);

	return ret;
}

static int disk_usdhc_access_status(struct disk_info *disk)
{
	const struct device *dev = disk->dev;
	struct usdhc_priv *priv = dev->data;

	return priv->status;
}

static int disk_usdhc_access_read(struct disk_info *disk, uint8_t *buf,
				 uint32_t sector, uint32_t count)
{
	const struct device *dev = disk->dev;
	struct usdhc_priv *priv = dev->data;

	LOG_DBG("sector=%u count=%u", sector, count);

	return usdhc_read_sector(priv, buf, sector, count);
}

static int disk_usdhc_access_write(struct disk_info *disk, const uint8_t *buf,
				  uint32_t sector, uint32_t count)
{
	const struct device *dev = disk->dev;
	struct usdhc_priv *priv = dev->data;

	LOG_DBG("sector=%u count=%u", sector, count);

	return usdhc_write_sector(priv, buf, sector, count);
}

static int disk_usdhc_access_ioctl(struct disk_info *disk, uint8_t cmd, void *buf)
{
	const struct device *dev = disk->dev;
	struct usdhc_priv *priv = dev->data;
	int err;

	err = sdhc_map_disk_status(priv->status);
	if (err != 0) {
		return err;
	}

	switch (cmd) {
	case DISK_IOCTL_CTRL_SYNC:
		break;
	case DISK_IOCTL_GET_SECTOR_COUNT:
		*(uint32_t *)buf = priv->card_info.sd_block_count;
		break;
	case DISK_IOCTL_GET_SECTOR_SIZE:
		*(uint32_t *)buf = priv->card_info.sd_block_size;
		break;
	case DISK_IOCTL_GET_ERASE_BLOCK_SZ:
		*(uint32_t *)buf = priv->card_info.sd_block_size;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int disk_usdhc_access_init(struct disk_info *disk)
{
	const struct device *dev = disk->dev;
	struct usdhc_priv *priv = dev->data;

	if (priv->status == DISK_STATUS_OK) {
		/* Called twice, don't re-init. */
		return 0;
	}

	return usdhc_access_init(dev);
}

static const struct disk_operations usdhc_disk_ops = {
	.init = disk_usdhc_access_init,
	.status = disk_usdhc_access_status,
	.read = disk_usdhc_access_read,
	.write = disk_usdhc_access_write,
	.ioctl = disk_usdhc_access_ioctl,
};

static struct disk_info usdhc_disk = {
	.name = CONFIG_SDMMC_VOLUME_NAME,
	.ops = &usdhc_disk_ops,
};

static int disk_usdhc_init(const struct device *dev)
{
	struct usdhc_priv *priv = dev->data;

	priv->status = DISK_STATUS_UNINIT;

	usdhc_disk.dev = dev;

	return disk_access_register(&usdhc_disk);
}

#define DISK_ACCESS_USDHC_INIT_NONE(n)

#define DISK_ACCESS_USDHC_INIT_PWR_PROPS(n)				\
	.pwr_name = DT_INST_GPIO_LABEL(n, pwr_gpios),			\
	.pwr_pin = DT_INST_GPIO_PIN(n, pwr_gpios),			\
	.pwr_flags = DT_INST_GPIO_FLAGS(n, pwr_gpios),

#define DISK_ACCESS_USDHC_INIT_CD_PROPS(n)				\
	.detect_name = DT_INST_GPIO_LABEL(n, cd_gpios),			\
	.detect_pin = DT_INST_GPIO_PIN(n, cd_gpios),			\
	.detect_flags = DT_INST_GPIO_FLAGS(n, cd_gpios),

#define DISK_ACCESS_USDHC_INIT_PWR(n)					\
	COND_CODE_1(DT_INST_NODE_HAS_PROP(n, pwr_gpios),		\
		    (DISK_ACCESS_USDHC_INIT_PWR_PROPS(n)),		\
		    (DISK_ACCESS_USDHC_INIT_NONE(n)))

#define DISK_ACCESS_USDHC_INIT_CD(n)					\
	COND_CODE_1(DT_INST_NODE_HAS_PROP(n, cd_gpios),			\
		    (DISK_ACCESS_USDHC_INIT_CD_PROPS(n)),		\
		    (DISK_ACCESS_USDHC_INIT_NONE(n)))

#define DISK_ACCESS_USDHC_INIT(n)					\
	static const struct usdhc_config usdhc_config_##n = {		\
		.base = (USDHC_Type  *) DT_INST_REG_ADDR(n),		\
		.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)),	\
		.clock_subsys =						\
		(clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name),	\
		.nusdhc = n,						\
		DISK_ACCESS_USDHC_INIT_PWR(n)				\
		DISK_ACCESS_USDHC_INIT_CD(n)				\
		.no_1_8_v = DT_INST_PROP(n, no_1_8_v),			\
		.data_timeout = USDHC_DATA_TIMEOUT,			\
		.endian = USDHC_LITTLE_ENDIAN,				\
		.read_watermark = USDHC_READ_WATERMARK_LEVEL,		\
		.write_watermark =  USDHC_WRITE_WATERMARK_LEVEL,	\
		.read_burst_len = USDHC_READ_BURST_LEN,			\
		.write_burst_len = USDHC_WRITE_BURST_LEN,		\
	};								\
									\
	static struct usdhc_priv usdhc_priv_##n;			\
									\
	DEVICE_DT_INST_DEFINE(n,					\
			    &disk_usdhc_init,				\
			    NULL,					\
			    &usdhc_priv_##n,				\
			    &usdhc_config_##n,				\
			    POST_KERNEL,				\
			    CONFIG_SDMMC_INIT_PRIORITY,			\
			    NULL);

DT_INST_FOREACH_STATUS_OKAY(DISK_ACCESS_USDHC_INIT)
