2410上NAND的初始化及驱动流程
一 NAND设备及资源的定义和注册
我们通过MACHINE_START定义了smdk2410的machine_desc对象,这个对象里面有个init_machine的函数指针,这里指向smdk_machine_init(),我们的NAND设备就是在这个函数里注册到系统的.
void __init smdk_machine_init(void)
{
….
s3c_device_nand.dev.platform_data = &smdk_nand_info;
platform_add_device(smdk_devs, ARRAY_SIZE(smdk_devs)); //这里就把设备注册到系统里去了
…
}
Static struct platform_device __initdata *smdk_devs[] =
{
&s3c_device_nand, //这样在上面的函数里我们的nand设备就注册好了.
...
}
其他设备我们也可以在这里注册进系统.
struct platform_device s3c_device_nand =
{
.name = “s3c2410-nand”, /*名字很重要*/
.id = -1,
. num_resources = ARRAY_SIZE(s3c_nand_resource),
.resource = s3c_nand_resource, //这个是NAND占用的资源.
};
Static struct s3c2410_platform_nand smdk_nand_info = {
.tacks = 20, /*在datasheet上有描述*/
.twrph0 = 60, /*在datasheet上有描述*/
.twrph1 = 20, /*在datasheet上有描述*/
.nr_sets = ARRAY_SIZE(smdk_nand_sets),
.sets = smdk_nand_sets
}
static struct s3c2410_nand_set smdk_nand_sets[] = {
[0] = {
.name = "NAND",
.nr_chips = 1,
.nr_partitions = ARRAY_SIZE(smdk_default_nand_part),
.partitions = smdk_default_nand_part, /*nand的分区信息*/
},
};
/*分区信息,我们可以在这里修改分区内容*/
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
.name = "Boot Agent",
.size = SZ_16K,
.offset = 0,
},
[1] = {
.name = "S3C2410 flash partition 1",
.offset = 0,
.size = SZ_2M,
},
[2] = {
.name = "S3C2410 flash partition 2",
.offset = SZ_4M,
.size = SZ_4M,
},
[3] = {
.name = "S3C2410 flash partition 3",
.offset = SZ_8M,
.size = SZ_2M,
},
[4] = {
.name = "S3C2410 flash partition 4",
.offset = SZ_1M * 10,
.size = SZ_4M,
},
[5] = {
.name = "S3C2410 flash partition 5",
.offset = SZ_1M * 14,
.size = SZ_1M * 10,
},
[6] = {
.name = "S3C2410 flash partition 6",
.offset = SZ_1M * 24,
.size = SZ_1M * 24,
},
[7] = {
.name = "S3C2410 flash partition 7",
.offset = SZ_1M * 48,
.size = SZ_16M,
}
};
这样NAND设备(连同设备的详细信息)就注册进了系统,以后在nand的驱动注册后就会probe到并使用这里定义的资源信息.
二 NAND驱动流程
2410的驱动实现在driver/mtd/nand/S3c2410.c里
首先和其他驱动一样先通过module_init(), module_exit()注册一个初始化/卸载函数,
module_init(s3c2410_nand_init);
module_exit(s3c2410_nand_exit);
系统初始化时该函数被调用
static int __init s3c2410_nand_init(void)
{
printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics/n");
platform_driver_register(&s3c2412_nand_driver); /*注册nand驱动*/
platform_driver_register(&s3c2440_nand_driver); /*注册nand驱动*/
return platform_driver_register(&s3c2410_nand_driver); /*注册nand驱动*/
}
从上面可以看到我们注册了3个驱动程序,但在系统probe时它只会匹配到s3c2410_nand_driver的驱动,因为各个驱动的名字是不一样的,而系统是安名字来probe的.
static struct platform_driver s3c2410_nand_driver = {
.probe = s3c2410_nand_probe,
.remove = s3c2410_nand_remove,
.suspend = s3c24xx_nand_suspend,
.resume = s3c24xx_nand_resume,
.driver = {
.name = "s3c2410-nand", /*这里的名字一定要与设备定义的名字相同*/
.owner = THIS_MODULE,
},
};
当系统probe到我们刚才注册的nand设备后即调用s3c2410_nand_probe函数
static int s3c2410_nand_probe(struct platform_device *dev)
{
return s3c24xx_nand_probe(dev, TYPE_S3C2410);
}
static int s3c24xx_nand_probe(struct platform_device *pdev, /*nand设备,前面已经列出*/
enum s3c_cpu_type cpu_type /*TYPE_S3C2410*/)
{
struct s3c2410_platform_nand *plat = to_nand_plat(pdev);/*对照前面列出的设备定义来看*/
struct s3c2410_nand_info *info;
struct s3c2410_nand_mtd *nmtd;
struct s3c2410_nand_set *sets;
struct resource *res;
int err = 0;
int size;
int nr_sets;
int setno;
pr_debug("s3c2410_nand_probe(%p)/n", pdev);
/*该变量用来保存nand详细信息,以后访问nand信息都将从这个变量里得到*/
info = kmalloc(sizeof(*info), GFP_KERNEL);
if (info == NULL) {
dev_err(&pdev->dev, "no memory for flash info/n");
err = -ENOMEM;
goto exit_error;
}
memzero(info, sizeof(*info));
platform_set_drvdata(pdev, info);
spin_lock_init(&info->controller.lock); /*自选锁初始化*/
init_waitqueue_head(&info->controller.wq); /*等待队列初始化*/
/* get the clock source and enable it */
info->clk = clk_get(&pdev->dev, "nand"); /*获取用于nand的clock(nand也要时钟信号的哦)*/
if (IS_ERR(info->clk)) {
dev_err(&pdev->dev, "failed to get clock");
err = -ENOENT;
goto exit_error;
}
clk_enable(info->clk); /*使能该clock,实际上就是设置CLKCON的第四位(详见2410datasheet)*/
/* allocate and map the resource */
/* currently we assume we have the one resource */
res = pdev->resource; /*nand资源,见前面的定义*/
size = res->end - res->start + 1;
/*
*请求指定的memory区域(实际上是nand的寄存器区域),这是物理内存,实际上只是检测该区域
*是否空闲的
*/
info->area = request_mem_region(res->start, size, pdev->name);
if (info->area == NULL) {
dev_err(&pdev->dev, "cannot reserve register region/n");
err = -ENOENT;
goto exit_error;
}
/*保存nand信息*/
info->device = &pdev->dev;
info->platform = plat;
/*虚实地址映射,以后程序里就可以直接访问nand的寄存器了*/
info->regs = ioremap(res->start, size);
info->cpu_type = cpu_type;
if (info->regs == NULL) {
dev_err(&pdev->dev, "cannot reserve register region/n");
err = -EIO;
goto exit_error;
}
dev_dbg(&pdev->dev, "mapped registers at %p/n", info->regs);
/* initialise the hardware */
err = s3c2410_nand_inithw(info, pdev); /*初始化nand硬件设备*/
if (err != 0)
goto exit_error;
/*接下来是MTD方面的初始化*/
sets = (plat != NULL) ? plat->sets : NULL;
nr_sets = (plat != NULL) ? plat->nr_sets : 1;
info->mtd_count = nr_sets;
/* allocate our information */
size = nr_sets * sizeof(*info->mtds);
info->mtds = kmalloc(size, GFP_KERNEL);
if (info->mtds == NULL) {
dev_err(&pdev->dev, "failed to allocate mtd storage/n");
err = -ENOMEM;
goto exit_error;
}
memzero(info->mtds, size);
/* initialise all possible chips */
nmtd = info->mtds;
for (setno = 0; setno < nr_sets; setno++, nmtd++) {
pr_debug("initialising set %d (%p, info %p)/n", setno, nmtd, info);
/*初始化nand chip实例*/
s3c2410_nand_init_chip(info, nmtd, sets);
/*初始化mtd及相关信息,详情可参考MTD的源码*/
nmtd->scan_res = nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1) ;
if (nmtd->scan_res == 0) {
s3c2410_nand_add_partition(info, nmtd, sets); /*添加分区,详情参看MTD*/
}
if (sets != NULL)
sets++;
}
if (allow_clk_stop(info)) {
dev_info(&pdev->dev, "clock idle support enabled/n");
clk_disable(info->clk);
}
pr_debug("initialised ok/n");
return 0;
exit_error:
s3c2410_nand_remove(pdev);
if (err == 0)
err = -EINVAL;
return err;
}
这个函数是最关键的,要要给nand设备进行很多初始化的工作,并保存nand的所有详细信息,便于以后使用,详细功能已在函数里描述了. clock方面的内容会有专门的文章讲解
/* nand控制器的初始化 */
static int s3c2410_nand_inithw(struct s3c2410_nand_info *info,
struct platform_device *pdev)
{
struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
unsigned long clkrate = clk_get_rate(info->clk); /*nand时钟主频*/
int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;
int tacls, twrph0, twrph1;
unsigned long cfg = 0;
/* calculate the timing information for the controller */
clkrate /= 1000; /* turn clock into kHz for ease of use */
/*计算各时钟频率,这些时钟参数可参考2410的datasheet*/
if (plat != NULL) {
tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max);
twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8);
twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8);
} else {
/* default timings */
tacls = tacls_max;
twrph0 = 8;
twrph1 = 8;
}
if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {
dev_err(info->device, "cannot get suitable timings/n");
return -EINVAL;
}
dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns/n",
tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), twrph1, to_ns(twrph1, clkrate));
switch (info->cpu_type) {
case TYPE_S3C2410:
cfg = S3C2410_NFCONF_EN; /*使能*/
cfg |= S3C2410_NFCONF_TACLS(tacls - 1);
cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);
break;
case TYPE_S3C2440:
case TYPE_S3C2412:
cfg = S3C2440_NFCONF_TACLS(tacls - 1);
cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
/* enable the controller and de-assert nFCE */
writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);
}
dev_dbg(info->device, "NF_CONF is 0x%lx/n", cfg);
/*把使能参数,时钟参数设到寄存器NFCONF里*/
writel(cfg, info->regs + S3C2410_NFCONF);
return 0;
}
/*
*为每个nand chip初始化一个实例.
*比较简单,就是初始化各种参数和函数指针,以后会用到
*/
static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
struct s3c2410_nand_mtd *nmtd,
struct s3c2410_nand_set *set)
{
struct nand_chip *chip = &nmtd->chip;
void __iomem *regs = info->regs;
chip->write_buf = s3c2410_nand_write_buf;
chip->read_buf = s3c2410_nand_read_buf;
chip->select_chip = s3c2410_nand_select_chip;
chip->chip_delay = 50;
chip->priv = nmtd;
chip->options = 0;
chip->controller = &info->controller;
switch (info->cpu_type) {
case TYPE_S3C2410:
chip->IO_ADDR_W = regs + S3C2410_NFDATA;
info->sel_reg = regs + S3C2410_NFCONF;
info->sel_bit = S3C2410_NFCONF_nFCE;
chip->cmd_ctrl = s3c2410_nand_hwcontrol;
chip->dev_ready = s3c2410_nand_devready;
break;
case TYPE_S3C2440:
chip->IO_ADDR_W = regs + S3C2440_NFDATA;
info->sel_reg = regs + S3C2440_NFCONT;
info->sel_bit = S3C2440_NFCONT_nFCE;
chip->cmd_ctrl = s3c2440_nand_hwcontrol;
chip->dev_ready = s3c2440_nand_devready;
break;
case TYPE_S3C2412:
chip->IO_ADDR_W = regs + S3C2440_NFDATA;
info->sel_reg = regs + S3C2440_NFCONT;
info->sel_bit = S3C2412_NFCONT_nFCE0;
chip->cmd_ctrl = s3c2440_nand_hwcontrol;
chip->dev_ready = s3c2412_nand_devready;
if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT)
dev_info(info->device, "System booted from NAND/n");
break;
}
chip->IO_ADDR_R = chip->IO_ADDR_W;
nmtd->info = info;
nmtd->mtd.priv = chip;
nmtd->mtd.owner = THIS_MODULE;
nmtd->set = set;
if (hardware_ecc) {
chip->ecc.calculate = s3c2410_nand_calculate_ecc;
chip->ecc.correct = s3c2410_nand_correct_data;
chip->ecc.mode = NAND_ECC_HW;
chip->ecc.size = 512;
chip->ecc.bytes = 3;
chip->ecc.layout = &nand_hw_eccoob;
switch (info->cpu_type) {
case TYPE_S3C2410:
chip->ecc.hwctl = s3c2410_nand_enable_hwecc;
chip->ecc.calculate = s3c2410_nand_calculate_ecc;
break;
case TYPE_S3C2412:
case TYPE_S3C2440:
chip->ecc.hwctl = s3c2440_nand_enable_hwecc;
chip->ecc.calculate = s3c2440_nand_calculate_ecc;
break;
}
} else {
chip->ecc.mode = NAND_ECC_SOFT;
}
}
Nand驱动剩下的就是实现上面这个函数里初始化的各个函数指针了.而这些函数就是实实在在的对nand硬件的操作了,大家可以对照着2410的nand部分的datasheet来看这些代码