1961 lines
67 KiB
C
1961 lines
67 KiB
C
/*
|
|
* Copyright (c) 2013, 2023, BlackBerry Limited.
|
|
* Copyright (c) 2016, Freescale Semiconductor, Inc.
|
|
* Copyright (c) 2017-2022, NXP.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
* may not reproduce, modify or distribute this software except in
|
|
* compliance with the License. You may obtain a copy of the License
|
|
* at: http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTIES OF ANY KIND, either express or implied.
|
|
*
|
|
* This file may contain contributions from others, either as
|
|
* contributors under the License or as licensors under other terms.
|
|
* Please review this entire file for other proprietary rights or license
|
|
* notices, as well as the QNX Development Suite License Guide at
|
|
* http://licensing.qnx.com/license-guide/ for other information.
|
|
*
|
|
*/
|
|
|
|
#include <drvr/hwinfo.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <hw/inout.h>
|
|
#include <sys/mman.h>
|
|
#include <internal.h>
|
|
#include <sys/syspage.h>
|
|
#include <inttypes.h>
|
|
#include <fcntl.h>
|
|
|
|
#ifdef SDIO_HC_IMX8
|
|
|
|
#include <soc/nxp/imx8/common/imx_usdhc.h>
|
|
|
|
#include "imx8_hc.h"
|
|
|
|
//#define IMX8_SDHCX_DEBUG
|
|
|
|
/**
|
|
* Host controller interface
|
|
*
|
|
* @file hc/imx8_hc.c
|
|
* @addtogroup sdmmc_hc
|
|
* @{
|
|
*/
|
|
#ifdef IMX8_SDHCX_DEBUG
|
|
static int imx_sdhcx_reg_dump(sdio_hc_t *const hc, const char *const func, const int line)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1, "%s: line %d", func, line);
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1, "%s: HOST_CTRL_VER %x, HOST_CTRL_CAP %x",
|
|
__FUNCTION__,
|
|
in32(base + IMX_USDHC_HOST_CTRL_VER),
|
|
in32(base + IMX_USDHC_HOST_CTRL_CAP));
|
|
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s: SDMA_ARG2 %x, BLK %x, ARG %x, CMD %x, RSP10 %x, RSP32 %x, RSP54 %x, RSP76 %x",
|
|
__FUNCTION__,
|
|
in32(base + IMX_USDHC_DS_ADDR),
|
|
in32(base + IMX_USDHC_BLK_ATT),
|
|
in32(base + IMX_USDHC_CMD_ARG),
|
|
in32(base + IMX_USDHC_CMD_XFR_TYP),
|
|
in32(base + IMX_USDHC_CMD_RSP0),
|
|
in32(base + IMX_USDHC_CMD_RSP1),
|
|
in32(base + IMX_USDHC_CMD_RSP2),
|
|
in32(base + IMX_USDHC_CMD_RSP3));
|
|
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s: PSTATE %x, PCTL %x, SYSCTL %x, IS %x, IE %x, ISE %x, MIX %x, ADMA_ES %x, ADMA_ADDR %x",
|
|
__FUNCTION__,
|
|
in32(base + IMX_USDHC_PRES_STATE),
|
|
in32(base + IMX_USDHC_PROT_CTRL),
|
|
in32(base + IMX_USDHC_SYS_CTRL),
|
|
in32(base + IMX_USDHC_INT_STATUS),
|
|
in32(base + IMX_USDHC_INT_STATUS_EN),
|
|
in32(base + IMX_USDHC_INT_SIGNAL_EN),
|
|
in32(base + IMX_USDHC_MIX_CTRL),
|
|
in32(base + IMX_USDHC_ADMA_ERR_STATUS),
|
|
in32(base + IMX_USDHC_ADMA_SYS_ADDR));
|
|
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s: ACMD12 %x, DLLCTRL %x, DLLSTAT %x, CTCS %x, SDC %x, SDS %x, VSPEC %x, MBOOT %x, VSPEC2 %x, TUN %x",
|
|
__FUNCTION__,
|
|
in32(base + IMX_USDHC_AUTOCMD12_ERR_STATUS),
|
|
in32(base + IMX_USDHC_DLL_CTRL),
|
|
in32(base + IMX_USDHC_DLL_STATUS),
|
|
in32(base + IMX_USDHC_CLK_TUNE_CTRL_STATUS),
|
|
in32(base + IMX_USDHC_STROBE_DLL_CTRL),
|
|
in32(base + IMX_USDHC_STROBE_DLL_STATUS),
|
|
in32(base + IMX_USDHC_VEND_SPEC),
|
|
in32(base + IMX_USDHC_MMC_BOOT),
|
|
in32(base + IMX_USDHC_VEND_SPEC2),
|
|
in32(base + IMX_USDHC_TUNING_CTRL));
|
|
|
|
return (EOK);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Voltage reset using IPP_RST_N bit.
|
|
*
|
|
* @param hc Host controller handle.
|
|
*
|
|
*/
|
|
static void imx_sdhcx_voltage_reset(const sdio_hc_t *const hc)
|
|
{
|
|
#if SDHC_IPP_RESET_CTL != 0
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t sctl;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
sctl = in32(base + IMX_USDHC_SYS_CTRL);
|
|
|
|
sctl &= ~IMX_USDHC_SYS_CTRL_IPP_RST_N_MASK;
|
|
out32(base + IMX_USDHC_SYS_CTRL, sctl);
|
|
delay(10); /* Delay for card reset */
|
|
sctl |= IMX_USDHC_SYS_CTRL_IPP_RST_N_MASK;
|
|
out32(base + IMX_USDHC_SYS_CTRL, sctl);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Common wait routine.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param reg Register.
|
|
* @param mask Mask.
|
|
* @param val Value to check against.
|
|
* @param usec Delay time.
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_waitmask(const sdio_hc_t *const hc, const uint32_t reg, const uint32_t mask, const uint32_t val, const uint32_t usec)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t cnt;
|
|
int status;
|
|
uint32_t iter;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
status = ETIMEDOUT;
|
|
|
|
/* Fast poll for 1ms - 1us intervals */
|
|
for (cnt = min(usec, 1000); cnt; cnt--) {
|
|
if (((in32(base + reg)) & mask) == val) {
|
|
status = EOK;
|
|
break;
|
|
}
|
|
nanospin_ns(1000L);
|
|
}
|
|
|
|
if ((usec > 1000U) && status) {
|
|
iter = usec / 1000U;
|
|
for (cnt = iter; cnt; cnt--) {
|
|
if (((in32(base + reg)) & mask) == val) {
|
|
status = EOK;
|
|
break;
|
|
}
|
|
delay(1);
|
|
}
|
|
}
|
|
|
|
return (status);
|
|
}
|
|
|
|
/**
|
|
* Reset tuning circuit.
|
|
*
|
|
* @param hc Host controller handle.
|
|
*
|
|
*/
|
|
static void imx_sdhcx_reset_tuning(const sdio_hc_t *const hc)
|
|
{
|
|
const imx_sdhcx_hc_t *const sdhc = hc->cs_hdl;
|
|
const uintptr_t base = sdhc->base;
|
|
|
|
if (sdhc->tuning_mode == IMX_USDHC_TUNING_MANUAL) {
|
|
uint32_t mix_ctrl = in32(base + IMX_USDHC_MIX_CTRL);
|
|
mix_ctrl &= ~IMX_USDHC_MIX_CTRL_SMP_CLK_SEL_MASK;
|
|
mix_ctrl &= ~IMX_USDHC_MIX_CTRL_FBCLK_SEL_MASK;
|
|
out32(base + IMX_USDHC_MIX_CTRL, mix_ctrl);
|
|
out32(base + IMX_USDHC_CLK_TUNE_CTRL_STATUS, 0);
|
|
} else if (sdhc->tuning_mode == IMX_USDHC_TUNING_STANDARD) {
|
|
uint32_t acmd12 = in32(base + IMX_USDHC_AUTOCMD12_ERR_STATUS);
|
|
acmd12 &= ~IMX_USDHC_AUTOCMD12_ERR_STATUS_SMP_CLK_SEL_MASK;
|
|
acmd12 &= ~IMX_USDHC_AUTOCMD12_ERR_STATUS_EXECUTE_TUNING_MASK;
|
|
out32(base + IMX_USDHC_AUTOCMD12_ERR_STATUS, acmd12);
|
|
|
|
const uint32_t mask = IMX_USDHC_AUTOCMD12_ERR_STATUS_EXECUTE_TUNING_MASK;
|
|
const int status = imx_sdhcx_waitmask(hc, IMX_USDHC_AUTOCMD12_ERR_STATUS, mask, 0, 50);
|
|
if (status == ETIMEDOUT) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 1,
|
|
"Failed to clear EXECUTE_TUNING bit");
|
|
}
|
|
/* Clear Buffer Read Ready interrupt for cmd19 */
|
|
uint32_t intr_sts = in32(base + IMX_USDHC_INT_STATUS);
|
|
intr_sts |= IMX_USDHC_INT_STATUS_BRR_MASK;
|
|
out32(base + IMX_USDHC_INT_STATUS, intr_sts);
|
|
} else {
|
|
/* no operation. */
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param rst Reset mask (type). IMX_USDHC_SYS_CTRL_RSTA_MASK - Reset for ALL.
|
|
* IMX_USDHC_SYS_CTRL_RSTD_MASK - Reset DATA Line.
|
|
* IMX_USDHC_SYS_CTRL_RSTC_MASK - Reset CMD Line.
|
|
* IMX_USDHC_SYS_CTRL_RSTT_MASK - Reset tuning.
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_reset(const sdio_hc_t *const hc, const uint32_t rst)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t sctl;
|
|
int status;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
|
|
sctl = in32(base + IMX_USDHC_SYS_CTRL);
|
|
|
|
/* Wait up to 100 ms for reset to complete */
|
|
out32(base + IMX_USDHC_SYS_CTRL, sctl | rst);
|
|
status = imx_sdhcx_waitmask(hc, IMX_USDHC_SYS_CTRL, rst, 0, 100000);
|
|
|
|
return (status);
|
|
}
|
|
|
|
/**
|
|
* PIO transfer.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param cmd Command.
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_pio_xfer(sdio_hc_t *const hc, const sdio_cmd_t *const cmd)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
int len;
|
|
uint32_t msk;
|
|
int blksz;
|
|
uint8_t *addr;
|
|
int blks = (int)cmd->blks;
|
|
int xfer_len;
|
|
|
|
#ifdef IMX8_SDHCX_DEBUG
|
|
uint64_t cps, cycle1, cycle2 = 0;
|
|
cps = SYSPAGE_ENTRY(qtime)->cycles_per_sec;
|
|
cycle1 = ClockCycles();
|
|
#endif
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
msk = (cmd->flags & SCF_DIR_IN) ? (uint32_t)IMX_USDHC_PRES_STATE_BREN_MASK : (uint32_t)IMX_USDHC_PRES_STATE_BWEN_MASK;
|
|
|
|
/* Multiple blocks */
|
|
while (blks--) {
|
|
blksz = (int)cmd->blksz;
|
|
while (blksz) {
|
|
if (sdio_sg_nxt(hc, &addr, &len, blksz)) {
|
|
break;
|
|
}
|
|
blksz -= len;
|
|
len /= 4;
|
|
|
|
/* BRE or BWE is asserted when the available buffer is more than the watermark level */
|
|
while (len) {
|
|
xfer_len = min(len, IMX_USDHC_WTMK_LVL_WR_WML_BV_MAX_VAL);
|
|
len -= xfer_len;
|
|
|
|
/* Wait until the read or write buffer is ready */
|
|
if (imx_sdhcx_waitmask(hc, IMX_USDHC_PRES_STATE, msk, msk, IMX_USDHC_TRANSFER_TIMEOUT)) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 1,
|
|
"pio read: timedout for BREN in present state register");
|
|
return ETIMEDOUT;
|
|
}
|
|
|
|
if ((cmd->flags & SCF_DIR_IN)) {
|
|
in32s(addr, (uint32_t)xfer_len, base + IMX_USDHC_DATA_BUFF_ACC_PORT);
|
|
} else {
|
|
out32s(addr, (uint32_t)xfer_len, base + IMX_USDHC_DATA_BUFF_ACC_PORT);
|
|
}
|
|
addr += xfer_len * 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef IMX8_SDHCX_DEBUG
|
|
cycle2 = ClockCycles();
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s: CMD %d flags:%x, cmd->blks: %d, cmd->blksz: %d, it took %d us",
|
|
__FUNCTION__, cmd->opcode, cmd->flags, cmd->blks, cmd->blksz,
|
|
(int)((cycle2 - cycle1) * 1000 / (cps / 1000)));
|
|
#endif
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Interrupt event.
|
|
*
|
|
* @param hc Host controller handle.
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_intr_event(sdio_hc_t *const hc)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
sdio_cmd_t *cmd;
|
|
uint32_t sts;
|
|
int cs;
|
|
int idx;
|
|
uint32_t rsp;
|
|
uintptr_t base;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
cs = CS_CMD_INPROG;
|
|
sts = in32(base + IMX_USDHC_INT_STATUS);
|
|
|
|
#ifdef IMX8_SDHCX_DEBUG
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s: is:%x ie:%x ise:%x", __FUNCTION__,
|
|
in32(base + IMX_USDHC_INT_STATUS),
|
|
in32(base + IMX_USDHC_INT_STATUS_EN),
|
|
in32(base + IMX_USDHC_INT_SIGNAL_EN));
|
|
#endif
|
|
|
|
/*
|
|
* Errata TKT070753, Rev 1.0, IMX6DQCE
|
|
* on mx6q, there is low possibility that DATA END interrupt comes earlier than DMA
|
|
* END interrupt which is conflict with standard host controller spec. In this case, read the
|
|
* status register again.
|
|
*/
|
|
if ((sts & IMX_USDHC_INT_STATUS_TC_MASK) && !(sts & IMX_USDHC_INT_STATUS_DINT_MASK)) {
|
|
sts = in32(base + IMX_USDHC_INT_STATUS);
|
|
}
|
|
/* Debounce delay when card inserted or removed. */
|
|
if ((sts & (IMX_USDHC_INT_STATUS_CINS_MASK | IMX_USDHC_INT_STATUS_CRM_MASK))) {
|
|
delay(100);
|
|
}
|
|
/* Clear interrupt events. CINS, CREM flags cleared always since sts variable
|
|
* is loaded before debounce delay. */
|
|
out32(base + IMX_USDHC_INT_STATUS, sts | (IMX_USDHC_INT_STATUS_CINS_MASK | IMX_USDHC_INT_STATUS_CRM_MASK));
|
|
|
|
/* Card insertion and card removal events */
|
|
if ((sts & (IMX_USDHC_INT_STATUS_CINS_MASK | IMX_USDHC_INT_STATUS_CRM_MASK))) {
|
|
sdio_hc_event(hc, HC_EV_CD);
|
|
}
|
|
|
|
cmd = hc->wspc.cmd;
|
|
if (cmd == NULL) {
|
|
return (EOK);
|
|
}
|
|
|
|
/* Tuning commands */
|
|
if ((cmd->opcode == SD_SEND_TUNING_BLOCK) || (cmd->opcode == MMC_SEND_TUNING_BLOCK)) {
|
|
if ((sdhc->tuning_mode == IMX_USDHC_TUNING_MANUAL) && (sts & IMX_USDHC_INT_STATUS_BRR_MASK)) {
|
|
/* Though we don't need the data, we need to clear the buffer */
|
|
imx_sdhcx_pio_xfer(hc, cmd);
|
|
}
|
|
if (sts & IMX_USDHC_INT_STATUS_ERRI) {
|
|
cs = CS_CMD_CMP_ERR;
|
|
} else {
|
|
cs = CS_CMD_CMP;
|
|
}
|
|
sdio_cmd_cmplt(hc, cmd, cs);
|
|
return EOK;
|
|
}
|
|
|
|
/* Check of errors */
|
|
if (sts & IMX_USDHC_INT_STATUS_ERRI) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 1,
|
|
"%s, ERROR in HC, CMD%d, sts: 0x%x: is 0x%x, ac12 0x%x, IMX_USDHC_INT_STATUS_ERRI: 0x%x",
|
|
__FUNCTION__, cmd->opcode, sts, in32(base + IMX_USDHC_INT_STATUS),
|
|
in32(base + IMX_USDHC_AUTOCMD12_ERR_STATUS), IMX_USDHC_INT_STATUS_ERRI);
|
|
#ifdef IMX8_SDHCX_DEBUG
|
|
imx_sdhcx_reg_dump(hc, __FUNCTION__, __LINE__);
|
|
#endif
|
|
if (sts & IMX_USDHC_INT_STATUS_DTOE_MASK) {
|
|
cs = CS_DATA_TO_ERR;
|
|
}
|
|
if (sts & IMX_USDHC_INT_STATUS_DCE_MASK) {
|
|
cs = CS_DATA_CRC_ERR;
|
|
}
|
|
if (sts & IMX_USDHC_INT_STATUS_DEBE_MASK) {
|
|
cs = CS_DATA_END_ERR;
|
|
}
|
|
if (sts & IMX_USDHC_INT_STATUS_CTOE_MASK) {
|
|
cs = CS_CMD_TO_ERR;
|
|
}
|
|
if (sts & IMX_USDHC_INT_STATUS_CCE_MASK) {
|
|
cs = CS_CMD_CRC_ERR;
|
|
}
|
|
if (sts & IMX_USDHC_INT_STATUS_CEBE_MASK) {
|
|
cs = CS_CMD_END_ERR;
|
|
}
|
|
if (sts & IMX_USDHC_INT_STATUS_CIE_MASK) {
|
|
cs = CS_CMD_IDX_ERR;
|
|
}
|
|
if (sts & IMX_USDHC_INT_STATUS_DMAE_MASK) {
|
|
cs = CS_DATA_TO_ERR;
|
|
}
|
|
if (sts & IMX_USDHC_INT_STATUS_AC12E_MASK) {
|
|
cs = CS_DATA_TO_ERR;
|
|
}
|
|
if (!cs) {
|
|
cs = CS_CMD_CMP_ERR;
|
|
}
|
|
imx_sdhcx_reset(hc, IMX_USDHC_SYS_CTRL_RSTC_MASK | IMX_USDHC_SYS_CTRL_RSTD_MASK);
|
|
} else {
|
|
/* End of command */
|
|
if ((sts & IMX_USDHC_INT_STATUS_CC_MASK)) {
|
|
cs = CS_CMD_CMP;
|
|
|
|
/* Errata ENGcm03648 */
|
|
if (cmd->flags & SCF_RSP_BUSY) {
|
|
int timeout = 16 * 1024 * 1024;
|
|
|
|
while (!(in32(base + IMX_USDHC_PRES_STATE) & IMX_USDHC_PRES_STATE_DLSL0_MASK) && timeout) {
|
|
nanospin_ns(100L);
|
|
timeout--;
|
|
}
|
|
if (timeout <= 0) {
|
|
cs = CS_CMD_TO_ERR;
|
|
imx_sdhcx_reset(hc, IMX_USDHC_SYS_CTRL_RSTC_MASK | IMX_USDHC_SYS_CTRL_RSTD_MASK);
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 1,
|
|
"%s: busy done wait timeout for cmd: %d", __func__, cmd->opcode);
|
|
}
|
|
}
|
|
|
|
if ((cmd->flags & SCF_RSP_136)) {
|
|
/*
|
|
* CRC is not included in the response reg, left
|
|
* shift 8 bits to match the 128 CID/CSD structure
|
|
*/
|
|
uint8_t rbyte = 0;
|
|
for (idx = 0; idx < 4; idx++) {
|
|
rsp = in32(base + IMX_USDHC_CMD_RSP0 + (uint32_t)idx * 4);
|
|
cmd->rsp[3 - idx] = (rsp << 8) + rbyte;
|
|
rbyte = (uint8_t)(rsp >> 24);
|
|
}
|
|
} else if ((cmd->flags & SCF_RSP_PRESENT)) {
|
|
cmd->rsp[0] = in32(base + IMX_USDHC_CMD_RSP0);
|
|
} else {
|
|
/* no operation */
|
|
}
|
|
}
|
|
|
|
/* End of data transfer */
|
|
if (sts & IMX_USDHC_INT_STATUS_TC_MASK) {
|
|
cs = CS_CMD_CMP;
|
|
cmd->rsp[0] = in32(base + IMX_USDHC_CMD_RSP0);
|
|
}
|
|
|
|
/* Doesn't need to do anything for DMA interrupt */
|
|
if ((sts & IMX_USDHC_INT_STATUS_DINT_MASK)) {
|
|
cs = CS_CMD_CMP;
|
|
}
|
|
|
|
if ((sts & (IMX_USDHC_INT_STATUS_BWR_MASK | IMX_USDHC_INT_STATUS_BRR_MASK))) {
|
|
if (EOK == imx_sdhcx_pio_xfer(hc, cmd)) {
|
|
cs = CS_CMD_CMP;
|
|
} else {
|
|
cs = CS_DATA_TO_ERR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cs != CS_CMD_INPROG) {
|
|
if ((cs == CS_DATA_TO_ERR) || (cs == CS_CMD_TO_ERR)) {
|
|
/* Timeout error case, check if card removed */
|
|
if (!(hc->entry.cd(hc) & CD_INS)) {
|
|
cs = CS_CARD_REMOVED;
|
|
}
|
|
}
|
|
sdio_cmd_cmplt(hc, cmd, cs);
|
|
}
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* USDHC Interrupt/Board (GPIO) specific event.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param ev Event.
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_event(sdio_hc_t *const hc, sdio_event_t *const ev)
|
|
{
|
|
int status;
|
|
|
|
switch (ev->code) {
|
|
case HC_EV_INTR:
|
|
status = imx_sdhcx_intr_event(hc);
|
|
InterruptUnmask(hc->cfg.irq[0], hc->hc_iid);
|
|
break;
|
|
default:
|
|
status = bs_event(hc, ev);
|
|
break;
|
|
}
|
|
|
|
return (status);
|
|
}
|
|
|
|
/**
|
|
* ADMA configuration
|
|
* @param hc Host controller handle
|
|
* @param cmd Command
|
|
*
|
|
* @return Execution status
|
|
*/
|
|
static int imx_sdhcx_adma_setup(sdio_hc_t *const hc, sdio_cmd_t *const cmd)
|
|
{
|
|
imx_sdhcx_hc_t *sdhc;
|
|
imx_usdhc_adma32_t *adma;
|
|
sdio_sge_t *sgp;
|
|
int sgc;
|
|
int sgi;
|
|
int acnt = 0;
|
|
int alen;
|
|
int sg_count;
|
|
paddr_t paddr;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
adma = sdhc->adma;
|
|
|
|
sgc = (int)cmd->sgc;
|
|
sgp = cmd->sgl;
|
|
|
|
if (!(cmd->flags & SCF_DATA_PHYS)) {
|
|
sdio_vtop_sg(sgp, sdhc->sgl, sgc, cmd->mhdl);
|
|
sgp = sdhc->sgl;
|
|
}
|
|
|
|
for (sgi = 0; sgi < sgc; sgi++) {
|
|
paddr = sgp->sg_address;
|
|
sg_count = (int)sgp->sg_count;
|
|
while (sg_count) {
|
|
alen = min(sg_count, IMX_USDHC_ADMA2_MAX_XFER);
|
|
adma->attr = IMX_USDHC_ADMA2_VALID | IMX_USDHC_ADMA2_TRAN;
|
|
adma->addr = (uint32_t)paddr;
|
|
adma->len = (uint16_t)alen;
|
|
sg_count -= alen;
|
|
paddr += (paddr_t)alen;
|
|
adma++;
|
|
if (++acnt > ADMA_DESC_MAX) {
|
|
return (ENOTSUP);
|
|
}
|
|
}
|
|
sgp++;
|
|
}
|
|
|
|
adma--;
|
|
adma->attr |= IMX_USDHC_ADMA2_END;
|
|
|
|
out32(sdhc->base + IMX_USDHC_ADMA_SYS_ADDR, sdhc->admap);
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* SDMA configuration.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param cmd Command.
|
|
*
|
|
* @return EOK always.
|
|
*/
|
|
static int imx_sdhcx_sdma_setup(sdio_hc_t *const hc, sdio_cmd_t *const cmd)
|
|
{
|
|
imx_sdhcx_hc_t *sdhc;
|
|
sdio_sge_t *sgp;
|
|
int sgc;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
|
|
sgc = (int)cmd->sgc;
|
|
sgp = cmd->sgl;
|
|
|
|
if (!(cmd->flags & SCF_DATA_PHYS)) {
|
|
sdio_vtop_sg(sgp, sdhc->sgl, sgc, cmd->mhdl);
|
|
sgp = sdhc->sgl;
|
|
}
|
|
|
|
out32(sdhc->base + IMX_USDHC_DS_ADDR, (uint32_t)sgp->sg_address);
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Transfer setup.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param cmd Command.
|
|
* @param command Command.
|
|
* @param imask Bits mask of USDHC_INT_STATUS_EN register.
|
|
*
|
|
* @return Execution status
|
|
*/
|
|
static int imx_sdhcx_xfer_setup(sdio_hc_t *const hc, sdio_cmd_t *const cmd, uint32_t *command, uint32_t *imask)
|
|
{
|
|
imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t pctl, mix_ctrl;
|
|
int status = EOK;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
pctl = in32(base + IMX_USDHC_PROT_CTRL) & ~IMX_USDHC_PROT_CTRL_DMASEL_MASK;
|
|
mix_ctrl = 0;
|
|
|
|
/* Data present */
|
|
*command |= IMX_USDHC_CMD_XFR_TYP_DPSEL_MASK;
|
|
|
|
if (cmd->flags & SCF_DIR_IN) {
|
|
mix_ctrl |= IMX_USDHC_MIX_CTRL_DTDSEL_MASK;
|
|
}
|
|
|
|
*imask |= IMX_USDHC_INT_STATUS_EN_DTOESEN_MASK |
|
|
IMX_USDHC_INT_STATUS_EN_DCESEN_MASK |
|
|
IMX_USDHC_INT_STATUS_EN_DEBESEN_MASK |
|
|
IMX_USDHC_INT_STATUS_EN_TCSEN_MASK;
|
|
|
|
status = sdio_sg_start(hc, cmd->sgl, (int)cmd->sgc);
|
|
|
|
if (cmd->sgc && (hc->caps & HC_CAP_DMA)) {
|
|
if ((sdhc->flags & SF_USE_ADMA)) {
|
|
status = imx_sdhcx_adma_setup(hc, cmd);
|
|
if (status == EOK) {
|
|
pctl |= IMX_USDHC_PROT_CTRL_DMASEL(IMX_USDHC_PROT_CTRL_DMASEL_BV_ADMA2);
|
|
}
|
|
} else {
|
|
status = imx_sdhcx_sdma_setup(hc, cmd);
|
|
if ( status == EOK) {
|
|
pctl |= IMX_USDHC_PROT_CTRL_DMASEL(IMX_USDHC_PROT_CTRL_DMASEL_BV_SDMA);
|
|
}
|
|
}
|
|
|
|
if (status == EOK) {
|
|
*imask |= IMX_USDHC_INT_STATUS_EN_DMAESEN_MASK;
|
|
mix_ctrl |= IMX_USDHC_MIX_CTRL_DMAEN_MASK;
|
|
}
|
|
}
|
|
|
|
/* Use PIO */
|
|
if (status || !(hc->caps & HC_CAP_DMA)) {
|
|
if ((cmd->flags & SCF_DATA_PHYS)) {
|
|
return (status);
|
|
}
|
|
status = EOK;
|
|
|
|
*imask |= (cmd->flags & SCF_DIR_IN) ? IMX_USDHC_INT_STATUS_EN_BRRSEN_MASK : IMX_USDHC_INT_STATUS_EN_BWRSEN_MASK;
|
|
}
|
|
|
|
if (cmd->blks > 1) {
|
|
mix_ctrl |= IMX_USDHC_MIX_CTRL_MSBSEL_MASK | IMX_USDHC_MIX_CTRL_BCEN_MASK;
|
|
if ((hc->caps & HC_CAP_ACMD23) && (cmd->flags & SCF_SBC)) {
|
|
/* Auto CMD23 need to use sdma address register to store the argument */
|
|
out32(base + IMX_USDHC_DS_ADDR, cmd->blks);
|
|
mix_ctrl |= IMX_USDHC_MIX_CTRL_AC23EN_MASK;
|
|
} else if ((hc->caps & HC_CAP_ACMD12)) {
|
|
mix_ctrl |= IMX_USDHC_MIX_CTRL_AC12EN_MASK;
|
|
} else {
|
|
/* no operation */
|
|
}
|
|
}
|
|
|
|
sdhc->mix_ctrl = mix_ctrl;
|
|
|
|
out32(base + IMX_USDHC_PROT_CTRL, pctl);
|
|
out32(base + IMX_USDHC_BLK_ATT,
|
|
cmd->blksz | (cmd->blks << IMX_USDHC_BLK_ATT_BLKCNT_SHIFT));
|
|
|
|
return (status);
|
|
}
|
|
|
|
/**
|
|
* Process command.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param cmd Command.
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_cmd(sdio_hc_t *const hc, sdio_cmd_t *const cmd)
|
|
{
|
|
imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t pmask, pval;
|
|
uint32_t imask;
|
|
int status;
|
|
uint32_t command, reg;
|
|
const sdio_dev_t *const dev = &hc->device;
|
|
|
|
#ifdef IMX8_SDHCX_DEBUG
|
|
uint64_t cps, cycle1, cycle2 = 0;
|
|
cps = SYSPAGE_ENTRY(qtime)->cycles_per_sec;
|
|
cycle1 = ClockCycles();
|
|
#endif
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
|
|
/* Command inhibit (CMD) and CMD line signal level state */
|
|
pmask = IMX_USDHC_PRES_STATE_CIHB_MASK | IMX_USDHC_PRES_STATE_CLSL_MASK;
|
|
|
|
command = cmd->opcode << 24;
|
|
|
|
if ((cmd->opcode == MMC_STOP_TRANSMISSION) || (cmd->opcode == SD_STOP_TRANSMISSION)) {
|
|
command |= IMX_USDHC_CMD_XFR_TYP_CMDTYP(IMX_USDHC_CMD_XFR_TYP_CMDTYP_BV_ABORT);
|
|
}
|
|
|
|
imask = IMX_USDHC_INT_STATUS_EN_DFLTS;
|
|
|
|
if ((cmd->flags & SCF_DATA_MSK)) {
|
|
pmask |= IMX_USDHC_PRES_STATE_CDIHB_MASK | IMX_USDHC_PRES_STATE_DLSL0_MASK;
|
|
|
|
if (cmd->blks) {
|
|
status = imx_sdhcx_xfer_setup(hc, cmd, &command, &imask);
|
|
if (status != EOK) {
|
|
return status;
|
|
}
|
|
}
|
|
} else {
|
|
/* Enable command complete intr */
|
|
imask |= IMX_USDHC_INT_STATUS_EN_CCSEN_MASK;
|
|
}
|
|
|
|
if ((cmd->flags & SCF_RSP_PRESENT)) {
|
|
if (cmd->flags & SCF_RSP_136) {
|
|
command |= IMX_USDHC_CMD_XFR_TYP_RSPTYP(IMX_USDHC_CMD_XFR_TYP_RSPTYP_BV_136);
|
|
} else if (cmd->flags & SCF_RSP_BUSY) {
|
|
command |= IMX_USDHC_CMD_XFR_TYP_RSPTYP(IMX_USDHC_CMD_XFR_TYP_RSPTYP_BV_48B);
|
|
|
|
/* Command 12 can be asserted even if data lines are busy */
|
|
if ((cmd->opcode != MMC_STOP_TRANSMISSION) && (cmd->opcode != SD_STOP_TRANSMISSION)) {
|
|
pmask |= IMX_USDHC_PRES_STATE_CDIHB_MASK | IMX_USDHC_PRES_STATE_DLSL0_MASK;
|
|
}
|
|
} else {
|
|
command |= IMX_USDHC_CMD_XFR_TYP_RSPTYP(IMX_USDHC_CMD_XFR_TYP_RSPTYP_BV_48);
|
|
}
|
|
/* Index check */
|
|
if (cmd->flags & SCF_RSP_OPCODE) {
|
|
command |= IMX_USDHC_CMD_XFR_TYP_CICEN_MASK;
|
|
}
|
|
/* CRC check */
|
|
if (cmd->flags & SCF_RSP_CRC) {
|
|
command |= IMX_USDHC_CMD_XFR_TYP_CCCEN_MASK;
|
|
}
|
|
}
|
|
|
|
reg = in32(base + IMX_USDHC_PRES_STATE);
|
|
|
|
/* In some scenarios, espcially the std tuning circuit is active, usdhc controller tends
|
|
* to set the RTR bit even when a HS sd card is inserted. Adding more restriction to
|
|
* prevent triggering re-tune event for those cards which don't require tuning.
|
|
*/
|
|
if (dev->flags & (DEV_FLAG_UHS | DEV_FLAG_HS200 | DEV_FLAG_HS400)) {
|
|
/* If host requires tuning */
|
|
if (reg & (IMX_USDHC_PRES_STATE_RTR_MASK)) {
|
|
sdio_hc_event(hc, HC_EV_TUNE);
|
|
}
|
|
}
|
|
|
|
/* Wait till card is ready to handle next command */
|
|
pval = pmask & (IMX_USDHC_PRES_STATE_CLSL_MASK | IMX_USDHC_PRES_STATE_DLSL0_MASK);
|
|
|
|
if ((reg & pmask) != pval) {
|
|
status = imx_sdhcx_waitmask(hc, IMX_USDHC_PRES_STATE, pmask, pval, IMX_USDHC_COMMAND_TIMEOUT);
|
|
if (status != EOK) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 1,
|
|
"%s: Timeout CMD_INHIBIT at CMD%d", __FUNCTION__, cmd->opcode);
|
|
return (status);
|
|
}
|
|
}
|
|
|
|
/* IDLE state, need to initialize clock */
|
|
if (cmd->opcode == 0) {
|
|
out32(base + IMX_USDHC_SYS_CTRL, (in32(base + IMX_USDHC_SYS_CTRL) | IMX_USDHC_SYS_CTRL_INITA_MASK));
|
|
if ((status = imx_sdhcx_waitmask(hc, IMX_USDHC_SYS_CTRL, IMX_USDHC_SYS_CTRL_INITA_MASK, 0,
|
|
IMX_USDHC_COMMAND_TIMEOUT))) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 1,
|
|
"%s: Timeout IMX_USDHC_SYS_CTRL at CMD%d", __FUNCTION__, cmd->opcode);
|
|
return (status);
|
|
}
|
|
}
|
|
|
|
sdhc->mix_ctrl |= (in32(base + IMX_USDHC_MIX_CTRL) & \
|
|
(IMX_USDHC_MIX_CTRL_EXE_TUNE_MASK | \
|
|
IMX_USDHC_MIX_CTRL_SMP_CLK_SEL_MASK | \
|
|
IMX_USDHC_MIX_CTRL_AUTO_TUNE_EN_MASK | \
|
|
IMX_USDHC_MIX_CTRL_FBCLK_SEL_MASK |
|
|
IMX_USDHC_MIX_CTRL_HS400_MODE_MASK |
|
|
IMX_USDHC_MIX_CTRL_EN_HS400_MODE_MASK |
|
|
IMX_USDHC_MIX_CTRL_DDR_EN_MASK));
|
|
|
|
out32(base + IMX_USDHC_MIX_CTRL, sdhc->mix_ctrl);
|
|
sdhc->mix_ctrl = 0;
|
|
|
|
/* Enable interrupts */
|
|
out32(base + IMX_USDHC_INT_STATUS, IMX_USDHC_INT_STATUS_INTR_CLR_ALL);
|
|
out32(base + IMX_USDHC_INT_STATUS_EN, imask);
|
|
out32(base + IMX_USDHC_CMD_ARG, cmd->arg);
|
|
#if SDHC_BUS_SYNC != 0
|
|
/* Ensure that all data are in RAM when cmd is triggered. */
|
|
dsb();
|
|
#endif
|
|
out32(base + IMX_USDHC_CMD_XFR_TYP, command);
|
|
|
|
#ifdef IMX8_SDHCX_DEBUG
|
|
cycle2 = ClockCycles();
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s(), Issuing CMD%d, cmd_arg:0x%x command:0x%x blks: %d, blksz: %d, mix_ctrl: 0x%x, it took %d us",
|
|
__FUNCTION__, hc->wspc.cmd->opcode, cmd->arg, command, cmd->blks, cmd->blksz,
|
|
in32(base + IMX_USDHC_MIX_CTRL), (int)((cycle2 - cycle1) * 1000 / (cps / 1000)));
|
|
#endif
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Abort function. Present to fulfill specification of caller layer.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param cmd Command structure.
|
|
*
|
|
* @return EOK always.
|
|
*/
|
|
static int imx_sdhcx_abort(__attribute__((unused)) sdio_hc_t *const hc, __attribute__((unused)) sdio_cmd_t *const cmd)
|
|
{
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Configure power capabilities.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param vdd Power.
|
|
*
|
|
* @return EOK always.
|
|
*/
|
|
static int imx_sdhcx_pwr(sdio_hc_t *const hc, const int vdd)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t reg;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
|
|
if (!hc->vdd || !vdd) {
|
|
if (!((hc->caps & HC_CAP_SLOT_TYPE_EMBEDDED) && (hc->flags & HC_FLAG_DEV_MMC))) {
|
|
imx_sdhcx_voltage_reset(hc);
|
|
}
|
|
/* Reset core */
|
|
imx_sdhcx_reset(hc, IMX_USDHC_SYS_CTRL_RSTA_MASK);
|
|
/* reset tuning circuit (not affected by core reset) otherwise the active
|
|
* tuning circuit tends to set the RTR bit for those cards which don't
|
|
* require tuning.
|
|
*/
|
|
imx_sdhcx_reset_tuning(hc);
|
|
/* Make a clean environment */
|
|
out32(base + IMX_USDHC_MIX_CTRL, 0);
|
|
out32(base + IMX_USDHC_CLK_TUNE_CTRL_STATUS, 0);
|
|
}
|
|
|
|
if (vdd) {
|
|
reg = in32(base + IMX_USDHC_VEND_SPEC) & ~IMX_USDHC_VEND_SPEC_VSELECT_MASK;
|
|
switch (vdd) {
|
|
case OCR_VDD_17_195:
|
|
reg |= IMX_USDHC_VEND_SPEC_VSELECT(IMX_USDHC_VEND_SPEC_VSELECT_BV_1V8);
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s(): setting power to 1.8v", __FUNCTION__);
|
|
break;
|
|
case OCR_VDD_29_30:
|
|
case OCR_VDD_30_31:
|
|
case OCR_VDD_32_33:
|
|
case OCR_VDD_33_34:
|
|
reg |= IMX_USDHC_VEND_SPEC_VSELECT(IMX_USDHC_VEND_SPEC_VSELECT_BV_3V0);
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s(): setting power to 3.3v", __FUNCTION__);
|
|
break;
|
|
default:
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s(): unrecognized voltage level. vdd: 0x%x", __FUNCTION__, vdd);
|
|
return (EINVAL);
|
|
}
|
|
|
|
out32(base + IMX_USDHC_VEND_SPEC, reg);
|
|
out32(base + IMX_USDHC_INT_SIGNAL_EN, IMX_USDHC_INT_SIGNAL_EN_DFLTS);
|
|
}
|
|
|
|
hc->vdd = (uint32_t)vdd;
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Bus mode.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param bus_mode Bus mode.
|
|
*
|
|
* @return EOK always.
|
|
*/
|
|
static int imx_sdhcx_bus_mode(sdio_hc_t *const hc, const int bus_mode)
|
|
{
|
|
hc->bus_mode = (uint32_t)bus_mode;
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Configure bus width.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param width Bus width.
|
|
*
|
|
* @return EOK always.
|
|
*/
|
|
static int imx_sdhcx_bus_width(sdio_hc_t *const hc, const int width)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t hctl;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
hctl = in32(base + IMX_USDHC_PROT_CTRL) & ~(IMX_USDHC_PROT_CTRL_DTW_MASK);
|
|
|
|
switch (width) {
|
|
case BUS_WIDTH_8:
|
|
hctl |= IMX_USDHC_PROT_CTRL_DTW(IMX_USDHC_PROT_CTRL_DTW_BV_8);
|
|
break;
|
|
case BUS_WIDTH_4:
|
|
hctl |= IMX_USDHC_PROT_CTRL_DTW(IMX_USDHC_PROT_CTRL_DTW_BV_4);
|
|
break;
|
|
case BUS_WIDTH_1:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
out32(base + IMX_USDHC_PROT_CTRL, hctl);
|
|
hc->bus_width = (uint32_t)width;
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Configure sdll.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param val Requested strobe delay line value.
|
|
*
|
|
* @return EOK if sdll set correctly.
|
|
* @return EIO if PLL lock fail.
|
|
*/
|
|
int imx_sdhcx_host_sdll(sdio_hc_t *const hc, const unsigned val)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t strdll_ctrl_reg, reg;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
|
|
/* Disable clock prior to sdll manipulation */
|
|
reg = in32(base + IMX_USDHC_VEND_SPEC);
|
|
reg &= ~IMX_USDHC_VEND_SPEC_FRC_SDCLK_ON_MASK;
|
|
out32(base + IMX_USDHC_VEND_SPEC, reg);
|
|
|
|
if (((hc->timing == TIMING_HS400) || (hc->timing == TIMING_HS400ES))) {
|
|
out32(base + IMX_USDHC_STROBE_DLL_CTRL, (IMX_USDHC_STROBE_DLL_CTRL_RESET_MASK |
|
|
IMX_USDHC_STROBE_DLL_CTRL_ENABLE_MASK |
|
|
(IMX_USDHC_STROBE_DLL_CTRL_SLV_UPDATE_INT_DEFAULT_VALUE << IMX_USDHC_STROBE_DLL_CTRL_SLV_UPDATE_INT_SHIFT) |
|
|
((val << IMX_USDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_SHIFT) &
|
|
IMX_USDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_MASK)));
|
|
strdll_ctrl_reg = in32(base + IMX_USDHC_STROBE_DLL_CTRL);
|
|
strdll_ctrl_reg &= ~IMX_USDHC_STROBE_DLL_CTRL_RESET_MASK;
|
|
out32(base + IMX_USDHC_STROBE_DLL_CTRL, strdll_ctrl_reg);
|
|
nanospin_ns(100000L);
|
|
if ((in32(base + IMX_USDHC_STROBE_DLL_STATUS) & (IMX_USDHC_STROBE_DLL_STATUS_REF_LOCK_MASK |
|
|
IMX_USDHC_STROBE_DLL_STATUS_SLV_LOCK_MASK))
|
|
!= (IMX_USDHC_STROBE_DLL_STATUS_REF_LOCK_MASK | IMX_USDHC_STROBE_DLL_STATUS_SLV_LOCK_MASK)) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1, "Strobe delay line not locked");
|
|
return (EIO);
|
|
}
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1, "SDLL status 0x%x", in32(base + IMX_USDHC_STROBE_DLL_STATUS));
|
|
} else {
|
|
return ENOTSUP;
|
|
}
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Configure clock frequency.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param clk Clock value.
|
|
*
|
|
* @return EOK always.
|
|
*/
|
|
static int imx_sdhcx_clk(sdio_hc_t *hc, int clk)
|
|
{
|
|
imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t sctl;
|
|
uint32_t pre_div = 1, divd = 1;
|
|
int ddr_mode = 0;
|
|
uint32_t dll_ctrl_reg, strdll_ctrl_reg;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
sctl = in32(base + IMX_USDHC_SYS_CTRL);
|
|
|
|
/* Stop clock */
|
|
sctl &= ~(IMX_USDHC_SYS_CTRL_DTOCV_MASK | IMX_USDHC_SYS_CTRL_SDCLKFS_MASK);
|
|
sctl |= IMX_USDHC_SYS_CTRL_DTOCV_MASK | IMX_USDHC_SYS_CTRL_RSTC_MASK | IMX_USDHC_SYS_CTRL_RSTD_MASK;
|
|
out32(base + IMX_USDHC_SYS_CTRL, sctl);
|
|
|
|
if ((hc->timing == TIMING_DDR50) || (hc->timing == TIMING_HS400) || (hc->timing == TIMING_HS400ES)) {
|
|
ddr_mode = 1;
|
|
pre_div = 2;
|
|
}
|
|
if ((uint32_t)clk > hc->clk_max) {
|
|
clk = (int)hc->clk_max;
|
|
}
|
|
|
|
while ((hc->clk_max / (pre_div * 16) > (uint32_t)clk) && (pre_div < 256)) {
|
|
pre_div *= 2;
|
|
}
|
|
|
|
while ((hc->clk_max / (pre_div * divd) > (uint32_t)clk) && (divd < 16)) {
|
|
divd++;
|
|
}
|
|
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 5, "desired SD clock: %d, actual: %d",
|
|
clk, hc->clk_max / pre_div / divd);
|
|
|
|
pre_div >>= (uint32_t)(1 + ddr_mode);
|
|
divd--;
|
|
|
|
if ((hc->timing == TIMING_HS200) && (clk > 100000000)) {
|
|
out32(base + IMX_USDHC_DLL_CTRL, (IMX_USDHC_DLL_CTRL_RESET_MASK | IMX_USDHC_DLL_CTRL_ENABLE_MASK));
|
|
} else {
|
|
out32(base + IMX_USDHC_DLL_CTRL, 0);
|
|
}
|
|
if (((hc->timing == TIMING_HS400) || (hc->timing == TIMING_HS400ES)) && (clk > 100000000)) {
|
|
out32(base + IMX_USDHC_STROBE_DLL_CTRL, (uint32_t)(IMX_USDHC_STROBE_DLL_CTRL_RESET_MASK |
|
|
IMX_USDHC_STROBE_DLL_CTRL_ENABLE_MASK |
|
|
(IMX_USDHC_STROBE_DLL_CTRL_SLV_UPDATE_INT_DEFAULT_VALUE << IMX_USDHC_STROBE_DLL_CTRL_SLV_UPDATE_INT_SHIFT) |
|
|
((sdhc->sdll << IMX_USDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_SHIFT) &
|
|
IMX_USDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_MASK)));
|
|
} else {
|
|
out32(base + IMX_USDHC_STROBE_DLL_CTRL, 0);
|
|
}
|
|
|
|
sctl = ((0xE << IMX_USDHC_SYS_CTRL_DTOCV_SHIFT) | (pre_div << IMX_USDHC_SYS_CTRL_SDCLKFS_SHIFT) |
|
|
(divd << IMX_USDHC_SYS_CTRL_DVS_SHIFT) | IMX_USDHC_SYS_CTRL_IPP_RST_N_MASK | 0xF);
|
|
|
|
/* Enable clock to the card */
|
|
out32(base + IMX_USDHC_SYS_CTRL, sctl);
|
|
|
|
/* Wait for clock to stabilize */
|
|
imx_sdhcx_waitmask(hc, IMX_USDHC_PRES_STATE, IMX_USDHC_PRES_STATE_SDSTB_MASK, IMX_USDHC_PRES_STATE_SDSTB_MASK, IMX_USDHC_CLOCK_TIMEOUT);
|
|
|
|
if ((hc->timing == TIMING_HS200) && (clk > 100000000)) {
|
|
dll_ctrl_reg = in32(base + IMX_USDHC_DLL_CTRL);
|
|
dll_ctrl_reg &= ~IMX_USDHC_DLL_CTRL_RESET_MASK;
|
|
out32(base + IMX_USDHC_DLL_CTRL, dll_ctrl_reg);
|
|
IMX_INIT_DELAY;
|
|
if ((in32(base + IMX_USDHC_DLL_STATUS) & (IMX_USDHC_DLL_STATUS_REF_LOCK_MASK | IMX_USDHC_DLL_STATUS_SLV_LOCK_MASK))
|
|
!= (IMX_USDHC_DLL_STATUS_REF_LOCK_MASK | IMX_USDHC_DLL_STATUS_SLV_LOCK_MASK)) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1, "Delay line not locked");
|
|
}
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 5, "DLL status: 0x%x", in32(base + IMX_USDHC_DLL_STATUS));
|
|
}
|
|
if (((hc->timing == TIMING_HS400) || (hc->timing == TIMING_HS400ES)) && (clk > 100000000)) {
|
|
strdll_ctrl_reg = in32(base + IMX_USDHC_STROBE_DLL_CTRL);
|
|
strdll_ctrl_reg &= ~IMX_USDHC_STROBE_DLL_CTRL_RESET_MASK;
|
|
out32(base + IMX_USDHC_STROBE_DLL_CTRL, strdll_ctrl_reg);
|
|
IMX_INIT_DELAY;
|
|
if ((in32(base + IMX_USDHC_STROBE_DLL_STATUS) & (IMX_USDHC_STROBE_DLL_STATUS_REF_LOCK_MASK |
|
|
IMX_USDHC_STROBE_DLL_STATUS_SLV_LOCK_MASK))
|
|
!= (IMX_USDHC_STROBE_DLL_STATUS_REF_LOCK_MASK | IMX_USDHC_STROBE_DLL_STATUS_SLV_LOCK_MASK)) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1, "Strobe delay line not locked");
|
|
}
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 5, "SDLL status 0x%x", in32(base + IMX_USDHC_STROBE_DLL_STATUS));
|
|
}
|
|
|
|
nanospin_ns(1000L);
|
|
|
|
hc->clk = (uint32_t)clk;
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Configure timing.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param timing Timing value.
|
|
*
|
|
* @return EOK always.
|
|
*/
|
|
static int imx_sdhcx_timing(sdio_hc_t *hc, const int timing)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t mix_ctl;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
|
|
hc->timing = (uint32_t)timing;
|
|
mix_ctl = in32(base + IMX_USDHC_MIX_CTRL);
|
|
|
|
if (timing == TIMING_DDR50) {
|
|
mix_ctl |= IMX_USDHC_MIX_CTRL_DDR_EN_MASK;
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_HS400_MODE_MASK;
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_EN_HS400_MODE_MASK;
|
|
} else if (timing == TIMING_HS400) {
|
|
mix_ctl |= IMX_USDHC_MIX_CTRL_DDR_EN_MASK;
|
|
mix_ctl |= IMX_USDHC_MIX_CTRL_HS400_MODE_MASK;
|
|
mix_ctl |= IMX_USDHC_MIX_CTRL_SMP_CLK_SEL_MASK;
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_EN_HS400_MODE_MASK;
|
|
} else if (timing == TIMING_HS400ES) {
|
|
mix_ctl |= IMX_USDHC_MIX_CTRL_DDR_EN_MASK;
|
|
mix_ctl |= IMX_USDHC_MIX_CTRL_HS400_MODE_MASK;
|
|
mix_ctl |= IMX_USDHC_MIX_CTRL_EN_HS400_MODE_MASK;
|
|
} else if (timing == TIMING_LS) {
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_DDR_EN_MASK;
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_HS400_MODE_MASK;
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_EN_HS400_MODE_MASK;
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_SMP_CLK_SEL_MASK;
|
|
} else {
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_DDR_EN_MASK;
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_HS400_MODE_MASK;
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_EN_HS400_MODE_MASK;
|
|
}
|
|
|
|
out32(base + IMX_USDHC_MIX_CTRL, mix_ctl);
|
|
|
|
if (timing == TIMING_DDR50) {
|
|
if( hc->clk > DTR_MAX_HS52 ) {
|
|
hc->clk = DTR_MAX_HS52; // max dtr for HS is 52MHz
|
|
}
|
|
|
|
imx_sdhcx_clk( hc, (int)hc->clk );
|
|
}
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Signal voltage configuration.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param signal_voltage Signal voltage value.
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_signal_voltage(sdio_hc_t *hc, const int signal_voltage)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t reg, dlsl;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
|
|
|
|
if ((hc->version < IMX_USDHC_HOST_CTRL_VER_SVN_BV_VER_3) || (hc->signal_voltage == (uint32_t)signal_voltage)) {
|
|
return (EOK);
|
|
}
|
|
|
|
/* Its expected that eMMC operates in 1.8 V by default. In this case we skip
|
|
* code to switch to 1.8 V */
|
|
if ((hc->caps & HC_CAP_SLOT_TYPE_EMBEDDED) && (hc->flags & HC_FLAG_DEV_MMC)) {
|
|
if (signal_voltage == SIGNAL_VOLTAGE_1_8) {
|
|
hc->signal_voltage = (uint32_t)signal_voltage;
|
|
return (EOK);
|
|
}
|
|
}
|
|
|
|
if ((signal_voltage == SIGNAL_VOLTAGE_3_3) || (signal_voltage == SIGNAL_VOLTAGE_3_0)) {
|
|
reg = in32(base + IMX_USDHC_VEND_SPEC);
|
|
reg &= ~IMX_USDHC_VEND_SPEC_VSELECT_MASK;
|
|
reg |= IMX_USDHC_VEND_SPEC_VSELECT(IMX_USDHC_VEND_SPEC_VSELECT_BV_3V0);
|
|
out32(base + IMX_USDHC_VEND_SPEC, reg);
|
|
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1, "%s: switched to 3.3V ", __FUNCTION__);
|
|
} else if (signal_voltage == SIGNAL_VOLTAGE_1_8) {
|
|
reg = in32(base + IMX_USDHC_VEND_SPEC);
|
|
reg &= ~IMX_USDHC_VEND_SPEC_FRC_SDCLK_ON_MASK;
|
|
|
|
/* Stop sd clock */
|
|
out32(base + IMX_USDHC_VEND_SPEC, reg);
|
|
|
|
dlsl = IMX_USDHC_PRES_STATE_DLSL0_MASK |
|
|
IMX_USDHC_PRES_STATE_DLSL1_MASK |
|
|
IMX_USDHC_PRES_STATE_DLSL2_MASK |
|
|
IMX_USDHC_PRES_STATE_DLSL3_MASK;
|
|
if (imx_sdhcx_waitmask(hc, IMX_USDHC_PRES_STATE, dlsl, 0, IMX_USDHC_CLOCK_TIMEOUT)) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s() : Failed to switch to 1.8V, can't stop SD Clock", __FUNCTION__);
|
|
return (EIO);
|
|
}
|
|
reg |= IMX_USDHC_VEND_SPEC_VSELECT(IMX_USDHC_VEND_SPEC_VSELECT_BV_1V8);
|
|
out32(base + IMX_USDHC_VEND_SPEC, reg);
|
|
|
|
/* Sleep at least 5ms */
|
|
delay(5);
|
|
|
|
/* Restore sd clock status */
|
|
out32(base + IMX_USDHC_VEND_SPEC, reg | IMX_USDHC_VEND_SPEC_FRC_SDCLK_ON_MASK);
|
|
IMX_INIT_DELAY;
|
|
|
|
reg = in32(base + IMX_USDHC_VEND_SPEC);
|
|
|
|
/* Data lines should be high now */
|
|
if (!(reg & IMX_USDHC_VEND_SPEC_VSELECT(IMX_USDHC_VEND_SPEC_VSELECT_BV_1V8)) ||
|
|
!(in32(base + IMX_USDHC_PRES_STATE) & dlsl)) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,
|
|
"%s(): Failed to switch to 1.8V, DATA lines remain in low", __FUNCTION__);
|
|
return (EIO);
|
|
}
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1, "%s: switched to 1.8V ", __FUNCTION__);
|
|
} else {
|
|
return (EINVAL);
|
|
}
|
|
|
|
hc->signal_voltage = (uint32_t)signal_voltage;
|
|
|
|
/* The board specific code may need to do something */
|
|
#ifdef BS_PAD_CONF
|
|
bs_pad_conf(hc, SDIO_TRUE);
|
|
#endif
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
/**
|
|
* Pre-tuning functionality.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param tuning Tuning value.
|
|
*/
|
|
static void imx_sdhcx_pre_tuning(sdio_hc_t *const hc, const int tuning)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t mix_ctl;
|
|
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 2, "%s: tuning at %d", __FUNCTION__, tuning);
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
|
|
if (!((hc->caps & HC_CAP_SLOT_TYPE_EMBEDDED) && (hc->flags & HC_FLAG_DEV_MMC))) {
|
|
/* Reset controller before tuning otherwise tuning fails on some cards. */
|
|
imx_sdhcx_reset(hc, IMX_USDHC_SYS_CTRL_RSTA_MASK);
|
|
}
|
|
IMX_INIT_DELAY;
|
|
|
|
mix_ctl = in32(base + IMX_USDHC_MIX_CTRL);
|
|
mix_ctl |= (IMX_USDHC_MIX_CTRL_EXE_TUNE_MASK |
|
|
IMX_USDHC_MIX_CTRL_SMP_CLK_SEL_MASK |
|
|
IMX_USDHC_MIX_CTRL_FBCLK_SEL_MASK);
|
|
out32(base + IMX_USDHC_MIX_CTRL, mix_ctl);
|
|
|
|
out32(base + IMX_USDHC_CLK_TUNE_CTRL_STATUS, (uint32_t)(tuning << IMX_USDHC_CLK_TUNE_CTRL_STATUS_DLY_CELL_SET_PRE_SHIFT));
|
|
}
|
|
|
|
/**
|
|
* Send tuning command.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param op Device operand (CMD19 - SD, CMD21 - eMMC).
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_send_tune_cmd(sdio_hc_t *hc, const int op)
|
|
{
|
|
struct sdio_cmd cmd;
|
|
int tlen;
|
|
int status;
|
|
sdio_sge_t sge;
|
|
uint32_t *buf;
|
|
|
|
tlen = (hc->bus_width == BUS_WIDTH_8) ? 128 : 64;
|
|
|
|
buf = sdio_alloc((size_t)tlen);
|
|
if (buf == NULL) {
|
|
sdio_free_cmd(&cmd);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
/* Clear buffer for tuning data */
|
|
memset(buf, 0, (size_t)tlen);
|
|
|
|
sge.sg_count = (uint32_t)tlen;
|
|
sge.sg_address = (paddr_t)buf;
|
|
|
|
memset((void *)&cmd, 0, sizeof(struct sdio_cmd));
|
|
sdio_setup_cmd(&cmd, SCF_CTYPE_ADTC | SCF_RSP_R1, (uint32_t)op, 0);
|
|
|
|
/* Seems if not read the data out of the buffer, it will be some problems */
|
|
sdio_setup_cmd_io(&cmd, SCF_DIR_IN, 1, tlen, &sge, 1, NULL);
|
|
status = sdio_issue_cmd(&hc->device, &cmd, SDIO_TIME_DEFAULT);
|
|
|
|
/* Command error */
|
|
if ((cmd.status > CS_CMD_CMP) || (memcmp(buf, (hc->bus_width == BUS_WIDTH_8) ? sdio_tbp_8bit : sdio_tbp_4bit, (size_t)tlen))) {
|
|
status = EIO;
|
|
}
|
|
|
|
sdio_free(buf, (size_t)tlen);
|
|
|
|
return (status);
|
|
}
|
|
|
|
/**
|
|
* Tuning routine for standard tuning execution.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param op Device operand (CMD19 - SD, CMD21 - eMMC).
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_std_tune(sdio_hc_t *hc, const int op)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uint32_t *buf;
|
|
struct sdio_cmd *cmd;
|
|
uintptr_t base;
|
|
uint32_t acmd12, mix_ctl;
|
|
uint16_t i;
|
|
int tlen = 0;
|
|
int status = EIO;
|
|
unsigned sigen, stsen;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
tlen = (hc->bus_width == BUS_WIDTH_8) ? 128 : 64;
|
|
|
|
if (hc->version < IMX_USDHC_HOST_CTRL_VER_SVN_BV_VER_3) {
|
|
return (EOK);
|
|
}
|
|
|
|
/* Return if not HS200 or SDR104, and not SDR50 that requires tuning */
|
|
if ((hc->timing != TIMING_SDR104) &&
|
|
(hc->timing != TIMING_HS200) &&
|
|
((hc->timing == TIMING_SDR50) &&
|
|
!(sdhc->flags & SF_TUNE_SDR50))) {
|
|
return (EOK);
|
|
}
|
|
|
|
buf = sdio_alloc((size_t)tlen);
|
|
if (buf == NULL) {
|
|
return (ENOMEM);
|
|
}
|
|
|
|
cmd = sdio_alloc_cmd();
|
|
if (cmd == NULL) {
|
|
sdio_free(buf, (size_t)tlen);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
if (!((hc->caps & HC_CAP_SLOT_TYPE_EMBEDDED) && (hc->flags & HC_FLAG_DEV_MMC))) {
|
|
/* Reset controller before tuning otherwise tuning fails on some cards. */
|
|
imx_sdhcx_reset(hc, IMX_USDHC_SYS_CTRL_RSTA_MASK);
|
|
}
|
|
/* Backup interrupt settings */
|
|
sigen = in32(base + IMX_USDHC_INT_SIGNAL_EN);
|
|
stsen = in32(base + IMX_USDHC_INT_STATUS_EN);
|
|
/* As per the Host Controller spec v3.00, tuning command generates Buffer Read Ready interrupt. Enable only this interrupt. */
|
|
out32(base + IMX_USDHC_INT_SIGNAL_EN, IMX_USDHC_INT_SIGNAL_EN_BRRIEN_MASK);
|
|
out32(base + IMX_USDHC_INT_STATUS_EN, IMX_USDHC_INT_STATUS_EN_BRRSEN_MASK);
|
|
/* It is recommended to use internal clock as the tuning clock. */
|
|
mix_ctl = in32(base + IMX_USDHC_MIX_CTRL);
|
|
mix_ctl |= IMX_USDHC_MIX_CTRL_FBCLK_SEL_MASK;
|
|
out32(base + IMX_USDHC_MIX_CTRL, mix_ctl);
|
|
/* Enable tuning execution */
|
|
acmd12 = in32(base + IMX_USDHC_AUTOCMD12_ERR_STATUS);
|
|
acmd12 |= IMX_USDHC_AUTOCMD12_ERR_STATUS_EXECUTE_TUNING_MASK;
|
|
out32(base + IMX_USDHC_AUTOCMD12_ERR_STATUS, acmd12);
|
|
|
|
for (i = 0; i < IMX_USDHC_TUNING_RETRIES; i++) {
|
|
cmd->status = CS_CMD_INPROG;
|
|
sdio_setup_cmd(cmd, SCF_CTYPE_ADTC | SCF_RSP_R1, (uint32_t)op, 0);
|
|
sdio_setup_cmd_io(cmd, SCF_DIR_IN, 1, tlen, NULL, 0, NULL);
|
|
/* Send tuning command */
|
|
if ((status = sdio_issue_cmd(&hc->device, cmd, IMX_USDHC_TUNING_TIMEOUT))) {
|
|
break;
|
|
}
|
|
/* Empty buffer */
|
|
in32s(buf, (unsigned)(tlen >> 2), base + IMX_USDHC_DATA_BUFF_ACC_PORT);
|
|
/* Check if tuning was successful or not. */
|
|
acmd12 = in32(base + IMX_USDHC_AUTOCMD12_ERR_STATUS);
|
|
if (!(acmd12 & IMX_USDHC_AUTOCMD12_ERR_STATUS_EXECUTE_TUNING_MASK)) {
|
|
if (acmd12 & IMX_USDHC_AUTOCMD12_ERR_STATUS_SMP_CLK_SEL_MASK) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 2, "%s: Tuning pass within %d iteration.", __func__, i);
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 2, "%s: IMX_USDHC_CLK_TUNE_CTRL_STATUS 0x%x", __func__, in32(base + IMX_USDHC_CLK_TUNE_CTRL_STATUS));
|
|
mix_ctl = in32(base + IMX_USDHC_MIX_CTRL);
|
|
mix_ctl |= IMX_USDHC_MIX_CTRL_AUTO_TUNE_EN_MASK;
|
|
out32(base + IMX_USDHC_MIX_CTRL, mix_ctl);
|
|
delay(1); /* Make sure sdmmc finish handling of tuning data */
|
|
break;
|
|
}
|
|
}
|
|
delay(1);
|
|
}
|
|
|
|
if (status || !(acmd12 & IMX_USDHC_AUTOCMD12_ERR_STATUS_SMP_CLK_SEL_MASK)) {
|
|
status = EOK;
|
|
#ifdef IMX8_SDHCX_DEBUG
|
|
imx_sdhcx_reg_dump(hc, __FUNCTION__, __LINE__);
|
|
#endif
|
|
acmd12 &= ~(IMX_USDHC_AUTOCMD12_ERR_STATUS_SMP_CLK_SEL_MASK | IMX_USDHC_AUTOCMD12_ERR_STATUS_EXECUTE_TUNING_MASK);
|
|
out32(base + IMX_USDHC_AUTOCMD12_ERR_STATUS, acmd12);
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1, "%s(), failed tuning, using the fixed clock", __func__);
|
|
}
|
|
/* Free used buffers */
|
|
sdio_free(buf, (size_t)tlen);
|
|
sdio_free_cmd(cmd);
|
|
/* Restore interrupt settings */
|
|
out32(base + IMX_USDHC_INT_SIGNAL_EN, sigen);
|
|
out32(base + IMX_USDHC_INT_STATUS_EN, stsen);
|
|
|
|
return (status);
|
|
}
|
|
|
|
/**
|
|
* Tuning routine for manual tuning execution.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param op Device operand (CMD19 - SD, CMD21 - eMMC).
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_man_tune(sdio_hc_t *const hc, const int op)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
uint32_t mix_ctl;
|
|
int status = EIO;
|
|
int min, max;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
|
|
if (hc->version < IMX_USDHC_HOST_CTRL_VER_SVN_BV_VER_3) {
|
|
return (EOK);
|
|
}
|
|
|
|
/* Return if not HS200 or SDR104, and not SDR50 that requires tuning */
|
|
if ((hc->timing != TIMING_SDR104) &&
|
|
(hc->timing != TIMING_HS200) &&
|
|
((hc->timing == TIMING_SDR50) &&
|
|
!(sdhc->flags & SF_TUNE_SDR50))) {
|
|
return (EOK);
|
|
}
|
|
/* Minimum good value */
|
|
min = IMX_USDHC_CLK_TUNE_CTRL_STATUS_PRE_MIN;
|
|
while (min < IMX_USDHC_CLK_TUNE_CTRL_STATUS_PRE_MAX) {
|
|
imx_sdhcx_pre_tuning(hc, min);
|
|
status = imx_sdhcx_send_tune_cmd(hc, op);
|
|
if (status == EOK) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 2, "%s() Found the minimum good value: %d",
|
|
__func__, min);
|
|
break;
|
|
}
|
|
|
|
min += IMX_USDHC_CLK_TUNE_CTRL_STATUS_PRE_STEP;
|
|
}
|
|
/* Maximum not-good value */
|
|
max = min + IMX_USDHC_CLK_TUNE_CTRL_STATUS_PRE_STEP;
|
|
while (max < IMX_USDHC_CLK_TUNE_CTRL_STATUS_PRE_MAX) {
|
|
imx_sdhcx_pre_tuning(hc, max);
|
|
status = imx_sdhcx_send_tune_cmd(hc, op);
|
|
if (status != EOK) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 2, "%s() Found the maximum not-good value: %d",
|
|
__func__, max);
|
|
max -= IMX_USDHC_CLK_TUNE_CTRL_STATUS_PRE_STEP;
|
|
break;
|
|
}
|
|
max += IMX_USDHC_CLK_TUNE_CTRL_STATUS_PRE_STEP;
|
|
}
|
|
|
|
imx_sdhcx_pre_tuning(hc, (min + max) / 2);
|
|
if ((status = imx_sdhcx_send_tune_cmd(hc, op) != EOK)) {
|
|
status = EIO;
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 2, "%s(), failed tuning", __func__);
|
|
}
|
|
|
|
mix_ctl = in32(base + IMX_USDHC_MIX_CTRL);
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_EXE_TUNE_MASK;
|
|
|
|
/* Use the fixed clock if failed */
|
|
if (status) {
|
|
status = EOK;
|
|
mix_ctl &= ~IMX_USDHC_MIX_CTRL_SMP_CLK_SEL_MASK;
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1, "%s(), failed tuning, using the fixed clock",
|
|
__func__);
|
|
} else {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 2, "%s(), tuned DLY_CELL_SET_PRE at: %d",
|
|
__func__, (min + max) / 2);
|
|
#ifdef IMX8_SDHCX_DEBUG
|
|
imx_sdhcx_reg_dump(hc, __FUNCTION__, __LINE__);
|
|
#endif
|
|
}
|
|
|
|
out32(base + IMX_USDHC_MIX_CTRL, mix_ctl);
|
|
return (status);
|
|
}
|
|
|
|
/**
|
|
* Tuning routine select.
|
|
*
|
|
* @param hc Host controller handle.
|
|
* @param op Device operand (CMD19 - SD, CMD21 - eMMC).
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
static int imx_sdhcx_tune(sdio_hc_t *const hc, const int op)
|
|
{
|
|
int status = EOK;
|
|
const imx_sdhcx_hc_t *const sdhc = hc->cs_hdl;
|
|
|
|
if (sdhc->tuning_mode == IMX_USDHC_TUNING_STANDARD) {
|
|
status = imx_sdhcx_std_tune(hc, op);
|
|
} else {
|
|
status = imx_sdhcx_man_tune(hc, op);
|
|
}
|
|
|
|
return (status);
|
|
}
|
|
|
|
/**
|
|
* Card detect routine.
|
|
*
|
|
* @param hc Host controller handle.
|
|
*
|
|
* @retval SDIO_TRUE Card inserted.
|
|
* @retval SDIO_FALSE Card removed.
|
|
*/
|
|
static int imx_sdhcx_cd(sdio_hc_t *const hc)
|
|
{
|
|
const imx_sdhcx_hc_t *sdhc;
|
|
uintptr_t base;
|
|
int status;
|
|
|
|
sdhc = hc->cs_hdl;
|
|
base = sdhc->base;
|
|
|
|
status = imx_sdhcx_waitmask(hc, IMX_USDHC_PRES_STATE,
|
|
IMX_USDHC_PRES_STATE_CARD_STABLE,
|
|
IMX_USDHC_PRES_STATE_CARD_STABLE,
|
|
100000);
|
|
|
|
if (!(in32(base + IMX_USDHC_PRES_STATE) & IMX_USDHC_PRES_STATE_CINST_MASK)) {
|
|
return (SDIO_FALSE);
|
|
}
|
|
|
|
return ((status == EOK) ? SDIO_TRUE : SDIO_FALSE);
|
|
}
|
|
|
|
/**
|
|
* Host controller de-initialization.
|
|
*
|
|
* @param hc Host controller handle.
|
|
*
|
|
* @return EOK always.
|
|
*/
|
|
int imx_sdhcx_dinit(sdio_hc_t *hc)
|
|
{
|
|
imx_sdhcx_hc_t *sdhc;
|
|
|
|
if (!hc || !hc->cs_hdl) {
|
|
return (EOK);
|
|
}
|
|
|
|
sdhc = hc->cs_hdl;
|
|
|
|
if (sdhc->base) {
|
|
imx_sdhcx_pwr(hc, 0);
|
|
imx_sdhcx_reset(hc, IMX_USDHC_SYS_CTRL_RSTA_MASK);
|
|
out32(sdhc->base + IMX_USDHC_INT_SIGNAL_EN, 0);
|
|
out32(sdhc->base + IMX_USDHC_INT_STATUS_EN, 0);
|
|
|
|
if (hc->hc_iid != -1) {
|
|
InterruptDetach(hc->hc_iid);
|
|
}
|
|
munmap_device_memory((void *)sdhc->base, (size_t)hc->cfg.base_addr_size[0]);
|
|
}
|
|
|
|
if (sdhc->adma) {
|
|
sdio_free(sdhc->adma, sizeof(imx_usdhc_adma32_t) * ADMA_DESC_MAX);
|
|
}
|
|
if (sdhc->fd) {
|
|
close(sdhc->fd);
|
|
}
|
|
|
|
free(sdhc);
|
|
hc->cs_hdl = NULL;
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
static int imx_sdhcx_hs_args(sdio_hc_t *hc)
|
|
{
|
|
sdio_hc_cfg_t *const cfg = &hc->cfg;
|
|
imx_sdhcx_hc_t *const sdhc = hc->cs_hdl;
|
|
int opt;
|
|
int status = EOK;
|
|
char *value;
|
|
char *options = cfg->hsoptions;
|
|
|
|
sdhc->sdll = IMX_USDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_DEFAULT_VALUE;
|
|
sdhc->tuning_mode = IMX_USDHC_TUNING_MANUAL;
|
|
sdhc->std_step = IMX_USDHC_TUNING_CTRL_TUNING_STEP_DEFAULT;
|
|
sdhc->std_start_tap = IMX_USDHC_TUNING_CTRL_TUNING_START_TAP_DEFAULT;
|
|
|
|
static char *const opts[] = {
|
|
#define SDLL 0
|
|
"sdll",
|
|
#define TUNING_MODE 1
|
|
"tuning_mode",
|
|
#define STD_TUNING_CFG 2
|
|
"std_tuning_cfg",
|
|
NULL
|
|
};
|
|
|
|
while (options && (*options != '\0')) {
|
|
opt = sdio_hc_getsubopt( &options, opts, &value );
|
|
if (opt == -1) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 0,
|
|
"%s: invalid HS options %s", __func__, value);
|
|
return EINVAL;
|
|
}
|
|
|
|
switch (opt) {
|
|
case SDLL:
|
|
if ((value == NULL) || (*value == '\0')) {
|
|
sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 0, 0, "%s: Missing argument for '%s'", __FUNCTION__, opts[ opt ] );
|
|
status = EINVAL;
|
|
break;
|
|
}
|
|
sdhc->sdll = (int)strtoull(value, NULL, 0);
|
|
if ((sdhc->sdll << IMX_USDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_SHIFT) > IMX_USDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_MASK) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 0,
|
|
"Strobe DLL Control Slave Delay Target too high. Resetting to %d.",
|
|
IMX_USDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_DEFAULT_VALUE);
|
|
sdhc->sdll = IMX_USDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_DEFAULT_VALUE;
|
|
}
|
|
break;
|
|
case TUNING_MODE:
|
|
if ((value == NULL) || (*value == '\0')) {
|
|
sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 0, 0, "%s: Missing argument for '%s'", __FUNCTION__, opts[ opt ] );
|
|
status = EINVAL;
|
|
break;
|
|
}
|
|
if (strncasecmp(value, "manual", sizeof("manual")) == 0) {
|
|
sdhc->tuning_mode = IMX_USDHC_TUNING_MANUAL;
|
|
} else if (strncasecmp(value, "standard", sizeof("standard")) == 0) {
|
|
sdhc->tuning_mode = IMX_USDHC_TUNING_STANDARD;
|
|
} else {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 0,
|
|
"Tuning mode is invalid(%s). Defaulting to manual.", value);
|
|
sdhc->tuning_mode = IMX_USDHC_TUNING_MANUAL;
|
|
}
|
|
break;
|
|
case STD_TUNING_CFG:
|
|
if ((value == NULL) || (*value == '\0')) {
|
|
sdio_slogf( _SLOGC_SDIODI, _SLOG_ERROR, 0, 0, "%s: Missing argument for '%s'", __FUNCTION__, opts[ opt ] );
|
|
status = EINVAL;
|
|
break;
|
|
}
|
|
sdhc->std_step = (uint16_t)strtoull(value, &value, 0);
|
|
if ((sdhc->std_step << IMX_USDHC_TUNING_CTRL_TUNING_STEP_SHIFT) > IMX_USDHC_TUNING_CTRL_TUNING_STEP_MASK) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 0,
|
|
"Tuning Step is too high. Resetting to %d.",
|
|
IMX_USDHC_TUNING_CTRL_TUNING_STEP_DEFAULT);
|
|
sdhc->std_step = IMX_USDHC_TUNING_CTRL_TUNING_STEP_DEFAULT;
|
|
}
|
|
if (*value == '^') {
|
|
sdhc->std_start_tap = (uint16_t)strtol(value + 1, &value, 0);
|
|
if ((sdhc->std_start_tap << IMX_USDHC_TUNING_CTRL_TUNING_START_TAP_SHIFT) > IMX_USDHC_TUNING_CTRL_TUNING_START_TAP_MASK) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 0,
|
|
"Tuning Start Tap is too high. Resetting to %d.",
|
|
IMX_USDHC_TUNING_CTRL_TUNING_START_TAP_DEFAULT);
|
|
sdhc->std_start_tap = IMX_USDHC_TUNING_CTRL_TUNING_START_TAP_DEFAULT;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return (status);
|
|
}
|
|
|
|
/**
|
|
* Host controller initialization.
|
|
*
|
|
* @param hc Host controller handle.
|
|
*
|
|
* @return Execution status.
|
|
*/
|
|
int imx_sdhcx_init(sdio_hc_t *hc)
|
|
{
|
|
sdio_hc_cfg_t *cfg;
|
|
imx_sdhcx_hc_t *sdhc;
|
|
uint32_t cap;
|
|
uintptr_t base;
|
|
struct sigevent event;
|
|
unsigned hwi_off, mcu = 0, mcu_rev = 0;
|
|
const hwi_tag *tag_hwversion = NULL;
|
|
uint32_t tuning_ctrl;
|
|
int status = EOK;
|
|
|
|
|
|
/** Controller entry configuration (some overrides done in bs.c file) */
|
|
static const sdio_hc_entry_t imx_sdhcx_hc_entry = {
|
|
.nentries = SDIO_HC_ENTRY_NFUNCS,
|
|
.dinit = imx_sdhcx_dinit,
|
|
.pm = NULL,
|
|
.cmd = imx_sdhcx_cmd,
|
|
.abort = imx_sdhcx_abort,
|
|
.event = imx_sdhcx_event,
|
|
.cd = imx_sdhcx_cd,
|
|
.pwr = imx_sdhcx_pwr,
|
|
.clk = imx_sdhcx_clk,
|
|
.bus_mode = imx_sdhcx_bus_mode,
|
|
.bus_width = imx_sdhcx_bus_width,
|
|
.timing = imx_sdhcx_timing,
|
|
.signal_voltage = imx_sdhcx_signal_voltage,
|
|
.drv_type = NULL,
|
|
.driver_strength = NULL,
|
|
.tune = imx_sdhcx_tune,
|
|
.preset = NULL
|
|
};
|
|
|
|
hc->hc_iid = -1;
|
|
cfg = &hc->cfg;
|
|
|
|
memcpy(&hc->entry, &imx_sdhcx_hc_entry, sizeof(sdio_hc_entry_t));
|
|
|
|
if (!cfg->base_addr[0]) {
|
|
return (ENODEV);
|
|
}
|
|
if (!cfg->base_addr_size[0]) {
|
|
cfg->base_addr_size[0] = IMX_USDHC_SIZE;
|
|
}
|
|
sdhc = calloc(1, sizeof(imx_sdhcx_hc_t));
|
|
if (sdhc == NULL) {
|
|
return (ENOMEM);
|
|
}
|
|
hc->cs_hdl = sdhc;
|
|
|
|
status = imx_sdhcx_hs_args(hc);
|
|
if (status != EOK) {
|
|
return status;
|
|
}
|
|
|
|
base = mmap_device_io((size_t)cfg->base_addr_size[0], cfg->base_addr[0]);
|
|
if (base == (uintptr_t)MAP_FAILED) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 1,
|
|
"%s: SDHCI base mmap_device_memory (0x%lx) %s",
|
|
__FUNCTION__, cfg->base_addr[0], strerror(errno));
|
|
imx_sdhcx_dinit(hc);
|
|
return (errno);
|
|
}
|
|
sdhc->base = base;
|
|
sdhc->usdhc_addr = cfg->base_addr[0];
|
|
#ifdef IMX8_SDHCX_DEBUG
|
|
imx_sdhcx_reg_dump(hc, __FUNCTION__, __LINE__);
|
|
#endif
|
|
|
|
/* The CD_B pin mux configuration from startup routine can cause a spike on CD_B pin and this
|
|
* will be interpreted incorrectly as a transition from "0" to "1" to cause the CRM bit is
|
|
* being set incorretly from usdhc controller. This eventually causes the first initialization
|
|
* process is always failed. So, all pending bits of INT_STATUS register should be clear before
|
|
* software reset.
|
|
*/
|
|
out32(base + IMX_USDHC_INT_STATUS, in32(base + IMX_USDHC_INT_STATUS));
|
|
|
|
|
|
/* Device voltage reset. Needed for SDR104 cards for correct re-initialization.
|
|
* This routine is executed for SD cards only since reset signal is ignored by device by default
|
|
* according to the eMMC specification. */
|
|
if (!((hc->caps & HC_CAP_SLOT_TYPE_EMBEDDED) && (hc->flags & HC_FLAG_DEV_MMC))) {
|
|
imx_sdhcx_voltage_reset(hc);
|
|
}
|
|
#if SDHC_CTRL_RESET_CTL != 0
|
|
/* RM: During initialization, the Host Driver shall set this bit to 1 to reset the uSDHC */
|
|
imx_sdhcx_reset(hc, IMX_USDHC_SYS_CTRL_RSTA_MASK);
|
|
#endif
|
|
#if SDHC_REG_RESET_CTL != 0
|
|
/* Reset tuning circuit. It is necessary when booting from uBoot.
|
|
* Otherwise driver will not pass initialization. */
|
|
out32(base + IMX_USDHC_AUTOCMD12_ERR_STATUS, 0x0);
|
|
/* iMXMP - when boot from eMMC, ROM left its configuration.
|
|
* Reset MIX, ACMD12, DLLCTRL, VSPEC to after reset state for
|
|
* correct driver functionality. */
|
|
out32(base + IMX_USDHC_DLL_CTRL, IMX_USDHC_DLL_CTRL_RESET_MASK);
|
|
out32(base + IMX_USDHC_DLL_CTRL, 0);
|
|
out32(base + IMX_USDHC_VEND_SPEC, in32(base + IMX_USDHC_VEND_SPEC) & ~IMX_USDHC_VEND_SPEC_FRC_SDCLK_ON_MASK);
|
|
out32(base + IMX_USDHC_MIX_CTRL, IMX_USDHC_MIX_CTRL_RESERVED31_MASK);
|
|
#endif
|
|
|
|
if (!hc->version) {
|
|
hc->version = in32(base + IMX_USDHC_HOST_CTRL_VER) & IMX_USDHC_HOST_CTRL_VER_SVN_MASK;
|
|
}
|
|
|
|
cap = in32(base + IMX_USDHC_HOST_CTRL_CAP);
|
|
|
|
if (cfg->clk) {
|
|
hc->clk_max = (uint32_t)cfg->clk;
|
|
} else {
|
|
hc->clk_max = IMX_USDHC_CLOCK_DEFAULT;
|
|
}
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,"%s: Base: 0x%"PRIX64", Clk: %d Hz, Irq: %d",
|
|
__FUNCTION__, sdhc->usdhc_addr, hc->clk_max, cfg->irq[0]);
|
|
|
|
/* TKT357500 - Look for board and silicon version */
|
|
hwi_off = hwi_find_device("board", 0);
|
|
if (hwi_off != HWI_NULL_OFF) {
|
|
tag_hwversion = hwi_tag_find(hwi_off, HWI_TAG_NAME_hwversion, NULL);
|
|
if (tag_hwversion != NULL) {
|
|
mcu = tag_hwversion->hwversion.hclass;
|
|
mcu_rev = tag_hwversion->hwversion.version;
|
|
}
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_INFO, hc->cfg.verbosity, 1,"%s: Mcu: %d, Mcu rev: %d", __FUNCTION__, mcu, mcu_rev);
|
|
}
|
|
|
|
hc->caps |= HC_CAP_BSY | HC_CAP_BW4 | HC_CAP_BW8 | HC_CAP_CD_INTR | HC_CAP_CD_WP;
|
|
hc->caps |= HC_CAP_ACMD12 | HC_CAP_200MA | HC_CAP_DRV_TYPE_B;
|
|
|
|
if (cap & IMX_USDHC_HOST_CTRL_CAP_HSS_MASK) {
|
|
hc->caps |= HC_CAP_HS;
|
|
}
|
|
if (cap & IMX_USDHC_HOST_CTRL_CAP_DMAS_MASK) {
|
|
hc->caps |= HC_CAP_DMA;
|
|
}
|
|
|
|
hc->caps |= HC_CAP_SDR104;
|
|
hc->caps |= HC_CAP_SDR50 | HC_CAP_SDR25 | HC_CAP_SDR12;
|
|
/* TKT357500 - In enhance HS400 mode, usdhc can not report command timeout when emmc does not send response. */
|
|
if ((tag_hwversion != NULL) && (mcu == IMX_CHIP_TYPE_QUAD_MAX) && (mcu_rev == IMX_CHIP_REV_A)) {
|
|
hc->caps |= HC_CAP_DDR50 | HC_CAP_HS200 | HC_CAP_HS400;
|
|
} else {
|
|
hc->caps |= HC_CAP_DDR50 | HC_CAP_HS200 | HC_CAP_HS400 | HC_CAP_HS400ES;
|
|
}
|
|
sdhc->flags |= SF_TUNE_SDR50;
|
|
|
|
if (cap & IMX_USDHC_HOST_CTRL_CAP_VS18_MASK) {
|
|
hc->ocr = OCR_VDD_17_195;
|
|
hc->caps |= HC_CAP_SV_1_8V;
|
|
}
|
|
if ((cap & IMX_USDHC_HOST_CTRL_CAP_VS30_MASK)) {
|
|
hc->ocr = OCR_VDD_30_31 | OCR_VDD_29_30;
|
|
hc->caps |= HC_CAP_SV_3_0V;
|
|
}
|
|
if ((cap & IMX_USDHC_HOST_CTRL_CAP_VS33_MASK)) {
|
|
hc->ocr = OCR_VDD_32_33 | OCR_VDD_33_34;
|
|
hc->caps |= HC_CAP_SV_3_3V;
|
|
}
|
|
|
|
#ifdef ADMA_SUPPORTED
|
|
if ((cap & IMX_USDHC_HOST_CTRL_CAP_DMAS_MASK)) {
|
|
if (hc->version >= IMX_USDHC_HOST_CTRL_VER_SVN_BV_VER_3) {
|
|
hc->cfg.sg_max = ADMA_DESC_MAX;
|
|
sdhc->adma = sdio_alloc(sizeof(imx_usdhc_adma32_t) * ADMA_DESC_MAX);
|
|
if (sdhc->adma == NULL) {
|
|
sdio_slogf(_SLOGC_SDIODI, _SLOG_ERROR, hc->cfg.verbosity, 1, "%s: ADMA mmap %s",
|
|
__FUNCTION__,
|
|
strerror(errno));
|
|
imx_sdhcx_dinit(hc);
|
|
return (errno);
|
|
}
|
|
sdhc->flags |= SF_USE_ADMA;
|
|
/* Get physical address of the ADMA descriptor location */
|
|
sdhc->admap = (uint32_t)sdio_vtop(sdhc->adma);
|
|
if (hc->version >= IMX_USDHC_HOST_CTRL_VER_SVN_BV_VER_3) {
|
|
hc->caps |= HC_CAP_ACMD23;
|
|
}
|
|
} else {
|
|
hc->cfg.sg_max = 1;
|
|
sdhc->flags |= SF_USE_SDMA;
|
|
}
|
|
}
|
|
#endif /* ADMA_SUPPORTED */
|
|
hc->caps &= cfg->caps; /* Reconcile command line options */
|
|
|
|
SIGEV_PULSE_INIT(&event, hc->hc_coid, (short)hc->priority, HC_EV_INTR, NULL);
|
|
hc->hc_iid = InterruptAttachEvent(cfg->irq[0], &event,
|
|
_NTO_INTR_FLAGS_TRK_MSK);
|
|
if (hc->hc_iid == -1) {
|
|
imx_sdhcx_dinit(hc);
|
|
return (errno);
|
|
}
|
|
|
|
/* Configure watermark value */
|
|
out32(base + IMX_USDHC_WTMK_LVL, (IMX_USDHC_WTMK_LVL_WR_WML_BV_MAX_VAL << IMX_USDHC_WTMK_LVL_WR_WML_SHIFT) |
|
|
(IMX_USDHC_WTMK_LVL_RD_WML_BV_MAX_VAL << IMX_USDHC_WTMK_LVL_RD_WML_SHIFT));
|
|
|
|
out32(base + IMX_USDHC_INT_SIGNAL_EN, IMX_USDHC_INT_SIGNAL_EN_DFLTS);
|
|
/* Only enable the card insertion and removal interrupts */
|
|
out32(base + IMX_USDHC_INT_STATUS_EN, IMX_USDHC_INT_STATUS_EN_CINSSEN_MASK |
|
|
IMX_USDHC_INT_STATUS_EN_CRMSEN_MASK);
|
|
|
|
if (sdhc->tuning_mode == IMX_USDHC_TUNING_STANDARD) {
|
|
/* Enable STD tuning */
|
|
tuning_ctrl = in32(base + IMX_USDHC_TUNING_CTRL);
|
|
tuning_ctrl &= ~(IMX_USDHC_TUNING_CTRL_TUNING_STEP_MASK | IMX_USDHC_TUNING_CTRL_TUNING_START_TAP_MASK);
|
|
tuning_ctrl |= IMX_USDHC_TUNING_CTRL_TUNING_STEP(sdhc->std_step);
|
|
tuning_ctrl |= IMX_USDHC_TUNING_CTRL_TUNING_START_TAP(sdhc->std_start_tap);
|
|
tuning_ctrl |= IMX_USDHC_TUNING_CTRL_STD_TUNING_EN_MASK;
|
|
tuning_ctrl |= IMX_USDHC_TUNING_CTRL_DIS_CMD_CHK_FOR_STD_TUNING_MASK;
|
|
out32(base + IMX_USDHC_TUNING_CTRL, tuning_ctrl);
|
|
} else {
|
|
/* Disable STD tuning */
|
|
tuning_ctrl = in32(base + IMX_USDHC_TUNING_CTRL);
|
|
tuning_ctrl &= ~IMX_USDHC_TUNING_CTRL_STD_TUNING_EN_MASK;
|
|
out32(base + IMX_USDHC_TUNING_CTRL, tuning_ctrl);
|
|
}
|
|
|
|
return (EOK);
|
|
}
|
|
|
|
#endif
|
|
|
|
/** @} */
|
|
|
|
#if defined(__QNXNTO__) && defined(__USESRCVERSION)
|
|
#include <sys/srcversion.h>
|
|
__SRCVERSION("$URL: http://svn.ott.qnx.com/product/hardware/branches/release/hardware/devb/sdmmc/sdiodi/hc/imx8_hc.c $ $Rev: 993539 $")
|
|
#endif
|