Files
1CAR/drivers/source/mmc/sdrv_sdhci.c
2025-10-25 21:11:06 +08:00

1876 lines
58 KiB
C

/**
* @file sdrv_sdhci.c
* @brief sdrv sdhci drv source.
*
* @copyright Copyright (c) 2020 Semidrive Semiconductor.
* All rights reserved.
*/
#include <stdlib.h>
#include <string.h>
#include <debug.h>
#include <errno.h>
#include <compiler.h>
#include <param.h>
#include <armv7-r/cache.h>
#include <armv7-r/irq.h>
#include <sdrv_mmc_sdhci.h>
#include <sdrv_sdhci.h>
#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
}