rt-thread/bsp/x1000/driver/drv_mmc.c

751 lines
20 KiB
C

/*
* File : drv_mmc.c
* This file is part of RT-Thread RTOS
* COPYRIGHT (C) 2013 - 2015, RT-Thread Development Team
*
* The license and distribution terms for this file may be
* found in the file LICENSE in this distribution or at
* http://www.rt-thread.org/license/LICENSE
*
* Change Logs:
* Date Author Notes
* 2013-03-09 aozima the first version
* 2013-03-29 aozima support Jz4770.
* 2013-04-01 aozima add interrupt support for Jz4770.
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <drivers/mmcsd_core.h>
#include <drivers/sdio.h>
#include "board.h"
#include "drv_gpio.h"
#include "drv_clock.h"
#include "drv_mmc.h"
#define RT_USING_MSC0
#define RT_USING_MSC1
// #define JZ47XX_SDIO_DBG
#ifdef JZ47XX_SDIO_DBG
#define sdio_dbg(fmt, ...) rt_kprintf("[SDIO]");rt_kprintf(fmt, ##__VA_ARGS__)
#else
#define sdio_dbg(fmt, ...)
#endif
static void msc_handler(int irqno, void* param)
{
struct jz47xx_sdio * jz_sdio = (struct jz47xx_sdio *)param;
/* disable interrupt */
rt_hw_interrupt_mask(jz_sdio->irqno);
rt_completion_done(&jz_sdio->completion);
}
rt_inline void jz_mmc_clk_autoctrl(struct jz47xx_sdio *host, unsigned int on)
{
if(on)
{
if(!clk_is_enabled(host->clock))
clk_enable(host->clock);
if(!clk_is_enabled(host->clock_gate))
clk_enable(host->clock_gate);
}
else
{
if(clk_is_enabled(host->clock_gate))
clk_disable(host->clock_gate);
if(clk_is_enabled(host->clock))
clk_disable(host->clock);
}
}
/* Stop the MMC clock and wait while it happens */
rt_inline rt_err_t jz_mmc_stop_clock(uint32_t hw_base)
{
uint16_t value;
int timeout = 10000;
value = readw(hw_base + MSC_CTRL_OFFSET);
value |= MSC_CTRL_CLOCK_STOP;
writew(value, hw_base + MSC_CTRL_OFFSET);
while (timeout && (readl(hw_base + MSC_STAT_OFFSET) & MSC_STAT_CLK_EN))
{
timeout--;
if (timeout == 0)
{
return -RT_ETIMEOUT;
}
rt_thread_delay(1);
}
return RT_EOK;
}
/* Start the MMC clock and operation */
rt_inline void jz_mmc_start_clock(uint32_t hw_base)
{
uint16_t value;
value = readw(hw_base + MSC_CTRL_OFFSET);
value |= (MSC_CTRL_CLOCK_START | MSC_CTRL_START_OP);
writew(value, hw_base + MSC_CTRL_OFFSET);
}
static int jz_mmc_hardware_init(struct jz47xx_sdio * jz_sdio)
{
uint32_t hw_base = jz_sdio->hw_base;
uint32_t value;
/* reset mmc/sd controller */
value = readl(hw_base + MSC_CTRL_OFFSET);
value |= MSC_CTRL_RESET;
writel(value, hw_base + MSC_CTRL_OFFSET);
rt_thread_delay(1);
value &= ~MSC_CTRL_RESET;
writel(value, hw_base + MSC_CTRL_OFFSET);
while(readl(hw_base + MSC_STAT_OFFSET) & MSC_STAT_IS_RESETTING);
/* mask all IRQs */
writel(0xffffffff, hw_base + MSC_IMASK_OFFSET);
writel(0xffffffff, hw_base + MSC_IREG_OFFSET);
/* set timeout */
writel(0x100, hw_base + MSC_RESTO_OFFSET);
writel(0x1ffffff, hw_base + MSC_RDTO_OFFSET);
/* stop MMC/SD clock */
jz_mmc_stop_clock(hw_base);
}
/* Set the MMC clock frequency */
void jz_mmc_set_clock(struct jz47xx_sdio * jz_sdio, unsigned int clock)
{
unsigned int msc_clock = jz_sdio->msc_clock;
/* calc and set MSC_CLKRT. */
{
unsigned int div = 0;
while (clock < msc_clock)
{
div++;
msc_clock >>= 1;
}
if(div > 7) div = 7;
sdio_dbg("msc_clock: %u, SDIO_CLK: %u, MSC_CLKRT: %u\r\n", jz_sdio->msc_clock, clock, div);
writew(div, jz_sdio->hw_base + MSC_CLKRT_OFFSET);
}
}
/* RT-Thread SDIO interface */
static void jz47xx_sdio_request(struct rt_mmcsd_host *host,
struct rt_mmcsd_req *req)
{
struct jz47xx_sdio *sdio = host->private_data;
unsigned int cmdat = 0;
unsigned int stat;
uint32_t hw_base, value;
hw_base = sdio->hw_base;
jz_mmc_stop_clock(hw_base);
sdio_dbg("CMD: %d ARG: %08X\n", req->cmd->cmd_code, req->cmd->arg);
if(sdio->flag & MSC_CMDAT_BUS_WIDTH_4BIT)
{
cmdat |= MSC_CMDAT_BUS_WIDTH_4BIT;
}
/* auto send stop */
if (req->stop)
{
sdio_dbg("CMD STOP: %d ARG: %08X\n", req->stop->cmd_code,
req->stop->arg);
cmdat |= MSC_CMDAT_SEND_AS_STOP;
}
if(req->cmd->cmd_code == GO_IDLE_STATE)
{
cmdat |= MSC_CMDAT_INIT;
}
/* clear status */
writew(0xFFFF, hw_base + MSC_IREG_OFFSET);
/* open interrupt */
value = readl(hw_base + MSC_IMASK_OFFSET);
value &= ~(MSC_DATA_TRAN_DONE | MSC_PRG_DONE | MSC_END_CMD_RES);
writel(value, hw_base + MSC_IMASK_OFFSET);
if(req->data)
{
writew(req->data->blksize, hw_base + MSC_BLKLEN_OFFSET);
writew(req->data->blks, hw_base + MSC_NOB_OFFSET);
cmdat |= MSC_CMDAT_DATA_EN;
if (req->data->flags & DATA_DIR_WRITE)
{
cmdat |= MSC_CMDAT_WRITE;
}
else if (req->data->flags & DATA_DIR_READ)
{
cmdat |= MSC_CMDAT_READ;
}
}
else
{
writew(0, hw_base + MSC_BLKLEN_OFFSET);
writew(0, hw_base + MSC_NOB_OFFSET);
}
/* set command */
writeb(req->cmd->cmd_code, hw_base + MSC_CMD_OFFSET);
/* set argument */
writel(req->cmd->arg, hw_base + MSC_ARG_OFFSET);
/* Set response type */
#ifdef JZ47XX_SDIO_DBG
{
int res_type = req->cmd->flags & RESP_MASK;
sdio_dbg("resp type:%u\r\n", res_type);
}
#endif
cmdat &= ~(MSC_CMDAT_RESP_FORMAT_MASK);
switch (req->cmd->flags & RESP_MASK)
{
case RESP_NONE:
break;
case RESP_R1B:
cmdat |= MSC_CMDAT_BUSY;
/*FALLTHRU*/
case RESP_R1:
cmdat |= MSC_CMDAT_RESPONSE_R1;
break;
case RESP_R2:
cmdat |= MSC_CMDAT_RESPONSE_R2;
break;
case RESP_R3:
cmdat |= MSC_CMDAT_RESPONSE_R3;
break;
case RESP_R4:
cmdat |= MSC_CMDAT_RESPONSE_R4;
break;
case RESP_R5:
cmdat |= MSC_CMDAT_RESPONSE_R5;
break;
case RESP_R6:
cmdat |= MSC_CMDAT_RESPONSE_R6;
case RESP_R7:
cmdat |= MSC_CMDAT_RESPONSE_R7;
break;
default:
break;
}
/* Set command */
sdio_dbg("cmdat: %08X\r\n", cmdat);
writel(cmdat, sdio->hw_base + MSC_CMDAT_OFFSET);
writel(MSC_CTRL_START_OP, sdio->hw_base + MSC_CTRL_OFFSET);
writel(0xFF, sdio->hw_base + MSC_RESTO_OFFSET);
writel(0xFFFFFFFF, sdio->hw_base + MSC_RDTO_OFFSET);
jz_mmc_start_clock(sdio->hw_base);
req->cmd->err = RT_EOK;
if(!(readl(sdio->hw_base + MSC_IREG_OFFSET) & MSC_END_CMD_RES))
{
rt_err_t ret;
rt_completion_init(&sdio->completion);
rt_hw_interrupt_umask(sdio->irqno);
ret = rt_completion_wait(&sdio->completion, RT_TICK_PER_SECOND);
if(ret == RT_EOK)
{
sdio_dbg("wait END_CMD_RES OK!\r\n");
}
else
{
uint32_t value;
value = readl(hw_base + MSC_STAT_OFFSET);
sdio_dbg("stat=0x%08x\n", value);
value = readl(hw_base + MSC_IREG_OFFSET);
sdio_dbg("iflag=0x%08x\n", value);
req->cmd->err = ret;
sdio_dbg("wait END_CMD_RES timeout[uncompletion]\r\n");
}
}
else
{
sdio_dbg("no need wait MSC_END_CMD_RES!\r\n");
}
stat = readl(hw_base + MSC_STAT_OFFSET);
writew(MSC_END_CMD_RES, hw_base + MSC_IREG_OFFSET);
/* get response. */
{
uint8_t buf[16];
uint32_t data;
if(req->cmd->err == RT_EOK)
{
if(stat & MSC_STAT_TIME_OUT_RES)
{
sdio_dbg("ERR: MSC_STAT_TIME_OUT_RES\r\n");
req->cmd->err = -RT_ETIMEOUT;
}
else if(stat & MSC_STAT_CRC_READ_ERR)
{
sdio_dbg("ERR: MSC_STAT_CRC_READ_ERR\r\n");
req->cmd->err = -1;
}
}
switch (req->cmd->flags & RESP_MASK)
{
case RESP_R1:
case RESP_R1B:
case RESP_R6:
case RESP_R3:
case RESP_R4:
case RESP_R5:
case RESP_R7:
data = readw(sdio->hw_base + MSC_RES_OFFSET);
buf[1] = data & 0xFF;
data = readw(sdio->hw_base + MSC_RES_OFFSET);
buf[2] = (data >> 8) & 0xFF;
buf[3] = data & 0xFF;
data = readw(sdio->hw_base + MSC_RES_OFFSET);
buf[4] = data & 0xFF;
req->cmd->resp[0] = buf[1] << 24 | buf[2] << 16
| buf[3] << 8 | buf[4];
break;
case RESP_R2:
{
uint32_t i, v, w1, w2;
data = readw(sdio->hw_base + MSC_RES_OFFSET);
v = data & 0xFFFF;
for(i=0; i<4; i++)
{
data = readw(sdio->hw_base + MSC_RES_OFFSET);
w1 = data & 0xFFFF;
data = readw(sdio->hw_base + MSC_RES_OFFSET);
w2 = data & 0xFFFF;
req->cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8;
v = w2;
}
}
break;
default:
break;
}
sdio_dbg("error:%d cmd->resp [%08X, %08X, %08X, %08X]\r\n\r\n",
req->cmd->err,
req->cmd->resp[0],
req->cmd->resp[1],
req->cmd->resp[2],
req->cmd->resp[3]
);
}
if(req->data)
{
unsigned int waligned;
uint32_t len = req->data->blksize * req->data->blks;
/* word aligned ? */
waligned = (((unsigned int)req->data->buf & 0x3) == 0);
if (req->data->flags & DATA_DIR_WRITE)
{
if(waligned)
{
uint32_t i;
uint32_t *src = (uint32_t *)req->data->buf;
for(i=0; i<len; i+=4)
{
while (readl(sdio->hw_base + MSC_STAT_OFFSET) & MSC_STAT_DATA_FIFO_FULL);
writel(*src++, hw_base + MSC_TXFIFO_OFFSET);
}
}
else
{
uint32_t i, data;
uint8_t * src = (uint8_t *)req->data->buf;
for(i=0; i<len; i+=4)
{
while (readl(sdio->hw_base + MSC_STAT_OFFSET) & MSC_STAT_DATA_FIFO_FULL);
data = (*src++ << 0);
data |= (*src++ << 8);
data |= (*src++ << 16);
data |= (*src++ << 24);
writel(data, hw_base + MSC_TXFIFO_OFFSET);
}
}
writel(IFLG_PRG_DONE, hw_base + MSC_IREG_OFFSET);
}
else if (req->data->flags & DATA_DIR_READ)
{
if(waligned)
{
uint32_t i;
uint32_t * dst = (uint32_t *)req->data->buf;
for(i=0; i<len; i+=4)
{
while (readl(sdio->hw_base + MSC_STAT_OFFSET) & MSC_STAT_DATA_FIFO_EMPTY);
*dst ++ = readl(sdio->hw_base + MSC_RXFIFO_OFFSET);
}
}
else
{
uint32_t data, i;
uint8_t * dst = (uint8_t *)req->data->buf;
for(i=0; i<len; i+=4)
{
while (readl(sdio->hw_base + MSC_STAT_OFFSET) & MSC_STAT_DATA_FIFO_EMPTY);
data = readl(sdio->hw_base + MSC_RXFIFO_OFFSET);
*dst++ = (uint8_t)(data >> 0);
*dst++ = (uint8_t)(data >> 8);
*dst++ = (uint8_t)(data >> 16);
*dst++ = (uint8_t)(data >> 24);
}
}
writel(IFLG_DATA_TRAN_DONE, hw_base + MSC_IREG_OFFSET);
}
#if 0
value = readl(hw_base + MSC_IMASK_OFFSET);
value &= ~MSC_DATA_TRAN_DONE;
writel(value, hw_base + MSC_IMASK_OFFSET);
if(!(readl(sdio->hw_base + MSC_IREG_OFFSET) & MSC_DATA_TRAN_DONE))
{
rt_err_t ret;
rt_completion_init(&sdio->completion);
sdio_dbg("TRAN_DONE umask\r\n");
rt_hw_interrupt_umask(sdio->irqno);
ret = rt_completion_wait(&sdio->completion, RT_TICK_PER_SECOND);
if(ret == RT_EOK)
{
sdio_dbg("wait END_CMD_RES OK!\r\n");
}
else
{
rt_kprintf("SD DATA: int status 0x%08x\n", readl(sdio->hw_base + MSC_IREG_OFFSET));
sdio_dbg("wait END_CMD_RES timeout!\r\n");
}
}
else
{
sdio_dbg("no need wait MSC_DATA_TRAN_DONE!\r\n");
}
#endif
/* clear status */
writew(MSC_DATA_TRAN_DONE, hw_base + MSC_IREG_OFFSET);
} /* if req->data */
mmcsd_req_complete(host);
}
static void jz47xx_sdio_set_iocfg(struct rt_mmcsd_host *host,
struct rt_mmcsd_io_cfg *io_cfg)
{
struct jz47xx_sdio * jz_sdio = host->private_data;
rt_uint32_t clkdiv;
sdio_dbg("set_iocfg clock: %d\n", io_cfg->clock);
if (io_cfg->bus_width == MMCSD_BUS_WIDTH_4)
{
sdio_dbg("MMC: Setting controller bus width to 4\n");
jz_sdio->flag |= MSC_CMDAT_BUS_WIDTH_4BIT;
}
else
{
jz_sdio->flag &= ~(MSC_CMDAT_BUS_WIDTH_4BIT);
sdio_dbg("MMC: Setting controller bus width to 1\n");
}
if (io_cfg->clock)
{
unsigned int clk_set = 0, clkrt = 0;
unsigned int clk_want = io_cfg->clock;
unsigned int lpm = 0;
if (io_cfg->clock > 1 * 1000 * 1000)
{
io_cfg->clock = 1000 * 1000;
}
jz_mmc_clk_autoctrl(jz_sdio, 1);
if (clk_want > 3000000)
{
clk_set_rate(jz_sdio->clock, io_cfg->clock);
}
else
{
clk_set_rate(jz_sdio->clock, 24000000);
}
clk_set = clk_get_rate(jz_sdio->clock);
while (clk_want < clk_set)
{
clkrt++;
clk_set >>= 1;
}
if (clkrt > 7)
{
sdio_dbg("invalid value of CLKRT: "
"ios->clock=%d clk_want=%d "
"clk_set=%d clkrt=%X,\n",
io_cfg->clock, clk_want, clk_set, clkrt);
return;
}
if (!clkrt)
{
sdio_dbg("clk_want: %u, clk_set: %luHz\n", io_cfg->clock, clk_get_rate(jz_sdio->clock));
}
writel(clkrt, jz_sdio->hw_base + MSC_CLKRT_OFFSET);
if (clk_set > 25000000)
{
lpm = (0x2 << LPM_DRV_SEL_SHF) | LPM_SMP_SEL;
}
if(jz_sdio->sdio_clk)
{
writel(lpm, jz_sdio->hw_base + MSC_LPM_OFFSET);
writel(MSC_CTRL_CLOCK_START, jz_sdio->hw_base + MSC_CTRL_OFFSET);
}
else
{
lpm |= LPM_LPM;
writel(lpm, jz_sdio->hw_base + MSC_LPM_OFFSET);
}
}
else
{
jz_mmc_clk_autoctrl(jz_sdio, 0);
}
/* maybe switch power to the card */
switch (io_cfg->power_mode)
{
case MMCSD_POWER_OFF:
sdio_dbg("MMCSD_POWER_OFF\r\n");
break;
case MMCSD_POWER_UP:
sdio_dbg("MMCSD_POWER_UP\r\n");
break;
case MMCSD_POWER_ON:
sdio_dbg("MMCSD_POWER_ON\r\n");
jz_mmc_hardware_init(jz_sdio);
// jz_mmc_set_clock(jz_sdio, io_cfg->clock);
break;
default:
sdio_dbg("unknown power_mode %d\n", io_cfg->power_mode);
break;
}
}
static rt_int32_t jz47xx_SD_Detect(struct rt_mmcsd_host *host)
{
sdio_dbg("jz47xx_SD_Detect\n");
}
static void jz47xx_sdio_enable_sdio_irq(struct rt_mmcsd_host *host,
rt_int32_t enable)
{
sdio_dbg("jz47xx_sdio_enable_sdio_irq, enable:%d\n", enable);
}
static const struct rt_mmcsd_host_ops ops =
{
jz47xx_sdio_request,
jz47xx_sdio_set_iocfg,
jz47xx_SD_Detect,
jz47xx_sdio_enable_sdio_irq,
};
int jz47xx_sdio_init(void)
{
struct rt_mmcsd_host *host = RT_NULL;
struct jz47xx_sdio * jz_sdio = RT_NULL;
#ifdef RT_USING_MSC0
host = mmcsd_alloc_host();
jz_sdio = rt_malloc(sizeof(struct jz47xx_sdio));
if(!(host && jz_sdio))
{
goto err;
}
rt_memset(jz_sdio, 0, sizeof(struct jz47xx_sdio));
/* set hardware base firstly */
jz_sdio->hw_base = MSC0_BASE;
jz_sdio->clock = clk_get("cgu_msc0");
jz_sdio->clock_gate = clk_get("msc0");
/* init GPIO (msc0 boot)
* name pin fun
* X1000 MSC0_D0: PA23 1
* X1000 MSC0_D1: PA22 1
* X1000 MSC0_D2: PA21 1
* X1000 MSC0_D3: PA20 1
* X1000 MSC0_CMD: PA25 1
* X1000 MSC0_CLK: PA24 1
*/
{
gpio_set_func(GPIO_PORT_A, GPIO_Pin_20, GPIO_FUNC_1);
gpio_set_func(GPIO_PORT_A, GPIO_Pin_21, GPIO_FUNC_1);
gpio_set_func(GPIO_PORT_A, GPIO_Pin_22, GPIO_FUNC_1);
gpio_set_func(GPIO_PORT_A, GPIO_Pin_23, GPIO_FUNC_1);
gpio_set_func(GPIO_PORT_A, GPIO_Pin_24, GPIO_FUNC_1);
gpio_set_func(GPIO_PORT_A, GPIO_Pin_25, GPIO_FUNC_1);
}
/* enable MSC0 clock gate. */
clk_enable(jz_sdio->clock_gate);
jz_sdio->msc_clock = 24UL * 1000 * 1000; /* 50Mhz */
host->freq_min = 400 * 1000; /* min 400Khz. */
host->freq_max = 24 * 1000 * 1000; /* max 50Mhz. */
/* set clock */
clk_set_rate(jz_sdio->clock, BOARD_EXTAL_CLK);
host->ops = &ops;
host->valid_ocr = VDD_27_28 | VDD_28_29 | VDD_29_30 | VDD_30_31 | VDD_31_32 |
VDD_32_33 | VDD_33_34 | VDD_34_35 | VDD_35_36;
host->flags = MMCSD_BUSWIDTH_4 | MMCSD_MUTBLKWRITE | MMCSD_SUP_HIGHSPEED;
host->max_seg_size = 65535;
host->max_dma_segs = 2;
host->max_blk_size = 512;
host->max_blk_count = 4096;
host->private_data = jz_sdio;
jz_sdio->host = host;
jz_sdio->irqno = IRQ_MSC0;
rt_hw_interrupt_install(jz_sdio->irqno, msc_handler, jz_sdio, "msc0");
rt_hw_interrupt_mask(jz_sdio->irqno);
mmcsd_change(host);
#endif // RT_USING_MSC0
#ifdef RT_USING_MSC1
host = mmcsd_alloc_host();
jz_sdio = rt_malloc(sizeof(struct jz47xx_sdio));
if(!(host && jz_sdio))
{
goto err;
}
rt_memset(jz_sdio, 0, sizeof(struct jz47xx_sdio));
jz_sdio->hw_base = MSC1_BASE;
jz_sdio->clock = clk_get("cgu_msc1");
jz_sdio->clock_gate = clk_get("msc1");
/* init GPIO (paladin msc1 SDIO wifi)
* name pin fun
* X1000 MSC1_D0: PC02 0
* X1000 MSC1_D1: PC03 0
* X1000 MSC1_D2: PC04 0
* X1000 MSC1_D3: PC05 0
* X1000 MSC1_CMD: PC01 0
* X1000 MSC1_CLK: PC00 0
*
*/
{
gpio_set_func(GPIO_PORT_C, GPIO_Pin_0, GPIO_FUNC_0);
gpio_set_func(GPIO_PORT_C, GPIO_Pin_1, GPIO_FUNC_0);
gpio_set_func(GPIO_PORT_C, GPIO_Pin_2, GPIO_FUNC_0);
gpio_set_func(GPIO_PORT_C, GPIO_Pin_3, GPIO_FUNC_0);
gpio_set_func(GPIO_PORT_C, GPIO_Pin_4, GPIO_FUNC_0);
gpio_set_func(GPIO_PORT_C, GPIO_Pin_5, GPIO_FUNC_0);
}
/* enable MSC1 clock gate. */
clk_enable(jz_sdio->clock_gate);
jz_sdio->msc_clock = 50UL * 1000 * 1000; /* 50Mhz */
host->freq_min = 400 * 1000; /* min 400Khz. */
host->freq_max = 50 * 1000 * 1000; /* max 50Mhz. */
/* set clock */
clk_set_rate(jz_sdio->clock, BOARD_EXTAL_CLK);
host->ops = &ops;
host->valid_ocr = VDD_27_28 | VDD_28_29 | VDD_29_30 | VDD_30_31 | VDD_31_32 |
VDD_32_33 | VDD_33_34 | VDD_34_35 | VDD_35_36;
host->flags = MMCSD_BUSWIDTH_4 | MMCSD_MUTBLKWRITE;
host->max_seg_size = 65535;
host->max_dma_segs = 2;
host->max_blk_size = 512;
host->max_blk_count = 4096;
host->private_data = jz_sdio;
jz_sdio->host = host;
jz_sdio->irqno = IRQ_MSC1;
rt_hw_interrupt_install(jz_sdio->irqno, msc_handler, jz_sdio, "msc1");
rt_hw_interrupt_mask(jz_sdio->irqno);
mmcsd_change(host);
#endif // RT_USING_MSC1
return RT_EOK;
err:
if(host)
{
mmcsd_free_host(host);
}
if(jz_sdio)
{
rt_free(host);
}
return -RT_ENOMEM;
}