| /* |
| * Copyright (c) 2018, ARM Limited and Contributors. All rights reserved. |
| * Copyright 2025 NXP |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <string.h> |
| |
| #include <arch.h> |
| #include <arch_helpers.h> |
| #include <common/debug.h> |
| #include <drivers/delay_timer.h> |
| #include <drivers/mmc.h> |
| #include <lib/mmio.h> |
| #include <lib/xlat_tables/xlat_tables_v2.h> |
| |
| #include <imx_usdhc.h> |
| |
| /* These masks represent the commands which involve a data transfer. */ |
| #define ADTC_MASK_SD (BIT_32(6U) | BIT_32(17U) | BIT_32(18U) |\ |
| BIT_32(24U) | BIT_32(25U)) |
| #define ADTC_MASK_ACMD (BIT_64(51U)) |
| |
| struct imx_usdhc_device_data { |
| uint32_t addr; |
| uint32_t blk_size; |
| uint32_t blks; |
| bool valid; |
| }; |
| |
| static void imx_usdhc_initialize(void); |
| static int imx_usdhc_send_cmd(struct mmc_cmd *cmd); |
| static int imx_usdhc_set_ios(unsigned int clk, unsigned int width); |
| static int imx_usdhc_prepare(int lba, uintptr_t buf, size_t size); |
| static int imx_usdhc_read(int lba, uintptr_t buf, size_t size); |
| static int imx_usdhc_write(int lba, uintptr_t buf, size_t size); |
| |
| static const struct mmc_ops imx_usdhc_ops = { |
| .init = imx_usdhc_initialize, |
| .send_cmd = imx_usdhc_send_cmd, |
| .set_ios = imx_usdhc_set_ios, |
| .prepare = imx_usdhc_prepare, |
| .read = imx_usdhc_read, |
| .write = imx_usdhc_write, |
| }; |
| |
| static imx_usdhc_params_t imx_usdhc_params; |
| static struct imx_usdhc_device_data imx_usdhc_data; |
| |
| static bool imx_usdhc_is_buf_valid(void) |
| { |
| return imx_usdhc_data.valid; |
| } |
| |
| static bool imx_usdhc_is_buf_multiblk(void) |
| { |
| return imx_usdhc_data.blks > 1U; |
| } |
| |
| static void imx_usdhc_inval_buf_data(void) |
| { |
| imx_usdhc_data.valid = false; |
| } |
| |
| static int imx_usdhc_save_buf_data(uintptr_t buf, size_t size) |
| { |
| uint32_t block_size; |
| uint64_t blks; |
| |
| if (size <= MMC_BLOCK_SIZE) { |
| block_size = (uint32_t)size; |
| } else { |
| block_size = MMC_BLOCK_SIZE; |
| } |
| |
| if (buf > UINT32_MAX) { |
| return -EOVERFLOW; |
| } |
| |
| imx_usdhc_data.addr = (uint32_t)buf; |
| imx_usdhc_data.blk_size = block_size; |
| blks = size / block_size; |
| imx_usdhc_data.blks = (uint32_t)blks; |
| |
| imx_usdhc_data.valid = true; |
| |
| return 0; |
| } |
| |
| static void imx_usdhc_write_buf_data(void) |
| { |
| uintptr_t reg_base = imx_usdhc_params.reg_base; |
| uint32_t addr, blks, blk_size; |
| |
| addr = imx_usdhc_data.addr; |
| blks = imx_usdhc_data.blks; |
| blk_size = imx_usdhc_data.blk_size; |
| |
| mmio_write_32(reg_base + DSADDR, addr); |
| mmio_write_32(reg_base + BLKATT, BLKATT_BLKCNT(blks) | |
| BLKATT_BLKSIZE(blk_size)); |
| } |
| |
| #define IMX7_MMC_SRC_CLK_RATE (200 * 1000 * 1000) |
| static void imx_usdhc_set_clk(unsigned int clk) |
| { |
| unsigned int sdhc_clk = IMX7_MMC_SRC_CLK_RATE; |
| uintptr_t reg_base = imx_usdhc_params.reg_base; |
| unsigned int pre_div = 1U, div = 1U; |
| |
| assert(clk > 0); |
| |
| while (sdhc_clk / (16 * pre_div) > clk && pre_div < 256) |
| pre_div *= 2; |
| |
| while (((sdhc_clk / (div * pre_div)) > clk) && (div < 16U)) { |
| div++; |
| } |
| |
| pre_div >>= 1; |
| div -= 1; |
| clk = (pre_div << 8) | (div << 4); |
| |
| while ((mmio_read_32(reg_base + PSTATE) & PSTATE_SDSTB) == 0U) { |
| } |
| |
| mmio_clrbits32(reg_base + VENDSPEC, VENDSPEC_CARD_CLKEN); |
| mmio_clrsetbits32(reg_base + SYSCTRL, SYSCTRL_CLOCK_MASK, clk); |
| udelay(10000); |
| |
| mmio_setbits32(reg_base + VENDSPEC, VENDSPEC_PER_CLKEN | VENDSPEC_CARD_CLKEN); |
| } |
| |
| static void imx_usdhc_initialize(void) |
| { |
| unsigned int timeout = 10000; |
| uintptr_t reg_base = imx_usdhc_params.reg_base; |
| |
| assert((imx_usdhc_params.reg_base & MMC_BLOCK_MASK) == 0); |
| |
| /* reset the controller */ |
| mmio_setbits32(reg_base + SYSCTRL, SYSCTRL_RSTA); |
| |
| /* wait for reset done */ |
| while ((mmio_read_32(reg_base + SYSCTRL) & SYSCTRL_RSTA)) { |
| if (!timeout) |
| ERROR("IMX MMC reset timeout.\n"); |
| timeout--; |
| } |
| |
| mmio_write_32(reg_base + MMCBOOT, 0); |
| mmio_write_32(reg_base + MIXCTRL, 0); |
| mmio_write_32(reg_base + CLKTUNECTRLSTS, 0); |
| |
| mmio_write_32(reg_base + VENDSPEC, VENDSPEC_INIT); |
| mmio_write_32(reg_base + DLLCTRL, 0); |
| mmio_setbits32(reg_base + VENDSPEC, VENDSPEC_IPG_CLKEN | VENDSPEC_PER_CLKEN); |
| |
| /* Set the initial boot clock rate */ |
| imx_usdhc_set_clk(MMC_BOOT_CLK_RATE); |
| udelay(100); |
| |
| /* Clear read/write ready status */ |
| mmio_clrbits32(reg_base + INTSTATEN, INTSTATEN_BRR | INTSTATEN_BWR); |
| |
| /* configure as little endian */ |
| mmio_write_32(reg_base + PROTCTRL, PROTCTRL_LE); |
| |
| /* Set timeout to the maximum value */ |
| mmio_clrsetbits32(reg_base + SYSCTRL, SYSCTRL_TIMEOUT_MASK, |
| SYSCTRL_TIMEOUT(15)); |
| |
| /* set wartermark level as 16 for safe for MMC */ |
| mmio_clrsetbits32(reg_base + WATERMARKLEV, WMKLV_MASK, 16 | (16 << 16)); |
| } |
| |
| #define FSL_CMD_RETRIES 1000 |
| |
| static bool is_data_transfer_to_card(const struct mmc_cmd *cmd) |
| { |
| unsigned int cmd_idx = cmd->cmd_idx; |
| |
| return (cmd_idx == MMC_CMD(24)) || (cmd_idx == MMC_CMD(25)); |
| } |
| |
| static bool is_data_transfer_cmd(const struct mmc_cmd *cmd) |
| { |
| uintptr_t reg_base = imx_usdhc_params.reg_base; |
| unsigned int cmd_idx = cmd->cmd_idx; |
| uint32_t xfer_type; |
| |
| xfer_type = mmio_read_32(reg_base + XFERTYPE); |
| |
| if (XFERTYPE_GET_CMD(xfer_type) == MMC_CMD(55)) { |
| return (ADTC_MASK_ACMD & BIT_64(cmd_idx)) != 0ULL; |
| } |
| |
| if ((ADTC_MASK_SD & BIT_32(cmd->cmd_idx)) != 0U) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static int get_xfr_type(const struct mmc_cmd *cmd, bool data, uint32_t *xfertype) |
| { |
| *xfertype = XFERTYPE_CMD(cmd->cmd_idx); |
| |
| switch (cmd->resp_type) { |
| case MMC_RESPONSE_R2: |
| *xfertype |= XFERTYPE_RSPTYP_136; |
| *xfertype |= XFERTYPE_CCCEN; |
| break; |
| case MMC_RESPONSE_R4: |
| *xfertype |= XFERTYPE_RSPTYP_48; |
| break; |
| case MMC_RESPONSE_R6: |
| *xfertype |= XFERTYPE_RSPTYP_48; |
| *xfertype |= XFERTYPE_CICEN; |
| *xfertype |= XFERTYPE_CCCEN; |
| break; |
| case MMC_RESPONSE_R1B: |
| *xfertype |= XFERTYPE_RSPTYP_48_BUSY; |
| *xfertype |= XFERTYPE_CICEN; |
| *xfertype |= XFERTYPE_CCCEN; |
| break; |
| default: |
| ERROR("Invalid CMD response: %u\n", cmd->resp_type); |
| return -EINVAL; |
| } |
| |
| if (data) { |
| *xfertype |= XFERTYPE_DPSEL; |
| } |
| |
| return 0; |
| } |
| |
| static int imx_usdhc_send_cmd(struct mmc_cmd *cmd) |
| { |
| uintptr_t reg_base = imx_usdhc_params.reg_base; |
| unsigned int state, flags = INTSTATEN_CC | INTSTATEN_CTOE; |
| unsigned int mixctl = 0; |
| unsigned int cmd_retries = 0; |
| uint32_t xfertype; |
| bool data; |
| int err = 0; |
| |
| assert(cmd); |
| |
| data = is_data_transfer_cmd(cmd); |
| |
| err = get_xfr_type(cmd, data, &xfertype); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* clear all irq status */ |
| mmio_write_32(reg_base + INTSTAT, 0xffffffff); |
| |
| /* Wait for the bus to be idle */ |
| do { |
| state = mmio_read_32(reg_base + PSTATE); |
| } while (state & (PSTATE_CDIHB | PSTATE_CIHB)); |
| |
| while (mmio_read_32(reg_base + PSTATE) & PSTATE_DLA) |
| ; |
| |
| mmio_write_32(reg_base + INTSIGEN, 0); |
| |
| if (data) { |
| mixctl |= MIXCTRL_DMAEN; |
| } |
| |
| if (!is_data_transfer_to_card(cmd)) { |
| mixctl |= MIXCTRL_DTDSEL; |
| } |
| |
| if ((cmd->cmd_idx != MMC_CMD(55)) && imx_usdhc_is_buf_valid()) { |
| if (imx_usdhc_is_buf_multiblk()) { |
| mixctl |= MIXCTRL_MSBSEL | MIXCTRL_BCEN; |
| } |
| |
| imx_usdhc_write_buf_data(); |
| imx_usdhc_inval_buf_data(); |
| } |
| |
| /* Send the command */ |
| mmio_write_32(reg_base + CMDARG, cmd->cmd_arg); |
| mmio_clrsetbits32(reg_base + MIXCTRL, MIXCTRL_DATMASK, mixctl); |
| mmio_write_32(reg_base + XFERTYPE, xfertype); |
| |
| /* Wait for the command done */ |
| do { |
| state = mmio_read_32(reg_base + INTSTAT); |
| if (cmd_retries) |
| udelay(1); |
| } while ((!(state & flags)) && ++cmd_retries < FSL_CMD_RETRIES); |
| |
| if ((state & (INTSTATEN_CTOE | CMD_ERR)) || cmd_retries == FSL_CMD_RETRIES) { |
| if (cmd_retries == FSL_CMD_RETRIES) |
| err = -ETIMEDOUT; |
| else |
| err = -EIO; |
| ERROR("imx_usdhc mmc cmd %d state 0x%x errno=%d\n", |
| cmd->cmd_idx, state, err); |
| goto out; |
| } |
| |
| /* Copy the response to the response buffer */ |
| if (cmd->resp_type & MMC_RSP_136) { |
| unsigned int cmdrsp3, cmdrsp2, cmdrsp1, cmdrsp0; |
| |
| cmdrsp3 = mmio_read_32(reg_base + CMDRSP3); |
| cmdrsp2 = mmio_read_32(reg_base + CMDRSP2); |
| cmdrsp1 = mmio_read_32(reg_base + CMDRSP1); |
| cmdrsp0 = mmio_read_32(reg_base + CMDRSP0); |
| cmd->resp_data[3] = (cmdrsp3 << 8) | (cmdrsp2 >> 24); |
| cmd->resp_data[2] = (cmdrsp2 << 8) | (cmdrsp1 >> 24); |
| cmd->resp_data[1] = (cmdrsp1 << 8) | (cmdrsp0 >> 24); |
| cmd->resp_data[0] = (cmdrsp0 << 8); |
| } else { |
| cmd->resp_data[0] = mmio_read_32(reg_base + CMDRSP0); |
| } |
| |
| /* Wait until all of the blocks are transferred */ |
| if (data) { |
| flags = DATA_COMPLETE; |
| do { |
| state = mmio_read_32(reg_base + INTSTAT); |
| |
| if (state & (INTSTATEN_DTOE | DATA_ERR)) { |
| err = -EIO; |
| ERROR("imx_usdhc mmc data state 0x%x\n", state); |
| goto out; |
| } |
| } while ((state & flags) != flags); |
| } |
| |
| out: |
| /* Reset CMD and DATA on error */ |
| if (err) { |
| mmio_setbits32(reg_base + SYSCTRL, SYSCTRL_RSTC); |
| while (mmio_read_32(reg_base + SYSCTRL) & SYSCTRL_RSTC) |
| ; |
| |
| if (data) { |
| mmio_setbits32(reg_base + SYSCTRL, SYSCTRL_RSTD); |
| while (mmio_read_32(reg_base + SYSCTRL) & SYSCTRL_RSTD) |
| ; |
| } |
| } |
| |
| /* clear all irq status */ |
| mmio_write_32(reg_base + INTSTAT, 0xffffffff); |
| |
| return err; |
| } |
| |
| static int imx_usdhc_set_ios(unsigned int clk, unsigned int width) |
| { |
| uintptr_t reg_base = imx_usdhc_params.reg_base; |
| |
| imx_usdhc_set_clk(clk); |
| |
| if (width == MMC_BUS_WIDTH_4) |
| mmio_clrsetbits32(reg_base + PROTCTRL, PROTCTRL_WIDTH_MASK, |
| PROTCTRL_WIDTH_4); |
| else if (width == MMC_BUS_WIDTH_8) |
| mmio_clrsetbits32(reg_base + PROTCTRL, PROTCTRL_WIDTH_MASK, |
| PROTCTRL_WIDTH_8); |
| |
| return 0; |
| } |
| |
| static int imx_usdhc_prepare(int lba, uintptr_t buf, size_t size) |
| { |
| flush_dcache_range(buf, size); |
| return imx_usdhc_save_buf_data(buf, size); |
| } |
| |
| static int imx_usdhc_read(int lba, uintptr_t buf, size_t size) |
| { |
| inv_dcache_range(buf, size); |
| return 0; |
| } |
| |
| static int imx_usdhc_write(int lba, uintptr_t buf, size_t size) |
| { |
| return 0; |
| } |
| |
| void imx_usdhc_init(imx_usdhc_params_t *params, |
| struct mmc_device_info *mmc_dev_info) |
| { |
| int ret __maybe_unused; |
| |
| assert((params != 0) && |
| ((params->reg_base & MMC_BLOCK_MASK) == 0) && |
| ((params->bus_width == MMC_BUS_WIDTH_1) || |
| (params->bus_width == MMC_BUS_WIDTH_4) || |
| (params->bus_width == MMC_BUS_WIDTH_8))); |
| |
| #if PLAT_XLAT_TABLES_DYNAMIC |
| ret = mmap_add_dynamic_region(params->reg_base, params->reg_base, |
| PAGE_SIZE, |
| MT_DEVICE | MT_RW | MT_SECURE); |
| if (ret != 0) { |
| ERROR("Failed to map the uSDHC registers\n"); |
| panic(); |
| } |
| #endif |
| |
| memcpy(&imx_usdhc_params, params, sizeof(imx_usdhc_params_t)); |
| mmc_init(&imx_usdhc_ops, params->clk_rate, params->bus_width, |
| params->flags, mmc_dev_info); |
| } |