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:]