s5pv210-Linux驱动之SD卡主机控制器

来源:互联网 发布:贫贱夫妻百事哀 知乎 编辑:程序博客网 时间:2024/06/01 08:46

一、开发环境

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

二、资源简介

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

三、驱动分析

1、在mach-s5pv210.c中添加HSMMC主机控制器设备

static struct platform_device *smdkv210_devices[] __initdata = {&s3c_device_adc,&s3c_device_cfcon,&s3c_device_fb,&s3c_device_hsmmc0,&s3c_device_hsmmc1,&s3c_device_hsmmc2,&s3c_device_hsmmc3,


然后,由此函数添加到platform_device

platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));

s3c_device_hsmmc0在文件arch/arm/plat-samsung.c中定义

#ifdef CONFIG_S3C_DEV_HSMMCstatic struct resource s3c_hsmmc_resource[] = {[0] = DEFINE_RES_MEM(S3C_PA_HSMMC0, SZ_4K),[1] = DEFINE_RES_IRQ(IRQ_HSMMC0),};struct s3c_sdhci_platdata s3c_hsmmc0_def_platdata = {.max_width= 4,.host_caps= (MMC_CAP_4_BIT_DATA |   MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED),};struct platform_device s3c_device_hsmmc0 = {.name= "s3c-sdhci",.id= 0,.num_resources= ARRAY_SIZE(s3c_hsmmc_resource),.resource= s3c_hsmmc_resource,.dev= {.dma_mask= &samsung_device_dma_mask,.coherent_dma_mask= DMA_BIT_MASK(32),.platform_data= &s3c_hsmmc0_def_platdata,},};void s3c_sdhci0_set_platdata(struct s3c_sdhci_platdata *pd){s3c_sdhci_set_platdata(pd, &s3c_hsmmc0_def_platdata);}#endif /* CONFIG_S3C_DEV_HSMMC */
当platform_driver的.name = "s3c-sdhci"时,就会和此平台设备匹配

2、注册s5pv210芯片SD卡的平台driver,driver/mmc/host/sdhci-s3c.c

static struct platform_driver sdhci_s3c_driver = {.probe= sdhci_s3c_probe,.remove= sdhci_s3c_remove,.id_table= sdhci_s3c_driver_ids,.driver= {.owner= THIS_MODULE,.name= "s3c-sdhci",.of_match_table = of_match_ptr(sdhci_s3c_dt_match),.pm= SDHCI_S3C_PMOPS,},};module_platform_driver(sdhci_s3c_driver);
在对sdhci_s3c_driver进行注册的过程中,系统会在platform_bus 总线上寻找同名字的platform_dvice(这个过程称之为“探测”),如果探测成功一次,就会调用sdhci_s3c_driver.probe函数---sdhci_s3c_probe一次。

3、下面来分析一下sdhci_s3c_probe函数

在分析代码之前,先简要的概括一下这个函数的功能:

1)、既然是讲host的注册,那么首先必须构造出一个host,这个host就是通过sdhci_alloc_host函数来构造出来的,它是一个struct sdhci_host类型的结构体。同时,也通过mmc_alloc_host函数构造了一个struct mmc_host的结构体变量mmc。

2)、初始化host的时钟,设置host的gpio等等其他一些“乱七八糟”的参数初始化(需要的时候再详细分析)。

3)、通过sdhci_add_host函数来注册host。


irq = platform_get_irq(pdev, 0);if (irq < 0) {dev_err(dev, "no irq specified\n");return irq;}
获取中断资源,即在devs.c中定义的平台资源,如下

static struct resource s3c_hsmmc_resource[] = {[0] = DEFINE_RES_MEM(S3C_PA_HSMMC0, SZ_4K),[1] = DEFINE_RES_IRQ(IRQ_HSMMC0),};



host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c));if (IS_ERR(host)) {dev_err(dev, "sdhci_alloc_host() failed\n");return PTR_ERR(host);}
分配一个sdhci_host的控制器,sdhci_alloc_host定义如下:

struct sdhci_host *sdhci_alloc_host(struct device *dev,size_t priv_size){struct mmc_host *mmc;struct sdhci_host *host;WARN_ON(dev == NULL);mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);if (!mmc)return ERR_PTR(-ENOMEM);host = mmc_priv(mmc);host->mmc = mmc;return host;}
分配了一个mmc_host结构体,同时struct sdhci_s3c 结构作为一个私有数据类型添加到structmmc_host的private域


sc = sdhci_priv(host);
得到sdhci_s3c结构体指针


sc->host = host;sc->pdev = pdev;sc->pdata = pdata;
这个私有结构的配置,对于core或block层见到的只有struct sdhci_host


res = platform_get_resource(pdev, IORESOURCE_MEM, 0);host->ioaddr = devm_ioremap_resource(&pdev->dev, res);
获取IO资源 


host->hw_name = "samsung-hsmmc";host->ops = &sdhci_s3c_ops;host->quirks = 0;host->irq = irq;
分配操作函数集等信息


ret = sdhci_add_host(host);

最后通过sdhci_add_host(host),添加到MMC系统中,此时dev与host已经完成了挂接的操作

sdhci_add_host函数中,会注册为断程序,用来处理SD卡插拨事件,如下:

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);
中断注册函_irq的第一个参数中断号就取自于s3c_device_hsmmc3.resource里面的irq参数,sdhci_irq就是中断服务程序,该中断函数一般在插卡、拔卡或者从设备反馈给host信息时会被调用数request,中断服务程序定义如下:

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,其实这个函数也没做什么事情,就是判读如果此时有卡的话就通过mmc_detect_chang函数调用mmc_rescan函数。


然后在此函数sdhci_add_host构造了一个struct mmc_host的结构体变量mmc,并添加到系统中:mmc_add_host(mmc),定义如下:

int mmc_add_host(struct mmc_host *host){int err;WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&!host->ops->enable_sdio_irq);err = device_add(&host->class_dev);if (err)return err;led_trigger_register_simple(dev_name(&host->class_dev), &host->led);#ifdef CONFIG_DEBUG_FSmmc_add_host_debugfs(host);#endifmmc_host_clk_sysfs_init(host);mmc_start_host(host);register_pm_notifier(&host->pm_notify);return 0;}
通过device_add函数将设备注册进linux设备模型,最终的结果就是在sys/bus/platform/devices目录下能见到s3c-sdhci.1,s3c-sdhci.2,s3c-sdhci.3设备节点。mmc_start_host定义如下:

void mmc_start_host(struct mmc_host *host){host->f_init = max(freqs[0], host->f_min);host->rescan_disable = 0;if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)mmc_power_off(host);elsemmc_power_up(host);mmc_detect_change(host, 0);}
mmc_power_off函数里,关心最多的就是host->ios当中的内容,前段的赋值真正作用在硬件上是调用host层向上提供的struct mmc_host_ops接口。后面最重要的函数mmc_detect_change(host, 0),定义如下:

void mmc_detect_change(struct mmc_host *host, unsigned long delay){#ifdef CONFIG_MMC_DEBUGunsigned long flags;spin_lock_irqsave(&host->lock, flags);WARN_ON(host->removed);spin_unlock_irqrestore(&host->lock, flags);#endifhost->detect_change = 1;mmc_schedule_delayed_work(&host->detect, delay);}
只关心最后一句,这就要用到内核的延时工作队列,在mmc_alloc_host函数中调用了这一句

INIT_DELAYED_WORK(&host->detect, mmc_rescan);
所以,延时delay 时间后就会去调用mmc_rescan了。前面我们传递的delay=0,那么这里就没有延时了直接mmc_rescan它的功能就是扫描所插入的卡。

四、小结

sdhci-s3c.c作为S5PV210芯片的SD卡主机控制器的platform_driver驱动,在其probe函数中分配一个sdhci_host的控制器,并通过sdhci_add_host函数注册,并且在此函数中分配了一个struct mmc_host结构体,注册了中断服务程序,用来发送命令、数据和检测SD卡插拨等操作。