mirror of
https://gitee.com/Vancouver2017/luban-lite-t3e-pro.git
synced 2025-12-14 18:38:55 +00:00
421 lines
12 KiB
C
421 lines
12 KiB
C
/*
|
|
* Copyright (c) 2023, Artinchip Technology Co., Ltd
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Wu Dehuang <dehuang.wu@artinchip.com>
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <aic_common.h>
|
|
#include <aic_core.h>
|
|
#include <aic_soc.h>
|
|
#include <aic_log.h>
|
|
#include <aic_hal.h>
|
|
#include <hal_sdmc.h>
|
|
#include <bouncebuf.h>
|
|
#include <hal_dma.h>
|
|
#include <partition_table.h>
|
|
#include "sdmc.h"
|
|
|
|
#define MSEC_PER_SEC 1000
|
|
static u32 aic_sdmc_buswidth(u32 type)
|
|
{
|
|
switch (type) {
|
|
case SDMC_CTYPE_8BIT:
|
|
return 8;
|
|
case SDMC_CTYPE_4BIT:
|
|
return 4;
|
|
case SDMC_CTYPE_1BIT:
|
|
return 1;
|
|
default:
|
|
case SDMC_CTYPE_RESERVED:
|
|
pr_warn("Invalid Card type %d\n", type);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static u32 aic_sdmc_get_timeout(struct aic_sdmc *host, const u32 size)
|
|
{
|
|
unsigned int timeout;
|
|
|
|
timeout = size * 8;
|
|
timeout /= aic_sdmc_buswidth(host->buswidth);
|
|
timeout *= 10; /* wait 10 times as long */
|
|
timeout /= (host->clock / MSEC_PER_SEC);
|
|
timeout /= host->ddr_mode ? 2 : 1;
|
|
timeout = (timeout < MSEC_PER_SEC) ? MSEC_PER_SEC : timeout;
|
|
return timeout;
|
|
}
|
|
|
|
static int aic_sdmc_data_transfer(struct aic_sdmc *host,
|
|
struct aic_sdmc_data *data)
|
|
{
|
|
int ret = 0;
|
|
u32 mask, size, total;
|
|
u64 timeout, end, start = aic_get_time_ms();
|
|
|
|
total = data->blksize * data->blks;
|
|
timeout = aic_sdmc_get_timeout(host, total);
|
|
|
|
size = total / 4;
|
|
for (;;) {
|
|
mask = hal_sdmc_int_stat(&host->host);
|
|
if (mask & (SDMC_DATA_ERR | SDMC_DATA_TOUT)) {
|
|
pr_err("Data error! size %d/%d, mode %s, status 0x%x\n",
|
|
size * 4, total,
|
|
host->fifo_mode ? "FIFO" : "IDMA", mask);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
//pr_err("%s: mask %#x, size %d, timeout %d\n",
|
|
// data->flags == MMC_DATA_READ ? "Read" : "Write", mask, size,
|
|
// timeout);
|
|
|
|
if (host->fifo_mode && size) {
|
|
if (data->flags == MMC_DATA_READ &&
|
|
(mask & (SDMC_INT_RXDR | SDMC_INT_DAT_DONE))) {
|
|
size = hal_sdmc_data_rx(&host->host, (u32 *)data->buf, size);
|
|
} else if (data->flags == MMC_DATA_WRITE &&
|
|
(mask & SDMC_INT_TXDR)) {
|
|
size = hal_sdmc_data_tx(&host->host, (u32 *)data->buf, size);
|
|
}
|
|
}
|
|
|
|
if (mask & SDMC_INT_DAT_DONE) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
end = aic_get_time_ms();
|
|
if (end - start > timeout) {
|
|
pr_err("Data timeout %lld - %lld > %lld! size %d/%d, mode %s-%s, status 0x%x\n",
|
|
end, start,
|
|
timeout, size * 4, total,
|
|
host->fifo_mode ? "FIFO" : "IDMA",
|
|
data->flags == MMC_DATA_READ ? "Read" : "Write", mask);
|
|
ret = -1;//RT_ETIMEOUT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
hal_sdmc_int_clr(&host->host, mask);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef AIC_SDMC_IRQ_MODE
|
|
static void aic_sdmc_enable_sdio_irq(struct aic_sdmc *host, int en)
|
|
{
|
|
hal_sdmc_sdio_irq_enable(&host->host, en);
|
|
}
|
|
#endif
|
|
|
|
/* Only enable two types of interrupt: SDIO and IDMA */
|
|
irqreturn_t aic_sdmc_irq(int irq, void *arg)
|
|
{
|
|
#ifdef AIC_SDMC_IRQ_MODE
|
|
struct aic_sdmc *host = (struct aic_sdmc *)arg;
|
|
u32 stat = 0;
|
|
|
|
stat = hal_sdmc_int_stat(&host->host);
|
|
pr_debug("SDMC IRQ status: 0x%x\n", stat);
|
|
|
|
if (stat & SDMC_INT_FROM_SDIO) {
|
|
hal_sdmc_int_clr(&host->host, SDMC_INT_FROM_SDIO);
|
|
if (host->dev->flags & EXT_CSD_SUP_SDIO_IRQ)
|
|
aic_sdmc_enable_sdio_irq(host, 0);
|
|
}
|
|
|
|
/* Then, check the DMA status */
|
|
if (stat & (SDMC_IDMAC_INT_MASK | SDMC_INT_DAT_DONE)) {
|
|
hal_sdmc_idma_update_intstat(&host->host);
|
|
aicos_sem_give(host->host.complete);
|
|
}
|
|
#endif
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
s32 aic_sdmc_clk_init(struct aic_sdmc *host)
|
|
{
|
|
s32 ret = 0;
|
|
|
|
/* First, disable SDMC controller to reset FIFO status */
|
|
hal_clk_disable_assertrst(host->clk);
|
|
hal_clk_disable(host->clk);
|
|
|
|
ret = hal_clk_get_freq(hal_clk_get_parent(host->clk));
|
|
hal_clk_set_freq(host->clk, SDMC_CLOCK_MAX);
|
|
host->sclk_rate = hal_clk_get_freq(host->clk) / 2;
|
|
pr_info("SDMC%d sclk: %d KHz, parent clk %d KHz\n",
|
|
host->index, host->sclk_rate / 1000, ret / 1000);
|
|
|
|
ret = hal_clk_enable(host->clk);
|
|
if (ret < 0) {
|
|
pr_err("SDMC%d clk enable failed!\n", host->index);
|
|
return -1;
|
|
}
|
|
|
|
ret = hal_clk_enable_deassertrst(host->clk);
|
|
if (ret < 0) {
|
|
pr_err("SDMC%d reset deassert failed!\n", host->index);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_sdmc_set_transfer_mode(struct aic_sdmc *host,
|
|
struct aic_sdmc_data *data)
|
|
{
|
|
unsigned long mode = SDMC_CMD_DAT_EXP;
|
|
|
|
if (data->flags & MMC_DATA_WRITE)
|
|
mode |= SDMC_CMD_DAT_WR;
|
|
|
|
return mode;
|
|
}
|
|
|
|
void aic_sdmc_request(struct aic_sdmc *host, struct aic_sdmc_cmd *cmd,
|
|
struct aic_sdmc_data *data)
|
|
{
|
|
s32 ret = 0, flags = 0, i;
|
|
u32 retry = SDMC_TIMEOUT;
|
|
u32 mask = 0;
|
|
u64 timeout = 500000;
|
|
u64 start = aic_get_time_us();
|
|
struct bounce_buffer bbstate;
|
|
ALLOC_CACHE_ALIGN_BUFFER(struct aic_sdmc_idma_desc, cur_idma,
|
|
data ? DIV_ROUND_UP(data->blks, 8) : 0);
|
|
|
|
if (cmd->cmd_code != MMC_CMD_STOP_TRANSMISSION) {
|
|
while (hal_sdmc_is_busy(&host->host)) {
|
|
if (aic_get_time_us() - start > timeout) {
|
|
pr_warn("Data transfer is busy\n");
|
|
cmd->err = -1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// pr_debug("cmd_code: %02d, arg: %08x, flags: %08x --> ", cmd->cmd_code, cmd->arg, cmd->flags);
|
|
|
|
hal_sdmc_int_clr(&host->host, SDMC_INT_ALL);
|
|
if (data) {
|
|
if ((data->blksize * data->blks) < 512)
|
|
host->fifo_mode = 1;
|
|
else
|
|
host->fifo_mode = 0;
|
|
|
|
hal_sdmc_set_blk(&host->host, data->blksize, data->blks);
|
|
hal_sdmc_reset(&host->host, SDMC_HCTRL1_FIFO_RESET);
|
|
if (!host->fifo_mode) {
|
|
ret = hal_sdmc_idma_start(&host->host, data->blksize * data->blks,
|
|
data->flags == MMC_DATA_READ,
|
|
(u32 *)data->buf, &bbstate);
|
|
if (ret) {
|
|
printf("hal_sdmc_idma_start failed.\n");
|
|
cmd->err = -1;
|
|
return;
|
|
}
|
|
|
|
hal_sdmc_idma_prepare(&host->host, data->blksize, data->blks,
|
|
cur_idma, bbstate.bounce_buffer);
|
|
} else {
|
|
if (hal_sdmc_get_idma_status(&host->host))
|
|
hal_sdmc_idma_disable(&host->host);
|
|
}
|
|
}
|
|
#ifdef SDMC_DUMP_CMD
|
|
if (data) {
|
|
printf("fifo_mode: %d, block: %d x %d\n", host->fifo_mode,
|
|
data->blksize, data->blks);
|
|
} else {
|
|
printf("fifo_mode: %d\n", host->fifo_mode);
|
|
}
|
|
#endif
|
|
|
|
hal_sdmc_set_arg(&host->host, cmd->arg);
|
|
|
|
if (data) {
|
|
flags = aic_sdmc_set_transfer_mode(host, data);
|
|
}
|
|
|
|
if (cmd->cmd_code == MMC_CMD_GO_IDLE_STATE)
|
|
flags |= SDMC_CMD_INIT;
|
|
|
|
if (cmd->cmd_code == MMC_CMD_STOP_TRANSMISSION)
|
|
flags |= SDMC_CMD_STOP;
|
|
else
|
|
flags |= SDMC_CMD_PRV_DAT_WAIT;
|
|
|
|
if (cmd->resp_type & MMC_RSP_PRESENT)
|
|
flags |= SDMC_CMD_RESP_EXP;
|
|
if (cmd->resp_type & MMC_RSP_136)
|
|
flags |= SDMC_CMD_RESP_LEN;
|
|
if (cmd->resp_type & MMC_RSP_CRC)
|
|
flags |= SDMC_CMD_RESP_CRC;
|
|
|
|
flags |= (cmd->cmd_code | SDMC_CMD_START | SDMC_CMD_USE_HOLD_REG);
|
|
hal_sdmc_set_cmd(&host->host, flags);
|
|
|
|
for (i = 0; i < retry; i++) {
|
|
mask = hal_sdmc_int_stat(&host->host);
|
|
if (mask & SDMC_INT_CMD_DONE) {
|
|
if (!data)
|
|
hal_sdmc_int_clr(&host->host, mask);
|
|
break;
|
|
}
|
|
}
|
|
#ifdef SDMC_DUMP_CMD
|
|
printf("Flags: 0x%x, mask: 0x%x\n", flags, mask);
|
|
#endif
|
|
|
|
if (i == retry) {
|
|
pr_err("CMD%d done timeout.\n", cmd->cmd_code);
|
|
cmd->err = -1;
|
|
return;
|
|
}
|
|
|
|
if (mask & SDMC_INT_RTO) {
|
|
pr_debug("CMD%d Response Timeout.\n", cmd->cmd_code);
|
|
cmd->err = -1;
|
|
return;
|
|
} else if (mask & SDMC_INT_RESP_ERR) {
|
|
pr_err("CMD%d Response Error.\n", cmd->cmd_code);
|
|
cmd->err = -1;
|
|
return;
|
|
} else if ((cmd->resp_type & MMC_RSP_CRC) && (mask & SDMC_INT_RCRC)) {
|
|
pr_err("CMD%d Response CRC Error.\n", cmd->cmd_code);
|
|
cmd->err = -1;
|
|
return;
|
|
}
|
|
|
|
if ((cmd->resp_type & MMC_RSP_MASK) != MMC_RSP_NONE) {
|
|
hal_sdmc_get_rsp(&host->host, (u32 *)cmd->resp,
|
|
(cmd->resp_type & MMC_RSP_MASK) == MMC_RSP_R2);
|
|
}
|
|
if (data) {
|
|
ret = aic_sdmc_data_transfer(host, data);
|
|
|
|
if (!host->fifo_mode)
|
|
ret = hal_sdmc_idma_stop(&host->host, &bbstate,
|
|
data->flags == MMC_DATA_READ);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static u32 aic_sdmc_get_best_div(u32 sclk, u32 target_freq)
|
|
{
|
|
u32 down, up, f_down, f_up;
|
|
|
|
down = sclk / target_freq;
|
|
up = DIV_ROUND_UP(sclk, target_freq);
|
|
|
|
f_down = down == 0 ? sclk : sclk / down;
|
|
f_up = up == 0 ? sclk : sclk / up;
|
|
|
|
/* Select the closest div parameter */
|
|
if ((f_down - target_freq) < (target_freq - f_up))
|
|
return down;
|
|
return up;
|
|
}
|
|
|
|
static int aic_sdmc_setup_bus(struct aic_sdmc *host, u32 freq)
|
|
{
|
|
u32 mux, div, sclk = host->sclk_rate;
|
|
|
|
if ((freq == host->clock) || (freq == 0))
|
|
return 0;
|
|
|
|
if (sclk == freq) {
|
|
/* bypass mode */
|
|
mux = 1;
|
|
div = 0;
|
|
} else {
|
|
div = aic_sdmc_get_best_div(sclk, freq);
|
|
if (div <= 4) {
|
|
mux = DIV_ROUND_UP(div, 2);
|
|
} else {
|
|
if (div % 8)
|
|
mux = 2;
|
|
else
|
|
mux = 4;
|
|
}
|
|
div /= mux * 2;
|
|
if (div > SDMC_CLKCTRL_DIV_MAX)
|
|
div = SDMC_CLKCTRL_DIV_MAX;
|
|
}
|
|
aic_sdmc_set_ext_clk_mux(&host->host, mux);
|
|
printf("SDMC%d BW %d, sclk %d KHz, clk %d KHz(%d KHz), div %d-%d\n",
|
|
host->index, aic_sdmc_buswidth(host->buswidth),
|
|
sclk / 1000, freq / 1000,
|
|
div ? sclk / mux / div / 2 / 1000 : sclk / mux / 1000,
|
|
mux, div);
|
|
|
|
hal_sdmc_set_div(&host->host, div);
|
|
hal_sdmc_set_cmd(&host->host,
|
|
SDMC_CMD_PRV_DAT_WAIT | SDMC_CMD_UPD_CLK | SDMC_CMD_START);
|
|
|
|
if (hal_sdmc_wait_cmd_started(&host->host))
|
|
return -1;
|
|
|
|
hal_sdmc_clk_enable(&host->host);
|
|
hal_sdmc_set_cmd(&host->host,
|
|
SDMC_CMD_PRV_DAT_WAIT | SDMC_CMD_UPD_CLK | SDMC_CMD_START);
|
|
|
|
if (hal_sdmc_wait_cmd_started(&host->host))
|
|
return -1;
|
|
|
|
host->clock = freq;
|
|
return 0;
|
|
}
|
|
|
|
int aic_sdmc_init(struct aic_sdmc *host)
|
|
{
|
|
if (hal_sdmc_reset(&host->host, SDMC_HCTRL1_RESET_ALL)) {
|
|
pr_err("Failed to reset!\n");
|
|
return -1;
|
|
}
|
|
|
|
aic_sdmc_setup_bus(host, host->dev->freq_min);
|
|
|
|
hal_sdmc_init(&host->host);
|
|
hal_sdmc_fifo_init(&host->host, &host->host.fifoth_val);
|
|
hal_sdmc_set_phase(&host->host,
|
|
host->pdata->drv_phase, host->pdata->smp_phase);
|
|
hal_sdmc_clk_enable(&host->host);
|
|
return 0;
|
|
}
|
|
|
|
void aic_sdmc_set_cfg(struct aic_sdmc *host)
|
|
{
|
|
aic_sdmc_setup_bus(host, host->dev->clock);
|
|
|
|
switch (host->dev->bus_width) {
|
|
case 8:
|
|
host->buswidth = SDMC_CTYPE_8BIT;
|
|
break;
|
|
case 4:
|
|
host->buswidth = SDMC_CTYPE_4BIT;
|
|
break;
|
|
default:
|
|
host->buswidth = SDMC_CTYPE_1BIT;
|
|
break;
|
|
}
|
|
|
|
if (host->buswidth != SDMC_CTYPE_1BIT)
|
|
pr_info("SDMC%d Buswidth %d, DDR mode %d, Current clock: %d KHz\n",
|
|
host->index,
|
|
aic_sdmc_buswidth(host->buswidth), host->ddr_mode,
|
|
host->dev->clock / 1000);
|
|
|
|
hal_sdmc_set_buswidth(&host->host, host->buswidth);
|
|
hal_sdmc_set_ddrmode(&host->host, host->ddr_mode);
|
|
}
|