Chinaunix首页 | 论坛 | 博客
  • 博客访问: 130551
  • 博文数量: 22
  • 博客积分: 949
  • 博客等级: 准尉
  • 技术积分: 266
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-10 22:31
文章分类

全部博文(22)

文章存档

2012年(1)

2011年(2)

2010年(19)

分类: 嵌入式

2010-11-16 19:49:28

上次说到,如果注册成功的话,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
    }              
}
 
阅读(2833) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~