转自:http : //blog.csdn.net/liuhaoyutz
内核版本:3.10.1
一,s3cmci_ops分析
在上一篇文章中我们分析了Mini2440 MMC / SD驱动的探针函数s3cmci_probe。在该函数中初始化了结构mmc_host指针变量mmc,其中,设置mmc-> ops为s3cmci_ops,s3cmci_ops定义在drivers / mmc / host / s3cmci.c文件中:
struct mmc_host是mmc core层与主机层的接口,mmc_host.ops是控制主机完成用户请求的接口函数集,其类型是struct mmc_host_ops,该结构体定义在include / linux / mmc / host.h文件中:
请求函数用于处理用户的请求。
set_ios函数用于设置SDI的控制参数,如时钟,总线宽度等等。
get_ro函数用于探测SD卡是否有写保护。
get_cd函数用于探测卡是否已插入插槽。
enable_sdio_irq函数用于启动或禁用SDI中断。
需要注意的是,为什么没有对MMC / SD进行读写的读取和写入函数呢?这是因为Linux的块设备的读写操作是通过请求函数完成的。
那么对于mini2440的,它的s3cmci_ops中的成员函数在什么时候会被调用呢举例如下?
在驱动器/ MMC /核心/ core.c文件中:
可以看到255行,调用了宿主 - > ops->请求函数,即s3cmci_request函数。
再比如,在驱动器/ MMC /核心/ core.c文件中:
可以看到,970行,调用了宿主 - > ops-> set_ios函数,即s3cmci_set_ios函数。
下面我们就来看一下s3cmci_ops的各个成员函数的实现。
s3cmci_get_ro函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
1374行,由mmc_host取得s3cmci_host。
1375行,取得s3c24xx_mci_pdata,其它保存着SDI的平台数据。
1378行,如果s3c24xx_mci_pdata.no_wprotect为1,表明没有写保护开关,直接退出。例如MMC卡就没有写保护开关,只有SD卡才有写保护开关。
1381行,读取gpio_wprotect引脚电平,对于MINI2440,即GPH8引脚。
1382行,与pdata-> wprotect_invert执行异或操作,即反转上步得到GPH8引脚电平值。
s3cmci_card_present函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
1256行,由mmc_host取得s3cmci_host。
1257行,取得s3c24xx_mci_pdata,其它保存着SDI的平台数据。
1260行,如果s3c24xx_mci_pdata.no_detect为1,表明没有卡探测引脚,直接退出。
1263行,读取gpio_detect引脚电平值,对于MINI2440,即GPG8引脚。
1264行,与pdata-> detect_invert进行异或操作,即反转上步得到的GPG8引脚电平值。
s3cmci_enable_sdio_irq函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
1389行,由mmc_host取得s3cmci_host。
1395行,读取SDICON即SDI控制寄存器的内容,保存在CON中。
1396行,我觉得这一行不应该存在,因为这一行将参数启用的值赋值给宿主> sdio_irqen,但是1398行又接着判断启用与主机 - > sdio_irqen是否相等,如果相等就退出了。
1401年至1408年行,启用为1,使能SDIO中断。
1402行,S3C2410_SDICON_SDIOIRQ定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
57#define S3C2410_SDICON_SDIOIRQ(1 << 3)
对照S3C2440数据手册,可知这个宏用来设置SDICON寄存器的第3位,该位决定是否接收SDIO中断。
1403行,S3C2410_SDIIMSK_SDIOIRQ定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
105#define S3C2410_SDIIMSK_SDIOIRQ(1 << 12)
对照S3C2440数据手册,可知这个宏用来设置SDIIntMsk寄存器的第13位,该位决定当读等待请求发生时,SDI是否产生一个中断。
enable_imask函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
该函数用来设置SDIIntMsk寄存器。
1409至1416年行,使为0,禁用SDIO中断。
1419行,用新的骗子设置SDICON即SDI控制寄存器。
s3cmci_set_ios函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
1306行,参数ios是structmmc_ios类型指针.struct mmc_ios定义在include / linux / mmc / host.h文件中:
1308行,由mmc_host取得s3cmci_host。
1313行,读取SDICON即SDI控制寄存器的值,保存在mci_con中。
1316至1328年行,如果ios-> power_mode为MMC_POWER_ON或MMC_POWER_UP,则执行这个分支。
S3C_GPIO_SFN宏定义在拱/臂/高原三星/包含/高原/ GPIO-cfg.h文件中:
S3C_GPIO_PULL_NONE宏定义在拱/臂/高原三星/包含/高原/ GPIO-cfg.h文件中:
s3c_gpio_cfgall_range函数定义在驱动器/ GPIO / GPIO-samsung.c文件中:
可以看到,s3c_gpio_cfgall_range函数设置从GPE5开始的6个GPIO,即GPE5,GPE6,GPE7,GPE8,GPE9,GPE10。使用参数CFG设置GPECON寄存器,使用参数拉设置GPEUP寄存器。
GPECON寄存器对应的位置被设置为01,即使能相关SDI功能。
一三三○年至1341年行,如果ios-> power_mode为MMC_POWER_OFF或者默认情况下,则执行这个分支。
1332行,调用gpio_direction_output(S3C2410_GPE(5),0)关闭SDI时钟。
1335行,mci_con | = S3C2440_SDICON_SDRESET,根据S3C2440数据手册,这句用于重置整个sd / mmc模块。
1343行,调用s3cmci_set_clk(主机,IOS)设置时钟,s3cmci_set_clk定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
1346至1349年行,如果ios->时钟不为0时,使能时钟,否则禁用时钟。
1351行,将mci_con写回SDICON寄存器。
1361行,用ios-> bus_width设置数据总线宽度宿主 - > bus_width。
s3cmci_request函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
struct mmc_request代表一个请求,该结构体定义在include / linux / mmc / core.h文件中:
1271行,将宿主>状态设置为 “mmcrequest”,主机 - >状态主要用于记录请求处理所处的阶段及状态,方便调试使用。
1272行,设置宿主> cmd_is_stop为0,从字面上理解,我认为宿主> cmd_is_stop代表命令是否是停止命令(即有一个命令是停止),0表示不是停止命令。
1273行,将mmc_requestmrp保存在主机 - > MRQ中,方便以后使用。
1275年至1280年行,如果卡不存在,则调用mmc_request_done(MMC,MRQ)结束这次请求处理,否则,调用s3cmci_send_request(MMC)。
s3cmci_send_request函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
1204行,由structmmc_host得到结构s3cmci_host。
1205行,从宿主> MRQ取出mmc_request以便使用。
1206行,因为宿主> cmd_is_stop被设置为0,所以CMD被设置为mrq-> CMD。
1214-1216行,清空SDICmdSta寄存器,SDIDatSta寄存器和SDIFSTA寄存器。
1216至1245年行,如果CMD->数据不为0,即当前命令带有要处理的数据,则执行这个如果语句块,进行数据处理的准备工作。
1219行,调用s3cmci_setup_data(主机,CMD->数据),该函数定义在驱动/ MMC /主机/ s3cmci.c文件中:
1054-1057行,如果命令数据为空,则清零SDIDatCon寄存器。
1059至1067年行,根据数据表的描述,如果在多模块下必须分配字大小,即BLKSIZE [1:0] = 00,所以这里“与” 3来判断是不是单模块如果在单模块处理的情况下,模块数大于1,则出错退出。
1069至1082年行,循环判断是否有数据正在发送或接收,如果有,则停止传输,并复位时钟。最多循环3次。
1086年至1087年行,如果使用DMA传输,则使能SDIDatCon寄存器的第15位的DMA功能。
1089至1090年行,如果数据总线宽度为4线,则使能SDIDatCon寄存器的第16位宽总线WideBus功能。
一〇九二年至1093年行,配置SDIDatCon寄存器的第17位,数据传输模式为块传输模式。
1095至1098年行,如果是写数据,配置SDIDatCon寄存器的第20位,收到回应后开始写数据。然后配置SDIDatCon寄存器的第12,13位,设置为写模式。
一一〇〇年至1103年行,如果是读数据,配置SDIDatCon寄存器的第19位,命令发送后开始读数据。
然后配置SDIDatCon寄存器的第12,13位,设置为读模式。
1105至1108年行,如果是S3C2440,配置SDIDatCon寄存器的第22,23位为10,即传输单位为字。然后配置SDIDatCon寄存器的第14位,开始数据传输。
1110行,将DCON写入SDIDatCon寄存器。
1114行,将数据 - > blksz写入SDIBSize寄存器。
1117至1118年行,设置出现FIFO失败SDI中断使能;数据接收CRC错误SDI中断使能;数据接收超时SDI中断使能;数据计时为0SDI中断使能。
1120行,调用enable_imask使能设置的中断。
1124年至1132年行,设置SDIDTimer寄存器。
回到s3cmci_send_request函数:
1223-1230行,如果s3cmci_setup_data出错,则打印信息并退出。
1232年至1233年行,如果使用DMA,则调用s3cmci_prepare_dma函数,该函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中,关于DMA相关的函数,我们不再详细跟踪了。
1235行,如果没有使用DMA数据传输方式,则调用s3cmci_prepare_pio函数,即使用FIFO数据传输方式,具体来说,就是调用do_pio_write向FIFO中填充数据,当64字节的FIFO少于33字节时就会产生中断;或者从SD读数据,则先使能中断,当FIFO多于31字节时,则会调用中断服务程序,中断服务程序会调用do_pio_read读出FIFO的数据。
s3cmci_prepare_pio函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
1141行,根据数据 - >标志确定是读还是写。
1151行,如果是写,则调用do_pio_write函数.do_pio_write函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
527行,取得SDIDAT寄存器的物理地址保存在to_ptr变量中。
529行,调用fifo_free(主机)函数取得FIFO的剩余可用空间的字节数保存在FIFO变量中,这里,FIFO变量代表这次而循环最多可写的字节个数。如果剩余空间大于3个字节,即最小写一个字,则循环条件成立。
530-532行,如果宿主> pio_bytes为0,则调用get_data_buffer从分散聚集列表中取得保存要写的数据的缓冲区,缓冲区的长度和起始地址分别保存在get_data_buffer的2个参数宿主> pio_bytes和第3个参数宿主> pio_ptr中。
533-539行,如果get_data_buffer出错,打印信息退出。
551-552行,如果FIFO大于等于宿主> pio_bytes,即FIFO的可用空间大于等于保存要写数据的缓冲区长度,则将FIFO设置为主机 - > pio_bytes。
554行,如果fifo小于主机 - > pio_bytes,即FIFO的可用空间小于要写数据的缓冲区长度,则将fifo设置为fifo - (fifo&3)。从注释可以看到,这是为了保证以字为单位进行写操作。
556行,主机 - > pio_bytes- = fifo,保存这次写操作后,剩余的要写的字节数。
557行,主机 - > pio_count + = fifo,保存已经写了多个个字节。
559行,将字节数转化为字数。
561-562行,写数据到SDIDAT寄存器。
563行,主机 - > pio_ptr = ptr,保存当前还剩余要写数据的位置。
564行,结束这次虽然循环,回到529行重新执行上述过程。
566行,使能SDIIntMsk第4位,如果Tx FIFO填充满半,就产生中断。
回到s3cmci_prepare_pio函数中:
1152行,使能SDIIntMsk第4位,如果Tx FIFO填充满半,就产生中断。
1154行,使能SDIIntMsk第0位,如果Rx FIFO填充满半,就生产中断。
1155行,使能SDIIntMsk第2位,如果Rx FIFO读取了最后的数据,就产生中断。
回到s3cmci_send_request函数:
1248行,调用s3cmci_send_command(主机,CMD)发送命令。
该函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
一○二一年至1025年行,出现CRC状态错误,命令响应超时,接收命令响应,命令发送,响应CRC校验失败时,将产生SDI中断。
一零二七年至1032年行,设置宿主> complete_what。
在驱动器/ MMC /主机/ s3cmci.h文件中,有如下定义:
另外,在驱动器/ MMC /主机/ s3cmci.c文件的s3cmci_irq函数的注释中,有如下内容:
1034行,用CMD-> ARG设置设置SDICmdArg寄存器。
1036年至1045年行,配置SDICmdCon寄存器。
1036行,取得命令索引。
1037行,命令启动。
1039年至1040年行,配置主机等待响应。
1042至1043年行,配置主机接收136位长响应。
回到s3cmci_send_request函数:
1251行,使能中断。
至此,s3cmci_send_request函数我们就分析完了。
s3cmci_request函数我们也就分析完了。
s3cmci_ops结构体我们也就分析完了。
二,中断处理函数s3cmci_irq分析
SDI中断处理函数s3cmci_irq定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
在分析这个函数之前,请先看一下599-624行的注释。
628行,的dev_id是中断处理函数传递过来的structs3cmci_host指针。
634行,读取SDIDatSta寄存器,保存在mci_dsta变量中。
635行,读取SDIIntMsk寄存器,保存在mci_imsk变量中。
637行,S3C2410_SDIDSTA_SDIOIRQDETECT宏标志着SDIDatSta寄存器的第9位被置位,说明有SDIO中断被检测到。
638行,S3C2410_SDIIMSK_SDIOIRQ宏标志着SDIIntMsk寄存器的第12位被置位,表示使能SDI产生SDIO中断。
行639-640,根据数据表,这两句的作用是清零SDIDatSta寄存器的第9位。
642行,调用mmc_signal_sdio_irq函数处理SDIO中断,该函数定义在包括/ LINUX / MMC / host.h文件中:
649行,读取SDICmdSta寄存器,保存在mci_csta变量中。
650行,读取SDIDatCnt寄存器,保存在mci_dcnt变量中。
651行,读取SDIFSTA寄存器,保存在mci_fsta变量中。
654-665行,做一些检查工作。
667行,设置CMD。
675-694行,如果没有使用DMA,则执行这个如果分支。
676-677行,如果宿主> pio_active为XFER_WRITE,并且SDIFSTA寄存器的第13位被置位,表明FIFO可以用于写操作。
679行,禁用TFHalf中断。
680行,调用主机 - > pio_tasklet。
681行,设置主机 - >状态为 “piotx”。
684-685行,如果宿主> pio_active为XFER_READ,并且SDIFSTA寄存器的第12位被置位,表明FIFO可以用于读操作。
687-689行,禁用Rx FIFO相关中断。
691行,调用主机 - > pio_tasklet。
692行,设置主机 - >状态为 “piorx”。
696-701行,处理命令超时。
703-710行,命令发送完成(不论是否得到应答)。
712-730行,处理CRC校验错误。
732-742行,处理收到命令应答。
750-798行,处理数据传输相关错误。
751-765行,处理FIFO相关错误。
767-772行,处理读数据CRC校验错误。
774-779行,处理发送数据时CRC校验错误。
781-786行,处理数据超时。
788-798行,处理数据传输结束。
下面我们来看宿主> pio_tasklet,在s3cmci_probe函数中,有如下语句:
1662 tasklet_init(&host-> pio_tasklet,pio_tasklet,(unsigned long)host);
可以看到,宿主> pio_tasklet对应的微进程函数为pio_tasklet,并将主机做为参数传递给该函数.pio_tasklet函数定义在驱动/ MMC /主机/ s3cmci.c文件中:
575-576行,如果宿主> pio_active为XFER_WRITE,即写数据,则调用do_pio_write(主机)函数,该函数我们前面已经分析过了。
578-579行,如果宿主> pio_active为XFER_READ,即读数据,则调用do_pio_read(主机)函数,该函数定义在驱动/ MMC /主机/ s3cmci.c文件中:
446行,设置波特率预分频器寄存器SDIPRE。
448行,将SDI数据寄存器SDIDAT的虚拟地址保存在from_ptr变量中。
450行,调用fifo_count得到FIFO中可读取数据的字节数,保存在FIFO变量中。
451-466行,调用get_data_buffer函数,从分散聚集列表中获取用于存放被读取数据的缓冲区的相关信息,缓冲区的长度保存在主机 - > pio_bytes中,缓冲区的起始地址保存在主机 - > pio_ptr中如果get_data_buffer函数返回非0值,表示读操作完成。
478-481行,如果FIFO中可读取数据的字节数大于host-> pio_bytes(即缓冲区的大小),则将fifo设置为host-> pio_bytes,否则fifo - = fifo&3.从473- 477行的注释可以看出,这样做是为了按字来读取数据。
483行,修改主机 - > pio_bytes的值,缓冲区还有多少字节的空间。
484行,已经读取的数据的字节数保存在主机 - > pio_count变量中。
486行,以字为单位,要读取的数据个数保存在fifo_words变量中。
488-489行,循环读取数据。
490行,保存下次要读取的数据的起始位置到宿主> pio_ptr中。
492-501行,读取剩余的非字节对齐部分。
502行,结束这次而循环,回到450行,判断FIFO中是否还有可读的数据,如果有的话,继续进行读取操作。
504-514行,如果宿主> pio_bytes为0,并且get_data_buffer函数返回非0值,表示没有可用的缓冲区空间,读结束。
516-517行,便能读取中断。
至此,do_pio_read函数我们就分析完了。
回到pio_tasklet函数:
581-596行,如果命令处理结束,则调用finalize_request进行最后的处理否则,打开中断,继续监听中断.finalize_request函数定义在驱动程序/ MMC /主机/ s3cmci.c文件中:
920-924行,读取SDIRSP0 -SDIRSP3寄存器,保存在cmd-> resp中。
926行,将宿主>预分频器写入SDIPRE寄存器。
937行,清零SDICmdArg寄存器。
938行,清零SDIDatCon寄存器,除了第14位设置为1,表示启动数据传输。
939行,清零SDICmdCon寄存器。
940行,清零SDIIntMsk寄存器,只允许SDIO中断。
945-949行,发送停止命令。
955-961行,计算传输的字节总数。
965-983行,如果数据传输过程出错,刷新DMA通道和FIFO,清除垃圾数据。
至此,finalize_request函数我们就分析完了,pio_tasklet函数我们也就分析完了,同时中断处理函数s3cmci_irq函数我们也就分析完了。