MAX_CARLINK_A270S/MXC_A27-PCB4.5-270S/ArkmicroFiles/libcpu-amt630hv100/source/sdmmc.c

1318 lines
33 KiB
C

#include "FreeRTOS.h"
#include "chip.h"
#include "board.h"
#include "mmc.h"
#include "sdio.h"
#include "sdmmc.h"
#include "mmcsd_core.h"
#ifdef SDMMC_SUPPORT
#define SDMMC_TICK_DELAY(t) vTaskDelay(t)
#define SDMMC_MAX_TICK_COUNT pdMS_TO_TICKS(10)
/* static inline uint32_t MMC_GetCardStatus(struct ark_mmc_obj *mmc_obj)
{
uint32_t card_status = readl(mmc_obj->base + SDMMC_CDETECT);
return card_status & 0x1;
} */
static inline uint32_t MMC_GetWaterlevel(struct ark_mmc_obj *mmc_obj)
{
return (readl(mmc_obj->base + SDMMC_STATUS) >> 17) & 0x1fff;
}
static inline uint32_t MMC_GetStatus(struct ark_mmc_obj *mmc_obj)
{
return readl(mmc_obj->base + SDMMC_STATUS);
}
static inline uint32_t MMC_GetRawInterrupt(struct ark_mmc_obj *mmc_obj)
{
return readl(mmc_obj->base + SDMMC_RINTSTS);
}
static inline uint32_t MMC_GetUnmaskedInterrupt(struct ark_mmc_obj *mmc_obj)
{
return readl(mmc_obj->base + SDMMC_MINTSTS);
}
static inline uint32_t MMC_ClearRawInterrupt(struct ark_mmc_obj *mmc_obj, uint32_t interrupts)
{
return writel(interrupts, mmc_obj->base + SDMMC_RINTSTS);
}
static inline uint32_t MMC_GetInterruptMask(struct ark_mmc_obj *mmc_obj)
{
return readl(mmc_obj->base + SDMMC_INTMASK);
}
static inline uint32_t MMC_SetInterruptMask(struct ark_mmc_obj *mmc_obj, uint32_t mask)
{
return writel(mask, mmc_obj->base + SDMMC_INTMASK);
}
static inline void MMC_SetByteCount(struct ark_mmc_obj *mmc_obj, uint32_t bytes)
{
writel(bytes, mmc_obj->base + SDMMC_BYTCNT);
}
static inline void MMC_SetBlockSize(struct ark_mmc_obj *mmc_obj, uint32_t size)
{
writel(size, mmc_obj->base + SDMMC_BLKSIZ);
}
static inline uint32_t MMC_GetResponse(struct ark_mmc_obj *mmc_obj, int resp_num)
{
return readl(mmc_obj->base + SDMMC_RESP0 + resp_num * 4);
}
/* static inline uint32_t MMC_IsFifoEmpty(struct ark_mmc_obj *mmc_obj)
{
return (readl(mmc_obj->base + SDMMC_STATUS) >> 2) & 0x1;
}
static inline uint32_t MMC_IsDataStateBusy(struct ark_mmc_obj *mmc_obj)
{
return (readl(mmc_obj->base + SDMMC_STATUS) >> 10) & 0x1;
} */
int MMC_UpdateClockRegister(struct ark_mmc_obj *mmc_obj, int div)
{
uint32_t start_tick, tick, timeout;
start_tick = tick = xTaskGetTickCount();
timeout = tick + configTICK_RATE_HZ / 10; //100ms in total
/* disable clock */
writel(0, mmc_obj->base + SDMMC_CLKENA);
writel(0, mmc_obj->base + SDMMC_CLKSRC);
/* inform CIU */
writel(1<<31 | 1<<21, mmc_obj->base + SDMMC_CMD);
while(readl(mmc_obj->base + SDMMC_CMD) & 0x80000000)
{
tick = xTaskGetTickCount();
if(tick > timeout)
{
printf("ERROR: %s, update clock timeout\n", __func__);
return -1;
}
if((tick - start_tick) >= SDMMC_MAX_TICK_COUNT) {
SDMMC_TICK_DELAY(1);
start_tick = tick;
}
}
/* set clock to desired speed */
writel(div, mmc_obj->base + SDMMC_CLKDIV);
/* inform CIU */
writel(1<<31 | 1<<21, mmc_obj->base + SDMMC_CMD);
start_tick = xTaskGetTickCount();
while(readl(mmc_obj->base + SDMMC_CMD) & 0x80000000)
{
tick = xTaskGetTickCount();
if(tick > timeout)
{
printf("ERROR: %s, update clock timeout\n", __func__);
return -1;
}
if((tick - start_tick) >= SDMMC_MAX_TICK_COUNT) {
SDMMC_TICK_DELAY(1);
start_tick = tick;
}
}
/* enable clock */
writel(1, mmc_obj->base + SDMMC_CLKENA);
/* inform CIU */
writel(1<<31 | 1<<21, mmc_obj->base + SDMMC_CMD);
start_tick = xTaskGetTickCount();
while(readl(mmc_obj->base + SDMMC_CMD) & 0x80000000)
{
tick = xTaskGetTickCount();
if(tick > timeout)
{
printf("ERROR: %s, update clock timeout\n", __func__);
return -1;
}
if((tick - start_tick) >= SDMMC_MAX_TICK_COUNT) {
SDMMC_TICK_DELAY(1);
start_tick = tick;
}
}
return 0;
}
int MMC_SetCardWidth(struct ark_mmc_obj *mmc_obj, int width)
{
switch(width)
{
case SDMMC_CTYPE_1BIT:
writel(0, mmc_obj->base + SDMMC_CTYPE);
break;
case SDMMC_CTYPE_4BIT:
writel(1, mmc_obj->base + SDMMC_CTYPE);
break;
default:
printf("ERROR: %s, card width %d is not supported\n", __func__, width);
return -1;
break;
}
return 0;
}
int MMC_SendCommand(struct ark_mmc_obj *mmc_obj, uint32_t cmd, uint32_t arg, uint32_t flags)
{
uint32_t tick, start_tick, timeout;
start_tick = tick = xTaskGetTickCount();
timeout = tick + configTICK_RATE_HZ; //1s
writel(arg, mmc_obj->base + SDMMC_CMDARG);
flags |= 1<<31 | 1<<29 | cmd;
writel(flags, mmc_obj->base + SDMMC_CMD);
while(readl(mmc_obj->base + SDMMC_CMD) & SDMMC_CMD_START)
{
tick = xTaskGetTickCount();
if(tick > timeout)
{
printf("ERROR: %s, send cmd timeout\n", __func__);
return -1;
}
if((tick - start_tick) >= SDMMC_MAX_TICK_COUNT) {
SDMMC_TICK_DELAY(1);
start_tick = tick;
}
}
//fixme: check HLE_INT_STATUS
return 0;
}
int MMC_ResetFifo(struct ark_mmc_obj *mmc_obj)
{
uint32_t reg, tick, start_tick, timeout;
start_tick = tick = xTaskGetTickCount();
timeout = tick + configTICK_RATE_HZ / 10; //100ms
reg = readl(mmc_obj->base + SDMMC_CTRL);
reg |= SDMMC_CTRL_FIFO_RESET;
writel(reg, mmc_obj->base + SDMMC_CTRL);
//wait until fifo reset finish
while(readl(mmc_obj->base + SDMMC_CTRL) & SDMMC_CTRL_FIFO_RESET)
{
tick = xTaskGetTickCount();
if(tick > timeout)
{
printf("ERROR: %s, FIFO reset timeout\n", __func__);
return -1;
}
if((tick - start_tick) >= SDMMC_MAX_TICK_COUNT) {
SDMMC_TICK_DELAY(1);
start_tick = tick;
}
}
return 0;
}
int MMC_Reset(struct ark_mmc_obj *mmc_obj)
{
uint32_t reg, start_tick, tick, timeout;
reg = readl(mmc_obj->base + SDMMC_CTRL);
reg |= SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET | SDMMC_CTRL_DMA_RESET;
writel(reg, mmc_obj->base + SDMMC_CTRL);
start_tick = tick = xTaskGetTickCount();
timeout = tick + configTICK_RATE_HZ / 10; //100ms
while(readl(mmc_obj->base + SDMMC_CTRL) & (SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET | SDMMC_CTRL_DMA_RESET))
{
tick = xTaskGetTickCount();
if(tick > timeout)
{
printf("ERROR: %s, CTRL dma|fifo|ctrl reset timeout\n", __func__);
return -1;
}
if((tick - start_tick) >= SDMMC_MAX_TICK_COUNT) {
SDMMC_TICK_DELAY(1);
start_tick = tick;
}
}
return 0;
}
#define DW_MCI_DMA_THRESHOLD 32
/* DMA interface functions */
static void dw_mci_stop_dma(struct ark_mmc_obj *mmc_obj)
{
if (mmc_obj->using_dma) {
mmc_obj->dma_ops->stop(mmc_obj);
mmc_obj->dma_ops->cleanup(mmc_obj);
}
/* Data transfer was stopped by the interrupt handler */
//set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
}
static void dw_mci_dma_cleanup(struct ark_mmc_obj *mmc_obj)
{
return;
}
static void dw_mci_edmac_stop_dma(struct ark_mmc_obj *mmc_obj)
{
dma_stop_channel(mmc_obj->dms->ch);
}
static void dw_mci_dmac_complete_callback(void *param, unsigned int mask)
{
struct ark_mmc_obj *mmc_obj = param;
//struct mmcsd_data *data = mmc_obj->data;
dev_vdbg(mmc_obj->dev, "DMA complete\n");
mmc_obj->dma_ops->cleanup(mmc_obj);
xQueueSendFromISR(mmc_obj->transfer_completion, NULL, 0);
/*
* If the card was removed, data will be NULL. No point in trying to
* send the stop command or waiting for NBUSY in this case.
*/
/* if (data) {
set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
tasklet_schedule(&host->tasklet);
} */
}
static int dw_mci_edmac_start_dma(struct ark_mmc_obj *mmc_obj, struct mmcsd_data *data)
{
struct dma_config cfg = {0};
const u32 mszs[] = {1, 4, 8, 16, 32, 64, 128, 256};
u32 fifoth_val;
u32 dma_address;
int ret = 0;
/* Set external dma config: burst size, burst width */
cfg.dst_addr_width = DMA_BUSWIDTH_4_BYTES;
cfg.src_addr_width = DMA_BUSWIDTH_4_BYTES;
/* Match burst msize with external dma config */
fifoth_val = readl(mmc_obj->base + SDMMC_FIFOTH);
cfg.dst_maxburst = mszs[(fifoth_val >> 28) & 0x7];
cfg.src_maxburst = cfg.dst_maxburst;
dma_address = (u32)data->buf;
if (dma_address & (ARCH_DMA_MINALIGN - 1)) {
if (data->flags & DATA_DIR_WRITE) {
mmc_obj->tx_dummy_buffer = pvPortMalloc(data->blks * data->blksize);
if (!mmc_obj->tx_dummy_buffer)
return -ENOMEM;
memcpy(mmc_obj->tx_dummy_buffer, data->buf, data->blks * data->blksize);
dma_address = (u32)mmc_obj->tx_dummy_buffer;
} else if (data->flags & DATA_DIR_READ) {
mmc_obj->rx_dummy_buffer = pvPortMalloc(data->blks * data->blksize);
if (!mmc_obj->rx_dummy_buffer)
return -ENOMEM;
dma_address = (u32)mmc_obj->rx_dummy_buffer;
}
mmc_obj->dummy_buffer_used = 1;
} else {
mmc_obj->dummy_buffer_used = 0;
}
cfg.transfer_size = data->blks * data->blksize;
if (data->flags & DATA_DIR_WRITE) {
cfg.direction = DMA_MEM_TO_DEV;
cfg.src_addr = dma_address;
cfg.dst_addr = mmc_obj->base + SDMMC_FIFO;
cfg.dst_id = SDMMC0_RTX;
}
else {
cfg.direction = DMA_DEV_TO_MEM;
cfg.src_addr = mmc_obj->base + SDMMC_FIFO;
cfg.dst_addr = dma_address;
cfg.src_id = SDMMC0_RTX;
}
ret = dma_config_channel(mmc_obj->dms->ch, &cfg);
if (ret) {
printf("Failed to config edmac.\n");
return -EBUSY;
}
mmc_obj->data = data;
/* Set dw_mci_dmac_complete_dma as callback */
dma_register_complete_callback(mmc_obj->dms->ch, dw_mci_dmac_complete_callback, mmc_obj);
/* Flush cache before write */
if (data->flags & DATA_DIR_WRITE)
CP15_clean_dcache_for_dma(dma_address,
dma_address + data->blks * data->blksize);
/* Invalidate cache before read */
else if (data->flags & DATA_DIR_READ)
CP15_flush_dcache_for_dma(dma_address,
dma_address + data->blks * data->blksize);
dma_start_channel(mmc_obj->dms->ch);
return 0;
}
static int dw_mci_edmac_init(struct ark_mmc_obj *mmc_obj)
{
/* Request external dma channel */
mmc_obj->dms = pvPortMalloc(sizeof(struct dw_mci_dma_slave));
if (!mmc_obj->dms)
return -ENOMEM;
memset(mmc_obj->dms, 0, sizeof(struct dw_mci_dma_slave));
mmc_obj->dms->ch = dma_request_channel(SDMMC_DMA_CH);
if (!mmc_obj->dms->ch) {
printf("Failed to get external DMA channel.\n");
vPortFree(mmc_obj->dms);
mmc_obj->dms = NULL;
return -ENXIO;
}
return 0;
}
static void dw_mci_edmac_exit(struct ark_mmc_obj *mmc_obj)
{
if (mmc_obj->dms) {
if (mmc_obj->dms->ch) {
dma_release_channel(mmc_obj->dms->ch);
mmc_obj->dms->ch = NULL;
}
vPortFree(mmc_obj->dms);
mmc_obj->dms = NULL;
}
}
static struct dw_mci_dma_ops dw_mci_edmac_ops = {
.init = dw_mci_edmac_init,
.exit = dw_mci_edmac_exit,
.start = dw_mci_edmac_start_dma,
.stop = dw_mci_edmac_stop_dma,
.cleanup = dw_mci_dma_cleanup,
};
static void dw_mci_init_dma(struct ark_mmc_obj *mmc_obj)
{
/*
* Check tansfer mode from HCON[17:16]
* Clear the ambiguous description of dw_mmc databook:
* 2b'00: No DMA Interface -> Actually means using Internal DMA block
* 2b'01: DesignWare DMA Interface -> Synopsys DW-DMA block
* 2b'10: Generic DMA Interface -> non-Synopsys generic DMA block
* 2b'11: Non DW DMA Interface -> pio only
* Compared to DesignWare DMA Interface, Generic DMA Interface has a
* simpler request/acknowledge handshake mechanism and both of them
* are regarded as external dma master for dw_mmc.
*/
mmc_obj->use_dma = DMA_INTERFACE_NODMA;//SDMMC_GET_TRANS_MODE(readl(mmc_obj->base + SDMMC_HCON));
if (mmc_obj->use_dma == DMA_INTERFACE_IDMA) {
mmc_obj->use_dma = TRANS_MODE_IDMAC;
} else if (mmc_obj->use_dma == DMA_INTERFACE_DWDMA ||
mmc_obj->use_dma == DMA_INTERFACE_GDMA) {
mmc_obj->use_dma = TRANS_MODE_EDMAC;
} else {
goto no_dma;
}
/* Determine which DMA interface to use */
if (mmc_obj->use_dma == TRANS_MODE_IDMAC) {
/*
* Check ADDR_CONFIG bit in HCON to find
* IDMAC address bus width
*/
} else {
/* TRANS_MODE_EDMAC: check dma bindings again */
mmc_obj->dma_ops = &dw_mci_edmac_ops;
dev_info(mmc_obj->dev, "Using external DMA controller.\n");
}
if (mmc_obj->dma_ops->init && mmc_obj->dma_ops->start &&
mmc_obj->dma_ops->stop && mmc_obj->dma_ops->cleanup) {
if (mmc_obj->dma_ops->init(mmc_obj)) {
printf("%s: Unable to initialize DMA Controller.\n",
__func__);
goto no_dma;
}
} else {
printf("DMA initialization not found.\n");
goto no_dma;
}
return;
no_dma:
dev_info(mmc_obj->dev, "Using PIO mode.\n");
mmc_obj->use_dma = TRANS_MODE_PIO;
}
void MMC_Init(struct ark_mmc_obj *mmc_obj)
{
uint32_t reg;
if(mmc_obj->mmc_reset)
mmc_obj->mmc_reset(mmc_obj);
if (mmc_obj->id == 0)
vClkEnable(CLK_SDMMC0);
/* else if (mmc_obj->id == 1)
vClkEnable(CLK_SDMMC1); */
MMC_Reset(mmc_obj);
dw_mci_init_dma(mmc_obj);
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_ALL);
MMC_SetInterruptMask(mmc_obj, 0x0);
reg = readl(mmc_obj->base + SDMMC_CTRL);
reg |= SDMMC_CTRL_INT_ENABLE;
writel(reg, mmc_obj->base + SDMMC_CTRL);
//set timeout param
writel(0xffffffff, mmc_obj->base + SDMMC_TMOUT);
//set fifo
reg = readl(mmc_obj->base + SDMMC_FIFOTH);
reg = ((reg >> 16) & 0xfff) + 1;
mmc_obj->fifoth_val = SDMMC_SET_FIFOTH(0x3, reg / 2 - 1, reg / 2);
writel(mmc_obj->fifoth_val, mmc_obj->base + SDMMC_FIFOTH);
MMC_SetInterruptMask(mmc_obj, SDMMC_INT_CD);
}
static int ark_mmc_write_pio(struct mmc_driver *mmc_drv)
{
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
struct mmcsd_cmd *cmd = mmc_drv->cmd;
struct mmcsd_data *data = NULL;
uint32_t status;
uint32_t len;
uint32_t remain, fcnt;
uint32_t *buf;
int i;
if(cmd)
data = cmd->data;
if(!data)
{
printf("ERROR: %s, data is NULL\n", __func__);
return -EIO;
}
do {
if (data->blks * data->blksize == data->bytes_xfered)
break;
buf = data->buf + data->bytes_xfered / 4;
remain = data->blks * data->blksize - data->bytes_xfered;
do {
fcnt = (SDMMC_FIFO_DEPTH - MMC_GetWaterlevel(mmc_obj)) * 4;
len = configMIN(remain, fcnt);
if (!len)
break;
for (i = 0; i < len / 4; i ++) {
writel(*buf++, mmc_obj->base + SDMMC_FIFO);
}
data->bytes_xfered += len;
remain -= len;
} while (remain);
status = readl(mmc_obj->base + SDMMC_MINTSTS);
writel(SDMMC_INT_TXDR, mmc_obj->base + SDMMC_MINTSTS);
} while (status & SDMMC_INT_TXDR); /* if TXDR write again */
return 0;
}
static int ark_mmc_read_pio(struct mmc_driver *mmc_drv, bool dto)
{
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
struct mmcsd_cmd *cmd = mmc_drv->cmd;
struct mmcsd_data *data = NULL;
u32 status;
unsigned int len;
unsigned int remain, fcnt;
uint32_t *buf;
int i;
if(cmd)
data = cmd->data;
if(!data)
{
printf("ERROR: %s, data is NULL\n", __func__);
return -EIO;
}
do {
if (data->blks * data->blksize == data->bytes_xfered)
break;
buf = data->buf + data->bytes_xfered / 4;
remain = data->blks * data->blksize - data->bytes_xfered;
do {
fcnt = MMC_GetWaterlevel(mmc_obj) * 4;
len = configMIN(remain, fcnt);
if (!len)
break;
for (i = 0; i < len / 4; i ++) {
*buf++ = readl(mmc_obj->base + SDMMC_FIFO);
}
data->bytes_xfered += len;
remain -= len;
} while (remain);
status = readl(mmc_obj->base + SDMMC_MINTSTS);
writel(SDMMC_INT_RXDR, mmc_obj->base + SDMMC_RINTSTS);
/* if the RXDR is ready read again */
} while ((status & SDMMC_INT_RXDR) ||
(dto && MMC_GetWaterlevel(mmc_obj)));
return 0;
}
static void ark_mmc_set_iocfg(struct mmcsd_host *host, struct mmcsd_io_cfg *io_cfg)
{
uint32_t clksrc, clkdiv;
struct mmc_driver *mmc_drv = host->private_data;
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
TRACE_DEBUG("%s start\n", __func__);
//fixme: read from PMU
//why io_cfg->clock == 0 ?
if(io_cfg->clock)
{
if (mmc_obj->id == 0)
clksrc = ulClkGetRate(CLK_SDMMC0);
/* else if (mmc_obj->id == 1)
clksrc = ulClkGetRate(CLK_SDMMC1); */
else
return;
clkdiv = clksrc / io_cfg->clock / 2;
MMC_UpdateClockRegister(mmc_obj, clkdiv);
TRACE_DEBUG("io_cfg->clock: %lu, clock in: %lu, clkdiv: %d\n", io_cfg->clock, clkdiv, clkdiv);
}
if (io_cfg->bus_width == MMCSD_BUS_WIDTH_4)
{
MMC_SetCardWidth(mmc_obj, SDMMC_CTYPE_4BIT);
TRACE_DEBUG("set to 4-bit mode\n");
}
else
{
MMC_SetCardWidth(mmc_obj, SDMMC_CTYPE_1BIT);
TRACE_DEBUG("set to 1-bit mode\n");
}
/* maybe switch power to the card */
switch (io_cfg->power_mode)
{
case MMCSD_POWER_OFF:
break;
case MMCSD_POWER_UP:
break;
case MMCSD_POWER_ON:
break;
default:
printf("ERROR: %s, unknown power_mode %d\n", __func__, io_cfg->power_mode);
break;
}
TRACE_DEBUG("%s end\n", __func__);
}
static void ark_mmc_enable_sdio_irq(struct mmcsd_host *host, int32_t enable)
{
struct mmc_driver *mmc_drv = host->private_data;
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
uint32_t reg;
TRACE_DEBUG("%s start\n", __func__);
if (enable)
{
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_SDIO);
reg = MMC_GetInterruptMask(mmc_obj);
reg |= SDMMC_INT_SDIO;
MMC_SetInterruptMask(mmc_obj, reg);
}
else
{
reg = MMC_GetInterruptMask(mmc_obj);
reg &= ~SDMMC_INT_SDIO;
MMC_SetInterruptMask(mmc_obj, reg);
}
}
static int32_t ark_mmc_get_card_status(struct mmcsd_host *host)
{
struct mmc_driver *mmc_drv = host->private_data;
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
return !(readl(mmc_obj->base + SDMMC_CDETECT) & 0x1);
}
static int ark_mmc_send_command(struct mmc_driver *mmc_drv, struct mmcsd_cmd *cmd)
{
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
struct mmcsd_req *req = mmc_drv->req;
//fixme: cmd->data or req->data
struct mmcsd_data *data = cmd->data;
int ret;
uint32_t cmd_flags = 0;
TRACE_DEBUG("%s, start\n", __func__);
if (!cmd)
{
//fixme: stop dma
printf("ERROR: %s, cmd is NULL\n", __func__);
return -1;
}
if (data)
{
cmd_flags |= SDMMC_CMD_DAT_EXP;
/* always set data start - also set direction flag for read */
if (data->flags & DATA_DIR_WRITE)
cmd_flags |= SDMMC_CMD_DAT_WR;
if (data->flags & DATA_STREAM)
cmd_flags |= SDMMC_CMD_STRM_MODE;
}
if (cmd == req->stop)
cmd_flags |= SDMMC_CMD_STOP;
else
cmd_flags |= SDMMC_CMD_PRV_DAT_WAIT;
switch (resp_type(cmd))
{
case RESP_NONE:
break;
case RESP_R1:
case RESP_R5:
case RESP_R6:
case RESP_R7:
case RESP_R1B:
cmd_flags |= SDMMC_CMD_RESP_EXP;
cmd_flags |= SDMMC_CMD_RESP_CRC;
break;
case RESP_R2:
cmd_flags |= SDMMC_CMD_RESP_EXP;
cmd_flags |= SDMMC_CMD_RESP_CRC;
cmd_flags |= SDMMC_CMD_RESP_LONG;
break;
case RESP_R3:
case RESP_R4:
cmd_flags |= SDMMC_CMD_RESP_EXP;
break;
default:
printf("ERROR: %s, unknown cmd type %x\n", __func__, resp_type(cmd));
return -1;
}
if (cmd->cmd_code == GO_IDLE_STATE)
cmd_flags |= SDMMC_CMD_INIT;
/* CMD 11 check switch voltage */
if (cmd->cmd_code == READ_DAT_UNTIL_STOP)
cmd_flags |= SDMMC_CMD_VOLT_SWITCH;
TRACE_DEBUG("cmd code: %d, args: 0x%x, resp type: 0x%x, flag: 0x%x\n", cmd->cmd_code, cmd->arg, resp_type(cmd), cmd_flags);
ret = MMC_SendCommand(mmc_obj, cmd->cmd_code, cmd->arg, cmd_flags);
if(ret)
{
printf("ERROR: %s, Send command timeout, cmd: %d, status: 0x%x\n", __func__, cmd->cmd_code, MMC_GetStatus(mmc_obj));
return -1;
}
return 0;
}
static void dw_mci_adjust_fifoth(struct ark_mmc_obj *mmc_obj, struct mmcsd_data *data)
{
unsigned int blksz = data->blksize;
const u32 mszs[] = {1, 4, 8, 16, 32, 64, 128, 256};
u32 fifo_width = 4;
u32 blksz_depth = blksz / fifo_width, fifoth_val;
u32 msize = 0, rx_wmark = 1, tx_wmark, tx_wmark_invers;
int idx = ARRAY_SIZE(mszs) - 1;
/* pio should ship this scenario */
if (!mmc_obj->use_dma)
return;
tx_wmark = SDMMC_FIFO_DEPTH / 2;
tx_wmark_invers = SDMMC_FIFO_DEPTH - tx_wmark;
/*
* MSIZE is '1',
* if blksz is not a multiple of the FIFO width
*/
if (blksz % fifo_width)
goto done;
do {
if (!((blksz_depth % mszs[idx]) ||
(tx_wmark_invers % mszs[idx]))) {
msize = idx;
rx_wmark = mszs[idx] - 1;
break;
}
} while (--idx > 0);
/*
* If idx is '0', it won't be tried
* Thus, initial values are uesed
*/
done:
fifoth_val = SDMMC_SET_FIFOTH(msize, rx_wmark, tx_wmark);
writel(fifoth_val, mmc_obj->base + SDMMC_FIFOTH);
}
static int dw_mci_submit_data_dma(struct ark_mmc_obj *mmc_obj, struct mmcsd_data *data)
{
u32 temp;
mmc_obj->using_dma = 0;
/* If we don't have a channel, we can't do DMA */
if (!mmc_obj->use_dma)
return -1;
if (data->blks * data->blksize < DW_MCI_DMA_THRESHOLD ||
data->blksize & 3 || (u32)data->buf & 3) {
return -1;
}
mmc_obj->using_dma = 1;
temp = MMC_GetInterruptMask(mmc_obj);
temp |= SDMMC_INT_DATA_OVER | SDMMC_INT_DATA_ERROR;
temp &= ~(SDMMC_INT_RXDR | SDMMC_INT_TXDR);
MMC_SetInterruptMask(mmc_obj, temp);
/* Enable the DMA interface */
temp = readl(mmc_obj->base + SDMMC_CTRL);
temp |= SDMMC_CTRL_DMA_ENABLE;
writel(temp, mmc_obj->base + SDMMC_CTRL);
/*
* Decide the MSIZE and RX/TX Watermark.
* If current block size is same with previous size,
* no need to update fifoth.
*/
if (mmc_obj->prev_blksz != data->blksize)
dw_mci_adjust_fifoth(mmc_obj, data);
if (mmc_obj->dma_ops->start(mmc_obj, data)) {
mmc_obj->dma_ops->stop(mmc_obj);
/* We can't do DMA, try PIO for this one */
dev_dbg(mmc_obj->dev,
"%s: fall back to PIO mode for current transfer\n",
__func__);
mmc_obj->using_dma = 0;
return -1;
}
return 0;
}
static int ark_mmc_prepare_data(struct mmc_driver *mmc_drv)
{
struct mmcsd_cmd *cmd = mmc_drv->cmd;
struct mmcsd_data *data = cmd->data;
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
uint32_t data_size;
uint32_t reg;
if(!data)
{
MMC_SetBlockSize(mmc_obj, 0);
MMC_SetByteCount(mmc_obj, 0);
return 0;
}
TRACE_DEBUG("%s, start\n", __func__);
if(MMC_ResetFifo(mmc_obj))
{
return -1;
}
data_size = data->blks * data->blksize;
MMC_SetBlockSize(mmc_obj, data->blksize);
data->bytes_xfered = 0;
if(data_size % 4)
{
printf("ERROR: data_size should be a multiple of 4, but now is %d\n", data_size);
}
MMC_SetByteCount(mmc_obj, data_size);
TRACE_DEBUG("%s, set blk size: 0x%x, byte count: 0x%x\n", __func__, data->blksize, data_size);
if (dw_mci_submit_data_dma(mmc_obj, data)) {
reg = readl(mmc_obj->base + SDMMC_CTRL);
reg &= ~SDMMC_CTRL_DMA_ENABLE;
writel(reg, mmc_obj->base + SDMMC_CTRL);
} else {
mmc_obj->prev_blksz = data->blksize;
}
TRACE_DEBUG("%s, end\n", __func__);
return 0;
}
int ark_mmc_wait_card_idle(struct ark_mmc_obj *mmc_obj)
{
uint32_t start_tick, tick, timeout;
start_tick = tick = xTaskGetTickCount();
timeout = tick + configTICK_RATE_HZ*3; //3s
while(MMC_GetStatus(mmc_obj) & SDMMC_STATUS_BUSY)
{
tick = xTaskGetTickCount();
if(tick > timeout)
{
if (MMC_GetStatus(mmc_obj) & SDMMC_STATUS_BUSY)
return -1;
else
return 0;
}
if((tick - start_tick) >= SDMMC_MAX_TICK_COUNT) {
SDMMC_TICK_DELAY(1);
start_tick = tick;
}
}
return 0;
}
static int ark_mmc_get_response(struct mmc_driver *mmc_drv, struct mmcsd_cmd *cmd)
{
int i;
uint32_t tick, start_tick, timeout, status;
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
cmd->resp[0] = 0;
cmd->resp[1] = 0;
cmd->resp[2] = 0;
cmd->resp[3] = 0;
start_tick = tick = xTaskGetTickCount();
timeout = tick + configTICK_RATE_HZ / 2; //500ms
//fixme: spin_lock_irqsave?
do
{
status = MMC_GetRawInterrupt(mmc_obj);
tick = xTaskGetTickCount();
if(tick > timeout)
{
TRACE_DEBUG("ERROR: %s, get response timeout(cmd is not received by card), RINTSTS: 0x%x, cmd: %d\n", __func__, status, cmd->cmd_code);
return -1;
}
if((tick - start_tick) >= SDMMC_MAX_TICK_COUNT) {
SDMMC_TICK_DELAY(1);
start_tick = tick;
}
}
while(!(status & SDMMC_INT_CMD_DONE));
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_CMD_DONE);
for (i = 0; i < 4; i++)
{
if (resp_type(cmd) == RESP_R2)
{
cmd->resp[i] = MMC_GetResponse(mmc_obj, 3 - i);
//fixme : R2 must delay some time here ,when use UHI card, need check why
//1ms
//vTaskDelay(configTICK_RATE_HZ / 100);
}
else
{
cmd->resp[i] = MMC_GetResponse(mmc_obj, i);
}
}
TRACE_DEBUG("resp: 0x%x, 0x%x, 0x%x, 0x%x\n", cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
if (status & SDMMC_INT_RTO)
{
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_RTO);
TRACE_DEBUG("ERROR: %s, get response timeout, RINTSTS: 0x%x\n", __func__, status);
return -1;
}
else if (status & (SDMMC_INT_RCRC | SDMMC_INT_RESP_ERR))
{
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_RCRC | SDMMC_INT_RESP_ERR);
printf("ERROR: %s, response error or response crc error, RINTSTS: 0x%x\n", __func__, status);
//return -1;
}
return 0;
}
static int ark_mmc_start_transfer(struct mmc_driver *mmc_drv)
{
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
struct mmcsd_cmd *cmd = mmc_drv->cmd;
struct mmcsd_data *data = NULL;
int ret;
uint32_t interrupt, status, reg;
uint32_t timeout;
if(cmd)
data = cmd->data;
if(!data)
{
return 0;
}
TRACE_DEBUG("%s, start\n", __func__);
//fixme: spin_lock_irqsave(&host->lock, flags);
if (!mmc_obj->using_dma) {
//fifo mode open data interrupts
reg = MMC_GetInterruptMask(mmc_obj);
reg |= SDMMC_INT_STATUS_DATA;
MMC_SetInterruptMask(mmc_obj, reg);
}
//fixme: spin_unlock_irqrestore(&host->lock, flags);
timeout = configTICK_RATE_HZ + pdMS_TO_TICKS(data->blks * data->blksize / 1024);
ret = xQueueReceive(mmc_obj->transfer_completion, NULL, timeout);
if (mmc_obj->using_dma) {
if (mmc_obj->dummy_buffer_used) {
if (data->flags & DATA_DIR_WRITE) {
if (mmc_obj->tx_dummy_buffer) {
vPortFree(mmc_obj->tx_dummy_buffer);
mmc_obj->tx_dummy_buffer = NULL;
}
} else if (data->flags & DATA_DIR_READ) {
if (mmc_obj->rx_dummy_buffer) {
memcpy(data->buf, mmc_obj->rx_dummy_buffer, data->blks * data->blksize);
vPortFree(mmc_obj->rx_dummy_buffer);
mmc_obj->rx_dummy_buffer = NULL;
}
}
}
} else {
reg = MMC_GetInterruptMask(mmc_obj);
reg &= ~SDMMC_INT_STATUS_DATA;
MMC_SetInterruptMask(mmc_obj, reg);
}
if(ret != pdTRUE || mmc_obj->result)
{
//fixme: error handle
if (mmc_obj->using_dma)
dw_mci_stop_dma(mmc_obj);
cmd->err = ret;
interrupt = MMC_GetRawInterrupt(mmc_obj);
status = MMC_GetStatus(mmc_obj);
printf("ERROR: %s, transfer timeout, ret: %d, RINTSTS: 0x%x, STATUS: 0x%x\n", __func__, ret, interrupt, status);
return -1;
}
data->bytes_xfered = data->blks * data->blksize;
return 0;
}
static void ark_mmc_complete_request(struct mmc_driver *mmc_drv)
{
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
mmc_drv->cmd = NULL;
mmc_drv->req = NULL;
mmc_drv->data = NULL;
MMC_SetBlockSize(mmc_obj, 0);
MMC_SetByteCount(mmc_obj, 0);
mmcsd_req_complete(mmc_drv->host);
}
static void ark_mmc_request(struct mmcsd_host *host, struct mmcsd_req *req)
{
int ret;
struct mmc_driver *mmc_drv = host->private_data;
struct mmcsd_cmd *cmd = req->cmd;
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
unsigned int mmc_task_prio;
TRACE_DEBUG("%s start\n", __func__);
mmc_task_prio = uxTaskPriorityGet(NULL);
if(mmc_task_prio < configMAX_PRIORITIES - 2) {
vTaskPrioritySet(NULL, configMAX_PRIORITIES - 2);
}
mmc_drv->req = req;
mmc_drv->cmd = cmd;
if (mmc_drv->host->transfer_err > 5) {
goto out;
}
if (mmc_obj->transfer_completion == NULL)
mmc_obj->transfer_completion = xQueueCreate(1, 0);
else
xQueueReset(mmc_obj->transfer_completion);
ret = ark_mmc_wait_card_idle(mmc_obj);
if (ret)
{
printf("ERROR: %s, data transfer timeout, status: 0x%x\r\n", __func__, MMC_GetStatus(mmc_obj));
if (MMC_GetStatus(mmc_obj) & SDMMC_STATUS_BUSY)
goto out;
}
mmc_obj->result = 0;
if (ark_mmc_prepare_data(mmc_drv))
goto out;
if (ark_mmc_send_command(mmc_drv, cmd))
goto out;
ret = ark_mmc_get_response(mmc_drv, cmd);
if(ret)
{
cmd->err = ret;
mmc_drv->host->transfer_err++;
printf("%s,get response returns %d, cmd: %d err count: %d\r\n", __func__, ret, cmd->cmd_code, mmc_drv->host->transfer_err);
goto out;
}
ark_mmc_start_transfer(mmc_drv);
if(req->stop)
{
/* send stop command */
TRACE_DEBUG("%s send stop\n", __func__);
ark_mmc_send_command(mmc_drv, req->stop);
}
mmc_drv->host->transfer_err = 0;
out:
ark_mmc_complete_request(mmc_drv);
if(mmc_task_prio < configMAX_PRIORITIES - 2) {
vTaskPrioritySet(NULL, mmc_task_prio);
}
TRACE_DEBUG("%s end\n", __func__);
}
static const struct mmcsd_host_ops ark_mmc_ops =
{
.request = ark_mmc_request,
.set_iocfg = ark_mmc_set_iocfg,
.enable_sdio_irq = ark_mmc_enable_sdio_irq,
.get_card_status = ark_mmc_get_card_status,
};
static void ark_mmc_interrupt(void *param)
{
struct mmc_driver *mmc_drv = (struct mmc_driver *)param;
struct ark_mmc_obj *mmc_obj = (struct ark_mmc_obj *)mmc_drv->priv;
struct mmcsd_cmd *cmd = mmc_drv->cmd;
struct mmcsd_data *data = NULL;
uint32_t status;
if (cmd && cmd->data)
{
data = cmd->data;
}
status = MMC_GetUnmaskedInterrupt(mmc_obj);
TRACE_DEBUG("unmasked interrupts: 0x%x\n", status);
if (status & SDMMC_CMD_ERROR_FLAGS) {
MMC_ClearRawInterrupt(mmc_obj, SDMMC_CMD_ERROR_FLAGS);
mmc_obj->result = -1;
xQueueSendFromISR(mmc_obj->transfer_completion, NULL, 0);
}
if (status & SDMMC_DATA_ERROR_FLAGS) {
/* if there is an error report DATA_ERROR */
MMC_ClearRawInterrupt(mmc_obj, SDMMC_DATA_ERROR_FLAGS);
mmc_obj->result = -1;
xQueueSendFromISR(mmc_obj->transfer_completion, NULL, 0);
}
if (status & SDMMC_INT_DATA_OVER) {
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_DATA_OVER);
if (data && data->flags & DATA_DIR_READ) {
if (!mmc_obj->using_dma && data->bytes_xfered != data->blks * data->blksize)
ark_mmc_read_pio(mmc_drv, 1);
}
if (!mmc_obj->using_dma)
xQueueSendFromISR(mmc_obj->transfer_completion, NULL, 0);
}
if (status & SDMMC_INT_RXDR) {
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_RXDR);
if (data && data->flags & DATA_DIR_READ)
ark_mmc_read_pio(mmc_drv, 0);
}
if (status & SDMMC_INT_TXDR) {
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_TXDR);
if (data && data->flags & DATA_DIR_WRITE)
ark_mmc_write_pio(mmc_drv);
}
if (status & SDMMC_INT_CMD_DONE) {
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_CMD_DONE);
}
if (status & SDMMC_INT_CD) {
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_CD);
#ifndef WIFI_SUPPORT
mmcsd_change_from_isr(mmc_drv->host);
#endif
}
if (status & SDMMC_INT_SDIO) {
MMC_ClearRawInterrupt(mmc_obj, SDMMC_INT_SDIO);
sdio_irq_wakeup_isr(mmc_drv->host);
}
}
#define SYS_SOFT_RSTA 0x5c
void ark_mmc_reset(struct ark_mmc_obj *mmc_obj)
{
unsigned int val;
unsigned int offset;
if (mmc_obj->id == 0)
offset = 4;
/* else if (mmc_obj->id == 1)
offset = 31; */
else
return;
val = readl(REGS_SYSCTL_BASE + SYS_SOFT_RSTA);
writel(val & ~(1 << offset), REGS_SYSCTL_BASE + SYS_SOFT_RSTA);
udelay(10);
writel(val | (1 << offset), REGS_SYSCTL_BASE + SYS_SOFT_RSTA);
}
static struct ark_mmc_obj mmc0_obj =
{
.id = 0,
.irq = SDMMC0_IRQn,
.base = REGS_SDMMC0_BASE,
.mmc_reset = ark_mmc_reset,
.dma_args = {SDMMC0_RTX, 1, 0},
};
int ark_mmc_probe(struct ark_mmc_obj *mmc_obj)
{
struct mmc_driver *mmc_drv;
struct mmcsd_host *host;
TRACE_DEBUG("%s start\n", __func__);
mmc_drv = (struct mmc_driver*)pvPortMalloc(sizeof(struct mmc_driver));
memset(mmc_drv, 0, sizeof(struct mmc_driver));
mmc_drv->priv = mmc_obj;
host = mmcsd_alloc_host();
if (!host)
{
printf("ERROR: %s, failed to malloc host\n", __func__);
return -ENOMEM;
}
host->ops = &ark_mmc_ops;
host->freq_min = MMC_FEQ_MIN;
host->freq_max = MMC_FEQ_MAX;
host->valid_ocr = VDD_32_33 | VDD_33_34;
host->flags = MMCSD_MUTBLKWRITE | MMCSD_SUP_HIGHSPEED | MMCSD_BUSWIDTH_4;
host->max_blk_size = 512;
//fixme: max_blk_count?
host->max_blk_count = 2048;
host->private_data = mmc_drv;
mmc_drv->host = host;
MMC_Init(mmc_obj);
if(mmc_obj->id == 0)
{
request_irq(mmc_obj->irq, 0, ark_mmc_interrupt, mmc_drv);
}
/* else if(mmc_obj->id == 1)
{
request_irq(mmc_obj->irq, 0, ark_mmc_interrupt, mmc_drv);
} */
#ifndef WIFI_SUPPORT
if (mmc_obj->id == 0)
{
#if DEVICE_TYPE_SELECT != EMMC_FLASH
if (ark_mmc_get_card_status(host))
#endif
mmcsd_change(host);
}
else if (mmc_obj->id == 1)
#endif
{
ark_mmc_enable_sdio_irq(host, 1);
mmcsd_change(host);
}
TRACE_DEBUG("%s end\n", __func__);
return 0;
}
int mmc_init(void)
{
ark_mmc_probe(&mmc0_obj);
return 0;
}
#endif