您的位置:首页 > 其它

三星平台SD/MMC驱动分析

2013-11-06 13:02 260 查看
三星平台SD/MMC驱动主要有两个文件sdhci.c和sdhci-s3c.c,核心驱动在后者里面。

我们一步一步分析,先从平台驱动注册看起

sdhci-s3c.c:

static int __init sdhci_s3c_init(void)
{
return platform_driver_register(&sdhci_s3c_driver);
}


注册了sdhci-s3c-driver,看下该结构体定义:

static struct platform_driver sdhci_s3c_driver = {
.probe		= sdhci_s3c_probe,
.remove		= __devexit_p(sdhci_s3c_remove),
.suspend	= sdhci_s3c_suspend,
.resume	        = sdhci_s3c_resume,
.driver		= {
.owner	= THIS_MODULE,
.name	= "s3c-sdhci",
},
};


看重点——probe函数:

static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
{
struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
struct sdhci_host *host;
struct sdhci_s3c *sc;
struct resource *res;
int ret, irq, ptr, clks;

if (!pdata) {
dev_err(dev, "no device data specified\n");
return -ENOENT;
}

irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(dev, "no irq specified\n");
return irq;
}

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "no memory specified\n");
return -ENOENT;
}

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);
}

sc = sdhci_priv(host);

sc->host = host;
sc->pdev = pdev;
sc->pdata = pdata;
sc->ext_cd_gpio = -1; /* invalid gpio number */

platform_set_drvdata(pdev, host);

sc->clk_io = clk_get(dev, "hsmmc");
if (IS_ERR(sc->clk_io)) {
dev_err(dev, "failed to get io clock\n");
ret = PTR_ERR(sc->clk_io);
goto err_io_clk;
}

/* enable the local io clock and keep it running for the moment. */
clk_enable(sc->clk_io);

for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
struct clk *clk;
char *name = pdata->clocks[ptr];

if (name == NULL)
continue;

clk = clk_get(dev, name);
if (IS_ERR(clk)) {
dev_err(dev, "failed to get clock %s\n", name);
continue;
}

clks++;
sc->clk_bus[ptr] = clk;

/*
* save current clock index to know which clock bus
* is used later in overriding functions.
*/
sc->cur_clk = ptr;

clk_enable(clk);

dev_info(dev, "clock source %d: %s (%ld Hz)\n",
ptr, name, clk_get_rate(clk));
}

if (clks == 0) {
dev_err(dev, "failed to find any bus clocks\n");
ret = -ENOENT;
goto err_no_busclks;
}

sc->ioarea = request_mem_region(res->start, resource_size(res),
mmc_hostname(host->mmc));
if (!sc->ioarea) {
dev_err(dev, "failed to reserve register area\n");
ret = -ENXIO;
goto err_req_regs;
}

host->ioaddr = ioremap_nocache(res->start, resource_size(res));
if (!host->ioaddr) {
dev_err(dev, "failed to map registers\n");
ret = -ENXIO;
goto err_req_regs;
}

/* Ensure we have minimal gpio selected CMD/CLK/Detect */
if (pdata->cfg_gpio)
pdata->cfg_gpio(pdev, pdata->max_width);

host->hw_name = "samsung-hsmmc";
host->ops = &sdhci_s3c_ops;
host->quirks = 0;
host->irq = irq;

/* Setup quirks for the controller */
host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;

#ifndef CONFIG_MMC_SDHCI_S3C_DMA

/* we currently see overruns on errors, so disable the SDMA
* support as well. */
host->quirks |= SDHCI_QUIRK_BROKEN_DMA;

#endif /* CONFIG_MMC_SDHCI_S3C_DMA */

/* It seems we do not get an DATA transfer complete on non-busy
* transfers, not sure if this is a problem with this specific
* SDHCI block, or a missing configuration that needs to be set. */
host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ;

if (pdata->cd_type == S3C_SDHCI_CD_NONE ||
pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;

if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
host->mmc->caps = MMC_CAP_NONREMOVABLE;

if (pdata->host_caps)
host->mmc->caps |= pdata->host_caps;

host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR |
SDHCI_QUIRK_32BIT_DMA_SIZE);

/* HSMMC on Samsung SoCs uses SDCLK as timeout clock */
host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK;

/*
* If controller does not have internal clock divider,
* we can use overriding functions instead of default.
*/
if (pdata->clk_type) {
sdhci_s3c_ops.set_clock = sdhci_cmu_set_clock;
sdhci_s3c_ops.get_min_clock = sdhci_cmu_get_min_clock;
sdhci_s3c_ops.get_max_clock = sdhci_cmu_get_max_clock;
}

/* It supports additional host capabilities if needed */
if (pdata->host_caps)
host->mmc->caps |= pdata->host_caps;

ret = sdhci_add_host(host);
if (ret) {
dev_err(dev, "sdhci_add_host() failed\n");
goto err_add_host;
}

/* The following two methods of card detection might call
sdhci_s3c_notify_change() immediately, so they can be called
only after sdhci_add_host(). Setup errors are ignored. */
if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_init)
pdata->ext_cd_init(&sdhci_s3c_notify_change);
if (pdata->cd_type == S3C_SDHCI_CD_GPIO &&
gpio_is_valid(pdata->ext_cd_gpio))
sdhci_s3c_setup_card_detect_gpio(sc);

return 0;

err_add_host:
release_resource(sc->ioarea);
kfree(sc->ioarea);

err_req_regs:
for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
clk_disable(sc->clk_bus[ptr]);
clk_put(sc->clk_bus[ptr]);
}

err_no_busclks:
clk_disable(sc->clk_io);
clk_put(sc->clk_io);

err_io_clk:
sdhci_free_host(host);

return ret;
}


有点长,但可以看出大致流程还是sdhci-alloc-host -> sdhci-add-host。

主机控制器驱动添加完成后,如果platform_data的cd_type值为EXTERNAL或者GPIO,就是调用相应函数进行检测。

关于三星对cd_type的定义,可以在platform-samsung/include/plat/sdhci.h找到:

enum cd_types {
S3C_SDHCI_CD_INTERNAL,	/* use mmc internal CD line */
S3C_SDHCI_CD_EXTERNAL,	/* use external callback */
S3C_SDHCI_CD_GPIO,	/* use external gpio pin for CD line */
S3C_SDHCI_CD_NONE,	/* no CD line, use polling to detect card */
S3C_SDHCI_CD_PERMANENT,	/* no CD line, card permanently wired to host */
};


几种卡检测模式没有具体研究,但三星在定义platform_data变量的时候并没有对cd_type成员赋值,也就是说随机,但在platform_samsung/dev-hsmmc.c中设置平台数据时有隐性表示:

void s3c_sdhci0_set_platdata(struct s3c_sdhci_platdata *pd)
{
struct s3c_sdhci_platdata *set = &s3c_hsmmc0_def_platdata;

set->cd_type = pd->cd_type;
set->ext_cd_init = pd->ext_cd_init;
set->ext_cd_cleanup = pd->ext_cd_cleanup;
set->ext_cd_gpio = pd->ext_cd_gpio;
set->ext_cd_gpio_invert = pd->ext_cd_gpio_invert;

if (pd->max_width)
set->max_width = pd->max_width;
if (pd->cfg_gpio)
set->cfg_gpio = pd->cfg_gpio;
if (pd->cfg_card)
set->cfg_card = pd->cfg_card;
if (pd->host_caps)
set->host_caps |= pd->host_caps;
if (pd->clk_type)
set->clk_type = pd->clk_type;
}


可以看到,三星官方驱动认为cd_type必须给出,而max_width是可选变量。因此cd_type最好是定义下,否则可能导致驱动正常加载,但识别不到卡。

可以看到官方的说明,关于后两种cd_type,可能是程序一直在执行检测,我没有仔细研究,但测试了两个类型,全部可以识别到卡。

平台驱动的注册流程大致就这么多,只是做了框架性分析,因此内容并不多,下面看下平台设备是怎么注册的。

平台设备定义mach-s3c64xx/mach-mini6410.c:

static struct platform_device *mini6410_devices[] __initdata = {
&mini6410_device_eth,
&s3c_device_hsmmc0,
&s3c_device_hsmmc1,
&s3c_device_ohci,
&s3c_device_nand,
&s3c_device_fb,
&mini6410_lcd_powerdev,
&s3c_device_adc,
&s3c_device_ts,
};


mmc0设备plat-samsung/dev-hsmmc.c:

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		= &s3c_device_hsmmc_dmamask,
.coherent_dma_mask	= 0xffffffffUL,
.platform_data		= &s3c_hsmmc0_def_platdata,
},
};


资源定义:

#define S3C_SZ_HSMMC	(0x1000)

static struct resource s3c_hsmmc_resource[] = {
[0] = {
.start = S3C_PA_HSMMC0,
.end   = S3C_PA_HSMMC0 + S3C_SZ_HSMMC - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_HSMMC0,
.end   = IRQ_HSMMC0,
.flags = IORESOURCE_IRQ,
}
};


platform_data定义:

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),
.clk_type	= S3C_SDHCI_CLK_DIV_INTERNAL,
};


设备数据在平台初始化的过程中会做修改,文件mach-s3c64xx/mach-mini6410.c:

static void __init mini6410_machine_init(void)
{
// mmc platdata - remme
s3c_sdhci0_set_platdata(&mini6410_hsmmc0_pdata);

u32 cs1;
struct mini6410_features_t features = { 0 };

printk(KERN_INFO "MINI6410: Option string mini6410=%s\n",
mini6410_features_str);

/* Parse the feature string */
mini6410_parse_features(&features, mini6410_features_str);

mini6410_lcd_pdata.win[0] = &mini6410_fb_win[features.lcd_index];

printk(KERN_INFO "MINI6410: selected LCD display is %dx%d\n",
mini6410_lcd_pdata.win[0]->win_mode.xres,
mini6410_lcd_pdata.win[0]->win_mode.yres);

s3c_nand_set_platdata(&mini6410_nand_info);
s3c_fb_set_platdata(&mini6410_lcd_pdata);
s3c24xx_ts_set_platdata(&s3c_ts_platform);

/* configure nCS1 width to 16 bits */

cs1 = __raw_readl(S3C64XX_SROM_BW) &
~(S3C64XX_SROM_BW__CS_MASK << S3C64XX_SROM_BW__NCS1__SHIFT);
cs1 |= ((1 << S3C64XX_SROM_BW__DATAWIDTH__SHIFT) |
(1 << S3C64XX_SROM_BW__WAITENABLE__SHIFT) |
(1 << S3C64XX_SROM_BW__BYTEENABLE__SHIFT)) <<
S3C64XX_SROM_BW__NCS1__SHIFT;
__raw_writel(cs1, S3C64XX_SROM_BW);

/* set timing for nCS1 suitable for ethernet chip */

__raw_writel((0 << S3C64XX_SROM_BCX__PMC__SHIFT) |
(6 << S3C64XX_SROM_BCX__TACP__SHIFT) |
(4 << S3C64XX_SROM_BCX__TCAH__SHIFT) |
(1 << S3C64XX_SROM_BCX__TCOH__SHIFT) |
(13 << S3C64XX_SROM_BCX__TACC__SHIFT) |
(4 << S3C64XX_SROM_BCX__TCOS__SHIFT) |
(0 << S3C64XX_SROM_BCX__TACS__SHIFT), S3C64XX_SROM_BC1);

gpio_request(S3C64XX_GPF(15), "LCD power");
gpio_request(S3C64XX_GPE(0), "LCD power");

platform_add_devices(mini6410_devices, ARRAY_SIZE(mini6410_devices));
}

函数第一行有一句设置平台数据,平台数据定义如下:

static struct s3c_sdhci_platdata mini6410_hsmmc0_pdata = {
.max_width      = 4,
.cd_type        = S3C_SDHCI_CD_NONE,
};


由于该部分数据可能因为板子设计不一样而不相同,因此,三星并没有把该部分数据纳入到平台设备文件中,需要没个平台自己配置好参数,并调用设置平台数据函数进行设置。

这也是为什么新下载的内核配置好SD/MMC驱动后编译运行,SD卡驱动和平台设备均正常加载,但缺识别不到SD卡。就是因为默认情况下cd_type类型是不确定的,因此,卡无法被检测到,应该根据平台设置为相应的模式sd驱动程序才可以检测到。

至此,大致的流程全部分析完毕,在这个过程中我自己遇到的问题和需要注意的问题也用红色加粗很明确的标识了出来。

为了解决无法识别到SD卡的问题,下载编译了2.6.38、3.0.8、3.4的内核,并一一和官方比较,由于驱动加载都正常,导致整个查找过程可谓大海捞针,最终确定为平台启动文件中需要配置mmc设备数据。前后花了差不多2天时间,就等着搞定这个驱动了,下一步应该就可以将自己编译的安卓4.3启动了。等4.3成功启动了,下次再好好做个总结。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: