SDIO Card全称是Secure Digital Input Output Card,它是在SD memory card的基础上扩展了I/O功能。SDIO再加上memory就变成了Combo card(组合卡?),这里主要看下SDIO card。

SDIO card分快(High-Speed)慢(Low-Speed)两种版本。传输模式分为SPI和SD两种,SD又分成1-bit和4-bit,SPI和1-bit SD是强制要求,SD 4-bit是快速SDIO card必备了。

SD mode卡初始化流程图

先来看下Spec的初始化流程图,重点关注SDIO card(红线):

card_ini_flow_in_sd_mode.png

card_ini_flow_in_sd_mode_cont.png

提下图上的几个term含义:

OCR: Operation Conditions Register. The supported minimum and maximum values for VDD.

RCA: relative card address register, 所有的功能都共享同样的card addess。

CMD5: 是IO_SEND_OP_COND command,用来获取OCR。

CMD3: 是SEND_RELATIVE_ADDR command,用来获取RCA。

Linux Kernel初始化

参考5.x, 相关code文件如下:

linaro@rpi:~/code/kernel/linux/drivers/mmc$ ls -l core/sdio*
-rw-r--r-- 1 linaro linaro 8407 Feb 5 10:58 core/sdio_bus.c
-rw-r--r-- 1 linaro linaro 443 Feb 5 10:58 core/sdio_bus.h
-rw-r--r-- 1 linaro linaro 28230 Feb 5 10:58 core/sdio.c
-rw-r--r-- 1 linaro linaro 9021 Feb 5 10:58 core/sdio_cis.c
-rw-r--r-- 1 linaro linaro 476 Feb 5 10:58 core/sdio_cis.h
-rw-r--r-- 1 linaro linaro 21666 Feb 5 10:58 core/sdio_io.c
-rw-r--r-- 1 linaro linaro 8952 Feb 5 10:58 core/sdio_irq.c
-rw-r--r-- 1 linaro linaro 4601 Feb 5 10:58 core/sdio_ops.c
-rw-r--r-- 1 linaro linaro 932 Feb 5 10:58 core/sdio_ops.h

初始化入口:

int mmc_attach_sdio(struct mmc_host *host)
{
int err, i, funcs;
u32 ocr, rocr;
struct mmc_card *card;

WARN_ON(!host->claimed);

err = mmc_send_io_op_cond(host, 0, &ocr);
if (err)
return err;

mmc_attach_bus(host, &mmc_sdio_ops);
if (host->ocr_avail_sdio)
host->ocr_avail = host->ocr_avail_sdio;


rocr = mmc_select_voltage(host, ocr);

/*
* Can we support the voltage(s) of the card(s)?
*/
if (!rocr) {
err = -EINVAL;
goto err;
}


先走mmc_send_io_op_cond()获得ocr:

#define SD_IO_SEND_OP_COND          5 /* bcr  [23:0] OCR         R4  */

int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
{
struct mmc_command cmd = {};
int i, err = 0;

cmd.opcode = SD_IO_SEND_OP_COND;
cmd.arg = ocr;
cmd.flags = MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR;

for (i = 100; i; i--) {
err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
if (err)
break;

/* if we're just probing, do a single pass */
if (ocr == 0)
break; //tj: here

...
}

if (rocr)
*rocr = cmd.resp[mmc_host_is_spi(host) ? 1 : 0]; //tj: get OCR

return err;
}

这里ocr传进来就是0,被定义为probing。如果host支持,mmc_host_is_spi()会在drivers/mmc/host/mmc_spi.c里定义。

下来select voltage:

/*
* Mask off any voltages we don't support and select
* the lowest voltage
*/
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)

电压test and select后,进入探测初始化mmc_sdio_init_card(host, rocr, NULL)

/*
* Handle the detection and initialisation of a card.
*
* In the case of a resume, "oldcard" will contain the card
* we're trying to reinitialise.
*/
static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
struct mmc_card *oldcard)
{

oldcard是给reinit用,探测传入是NULL,暂不关心。看下这个接口主要做了哪些事情,这里不关心SPI mode:

/*
* Inform the card of the voltage
*/
err = mmc_send_io_op_cond(host, ocr, &rocr);
if (err)
goto err;

首先把之前select的voltage再设置下,然后根据响应rocr里是否有MP flag(R4_MEMORY_PRESENT)区分出SDIO card type(MMC_TYPE_SDIO)。

if ((rocr & R4_MEMORY_PRESENT) &&
mmc_sd_get_cid(host, ocr & rocr, card->raw_cid, NULL) == 0) {
card->type = MMC_TYPE_SD_COMBO;
...
}
} else {
card->type = MMC_TYPE_SDIO;
...
}

接下来set card RCA:

/*
* For native busses: set card RCA and quit open drain mode.
*/
if (!mmc_host_is_spi(host)) {
err = mmc_send_relative_addr(host, &card->rca);
if (err)
goto remove;
int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca)
{
int err;
struct mmc_command cmd = {};

cmd.opcode = SD_SEND_RELATIVE_ADDR;
cmd.arg = 0;
cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR;

err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
if (err)
return err;

*rca = cmd.resp[0] >> 16; //tj: get RCA

return 0;
}

then select card(due to 操作依赖):

/*
* Select card, as all following commands rely on that.
*/
if (!mmc_host_is_spi(host)) {
err = mmc_select_card(card);
if (err)
goto remove;
}

then 读common information area(CIA),包括CCCR(card common control)和CIS(card information struture):

/*
* Read the common registers. Note that we should try to
* validate whether UHS would work or not.
*/
err = sdio_read_cccr(card, ocr);
...
/*
* Read the common CIS tuples.
*/
err = sdio_read_common_cis(card);

目的就是让host决定要用哪些功能,最后设置High-Speed if support/clock/4-bit SD if support:

/*   
* Switch to high-speed (if supported).
*/
err = sdio_enable_hs(card);
...

/*
* Change to the card's maximum speed.
*/
mmc_set_clock(host, mmc_sdio_get_max_clock(card));

/*
* Switch to wider bus (if supported).
*/
err = sdio_enable_4bit_bus(card);

high-speed:

/*
* Enable SDIO/combo card's high-speed mode. Return 0/1 if [not]supported.
*/
static int sdio_enable_hs(struct mmc_card *card)
{
int ret;

ret = mmc_sdio_switch_hs(card, true);
if (ret <= 0 || card->type == MMC_TYPE_SDIO)
return ret;

先test是否支持then切换:

/*
* Test if the card supports high-speed mode and, if so, switch to it.
*/
static int mmc_sdio_switch_hs(struct mmc_card *card, int enable)
{
int ret;
u8 speed;

if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED))
return 0;

if (!card->cccr.high_speed)
return 0;

ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed); //tj:read
if (ret)
return ret;

if (enable)
speed |= SDIO_SPEED_EHS;
else
speed &= ~SDIO_SPEED_EHS;

ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_SPEED, speed, NULL); //tj:write
if (ret)
return ret;

return 1;
}

4-bit:

static int sdio_enable_4bit_bus(struct mmc_card *card)
{
int err;

if (card->type == MMC_TYPE_SDIO)
err = sdio_enable_wide(card);

先test是否支持then配置4-bit:

static int sdio_enable_wide(struct mmc_card *card)
{
int ret;
u8 ctrl;

if (!(card->host->caps & MMC_CAP_4_BIT_DATA))
return 0;

if (card->cccr.low_speed && !card->cccr.wide_bus)
return 0;

ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl); //tj: read
if (ret)
return ret;

if ((ctrl & SDIO_BUS_WIDTH_MASK) == SDIO_BUS_WIDTH_RESERVED)
pr_warn("%s: SDIO_CCCR_IF is invalid: 0x%02x\n",
mmc_hostname(card->host), ctrl);

/* set as 4-bit bus width */
ctrl &= ~SDIO_BUS_WIDTH_MASK;
ctrl |= SDIO_BUS_WIDTH_4BIT;

ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL); //tj: write
if (ret)
return ret;

return 1;
}

Done.

BTW: Mobile bootloader seems do not support SDIO card, but i can:]