Chinaunix首页 | 论坛 | 博客
  • 博客访问: 30734
  • 博文数量: 12
  • 博客积分: 291
  • 博客等级: 二等列兵
  • 技术积分: 140
  • 用 户 组: 普通用户
  • 注册时间: 2011-08-19 09:48
文章分类

全部博文(12)

文章存档

2011年(12)

我的朋友
最近访客

分类: LINUX

2011-08-24 19:14:32

Mini2440 SD卡驱动程序分析 收藏  Mini2440 SD卡驱动程序分析
参考资料:
1.SD Memory Card Specifications / Part 1. Physical Layer Specification; Version 1.0  
2.LDD3 CHAPTER-16 BLOCK DEVICE
3.
引言:前几天把mini2440的sd卡驱动程序移植到了Android平台,当时对SD卡以及内核的MMC子系统不是很了解,浏览了四天的代码,终于理清了一些头绪,尽管很多细节的实现还不是很清楚,不过先把知道的记录下来,细节部分由时间在慢慢挖掘。本文先介绍了一下MMC的基本框架结构,然后采用自底向上的方法来分析整个MMC层是如何共同作用的。阅读时请结合参考资料1和2.
1.硬件基础:
http://blog.ednchina.com/yelov/198217/message.aspx
SD/MMC/SDIO 概念区分概要
SD(Secure Digital)与 MMC(Multimedia Card)
SD 是一种 flash memory card 的标准,也就是一般常见的 SD 记忆卡,而 MMC 则是较早的一种记忆卡标准,目前已经被 SD 标准所取代。在维基百科上有相当详细的 SD/MMC 规格说明:[http://zh.wikipedia.org/wiki/Secure_Digital]。
SDIO 是目前我们比较关心的技术,SDIO 故名思义,就是 SD 的 I/O 接口(interface)的意思,不过这样解释可能还有点抽像。更具体的说明,SD 本来是记忆卡的标准,但是现在也可以把 SD 拿来插上一些外围接口使用,这样的技术便是 SDIO。
所以 SDIO 本身是一种相当单纯的技术,透过 SD 的 I/O 接脚来连接外部外围,并且透过 SD 上的 I/O 数据接位与这些外围传输数据,而且 SD 协会会员也推出很完整的 SDIO stack 驱动程序,使得 SDIO 外围(我们称为 SDIO 卡)的开发与应用变得相当热门。
现在已经有非常多的手机或是手持装置都支持 SDIO 的功能(SD 标准原本就是针对 mobile device 而制定),而且许多 SDIO 外围也都被开发出来,让手机外接外围更加容易,并且开发上更有弹性(不需要内建外围)。目前常见的 SDIO 外围(SDIO 卡)有:
·                                 Wi-Fi card(无线网络卡) 
·                                 CMOS sensor card(照相模块) 
·                                 GPS card 
·                                 GSM/GPRS modem card 
·                                 Bluetooth card 
·                                 Radio/TV card(很好玩)
SDIO 的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前 GPIO 式的 SPI 接口。
SD/SDIO 的传输模式
SD 传输模式有以下 3 种:
·                                 SPI mode(required) 
·                                 1-bit mode 
·                                 4-bit mode
SDIO 同样也支持以上 3 种传输模式。依据 SD 标准,所有的 SD(记忆卡)与 SDIO(外围)都必须支持 SPI mode,因此 SPI mode 是「required」。此外,早期的 MMC 卡(使用 SPI 传输)也能接到 SD 插糟(SD slot),并且使用 SPI mode 或 1-bit mode 来读取。
SD 的 MMC Mode
SD 也能读取 MMC 内存,虽然 MMC 标准上提到,MMC 内存不见得要支持 SPI mode(但是一定要支持 1-bit mode),但是市面上能看到的 MMC 卡其实都有支持 SPI mode。因此,我们可以把 SD 设定成 SPI mode 的传输方式来读取 MMC 记忆卡。
SD 的 MMC Mode 就是用来读取 MMC 卡的一种传输模式。不过,SD 的 MMC Mode 虽然也是使用 SPI mode,但其物理特性仍是有差异的:
·                                 MMC 的 SPI mode 最大传输速率为 20 Mbit/s; 
·                                 SD 的 SPI mode 最大传输速率为 25 Mbit/s。
为避免混淆,有时也用 SPI/MMC mode 与 SPI/SD mode 的写法来做清楚区别。
2.MMC子系统的基本框架结构:
很遗憾,内核没有为我们提供关于MMC子系统的文档,在谷歌上搜索了很多,也没有找到相关文章。只能自己看代码分析了,可能有很多理解不对的地方,希望研究过这方面的朋友多邮件交流一下。
MMC子系统的代码在kernel/driver/MMC下,目前的MMC子系统支持一些形式的记忆卡:SD,SDIO,MMC.由于笔者对SDIO的规范不是很清楚,后面的分析中不会涉及。MMC子系统范围三个部分:
HOST部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的。
CORE部分:这是整个MMC的核心存,这部分完成了不同协议和规范的实现,并为HOST层的驱动提供了接口函数。
CARD部分:因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD卡如何实现为块设备的。
3.HOST层分析:
HOST层实现的就是我们针对特定主机的驱动程序,这里以mini2440的s3cmci.c为例子进行分析,我们先采用platform_driver_register(&s3cmci_2440_driver)注册了一个平台设备,接下来重点关注probe函数。在这个函数总,我们与CORE的联系是通过下面三句实现的。首先分配一个mmc_host结构体,注意sizeof(struct s3cmci_host),这样就能在mmc_host中找到了s3cmci_host,嵌入结构和被嵌入的结构体能够找到对方在Linux内核代码中的常用技术了。接下来为mmc->pos赋值,s3cmci_ops结构实现了几个很重要的函数,待会我一一介绍。中间还对mmc结构的很多成员进行了赋值,最后将mmc结构加入到MMC子系统,mmc_alloc_host,以及mmc_add_host的具体做了什么事情,我们在下节再分析,这三句是些MMC层驱动必须包含的。
mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
mmc->ops = &s3cmci_ops;
……………
s3cmci_ops中包含了四个函数:
static struct mmc_host_ops s3cmci_ops = {
       .request  = s3cmci_request,
       .set_ios   = s3cmci_set_ios,
       .get_ro          = s3cmci_get_ro,
       .get_cd          = s3cmci_card_present,
};
我们从简单的开始分析,这些函数都会在core部分被调用:
s3cmci_get_ro:这个函数通过从GPIO读取,来判断我们的卡是否是写保护的
s3cmci_card_present:这个函数通过从GPIO读取来判断卡是否存在
s3cmci_set_ios:s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
依据核心层传递过来的ios,来设置硬件IO,包括引脚配置,使能时钟,和配置总线带宽。
s3cmci_request:这个函数是最主要,也最复杂的函数,实现了命令和数据的发送和接收,
当CORE部分需要发送命令或者传输数据时,都会调用这个函数,并传递mrq请求。
static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
       struct s3cmci_host *host = mmc_priv(mmc);
       host->status = "mmc request";
       host->cmd_is_stop = 0;
       host->mrq = mrq;
 
       if (s3cmci_card_present(mmc) == 0) {
              dbg(host, dbg_err, "%s: no medium present\n", __func__);
              host->mrq->cmd->error = -ENOMEDIUM;
              mmc_request_done(mmc, mrq);//如果卡不存在,就终止请求
       } else
              s3cmci_send_request(mmc);
}
接下来看s3cmci_send_request(mmc):
这个函数先判断一下请求时传输数据还是命令,如果是数据的话:
先调用s3cmci_setup_data来对S3C2410_SDIDCON寄存器进行设置,然后设置SDITIMER寄存器这就设置好了总线宽度,是否使用DMA,,并启动了数据传输模式,并且使能了下面这些中断:
imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
              S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
解析来判断是否是采用DMA进行数据传输还是采用FIFO进行数据传输
if (host->dodma)
/      because host->dodma           = 0,so we don't use it
                     res = s3cmci_prepare_dma(host, cmd->data);//准备DMA传输,
              else
                     res = s3cmci_prepare_pio(host, cmd->data);.//准备FIFO传输
如果是命令的话:则调用s3cmci_send_command()这个函数是命令发送的函数,和datesheet上描述的过程差不多,关于SD规范中命令的格式,请参考参考资料1.
writel(cmd->arg, host->base + S3C2410_SDICMDARG);/*先写参数寄存器
         ccon  = cmd->opcode & S3C2410_SDICMDCON_INDEX;//确定命令种类
         ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART;
/*with start 2bits*/
         if (cmd->flags & MMC_RSP_PRESENT)
                   ccon |= S3C2410_SDICMDCON_WAITRSP;
/*wait rsp*/
         if (cmd->flags & MMC_RSP_136)
                   ccon |= S3C2410_SDICMDCON_LONGRSP;
//确定respose的种类
         writel(ccon, host->base + S3C2410_SDICMDCON);
 
命令通道分析完了,我们分析数据通道,先分析采用FIFO方式传输是怎么样实现的。
先分析s3cmci_prepare_pio(host, cmd->data)
根据rw来判断是读还是写
if (rw) {
              do_pio_write(host);
              /* Determines SDI generate an interrupt if Tx FIFO fills half*/
              enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
       } else {
              enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF
                          | S3C2410_SDIIMSK_RXFIFOLAST);
       }
如果是写数据到SD的话,会调用do_pio_write,往FIFO中填充数据。当64字节的FIFO少于33字节时就会产生中断。如果是从SD读数据,则先使能中断,当FIFO多于31字节时时,则会调用中断服务程序,中断服务程序中将会调用do_pio_read FIFO的数据读出。
接下来分析do_pio_write:
to_ptr = host->base + host->sdidata;
fifo_free(host)用来检测fifo剩余空间
while ((fifo = fifo_free(host)) > 3) {
                   if (!host->pio_bytes) {
                            res = get_data_buffer(host, &host->pio_bytes,
                   /* If we have reached the end of the block, we have to
                    * write exactly the remaining number of bytes.  If we
                    * in the middle of the block, we have to write full
                    * words, so round down to an even multiple of 4. */
                   if (fifo >= host->pio_bytes)//fifo的空间比pio_bytes大,表明这是读这个块的最后一次
                            fifo = host->pio_bytes;
                   /* because the volume of FIFO can contain the remaning block*/
                   else
                            fifo -= fifo & 3;/*round down to an even multiple of 4*/
 
                   host->pio_bytes -= fifo;//更新还剩余的没有写完的字
                   host->pio_count += fifo;/*chang the value of pio_bytes*/
 
                   fifo = (fifo + 3) >> 2;//将字节数转化为字数
                   /*how many words fifo contain,every time we just writ one word*/
                   ptr = host->pio_ptr;
                   while (fifo--)
                            writel(*ptr++, to_ptr);//写往FIFO.
                   host->pio_ptr = ptr;
         }
注释一:注意,MMC核心为mrq->data成员分配了一个struct scatterlist的表,用来支持分散聚集,使用这种方法,这样使物理上不一致的内存页,被组装成一个连续的数组,避免了分配大的缓冲区的问题
我们看代码
         if (host->pio_sgptr >= host->mrq->data->sg_len) {
                   dbg(host, dbg_debug, "no more buffers (%i/%i)\n",
                         host->pio_sgptr, host->mrq->data->sg_len);
                   return -EBUSY;
         }
         sg = &host->mrq->data->sg[host->pio_sgptr];
         *bytes = sg->length;//页缓冲区中的长度
         *pointer = sg_virt(sg);将页地址映射为虚拟地址
         host->pio_sgptr++;这里表明我们的程序又完成了一次映射
这样,每一个mmc请求,我们只能处理scatterlist表中的一个页(块)。因此,完成一次完整的请求需要映射sg_len次
再来总结一下一个mmc写设备请求的过程:
在s3cmci_prepare_pio中我们第一次先调用do_pio_write,如果FIFO空间大于3,且能够获取到scatterlist,则我们就开始往FIFO写数据,当FIFO空间小于3,则使能TXFIFOHALF中断,在中断服务程序中,如果检测到TFDET表明又有FIFO空间了,则关闭TXFIFOHALF中断,并调用do_pio_write进行写。
数据流向如下:scatterlist-------->fifo---------->sdcard
一个mmc读设备请求的过程数据流向如下:sdcard --------> fifo ---------->scatterlist,
????关于读数据的过程,中断的触发不是很清楚,s3cmci_prepare_pio中enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF,S3C2410_SDIIMSK_RXFIFOLAST);但如果没从SD卡中读数据,怎么会引发这个中断呢?是由S3C2410_SDIIMSK_RXFIFOLAST引起的吗
接下来我们分析一下中断服务程序:
static irqreturn_t s3cmci_irq(int irq, void *dev_id)
该程序先获取所有的状态寄存器:
mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
         mci_dsta = readl(host->base + S3C2410_SDIDSTA);
         mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
         mci_fsta = readl(host->base + S3C2410_SDIFSTA);
         mci_imsk = readl(host->base + host->sdiimsk);
这些将作为中断处理的依据。
如果不是DMA模式,则处理数据的收发
if (!host->dodma) {
                   if ((host->pio_active == XFER_WRITE) &&
                       (mci_fsta & S3C2410_SDIFSTA_TFDET)) {
/*This bit indicates that FIFO data is available for transmit when
DatMode is data transmit mode. If DMA mode is enable, sd
host requests DMA operation.*/
                            disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
                            tasklet_schedule(&host->pio_tasklet);
注意我们采用tasklet这种延时机制来减少中断服务的时间,延时函数pio_tasklet中调用了do_pio_write和了do_pio_read
                            host->status = "pio tx";
                   }
 
                   if ((host->pio_active == XFER_READ) &&
                       (mci_fsta & S3C2410_SDIFSTA_RFDET)) {
 
                            disable_imask(host,
                                           S3C2410_SDIIMSK_RXFIFOHALF |
                                           S3C2410_SDIIMSK_RXFIFOLAST);
 
                            tasklet_schedule(&host->pio_tasklet);
                            host->status = "pio rx";
                   }
         接下来的很多代码是对其他的一些类型中断的处理。
最后来分析DMA模式:这种模式下不需要CPU的干预。S3C2440的DMA有4个通道,我们选择了通道0
static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
{
         int dma_len, i;
         int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;
 
         BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);
 
         s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW);//注一
         s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
 
         dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
                                 (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);//注二
 
         if (dma_len == 0)
                   return -ENOMEM;
 
         host->dma_complete = 0;
         host->dmatogo = dma_len;
 
         for (i = 0; i < dma_len; i++) {
                   int res;
 
                   dbg(host, dbg_dma, "enqueue %i:%u@%u\n", i,
                            sg_dma_address(&data->sg[i]),
                            sg_dma_len(&data->sg[i]));
 
                   res = s3c2410_dma_enqueue(host->dma, (void *) host,
                                                 sg_dma_address(&data->sg[i]),
                                                 sg_dma_len(&data->sg[i]));
 
                   if (res) {
                            s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
                            return -EBUSY;
                   }
         }
 
         s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);
 
         return 0;
}
注一:这个函数先调用s3c2410_dma_devconfig来配置DMA 源/目的的意见类型和地址,注意我们这里的设备地址host->mem->start + host->sdidata实际上就是SDIDATA寄存器的地址值,如果是写SD卡,则为目的地址,否则为源地址。然后调用s3c2410_dma_set_buffdone_fn(host->dma, s3cmci_dma_done_callback);
设置dma通道0的回调函数。
注二:
dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
                                 (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
这里进行分散/聚集映射(P444,LDD3),返回值是传送的DMA缓冲区数,可能会小于sg_len,也就是说sg_len与dma_len可能是不同的。
sg_dma_address(&data->sg[i]),返回的是总线(DMA)地址
sg_dma_len(&data->sg[i])); 返回的是缓冲区的长度。
最后调用s3c2410_dma_enqueue(host->dma, (void *) host,
                                                 sg_dma_address(&data->sg[i]),
                                                 sg_dma_len(&data->sg[i]));
对每个DMA缓冲区进行排队,等待处理。
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);启动DMA
这样DMA缓冲区就和scatterlist联系起来,当写数据时,scatterlist中的数据由于上面的映射关系会直接“拷贝”到DMA缓冲区,当读数据时则反之。整个过程不需要CPU干预,自动完成。
以上就是针对mini2440 HOST部分的内容。
 
 
4、CORE层分析:
CORE层完成了不同协议和规范的实现,并为HOST层的驱动提供了接口函数,在HOST层我们曾经调用的两个函数:
mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
mmc_add_host(mmc);
我们就从这两个函数入手,来分析CORE层与HOST层是如何交互的。
先看mmc_alloc_host函数:
    dev_set_name(&host->class_dev, "mmc%d", host->index);
         host->parent = dev;
         host->class_dev.parent = dev;
         host->class_dev.class = &mmc_host_class;
         device_initialize(&host->class_dev);
这几句是将导致在/SYS/CLASS/mmc_host下出现mmc0目录,添加类设备,在2.6.21后的版本中,类设备的class_device已近被device所取代,LDD3P387的内容有点OUT了
       INIT_DELAYED_WORK(&host->detect, mmc_rescan);
初始化了一个工作队列,延时函数为mmc_rescan,这个延时函数很重要,下午要详细分析
最后对host做一些默认配置,不过这些配置在probe函数的后面都被重置了。
分析mmc_add_host(mmc);
device_add(&host->class_dev);这里才真正的添加了类设备。
其中调用了mmc_start_host
void mmc_start_host(struct mmc_host *host)
{
       mmc_power_off(host);
       mmc_detect_change(host, 0);
}
mmc_power_off中对ios进行了设置,然后调用mmc_set_ios(host);
host->ios.power_mode = MMC_POWER_OFF;
       host->ios.bus_width = MMC_BUS_WIDTH_1;
       host->ios.timing = MMC_TIMING_LEGACY;
       mmc_set_ios(host);
mmc_set_ios(host)中的关键语句host->ops->set_ios(host, ios);这里的set_ios实际上就是我们前面所提到的.set_ios  = s3cmci_set_ios,
再看mmc_detect_change(host, 0);最后一句是
       mmc_schedule_delayed_work(&host->detect, delay);
实际上就是调用我们前面说的延时函数mmc_rescan
mmc_power_up(host);//这个函数实际上与前面的mmc_power_off类似,不过设置了启动时需要的ios
                   mmc_go_idle(host);
                   //CMD0 ,from inactive to idle
                   mmc_send_if_cond(host, host->ocr_avail);//发送SD_SEND_IF_COND,是使用SD2.0卡才需要设置的命令
/*suppot for 2.0 card*/
                    * ...then normal SD...
                    */
                   err = mmc_send_app_op_cond(host, 0, &ocr);
                   if (!err) {
                            if (mmc_attach_sd(host, ocr))
                                     mmc_power_off(host);
                            goto out;
                   }
蓝色部分是遵照SD卡协议的SD卡启动过程,包括了非激活模式、卡识别模式和数据传输模式三种模式共九种状态的转换,你需要参照相关规范来理解。可以先参考下面三章图对模式和状态,以及状态转换有个初步了解。
         
我们最初的SD卡的状态时inactive状态调用mmc_go_idle(host)后,发送命令CMD0是其处于IDLE状态。
我们详细分析一下mmc_go_idle
memset(&cmd, 0, sizeof(struct mmc_command));
         cmd.opcode = MMC_GO_IDLE_STATE; MMC_GO_IDLE_STATE就是命令CMD0
         cmd.arg = 0;此命令无参数
         cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
         err = mmc_wait_for_cmd(host, &cmd, 0);//见注1
         mmc_delay(1);
 

注1:mmc_wait_for_cmd(host, &cmd, 0)是用来发送命令的,我们揭开它的神秘面纱吧。
memset(&mrq, 0, sizeof(struct mmc_request));
         memset(cmd->resp, 0, sizeof(cmd->resp));
         cmd->retries = retries;
         mrq.cmd = cmd;将命令嵌入到一个mmc请求中
         cmd->data = NULL;mmc命令的data部分设置为NULL,这样表示我们要传输的是命令而不是数据
         mmc_wait_for_req(host, &mrq);//关键部分
在该函数中调用了mmc_start_request,而这个函数调用了host->ops->request(host, mrq),这个request函数就是我们在前面分析的s3cmci_request,这样MMC核心第二次核HOST层握手了
 
我们再看看:       err = mmc_send_app_op_cond(host, 0, &ocr);//注一
                   if (!err) {
                            if (mmc_attach_sd(host, ocr))//注二
                                     mmc_power_off(host);
                            goto out;
注一:实际上是要发送ACMD41命令,这条命令可以用来获取SDcard的允许电压范围值,由于这是一条应用命令,所有发送它之前需要发送CMD_55命令。执行完后card状态变为READY获取的电压范围保存在ocr中,再调用mmc_attach_sd(host, ocr)看这个电压范围是否满足主机的要求,不满足,则power_off主机。
注二:mmc_attach_sd完成匹配,和初始化卡的功能
host->ocr = mmc_select_voltage(host, ocr);看是否匹配,如果匹配则做下面初始化工作
mmc_sd_init_card(host, host->ocr, NULL);我们分析该函数
(1)mmc_all_send_cid()这个函数发生CMD2,获取卡的身份信息,进入到身份状态
(2)card = mmc_alloc_card(host, &sd_type);分配一张SD类型的card结构
(3)接着调用mmc_send_relative_add,获取卡的相对地址,注意一前卡和主机通信都采用默认地址,现在有了自己的地址了,进入到stand_by状态
(4)通过发送SEND_CSD (CMD9) 获取CSD 寄存器的信息,包括block长度,卡容量等信息
(5) mmc_select_card(card)发送CMD7,选中目前RADD地址上的卡,任何时候总线上只有一张卡被选中,进入了传输状态,
(6)调用mmc_app_send_scr发送命令ACMD51获取SRC寄存器的内容,进入到SENDING-DATA状态
在函数中还将获得的各个卡寄存器的内容解码,并保存到cmd结构的相应成员中。
(7)if (host->ops->get_ro(host) > 0)
                                     mmc_card_set_readonly(card);
通过调用get_ro(host)函数,实际上就是s3cmci_get_ro函数了。我们判断是否写保护,如果是的,将card状态设置为只读状态
最后再mmc_attach_sd里,我们将card结构添加进去
mmc_add_card(host->card); 
dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);这里我们以host名+rca地址来命名卡我们可以看到在/sys/devices/platform/s3c2440-sdi/mmc_host:mmc0/下出现mmc0:0002的目录,这个0002就是rca地址

到这里我们分析完了MMC的核心层。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zhenwenxian/archive/2009/11/13/4804870.aspx

阅读(1264) | 评论(0) | 转发(0) |
0

上一篇:MMC 卡驱动分析

下一篇:没有了

给主人留下些什么吧!~~