分类: LINUX
2014-09-26 06:53:28
原文地址:编写自己的SD/MMC Host驱动(二):工作过程和大结局 作者:fychit
上次说到,如果注册成功的话,Linux就会开始初始化SD/MMC了,SD/MMC的初始化都是通过drivers/mmc/core/core.c里面mmc_rescan来完成的:
void mmc_rescan(struct work_struct *work)
{
.......
if (host->ops->get_cd && host->ops->get_cd(host) == 0)
goto out;
...................
mmc_power_up(host);
mmc_go_idle(host);
...................
mmc_send_if_cond(host, host->ocr_avail);
/*
* First we search for SDIO...
*/
...................
err = mmc_send_io_op_cond(host, 0, &ocr);
...................
/*
* ...then normal SD...
*/
err = mmc_send_app_op_cond(host, 0, &ocr);
...................
/*
* ...and finally MMC.
*/
err = mmc_send_op_cond(host, 0, &ocr);
...................
}
首先调用get_cd方法,看看是不是有卡在里面,不然岂不是白忙活!
然后调用mmc_power_up来上电,该函数将调用驱动的set_ios操作函数,控制电源和时钟等。
电也上了,此时就需要调用mmc_go_idle让SD/MMC卡进入IDLE状态,如果你对协议比较熟的话,就是发送CMD0了。然后调用mmc_send_if_cond发送CMD8设置接口,这个命令只有SD2.0才会响应,对于1.0标准是没关系的。然后就开始判断插入的卡到底是SDIO,SD还是MMC,其实都是通过发送特定的命令,看看其是否响应来判断的,其实这种方法不是很好,估计协议的设计者开始的时候没有想到会有如此多类型,因此没有设计一条专门的命令来查询卡的类型。可以看到SDIO是通过CMD5来识别的,如果有回应则是SDIO卡。SD卡是通过ACMD41来实现的,由于ACMD需要先发切换命令CMD55,因此如果在request里面打log,将会发现发了两条opcode分别为55和41的命令。MMC的判断则是通过CMD1,即MMC的send operation condition 命令。这些命令都是各个类型所独有的,因此可以用来判断卡的类型。
卡的类型确定以后就会调用mmc_init_card来初始化卡,关于发送命令的过程需要参考协议的具体描述。
说了半天,终于应该说SD/MMC驱动中最重要的函数request了,其实SD/MMC的操作还是很单纯的,典型的一问一答型:先发送request,然后等待request完成,如果request没有完成,则不会发起下一个request,如果程序有bug,超过120s都不回应,恭喜你,系统会崩溃的。发送request当然是通过之前注册的mmc_host_ops里面的函数来发起的了,回应则是通过调用mmc_request_done来完成的。Request函数的实现一般是这样的:
static void cbpmci_send_request(struct mmc_host *mmc)
{
struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
if (cmd->data) {
cbpmci_setup_data(host, mrq->data);
host->dcnt++;
cbpmci_prepare_pio(host, cmd->data);
}
cbpmci_send_command(host, cmd);
}
首先通过cmd->data是否为空来判断这个命令是否有数据,然后做一些准备工作,比如block size, block count之类的东东,这些参数都在cmd->data里面。其实在这一段是不收发实际的数据的,只是准备而已,因为协议规定了,数据必须在命令发完了以后才能收发数据,因此其中一些准备工作也可以在命令完成中断里面来完成。不过标识request究竟对数据是进行读还是写的动作需要在这里完成,因为命令完成中断发生时需要利用这个信息。Samsung的那个prepare写的非常让人困惑,PXA的写得就清楚多了。
static int cbpmci_prepare_pio(struct cbpmci_host *host, struct mmc_data *data)
{
host->pio_active=XFER_NONE;
if(data->flags & MMC_DATA_READ) host->pio_active=XFER_READ;
if(data->flags & MMC_DATA_WRITE) host->pio_active=XFER_WRITE;
}
紧接着就是调用cbpmci_send_command发送命令了。
命令发送完成后会产生命令完成中断,在处理该中断时应该判断当前的状态,即host->pio_active的值,如果该命令没有数据,则调用mmc_request_done直接返回,如果有则打开读写相关的中断,读写数据,在数据读写完成后在调用mmc_request_done通知上层。一般的读写过程是这样的,以读为例,在收到命令完成的中断后,判断此时处于读状态,然后打开发送FIFO的almost full、full以及数据完成中断,在这almost full和full中断的处理过程中读取FIFO的值,放入上层提供的scatterlist中,SD/MMC中的scatterlist操作在上上一篇博文中有详细的描写,当数据完成中断产生或者scatterlist已经填满时调用mmc_request_done通知上层。
在发送命令和数据的过程中都可能产生错误,这些错误都可以通过host->mrq->cmd以及host->mrq->data中的相关元素返回:
if (stat & HWD_MMC_CMD_RSPTOUT_ERR) cmd->error = -ETIMEDOUT;
else if (stat & HWD_MMC_CMD_RSP_CRC_ERR&& cmd->flags & MMC_RSP_CRC) cmd->error = -EILSEQ;
或者:
if (stat & HWD_MMC_DAT_TOUT_ERR) data->error = -ETIMEDOUT;
else if (stat & (HWD_MMC_RXDAT_CRC_ERR|HWD_MMC_CRC_STS_ERR)) data->error = -EILSEQ;
至于发送命令的时候产生的response,则是根据返回是48bit或者136bit有所不同,host->mrq->cmd中的response只定义了4个word,也就是128位。在48bit返回值时cmd->resp[0]中是32bit的card status,cmd->resp[1]的高7位是回应的CRC值,第24位是停止位,就是“1”,没有研究代码,不知CRC和停止位不知是否有用。当为136bit时,其中的resp[0]到resp[3]中是128bit的card status。 PXA和CBP的状态位都需要调整后才能写入resp[0]到resp[3],而Samsung的s3cmci则可以直接从寄存器读出然后赋值,估计这也是三星的处理器之所以火的细节之一。
关于卡的热插拔,当卡插拔的时候,一般会产生中断,在中断中调用mmc_detect_change通知上层
if(isr_status & HWD_MMC_CARD_STS)mmc_detect_change(host->mmc, msecs_to_jiffies(500));
其实是启动了一个工作队列,看代码可知,该工作队列就是调用mmc_rescan函数,轰轰烈烈一番后,重新回到了原点,世界清静了。
如果各个过程都没有错误的话,在/dev下面就会出现两个设备节点mmcblk0和mmcblk0p1,如果没有的话,请问是不是忘敲了mdev –s 了?
此时就可以mount了,不过mount之前由于一般SD/MMC卡都是FAT文件系统,因此还要子在内核中加入dos 文件系统和FAT的支持。然后就可以mount了
Mount /dev/mmcblk0p1 /mnt
就可以到/mnt目录下看看你的成果了,如果写人小的文件,比如创建目录,一般要在sync或umount的时候才会产生实际的写入动作。
在mount的过程中如果出现错误:FAT: codepage cp437 not found。 那是你的内核字符集不支持437,在内核中选上就OK了。
以下是各个配置的图片:
1)MMC配置
2)FAT配置
3)Code Page 437配置
4)附录: set_ios范例
set_ios设置一些控制参数,比如时钟频率,因为在初始化正常操作的时候时钟是不一样的,初始化的时候一般小于400K,正常操作的时候是26M,52M等等高得多的频率。还有就是控制电源以及SD卡的总线宽度,CBP的SD总线宽度1,4,8都是支持的。Set_ios的代码基于文章后面的附录中。
static void cbpmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
..........................
if (ios->power_mode==MMC_POWER_UP) {
...................................................
}
else if(ios->power_mode=MMC_POWER_ON) {
............................
}
else if(ios->power_mode=MMC_POWER_OFF) {
............................
}
if (ios->clock)
{
/*此处的频率是以HZ为单位的*/
}
else
{
/*关闭时钟*/
}
if (ios->bus_width == MMC_BUS_WIDTH_4)
{
//4bit
}
else if (ios->bus_width == MMC_BUS_WIDTH_8)
{
//8bit
}
else
{
//1bit
}
}