1068 lines
32 KiB
C
1068 lines
32 KiB
C
/*
|
|
* Copyright (C) 2022-2024, Xiaohua Semiconductor Co., Ltd.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2023-02-14 CDT first version
|
|
*/
|
|
|
|
|
|
/*******************************************************************************
|
|
* Include files
|
|
******************************************************************************/
|
|
#include <rtthread.h>
|
|
#include <rtdevice.h>
|
|
|
|
#if defined(RT_USING_SDIO)
|
|
|
|
#if defined(BSP_USING_SDIO1) || defined(BSP_USING_SDIO2)
|
|
|
|
#include "drv_sdio.h"
|
|
#include "board_config.h"
|
|
|
|
/*******************************************************************************
|
|
* Local type definitions ('typedef')
|
|
******************************************************************************/
|
|
|
|
/* rthw sdio */
|
|
struct rthw_sdio
|
|
{
|
|
struct rt_mmcsd_host *host;
|
|
struct hc32_sdio_config *config;
|
|
struct hc32_sdio_des des;
|
|
struct rt_event event;
|
|
struct rt_mutex mutex;
|
|
struct sdio_pkg *pkg;
|
|
uint8_t *cache_buf;
|
|
};
|
|
|
|
/*******************************************************************************
|
|
* Local pre-processor symbols/macros ('#define')
|
|
******************************************************************************/
|
|
//#define DRV_DEBUG
|
|
#define LOG_TAG "drv.sdio"
|
|
#include <drv_log.h>
|
|
|
|
#ifndef SDIO_BUFF_SIZE
|
|
#define SDIO_BUFF_SIZE (4096)
|
|
#endif
|
|
|
|
#ifndef SDIO_ALIGN_LEN
|
|
#define SDIO_ALIGN_LEN (4)
|
|
#endif
|
|
|
|
#ifndef SDIO_MAX_FREQ
|
|
#define SDIO_MAX_FREQ (50*1000*1000)
|
|
#endif
|
|
|
|
#define RTHW_SDIO_LOCK(_sdio) rt_mutex_take(&(_sdio)->mutex, RT_WAITING_FOREVER)
|
|
#define RTHW_SDIO_UNLOCK(_sdio) rt_mutex_release(&(_sdio)->mutex);
|
|
|
|
/*******************************************************************************
|
|
* Global variable definitions (declared in header file with 'extern')
|
|
******************************************************************************/
|
|
extern rt_err_t rt_hw_board_sdio_init(CM_SDIOC_TypeDef *SDIOCx);
|
|
|
|
/*******************************************************************************
|
|
* Local function prototypes ('static')
|
|
******************************************************************************/
|
|
#ifdef BSP_USING_SDIO1
|
|
static void _sdio1_handler(void);
|
|
#endif
|
|
|
|
#ifdef BSP_USING_SDIO2
|
|
static void _sdio2_handler(void);
|
|
#endif
|
|
|
|
/*******************************************************************************
|
|
* Local variable definitions ('static')
|
|
******************************************************************************/
|
|
enum
|
|
{
|
|
#ifdef BSP_USING_SDIO1
|
|
SDIO1_INDEX,
|
|
#endif
|
|
#ifdef BSP_USING_SDIO2
|
|
SDIO2_INDEX,
|
|
#endif
|
|
};
|
|
|
|
static struct hc32_sdio_config _sdio_config[] =
|
|
{
|
|
#ifdef BSP_USING_SDIO1
|
|
SDIO1_BUS_CONFIG,
|
|
#endif /* BSP_USING_SDIO1 */
|
|
#ifdef BSP_USING_SDIO2
|
|
SDIO2_BUS_CONFIG,
|
|
#endif /* BSP_USING_SDIO2 */
|
|
};
|
|
|
|
static const func_ptr_t _sdio_irq_handler[] =
|
|
{
|
|
#ifdef BSP_USING_SDIO1
|
|
_sdio1_handler,
|
|
#endif /* BSP_USING_SDIO1 */
|
|
#ifdef BSP_USING_SDIO2
|
|
_sdio2_handler,
|
|
#endif /* BSP_USING_SDIO2 */
|
|
};
|
|
|
|
#ifdef BSP_USING_SDIO1
|
|
rt_align(SDIO_ALIGN_LEN)
|
|
static rt_uint8_t _sdio1_cache_buf[SDIO_BUFF_SIZE];
|
|
#endif
|
|
|
|
#ifdef BSP_USING_SDIO2
|
|
rt_align(SDIO_ALIGN_LEN)
|
|
static rt_uint8_t _sdio2_cache_buf[SDIO_BUFF_SIZE];
|
|
#endif
|
|
|
|
static rt_uint8_t *const _sdio_cache_buf[] =
|
|
{
|
|
#ifdef BSP_USING_SDIO1
|
|
_sdio1_cache_buf,
|
|
#endif /* BSP_USING_SDIO1 */
|
|
#ifdef BSP_USING_SDIO2
|
|
_sdio2_cache_buf,
|
|
#endif /* BSP_USING_SDIO2 */
|
|
};
|
|
|
|
static struct rt_mmcsd_host *_sdio_host[sizeof(_sdio_config) / sizeof(_sdio_config[0])] = {0};
|
|
|
|
/*******************************************************************************
|
|
* Function implementation - global ('extern') and local ('static')
|
|
******************************************************************************/
|
|
|
|
/**
|
|
* @brief Get the response type of hc32 sdioc driver.
|
|
* @param [in] rt_resp_type SDIO command response type defined in mmcs_core.h
|
|
* @retval The response type of hc32 sdioc driver
|
|
*/
|
|
static rt_uint32_t _sdio_get_cmd_resptype(rt_uint32_t rt_resptype)
|
|
{
|
|
rt_uint32_t sdioc_resptype;
|
|
|
|
switch (rt_resptype)
|
|
{
|
|
case RESP_NONE:
|
|
sdioc_resptype = SDIOC_RESP_TYPE_NO;
|
|
break;
|
|
case RESP_R1:
|
|
sdioc_resptype = SDIOC_RESP_TYPE_R1_R5_R6_R7;
|
|
break;
|
|
case RESP_R2:
|
|
sdioc_resptype = SDIOC_RESP_TYPE_R2;
|
|
break;
|
|
case RESP_R3:
|
|
case RESP_R4:
|
|
sdioc_resptype = SDIOC_RESP_TYPE_R3_R4;
|
|
break;
|
|
case RESP_R5:
|
|
case RESP_R6:
|
|
case RESP_R7:
|
|
sdioc_resptype = SDIOC_RESP_TYPE_R1_R5_R6_R7;
|
|
break;
|
|
case RESP_R1B:
|
|
sdioc_resptype = SDIOC_RESP_TYPE_R1B_R5B;
|
|
break;
|
|
default:
|
|
sdioc_resptype = SDIOC_RESP_TYPE_NO;
|
|
LOG_E("unknown response type: %d", rt_resptype);
|
|
break;
|
|
}
|
|
|
|
return sdioc_resptype;
|
|
}
|
|
|
|
/**
|
|
* @brief This function wait sdio completed.
|
|
* @param [in] sdio Pointer to a @ref rthw_sdio structure
|
|
* @retval None
|
|
*/
|
|
static void _sdio_wait_completed(struct rthw_sdio *sdio)
|
|
{
|
|
rt_uint32_t status;
|
|
rt_uint32_t response[4];
|
|
__IO rt_uint32_t to_count;
|
|
struct rt_mmcsd_cmd *cmd = sdio->pkg->cmd;
|
|
struct rt_mmcsd_data *data = cmd->data;
|
|
CM_SDIOC_TypeDef *instance = sdio->config->instance;
|
|
|
|
if (rt_event_recv(&sdio->event, 0xFFFFFFFF, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
|
|
rt_tick_from_millisecond(5000), &status) != RT_EOK)
|
|
{
|
|
LOG_E("[%s timeout] sta=0x%08X, cmd %d, arg:0x%08X", __func__, status, cmd->cmd_code, cmd->arg);
|
|
cmd->err = -RT_ETIMEOUT;
|
|
return;
|
|
}
|
|
|
|
if (sdio->pkg == RT_NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (resp_type(cmd) == RESP_NONE)
|
|
{
|
|
;
|
|
}
|
|
else if (resp_type(cmd) == RESP_R2)
|
|
{
|
|
LOG_D("R2");
|
|
(void)SDIOC_GetResponse(instance, SDIOC_RESP_REG_BIT0_31, &response[0]);
|
|
(void)SDIOC_GetResponse(instance, SDIOC_RESP_REG_BIT32_63, &response[1]);
|
|
(void)SDIOC_GetResponse(instance, SDIOC_RESP_REG_BIT64_95, &response[2]);
|
|
(void)SDIOC_GetResponse(instance, SDIOC_RESP_REG_BIT96_127, &response[3]);
|
|
|
|
cmd->resp[0] = (response[3] << 8) + ((response[2] >> 24) & 0xFF);
|
|
cmd->resp[1] = (response[2] << 8) + ((response[1] >> 24) & 0xFF);
|
|
cmd->resp[2] = (response[1] << 8) + ((response[0] >> 24) & 0xFF);
|
|
cmd->resp[3] = (response[0] << 8) + 0x00;
|
|
}
|
|
else
|
|
{
|
|
(void)SDIOC_GetResponse(instance, SDIOC_RESP_REG_BIT0_31, &response[0]);
|
|
cmd->resp[0] = response[0];
|
|
}
|
|
|
|
if (status & SDIOC_INT_FLAG_EI)
|
|
{
|
|
if (status & (SDIOC_INT_FLAG_CTOE | SDIOC_INT_FLAG_CCE | SDIOC_INT_FLAG_CEBE | SDIOC_INT_FLAG_CIE))
|
|
{
|
|
SDIOC_SWReset(instance, SDIOC_SW_RST_CMD_LINE);
|
|
cmd->err = -RT_ERROR;
|
|
LOG_D("[%s cmd err] sta=0x%08X, %s%s%s%s cmd %d arg:0x%08X",
|
|
__func__,
|
|
status,
|
|
status & SDIOC_INT_FLAG_CCE ? "Command CRC Error " : "",
|
|
status & SDIOC_INT_FLAG_CEBE ? "Command End Bit Error" : "",
|
|
status & SDIOC_INT_FLAG_CTOE ? "Command Timeout Error" : "",
|
|
status == 0 ? "NULL" : "",
|
|
cmd->cmd_code,
|
|
cmd->arg);
|
|
}
|
|
|
|
if (status & (SDIOC_INT_FLAG_DTOE | SDIOC_INT_FLAG_DCE | SDIOC_INT_FLAG_DEBE))
|
|
{
|
|
SDIOC_SWReset(instance, SDIOC_SW_RST_DATA_LINE);
|
|
if (data != NULL)
|
|
{
|
|
data->err = -RT_ERROR;
|
|
LOG_D("[%s dat err] sta=0x%08X, %s%s%s%s cmd %d arg:0x%08X rw:%c len:%d blksize:%d",
|
|
__func__,
|
|
status,
|
|
status & SDIOC_INT_FLAG_DCE ? "Data CRC Error " : "",
|
|
status & SDIOC_INT_FLAG_DEBE ? "Data End Bit Error" : "",
|
|
status & SDIOC_INT_FLAG_DTOE ? "Data Timeout Error" : "",
|
|
status == 0 ? "NULL" : "",
|
|
cmd->cmd_code,
|
|
cmd->arg,
|
|
data ? (data->flags & DATA_DIR_WRITE ? 'w' : 'r') : '-',
|
|
data ? data->blks * data->blksize : 0,
|
|
data ? data->blksize : 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cmd->err = RT_EOK;
|
|
LOG_D("[%s xfer ok] sta=0x%08X, cmd %d, arg:0x%08X, resp[%08X %08X %08X %08X]",
|
|
__func__,
|
|
status,
|
|
cmd->cmd_code,
|
|
cmd->arg,
|
|
cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Transfer data by dma.
|
|
* @param [in] sdio Pointer to a @ref rthw_sdio structure
|
|
* @param [in] pkg Pointer to a @ref sdio_pkg structure
|
|
* @retval None
|
|
*/
|
|
static void _sdio_transfer_by_dma(struct rthw_sdio *sdio, struct sdio_pkg *pkg)
|
|
{
|
|
struct rt_mmcsd_data *data = pkg->cmd->data;
|
|
|
|
if ((NULL == sdio) || (NULL == pkg) || (NULL == pkg->buf) || (NULL == pkg->cmd) || (NULL == pkg->cmd->data))
|
|
{
|
|
LOG_E("%s function arguments error: %s %s %s %s %s",
|
|
__func__,
|
|
(sdio == RT_NULL ? "sdio is NULL" : ""),
|
|
(pkg == RT_NULL ? "pkg is NULL" : ""),
|
|
(sdio ? (pkg->buf == RT_NULL ? "pkg->buf is NULL" : "") : ""),
|
|
(sdio ? (pkg->cmd == RT_NULL ? "pkg->cmd is NULL" : "") : ""),
|
|
(sdio ? (pkg->cmd->data == RT_NULL ? "pkg->cmd->data is NULL" : "") : "")
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (data->flags & DATA_DIR_WRITE)
|
|
{
|
|
sdio->des.txconfig(sdio->config->dma_tx.Instance, sdio->config->dma_tx.channel, pkg);
|
|
DMA_ChCmd(sdio->config->dma_tx.Instance, sdio->config->dma_tx.channel, ENABLE);
|
|
}
|
|
else if (data->flags & DATA_DIR_READ)
|
|
{
|
|
sdio->des.rxconfig(sdio->config->dma_rx.Instance, sdio->config->dma_rx.channel, pkg);
|
|
DMA_ChCmd(sdio->config->dma_rx.Instance, sdio->config->dma_rx.channel, ENABLE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Send command.
|
|
* @param [in] sdio Pointer to a @ref rthw_sdio structure
|
|
* @param [in] pkg Pointer to a @ref sdio_pkg structure
|
|
* @retval None
|
|
*/
|
|
static void _sdio_send_command(struct rthw_sdio *sdio, struct sdio_pkg *pkg)
|
|
{
|
|
rt_int32_t ret;
|
|
struct rt_mmcsd_cmd *cmd = pkg->cmd;
|
|
struct rt_mmcsd_data *data = cmd->data;
|
|
CM_SDIOC_TypeDef *instance = sdio->config->instance;
|
|
stc_sdioc_cmd_config_t stcCmdConfig;
|
|
stc_sdioc_data_config_t stcDataConfig;
|
|
|
|
/* save pkg */
|
|
sdio->pkg = pkg;
|
|
|
|
LOG_D("CMD:%d ARG:0x%08x RES:%s%s%s%s%s%s%s%s%s rw:%c len:%d blksize:%d",
|
|
cmd->cmd_code,
|
|
cmd->arg,
|
|
resp_type(cmd) == RESP_NONE ? "NONE" : "",
|
|
resp_type(cmd) == RESP_R1 ? "R1" : "",
|
|
resp_type(cmd) == RESP_R1B ? "R1B" : "",
|
|
resp_type(cmd) == RESP_R2 ? "R2" : "",
|
|
resp_type(cmd) == RESP_R3 ? "R3" : "",
|
|
resp_type(cmd) == RESP_R4 ? "R4" : "",
|
|
resp_type(cmd) == RESP_R5 ? "R5" : "",
|
|
resp_type(cmd) == RESP_R6 ? "R6" : "",
|
|
resp_type(cmd) == RESP_R7 ? "R7" : "",
|
|
data ? (data->flags & DATA_DIR_WRITE ? 'w' : 'r') : '-',
|
|
data ? data->blks * data->blksize : 0,
|
|
data ? data->blksize : 0);
|
|
|
|
/* config command */
|
|
stcCmdConfig.u16CmdIndex = cmd->cmd_code;
|
|
|
|
/* config command argument */
|
|
stcCmdConfig.u32Argument = cmd->arg;
|
|
|
|
/* config command type */
|
|
stcCmdConfig.u16CmdType = SDIOC_CMD_TYPE_NORMAL;
|
|
|
|
/* config response type */
|
|
stcCmdConfig.u16ResponseType = _sdio_get_cmd_resptype(resp_type(cmd));
|
|
|
|
if (data != RT_NULL)
|
|
{
|
|
/* config data */
|
|
stcDataConfig.u16BlockSize = data->blksize;
|
|
stcDataConfig.u16BlockCount = data->blks;
|
|
stcDataConfig.u16TransDir = (data->flags & DATA_DIR_READ) ? SDIOC_TRANS_DIR_TO_HOST : SDIOC_TRANS_DIR_TO_CARD;
|
|
stcDataConfig.u16AutoCmd12 = SDIOC_AUTO_SEND_CMD12_DISABLE;
|
|
stcDataConfig.u16DataTimeout = SDIOC_DATA_TIMEOUT_CLK_2E27;
|
|
stcDataConfig.u16TransMode = (data->blks > 1U) ? SDIOC_TRANS_MD_MULTI : SDIOC_TRANS_MD_SINGLE;
|
|
ret = SDIOC_ConfigData(instance, &stcDataConfig);
|
|
if (ret != 0)
|
|
{
|
|
LOG_E("configure data error : %d", ret);
|
|
}
|
|
|
|
/* transfer config */
|
|
_sdio_transfer_by_dma(sdio, pkg);
|
|
|
|
stcCmdConfig.u16DataLine = SDIOC_DATA_LINE_ENABLE;;
|
|
}
|
|
else
|
|
{
|
|
stcCmdConfig.u16DataLine = SDIOC_DATA_LINE_DISABLE;
|
|
}
|
|
|
|
/* send cmd */
|
|
ret = SDIOC_SendCommand(instance, &stcCmdConfig);
|
|
if (ret != 0)
|
|
{
|
|
LOG_E("send command error : %d", ret);
|
|
}
|
|
|
|
/* wait completed */
|
|
_sdio_wait_completed(sdio);
|
|
|
|
/* clear pkg */
|
|
sdio->pkg = RT_NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Send sdio request.
|
|
* @param [in] host Pointer to a @ref rt_mmcsd_host structure
|
|
* @param [in] req Pointer to a @ref rt_mmcsd_req structure
|
|
* @retval None
|
|
*/
|
|
static void _sdio_request(struct rt_mmcsd_host *host, struct rt_mmcsd_req *req)
|
|
{
|
|
rt_uint32_t mask;
|
|
struct sdio_pkg pkg;
|
|
struct rt_mmcsd_data *data;
|
|
struct rthw_sdio *sdio = host->private_data;
|
|
|
|
if ((NULL == host) || (NULL == req) || (NULL == sdio) || (NULL == sdio->config))
|
|
{
|
|
LOG_E("%s function arguments error: %s %s %s %s",
|
|
__func__,
|
|
(host == RT_NULL ? "host is NULL" : ""),
|
|
(req == RT_NULL ? "req is NULL" : ""),
|
|
(sdio == RT_NULL ? "sdio is NULL" : ""),
|
|
(sdio ? (sdio->config == RT_NULL ? "sdio->config is NULL" : "") : "")
|
|
);
|
|
return;
|
|
}
|
|
|
|
RTHW_SDIO_LOCK(sdio);
|
|
|
|
if (req->cmd != RT_NULL)
|
|
{
|
|
rt_memset(&pkg, 0, sizeof(pkg));
|
|
data = req->cmd->data;
|
|
pkg.cmd = req->cmd;
|
|
|
|
if (SD_SEND_IF_COND == pkg.cmd->cmd_code)
|
|
{
|
|
mask = (CM_SDIOC1 == sdio->config->instance) ? PERIC_SDIOC_SYCTLREG_SELMMC1 : PERIC_SDIOC_SYCTLREG_SELMMC2;
|
|
if (data == RT_NULL)
|
|
{
|
|
CM_PERIC->SDIOC_SYCTLREG &= ~mask;
|
|
}
|
|
else
|
|
{
|
|
CM_PERIC->SDIOC_SYCTLREG |= mask;
|
|
}
|
|
}
|
|
|
|
if (data != RT_NULL)
|
|
{
|
|
rt_uint32_t size = data->blks * data->blksize;
|
|
|
|
RT_ASSERT(size <= SDIO_BUFF_SIZE);
|
|
|
|
/* buffer unaligned */
|
|
if ((rt_uint32_t)data->buf & (SDIO_ALIGN_LEN - 1))
|
|
{
|
|
if (data->flags & DATA_DIR_WRITE)
|
|
{
|
|
rt_memcpy(sdio->cache_buf, data->buf, size);
|
|
}
|
|
pkg.buf = sdio->cache_buf;
|
|
}
|
|
else
|
|
{
|
|
pkg.buf = data->buf;
|
|
}
|
|
}
|
|
|
|
_sdio_send_command(sdio, &pkg);
|
|
|
|
if ((data != RT_NULL) && (data->flags & DATA_DIR_READ) && ((rt_uint32_t)data->buf & (SDIO_ALIGN_LEN - 1)))
|
|
{
|
|
rt_memcpy(data->buf, sdio->cache_buf, data->blksize * data->blks);
|
|
}
|
|
}
|
|
|
|
if (req->stop != RT_NULL)
|
|
{
|
|
rt_memset(&pkg, 0, sizeof(pkg));
|
|
pkg.cmd = req->stop;
|
|
_sdio_send_command(sdio, &pkg);
|
|
}
|
|
|
|
RTHW_SDIO_UNLOCK(sdio);
|
|
|
|
mmcsd_req_complete(sdio->host);
|
|
}
|
|
|
|
/**
|
|
* @brief Config sdio.
|
|
* @param [in] host Pointer to a @ref rt_mmcsd_host structure
|
|
* @param [in] io_cfg Pointer to a @ref rt_mmcsd_io_cfg structure
|
|
* @retval None
|
|
*/
|
|
static void _sdio_iocfg(struct rt_mmcsd_host *host, struct rt_mmcsd_io_cfg *io_cfg)
|
|
{
|
|
rt_int32_t ret;
|
|
rt_uint32_t clk;
|
|
rt_uint16_t clk_div;
|
|
rt_uint32_t clk_src;
|
|
struct rthw_sdio *sdio = host->private_data;
|
|
CM_SDIOC_TypeDef *instance;
|
|
|
|
if ((NULL == host) || (NULL == io_cfg) || (NULL == sdio) || (NULL == sdio->config))
|
|
{
|
|
LOG_E("%s function arguments error: %s %s %s %s",
|
|
__func__,
|
|
(host == RT_NULL ? "host is NULL" : ""),
|
|
(io_cfg == RT_NULL ? "io_cfg is NULL" : ""),
|
|
(sdio == RT_NULL ? "sdio_des is NULL" : ""),
|
|
(sdio ? (sdio->config == RT_NULL ? "sdio->config is NULL" : "") : "")
|
|
);
|
|
return;
|
|
}
|
|
|
|
instance = sdio->config->instance;
|
|
|
|
clk_src = sdio->des.clk_get(instance);
|
|
if (clk_src < 400 * 1000)
|
|
{
|
|
LOG_E("clock rate is too low! rate:%d", clk_src);
|
|
return;
|
|
}
|
|
|
|
clk = io_cfg->clock;
|
|
if (clk > host->freq_max)
|
|
{
|
|
clk = host->freq_max;
|
|
}
|
|
|
|
if (clk > clk_src)
|
|
{
|
|
LOG_W("setting rate is greater than clock source rate.");
|
|
clk = clk_src;
|
|
}
|
|
|
|
LOG_D("clk:%d width:%s%s%s power:%s%s%s",
|
|
clk,
|
|
io_cfg->bus_width == MMCSD_BUS_WIDTH_8 ? "8" : "",
|
|
io_cfg->bus_width == MMCSD_BUS_WIDTH_4 ? "4" : "",
|
|
io_cfg->bus_width == MMCSD_BUS_WIDTH_1 ? "1" : "",
|
|
io_cfg->power_mode == MMCSD_POWER_OFF ? "OFF" : "",
|
|
io_cfg->power_mode == MMCSD_POWER_UP ? "UP" : "",
|
|
io_cfg->power_mode == MMCSD_POWER_ON ? "ON" : "");
|
|
|
|
RTHW_SDIO_LOCK(sdio);
|
|
|
|
switch (io_cfg->bus_width)
|
|
{
|
|
case MMCSD_BUS_WIDTH_1:
|
|
SDIOC_SetBusWidth(instance, SDIOC_BUS_WIDTH_1BIT);
|
|
break;
|
|
case MMCSD_BUS_WIDTH_4:
|
|
SDIOC_SetBusWidth(instance, SDIOC_BUS_WIDTH_4BIT);
|
|
break;
|
|
case MMCSD_BUS_WIDTH_8:
|
|
SDIOC_SetBusWidth(instance, SDIOC_BUS_WIDTH_8BIT);
|
|
break;
|
|
default:
|
|
LOG_E("unknown bus width: %d", io_cfg->bus_width);
|
|
break;
|
|
}
|
|
|
|
switch (io_cfg->power_mode)
|
|
{
|
|
case MMCSD_POWER_OFF:
|
|
SDIOC_PowerCmd(instance, DISABLE);
|
|
break;
|
|
case MMCSD_POWER_UP:
|
|
case MMCSD_POWER_ON:
|
|
SDIOC_PowerCmd(instance, ENABLE);
|
|
break;
|
|
default:
|
|
LOG_W("unknown power_mode %d", io_cfg->power_mode);
|
|
break;
|
|
}
|
|
|
|
instance->CLKCON = 0;
|
|
|
|
if (clk > 0)
|
|
{
|
|
ret = SDIOC_GetOptimumClockDiv(clk, &clk_div);
|
|
if (ret != LL_OK)
|
|
{
|
|
LOG_E("clock division error");
|
|
}
|
|
else
|
|
{
|
|
SDIOC_SetClockDiv(instance, clk_div);
|
|
|
|
if ((clk << 1) <= host->freq_max)
|
|
{
|
|
SDIOC_SetSpeedMode(instance, SDIOC_SPEED_MD_NORMAL);
|
|
}
|
|
else
|
|
{
|
|
SDIOC_SetSpeedMode(instance, SDIOC_SPEED_MD_HIGH);
|
|
}
|
|
|
|
instance->CLKCON = (clk_div | SDIOC_CLKCON_ICE | SDIOC_CLKCON_CE);
|
|
}
|
|
}
|
|
|
|
RTHW_SDIO_UNLOCK(sdio);
|
|
}
|
|
|
|
/**
|
|
* @brief Update the sdio interrupt.
|
|
* @param [in] host Pointer to a @ref rt_mmcsd_host structure
|
|
* @param [in] enable Enable interrupt when value is non-zero
|
|
* @retval None
|
|
*/
|
|
static void _sdio_enable_sdio_irq(struct rt_mmcsd_host *host, rt_int32_t enable)
|
|
{
|
|
struct rthw_sdio *sdio = host->private_data;
|
|
CM_SDIOC_TypeDef *instance = sdio->config->instance;
|
|
|
|
if (enable)
|
|
{
|
|
LOG_D("enable sdio interrupt");
|
|
SDIOC_IntStatusCmd(instance, SDIOC_INT_CINTSEN, ENABLE);
|
|
SDIOC_IntCmd(instance, SDIOC_INT_CINTSEN, ENABLE);
|
|
}
|
|
else
|
|
{
|
|
LOG_D("disable sdio interrupt");
|
|
SDIOC_IntStatusCmd(instance, SDIOC_INT_CINTSEN, DISABLE);
|
|
SDIOC_IntCmd(instance, SDIOC_INT_CINTSEN, DISABLE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief update all of the using interrupt.
|
|
* @param [in] host Pointer to a @ref rt_mmcsd_host structure
|
|
* @param [in] enable Enable interrupt when value is non-zero
|
|
* @retval None
|
|
*/
|
|
static void _sdio_update_irq(struct rt_mmcsd_host *host, rt_int32_t enable)
|
|
{
|
|
struct rthw_sdio *sdio = host->private_data;
|
|
CM_SDIOC_TypeDef *instance = sdio->config->instance;
|
|
const rt_uint32_t int_type = (SDIOC_INT_CCSEN | SDIOC_INT_TCSEN | SDIOC_ERR_INT_ALL);
|
|
|
|
if (enable)
|
|
{
|
|
LOG_D("enable all of the using interrupt");
|
|
SDIOC_IntStatusCmd(instance, (int_type | SDIOC_INT_BRRSEN | SDIOC_INT_BWRSEN), ENABLE);
|
|
SDIOC_IntCmd(instance, int_type, ENABLE);
|
|
}
|
|
else
|
|
{
|
|
LOG_D("disable all of the using interrupt");
|
|
SDIOC_IntStatusCmd(instance, (int_type | SDIOC_INT_BRRSEN | SDIOC_INT_BWRSEN), DISABLE);
|
|
SDIOC_IntCmd(instance, int_type, DISABLE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Get card status.
|
|
* @param [in] host Pointer to a @ref rt_mmcsd_host structure
|
|
* @retval rt_int32_t:
|
|
* - 0: No card
|
|
* - 1: Card inserted
|
|
*/
|
|
static rt_int32_t _sdio_get_card_status(struct rt_mmcsd_host *host)
|
|
{
|
|
struct rthw_sdio *sdio = host->private_data;
|
|
|
|
LOG_D("try to detect device");
|
|
return (rt_int32_t)SDIOC_GetHostStatus(sdio->config->instance, SDIOC_HOST_FLAG_CIN);
|
|
}
|
|
|
|
static const struct rt_mmcsd_host_ops _mmcsd_host_ops =
|
|
{
|
|
_sdio_request,
|
|
_sdio_iocfg,
|
|
_sdio_get_card_status,
|
|
_sdio_enable_sdio_irq,
|
|
};
|
|
|
|
/**
|
|
* @brief Get SDIO clock source frequency.
|
|
* @param [in] SDIOCx Pointer to SDIOC unit instance
|
|
* @retval SDIO clock source frequency
|
|
*/
|
|
static rt_uint32_t _sdio_clock_get(CM_SDIOC_TypeDef *SDIOCx)
|
|
{
|
|
rt_uint32_t clk;
|
|
|
|
(void)SDIOCx;
|
|
#if defined (HC32F4A0)
|
|
clk = CLK_GetBusClockFreq(CLK_BUS_PCLK1);
|
|
#elif defined (HC32F460)
|
|
clk = CLK_GetBusClockFreq(CLK_BUS_EXCLK);
|
|
#endif
|
|
|
|
return clk;
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize DMA for SDIO.
|
|
* @param [in] config Pointer to hc32_sdio_config structure
|
|
* @retval None
|
|
*/
|
|
static void _sdio_dma_init(struct hc32_sdio_config *config)
|
|
{
|
|
stc_dma_init_t stcDmaInit;
|
|
|
|
/* Enable DMA and AOS clock */
|
|
FCG_Fcg0PeriphClockCmd(FCG0_PERIPH_AOS, ENABLE);
|
|
FCG_Fcg0PeriphClockCmd(config->dma_rx.clock, ENABLE);
|
|
FCG_Fcg0PeriphClockCmd(config->dma_tx.clock, ENABLE);
|
|
|
|
/* Initialize DMA config structure */
|
|
(void)DMA_StructInit(&stcDmaInit);
|
|
stcDmaInit.u32BlockSize = 512UL;
|
|
stcDmaInit.u32DataWidth = DMA_DATAWIDTH_32BIT;
|
|
|
|
/* Configure DMA_RX Transfer */
|
|
stcDmaInit.u32SrcAddr = (uint32_t)(&config->instance->BUF0);
|
|
stcDmaInit.u32DestAddr = 0UL;
|
|
stcDmaInit.u32SrcAddrInc = DMA_SRC_ADDR_FIX;
|
|
stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_INC;
|
|
if (LL_OK != DMA_Init(config->dma_rx.Instance, config->dma_rx.channel, &stcDmaInit))
|
|
{
|
|
LOG_E("DMA_RX initialization fail");
|
|
}
|
|
AOS_SetTriggerEventSrc(config->dma_rx.trigger_select, config->dma_rx.trigger_event);
|
|
|
|
/* Configure DMA_TX Transfer */
|
|
stcDmaInit.u32SrcAddr = 0UL;
|
|
stcDmaInit.u32DestAddr = (uint32_t)(&config->instance->BUF0);
|
|
stcDmaInit.u32SrcAddrInc = DMA_SRC_ADDR_INC;
|
|
stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_FIX;
|
|
if (LL_OK != DMA_Init(config->dma_tx.Instance, config->dma_tx.channel, &stcDmaInit))
|
|
{
|
|
LOG_E("DMA_TX initialization fail");
|
|
}
|
|
AOS_SetTriggerEventSrc(config->dma_tx.trigger_select, config->dma_tx.trigger_event);
|
|
|
|
/* Enable DMA */
|
|
DMA_Cmd(config->dma_rx.Instance, ENABLE);
|
|
DMA_Cmd(config->dma_tx.Instance, ENABLE);
|
|
}
|
|
|
|
/**
|
|
* @brief Configure DMA for SDIO receiving.
|
|
* @param [in] instance Pointer to DMA instance register base
|
|
* @param [in] ch DMA channel
|
|
* @param [in] pkg Pointer to sdio_pkg structure
|
|
* @retval RT_EOK
|
|
*/
|
|
static rt_err_t _sdio_dma_rxconfig(CM_DMA_TypeDef *instance, rt_uint8_t ch, struct sdio_pkg *pkg)
|
|
{
|
|
struct rt_mmcsd_cmd *cmd = pkg->cmd;
|
|
struct rt_mmcsd_data *data = cmd->data;
|
|
|
|
DMA_ClearTransCompleteStatus(instance, (DMA_INTSTAT1_BTC_0 | DMA_INTSTAT1_TC_0) << ch);
|
|
DMA_SetDestAddr(instance, ch, (rt_uint32_t)pkg->buf);
|
|
DMA_SetBlockSize(instance, ch, (rt_uint16_t)(data->blksize >> 2));
|
|
DMA_SetTransCount(instance, ch, (rt_uint16_t)data->blks);
|
|
return RT_EOK;
|
|
}
|
|
|
|
/**
|
|
* @brief Configure DMA for SDIO transmitting.
|
|
* @param [in] instance Pointer to DMA instance register base
|
|
* @param [in] ch DMA channel
|
|
* @param [in] pkg Pointer to sdio_pkg structure
|
|
* @retval RT_EOK
|
|
*/
|
|
static rt_err_t _sdio_dma_txconfig(CM_DMA_TypeDef *instance, rt_uint8_t ch, struct sdio_pkg *pkg)
|
|
{
|
|
struct rt_mmcsd_cmd *cmd = pkg->cmd;
|
|
struct rt_mmcsd_data *data = cmd->data;
|
|
|
|
DMA_ClearTransCompleteStatus(instance, (DMA_INTSTAT1_BTC_0 | DMA_INTSTAT1_TC_0) << ch);
|
|
DMA_SetSrcAddr(instance, ch, (rt_uint32_t)pkg->buf);
|
|
DMA_SetBlockSize(instance, ch, (rt_uint16_t)(data->blksize >> 2));
|
|
DMA_SetTransCount(instance, ch, (rt_uint16_t)data->blks);
|
|
return RT_EOK;
|
|
}
|
|
|
|
/**
|
|
* @brief This function interrupt process function.
|
|
* @param [in] host Pointer to rt_mmcsd_host structure
|
|
* @retval None
|
|
*/
|
|
static void _sdio_irq_process(struct rt_mmcsd_host *host)
|
|
{
|
|
int complete = 0;
|
|
struct rthw_sdio *sdio = host->private_data;
|
|
CM_SDIOC_TypeDef *instance = sdio->config->instance;
|
|
rt_uint32_t norint_status = (rt_uint32_t)instance->NORINTST;
|
|
rt_uint32_t errint_status = (rt_uint32_t)instance->ERRINTST;
|
|
rt_uint32_t status = (errint_status << 16) | norint_status;
|
|
|
|
LOG_D("[%s] sta=0x%08X, cmd %d, arg=0x%08X", __func__, status, sdio->pkg->cmd->cmd_code, sdio->pkg->cmd->arg);
|
|
|
|
if (norint_status & (SDIOC_INT_FLAG_CRM | SDIOC_INT_FLAG_CIST))
|
|
{
|
|
SDIOC_ClearIntStatus(instance, SDIOC_INT_FLAG_CLR_ALL);
|
|
|
|
/* ready to change */
|
|
mmcsd_change(host);
|
|
|
|
LOG_D("[%s] card insert or remove", __func__);
|
|
complete = 1;
|
|
}
|
|
else
|
|
{
|
|
if (norint_status & SDIOC_INT_FLAG_EI)
|
|
{
|
|
SDIOC_ClearIntStatus(instance, SDIOC_INT_FLAG_CLR_ALL);
|
|
complete = 1;
|
|
|
|
LOG_D("[%s] error", __func__);
|
|
}
|
|
else
|
|
{
|
|
if (norint_status & SDIOC_INT_FLAG_CC)
|
|
{
|
|
SDIOC_ClearIntStatus(instance, SDIOC_INT_FLAG_CC);
|
|
if (sdio->pkg != RT_NULL)
|
|
{
|
|
if ((!sdio->pkg->cmd->data) && (resp_type(sdio->pkg->cmd) != RESP_R1B))
|
|
{
|
|
complete = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (norint_status & SDIOC_INT_FLAG_TC)
|
|
{
|
|
SDIOC_ClearIntStatus(instance, SDIOC_INT_FLAG_TC);
|
|
complete = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (norint_status & SDIOC_INT_FLAG_CINT)
|
|
{
|
|
SDIOC_ClearIntStatus(instance, SDIOC_INT_FLAG_CINT);
|
|
sdio_irq_wakeup(host);
|
|
}
|
|
|
|
if (complete)
|
|
{
|
|
rt_event_send(&sdio->event, status);
|
|
LOG_D("[%s] complete", __func__);
|
|
}
|
|
}
|
|
|
|
#ifdef BSP_USING_SDIO1
|
|
/**
|
|
* @brief SDIOC1 irq handler.
|
|
* @param None
|
|
* @retval None
|
|
*/
|
|
static void _sdio1_handler(void)
|
|
{
|
|
/* enter interrupt */
|
|
rt_interrupt_enter();
|
|
|
|
/* process all SDIO interrupt sources */
|
|
_sdio_irq_process(_sdio_host[0]);
|
|
|
|
/* leave interrupt */
|
|
rt_interrupt_leave();
|
|
}
|
|
#endif
|
|
|
|
#ifdef BSP_USING_SDIO2
|
|
/**
|
|
* @brief SDIOC2 irq handler.
|
|
* @param None
|
|
* @retval None
|
|
*/
|
|
static void _sdio2_handler(void)
|
|
{
|
|
/* enter interrupt */
|
|
rt_interrupt_enter();
|
|
|
|
/* process all SDIO interrupt sources */
|
|
_sdio_irq_process(_sdio_host[1]);
|
|
|
|
/* leave interrupt */
|
|
rt_interrupt_leave();
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief verify bus clock frequency.
|
|
* @param [in] config Pointer to hc32_sdio_config structure
|
|
* @retval RT_EOK pass to verify
|
|
* -RT_ERROR fail to verify
|
|
*/
|
|
static rt_err_t _sdio_verify_bus_clock_frequency(struct hc32_sdio_config *config)
|
|
{
|
|
rt_err_t ret = RT_EOK;
|
|
|
|
#if defined (HC32F4A0)
|
|
rt_uint32_t pclk1;
|
|
rt_uint32_t exlck;
|
|
|
|
(void)config;
|
|
/* ensure bus frequency condition: EXCLK >= PCLK1 */
|
|
pclk1 = CLK_GetBusClockFreq(CLK_BUS_PCLK1);
|
|
exlck = CLK_GetBusClockFreq(CLK_BUS_EXCLK);
|
|
if (exlck < pclk1)
|
|
{
|
|
LOG_E("bus frequency error: EXCLK < PCLK1. Please meet the bus frequency condition: EXCLK >= PCLK1");
|
|
ret = -RT_ERROR;
|
|
}
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Enable SDIO clock.
|
|
* @param [in] config Pointer to hc32_sdio_config structure
|
|
* @retval RT_EOK pass to enable SDIO clock
|
|
* -RT_ERROR fail to enable SDIO clock
|
|
*/
|
|
static rt_err_t _sdio_clock_enable(struct hc32_sdio_config *config)
|
|
{
|
|
/* verify bus clock frequency */
|
|
if (_sdio_verify_bus_clock_frequency(config) != RT_EOK)
|
|
{
|
|
LOG_E("[%s] fail to verify bus clock frequency", __func__);
|
|
return -RT_ERROR;
|
|
}
|
|
|
|
FCG_Fcg1PeriphClockCmd(config->clock, ENABLE);
|
|
return RT_EOK;
|
|
}
|
|
|
|
/**
|
|
* @brief Create mmcsd host.
|
|
* @param [in] config Pointer to a @ref hc32_sdio_config structure
|
|
* @param [in] cache_buf Pointer to cache buffer
|
|
* @param [in] sdio_des Pointer to a @ref hc32_sdio_des structure
|
|
* @retval rt_mmcsd_host
|
|
*/
|
|
static struct rt_mmcsd_host *_sdio_host_create(struct hc32_sdio_config *config,
|
|
uint8_t *cache_buf,
|
|
const struct hc32_sdio_des *sdio_des)
|
|
{
|
|
struct rt_mmcsd_host *host;
|
|
struct rthw_sdio *sdio = RT_NULL;
|
|
|
|
if ((config == RT_NULL) || (cache_buf == RT_NULL) || \
|
|
(sdio_des == RT_NULL) || (sdio_des->txconfig == RT_NULL) || (sdio_des->rxconfig == RT_NULL))
|
|
{
|
|
LOG_E("function arguments error: %s %s %s %s %s",
|
|
(config == RT_NULL ? "config is NULL" : ""),
|
|
(cache_buf == RT_NULL ? "cache_buf is NULL" : ""),
|
|
(sdio_des == RT_NULL ? "sdio_des is NULL" : ""),
|
|
(sdio_des ? (sdio_des->txconfig ? "txconfig is NULL" : "") : ""),
|
|
(sdio_des ? (sdio_des->rxconfig ? "rxconfig is NULL" : "") : "")
|
|
);
|
|
return RT_NULL;
|
|
}
|
|
|
|
/* malloc rthw_sdio */
|
|
sdio = rt_malloc(sizeof(struct rthw_sdio));
|
|
if (sdio == RT_NULL)
|
|
{
|
|
LOG_E("malloc rthw_sdio fail");
|
|
return RT_NULL;
|
|
}
|
|
rt_memset(sdio, 0, sizeof(struct rthw_sdio));
|
|
|
|
/* malloc mmcsd_alloc_host */
|
|
host = mmcsd_alloc_host();
|
|
if (host == RT_NULL)
|
|
{
|
|
LOG_E("mmcsd_alloc_host fail");
|
|
rt_free(sdio);
|
|
return RT_NULL;
|
|
}
|
|
|
|
rt_memcpy(&sdio->des, sdio_des, sizeof(struct hc32_sdio_des));
|
|
|
|
rt_event_init(&sdio->event, "sdio", RT_IPC_FLAG_FIFO);
|
|
rt_mutex_init(&sdio->mutex, "sdio", RT_IPC_FLAG_FIFO);
|
|
|
|
/* set host default attributes */
|
|
host->ops = &_mmcsd_host_ops;
|
|
host->freq_min = 400 * 1000;
|
|
host->freq_max = SDIO_MAX_FREQ;
|
|
host->valid_ocr = VDD_26_27 | VDD_27_28 | VDD_28_29 | VDD_29_30 | VDD_30_31 | VDD_31_32 | VDD_32_33 | VDD_33_34;/* The voltage range supported is 2.6v-3.4v */
|
|
host->valid_ocr = VDD_32_33 | VDD_33_34;
|
|
#ifndef SDIO_USING_1_BIT
|
|
host->flags = MMCSD_BUSWIDTH_4 | MMCSD_MUTBLKWRITE | MMCSD_SUP_SDIO_IRQ;
|
|
#else
|
|
host->flags = MMCSD_MUTBLKWRITE | MMCSD_SUP_SDIO_IRQ;
|
|
#endif
|
|
host->max_seg_size = SDIO_BUFF_SIZE;
|
|
host->max_dma_segs = 1;
|
|
host->max_blk_size = 512;
|
|
host->max_blk_count = (SDIO_BUFF_SIZE / host->max_blk_size);
|
|
|
|
/* link up host, config, cache_buf and sdio */
|
|
sdio->host = host;
|
|
sdio->config = config;
|
|
sdio->cache_buf = cache_buf;
|
|
host->private_data = sdio;
|
|
|
|
/* enable interrupt */
|
|
_sdio_update_irq(host, 1);
|
|
|
|
/* ready to change */
|
|
mmcsd_change(host);
|
|
|
|
return host;
|
|
}
|
|
|
|
int rt_hw_sdio_init(void)
|
|
{
|
|
struct rt_mmcsd_host *host;
|
|
struct hc32_sdio_config *sdio_config;
|
|
rt_size_t obj_num = sizeof(_sdio_config) / sizeof(struct hc32_sdio_config);
|
|
|
|
const struct hc32_sdio_des sdio_des =
|
|
{
|
|
.clk_get = _sdio_clock_get,
|
|
.rxconfig = _sdio_dma_rxconfig,
|
|
.txconfig = _sdio_dma_txconfig,
|
|
};
|
|
|
|
for (rt_size_t i = 0; i < obj_num; i++)
|
|
{
|
|
sdio_config = &_sdio_config[i];
|
|
|
|
if (_sdio_clock_enable(sdio_config) != RT_EOK)
|
|
{
|
|
LOG_E("clock enable fail");
|
|
return -1;
|
|
}
|
|
|
|
host = _sdio_host_create(sdio_config, _sdio_cache_buf[i], &sdio_des);
|
|
if (host == RT_NULL)
|
|
{
|
|
LOG_E("host create fail");
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
/* link host */
|
|
_sdio_host[i] = host;
|
|
|
|
/* init board pin */
|
|
rt_hw_board_sdio_init(sdio_config->instance);
|
|
|
|
/* init DMA */
|
|
_sdio_dma_init(sdio_config);
|
|
|
|
/* register the irq handler */
|
|
hc32_install_irq_handler(&sdio_config->irq_config, _sdio_irq_handler[i], RT_TRUE);
|
|
}
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|
|
INIT_DEVICE_EXPORT(rt_hw_sdio_init);
|
|
|
|
#endif /* BSP_USING_SDIO */
|
|
#endif /* RT_USING_SDIO */
|