Files
03-QNX_Drivers/src/hardware/devb/sdmmc/sdiodi/hc/imx8_hc.c
2025-05-22 22:52:09 +08:00

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