s5pv210-Linux驱动之SD卡插拨识别

来源:互联网 发布:win10软件安装不了 编辑:程序博客网 时间:2024/05/16 19:06

一、开发环境

硬件平台:我用的是TQ210核心板,板载S5PV210芯片
软件平台:开发板移植的是Linux3.10.46内核,UBOOT移植的是2014.12版本

二、资源简介

内核自带S5PV210芯片的SD卡驱动,drivers/mmc/host/sdhci-s3c.c

三、驱动分析

1、插入SD卡识别

在前一篇对主机控制器驱动分析中,sdhci-s3c.c的probe函数中调用sdhci_add_host函数,在此函数里会注册一个中断,如下:

int sdhci_add_host(struct sdhci_host *host){... ... ... ...    tasklet_init(&host->card_tasklet,          sdhci_tasklet_card, (unsigned long)host);      tasklet_init(&host->finish_tasklet,          sdhci_tasklet_finish, (unsigned long)host);  ... ... ...      ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,          mmc_hostname(mmc), host); ... ... ... ...}
中断服务程序定义如下:

static irqreturn_t sdhci_irq(int irq, void *dev_id)  {  ... ... ... ...      intmask = sdhci_readl(host, SDHCI_INT_STATUS);  ... ... ... ...          sdhci_mask_irqs(host, present ? SDHCI_INT_CARD_INSERT :                          SDHCI_INT_CARD_REMOVE);          sdhci_unmask_irqs(host, present ? SDHCI_INT_CARD_REMOVE :                            SDHCI_INT_CARD_INSERT);            sdhci_writel(host, intmask & (SDHCI_INT_CARD_INSERT |                   SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS);          intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE);          tasklet_schedule(&host->card_tasklet);      }  ... ... ... ...  }
程序首先读取寄存器NORINTSTSn的值,该寄存器中有两个bit分别来表示卡的插入与拔出过程(注意,必须是动态变化过程,才会让相应的两个bit置1),那么接下来的if语句就是从该寄存器的那两个bit来判断是否有卡的插入或拔出,并同时清除这两个bit,准备下一次的检测,紧接着就调用中断的下半部分函数 sdhci_tasklet_card,函数定义如下:

static void sdhci_tasklet_card(unsigned long param){struct sdhci_host *host = (struct sdhci_host*)param;sdhci_card_event(host->mmc);mmc_detect_change(host->mmc, msecs_to_jiffies(200));}
这个函数判断,如果此时有卡的话就通过mmc_detect_chang函数调用mmc_schedule_delayed_work(&host->detect, delay),最终调用mmc_rescan函数。定义如下:

static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };void mmc_rescan(struct work_struct *work){... ... ...for (i = 0; i < ARRAY_SIZE(freqs); i++) {if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))break;if (freqs[i] <= host->f_min)break;}... ... ...}
这个函数我们重点关注上述两个地方,其实真正的扫描动作发生在函数mmc_rescan_try_freq函数里面,该函数的第二个参数表示以什么样的频率去进行扫描,那么可选的频率值在那个数组freqs里面,一般当用某个频率值扫描成功后,就直接退出了,否则,会以下一个更低的频率值来扫描。mmc_rescan_try_freq定义如下:

static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq){... ... ...if (!mmc_attach_sdio(host))return 0;if (!mmc_attach_sd(host))return 0;if (!mmc_attach_mmc(host))return 0;... ... ...}
可以看到,其识别的顺序为SDIO  SD MMC

看看SDIO的识别过程,它对应的函数就是mmc_attach_sdio(host),如下:

这个函数大概来说做了如下的工作

               1、向卡发送CMD5命令,该命令有两个作用:第一,通过判断卡是否有反馈信息来判断是否为SDIO设备(只有SDIO设备才对CMD5命令有反馈,其他卡是没有回馈的);第二,如果是SDIO设备,就会给host反馈电压信息,就是说告诉host,本卡所能支持的电压是多少多少。

               2、host根据SDIO卡反馈回来的电压要求,给其提供合适的电压。

               3、初始化该SDIO卡

               4、注册SDIO的各个功能模块

               5、注册SDIO卡


err = mmc_send_io_op_cond(host, 0, &ocr);
向卡发送CMD5命令,通过判断卡是否有反馈信息来判断是否为SDIO设备,只有SDIO设备才对CMD5命令有反馈,其他卡是没有回馈的 ,最后一个参数ocr,存储反馈命令。


if (ocr & 0x7F) {pr_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);
ocr就是我们上面讲的反馈命令R4(截短之后的32位),那么ocr & 0x7f的意义是什么呢,从R4的格式就可以看出来,其低24位就代表了所能支持的电压范围。


err = mmc_sdio_init_card(host, host->ocr, NULL, 0);
初始化SDIO卡


funcs = (ocr & 0x70000000) >> 28;card->sdio_funcs = 0;
变量funcs存储该SDIO卡所包含的IO功能块的个数


err = mmc_add_card(host->card);

注册card (这个card是在初始化SDIO卡里面分配和定义的)


for (i = 0;i < funcs;i++) {err = sdio_add_func(host->card->sdio_func[i]);if (err)goto remove_added;}
将功能模块逐个的注册进设备模型

其中,mmc_sdio_init_card非常重要,如下:

card = mmc_alloc_card(host, NULL);
分配一个mmc_card的变量card,设置总线类型为mmc_bus_type


if ((ocr & R4_MEMORY_PRESENT) &&    mmc_sd_get_cid(host, host->ocr & ocr, card->raw_cid, NULL) == 0) {... ... ...}
通过读取R4命令中的bit27(也就是Memory Present)来判断此卡是纯IO卡,还是同时包含存储功能


if (!powered_resume && !mmc_host_is_spi(host)) {err = mmc_send_relative_addr(host, &card->rca);
通过发送CMD3命令获取设备的从地址(relative addr),并且存放在变量card->rca中


if (!powered_resume && !mmc_host_is_spi(host)) {err = mmc_select_card(card);
通过发送CMD7,选中相应从地址的卡

mmc_add_card函数定义如下:

dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);
给card命名,格式为host名字:从地址


if (mmc_host_is_spi(card->host)) {pr_info("%s: new %s%s%s card on SPI\n",
插入SD卡时的打印信息


ret = device_add(&card->dev);

调用 mmc_add_card 把 mmc_card 挂载到 mmc_bus_type 总线去,此时就会调用mmc_bus_match函数,此函数始终返回1。所以继续调用mmc_bus_probe函数,如下:

static int mmc_bus_probe(struct device *dev){struct mmc_driver *drv = to_mmc_driver(dev->driver);struct mmc_card *card = mmc_dev_to_card(dev);return drv->probe(card);}

mmc_driver *drv是在 mmc_blk_init函数中 res = mmc_register_driver(&mmc_driver);注册的

注册结果就是可以在/sys/bus/mmc/devices目录下见到card 的名字,如 mmc2:0001


======================================================


下面再看看普通SD卡的识别过程,函数mmc_attach_sd,如下:


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

发送命令SD_APP_OP_COND,只有SD2.0的协议支持,也就是说,只有普通SD卡支持,所以也只有普通SD卡能够成功上电


mmc_sd_attach_bus_ops(host);
注册SD总线上的操作函数


err = mmc_sd_init_card(host, host->ocr, NULL);
对mmc卡进行初始化,主要是读取mmc卡里的一些寄存器信息,且对这些寄存器的值进行设置,定义如下:

err = mmc_sd_get_cid(host, ocr, cid, &rocr);
发送 CMD2,获取卡的身份信息,进入到身份状态


card = mmc_alloc_card(host, &sd_type);
分配一个mmc_card的变量card


card->type = MMC_TYPE_SD;
设置卡类型

if (!mmc_host_is_spi(host)) {err = mmc_send_relative_addr(host, &card->rca);
通过发送CMD3命令获取设备的从地址(relative addr),并且存放在变量card->rca中,注意一前卡和主机通信都采用默认地址,现在有了自己的地址了,进入到 stand_by 状态


err = mmc_sd_get_csd(host, card);
CMD9,获取 CSD 寄存器的信息,包括 block 长度,卡容量等信息


if (!mmc_host_is_spi(host)) {err = mmc_select_card(card);
选中目前 RADD 地址上的卡,任何时候总线上只有一张卡被选中,进入了传输状态

mmc_set_clock(host, mmc_sd_get_max_clock(card));

设置卡工作的时钟频率


2、拨出SD卡识别

拨出SD卡也会触发中断,最终调用mmc_rescan函数,如下:

void mmc_rescan(struct work_struct *work){... ... ...if (host->bus_ops && host->bus_ops->detect && !host->bus_dead    && !(host->caps & MMC_CAP_NONREMOVABLE))host->bus_ops->detect(host);... ... ...}

存在热插拔卡,不包括emmc,调用探测函数host->bus_ops->detect(host),此操作函数在匹配函数中设置,如下:

int mmc_attach_sdio(struct mmc_host *host){... ... ...mmc_attach_bus(host, &mmc_sdio_ops);... ... ...}int mmc_attach_sd(struct mmc_host *host){... ... ...mmc_sd_attach_bus_ops(host);... ... ...}
mmc_rescan()扫描SD总线,如果发现host->ops上赋了值,即之前已有SD卡注册过,就执行bus_ops->detect()操作去探测SD总线上是否还存在SD卡,如果不存在了,就执行bus_ops->remove()拔出SD卡,detect函数定义如下:

static void mmc_sd_detect(struct mmc_host *host){int err;BUG_ON(!host);BUG_ON(!host->card);mmc_claim_host(host);// set_current_state(TASK_RUNNING);将当前进程设置为正在运行进程/* * Just check if our card has been removed. */err = _mmc_detect_card_removed(host);// 发送得到SD卡状态的请求,如果未能得到状态数据mmc_release_host(host);if (err) {mmc_sd_remove(host);mmc_claim_host(host);mmc_detach_bus(host);mmc_power_off(host);mmc_release_host(host);}}
这里的mmc_claim_host(host)通过set_current_state(TASK_RUNNING);将当前进程设置为正在运行进程。
mmc_send_status()发送得到SD卡状态的请求,如果未能得到状态数据,则执行mmc_sd_remove(host)拔出SD卡

mmc_sd_remove函数其实就是注册SD卡驱动的反操作,实质就是执行device_del()和device_put()


四、小结

插入SD卡,主控制器产生中断,进入中断处理函数sdhci_irq,调用中断下半部函数sdhci_tasklet_card,调用函数mmc_detect_change,它将最终调用queue_delayed_work执行工作队列里的mmc_rescan函数,来扫描插入的SD卡。