您的位置:首页 > 运维架构 > Linux

linux2.6.28块设备mmc_sd卡初始化和识别流程及读写请求流程

2016-12-11 23:49 337 查看


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/core/core.c>

//扫描总线上设备mmc设备

void mmc_rescan(struct
work_struct *work)

{

struct mmc_host *host =

container_of(work, struct mmc_host, detect.work);

u32 ocr;

int err;

mmc_bus_get(host);

//如果为第一次扫描总线上设备

if (host->bus_ops == NULL) {

/*

* Only we can add a new handler, so it's safe to

* release the lock here.

*/

mmc_bus_put(host);

if (host->ops->get_cd && host->ops->get_cd(host) == 0)

goto out;

/* 驱动中使用mmc_claim_host(host);来得知,当前mmc控制器是否被占用,当前mmc控制器如果被占用,那么 host->claimed
= 1;否则为0,如果为1,那么会在for(;;)循环中调用schedule切换出自己,当占用mmc控制器的操作完成之后,执行 mmc_release_host()的时候,会激活登记到等待队列&host->wq中的其他程序获得mmc主控制器的物理使用权*/

//这个函数和mmc_release_host(host);配对使用,相当于一把锁,就是在同一个时间只有一个sd卡可以保持和主控制器通讯;

mmc_claim_host(host);

////最开始,卡是power_off

//开启host电源

mmc_power_up(host);

设置host为idle模式

mmc_go_idle(host);

///是用于验证SD卡接口操作状态的有效性命令(CMD8)。

//如果 SD_SEND_IF_COND指示为符合SD2.0标准的卡,

//则设置操作状态寄存器ocrbit30指示能 够处理块地址SDHC卡

//在SD子系统中,所有的数据传输均是由host发起。

//Host发送完一命令则会等待中断的产生,在这里采用完成量来实现进程的阻塞。

mmc_send_if_cond(host,
host->ocr_avail);

/*

* First we search for SDIO...

*/

//判断是否是SDIO协议卡

//通过发送命令CMD5

//CMD5协议与SD接口中的ACMD41类似,用于检查是否支持SDIO的电压。

//CMD5的回应中,MP为0,表示是SDIO卡;如果MP为1,表示不但是SDIO卡,并且是SD卡。

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

if (!err) {

if (mmc_attach_sdio(host, ocr))

mmc_power_off(host);

goto out;

}

/*

* ...then normal SD...

*/

//判断是否是SD协议卡

//ocr 是指 card 內部的 Operation Condition Register (OCR) 讀出來的值

//發送 CMD41 CMD55 讀取 OCR 的值

//#define SD_APP_OP_COND 41 /* bcr [31:0] OCR R3 */

err = mmc_send_app_op_cond(host,
0, &ocr);//本函数后面有详解

if (!err) {

//装载 绑定 SD卡设备

if (mmc_attach_sd(host, ocr))

mmc_power_off(host);

goto out;

}

/*

* ...and finally MMC.

*/

//判断是否是MMC协议卡

err = mmc_send_op_cond(host, 0, &ocr);

if (!err) {

if (mmc_attach_mmc(host, ocr))

mmc_power_off(host);

goto out;

}

////释放卡对主机的持有权

mmc_release_host(host);

mmc_power_off(host);

} else {

if (host->bus_ops->detect && !host->bus_dead)

host->bus_ops->detect(host);

mmc_bus_put(host);

}

out:

if (host->caps & MMC_CAP_NEEDS_POLL)

mmc_schedule_delayed_work(&host->detect, HZ);

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/core/core.c>

/**

* mmc_wait_for_cmd - start a command and wait for completion

* @host: MMC host to start command

* @cmd: MMC command to start

* @retries: maximum number of retries

*

* Start a new MMC command for a host, and wait for the command

* to complete. Return any error that occurred while the command

* was executing. Do not attempt to parse the response.

*/

//这个是SDIO卡的

int mmc_wait_for_cmd(struct
mmc_host *host, struct mmc_command *cmd, int retries)

{

struct mmc_request mrq;

cmd->retries = retries;

mrq.cmd = cmd;

cmd->data = NULL;

mmc_wait_for_req(host,
&mrq);

return cmd->error;

}

////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/core/sd_ops.c>

/**

* mmc_wait_for_app_cmd - start an application command and wait for

completion

* @host: MMC host to start command

* @card: Card to send MMC_APP_CMD to

* @cmd: MMC command to start

* @retries: maximum number of retries

*

* Sends a MMC_APP_CMD, checks the card response, sends the command

* in the parameter and waits for it to complete. Return any error

* that occurred while the command was executing. Do not attempt to

* parse the response.

*/

//这个是SD协议卡的

int mmc_wait_for_app_cmd(struct
mmc_host *host, struct mmc_card *card,

struct mmc_command *cmd, int retries)

{

struct mmc_request mrq;

int i, err;

BUG_ON(!cmd);

BUG_ON(retries < 0);

err = -EIO;

/*

* We have to resend MMC_APP_CMD for each attempt so

* we cannot use the retries field in mmc_command.

*/

for (i = 0;i <= retries;i++) {

memset(&mrq, 0, sizeof(struct mmc_request));

err = mmc_app_cmd(host, card);

if (err) {

/* no point in retrying; no APP commands allowed */

if (mmc_host_is_spi(host)) {

if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)

break;

}

continue;

}

memset(&mrq, 0, sizeof(struct mmc_request));

memset(cmd->resp, 0, sizeof(cmd->resp));

cmd->retries = 0;

mrq.cmd = cmd;

cmd->data = NULL;

mmc_wait_for_req(host, &mrq);

err = cmd->error;

if (!cmd->error)

break;

/* no point in retrying illegal APP commands */

if (mmc_host_is_spi(host)) {

if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)

break;

}

}

return err;

}

/////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/core/sd_ops.c>

//判断是否是SD协议卡

int mmc_send_app_op_cond(struct
mmc_host *host, u32 ocr, u32 *rocr)

{

struct mmc_command cmd;

int i, err = 0;

BUG_ON(!host);

memset(&cmd, 0, sizeof(struct mmc_command));

//判断是否是SD协议卡

//ocr 是指 card 內部的 Operation Condition Register (OCR) 讀出來的值

//發送 CMD41 CMD55 讀取 OCR 的值

//#define SD_APP_OP_COND 41 /* bcr [31:0] OCR R3 */

//见SD卡手册









cmd.opcode = SD_APP_OP_COND;

if (mmc_host_is_spi(host))

cmd.arg = ocr & (1 << 30); /* SPI only defines one bit */

else

cmd.arg = ocr;

cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;

for (i = 100; i; i--) {

//MMC_CMD_RETRIES 重复的次数?

err = mmc_wait_for_app_cmd(host,
NULL, &cmd, MMC_CMD_RETRIES);

if (err)

break;

/* if we're just probing, do a single pass */

if (ocr == 0)

break;

/* otherwise wait until reset completes */

if (mmc_host_is_spi(host)) {

if (!(cmd.resp[0] & R1_SPI_IDLE))

break;

} else {

if (cmd.resp[0] & MMC_CARD_BUSY)

break;

}

err = -ETIMEDOUT;

mmc_delay(10);

}

if (rocr && !mmc_host_is_spi(host))

*rocr = cmd.resp[0];

return err;

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/core/core.c>

/**

* mmc_wait_for_req - start a request and wait for completion

* @host: MMC host to start command

* @mrq: MMC request to start

*

* Start a new MMC custom command request for a host, and wait

* for the command to complete. Does not attempt to parse the

* response.

*/

void mmc_wait_for_req(struct
mmc_host *host, struct mmc_request *mrq)

{

DECLARE_COMPLETION_ONSTACK(complete);

mrq->done_data = &complete;

mrq->done = mmc_wait_done;

//开始request

//mmc_start_request->host->ops->request(即sdhci_request)

mmc_start_request(host,
mrq);

//这里采用完成量来实现进程的阻塞。

wait_for_completion(&complete);

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/core/sd_ops.c>

mmc_send_if_cond实现,从字面意思上看该函数就是发送一个SD标准命令,亦如usb的标准命令一样。

在这里不得不先说明一点就是在SD子系统中,所有的数据传输均是由host发起。Host发送完一命令则会等待中断的产生,在这里采用完成量来实现进程的阻塞。
int mmc_send_if_cond(struct
mmc_host *host, u32 ocr)

{

struct mmc_command cmd;

int err;

static const u8 test_pattern = 0xAA;

u8 result_pattern;

/*

* To support SD 2.0 cards, we must always invoke SD_SEND_IF_COND

* before SD_APP_OP_COND. This command will harmlessly fail for

* SD 1.0 cards.

*/

cmd.opcode = SD_SEND_IF_COND;

cmd.arg = ((ocr & 0xFF8000) != 0) << 8 | test_pattern;

cmd.flags = MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR;

//发送指令,并等待complete 完成量完成

err = mmc_wait_for_cmd(host,
&cmd, 0);

if (err)

return err;

if (mmc_host_is_spi(host))

result_pattern = cmd.resp[1] & 0xFF;

else

result_pattern = cmd.resp[0] & 0xFF;

if (result_pattern != test_pattern)

return -EIO;

return 0;

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/core/sd.c>

/*

* Starting point for SD card init.

*/

int mmc_attach_sd(struct
mmc_host *host, u32 ocr)

{

int err;

BUG_ON(!host);

WARN_ON(!host->claimed);

mmc_attach_bus(host, &mmc_sd_ops);

/*

* We need to get OCR a different way for SPI.

*/

if (mmc_host_is_spi(host)) {

mmc_go_idle(host);

err = mmc_spi_read_ocr(host, 0, &ocr);

if (err)

goto err;

}

/*

* Sanity check the voltages that the card claims to

* support.

*/

if (ocr & 0x7F) {

printk(KERN_WARNING "%s: card claims to support voltages "

"below the defined range. These will be ignored.\n",

mmc_hostname(host));

ocr &= ~0x7F;

}

if (ocr & MMC_VDD_165_195) {

printk(KERN_WARNING "%s: SD card claims to support the "

"incompletely defined 'low voltage range'. This "

"will be ignored.\n", mmc_hostname(host));

ocr &= ~MMC_VDD_165_195;

}

host->ocr = mmc_select_voltage(host, ocr);

/*

* Can we support the voltage(s) of the card(s)?

*/

if (!host->ocr) {

err = -EINVAL;

goto err;

}

/*

* Detect and init the card.

*/

err = mmc_sd_init_card(host,
host->ocr, NULL);

if (err)

goto err;

mmc_release_host(host);

err = mmc_add_card(host->card);

if (err)

goto remove_card;

return 0;

remove_card:

mmc_remove_card(host->card);

host->card = NULL;

mmc_claim_host(host);

err:

mmc_detach_bus(host);

mmc_release_host(host);

printk(KERN_ERR "%s: error %d whilst initialising SD card\n",

mmc_hostname(host), err);

return err;

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/core/mmc.c>

/*

* Starting point for MMC card init.

*/

int mmc_attach_mmc(struct
mmc_host *host, u32 ocr)

{

int err;

BUG_ON(!host);

WARN_ON(!host->claimed);

mmc_attach_bus(host, &mmc_ops);

/*

* We need to get OCR a different way for SPI.

*/

if (mmc_host_is_spi(host)) {

err = mmc_spi_read_ocr(host, 1, &ocr);

if (err)

goto err;

}

/*

* Sanity check the voltages that the card claims to

* support.

*/

if (ocr & 0x7F) {

printk(KERN_WARNING "%s: card claims to support voltages "

"below the defined range. These will be ignored.\n",

mmc_hostname(host));

ocr &= ~0x7F;

}

host->ocr = mmc_select_voltage(host, ocr);

/*

* Can we support the voltage of the card?

*/

if (!host->ocr) {

err = -EINVAL;

goto err;

}

/*

* Detect and init the card.

*/

err = mmc_init_card(host, host->ocr, NULL);

if (err)

goto err;

mmc_release_host(host);

err = mmc_add_card(host->card);

if (err)

goto remove_card;

return 0;

remove_card:

mmc_remove_card(host->card);

host->card = NULL;

mmc_claim_host(host);

err:

mmc_detach_bus(host);

mmc_release_host(host);

printk(KERN_ERR "%s: error %d whilst initialising MMC card\n",

mmc_hostname(host), err);

return err;

}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/core/sdio.c>

/*

* Starting point for SDIO card init.

*/

int mmc_attach_sdio(struct
mmc_host *host, u32 ocr)

{

int err;

int i, funcs;

struct mmc_card *card;

BUG_ON(!host);

WARN_ON(!host->claimed);

mmc_attach_bus(host, &mmc_sdio_ops);

/*

* Sanity check the voltages that the card claims to

* support.

*/

if (ocr & 0x7F) {

printk(KERN_WARNING "%s: card claims to support voltages "

"below the defined range. These will be ignored.\n",

mmc_hostname(host));

ocr &= ~0x7F;

}

if (ocr & MMC_VDD_165_195) {

printk(KERN_WARNING "%s: SDIO card claims to support the "

"incompletely defined 'low voltage range'. This "

"will be ignored.\n", mmc_hostname(host));

ocr &= ~MMC_VDD_165_195;

}

host->ocr = mmc_select_voltage(host, ocr);

/*

* Can we support the voltage(s) of the card(s)?

*/

if (!host->ocr) {

err = -EINVAL;

goto err;

}

/*

* Inform the card of the voltage

*/

err = mmc_send_io_op_cond(host,
host->ocr, &ocr);

if (err)

goto err;

/*

* For SPI, enable CRC as appropriate.

*/

if (mmc_host_is_spi(host)) {

err = mmc_spi_set_crc(host, use_spi_crc);

if (err)

goto err;

}

/*

* The number of functions on the card is encoded inside

* the ocr.

*/

funcs = (ocr & 0x70000000) >> 28;

/*

* Allocate card structure.

*/

card = mmc_alloc_card(host, NULL);

if (IS_ERR(card)) {

err = PTR_ERR(card);

goto err;

}

card->type = MMC_TYPE_SDIO;

card->sdio_funcs = funcs;

host->card = card;

/*

* 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;

mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);

}

/*

* Select card, as all following commands rely on that.

*/

if (!mmc_host_is_spi(host)) {

err = mmc_select_card(card);

if (err)

goto remove;

}

/*

* Read the common registers.

*/

err = sdio_read_cccr(card);

if (err)

goto remove;

/*

* Read the common CIS tuples.

*/

err = sdio_read_common_cis(card);

if (err)

goto remove;

/*

* Switch to high-speed (if supported).

*/

err = sdio_enable_hs(card);

if (err)

goto remove;

/*

* Change to the card's maximum speed.

*/

if (mmc_card_highspeed(card)) {

/*

* The SDIO specification doesn't mention how

* the CIS transfer speed register relates to

* high-speed, but it seems that 50 MHz is

* mandatory.

*/

mmc_set_clock(host, 50000000);

} else {

mmc_set_clock(host, card->cis.max_dtr);

}

/*

* Switch to wider bus (if supported).

*/

err = sdio_enable_wide(card);

if (err)

goto remove;

/*

* Initialize (but don't add) all present functions.

*/

for (i = 0;i < funcs;i++) {

err = sdio_init_func(host->card, i + 1);

if (err)

goto remove;

}

mmc_release_host(host);

/*

* First add the card to the driver model...

*/

err = mmc_add_card(host->card);

if (err)

goto remove_added;

/*

* ...then the SDIO functions.

*/

for (i = 0;i < funcs;i++) {

err = sdio_add_func(host->card->sdio_func[i]);

if (err)

goto remove_added;

}

return 0;

remove_added:

/* Remove without lock if the device has been added. */

mmc_sdio_remove(host);

mmc_claim_host(host);

remove:

/* And with lock if it hasn't been added. */

if (host->card)

mmc_sdio_remove(host);

err:

mmc_detach_bus(host);

mmc_release_host(host);

printk(KERN_ERR "%s: error %d whilst initialising SDIO card\n",

mmc_hostname(host), err);

return err;

}

<drivers/mmc/core/core.c>

观察传递的参数,是mrq。可以知道mmc最终命令的承载都是用struct mmc_request *mrq 这样的结构完成

static void

mmc_start_request(struct mmc_host *host, struct
mmc_request *mrq)

{

if (mrq->data) {

mmc_hostname(host), mrq->data->blksz,

mrq->data->blocks, mrq->data->flags,

mrq->data->timeout_ns / 1000000,

mrq->data->timeout_clks);

}

if (mrq->stop) {

pr_debug("%s: CMD%u arg %08x flags %08x\n",

mmc_hostname(host), mrq->stop->opcode,

mrq->stop->arg, mrq->stop->flags);

}

led_trigger_event(host->led, LED_FULL);

mrq->cmd->error = 0;

mrq->cmd->mrq = mrq;

if (mrq->data) {

host->max_req_size);

mrq->cmd->data = mrq->data;

mrq->data->error = 0;

mrq->data->mrq = mrq;

if (mrq->stop) {

mrq->data->stop = mrq->stop;

mrq->stop->error = 0;

mrq->stop->mrq = mrq;

}

}

////mmc_start_request->host->ops->request(即sdhci_request)

///mmc_start_request要交给各个host完成处理了。Mmc驱动是一个通用框架驱动,

//不同的host对应的命令处理必定有所差别。针对sdhci标准的host mmc驱动。

//host->ops->request(host, mrq);的执行将交给,sdhci_request()完成。

host->ops->request(host, mrq);

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/host/sdhci.c>

static const struct mmc_host_ops sdhci_ops =
{

//request函数指针指向的函数用来处理host向从设备发送命令的请求,

.request = sdhci_request,

//set_ios用来设置电源、时钟等等之类(需要重点关注),

.set_ios = sdhci_set_ios,

//get_ro用来判断是否写保护

.get_ro = sdhci_get_ro,

//使能SD/MMC IRQ中断

.enable_sdio_irq = sdhci_enable_sdio_irq,

};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/host/sdhci.c>

//static const struct mmc_host_ops sdhci_ops = {

// .request = sdhci_request,

//request函数指针指向的函数用来处理host向从设备发送命令的请求,

/*****************************************************************************\

* *

* MMC callbacks *

* *

\*****************************************************************************/

static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)

{

//注意函数一进来,host结构体发生变化,已经不再是mmc_host结构,

//而是各具体的厂商的host,如这里的struct sdhci_host *host;

//其实是host = mmc_priv(mmc);这么的得来的。

struct sdhci_host *host;

unsigned long flags;

host = mmc_priv(mmc);

spin_lock_irqsave(&host->lock, flags);

#ifndef CONFIG_LEDS_CLASS

sdhci_activate_led(host);

#endif

//保存起mrq结构

host->mrq = mrq;

if ((mmc->caps & MMC_CAP_ON_BOARD) || (host->flags & SDHCI_DEVICE_ALIVE))

//当命令传输完成系统调用中断处理函数sdhci_irq。在其中进行中断完成后的处理???。

//<drivers/mmc/host/sdhci.c>sdhci_add_host 中

// tasklet_init(&host->finish_tasklet, sdhci_tasklet_finish, (unsigned long)host);

sdhci_send_command(host, mrq->cmd);//发送命令

else {

if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)

|| (host->flags & SDHCI_DEVICE_DEAD)) {

host->mrq->cmd->error = -ENOMEDIUM;

tasklet_schedule(&host->finish_tasklet);

} else

sdhci_send_command(host, mrq->cmd);

}

//为了保证编译器顺序编译,防止编译器优化打乱执行顺序

mmiowb();

spin_unlock_irqrestore(&host->lock, flags);

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

<drivers/mmc/host/sdhci.c>

static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)

{

int flags;

u32 mask;

unsigned long timeout;

/* Wait max 10 ms */

timeout = 10;

//SDHCI_PRESENT_STATE 寄存器的第0位



mask = SDHCI_CMD_INHIBIT;

//SDHCI_PRESENT_STATE 寄存器的第1位



if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY))

mask |= SDHCI_DATA_INHIBIT;

/* We shouldn't wait for data inihibit for stop commands, even

though they might use busy signaling */

if (host->mrq->data && (cmd == host->mrq->data->stop))

mask &= ~SDHCI_DATA_INHIBIT;

//<drivers/mmc/host/sdhci.h>

//#define SDHCI_PRESENT_STATE 0x24

//读一个长整形数据(8字节)当前状态寄存器

while (readl(host->ioaddr + SDHCI_PRESENT_STATE) & mask) {

if (timeout == 0) {

printk(KERN_ERR "%s: Controller never released "

"inhibit bit(s).\n", mmc_hostname(host->mmc));

sdhci_dumpregs(host);

cmd->error = -EIO;

//<drivers/mmc/host/sdhci.c>

//int sdhci_add_host(struct sdhci_host *host)

// tasklet_init(&host->finish_tasklet, sdhci_tasklet_finish, (unsigned long)host);

////finish_tasklet用于命令传输完成后的处理

tasklet_schedule(&host->finish_tasklet);

return;

}

timeout--;

mdelay(1);

}

//改变定时器时间,设置定时时间

mod_timer(&host->timer, jiffies + 10 * HZ);

//命令保存到host起来

host->cmd = cmd;

//准备数据

//里面的关键就是准备DMA,dma_map_sg(),

//从data->sg里面获取到信息,填充到DMA控制器里面。

sdhci_prepare_data(host, cmd->data);

////<drivers/mmc/host/sdhci.h>

//#define SDHCI_ARGUMENT 0x08

//SD命令参数寄存器



writel(cmd->arg, host->ioaddr + SDHCI_ARGUMENT);

sdhci_set_transfer_mode(host, cmd->data);

if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {

printk(KERN_ERR "%s: Unsupported response type!\n",

mmc_hostname(host->mmc));

cmd->error = -EINVAL;

//启动finish_tasklet完成后面的事情。finish_tasklet的定义是sdhci_tasklet_finish

tasklet_schedule(&host->finish_tasklet);

return;

}

if (!(cmd->flags & MMC_RSP_PRESENT))

flags = SDHCI_CMD_RESP_NONE;

else if (cmd->flags & MMC_RSP_136)

flags = SDHCI_CMD_RESP_LONG;

else if (cmd->flags & MMC_RSP_BUSY)

flags = SDHCI_CMD_RESP_SHORT_BUSY;

else

flags = SDHCI_CMD_RESP_SHORT;

if (cmd->flags & MMC_RSP_CRC)

flags |= SDHCI_CMD_CRC;

if (cmd->flags & MMC_RSP_OPCODE)

flags |= SDHCI_CMD_INDEX;

if (cmd->data)

flags |= SDHCI_CMD_DATA;

//数据发送,这才是真正的控制host发送命令的操作,到这类,mmc控制器才开始跟sd卡做交互

//#define SDHCI_COMMAND 0x0E



writew(SDHCI_MAKE_CMD(cmd->opcode, flags),

host->ioaddr + SDHCI_COMMAND);

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息