1318 lines
33 KiB
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
|