rt-thread-official/bsp/nuvoton/libraries/m2354/rtt_port/drv_sdio.c

797 lines
20 KiB
C

/**************************************************************************//**
*
* @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-4-21 Wayne First version
*
******************************************************************************/
#include "rtconfig.h"
#if defined(BSP_USING_SDH)
#include <rtdevice.h>
#include <drivers/dev_mmcsd_core.h>
#include <drivers/dev_sdio.h>
#include "NuMicro.h"
#define LOG_TAG "drv.sdh"
#undef DBG_ENABLE
#define DBG_SECTION_NAME LOG_TAG
#define DBG_LEVEL LOG_LVL_ASSERT
#define DBG_COLOR
#include <rtdbg.h>
#define SDH_ALIGN_LEN 4
#define SDH_BUFF_SIZE 512
#define SDH_BLOCK_SIZE 512
enum
{
SDH_START = -1,
#if defined(BSP_USING_SDH0)
SDH0_IDX,
#endif
#if defined(BSP_USING_SDH1)
SDH1_IDX,
#endif
SDH_CNT
};
struct nu_sdh
{
struct rt_mmcsd_host *host;
char *name;
SDH_T *base;
IRQn_Type irqn;
uint32_t rstidx;
uint32_t modid;
uint8_t *cachebuf;
uint32_t u32CmdResp0;
uint32_t u32CmdResp1;
uint32_t u32CurClk;
rt_tick_t LastNotice;
};
typedef struct nu_sdh *nu_sdh_t;
/* Private variables ------------------------------------------------------------*/
static struct nu_sdh nu_sdh_arr [] =
{
#if defined(BSP_USING_SDH0)
{
.name = "sdh0",
.base = SDH0,
.irqn = SDH0_IRQn,
.rstidx = SDH0_RST,
.modid = SDH0_MODULE,
.cachebuf = RT_NULL,
},
#endif
#if defined(BSP_USING_SDH1)
{
.name = "sdh1",
.base = SDH1,
.irqn = SDH1_IRQn,
.rstidx = SDH1_RST,
.modid = SDH1_MODULE,
.cachebuf = RT_NULL,
},
#endif
}; /* struct nu_sdh nu_sdh_arr [] */
#define SDH_SetClock SDH_Set_clock
static int SDH_SetBusWidth(SDH_T *sdh, uint32_t bw)
{
if (bw == 4)
{
sdh->CTL |= SDH_CTL_DBW_Msk;
}
else if (bw == 1)
{
sdh->CTL &= ~SDH_CTL_DBW_Msk;
}
else
{
goto exit_SDH_SetBusWidth;
}
return 0;
exit_SDH_SetBusWidth:
return -1;
}
static int SDH_GetBusStatus(SDH_T *sdh, uint32_t mask)
{
int cnt = 0x100000;
while (cnt-- > 0)
{
sdh->CTL |= SDH_CTL_CLK8OEN_Msk;
while (sdh->CTL & SDH_CTL_CLK8OEN_Msk) { }
if (SDH_GET_INT_FLAG(sdh, SDH_INTSTS_DAT0STS_Msk))
break;
}
return (cnt == 0) ? -1 : 0 ;
}
static int SDH_GetCD(SDH_T *sdh)
{
int i32CD = 0;
if ((sdh->GCTL & SDH_GCTL_SDEN_Msk) == SDH_GCTL_SDEN_Msk)
{
if ((sdh->INTEN & SDH_INTEN_CDSRC_Msk) == SDH_INTEN_CDSRC_Msk) /* Card detect pin from GPIO */
{
i32CD = (sdh->INTSTS & SDH_INTSTS_CDSTS_Msk) ? 0 : 1;
}
else /* Card detect pin from DAT3 mode */
{
__IO uint32_t i;
sdh->CTL |= SDH_CTL_CLKKEEP_Msk;
for (i = 0ul; i < 5000ul; i++) { }
i32CD = ((sdh->INTSTS & SDH_INTSTS_CDSTS_Msk) == SDH_INTSTS_CDSTS_Msk) ? 1 : 0;
sdh->CTL &= ~SDH_CTL_CLKKEEP_Msk;
}
}
return i32CD;
}
static void SDH_Enable(SDH_T *sdh)
{
/* Reset sdh and its DMA engine at first. */
sdh->DMACTL |= SDH_DMACTL_DMARST_Msk | SDH_DMACTL_DMAEN_Msk;
while ((sdh->DMACTL & SDH_DMACTL_DMARST_Msk) == SDH_DMACTL_DMARST_Msk) { }
sdh->DMACTL = SDH_DMACTL_DMAEN_Msk;
sdh->DMAINTSTS = SDH_DMAINTSTS_ABORTIF_Msk | SDH_DMAINTSTS_WEOTIF_Msk; // clear all interrupt flag
sdh->GCTL = SDH_GCTL_GCTLRST_Msk;
while ((sdh->GCTL & SDH_GCTL_GCTLRST_Msk) == SDH_GCTL_GCTLRST_Msk) { }// clear all interrupt flag
sdh->GINTSTS = SDH_GINTSTS_DTAIF_Msk;
sdh->GCTL = SDH_GCTL_SDEN_Msk;
sdh->CTL |= SDH_CTL_CTLRST_Msk;
while ((sdh->CTL & SDH_CTL_CTLRST_Msk) == SDH_CTL_CTLRST_Msk) { }
sdh->INTSTS = 0xFFFFFFFF; // clear all interrupt flag
sdh->INTEN |= SDH_INTEN_CDSRC_Msk;
sdh->INTEN |= SDH_INTEN_CDIEN_Msk;
}
/**
* @brief This function get command responding.
* @param sdh SDH instance
* @param cmd rt_mmcsd_cmd
* @retval none
*/
static void nu_sdh_sendcmd_done(SDH_T *sdh, struct rt_mmcsd_cmd *cmd)
{
if (resp_type(cmd) == RESP_R2)
{
uint8_t *c = (uint8_t *)&sdh->FB[0];
int i, j, tmp[5];
for (i = 0, j = 0; j < 5; i += 4, j++)
{
tmp[j] = (*(c + i) << 24) | (*(c + i + 1) << 16) | (*(c + i + 2) << 8) | (*(c + i + 3));
}
for (i = 0; i < 4; i++)
{
cmd->resp[i] = ((tmp[i] & 0x00ffffff) << 8) |
((tmp[i + 1] & 0xff000000) >> 24);
}
}
else
{
cmd->resp[0] = (sdh->RESP0 << 8) | (sdh->RESP1 & 0xff);
cmd->resp[1] = cmd->resp[2] = cmd->resp[3] = 0;
}
}
/**
* @brief This function wait data-sending/receiving.
* @param sdh SDH instance
* @param data rt_mmcsd_data
* @retval error code
*/
static int nu_sdh_xfer_data(SDH_T *sdh, struct rt_mmcsd_data *data)
{
while (!SDH_GET_INT_FLAG(sdh, SDH_INTSTS_BLKDIF_Msk)) { }
SDH_CLR_INT_FLAG(sdh, SDH_INTSTS_BLKDIF_Msk);
if (data->flags & DATA_DIR_WRITE)
{
sdh->CTL |= SDH_CTL_CLKKEEP_Msk;
while (!SDH_GET_INT_FLAG(sdh, SDH_INTSTS_DAT0STS_Msk)) { }
sdh->CTL &= ~SDH_CTL_CLKKEEP_Msk;
}
return 0;
}
/**
* @brief This function send command and wait its response.
* @param host rt_mmcsd_host
* @param cmd rt_mmcsd_cmd
* @param data rt_mmcsd_data
* @retval error code
*/
static int nu_sdh_sendcmd(struct rt_mmcsd_host *host, struct rt_mmcsd_cmd *cmd, struct rt_mmcsd_data *data)
{
int ret;
nu_sdh_t NuSdh = (nu_sdh_t)host->private_data;
SDH_T *sdh = NuSdh->base;
volatile uint32_t ctl = 0, tout = 0;
switch (host->io_cfg.bus_width)
{
case MMCSD_BUS_WIDTH_1:
ctl &= ~SDH_CTL_DBW_Msk;
break;
case MMCSD_BUS_WIDTH_4:
ctl |= SDH_CTL_DBW_Msk;
break;
case MMCSD_BUS_WIDTH_8:
default:
return -1;
}
/* Reset sdh and its DMA engine at first. */
sdh->DMACTL |= SDH_DMACTL_DMARST_Msk | SDH_DMACTL_DMAEN_Msk;
while ((sdh->DMACTL & SDH_DMACTL_DMARST_Msk) == SDH_DMACTL_DMARST_Msk) { }
sdh->DMACTL = SDH_DMACTL_DMAEN_Msk;
sdh->DMAINTSTS = SDH_DMAINTSTS_ABORTIF_Msk | SDH_DMAINTSTS_WEOTIF_Msk; // clear all interrupt flag
if (resp_type(cmd) != RESP_NONE)
{
if (resp_type(cmd) == RESP_R2)
{
ctl |= SDH_CTL_R2EN_Msk;
}
else
{
ctl |= SDH_CTL_RIEN_Msk;
}
tout = 0xFFFF;
}
/* Set SDNWR and BLK_CNT to 1 */
ctl |= ((9 << SDH_CTL_SDNWR_Pos) | (1 << SDH_CTL_BLKCNT_Pos));
ctl |= ((cmd->cmd_code << SDH_CTL_CMDCODE_Pos) | SDH_CTL_COEN_Msk);
/* Set Transfer mode regarding to data flag */
if (data != RT_NULL)
{
sdh->BLEN = data->blksize - 1;
if (data->blksize <= 0x200)
{
if (data->blks < 256)
{
ctl = (ctl & ~SDH_CTL_BLKCNT_Msk) | (data->blks << SDH_CTL_BLKCNT_Pos);
}
else
{
LOG_E("SD Max block transfer is 255!!");
}
}
if (data->flags & DATA_DIR_READ)
{
tout = 0xFFFFFF;
ctl |= SDH_CTL_DIEN_Msk; // Data-in
sdh->DMASA = (uint32_t)data->buf; // Read from dest
}
else if (data->flags & DATA_DIR_WRITE)
{
ctl |= SDH_CTL_DOEN_Msk; // Data-out
sdh->DMASA = (uint32_t)data->buf; // Write to dest
}
}
else if (resp_type(cmd) == RESP_R1B)
{
}
/* Clear response-timeout flag first for safty and reset new timeout value. */
SDH_CLR_INT_FLAG(sdh, SDH_INTSTS_RTOIF_Msk);
sdh->TOUT = tout;
/* Set argument and start a transaction. */
sdh->CMDARG = cmd->arg;
sdh->CTL = ctl;
/* Wait a command done. */
while ((sdh->CTL & (SDH_CTL_COEN_Msk)) == SDH_CTL_COEN_Msk) { }
if (resp_type(cmd) != RESP_NONE)
{
if (resp_type(cmd) == RESP_R2)
{
/* Wait to receive a response R2 from SD card and store the response data into DMC's Flash buffer (exclude CRC7). */
while (sdh->CTL & SDH_CTL_R2EN_Msk)
{
/* When get a Response timeout, break the polling. */
if (SDH_GET_INT_FLAG(sdh, SDH_INTSTS_RTOIF_Msk))
{
ret = __LINE__;
goto exit_nu_sdh_sendcmd;
}
}
}
else
{
/* Wait to receive a response from SD card. */
while ((sdh->CTL & SDH_CTL_RIEN_Msk))
{
/* When get a Response timeout, break the polling. */
if (SDH_GET_INT_FLAG(sdh, SDH_INTSTS_RTOIF_Msk))
{
ret = __LINE__;
goto exit_nu_sdh_sendcmd;
}
}
/* TOFIX: ISSUE: Sometimes, SDH's RIEN is cleared automatically by controller after host sending CMD5 to SD card. */
/* Workaround: To check previous cmd's response with CMD's. */
if (cmd->cmd_code == 5)
{
if ((NuSdh->u32CmdResp0 == sdh->RESP0) && (NuSdh->u32CmdResp1 == sdh->RESP1))
{
LOG_E("False CMD5-RESP issue occured.\n");
ret = __LINE__;
goto exit_nu_sdh_sendcmd;
}
}
NuSdh->u32CmdResp0 = sdh->RESP0;
NuSdh->u32CmdResp1 = sdh->RESP1;
}
/* Get response from FB or register */
nu_sdh_sendcmd_done(sdh, cmd);
}
if (data != RT_NULL)
{
/* Wait data processing done */
nu_sdh_xfer_data(sdh, data);
ret = SDH_GetBusStatus(sdh, 0);
if (ret)
{
LOG_E("ERROR: Busy %d\n", ret);
ret = __LINE__;
goto exit_nu_sdh_sendcmd;
}
}
/* Handle CRC flag */
if (SDH_GET_INT_FLAG(sdh, SDH_INTSTS_CRCIF_Msk)) // Fault
{
uint32_t u32INTSTS = sdh->INTSTS;
SDH_CLR_INT_FLAG(sdh, SDH_INTSTS_CRCIF_Msk);
ret = __LINE__;
if ((resp_type(cmd) != RESP_R3) && (u32INTSTS & SDH_INTSTS_CRC7_Msk) == 0) //CRC7, Ignore R3
{
LOG_E("CRC7 error! (resp_type=%d)", resp_type(cmd));
goto exit_nu_sdh_sendcmd;
}
if ((u32INTSTS & SDH_INTSTS_CRC16_Msk) == 0) //CRC16
{
LOG_E("CRC16 error! (resp_type=%d)", resp_type(cmd));
goto exit_nu_sdh_sendcmd;
}
}
return 0;
exit_nu_sdh_sendcmd:
LOG_D("[%s %d] cmdid=%d error line=%d", __func__, __LINE__, cmd->cmd_code, ret);
cmd->resp[0] = cmd->resp[1] = cmd->resp[2] = cmd->resp[3] = 0;
sdh->TOUT = 0;
SDH_Enable(sdh);
return -ret;
}
/**
* @brief This function send request.
* @param host rt_mmcsd_host
* @param req request
* @retval None
*/
static void nu_sdh_request(struct rt_mmcsd_host *host, struct rt_mmcsd_req *req)
{
nu_sdh_t NuSdh;
SDH_T *sdh;
RT_ASSERT(host);
RT_ASSERT(req);
NuSdh = (nu_sdh_t)host->private_data;
sdh = NuSdh->base;
if (!SDH_GetCD(sdh)) // card is not present
{
LOG_E("Card is not present");
req->cmd->err = -RT_EIO;
goto exit_nu_sdh_request;
}
if (req->cmd != RT_NULL)
{
struct rt_mmcsd_cmd *cmd = req->cmd;
struct rt_mmcsd_data *data = req->data;
LOG_D("[%s%s%s%s%s]REQ: CMD:%d ARG:0x%08x RESP_TYPE:%d rw:%c addr:%08x, blks:%d, blksize:%d datalen:%d",
(host->card == RT_NULL) ? "Unknown" : "",
(host->card) && (host->card->card_type == CARD_TYPE_MMC) ? "MMC" : "",
(host->card) && (host->card->card_type == CARD_TYPE_SD) ? "SD" : "",
(host->card) && (host->card->card_type == CARD_TYPE_SDIO) ? "SDIO" : "",
(host->card) && (host->card->card_type == CARD_TYPE_SDIO_COMBO) ? "SDIO_COMBO" : "",
cmd->cmd_code,
cmd->arg,
resp_type(cmd),
data ? (data->flags & DATA_DIR_WRITE ? 'w' : 'r') : '-',
data ? data->buf : 0,
data ? data->blks : 0,
data ? data->blksize : 0,
data ? data->blks * data->blksize : 0);
if (data != RT_NULL)
{
rt_uint32_t size;
rt_int32_t IsNonaligned = 0;
rt_uint32_t *org_data_buf = data->buf;
size = data->blksize * data->blks;
RT_ASSERT(org_data_buf);
IsNonaligned = (((rt_uint32_t)data->buf & (SDH_ALIGN_LEN - 1)) > 0) ? 1 : 0;
if (IsNonaligned)
{
/* Allocate memory temp buffer on demand. */
RT_ASSERT(size <= SDH_BUFF_SIZE);
if (NuSdh->cachebuf == RT_NULL)
{
NuSdh->cachebuf = rt_malloc_align(SDH_BUFF_SIZE, SDH_ALIGN_LEN);
RT_ASSERT(NuSdh->cachebuf);
}
data->buf = (rt_uint32_t *)NuSdh->cachebuf;
if (data->flags & DATA_DIR_WRITE)
{
LOG_D("Un-aligned, prepare into cache buf(%d)", size);
rt_memcpy(data->buf, org_data_buf, size);
}
}
cmd->err = nu_sdh_sendcmd(host, cmd, data);
if (!cmd->err && IsNonaligned)
{
if (data->flags & DATA_DIR_READ)
{
LOG_D("Un-aligned, restore from cache buf(%d)", size);
rt_memcpy(org_data_buf, data->buf, size);
}
}
data->buf = org_data_buf;
LOG_HEX("data.dest", 16, (void *)data->buf, size);
}
else
{
cmd->err = nu_sdh_sendcmd(host, cmd, NULL);
}
if (resp_type(cmd) != RESP_NONE)
LOG_HEX("cmd->resp", 16, (void *)&cmd->resp[0], 16);
}
if (req->stop != RT_NULL)
{
struct rt_mmcsd_cmd *stop = req->stop;
stop->err = nu_sdh_sendcmd(host, stop, NULL);
if (resp_type(stop) != RESP_NONE)
LOG_HEX("stop->resp", 16, (void *)&stop->resp[0], 16);
}
exit_nu_sdh_request:
mmcsd_req_complete(host);
}
/**
* @brief This function config.
* @param host rt_mmcsd_host
* @param io_cfg rt_mmcsd_io_cfg
* @retval None
*/
static void nu_sdh_iocfg(struct rt_mmcsd_host *host, struct rt_mmcsd_io_cfg *io_cfg)
{
nu_sdh_t NuSdh;
rt_uint32_t clk;
SDH_T *sdh;
RT_ASSERT(host);
RT_ASSERT(io_cfg);
NuSdh = (nu_sdh_t)host->private_data;
sdh = NuSdh->base;
clk = io_cfg->clock;
LOG_D("[%s]clk:%d width(%d):%s%s%s power:%s%s%s",
NuSdh->name,
clk,
io_cfg->bus_width,
(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" : "");
/* Clock */
if (clk > host->freq_max)
clk = host->freq_max;
if (clk < host->freq_min)
clk = host->freq_min;
LOG_D("[%s] ExceptedFreq: %d kHz", NuSdh->name, clk / 1000);
if (NuSdh->u32CurClk != (clk / 1000))
{
SDH_SetClock(sdh, clk / 1000);
NuSdh->u32CurClk = (clk / 1000);
}
switch (io_cfg->power_mode)
{
case MMCSD_POWER_UP:
if (clk <= 400000)
{
/* power ON 74 clock */
sdh->CTL |= SDH_CTL_CLK74OEN_Msk;
while ((sdh->CTL & SDH_CTL_CLK74OEN_Msk) == SDH_CTL_CLK74OEN_Msk)
{
}
}
break;
case MMCSD_POWER_ON:
break;
case MMCSD_POWER_OFF:
break;
default:
break;
}
/* Bus width */
switch ((io_cfg->bus_width))
{
case MMCSD_BUS_WIDTH_1:
SDH_SetBusWidth(sdh, 1);
break;
case MMCSD_BUS_WIDTH_4:
SDH_SetBusWidth(sdh, 4);
break;
case MMCSD_BUS_WIDTH_8:
default:
break;
}
}
/**
* @brief This function detect sdcard.
* @param host rt_mmcsd_host
* @retval card detection status
*/
static rt_int32_t nu_sdh_card_detect(struct rt_mmcsd_host *host)
{
nu_sdh_t NuSdh;
RT_ASSERT(host);
NuSdh = (nu_sdh_t)host->private_data;
SDH_T *sdh = NuSdh->base;
LOG_D("try to detect device");
return SDH_GetCD(sdh);
}
static void nu_sdh_isr(nu_sdh_t NuSdh)
{
SDH_T *sdh = NuSdh->base;
uint32_t isr = sdh->INTSTS;
/* card detected */
if (isr & SDH_INTSTS_CDIF_Msk)
{
rt_tick_t cur_tick = rt_tick_get();
rt_tick_t diff_tick;
/* ready to change */
if (cur_tick >= NuSdh->LastNotice)
diff_tick = (cur_tick - NuSdh->LastNotice);
else
diff_tick = ((rt_tick_t) -1) - NuSdh->LastNotice + cur_tick;
if (!NuSdh->LastNotice || (diff_tick > (RT_TICK_PER_SECOND / 5))) // Debounce 200ms
{
NuSdh->LastNotice = cur_tick;
mmcsd_change(NuSdh->host);
}
/* Clear CDIF interrupt flag */
SDH_CLR_INT_FLAG(sdh, SDH_INTSTS_CDIF_Msk);
}
}
#if defined(BSP_USING_SDH0)
void SDH0_IRQHandler(void)
{
/* enter interrupt */
rt_interrupt_enter();
nu_sdh_isr(&nu_sdh_arr[SDH0_IDX]);
/* leave interrupt */
rt_interrupt_leave();
}
#endif
#if defined(BSP_USING_SDH1)
void SDH1_IRQHandler(void)
{
/* enter interrupt */
rt_interrupt_enter();
nu_sdh_isr(&nu_sdh_arr[SDH1_IDX]);
/* leave interrupt */
rt_interrupt_leave();
}
#endif
/**
* @brief This function update sdh interrupt.
* @param host rt_mmcsd_host
* @param enable
* @retval None
*/
void nu_sdh_irq_update(struct rt_mmcsd_host *host, rt_int32_t enable)
{
nu_sdh_t NuSdh = (nu_sdh_t)host->private_data;
SDH_T *sdh = NuSdh->base;
if (enable)
{
LOG_D("Enable %s irq", NuSdh->name);
SDH_ENABLE_INT(sdh, SDH_INTSTS_CDIF_Msk);
}
else
{
LOG_D("Disable %s irq", NuSdh->name);
SDH_DISABLE_INT(sdh, SDH_INTSTS_CDIF_Msk);
}
}
static const struct rt_mmcsd_host_ops ops =
{
nu_sdh_request,
nu_sdh_iocfg,
nu_sdh_card_detect,
nu_sdh_irq_update,
};
/**
* @brief This function create mmcsd host.
* @param sdh nu_sdh_t
* @retval nuvton
*/
void nu_sdh_host_init(nu_sdh_t sdh)
{
struct rt_mmcsd_host *host = mmcsd_alloc_host();
RT_ASSERT(host);
/* set host default attributes */
host->ops = &ops;
host->freq_min = 300 * 1000;
host->freq_max = 48 * 1000 * 1000;
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;
host->flags = MMCSD_BUSWIDTH_4 | MMCSD_MUTBLKWRITE | MMCSD_SUP_HIGHSPEED;
host->max_seg_size = SDH_BUFF_SIZE;
host->max_dma_segs = 1;
host->max_blk_size = SDH_BLOCK_SIZE;
host->max_blk_count = (SDH_BUFF_SIZE / SDH_BLOCK_SIZE);
/* link up host and sdio */
host->private_data = sdh;
sdh->host = host;
/* Enable DMA engine at first. */
SDH_Enable(sdh->base);
/* Install ISR. */
NVIC_EnableIRQ(sdh->irqn);
/* ready to change */
//mmcsd_change(host);
}
static int rt_hw_sdh_init(void)
{
int i;
for (i = (SDH_START + 1); i < SDH_CNT; i++)
{
CLK_EnableModuleClock(nu_sdh_arr[i].modid);
SYS_ResetModule(nu_sdh_arr[i].rstidx);
nu_sdh_host_init(&nu_sdh_arr[i]);
}
return 0;
}
INIT_DEVICE_EXPORT(rt_hw_sdh_init);
void nu_sd_attach(void)
{
int i;
/* ready to change */
for (i = (SDH_START + 1); i < SDH_CNT; i++)
{
if (nu_sdh_arr[i].host)
mmcsd_change(nu_sdh_arr[i].host);
}
}
MSH_CMD_EXPORT(nu_sd_attach, attach card);
void nu_sd_regdump(void)
{
int i;
for (i = (SDH_START + 1); i < SDH_CNT; i++)
{
if (nu_sdh_arr[i].host)
LOG_HEX("sdh_reg", 16, (void *)nu_sdh_arr[i].base, sizeof(SDH_T));
}
}
MSH_CMD_EXPORT(nu_sd_regdump, dump sdh registers);
#endif