您的位置:首页 > 运维架构 > Linux

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

2017-11-29 09:56 686 查看

一、开发环境

硬件平台:我用的是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_HSMMC
static 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_FS
mmc_add_host_debugfs(host);
#endif
mmc_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);
else
mmc_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_DEBUG
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
WARN_ON(host->removed);
spin_unlock_irqrestore(&host->lock, flags);
#endif
host->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卡插拨等操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  S5PV210 linux SD卡