/** * @file sdrv_sdhci.c * @brief sdrv sdhci drv source. * * @copyright Copyright (c) 2020 Semidrive Semiconductor. * All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #if (CONFIG_OS_FREERTOS && CONFIG_SDHCI_SUPPORT_INTERRUPT) static TaskHandle_t mmc_process_thread; #endif #define SDHCI_TOUT_TIMER_CLK (1000000) #define MAX_TUNING_LOOP 140 #define lower_32_bits(n) ((uint32_t)(n)) #define upper_32_bits(n) ((uint32_t)(((n) >> 16) >> 16)) #define IS_ARCH_CACHE_LINE_ALIGNED(addr) !((addr_t)(addr) & (CONFIG_ARCH_CACHE_LINE - 1)) #define SDHCI_TRANS_USE_ADMA 1 #define INT_STACK_SZ 1024 #define ADMA_BOUNDARY_SIZE (0x8000000) #define CALC_BOUNDARY(addr, boundary) (((addr) + (boundary)) & ~((boundary) - 1)) static struct desc_entry g_dma_sg_list[ADMA_DESC_LIST_SIZE] __ALIGNED( CONFIG_ARCH_CACHE_LINE); static void sdrv_sdhci_dumpregs(struct sdhci_host *host) { DETAIL_PRINTF(SSDK_CRIT, "*************** SDHC REG DUMP START *****************\r\n"); ssdk_printf(SSDK_CRIT, "Version: 0x%08x\r\n", REG_READ16(host, SDHCI_SPEC_VERSION_REG)); ssdk_printf(SSDK_CRIT, "Arg2: 0x%08x\t", REG_READ32(host, SDHCI_SDMASA_BLKCNT_REG)); ssdk_printf(SSDK_CRIT, "Blk Cnt: 0x%08x\r\n", REG_READ16(host, SDHCI_BLK_CNT_REG)); ssdk_printf(SSDK_CRIT, "Arg1: 0x%08x\t", REG_READ32(host, SDHCI_ARGUMENT_REG)); ssdk_printf(SSDK_CRIT, "Blk Sz : 0x%08x\r\n", REG_READ16(host, SDHCI_BLKSZ_REG)); ssdk_printf(SSDK_CRIT, "Command: 0x%08x\t", REG_READ16(host, SDHCI_CMD_REG)); ssdk_printf(SSDK_CRIT, "Trans mode: 0x%08x\r\n", REG_READ16(host, SDHCI_TRANS_MODE_REG)); ssdk_printf(SSDK_CRIT, "Resp0: 0x%08x\t", REG_READ32(host, SDHCI_RESP_REG)); ssdk_printf(SSDK_CRIT, "Resp1: 0x%08x\r\n", REG_READ32(host, SDHCI_RESP_REG + 0x4)); ssdk_printf(SSDK_CRIT, "Resp2: 0x%08x\t", REG_READ32(host, SDHCI_RESP_REG + 0x8)); ssdk_printf(SSDK_CRIT, "Resp3: 0x%08x\r\n", REG_READ32(host, SDHCI_RESP_REG + 0xC)); ssdk_printf(SSDK_CRIT, "Prsnt State: 0x%08x\t", REG_READ32(host, SDHCI_PRESENT_STATE_REG)); ssdk_printf(SSDK_CRIT, "Host Ctrl1: 0x%08x\r\n", REG_READ8(host, SDHCI_HOST_CTRL1_REG)); ssdk_printf(SSDK_CRIT, "Timeout ctrl: 0x%08x\t", REG_READ8(host, SDHCI_TIMEOUT_REG)); ssdk_printf(SSDK_CRIT, "Power Ctrl: 0x%08x\r\n", REG_READ8(host, SDHCI_PWR_CTRL_REG)); ssdk_printf(SSDK_CRIT, "Error stat: 0x%08x\t", REG_READ16(host, SDHCI_ERR_INT_STS_REG)); ssdk_printf(SSDK_CRIT, "Int Status: 0x%08x\r\n", REG_READ16(host, SDHCI_NRML_INT_STS_REG)); ssdk_printf(SSDK_CRIT, "Host Ctrl2: 0x%08x\t", REG_READ16(host, SDHCI_HOST_CTRL2_REG)); ssdk_printf(SSDK_CRIT, "Clock ctrl: 0x%08x\r\n", REG_READ16(host, SDHCI_CLK_CTRL_REG)); ssdk_printf(SSDK_CRIT, "Caps1: 0x%08x\t", REG_READ32(host, SDHCI_CAPS_REG1)); ssdk_printf(SSDK_CRIT, "Caps2: 0x%08x\r\n", REG_READ32(host, SDHCI_CAPS_REG2)); ssdk_printf(SSDK_CRIT, "Adma Err: 0x%08x\t", REG_READ8(host, SDHCI_ADM_ERR_REG)); ssdk_printf(SSDK_CRIT, "Auto Cmd err: 0x%08x\r\n", REG_READ16(host, SDHCI_AUTO_CMD_ERR)); ssdk_printf(SSDK_CRIT, "Adma addr1: 0x%08x\t", REG_READ32(host, SDHCI_ADM_ADDR_REG)); ssdk_printf(SSDK_CRIT, "Adma addr2: 0x%08x\r\n", REG_READ32(host, SDHCI_ADM_ADDR_REG + 0x4)); } /* * Function: sdhci reset * Arg : Host structure & mask to write to reset register * Return : None * Flow: : Reset the host controller */ status_t sdrv_sdhci_reset(struct sdhci_host *host, uint8_t mask) { uint32_t reg; uint32_t timeout = SDHCI_RESET_MAX_TIMEOUT; REG_WRITE8(host, mask, SDHCI_RESET_REG); /* Wait for the reset to complete */ do { reg = REG_READ8(host, SDHCI_RESET_REG); reg &= mask; if (!reg) { break; } if (!timeout) { DETAIL_PRINTF(SSDK_CRIT, "Error: sdhci reset failed for: %x\r\n", mask); return SDRV_STATUS_TIMEOUT; } timeout--; udelay(1); } while (1); return SDRV_STATUS_OK; } /* * Function: sdhci error status enable * Arg : Host structure * Return : None * Flow: : Enable command error status */ static void sdrv_sdhci_error_status_enable(struct sdhci_host *host) { /* clear all interrupt status */ REG_WRITE16(host, SDHCI_NRML_INT_STS_EN, SDHCI_NRML_INT_STS_REG); REG_WRITE16(host, SDHCI_ERR_INT_STS_EN, SDHCI_ERR_INT_STS_REG); /* Enable all interrupt status */ REG_WRITE16(host, SDHCI_NRML_INT_STS_EN, SDHCI_NRML_INT_STS_EN_REG); REG_WRITE16(host, SDHCI_ERR_INT_STS_EN, SDHCI_ERR_INT_STS_EN_REG); /* Enable all interrupt signal */ REG_WRITE16(host, SDHCI_NRML_INT_SIG_EN, SDHCI_NRML_INT_SIG_EN_REG); REG_WRITE16(host, SDHCI_ERR_INT_SIG_EN, SDHCI_ERR_INT_SIG_EN_REG); } /* * Function: sdhci set bus power * Arg : Host structure * Return : None * Flow: : 1. Set the voltage * 2. Set the sd power control register */ static void sdrv_sdhci_set_bus_power_on(struct sdhci_host *host) { uint16_t ctrl; uint8_t voltage; voltage = host->caps.voltage; voltage <<= SDHCI_BUS_VOL_SEL; REG_WRITE8(host, voltage, SDHCI_PWR_CTRL_REG); voltage |= SDHCI_BUS_PWR_EN; REG_WRITE8(host, voltage, SDHCI_PWR_CTRL_REG); /* switch signal voltage by host ctrl2 register */ ctrl = REG_READ16(host, SDHCI_HOST_CTRL2_REG); if (host->caps.voltage < SDHCI_VOL_3_3) { ctrl |= SDHCI_1_8_VOL_SET; } else { ctrl &= ~SDHCI_1_8_VOL_SET; } REG_WRITE16(host, ctrl, SDHCI_HOST_CTRL2_REG); DETAIL_PRINTF(SSDK_DEBUG, "\r\n %s: voltage: 0x%02x; host ctrl2: 0x%04x\r\n", __func__, voltage, ctrl); } /* * Function: sdhci set uhs mode, needs to be used together with set clock api * Arg : Host structure, UHS mode * Return : None * Flow: : 1. Disable the clock * 2. Enable UHS mode * 3. Enable the clock * Details : */ void sdrv_sdhci_set_uhs_mode(struct sdhci_host *host, uint32_t mode) { uint8_t ctrl = 0; uint16_t ctrl2 = 0; uint16_t clk = 0; /* Disable the card clock */ clk = REG_READ16(host, SDHCI_CLK_CTRL_REG); clk &= ~SDHCI_CLK_EN; REG_WRITE16(host, clk, SDHCI_CLK_CTRL_REG); ctrl = REG_READ8(host, SDHCI_HOST_CTRL1_REG); if (mode > SDHCI_UHS_SDR12_MODE) ctrl |= SDHCI_HIGH_SPEED_EN; else ctrl &= ~SDHCI_HIGH_SPEED_EN; REG_WRITE8(host, ctrl, SDHCI_HOST_CTRL1_REG); /* sets the uhs mode */ ctrl2 = REG_READ16(host, SDHCI_HOST_CTRL2_REG); ctrl2 &= ~SDHCI_UHS_MODE_MASK; ctrl2 |= mode & SDHCI_UHS_MODE_MASK; REG_WRITE16(host, ctrl2, SDHCI_HOST_CTRL2_REG); } /* * Function: sdhci set adma mode * Arg : Host structure * Return : None * Flow: : Set adma mode */ static void sdrv_sdhci_set_adma_mode(struct sdhci_host *host) { uint8_t ctrl1; uint16_t ctrl2; ctrl1 = REG_READ8(host, SDHCI_HOST_CTRL1_REG); ctrl1 &= ~SDHCI_ADMA_SEL_MASK; if (host->caps.spec_version >= SDHCI_SPEC_VER4_NUM) { ctrl2 = REG_READ16(host, SDHCI_HOST_CTRL2_REG); ctrl2 |= SDHCI_VER4_ENABLE; ctrl2 &= ~SDHCI_ADMA_64BIT_V4; #ifdef SDHCI_DESC_ADDR_64BITS if (host->caps.addr_64bit_v4) { ctrl2 |= SDHCI_ADMA_64BIT_V4; } #endif ctrl2 |= SDHCI_ADMA2_26BIT_LEN_MODE; REG_WRITE16(host, ctrl2, SDHCI_HOST_CTRL2_REG); /* Select ADMA2 type */ ctrl1 |= SDHCI_ADMA2_SEL; } else { #ifdef SDHCI_DESC_ADDR_64BITS if (host->caps.addr_64bit_v3) /* Select ADMA2 type, 64bit address */ ctrl1 |= SDHCI_ADMA_64BIT; else #else ctrl1 |= SDHCI_ADMA_32BIT; #endif } REG_WRITE8(host, ctrl1, SDHCI_HOST_CTRL1_REG); } /* * Function: sdhci set bus width * Arg : Host & width * Return : 0 on Sucess, 1 on Failure * Flow: : Set the bus width for controller */ status_t sdrv_sdhci_set_bus_width(struct sdhci_host *host, uint16_t width) { uint16_t reg = 0; reg = REG_READ8(host, SDHCI_HOST_CTRL1_REG); switch (width) { case MMC_DATA_BUS_WIDTH_8BIT: width = SDHCI_BUS_WITDH_8BIT; break; case MMC_DATA_BUS_WIDTH_4BIT: width = SDHCI_BUS_WITDH_4BIT; break; case MMC_DATA_BUS_WIDTH_1BIT: width = SDHCI_BUS_WITDH_1BIT; break; default: DETAIL_PRINTF(SSDK_CRIT, "Bus width is invalid: %u\r\n", width); return SDRV_STATUS_INVALID_PARAM; } DETAIL_PRINTF(SSDK_INFO, "\r\n %s: bus width:0x%04x\r\n", __func__, width); host->bus_width = width; REG_WRITE8(host, (reg | width), SDHCI_HOST_CTRL1_REG); return SDRV_STATUS_OK; } /* * Function: sdhci command err status * Arg : Host structure * Return : 0 on Sucess, 1 on Failure * Flow: : Look for error status */ static status_t sdrv_sdhci_cmd_err_status(struct sdhci_host *host) { status_t ret = SDRV_STATUS_OK; uint32_t err; err = REG_READ16(host, SDHCI_ERR_INT_STS_REG); if (err & SDHCI_CMD_TIMEOUT_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: Command timeout error\r\n"); ret = SDRV_STATUS_MMC_CMD_ERR; } else if (err & SDHCI_CMD_CRC_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: Command CRC error\r\n"); ret = SDRV_STATUS_MMC_CMD_ERR; } else if (err & SDHCI_CMD_END_BIT_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: CMD end bit error\r\n"); ret = SDRV_STATUS_MMC_CMD_ERR; } else if (err & SDHCI_CMD_IDX_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: Command Index error\r\n"); ret = SDRV_STATUS_MMC_CMD_ERR; } else if (err & SDHCI_DAT_TIMEOUT_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: DATA time out error\r\n"); ret = SDRV_STATUS_MMC_DAT_ERR; } else if (err & SDHCI_DAT_CRC_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: DATA CRC error\r\n"); ret = SDRV_STATUS_MMC_DAT_ERR; } else if (err & SDHCI_DAT_END_BIT_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: DATA end bit error\r\n"); ret = SDRV_STATUS_MMC_DAT_ERR; } else if (err & SDHCI_CUR_LIM_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: Current limit error\r\n"); ret = SDRV_STATUS_MMC_MISC_ERR; } else if (err & SDHCI_AUTO_CMD12_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: Auto CMD12 error\r\n"); ret = SDRV_STATUS_MMC_MISC_ERR; } else if (err & SDHCI_ADMA_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: ADMA error\r\n"); ret = SDRV_STATUS_MMC_MISC_ERR; } return ret; } static void sdrv_sdhci_read_block_pio(struct sdhci_host *host, uint32_t num_blocks) { size_t blksize, len, chunk; uint32_t scratch; uint8_t *buf; struct mmc_command *cmd = &host->data_cmd; DETAIL_PRINTF(SSDK_DEBUG, "PIO reading\r\n"); if (cmd->data.blk_sz) { blksize = cmd->data.blk_sz; } else { blksize = SDHCI_MMC_BLK_SZ; } /* address offset */ buf = (uint8_t *)cmd->data.data_ptr + (num_blocks * blksize); len = blksize; chunk = 0; DETAIL_PRINTF(SSDK_DEBUG, "PIO reading len = %d ptr = %p\r\n", len, buf); while (len) { if (chunk == 0) { scratch = REG_READ32(host, SDHCI_BUF_DATA); chunk = 4; } *buf = scratch & 0xFF; buf++; scratch >>= 8; chunk--; len--; } } static void sdrv_sdhci_write_block_pio(struct sdhci_host *host, uint32_t num_blocks) { size_t blksize, len, chunk; uint32_t scratch; uint8_t *buf; struct mmc_command *cmd = &host->data_cmd; DETAIL_PRINTF(SSDK_DEBUG, "PIO writing\r\n"); if (cmd->data.blk_sz) { blksize = cmd->data.blk_sz; } else { blksize = SDHCI_MMC_BLK_SZ; } /* address offset */ buf = (uint8_t *)cmd->data.data_ptr + (num_blocks * blksize); len = blksize; chunk = 0; scratch = 0; DETAIL_PRINTF(SSDK_DEBUG, "PIO writing len = %d ptr = %p\r\n", len, buf); while (len) { scratch |= (uint32_t) * buf << (chunk * 8); buf++; chunk++; len--; if ((chunk == 4) || ((len == 0) && (blksize == 0))) { REG_WRITE32(host, scratch, SDHCI_BUF_DATA); chunk = 0; scratch = 0; } } } __UNUSED static void sdrv_sdhci_transfer_pio(struct sdhci_host *host) { uint32_t mask; uint32_t num_blocks = 0; uint32_t trans_retry = 0; if (host->data_cmd.data.num_blocks == 0) return; if (host->data_cmd.trans_mode == SDHCI_MMC_READ) mask = 0x800; else mask = 0x400; while (host->data_cmd.data.num_blocks) { if (REG_READ32(host, SDHCI_PRESENT_STATE_REG) & mask) { if (host->data_cmd.trans_mode == SDHCI_MMC_READ) sdrv_sdhci_read_block_pio(host, num_blocks); else sdrv_sdhci_write_block_pio(host, num_blocks); host->data_cmd.data.num_blocks--; trans_retry = 0; num_blocks++; } else { trans_retry++; if (SDHCI_MAX_TRANS_RETRY == trans_retry) { DETAIL_PRINTF(SSDK_CRIT, "PIO transfer timeout.\r\n"); return; } } } DETAIL_PRINTF(SSDK_DEBUG, "PIO transfer complete.\r\n"); } /* * Function: sdhci command complete * Arg : Host & command structure * Return : 0 on Sucess, 1 on Failure * Flow: : 1. Check for command complete * 2. Check for transfer complete * 3. Get the command response * 4. Check for errors & error recovery */ __UNUSED static status_t sdrv_sdhci_cmd_complete(struct sdhci_host *host, struct mmc_command *cmd) { uint8_t i; status_t ret = SDRV_STATUS_OK; uint64_t retry = 0; uint32_t int_status; uint32_t trans_complete = 0; uint32_t err_status; uint64_t max_trans_retry = (cmd->cmd_timeout ? cmd->cmd_timeout : SDHCI_MAX_TRANS_RETRY); do { int_status = REG_READ16(host, SDHCI_NRML_INT_STS_REG); /* command completion, out of the loop */ if ((int_status & SDHCI_INT_STS_CMD_COMPLETE) && !(int_status & SDHCI_ERR_INT_STAT_MASK)) { break; } /* command error, enter error handling */ else if (int_status & SDHCI_ERR_INT_STAT_MASK && !(host->tuning_in_progress || host->sw_tuning_in_progress)) { goto err; } /* * If Tuning is in progress ignore cmd crc, cmd timeout & cmd end bit * errors */ if (host->tuning_in_progress || host->sw_tuning_in_progress) { err_status = REG_READ16(host, SDHCI_ERR_INT_STS_REG); if ((err_status & SDHCI_CMD_CRC_MASK) || (err_status & SDHCI_CMD_END_BIT_MASK) || err_status & SDHCI_CMD_TIMEOUT_MASK) { sdrv_sdhci_reset(host, (SOFT_RESET_CMD | SOFT_RESET_DATA)); return SDRV_STATUS_OK; } /* * According sdhci v4.2 spec, * the tuning cmd is not need wait for cmd complete. */ if (host->tuning_in_progress) return SDRV_STATUS_OK; } retry++; udelay(1); if (retry == SDHCI_MAX_CMD_RETRY) { DETAIL_PRINTF(SSDK_CRIT, "Error: Command never completed, reg = 0x%08x\r\n", int_status); sdrv_sdhci_dumpregs(host); ret = SDRV_STATUS_MMC_CMD_ERR; cmd->error |= SDHCI_CMD_SW_TIMEOUT_MASK; goto err; } } while (1); /* Command is complete, clear the interrupt bit */ REG_WRITE16(host, SDHCI_INT_STS_CMD_COMPLETE, SDHCI_NRML_INT_STS_REG); /* Copy the command response, * The valid bits for R2 response are 0-119, & but the actual response * is stored in bits 8-128. We need to move 8 bits of MSB of each * response to register 8 bits of LSB of next response register. * As: * MSB 8 bits of RESP0 --> LSB 8 bits of RESP1 * MSB 8 bits of RESP1 --> LSB 8 bits of RESP2 * MSB 8 bits of RESP2 --> LSB 8 bits of RESP3 */ if (cmd->resp_type == SDHCI_CMD_RESP_R2) { for (i = 0; i < 4; i++) { cmd->resp[i] = REG_READ32(host, SDHCI_RESP_REG + (i * 4)); cmd->resp[i] <<= SDHCI_RESP_LSHIFT; if (i != 0) cmd->resp[i] |= (REG_READ32(host, SDHCI_RESP_REG + ((i - 1) * 4)) >> SDHCI_RESP_RSHIFT); } } else { cmd->resp[0] = REG_READ32(host, SDHCI_RESP_REG); } retry = 0; /* * Clear the transfer complete interrupt */ if (cmd->data_present || cmd->resp_type == SDHCI_CMD_RESP_R1B) { do { int_status = REG_READ16(host, SDHCI_NRML_INT_STS_REG); /* PIO mode to read or write data */ #ifndef SDHCI_TRANS_USE_ADMA if (host->cmd->data_present) { if (int_status & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)) { sdrv_sdhci_transfer_pio(host); REG_WRITE16(host, int_status & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL), SDHCI_NRML_INT_STS_REG); } } #endif /* The transmission is complete and the loop is out */ if ((int_status & SDHCI_INT_STS_TRANS_COMPLETE) && !(int_status & SDHCI_ERR_INT_STAT_MASK) && ((host->ops->mmc_ending_trans) ? (host->ops->mmc_ending_trans( host)) : 0x1)) { /* here will failed*/ trans_complete = 1; break; } /* * Some controllers set the data timout first on issuing an erase & * take time to set data complete interrupt. We need to wait hoping * the controller would set data complete */ else if (int_status & SDHCI_ERR_INT_STAT_MASK && !(host->tuning_in_progress || host->sw_tuning_in_progress) && !((REG_READ16(host, SDHCI_ERR_INT_STS_REG) & SDHCI_DAT_TIMEOUT_MASK))) { goto err; } /* * If we are in tuning then we need to wait until Data timeout , * Data end or Data CRC error */ if (host->tuning_in_progress || host->sw_tuning_in_progress) { err_status = REG_READ16(host, SDHCI_ERR_INT_STS_REG); if ((err_status & SDHCI_DAT_TIMEOUT_MASK) || (err_status & SDHCI_DAT_CRC_MASK)) { sdrv_sdhci_reset(host, (SOFT_RESET_CMD | SOFT_RESET_DATA)); return SDRV_STATUS_OK; } } retry++; udelay(1); if (retry == max_trans_retry) { DETAIL_PRINTF(SSDK_CRIT, "Error: Transfer never completed\r\n"); sdrv_sdhci_dumpregs(host); ret = SDRV_STATUS_MMC_DAT_ERR; cmd->error |= SDHCI_DAT_SW_TIMEOUT_MASK; goto err; } } while (1); /* Transfer is complete, clear the interrupt bit */ REG_WRITE16(host, SDHCI_INT_STS_TRANS_COMPLETE, SDHCI_NRML_INT_STS_REG); } err: /* Look for errors */ int_status = REG_READ16(host, SDHCI_NRML_INT_STS_REG); err_status = REG_READ16(host, SDHCI_ERR_INT_STS_REG); if (int_status & SDHCI_ERR_INT_STAT_MASK) { /* * As per SDHC spec transfer complete has higher priority than data * timeout If both transfer complete & data timeout are set then we * should ignore data timeout error. * -------------------------------------------------------------------- * | Transfer complete | Data timeout | Meaning of the Status | * |-------------------------------------------------------------------| * | 0 | 0 | Interrupted by another factor | * |-------------------------------------------------------------------| * | 0 | 1 | Timeout occured during transfer| * |-------------------------------------------------------------------| * | 1 | Don't Care | Command execution complete | * ------------------------------------------------------------------- */ if ((err_status & SDHCI_DAT_TIMEOUT_MASK) && trans_complete) { ret = SDRV_STATUS_OK; REG_WRITE16(host, err_status, SDHCI_ERR_INT_STS_REG); } else if ((ret = sdrv_sdhci_cmd_err_status(host)) && (ret != SDRV_STATUS_OK)) { cmd->error |= err_status; /* Dump sdhc registers on error */ sdrv_sdhci_dumpregs(host); REG_WRITE16(host, err_status, SDHCI_ERR_INT_STS_REG); } } return ret; } static void sdrv_sdhci_adma_write_desc(struct sdhci_host *host, void **desc, addr_t addr, int len, unsigned int attr) { struct desc_entry *dma_desc = *desc; #ifdef SDHCI_DESC_ADDR_64BITS dma_desc->addr_l = lower_32_bits((addr_t)addr); dma_desc->addr_h = upper_32_bits((addr_t)addr); dma_desc->tran_att = attr | FV_ADMA2_ATTR_LEN(len); #else dma_desc->addr = lower_32_bits((addr_t)addr); dma_desc->tran_att = attr | FV_ADMA2_ATTR_LEN(len); #endif *(struct desc_entry **)desc += 1u; } /* * Function: sdhci prep desc table * Arg : Pointer data & length * Return : Pointer to desc table * Flow: : Prepare the adma table as per the sd spec v 4.0 */ static struct desc_entry *sdrv_sdhci_prep_desc_table(struct sdhci_host *host, paddr_t data, uint32_t len) { uint32_t sg_len = 0; uint32_t remain = 0; uint32_t i; uint32_t table_len = 0; uint32_t desc_len_max = 0; uint32_t tran_len = 0; uint32_t adma_attr = 0; struct desc_entry *sg_list = NULL; struct desc_entry *desc = NULL; if (host->caps.addr_64bit_v4) { desc_len_max = SDHCI_ADMA2_DESC_LINE_SZ_V4; } else { desc_len_max = SDHCI_ADMA_DESC_LINE_SZ; } /* Calculate the number of entries in desc table */ sg_len = len / desc_len_max; remain = len - (sg_len * desc_len_max); /* Allocate sg_len + 1 entries if there are remaining bytes at the end */ if (remain) { sg_len++; } if (sg_len > (ADMA_DESC_LIST_SIZE - 1)) { DETAIL_PRINTF(SSDK_CRIT, "Out of ADMA DESC address range\r\n"); return NULL; } /* For workaround the snps adma boundary, alloc one more desc entry */ /* across adma boundary, alloc one more desc entry */ table_len = ((sg_len + 1) * sizeof(struct desc_entry)); sg_list = g_dma_sg_list; if (!sg_list) { DETAIL_PRINTF(SSDK_CRIT, "Error allocating memory\r\n"); return NULL; } memset(sg_list, 0, table_len); desc = sg_list; for (i = 0; i < sg_len; i++) { tran_len = MIN(len, desc_len_max); adma_attr = SDHCI_ADMA_DATA_VALID; sdrv_sdhci_adma_write_desc(host, (void **)&desc, data, tran_len, adma_attr); data += tran_len; len -= tran_len; } /* Add the last entry - nop, end, valid */ adma_attr = SDHCI_ADMA_NOP_END_VALID; sdrv_sdhci_adma_write_desc(host, (void **)&desc, 0, 0, adma_attr); arch_clean_invalidate_cache_range((addr_t)sg_list, ROUNDUP(table_len, CONFIG_ARCH_CACHE_LINE)); for (i = 0; i < sg_len + 1; i++) { #ifdef SDHCI_DESC_ADDR_64BITS DETAIL_PRINTF(SSDK_DEBUG, "\r\n %s: sg_list %p: addr_l: 0x%x addr_h: 0x%x attr: 0x%x\r\n", __func__, &sg_list[i], sg_list[i].addr_l, sg_list[i].addr_h, sg_list[i].tran_att); #else DETAIL_PRINTF(SSDK_DEBUG, "\r\n %s: sg_list %p: addr: 0x%x attr: 0x%x\r\n", __func__, &sg_list[i], sg_list[i].addr, sg_list[i].tran_att); #endif } return sg_list; } /* * Function: sdhci adma transfer * Arg : Host structure & command stucture * Return : Pointer to desc table * Flow : 1. Prepare descriptor table * 2. Write adma register * 3. Write block size & block count register */ static struct desc_entry *sdrv_sdhci_adma_transfer(struct sdhci_host *host, struct mmc_command *cmd) { uint32_t num_blks = 0; uint32_t sz; paddr_t data; struct desc_entry *adma_desc; paddr_t adma_desc_paddr; num_blks = cmd->data.num_blocks; data = (paddr_t)cmd->data.data_ptr; /* * Some commands send data on DAT lines which is less * than SDHCI_MMC_BLK_SZ, in that case trying to read * more than the data sent by the card results in data * CRC errors. To avoid such errors allow data to pass * the required block size, if the block size is not * passed use the default value */ if (cmd->data.blk_sz) { sz = num_blks * cmd->data.blk_sz; } else { sz = num_blks * SDHCI_MMC_BLK_SZ; } /* Prepare adma descriptor table */ adma_desc = sdrv_sdhci_prep_desc_table(host, data, sz); if (NULL == adma_desc) { DETAIL_PRINTF(SSDK_CRIT, "sdrv_sdhci_prep_desc_table desc error \r\n"); return NULL; } adma_desc_paddr = (paddr_t)adma_desc; /* Write adma address to adma register */ REG_WRITE32(host, (uint32_t)adma_desc_paddr, SDHCI_ADM_ADDR_REG); REG_WRITE32(host, upper_32_bits(adma_desc_paddr), SDHCI_ADM_ADDR_REG + 0x4); /* Write the block size */ if (cmd->data.blk_sz) { REG_WRITE16(host, cmd->data.blk_sz, SDHCI_BLKSZ_REG); } else { REG_WRITE16(host, SDHCI_MMC_BLK_SZ, SDHCI_BLKSZ_REG); } return adma_desc; } static void sdrv_sdhci_pre_xfer(struct sdhci_host *host) { REG_WRITE16(host, 0xFFFF, SDHCI_NRML_INT_STS_REG); REG_WRITE16(host, 0xFFFF, SDHCI_ERR_INT_STS_REG); } static inline bool sdrv_sdhci_data_line_cmd(struct mmc_command *cmd) { return cmd->data_present || cmd->resp_type & SDHCI_CMD_RESP_R1B; } __UNUSED static inline uint16_t sdrv_sdhci_cale_timeout(uint64_t us, uint32_t freq) { uint16_t i = 0; uint64_t t; for (i = 0; i < 0xF; i++) { t = 1 << (13 + i); if (t / freq * 1000 * 1000 > us) { break; } } return MIN(i, 0xE); } /* * Function: actual sdhci send command * Arg : Host structure & command stucture * Return : 0 on Success, 1 on Failure * Flow: : 1. Prepare the command register * 2. If data is present, prepare adma table * 3. Run the command * 4. Check for command results & take action */ static status_t sdrv_sdhci_trans_command(struct sdhci_host *host, struct mmc_command *cmd) { status_t ret = SDRV_STATUS_OK; uint32_t retry = 0; uint32_t resp_type = 0; uint32_t cmd_int = 0; uint16_t trans_mode = 0; uint16_t present_state; uint32_t flags; uint16_t mask; uint8_t timeout; DETAIL_PRINTF(SSDK_DEBUG, "\r\n %s: START: cmd:%04d, arg:0x%08x, resp_type:0x%04x, " "data_present:%d\r\n", __func__, cmd->cmd_index, cmd->argument, cmd->resp_type, cmd->data_present); cmd->error = 0; if (cmd->data_present) { if (!cmd->data.data_ptr) return SDRV_STATUS_INVALID_PARAM; if (!IS_ARCH_CACHE_LINE_ALIGNED(cmd->data.data_ptr)) return SDRV_STATUS_INVALID_PARAM; } do { mask = SDHCI_STATE_CMD_MASK; if (sdrv_sdhci_data_line_cmd(cmd)) { mask |= SDHCI_STATE_DAT_MASK; } present_state = REG_READ32(host, SDHCI_PRESENT_STATE_REG); /* check if CMD & DAT lines are free */ present_state &= mask; if (!present_state) { break; } udelay(1); retry++; if (retry == 1000) { DETAIL_PRINTF(SSDK_CRIT, "Error: CMD or DAT lines were never freed\r\n"); return SDRV_STATUS_BUSY; } } while (1); host->cmd = cmd; sdrv_sdhci_pre_xfer(host); switch (cmd->resp_type) { case SDHCI_CMD_RESP_R1: case SDHCI_CMD_RESP_R3: case SDHCI_CMD_RESP_R6: case SDHCI_CMD_RESP_R7: /* Response of length 48 have 32 bits * of response data stored in RESP0[0:31] */ resp_type = SDHCI_CMD_RESP_48; break; case SDHCI_CMD_RESP_R2: /* Response of length 136 have 120 bits * of response data stored in RESP0[0:119] */ resp_type = SDHCI_CMD_RESP_136; break; case SDHCI_CMD_RESP_R1B: /* Response of length 48 have 32 bits * of response data stored in RESP0[0:31] * & set CARD_BUSY status if card is busy */ resp_type = SDHCI_CMD_RESP_48_BUSY; break; case SDHCI_CMD_RESP_NONE: resp_type = SDHCI_CMD_RESP_NONE; break; default: DETAIL_PRINTF(SSDK_CRIT, "Invalid response type for the command\r\n"); return SDRV_STATUS_INVALID_PARAM; }; flags = (resp_type << SDHCI_CMD_RESP_TYPE_SEL_BIT); if (cmd->data_present || cmd->cmd_index == CMD21_SEND_TUNING_BLOCK) flags |= (1 << SDHCI_CMD_DATA_PRESENT_BIT); flags |= (cmd->cmd_type << SDHCI_CMD_CMD_TYPE_BIT); /* Enable Command CRC & Index check for commands with response * R1, R6, R7 & R1B. Also only CRC check for R2 response */ switch (cmd->resp_type) { case SDHCI_CMD_RESP_R1: case SDHCI_CMD_RESP_R6: case SDHCI_CMD_RESP_R7: case SDHCI_CMD_RESP_R1B: flags |= (1 << SDHCI_CMD_CRC_CHECK_BIT) | (1 << SDHCI_CMD_IDX_CHECK_BIT); break; case SDHCI_CMD_RESP_R2: flags |= (1 << SDHCI_CMD_CRC_CHECK_BIT); break; default: break; }; /* Set the timeout value */ #ifdef CONFIG_SDHCI_SUPPORT_INTERRUPT if (cmd->cmd_timeout) { timeout = sdrv_sdhci_cale_timeout(cmd->cmd_timeout, SDHCI_TOUT_TIMER_CLK); } else { timeout = SDHCI_CMD_TIMEOUT; } #else timeout = 0xE; #endif REG_WRITE8(host, timeout, SDHCI_TIMEOUT_REG); /* read or write cmd timeout is hw NAC timeout * sw cmd_timeout is complete read and write process time * use the default configured time */ if (MMC_CMD_READ(cmd) || MMC_CMD_WRITE(cmd)) { cmd->cmd_timeout = 0; } /* Check if data needs to be processed */ if (cmd->data_present) { #ifdef SDHCI_TRANS_USE_ADMA /* * clean any stale cache lines ensure cpu data buffer flush in memory, * and forbid cpu write back cache on unpredictable time. */ arch_clean_invalidate_cache_range( (addr_t)cmd->data.data_ptr, (cmd->data.blk_sz) ? (cmd->data.num_blocks * cmd->data.blk_sz) : (cmd->data.num_blocks * SDHCI_MMC_BLK_SZ)); cmd->data.sg_list = sdrv_sdhci_adma_transfer(host, cmd); if (NULL == cmd->data.sg_list) return SDRV_STATUS_FAIL; #else /* Write the block size */ if (cmd->data.blk_sz) { REG_WRITE16(host, cmd->data.blk_sz, SDHCI_BLKSZ_REG); } else { REG_WRITE16(host, SDHCI_MMC_BLK_SZ, SDHCI_BLKSZ_REG); } #endif memcpy(&host->data_cmd, cmd, sizeof(struct mmc_command)); } /* Write the argument 1 */ REG_WRITE32(host, cmd->argument, SDHCI_ARGUMENT_REG); if (cmd->trans_mode == SDHCI_MMC_READ) trans_mode |= SDHCI_READ_MODE; /* Set the Transfer mode */ if (cmd->data_present) { #ifdef SDHCI_TRANS_USE_ADMA /* Enable DMA */ trans_mode |= SDHCI_DMA_EN; #endif /* * Enable auto cmd23 or cmd12 for multi block transfer * based on what command card supports */ if (cmd->data.num_blocks > 1) { if (cmd->cmd23_support) { trans_mode |= SDHCI_TRANS_MULTI | SDHCI_AUTO_CMD23_EN | SDHCI_BLK_CNT_EN; } else { trans_mode |= SDHCI_TRANS_MULTI | SDHCI_AUTO_CMD12_EN | SDHCI_BLK_CNT_EN; } /* * Set block count in block count register */ if ((host->caps.spec_version >= SDHCI_SPEC_VER4_NUM) && (REG_READ16(host, SDHCI_HOST_CTRL2_REG) & SDHCI_VER4_ENABLE)) { REG_WRITE16(host, 0, SDHCI_BLK_CNT_REG); REG_WRITE32(host, cmd->data.num_blocks, SDHCI_SDMASA_BLKCNT_REG); } else { REG_WRITE16(host, cmd->data.num_blocks, SDHCI_BLK_CNT_REG); } } } /* Write to transfer mode register */ REG_WRITE16(host, trans_mode, SDHCI_TRANS_MODE_REG); /* Write the command register */ REG_WRITE16(host, SDHCI_PREP_CMD(cmd->cmd_index, flags), SDHCI_CMD_REG); #ifdef CONFIG_SDHCI_SUPPORT_INTERRUPT if (!host->tuning_in_progress && (xSemaphoreTake(host->cmd_sema, (TickType_t)SDHCI_CMD_SW_TIMEOUT) == pdFALSE) ) { if (!host->sw_tuning_in_progress) { DETAIL_PRINTF(SSDK_CRIT, "cmd_sema %d timeout\r\n", SDHCI_CMD_SW_TIMEOUT); sdrv_sdhci_dumpregs(host); } host->cmd->error |= SDHCI_CMD_SW_TIMEOUT_MASK; } cmd_int |= host->cmd->error; if (cmd_int) { ret = SDRV_STATUS_MMC_CMD_ERR; goto cmd_out; } /* * If has data and sync mode, or cmd has busy status bit, * need wait for data complete signal. */ if ((host->data_cmd.data_present && !host->async_mode) || cmd->resp_type == SDHCI_CMD_RESP_R1B) { TickType_t data_timeout = (cmd->cmd_timeout ? (cmd->cmd_timeout / 1000) : SDHCI_DATA_SW_TIMEOUT); if (!host->tuning_in_progress && (xSemaphoreTake(host->data_complete_sema, (TickType_t)data_timeout) == pdFALSE) ) { if (!host->sw_tuning_in_progress) { DETAIL_PRINTF(SSDK_CRIT, "data_complete_sema %d timeout\r\n", SDHCI_DATA_SW_TIMEOUT); sdrv_sdhci_dumpregs(host); } if (host->data_cmd.data_present) { host->data_cmd.error |= SDHCI_DAT_SW_TIMEOUT_MASK; } else { host->cmd->error |= SDHCI_DAT_SW_TIMEOUT_MASK; } } if (host->data_cmd.data_present) { cmd_int |= host->data_cmd.error; host->data_cmd.data_present = 0; } else { cmd_int |= host->cmd->error; } if (cmd_int) ret = SDRV_STATUS_MMC_DAT_ERR; } #else /* Command complete sequence */ ret = sdrv_sdhci_cmd_complete(host, cmd); if (ret) { cmd_int |= cmd->error; goto cmd_out; } #ifdef SDHCI_TRANS_USE_ADMA /* Invalidate the cache only for read operations */ if (cmd->trans_mode == SDHCI_MMC_READ) { /* Read can be performed on block size < SDHCI_MMC_BLK_SZ, make sure to * flush the data only for the read size instead */ arch_invalidate_cache_range( (addr_t)cmd->data.data_ptr, (cmd->data.blk_sz) ? (cmd->data.num_blocks * cmd->data.blk_sz) : (cmd->data.num_blocks * SDHCI_MMC_BLK_SZ)); } #endif #endif cmd_out: if (host->cmd->error && host->data_cmd.data_present) { host->data_cmd.data_present = 0; } host->cmd = NULL; if (cmd_int) { sdrv_sdhci_reset(host, SOFT_RESET_CMD | SOFT_RESET_DATA); } DETAIL_PRINTF(SSDK_DEBUG, "\r\n %s: END: host:0x%lx cmd:%04d, arg:0x%08x, resp:0x%08x 0x%08x " "0x%08x 0x%08x\r\n", __func__, (unsigned long)host->base, cmd->cmd_index, cmd->argument, cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]); return ret; } /* * Function: sdhci cmd abort for error recovery * Arg : Host & command structure * Return : None * Flow: : 1. Send abort command, the cmd type must be abort type */ static status_t sdrv_sdhci_cmd_abort(struct sdhci_host *host) { struct mmc_device *dev = NULL; uint32_t present_status; uint32_t inhibit_count = SDHCI_INHIBIT_COUNT; struct mmc_command cmd; memset(&cmd, 0, sizeof(struct mmc_command)); if (host->parent) { dev = host->parent; } else { return SDRV_STATUS_FAIL; } /* Send CMD12 for abort transfer*/ if (dev->host.card_type == SDHCI_SDIO_CARD) cmd.cmd_index = CMD52_SDIO_STOP_TRANSMISSION; else cmd.cmd_index = CMD12_STOP_TRANSMISSION; cmd.argument = (dev->card.rca << 16) | BIT(0); cmd.cmd_type = SDHCI_CMD_TYPE_ABORT; cmd.resp_type = SDHCI_CMD_RESP_R1; sdrv_sdhci_trans_command(host, &cmd); do { present_status = REG_READ32(host, SDHCI_PRESENT_STATE_REG); if (!(present_status & (SDHCI_PSTATE_CMD_INHIBIT_DAT_MASK | SDHCI_PSTATE_CMD_INHIBIT_MASK))) break; inhibit_count--; if (!inhibit_count) { DETAIL_PRINTF(SSDK_CRIT, "Error: cmd or data inhibit %x\r\n", present_status); return SDRV_STATUS_TIMEOUT; } } while (inhibit_count); if (cmd.error & SDHCI_CMD_ERR_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: CMD ABORT cmd lines error occur %x\r\n", cmd.error); } if (cmd.error & SDHCI_DAT_TIMEOUT_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: CMD ABORT data lines timeout occur\r\n", cmd.error); } /* wait for more than 40us */ udelay(50); present_status = REG_READ32(host, SDHCI_PRESENT_STATE_REG); present_status &= SDHCI_PRESENT_STATE_DAT_MASK; if (host->bus_width == MMC_BUS_WIDTH_1BIT && present_status != SDHCI_PRESENT_STATE_DAT_1BIT_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: 1BIT CMD ABORT one or more data lines low\n"); return SDRV_STATUS_MMC_DAT_ABORT; } else if (host->bus_width == MMC_BUS_WIDTH_4BIT && present_status != SDHCI_PRESENT_STATE_DAT_4BIT_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: 4BIT CMD ABORT one or more data lines low\n"); return SDRV_STATUS_MMC_DAT_ABORT; } else if (host->bus_width == MMC_BUS_WIDTH_8BIT && present_status != SDHCI_PRESENT_STATE_DAT_8BIT_MASK) { DETAIL_PRINTF(SSDK_CRIT, "Error: 8BIT CMD ABORT one or more data lines low\n"); return SDRV_STATUS_MMC_DAT_ABORT; } return SDRV_STATUS_OK; } /* * Function: sdhci send command * Arg : Host structure & command stucture * Return : 0 on Success, 1 on Failure * Flow: : 1. Prepare the command register * 2. If data is present, prepare adma table * 3. Run the command * 4. Check for command results & take action */ status_t sdrv_sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) { status_t ret = SDRV_STATUS_OK; do { ret = sdrv_sdhci_trans_command(host, cmd); if (ret) { /* Send abort cmd */ if (cmd->data_present && sdrv_sdhci_cmd_abort(host)) { goto abort_error; } sdrv_sdhci_error_status_enable(host); } if (ret && cmd->retries && !(cmd->error & SDHCI_DAT_SW_TIMEOUT_MASK)) { DETAIL_PRINTF(SSDK_CRIT, "%s: cmd retry: cmd:%04d, arg:0x%08x, resp_type:0x%04x, data_present:%d retries:%d\n", __func__, cmd->cmd_index, cmd->argument, cmd->resp_type, cmd->data_present, cmd->retries); cmd->retries--; cmd->error = 0; } else { break; } } while (cmd->retries); return ret; abort_error: sdrv_sdhci_error_status_enable(host); return ret; } void sdrv_sdhci_start_tuning(struct sdhci_host *host) { uint16_t ctrl2; ctrl2 = REG_READ16(host, SDHCI_HOST_CTRL2_REG); ctrl2 |= SDHCI_EXEC_TUNING; REG_WRITE16(host, ctrl2, SDHCI_HOST_CTRL2_REG); } void sdrv_sdhci_reset_tuning(struct sdhci_host *host) { uint16_t ctrl2; ctrl2 = REG_READ16(host, SDHCI_HOST_CTRL2_REG); ctrl2 &= ~SDHCI_EXEC_TUNING; ctrl2 &= ~SDHCI_SAMPLE_CLK_SEL; REG_WRITE16(host, ctrl2, SDHCI_HOST_CTRL2_REG); } void sdrv_sdhci_abort_tuning(struct sdhci_host *host) { uint16_t ctrl2; ctrl2 = REG_READ16(host, SDHCI_HOST_CTRL2_REG); ctrl2 &= ~SDHCI_EXEC_TUNING; ctrl2 &= ~SDHCI_SAMPLE_CLK_SEL; REG_WRITE16(host, ctrl2, SDHCI_HOST_CTRL2_REG); sdrv_sdhci_reset(host, SOFT_RESET_CMD | SOFT_RESET_DATA); } status_t sdrv_sdhci_tuning_sequence(struct sdhci_host *host, uint32_t opcode, uint32_t bus_width) { status_t ret = SDRV_STATUS_OK; uint32_t reg; struct mmc_command cmd; memset(&cmd, 0, sizeof(struct mmc_command)); cmd.cmd_index = opcode; cmd.argument = 0x0; cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL; cmd.resp_type = SDHCI_CMD_RESP_R1; cmd.trans_mode = SDHCI_MMC_READ; cmd.data_present = 0x0; cmd.data.data_ptr = NULL; cmd.data.num_blocks = 0x1; if (cmd.cmd_index == CMD21_SEND_TUNING_BLOCK && bus_width == MMC_DATA_BUS_WIDTH_8BIT) cmd.data.blk_sz = 128; else cmd.data.blk_sz = 64; REG_WRITE16(host, cmd.data.blk_sz, SDHCI_BLKSZ_REG); if (host->caps.spec_version >= SDHCI_SPEC_VER4_NUM) REG_WRITE32(host, cmd.data.num_blocks, SDHCI_SDMASA_BLKCNT_REG); else REG_WRITE16(host, cmd.data.num_blocks, SDHCI_BLK_CNT_REG); /* Write to transfer mode register */ REG_WRITE16(host, SDHCI_READ_MODE, SDHCI_TRANS_MODE_REG); for (int i = 0; i < MAX_TUNING_LOOP; i++) { sdrv_sdhci_send_command(host, &cmd); /* wait for read buf ready, timeout 50ms */ ret = sdrv_sdhci_wait_for_bit(host, SDHCI_NRML_INT_STS_REG, SDHCI_INT_DATA_AVAIL, 0, 50); if (ret) { DETAIL_PRINTF(SSDK_CRIT, "sdhci tuning timeout!\r\n"); sdrv_sdhci_dumpregs(host); return ret; } reg = REG_READ16(host, SDHCI_HOST_CTRL2_REG); if (!(reg & SDHCI_EXEC_TUNING)) { if (reg & SDHCI_SAMPLE_CLK_SEL) { DETAIL_PRINTF(SSDK_INFO, "sdhci: tuning cycle count = %d\r\n", i); if (i < SDHCI_HW_TUNING_MIN_COUNT) DETAIL_PRINTF(SSDK_CRIT, "SDHC: tuning cycle abnormal, num is %d!\r\n", i); return SDRV_STATUS_OK; } break; } } return SDRV_STATUS_MMC_TUNING_FAIL; } /* * Function: sdhci execute tuning * Arg : Host structure & tuning opcode & bus width * Return : 0 on Success, 1 on Failure * Flow: : 1. IF has platform tuning api, execute it * 2. start sdhci tuning * 3. execute sdhci tuning sequence */ status_t sdrv_sdhci_execute_tuning(struct sdhci_host *host, uint32_t opcode, uint32_t bus_width) { status_t ret = SDRV_STATUS_OK; if (host->ops->platform_execute_tuning) { return host->ops->platform_execute_tuning(host, opcode, bus_width); } /* reset the tuning circuit */ sdrv_sdhci_reset_tuning(host); /* start the tuning circuit */ sdrv_sdhci_start_tuning(host); host->tuning_in_progress = 1; udelay(1); ret = sdrv_sdhci_tuning_sequence(host, opcode, bus_width); if (ret) { DETAIL_PRINTF(SSDK_CRIT, "sdhci tuning failed, abort tuning state!\r\n"); sdrv_sdhci_abort_tuning(host); } host->tuning_in_progress = 0; return ret; } #ifdef CONFIG_SDHCI_SUPPORT_INTERRUPT static int sdrv_sdhci_irq_handler(uint32_t irq, void *arg) { uint32_t int_status = 0; uint32_t int_reg = 0; uint32_t mask; struct sdhci_host *host = arg; int max_loops = 16; struct mmc_command *cmd = host->cmd; struct mmc_command *data_cmd = &host->data_cmd; BaseType_t pxHigherPriorityTaskWoken = pdFALSE; /* check the interrupt status */ do { int_reg = REG_READ32(host, SDHCI_NRML_INT_STS_REG); int_status |= int_reg; DETAIL_PRINTF(SSDK_DEBUG, "sdhci: irq status 0x%08x\r\n", int_status); if (!int_reg) { break; } if (int_reg & SDHCI_INT_ERROR) { /* disable all interrupt singel*/ REG_WRITE16(host, 0x0, SDHCI_NRML_INT_SIG_EN_REG); REG_WRITE16(host, 0x0, SDHCI_ERR_INT_SIG_EN_REG); } /* clear the interrupts */ mask = int_reg & (SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK | SDHCI_INT_BUS_POWER); REG_WRITE32(host, mask, SDHCI_NRML_INT_STS_REG); } while (int_status & --max_loops); /* Process cmd interrupt */ if (int_status & SDHCI_INT_CMD_MASK) { /* cmd error occurred */ if (int_status & (SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX | SDHCI_INT_ACMD12ERR)) { if (host->sw_tuning_in_progress) { cmd->error = (int_status & SDHCI_INT_CMD_MASK) >> 16; } else { cmd->error = (int_status & SDHCI_INT_CMD_MASK) >> 16; DETAIL_PRINTF(SSDK_ERR, "sdhci cmd error! reg = 0x%08x\r\n", cmd->error); sdrv_sdhci_dumpregs(host); } /* get cmd response */ } else if (int_status & SDHCI_INT_RESPONSE) { if (cmd->resp_type == SDHCI_CMD_RESP_R2) { for (int i = 0; i < 4; i++) { cmd->resp[i] = REG_READ32(host, SDHCI_RESP_REG + (i * 4)); cmd->resp[i] <<= SDHCI_RESP_LSHIFT; if (i != 0) cmd->resp[i] |= (REG_READ32(host, SDHCI_RESP_REG + ((i - 1) * 4)) >> SDHCI_RESP_RSHIFT); } } else { cmd->resp[0] = REG_READ32(host, SDHCI_RESP_REG); } } /* Wake up cmd semaphore */ if (host->cmd_sema != NULL) { xSemaphoreGiveFromISR(host->cmd_sema, &pxHigherPriorityTaskWoken); if (pxHigherPriorityTaskWoken == pdTRUE) portYIELD_FROM_ISR(pxHigherPriorityTaskWoken); } /* cmd error, skip data interrupt */ if (cmd->error) goto out; } /* clear the data complete flag */ if (int_status & SDHCI_INT_DATA_MASK) { if (data_cmd->data_present) { data_cmd->error = (int_status & SDHCI_INT_DATA_MASK) >> 16; if (int_status & SDHCI_INT_DATA_END || data_cmd->error) { if (host->data_sema != NULL) { pxHigherPriorityTaskWoken = pdFALSE; xSemaphoreGiveFromISR(host->data_sema, &pxHigherPriorityTaskWoken); if (pxHigherPriorityTaskWoken == pdTRUE) portYIELD_FROM_ISR(pxHigherPriorityTaskWoken); } } if (int_status & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)) { sdrv_sdhci_transfer_pio(host); } } else { cmd->error |= (int_status & SDHCI_INT_DATA_MASK) >> 16; if (int_status & SDHCI_INT_DATA_END || cmd->error) { if (host->data_sema != NULL) { pxHigherPriorityTaskWoken = pdFALSE; xSemaphoreGiveFromISR(host->data_sema, &pxHigherPriorityTaskWoken); if (pxHigherPriorityTaskWoken == pdTRUE) portYIELD_FROM_ISR(pxHigherPriorityTaskWoken); } } } } out: return 0; } static void sdrv_sdhci_data_thread(void *arg) { struct sdhci_host *host = arg; struct mmc_command *data_cmd; struct mmc_device *dev = NULL; for (;;) { if (xSemaphoreTake(host->data_sema, portMAX_DELAY) == pdFALSE) { continue; } DETAIL_PRINTF(SSDK_DEBUG, "\r\n %s:sdhci data thread in!\r\n", __func__); if (host->parent) { dev = host->parent; } data_cmd = &host->data_cmd; /* verify that the data end trans is complete. */ if (host->ops->mmc_ending_trans && host->ops->mmc_ending_trans(host) == false) { /* start the timer, poll end trans, and wake up timer_sema when finished */ xTimerStart(host->data_timer, 0); host->data_end_trans_count = SDHCI_TRANS_COUNT_MAX; if (xSemaphoreTake(host->timer_sema, portMAX_DELAY) == pdFALSE) { if (host->data_cmd.data_present) { host->data_cmd.error |= SDHCI_DAT_SW_TIMEOUT_MASK; } else { host->cmd->error |= SDHCI_DAT_SW_TIMEOUT_MASK; } } } if (!host->data_cmd.data_present) { /* If no data cmd, may be RESP_R1B type cmd. */ if (host->cmd->error) { if (!host->sw_tuning_in_progress) { DETAIL_PRINTF(SSDK_CRIT, "sdhci busy error! 0x%08x\r\n", host->cmd->error); sdrv_sdhci_dumpregs(host); } } if (host->data_complete_sema != NULL) xSemaphoreGive(host->data_complete_sema); continue; } else { if (data_cmd->error) { if (!host->sw_tuning_in_progress) { DETAIL_PRINTF(SSDK_CRIT, "sdhci data error! 0x%08x\r\n", data_cmd->error); sdrv_sdhci_dumpregs(host); } if (host->data_complete_sema != NULL) xSemaphoreGive(host->data_complete_sema); continue; } } #ifdef SDHCI_TRANS_USE_ADMA if (data_cmd->trans_mode == SDHCI_MMC_READ) { /* * Read can be performed on block size < SDHCI_MMC_BLK_SZ, * make sure to flush the data only for the read size instead. */ arch_invalidate_cache_range( (addr_t)data_cmd->data.data_ptr, (data_cmd->data.blk_sz) ? (data_cmd->data.num_blocks * data_cmd->data.blk_sz) : (data_cmd->data.num_blocks * SDHCI_MMC_BLK_SZ)); } #endif /* Free the scatter/gather list */ if (data_cmd->data.sg_list) { data_cmd->data.sg_list = NULL; } if (dev && dev->event_handle) { data_cmd->data_present = 0; dev->opt_result = (enum mmc_opt_result)data_cmd->error; dev->event_handle(dev->opt_type, dev->opt_result); } else { DETAIL_PRINTF(SSDK_DEBUG, "\r\n %s:signal complete event!\r\n", __func__); if (host->data_complete_sema != NULL) xSemaphoreGive(host->data_complete_sema); } } } static void sdrv_sdhci_data_timer(void *parameter) { struct sdhci_host *host = pvTimerGetTimerID(parameter); /* Verify that the mmc_ending_trans function is supported */ if (NULL == host->ops->mmc_ending_trans) { DETAIL_PRINTF(SSDK_EMERG, "\r\n %s:sdhci mmc_ending_trans not define!\r\n", __func__); goto free_sema; } /* finish end trans and wake up timer_sema */ if (host->ops->mmc_ending_trans(host) == true) { goto free_sema; } else { host->data_end_trans_count--; /* polling end trans is incomplete and an error is reported. * otherwise, start the timer again */ if (host->data_end_trans_count == 0) { goto timeout_error; } else { xTimerStart(host->data_timer, 0); goto end; } } timeout_error: if (host->data_cmd.data_present) { host->data_cmd.error |= SDHCI_DAT_SW_TIMEOUT_MASK; } else { host->cmd->error |= SDHCI_DAT_SW_TIMEOUT_MASK; } free_sema: if (host->timer_sema != NULL) { xSemaphoreGive(host->timer_sema); } DETAIL_PRINTF(SSDK_DEBUG, "\r\n %s:sdhci data timer CallbackFunction counter:%d!\r\n", __func__, host->data_end_trans_count); end: return; } #endif /* * Function: sdhci init * Arg : Host structure * Return : None * Flow: : 1. Reset the controller * 2. Read the capabilities register & populate the host * controller capabilities for use by other functions * 3. Enable the power control * 4. Set initial bus width * 5. Set Adma mode * 6. Enable the error status */ void sdrv_sdhci_init(struct sdhci_host *host) { uint32_t caps[2]; uint16_t spec_version; sdrv_sdhci_reset(host, SDHCI_SOFT_RESET); spec_version = REG_READ16(host, SDHCI_SPEC_VERSION_REG); host->caps.spec_version = (uint8_t)(spec_version & 0xff); /* Read the capabilities register & store the info */ caps[0] = REG_READ32(host, SDHCI_CAPS_REG1); caps[1] = REG_READ32(host, SDHCI_CAPS_REG2); DETAIL_PRINTF(SSDK_DEBUG, "\r\n %s: Host version: %d capability: cap1:0x%08x, cap2: 0x%08x\r\n", __func__, host->caps.spec_version, caps[0], caps[1]); host->caps.base_clk_rate = (caps[0] & SDHCI_CLK_RATE_MASK) >> SDHCI_CLK_RATE_BIT; host->caps.base_clk_rate *= 1000000; /* Get the max block length for mmc */ host->caps.max_blk_len = (caps[0] & SDHCI_BLK_LEN_MASK) >> SDHCI_BLK_LEN_BIT; /* 8 bit Bus width */ if (caps[0] & SDHCI_8BIT_WIDTH_MASK) { host->caps.bus_width_8bit = 1; } /* Adma2 support */ if (caps[0] & SDHCI_BLK_ADMA_MASK) { host->caps.adma2_support = 1; } /* V4 64bit address support */ if (caps[0] & SDHCI_CAP_ADDR_64BIT_V4) { host->caps.addr_64bit_v4 = 1; } /* V3 64bit address support */ if (caps[0] & SDHCI_CAP_ADDR_64BIT_V3) { host->caps.addr_64bit_v3 = 1; } /* Supported voltage, do nothing when it has been set */ if (host->caps.voltage) { ; } else if (caps[0] & SDHCI_3_3_VOL_MASK) { host->caps.voltage = SDHCI_VOL_3_3; } else if (caps[0] & SDHCI_3_0_VOL_MASK) { host->caps.voltage = SDHCI_VOL_3_0; } else if (caps[0] & SDHCI_1_8_VOL_MASK) { host->caps.voltage = SDHCI_VOL_1_8; } /* DDR mode support */ host->caps.ddr_support = (caps[1] & SDHCI_DDR50_MODE_MASK) ? 1 : 0; /* SDR50 mode support */ host->caps.sdr50_support = (caps[1] & SDHCI_SDR50_MODE_MASK) ? 1 : 0; /* SDR104 mode support */ host->caps.sdr104_support = (caps[1] & SDHCI_SDR104_MODE_MASK) ? 1 : 0; sdrv_sdhci_reset(host, SDHCI_SOFT_RESET); /* Set bus power on */ if (!host->ops->set_power) { host->ops->set_power = sdrv_sdhci_set_bus_power_on; } host->ops->set_power(host); /* Set bus width */ sdrv_sdhci_set_bus_width(host, SDHCI_BUS_WITDH_1BIT); /* Set Adma mode */ sdrv_sdhci_set_adma_mode(host); /* Enable error status */ sdrv_sdhci_error_status_enable(host); #if (CONFIG_OS_FREERTOS && CONFIG_SDHCI_SUPPORT_INTERRUPT) host->cmd_sema = xSemaphoreCreateCounting(1, 0); host->data_sema = xSemaphoreCreateCounting(1, 0); host->data_complete_sema = xSemaphoreCreateCounting(1, 0); char sdhci_name[128] = {0}; sprintf(sdhci_name, "sdhci_data%d", host->slot); xTaskCreate(sdrv_sdhci_data_thread, sdhci_name, 4096, (void *)host, (configMAX_PRIORITIES - 2), &mmc_process_thread); /* timer is started. The delay time is 1ms */ char timer_name[128] = {0}; sprintf(timer_name, "sdhci_timer%d", host->slot); host->timer_sema = xSemaphoreCreateCounting(1, 0); host->data_end_trans_count = SDHCI_TRANS_COUNT_MAX; host->data_timer = xTimerCreate(timer_name, 1 / portTICK_RATE_MS, pdFALSE, (void * )host, (TimerCallbackFunction_t)sdrv_sdhci_data_timer); if (NULL == host->data_timer) { DETAIL_PRINTF(SSDK_EMERG, "\r\n Host data timer %s create error\r\n", timer_name); } DETAIL_PRINTF(SSDK_DEBUG, "\r\n Host irq: %d \r\n", host->irq); if (host->irq) { irq_attach(host->irq, sdrv_sdhci_irq_handler, (void *)host); irq_enable(host->irq); } #endif }