1135 lines
30 KiB
C
1135 lines
30 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"
|
||
|
||
#include <cache.h>
|
||
#include <string.h>
|
||
|
||
#define DMA_BUFFER
|
||
#define DMA_ALIGN (32U)
|
||
#define PIO_THRESHOLD 64 /* use pio mode if data length < PIO_THRESHOLD */
|
||
|
||
#define DEBUG_ENABLE
|
||
#define DEBUG_SECTION_NAME "[SDIO]"
|
||
#define DEBUG_LEVEL DBG_INFO
|
||
// #define DEBUG_LEVEL DBG_LOG
|
||
#define DEBUG_COLOR
|
||
|
||
#include <rtdbg.h>
|
||
|
||
/*
|
||
* Error status including CRC_READ_ERROR, CRC_WRITE_ERROR,
|
||
* CRC_RES_ERR, TIME_OUT_RES, TIME_OUT_READ
|
||
*/
|
||
#define ERROR_STAT 0x3f
|
||
|
||
#define JZMMC_USE_PIO 2
|
||
|
||
/* Register access macros */
|
||
#define msc_readl(host,reg) \
|
||
readl((host)->hw_base + reg)
|
||
#define msc_writel(host,reg,value) \
|
||
writel((value), (host)->hw_base + (reg))
|
||
|
||
#define is_pio_mode(host) \
|
||
(host->flags & (1 << JZMMC_USE_PIO))
|
||
#define enable_pio_mode(host) \
|
||
(host->flags |= (1 << JZMMC_USE_PIO))
|
||
#define disable_pio_mode(host) \
|
||
(host->flags &= ~(1 << JZMMC_USE_PIO))
|
||
|
||
/*-------------------End structure and macro define------------------------*/
|
||
|
||
#ifdef DMA_BUFFER
|
||
ALIGN(32)
|
||
uint8_t _dma_buffer_0[32 * 1024];
|
||
ALIGN(32)
|
||
uint8_t _dma_buffer_1[32 * 1024];
|
||
#endif
|
||
|
||
struct jzmmc_host *jz_host1 = RT_NULL;
|
||
volatile static int stopping_clock = 0;
|
||
volatile static int sdio_log = 0;
|
||
|
||
/*
|
||
* Functional functions.
|
||
*
|
||
* These small function will be called frequently.
|
||
*/
|
||
rt_inline void enable_msc_irq(struct jzmmc_host *host, unsigned long bits)
|
||
{
|
||
unsigned long imsk;
|
||
|
||
imsk = msc_readl(host, MSC_IMASK_OFFSET);
|
||
imsk &= ~bits;
|
||
msc_writel(host, MSC_IMASK_OFFSET, imsk);
|
||
}
|
||
|
||
rt_inline void clear_msc_irq(struct jzmmc_host *host, unsigned long bits)
|
||
{
|
||
msc_writel(host, MSC_IREG_OFFSET, bits);
|
||
}
|
||
|
||
rt_inline void disable_msc_irq(struct jzmmc_host *host, unsigned long bits)
|
||
{
|
||
unsigned long imsk;
|
||
|
||
imsk = msc_readl(host, MSC_IMASK_OFFSET);
|
||
imsk |= bits;
|
||
msc_writel(host, MSC_IMASK_OFFSET, imsk);
|
||
}
|
||
|
||
static inline int check_error_status(struct jzmmc_host *host, unsigned int status)
|
||
{
|
||
if (status & ERROR_STAT)
|
||
{
|
||
dbg_log(DBG_LOG, "Error status->0x%08X: cmd=%d\n", status, host->cmd->cmd_code);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Stop the MMC clock and wait while it happens */
|
||
rt_inline rt_err_t jzmmc_stop_clock(uint32_t hw_base)
|
||
{
|
||
uint16_t value;
|
||
int timeout = 10000;
|
||
|
||
stopping_clock = 1;
|
||
|
||
value = readw(hw_base + MSC_CTRL_OFFSET);
|
||
value &= ~MSC_CTRL_CLOCK_CONTROL_MASK;
|
||
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)
|
||
{
|
||
rt_kprintf("stop clock timeout!\n");
|
||
stopping_clock = 0;
|
||
return -RT_ETIMEOUT;
|
||
}
|
||
rt_thread_delay(1);
|
||
}
|
||
|
||
stopping_clock = 0;
|
||
return RT_EOK;
|
||
}
|
||
|
||
/* Start the MMC clock and operation */
|
||
rt_inline void jzmmc_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 jzmmc_polling_status(struct jzmmc_host *host, unsigned int status)
|
||
{
|
||
unsigned int cnt = 100 * 1000 * 1000;
|
||
|
||
while(!(msc_readl(host, MSC_STAT_OFFSET) & (status | ERROR_STAT)) \
|
||
&& (--cnt));
|
||
|
||
if (!cnt)
|
||
{
|
||
dbg_log(DBG_LOG, "polling status(0x%08X) time out, "
|
||
"op=%d, status=0x%08X\n", status,
|
||
host->cmd->cmd_code, msc_readl(host, MSC_STAT_OFFSET));
|
||
return -1;
|
||
}
|
||
|
||
if (msc_readl(host, MSC_STAT_OFFSET) & ERROR_STAT)
|
||
{
|
||
dbg_log(DBG_LOG, "polling status(0x%08X) error, "
|
||
"op=%d, status=0x%08X\n", status,
|
||
host->cmd->cmd_code, msc_readl(host, MSC_STAT_OFFSET));
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
rt_inline void jzmmc_stop_dma(struct jzmmc_host *host)
|
||
{
|
||
/*
|
||
* Theoretically, DMA can't be stopped when transfering, so we can only
|
||
* diable it when it is out of DMA request.
|
||
*/
|
||
msc_writel(host, MSC_DMAC_OFFSET, 0);
|
||
}
|
||
|
||
static void jzmmc_command_done(struct jzmmc_host *host, struct rt_mmcsd_cmd *cmd)
|
||
{
|
||
int i;
|
||
unsigned long res;
|
||
|
||
uint8_t buf[16];
|
||
uint32_t data;
|
||
|
||
memset(cmd->resp, 0x0, sizeof(cmd->resp));
|
||
|
||
if ((host->cmdat & MSC_CMDAT_RESP_FORMAT_MASK) == MSC_CMDAT_RESPONSE_R2)
|
||
{
|
||
res = msc_readl(host, MSC_RES_OFFSET);
|
||
for (i = 0 ; i < 4 ; i++) {
|
||
cmd->resp[i] = res << 24;
|
||
res = msc_readl(host, MSC_RES_OFFSET);
|
||
cmd->resp[i] |= res << 8;
|
||
res = msc_readl(host, MSC_RES_OFFSET);
|
||
cmd->resp[i] |= res >> 8;
|
||
}
|
||
}
|
||
else if ((host->cmdat & MSC_CMDAT_RESP_FORMAT_MASK) == MSC_CMDAT_RESPONSE_NONE)
|
||
{
|
||
}
|
||
else
|
||
{
|
||
res = msc_readl(host, MSC_RES_OFFSET);
|
||
cmd->resp[0] = res << 24;
|
||
res = msc_readl(host, MSC_RES_OFFSET);
|
||
cmd->resp[0] |= res << 8;
|
||
res = msc_readl(host, MSC_RES_OFFSET);
|
||
cmd->resp[0] |= res & 0xff;
|
||
}
|
||
|
||
dbg_log(DBG_LOG, "error:%d cmd->resp [%08X, %08X, %08X, %08X]\r\n\r\n",
|
||
cmd->err,
|
||
cmd->resp[0],
|
||
cmd->resp[1],
|
||
cmd->resp[2],
|
||
cmd->resp[3]
|
||
);
|
||
|
||
clear_msc_irq(host, IFLG_END_CMD_RES);
|
||
}
|
||
|
||
static void jzmmc_data_done(struct jzmmc_host *host)
|
||
{
|
||
struct rt_mmcsd_data *data = host->data;
|
||
|
||
if (host->cmd->err == RT_EOK)
|
||
{
|
||
data->bytes_xfered = (data->blks * data->blksize);
|
||
jzmmc_stop_dma(host);
|
||
}
|
||
else
|
||
{
|
||
jzmmc_stop_dma(host);
|
||
data->bytes_xfered = 0;
|
||
dbg_log(DBG_LOG, "error when request done\n");
|
||
}
|
||
}
|
||
|
||
#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ')
|
||
static void dump_hex(const rt_uint8_t *ptr, rt_size_t buflen)
|
||
{
|
||
unsigned char *buf = (unsigned char*)ptr;
|
||
int i, j;
|
||
|
||
for (i=0; i<buflen; i+=16)
|
||
{
|
||
rt_kprintf("%08X: ", i);
|
||
|
||
for (j=0; j<16; j++)
|
||
if (i+j < buflen)
|
||
rt_kprintf("%02X ", buf[i+j]);
|
||
else
|
||
rt_kprintf(" ");
|
||
rt_kprintf(" ");
|
||
|
||
for (j=0; j<16; j++)
|
||
if (i+j < buflen)
|
||
rt_kprintf("%c", __is_print(buf[i+j]) ? buf[i+j] : '.');
|
||
rt_kprintf("\n");
|
||
}
|
||
}
|
||
|
||
/*------------------------End functional functions-------------------------*/
|
||
|
||
rt_inline void jzmmc_submit_dma(struct jzmmc_host *host, struct rt_mmcsd_data *data)
|
||
{
|
||
host->dma_desc.nda = 0;
|
||
host->dma_desc.len = data->blks * data->blksize;
|
||
host->dma_desc.da = virt_to_phys(data->buf);
|
||
host->dma_desc.dcmd = DMACMD_ENDI | DMACMD_LINK; /* only one DMA descriptor */
|
||
|
||
#ifdef DMA_BUFFER
|
||
if ((uint32_t)(data->buf) & (DMA_ALIGN - 1))
|
||
{
|
||
/* not align */
|
||
host->dma_desc.da = virt_to_phys(host->_dma_buffer);
|
||
if (data->flags & DATA_DIR_WRITE)
|
||
{
|
||
dbg_log(DBG_LOG, "%d ->", data->blks * data->blksize);
|
||
memcpy(host->_dma_buffer, data->buf, data->blks * data->blksize);
|
||
rt_hw_dcache_flush_range((rt_ubase_t)(host->_dma_buffer), data->blks * data->blksize);
|
||
|
||
dbg_log(DBG_LOG, "| 0x%08x\n", data->buf);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (data->flags & DATA_DIR_WRITE)
|
||
{
|
||
rt_hw_dcache_flush_range((rt_ubase_t)(data->buf), data->blks * data->blksize);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
}
|
||
|
||
// #define PERFORMANCE_DMA
|
||
rt_inline void jzmmc_dma_start(struct jzmmc_host *host, struct rt_mmcsd_data *data)
|
||
{
|
||
volatile int i = 120;
|
||
uint32_t dma_addr = virt_to_phys(data->buf);
|
||
unsigned int dma_len = data->blks * data->blksize;
|
||
unsigned int dmac;
|
||
#ifdef PERFORMANCE_DMA
|
||
dmac = (0x01 << DMAC_INCR_SHF) | DMAC_DMAEN | DMAC_MODE_SEL;
|
||
#else
|
||
dmac = (0x01 << DMAC_INCR_SHF) | DMAC_DMAEN;
|
||
#endif
|
||
|
||
#ifndef DMA_BUFFER
|
||
if ((dma_addr & (DMA_ALIGN - 1)) || (dma_len & (DMA_ALIGN - 1)))
|
||
{
|
||
dbg_log(DBG_LOG, "DMA align, addr=0x%08x\n", dma_addr);
|
||
dmac |= DMAC_ALIGNEN;
|
||
if (dma_addr & (DMA_ALIGN - 1))
|
||
{
|
||
dmac |= (dma_addr & (DMA_ALIGN - 1)) << DMAC_AOFST_SHF;
|
||
}
|
||
}
|
||
#endif
|
||
dbg_log(DBG_LOG, "DMA start: nda 0x%08x, da 0x%08x, len 0x%04x, cmd 0x%08x\n", virt_to_phys(&(host->dma_desc)),
|
||
host->dma_desc.da, host->dma_desc.len, host->dma_desc.dcmd);
|
||
|
||
rt_hw_dcache_flush_range((rt_ubase_t)(&(host->dma_desc)), 32);
|
||
while(i--); //TODO:<3A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ,<2C><><EFBFBD><EFBFBD>ʱ<EFBFBD>ᷢ<EFBFBD><E1B7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
msc_writel(host, MSC_DMANDA_OFFSET, virt_to_phys(&(host->dma_desc)));
|
||
msc_writel(host, MSC_DMAC_OFFSET, dmac);
|
||
}
|
||
|
||
/*----------------------------End DMA handler------------------------------*/
|
||
|
||
/*
|
||
* PIO transfer mode.
|
||
*
|
||
* Functions of PIO read/write mode that can handle 1, 2 or 3 bytes transfer
|
||
* even though the FIFO register is 32-bits width.
|
||
* It's better just used for test.
|
||
*/
|
||
static int wait_cmd_response(struct jzmmc_host *host)
|
||
{
|
||
if (!(msc_readl(host, MSC_IREG_OFFSET) & IFLG_END_CMD_RES))
|
||
{
|
||
rt_err_t ret;
|
||
|
||
rt_completion_init(&host->completion);
|
||
|
||
enable_msc_irq(host, IMASK_TIME_OUT_RES | IMASK_END_CMD_RES);
|
||
|
||
rt_hw_interrupt_umask(host->irqno);
|
||
ret = rt_completion_wait(&host->completion, RT_TICK_PER_SECOND);
|
||
|
||
clear_msc_irq(host, IFLG_TIMEOUT_RES | IFLG_END_CMD_RES);
|
||
disable_msc_irq(host, IFLG_TIMEOUT_RES | IFLG_END_CMD_RES);
|
||
|
||
if(ret == RT_EOK)
|
||
{
|
||
dbg_log(DBG_LOG, "wait response OK!\r\n");
|
||
}
|
||
else
|
||
{
|
||
uint32_t value;
|
||
|
||
value = msc_readl(host, MSC_STAT_OFFSET);
|
||
dbg_log(DBG_LOG, "stat=0x%08x\n", value);
|
||
value = msc_readl(host, MSC_IREG_OFFSET);
|
||
dbg_log(DBG_LOG, "iflag=0x%08x\n", value);
|
||
|
||
host->cmd->err = ret;
|
||
dbg_log(DBG_LOG, "wait END_CMD_RES timeout[uncompletion]\r\n");
|
||
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
msc_writel(host, MSC_IREG_OFFSET, IFLG_END_CMD_RES);
|
||
return 0;
|
||
}
|
||
|
||
static void do_pio_read(struct jzmmc_host *host,
|
||
unsigned int *addr, unsigned int cnt)
|
||
{
|
||
int i = 0;
|
||
unsigned int status = 0;
|
||
|
||
for (i = 0; i < cnt / 4; i++)
|
||
{
|
||
while (((status = msc_readl(host, MSC_STAT_OFFSET))
|
||
& MSC_STAT_DATA_FIFO_EMPTY));
|
||
|
||
if (check_error_status(host, status))
|
||
{
|
||
host->cmd->err = -RT_EIO;
|
||
return;
|
||
}
|
||
*addr++ = msc_readl(host, MSC_RXFIFO_OFFSET);
|
||
}
|
||
|
||
/*
|
||
* These codes handle the last 1, 2 or 3 bytes transfer.
|
||
*/
|
||
if (cnt & 3)
|
||
{
|
||
uint32_t n = cnt & 3;
|
||
uint32_t data = msc_readl(host, MSC_RXFIFO_OFFSET);
|
||
uint8_t *p = (u8 *)addr;
|
||
|
||
while (n--)
|
||
{
|
||
*p++ = data;
|
||
data >>= 8;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void do_pio_write(struct jzmmc_host *host,
|
||
unsigned int *addr, unsigned int cnt)
|
||
{
|
||
int i = 0;
|
||
unsigned int status = 0;
|
||
|
||
for (i = 0; i < (cnt / 4); i++)
|
||
{
|
||
while (((status = msc_readl(host, MSC_STAT_OFFSET))
|
||
& MSC_STAT_DATA_FIFO_FULL));
|
||
|
||
if (check_error_status(host, status))
|
||
{
|
||
host->cmd->err = -RT_EIO;
|
||
return;
|
||
}
|
||
msc_writel(host, MSC_TXFIFO_OFFSET, *addr++);
|
||
}
|
||
|
||
/*
|
||
* These codes handle the last 1, 2 or 3 bytes transfer.
|
||
*/
|
||
if (cnt & 3)
|
||
{
|
||
uint32_t data = 0;
|
||
uint8_t *p = (uint8_t *)addr;
|
||
|
||
for (i = 0; i < (cnt & 3); i++)
|
||
data |= *p++ << (8 * i);
|
||
|
||
msc_writel(host, MSC_TXFIFO_OFFSET, data);
|
||
}
|
||
}
|
||
|
||
static inline void pio_trans_start(struct jzmmc_host *host, struct rt_mmcsd_data *data)
|
||
{
|
||
unsigned int *addr = (unsigned int *)data->buf;
|
||
unsigned int cnt = data->blks * data->blksize;
|
||
|
||
if (data->flags & DATA_DIR_WRITE)
|
||
do_pio_write(host, addr, cnt);
|
||
else
|
||
do_pio_read(host, addr, cnt);
|
||
}
|
||
|
||
static void pio_trans_done(struct jzmmc_host *host, struct rt_mmcsd_data *data)
|
||
{
|
||
if (host->cmd->err == RT_EOK)
|
||
data->bytes_xfered = data->blks * data->blksize;
|
||
else
|
||
data->bytes_xfered = 0;
|
||
|
||
if (host->req->stop)
|
||
{
|
||
if (jzmmc_polling_status(host, MSC_STAT_AUTO_CMD_DONE) < 0)
|
||
host->cmd->err = -RT_EIO;
|
||
}
|
||
|
||
if (data->flags & DATA_DIR_WRITE)
|
||
{
|
||
if (jzmmc_polling_status(host, MSC_STAT_PRG_DONE) < 0)
|
||
{
|
||
host->cmd->err = -RT_EIO;
|
||
}
|
||
clear_msc_irq(host, IFLG_PRG_DONE);
|
||
}
|
||
else
|
||
{
|
||
if (jzmmc_polling_status(host, MSC_STAT_DATA_TRAN_DONE) < 0)
|
||
{
|
||
host->cmd->err = -RT_EIO;
|
||
}
|
||
clear_msc_irq(host, IFLG_DATA_TRAN_DONE);
|
||
}
|
||
}
|
||
|
||
/*-------------------------End PIO transfer mode---------------------------*/
|
||
|
||
/*
|
||
* Achieve mmc_request here.
|
||
*/
|
||
static void jzmmc_data_pre(struct jzmmc_host *host, struct rt_mmcsd_data *data)
|
||
{
|
||
unsigned int nob = data->blks;
|
||
unsigned long cmdat,imsk;
|
||
|
||
msc_writel(host, MSC_RDTO_OFFSET, 0xffffff);
|
||
msc_writel(host, MSC_NOB_OFFSET, nob);
|
||
msc_writel(host, MSC_BLKLEN_OFFSET, data->blksize);
|
||
cmdat = MSC_CMDAT_DATA_EN;
|
||
|
||
msc_writel(host, MSC_CMDAT_OFFSET, MSC_CMDAT_DATA_EN);
|
||
|
||
if (data->flags & DATA_DIR_WRITE)
|
||
{
|
||
cmdat |= MSC_CMDAT_WRITE;
|
||
imsk = IMASK_WR_ALL_DONE | IMASK_CRC_WRITE_ERR;
|
||
}
|
||
else if (data->flags & DATA_DIR_READ)
|
||
{
|
||
cmdat &= ~MSC_CMDAT_WRITE;
|
||
imsk = IMASK_DMA_DATA_DONE | IMASK_TIME_OUT_READ | IMASK_CRC_READ_ERR;
|
||
}
|
||
else
|
||
{
|
||
rt_kprintf("data direction confused\n");
|
||
}
|
||
|
||
host->cmdat |= cmdat;
|
||
|
||
if (!is_pio_mode(host))
|
||
{
|
||
jzmmc_submit_dma(host, data);
|
||
clear_msc_irq(host, IFLG_PRG_DONE);
|
||
enable_msc_irq(host, imsk);
|
||
}
|
||
}
|
||
|
||
static void jzmmc_data_start(struct jzmmc_host *host, struct rt_mmcsd_data *data)
|
||
{
|
||
if (is_pio_mode(host))
|
||
{
|
||
pio_trans_start(host, data);
|
||
pio_trans_done(host, data);
|
||
|
||
disable_pio_mode(host);
|
||
}
|
||
else
|
||
{
|
||
rt_err_t ret;
|
||
|
||
rt_completion_init(&host->completion);
|
||
|
||
/* start DMA */
|
||
disable_msc_irq(host, IFLG_END_CMD_RES);
|
||
jzmmc_dma_start(host, data);
|
||
|
||
rt_hw_interrupt_umask(host->irqno);
|
||
ret = rt_completion_wait(&host->completion, RT_TICK_PER_SECOND);
|
||
|
||
if (ret != RT_EOK)
|
||
{
|
||
rt_kprintf("warning: msc dma timeout\n");
|
||
}
|
||
else
|
||
{
|
||
dbg_log(DBG_LOG, "msc status: 0x%08x\n", msc_readl(host, MSC_STAT_OFFSET));
|
||
|
||
clear_msc_irq(host, IFLG_DATA_TRAN_DONE | IFLG_DMAEND | IFLG_DMA_DATA_DONE | IFLG_TIMEOUT_RES);
|
||
disable_msc_irq(host, IMASK_DMA_DATA_DONE | IMASK_CRC_READ_ERR);
|
||
|
||
#ifdef DMA_BUFFER
|
||
if ((data->flags & DATA_DIR_READ))
|
||
{
|
||
if((uint32_t)data->buf & (DMA_ALIGN - 1))
|
||
{
|
||
rt_hw_dcache_invalidate_range((rt_ubase_t)(host->_dma_buffer), data->blks * data->blksize);
|
||
memcpy(data->buf, host->_dma_buffer, data->blks * data->blksize);
|
||
dbg_log(DBG_LOG, "0x%08x <-| %d\n", data->buf, data->blks * data->blksize);
|
||
}
|
||
else
|
||
{
|
||
rt_hw_dcache_invalidate_range((rt_ubase_t)(data->buf), data->blks * data->blksize);
|
||
}
|
||
|
||
}
|
||
#endif
|
||
}
|
||
|
||
jzmmc_data_done(host);
|
||
}
|
||
}
|
||
|
||
static void jzmmc_command_start(struct jzmmc_host *host, struct rt_mmcsd_cmd *cmd)
|
||
{
|
||
unsigned long cmdat = 0;
|
||
unsigned long imsk;
|
||
|
||
/* auto send stop */
|
||
if (host->req->stop) cmdat |= MSC_CMDAT_SEND_AS_STOP;
|
||
|
||
/* handle response type */
|
||
switch (cmd->flags & RESP_MASK)
|
||
{
|
||
#define _CASE(S,D) case RESP_##S: cmdat |= MSC_CMDAT_RESPONSE_##D; break
|
||
_CASE(R1, R1); /* r1 */
|
||
_CASE(R2, R2);
|
||
_CASE(R3, R3); /* r3 */
|
||
_CASE(R4, R4); /* r4 */
|
||
_CASE(R5, R5);
|
||
_CASE(R6, R6);
|
||
_CASE(R7, R7);
|
||
default:
|
||
break;
|
||
#undef _CASE
|
||
}
|
||
if ((cmd->flags & RESP_MASK) == RESP_R1B) cmdat |= MSC_CMDAT_BUSY;
|
||
|
||
host->cmdat |= cmdat;
|
||
|
||
if (!is_pio_mode(host))
|
||
{
|
||
imsk = IMASK_TIME_OUT_RES | IMASK_END_CMD_RES;
|
||
enable_msc_irq(host, imsk);
|
||
}
|
||
|
||
dbg_log(DBG_LOG, "dat: 0x%08x\n", host->cmdat);
|
||
dbg_log(DBG_LOG, "resp type: %d\n", cmd->flags & RESP_MASK);
|
||
|
||
writel(0xFF, host->hw_base + MSC_RESTO_OFFSET);
|
||
writel(0xFFFFFFFF, host->hw_base + MSC_RDTO_OFFSET);
|
||
|
||
msc_writel(host, MSC_CMD_OFFSET, cmd->cmd_code);
|
||
msc_writel(host, MSC_ARG_OFFSET, cmd->arg);
|
||
msc_writel(host, MSC_CMDAT_OFFSET, host->cmdat);
|
||
msc_writel(host, MSC_CTRL_OFFSET, MSC_CTRL_START_OP);
|
||
|
||
jzmmc_start_clock(host->hw_base);
|
||
cmd->err = RT_EOK;
|
||
|
||
if (is_pio_mode(host))
|
||
{
|
||
wait_cmd_response(host);
|
||
jzmmc_command_done(host, host->cmd);
|
||
}
|
||
}
|
||
|
||
static void jzmmc_sdio_request(struct rt_mmcsd_host *mmc, struct rt_mmcsd_req *req)
|
||
{
|
||
struct jzmmc_host *host = mmc->private_data;
|
||
char direction = '\0';
|
||
|
||
host->req = req;
|
||
host->data = req->data;
|
||
host->cmd = req->cmd;
|
||
host->cmdat = 0;
|
||
|
||
dbg_log(DBG_LOG, "CMD: %d ARG: %08X\n", req->cmd->cmd_code, req->cmd->arg);
|
||
if (host->data)
|
||
{
|
||
direction = (host->data->flags & DATA_DIR_WRITE)? 'w' : 'r';
|
||
}
|
||
|
||
jzmmc_stop_clock(host->hw_base);
|
||
|
||
/* disable pio mode firstly */
|
||
disable_pio_mode(host);
|
||
|
||
/* clear status */
|
||
writew(0xFFFF, host->hw_base + MSC_IREG_OFFSET);
|
||
disable_msc_irq(host, 0xffffffff);
|
||
|
||
if (host->flags & MSC_CMDAT_BUS_WIDTH_4BIT)
|
||
{
|
||
host->cmdat |= MSC_CMDAT_BUS_WIDTH_4BIT;
|
||
}
|
||
|
||
if(req->cmd->cmd_code == GO_IDLE_STATE)
|
||
{
|
||
host->cmdat |= MSC_CMDAT_INIT;
|
||
}
|
||
|
||
if(host->data)
|
||
{
|
||
dbg_log(DBG_LOG, "with data, datalen = %d\n", host->data->blksize * host->data->blks);
|
||
if (host->data->blksize * host->data->blks < PIO_THRESHOLD)
|
||
{
|
||
dbg_log(DBG_LOG, " pio mode!\n");
|
||
enable_pio_mode(host);
|
||
}
|
||
|
||
jzmmc_data_pre(host, host->data);
|
||
}
|
||
else
|
||
{
|
||
writew(0, host->hw_base + MSC_BLKLEN_OFFSET);
|
||
writew(0, host->hw_base + MSC_NOB_OFFSET);
|
||
|
||
enable_pio_mode(host);
|
||
}
|
||
|
||
jzmmc_command_start(host, host->cmd);
|
||
if (host->data)
|
||
{
|
||
jzmmc_data_start(host, host->data);
|
||
}
|
||
|
||
mmcsd_req_complete(mmc);
|
||
}
|
||
|
||
static void jzmmc_isr(int irqno, void* param)
|
||
{
|
||
uint32_t pending;
|
||
uint32_t pending_;
|
||
|
||
struct jzmmc_host * host = (struct jzmmc_host *)param;
|
||
|
||
pending_ = msc_readl(host, MSC_IREG_OFFSET);
|
||
pending = msc_readl(host, MSC_IREG_OFFSET) & (~ msc_readl(host, MSC_IMASK_OFFSET));
|
||
|
||
if(pending_ & IFLG_CRC_RES_ERR)
|
||
{
|
||
dbg_log(DBG_WARNING, "RES CRC err\n");
|
||
}
|
||
if(pending_ & IFLG_CRC_READ_ERR)
|
||
{
|
||
dbg_log(DBG_WARNING, "READ CRC err\n");
|
||
}
|
||
if(pending_ & IFLG_CRC_WRITE_ERR)
|
||
{
|
||
dbg_log(DBG_WARNING, "WRITE CRC err\n");
|
||
}
|
||
|
||
|
||
if (pending & IFLG_TIMEOUT_RES)
|
||
{
|
||
host->cmd->err = -RT_ETIMEOUT;
|
||
dbg_log(DBG_LOG, "TIMEOUT\n");
|
||
}
|
||
else if (pending & IFLG_CRC_READ_ERR)
|
||
{
|
||
host->cmd->err = -RT_EIO;
|
||
dbg_log(DBG_WARNING, "CRC READ\n");
|
||
}
|
||
else if (pending & (IFLG_CRC_RES_ERR | IFLG_CRC_WRITE_ERR | IFLG_TIMEOUT_READ))
|
||
{
|
||
dbg_log(DBG_ERROR, "MSC ERROR, pending=0x%08x\n", pending);
|
||
}
|
||
|
||
if (pending & (IFLG_DMA_DATA_DONE | IFLG_WR_ALL_DONE))
|
||
{
|
||
dbg_log(DBG_LOG, "msc DMA end!\n");
|
||
|
||
/* disable interrupt */
|
||
rt_hw_interrupt_mask(host->irqno);
|
||
rt_completion_done(&host->completion);
|
||
}
|
||
else if (pending & (MSC_TIME_OUT_RES | MSC_END_CMD_RES))
|
||
{
|
||
/* disable interrupt */
|
||
rt_hw_interrupt_mask(host->irqno);
|
||
rt_completion_done(&host->completion);
|
||
}
|
||
}
|
||
|
||
rt_inline void jzmmc_clk_autoctrl(struct jzmmc_host *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);
|
||
}
|
||
}
|
||
|
||
static int jzmmc_hardware_init(struct jzmmc_host *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 */
|
||
jzmmc_stop_clock(hw_base);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* RT-Thread SDIO interface */
|
||
static void jzmmc_sdio_set_iocfg(struct rt_mmcsd_host *host,
|
||
struct rt_mmcsd_io_cfg *io_cfg)
|
||
{
|
||
struct jzmmc_host * jz_sdio = host->private_data;
|
||
rt_uint32_t clkdiv;
|
||
|
||
dbg_log(DBG_LOG, "set_iocfg clock: %d\n", io_cfg->clock);
|
||
|
||
if (io_cfg->bus_width == MMCSD_BUS_WIDTH_4)
|
||
{
|
||
dbg_log(DBG_LOG, "MMC: Setting controller bus width to 4\n");
|
||
jz_sdio->flags |= MSC_CMDAT_BUS_WIDTH_4BIT;
|
||
}
|
||
else
|
||
{
|
||
jz_sdio->flags &= ~(MSC_CMDAT_BUS_WIDTH_4BIT);
|
||
dbg_log(DBG_LOG, "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;
|
||
}
|
||
|
||
jzmmc_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)
|
||
{
|
||
dbg_log(DBG_ERROR, "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)
|
||
{
|
||
dbg_log(DBG_LOG, "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
|
||
{
|
||
jzmmc_clk_autoctrl(jz_sdio, 0);
|
||
}
|
||
|
||
/* maybe switch power to the card */
|
||
switch (io_cfg->power_mode)
|
||
{
|
||
case MMCSD_POWER_OFF:
|
||
dbg_log(DBG_LOG, "MMCSD_POWER_OFF\r\n");
|
||
break;
|
||
case MMCSD_POWER_UP:
|
||
dbg_log(DBG_LOG, "MMCSD_POWER_UP\r\n");
|
||
break;
|
||
case MMCSD_POWER_ON:
|
||
dbg_log(DBG_LOG, "MMCSD_POWER_ON\r\n");
|
||
jzmmc_hardware_init(jz_sdio);
|
||
// jz_mmc_set_clock(jz_sdio, io_cfg->clock);
|
||
break;
|
||
default:
|
||
dbg_log(DBG_LOG, "unknown power_mode %d\n", io_cfg->power_mode);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static rt_int32_t jzmmc_sdio_detect(struct rt_mmcsd_host *host)
|
||
{
|
||
dbg_log(DBG_LOG, "jz47xx_SD_Detect\n");
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void jzmmc_sdio_enable_sdio_irq(struct rt_mmcsd_host *host,
|
||
rt_int32_t enable)
|
||
{
|
||
dbg_log(DBG_LOG, "jz47xx_sdio_enable_sdio_irq, enable:%d\n", enable);
|
||
}
|
||
|
||
static const struct rt_mmcsd_host_ops ops =
|
||
{
|
||
jzmmc_sdio_request,
|
||
jzmmc_sdio_set_iocfg,
|
||
jzmmc_sdio_detect,
|
||
jzmmc_sdio_enable_sdio_irq,
|
||
};
|
||
|
||
int jzmmc_sdio_init(void)
|
||
{
|
||
struct rt_mmcsd_host *host = RT_NULL;
|
||
struct jzmmc_host *jz_host = RT_NULL;
|
||
|
||
#ifdef RT_USING_MSC0
|
||
host = mmcsd_alloc_host();
|
||
jz_host = rt_malloc_align(sizeof(struct jzmmc_host), 32);
|
||
if(!(host && jz_host))
|
||
{
|
||
goto err;
|
||
}
|
||
|
||
rt_memset(jz_host, 0, sizeof(struct jzmmc_host));
|
||
/* set hardware base firstly */
|
||
jz_host->hw_base = MSC0_BASE;
|
||
jz_host->clock = clk_get("cgu_msc0");
|
||
jz_host->clock_gate = clk_get("msc0");
|
||
#ifdef DMA_BUFFER
|
||
jz_host->_dma_buffer = _dma_buffer_0;
|
||
#endif
|
||
/* 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_host->clock_gate);
|
||
|
||
jz_host->msc_clock = 25UL * 1000 * 1000; /* 25Mhz */
|
||
host->freq_min = 400 * 1000; /* min 400Khz. */
|
||
host->freq_max = 25 * 1000 * 1000; /* max 25Mhz. */
|
||
|
||
// jz_host->msc_clock = 400 * 1000; /* 25Mhz */
|
||
// host->freq_min = 400 * 1000; /* min 400Khz. */
|
||
// host->freq_max = 400 * 1000; /* max 25Mhz. */
|
||
|
||
/* set clock */
|
||
clk_set_rate(jz_host->clock, 50000000);
|
||
|
||
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_SDIO_IRQ | MMCSD_SUP_HIGHSPEED;
|
||
host->flags = MMCSD_MUTBLKWRITE | MMCSD_SUP_SDIO_IRQ | 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_host;
|
||
|
||
jz_host->host = host;
|
||
jz_host->irqno = IRQ_MSC0;
|
||
|
||
rt_hw_interrupt_install(jz_host->irqno, jzmmc_isr, jz_host, "msc0");
|
||
rt_hw_interrupt_mask(jz_host->irqno);
|
||
|
||
mmcsd_change(host);
|
||
#endif // RT_USING_MSC0
|
||
|
||
#ifdef RT_USING_MSC1
|
||
host = mmcsd_alloc_host();
|
||
jz_host = rt_malloc(sizeof(struct jzmmc_host));
|
||
if(!(host && jz_host))
|
||
{
|
||
goto err;
|
||
}
|
||
|
||
jz_host1 = jz_host; // for debug
|
||
|
||
rt_memset(jz_host, 0, sizeof(struct jzmmc_host));
|
||
jz_host->hw_base = MSC1_BASE;
|
||
jz_host->clock = clk_get("cgu_msc1");
|
||
jz_host->clock_gate = clk_get("msc1");
|
||
#ifdef DMA_BUFFER
|
||
jz_host->_dma_buffer = _dma_buffer_1;
|
||
#endif
|
||
/* 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_host->clock_gate);
|
||
|
||
jz_host->msc_clock = 25UL * 1000 * 1000; /* 25Mhz */
|
||
host->freq_min = 400 * 1000; /* min 400Khz. */
|
||
host->freq_max = 25 * 1000 * 1000; /* max 25Mhz. */
|
||
|
||
/* set clock */
|
||
clk_set_rate(jz_host->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_SDIO_IRQ | 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_host;
|
||
|
||
jz_host->host = host;
|
||
jz_host->irqno = IRQ_MSC1;
|
||
|
||
rt_hw_interrupt_install(jz_host->irqno, jzmmc_isr, jz_host, "msc1");
|
||
rt_hw_interrupt_mask(jz_host->irqno);
|
||
|
||
mmcsd_change(host);
|
||
#endif // RT_USING_MSC1
|
||
|
||
return RT_EOK;
|
||
|
||
err:
|
||
if(host)
|
||
{
|
||
mmcsd_free_host(host);
|
||
}
|
||
if(jz_host)
|
||
{
|
||
rt_free(jz_host);
|
||
}
|
||
|
||
return -RT_ENOMEM;
|
||
}
|
||
INIT_DEVICE_EXPORT(jzmmc_sdio_init);
|
||
|
||
#include <finsh.h>
|
||
int msc_status(void)
|
||
{
|
||
uint32_t value;
|
||
|
||
if (jz_host1)
|
||
{
|
||
value = msc_readl(jz_host1, MSC_STAT_OFFSET);
|
||
rt_kprintf("status: 0x%08x\n", value);
|
||
|
||
value = msc_readl(jz_host1, MSC_IMASK_OFFSET);
|
||
rt_kprintf("mask : 0x%08x -> 0x%08x\n", value, ~value);
|
||
|
||
value = msc_readl(jz_host1, MSC_IREG_OFFSET);
|
||
rt_kprintf("iflag : 0x%08x\n", value);
|
||
|
||
rt_kprintf("dma : nda 0x%08x, da 0x%08x, len 0x%04x, cmd 0x%08x\n", msc_readl(jz_host1, MSC_DMANDA_OFFSET),
|
||
msc_readl(jz_host1, MSC_DMADA_OFFSET),
|
||
msc_readl(jz_host1, MSC_DMALEN_OFFSET),
|
||
msc_readl(jz_host1, MSC_DMACMD_OFFSET));
|
||
|
||
rt_kprintf("clock : %s\n", (stopping_clock == 1)? "stopping" : "none stopping");
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
MSH_CMD_EXPORT(msc_status, dump msc status);
|
||
|
||
int msc_log(int argc, char** argv)
|
||
{
|
||
if (argc == 2)
|
||
sdio_log = atoi(argv[1]);
|
||
|
||
return 0;
|
||
}
|
||
MSH_CMD_EXPORT(msc_log, set msc log enable);
|