Removes unused instances of struct usdhc_priv in the nxp usdhc driver. The DISK_ACCESS_USDHC_INIT macro is responsible for instantiating these variables. Signed-off-by: Maureen Helm <maureen.helm@nxp.com>
2839 lines
73 KiB
C
2839 lines
73 KiB
C
/* disk_access_usdhc.c - NXP USDHC driver*/
|
|
|
|
/*
|
|
* Copyright (c) 2019 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nxp_imx_usdhc
|
|
|
|
#include <disk/disk_access.h>
|
|
#include <drivers/gpio.h>
|
|
#include <sys/byteorder.h>
|
|
#include <soc.h>
|
|
#include <drivers/clock_control.h>
|
|
|
|
#include "disk_access_sdhc.h"
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(usdhc, LOG_LEVEL_INF);
|
|
|
|
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;
|
|
char *clock_name;
|
|
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;
|
|
|
|
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;
|
|
|
|
struct device *pwr_gpio;
|
|
struct device *detect_gpio;
|
|
struct gpio_callback detect_cb;
|
|
|
|
enum host_detect_type detect_type;
|
|
bool inserted;
|
|
|
|
struct device *clock_dev;
|
|
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 = z_timer_cycle_get_32();
|
|
|
|
while (z_timer_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\r\n",
|
|
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(src_clk_hz != 0U);
|
|
assert((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(config);
|
|
assert((config->write_watermark >= 1U) &&
|
|
(config->write_watermark <= 128U));
|
|
assert((config->read_watermark >= 1U) &&
|
|
(config->read_watermark <= 128U));
|
|
assert(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(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(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
|
|
#error Not implemented!
|
|
/* 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 (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!\r\n");
|
|
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\r\n", 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;
|
|
}
|
|
/* 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\r\n", 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\r\n", 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\r\n", 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\r\n", 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\r\n", 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\r\n", 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\r\n", ret);
|
|
return ret;
|
|
}
|
|
usdhc_set_bus_width(base, USDHC_DATA_BUS_WIDTH_4BIT);
|
|
}
|
|
|
|
/* 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 strehgth failed: %d\r\n", 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\r\n", 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\r\n", ret);
|
|
return -EIO;
|
|
}
|
|
|
|
/* select bus timing */
|
|
ret = usdhc_select_bus_timing(priv);
|
|
if (ret) {
|
|
LOG_ERR("Select bus timing failed: %d\r\n", 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!\r\n");
|
|
}
|
|
|
|
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;
|
|
uint32_t gpio_level;
|
|
|
|
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 not implemented!\r\n");
|
|
return 0;
|
|
}
|
|
|
|
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!\r\n");
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
priv->inserted = true;
|
|
LOG_INF("SD inserted!\r\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int usdhc_access_init(const struct device *dev)
|
|
{
|
|
const struct usdhc_config *config = dev->config_info;
|
|
struct usdhc_priv *priv = dev->driver_data;
|
|
int ret;
|
|
|
|
(void)k_mutex_lock(&z_usdhc_init_lock, K_FOREVER);
|
|
|
|
memset((char *)priv, 0, sizeof(struct usdhc_priv));
|
|
priv->config = config;
|
|
|
|
priv->clock_dev = device_get_binding(config->clock_name);
|
|
if (priv->clock_dev == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!config->base) {
|
|
k_mutex_unlock(&z_usdhc_init_lock);
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (clock_control_get_rate(priv->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)
|
|
{
|
|
struct device *dev = disk->dev;
|
|
struct usdhc_priv *priv = dev->driver_data;
|
|
|
|
return priv->status;
|
|
}
|
|
|
|
static int disk_usdhc_access_read(struct disk_info *disk, uint8_t *buf,
|
|
uint32_t sector, uint32_t count)
|
|
{
|
|
struct device *dev = disk->dev;
|
|
struct usdhc_priv *priv = dev->driver_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)
|
|
{
|
|
struct device *dev = disk->dev;
|
|
struct usdhc_priv *priv = dev->driver_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)
|
|
{
|
|
struct device *dev = disk->dev;
|
|
struct usdhc_priv *priv = dev->driver_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)
|
|
{
|
|
struct device *dev = disk->dev;
|
|
struct usdhc_priv *priv = dev->driver_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_DISK_SDHC_VOLUME_NAME,
|
|
.ops = &usdhc_disk_ops,
|
|
};
|
|
|
|
static int disk_usdhc_init(struct device *dev)
|
|
{
|
|
struct usdhc_priv *priv = dev->driver_data;
|
|
|
|
priv->status = DISK_STATUS_UNINIT;
|
|
|
|
usdhc_disk.dev = dev;
|
|
|
|
return disk_access_register(&usdhc_disk);
|
|
}
|
|
|
|
#define DISK_ACCESS_USDHC_INIT(n) \
|
|
static const struct usdhc_config usdhc_config_##n = { \
|
|
.base = (USDHC_Type *) DT_INST_REG_ADDR(n), \
|
|
.clock_name = DT_INST_CLOCKS_LABEL(n), \
|
|
.clock_subsys = \
|
|
(clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \
|
|
.nusdhc = 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), \
|
|
.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), \
|
|
.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_AND_API_INIT(usdhc_dev##n, \
|
|
DT_INST_LABEL(n), \
|
|
&disk_usdhc_init, \
|
|
&usdhc_priv_##n, \
|
|
&usdhc_config_##n, \
|
|
APPLICATION, \
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
|
|
NULL);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(DISK_ACCESS_USDHC_INIT)
|