/* * 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 #include #include #include #include #include #include #include #include #include #include #ifdef SDIO_HC_IMX8 #include #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 __SRCVERSION("$URL: http://svn.ott.qnx.com/product/hardware/branches/release/hardware/devb/sdmmc/sdiodi/hc/imx8_hc.c $ $Rev: 993539 $") #endif