zephyr/drivers/disk/usdhc.c
Daniel DeGrasse 54eb607392 drivers: disk: usdhc: enable pinctrl for usdhc driver
Enable pinctrl driver for usdhc. USDHC driver uses custom pinctrl states
for fast, slow, and medium signal frequencies, as well as pin pull for
SD detection.

Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>
2022-03-25 15:47:54 -05:00

3058 lines
78 KiB
C

/*
* 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>
#ifdef CONFIG_PINCTRL
#include <drivers/pinctrl.h>
#define PINCTRL_STATE_SLOW PINCTRL_STATE_PRIV_START
#define PINCTRL_STATE_MED (PINCTRL_STATE_PRIV_START + 1U)
#define PINCTRL_STATE_FAST (PINCTRL_STATE_PRIV_START + 2U)
#define PINCTRL_STATE_NOPULL (PINCTRL_STATE_PRIV_START + 3U)
#endif
#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 detect_dat3;
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
*/
#ifdef CONFIG_PINCTRL
const struct pinctrl_dev_config *pincfg;
/* Pin configuration
*/
#endif
};
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)
#define USDHC_DAT3_PULL_DOWN 0U /*!< Data 3 pull down */
#define USDHC_DAT3_PULL_UP 1U /*!< Data 3 pull up */
#define USDHC_WAIT_IDLE_TIMEOUT 600U
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);
}
/* calculate 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);
}
#if (defined(FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN) && FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN)
/* select DMA mode */
base->PROT_CTRL &= ~(USDHC_PROT_CTRL_DMASEL_MASK);
base->PROT_CTRL |= USDHC_PROT_CTRL_DMASEL(dma_cfg->dma_mode);
#else
/* 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);
#endif
/* 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 delay 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_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;
}
/* Enable auto retuning */
base->MIX_CTRL |= USDHC_MIX_CTRL_AUTO_TUNE_EN_MASK;
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);
/* delay 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 != 0);
if (sd_clk_hz > src_clk_hz) {
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 , divisor 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
* total 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) {
#ifdef CONFIG_PINCTRL
pinctrl_apply_state(priv->config->pincfg, PINCTRL_STATE_MED);
#else
imxrt_usdhc_pinmux(config->nusdhc, false,
CARD_BUS_FREQ_100MHZ1,
CARD_BUS_STRENGTH_7);
#endif /* CONFIG_PINCTRL */
} else {
#ifdef CONFIG_PINCTRL
pinctrl_apply_state(priv->config->pincfg, PINCTRL_STATE_FAST);
#else
imxrt_usdhc_pinmux(config->nusdhc, false,
CARD_BUS_FREQ_200MHZ,
CARD_BUS_STRENGTH_7);
#endif /* CONFIG_PINCTRL */
}
/* 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 {
#ifdef CONFIG_PINCTRL
pinctrl_apply_state(priv->config->pincfg, PINCTRL_STATE_SLOW);
#else
imxrt_usdhc_pinmux(config->nusdhc, false,
CARD_BUS_FREQ_100MHZ1,
CARD_BUS_STRENGTH_4);
#endif /* CONFIG_PINCTRL */
}
return error;
}
static int usdhc_send_status(struct usdhc_priv *priv)
{
int retry = 10, ret = -ETIMEDOUT;
struct usdhc_cmd *cmd = &priv->op_context.cmd;
usdhc_op_ctx_init(priv, true, SDHC_SEND_STATUS,
priv->card_info.relative_addr << 16U,
SDHC_RSP_TYPE_R1);
while (retry--) {
ret = usdhc_xfer(priv);
if (ret) {
LOG_DBG("Send CMD13 failed with host error %d", ret);
continue;
} else {
if (((cmd->response[0U] & SDHC_R1READY_FOR_DATA) != 0U) &&
(SD_R1_CURRENT_STATE(cmd->response[0U]) != SDMMC_R1_PROGRAM)) {
/* Card is idle */
ret = 0;
} else {
ret = -EBUSY;
}
break;
}
}
return ret;
}
static int usdhc_poll_card_status_busy(struct usdhc_priv *priv,
uint32_t timeout_ms)
{
USDHC_Type *base = priv->config->base;
uint32_t timeout_us = timeout_ms * 1000;
int card_busy, ret = -ETIMEDOUT;
while (timeout_us) {
/* Check card status */
card_busy = (base->PRES_STATE & USDHC_DATA0_LINE_LEVEL_FLAG) !=
USDHC_DATA0_LINE_LEVEL_FLAG;
if (!card_busy) {
/* Send status to SD card and return from wait */
ret = usdhc_send_status(priv);
if (!ret) {
break;
}
} else {
/* Delay 125us to throttle the polling rate */
k_busy_wait(125);
timeout_us -= 125;
}
}
return ret;
}
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));
if (sector + count > priv->card_info.sd_block_count) {
return -EIO;
}
if (usdhc_poll_card_status_busy(priv, USDHC_WAIT_IDLE_TIMEOUT)) {
return -ETIMEDOUT;
}
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));
if (sector + count > priv->card_info.sd_block_count) {
return -EIO;
}
if (usdhc_poll_card_status_busy(priv, USDHC_WAIT_IDLE_TIMEOUT)) {
return -ETIMEDOUT;
}
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);
#if (defined(FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN) && FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN)
/* Watermark level */
wml &= ~(USDHC_WTMK_LVL_RD_WML_MASK | USDHC_WTMK_LVL_WR_WML_MASK);
wml |= (USDHC_WTMK_LVL_RD_WML(config->read_watermark) |
USDHC_WTMK_LVL_WR_WML(config->write_watermark));
#else
/* 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));
#endif
/* 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 initialization failed!");
}
return ret;
}
static K_MUTEX_DEFINE(z_usdhc_init_lock);
static int usdhc_dat3_pull(bool pullup, struct usdhc_priv *priv)
{
int ret = 0U;
#ifdef CONFIG_PINCTRL
if (pullup) {
/* Default pin configuration pulls dat3 up */
ret = pinctrl_apply_state(priv->config->pincfg, PINCTRL_STATE_DEFAULT);
if (ret) {
return ret;
}
} else {
/* remove pull on dat3 */
ret = pinctrl_apply_state(priv->config->pincfg, PINCTRL_STATE_NOPULL);
if (ret) {
LOG_ERR("No floating pinctrl state");
return ret;
}
}
#else
/* Call board specific function to pull down DAT3 */
imxrt_usdhc_dat3_pull(pullup);
#endif
#ifdef CONFIG_SDMMC_USDHC_DAT3_PWR_TOGGLE
if (!pullup) {
/* Power off the card to clear DAT3 legacy status */
if (priv->pwr_gpio) {
ret = gpio_pin_set(priv->pwr_gpio, priv->config->pwr_pin, 0);
if (ret) {
return ret;
}
/* Delay for card power off to complete */
usdhc_millsec_delay(CONFIG_SDMMC_USDHC_DAT3_PWR_DELAY);
ret = gpio_pin_set(priv->pwr_gpio, priv->config->pwr_pin, 1);
if (ret) {
return ret;
}
}
}
#endif
return ret;
}
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 (config->detect_dat3) {
priv->detect_type = SD_DETECT_HOST_DATA3;
}
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");
if (config->detect_dat3) {
LOG_INF("USDHC detection using DAT3 pull");
base->PROT_CTRL |= USDHC_PROT_CTRL_D3CD_MASK;
/* Pull down DAT3 */
usdhc_dat3_pull(USDHC_DAT3_PULL_DOWN, priv);
} else {
/* 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;
if (config->detect_dat3) {
usdhc_dat3_pull(USDHC_DAT3_PULL_UP, priv);
/* Disable DAT3 detect function */
base->PROT_CTRL &= ~USDHC_PROT_CTRL_D3CD_MASK;
}
} 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;
#ifdef CONFIG_PINCTRL
const struct usdhc_config *config = dev->config;
int err;
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
if (err) {
return err;
}
#endif /* CONFIG_PINCTRL */
priv->status = DISK_STATUS_UNINIT;
usdhc_disk.dev = dev;
return disk_access_register(&usdhc_disk);
}
#ifdef CONFIG_PINCTRL
#define PINCTRL_DEFINE(n) PINCTRL_DT_INST_DEFINE(n);
#define PINCTRL_INIT(n) .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),
#else
#define PINCTRL_DEFINE(n)
#define PINCTRL_INIT(n)
#endif /* CONFIG_PINCTRL */
#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) \
PINCTRL_DEFINE(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), \
.detect_dat3 = DT_INST_PROP(n, detect_dat3), \
.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, \
PINCTRL_INIT(n) \
}; \
\
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)