三星的nand flash 驱动架构是相当的复杂,虽然说,对驱动的架构大致了解,就可以按照韦东山老师的视频,一步步写出nandflash的驱动,但这样虽然写出来,心中仍有点不踏实。下面就对Linux-2.6.30.4内核中的三星nand_flash驱动结构所涉及到的函数,以及函数之间的关系详细的总结出来。
驱动主要在driver/mtd/nand/s3c2410.c中:
入口函数:module_init(s3c2410_nand_init);
在s3c2410_nand_init里面一个重要的函数调用:platform_driver_register(&s3c2410_nand_driver),就是注册一个平台驱动设备s3c2410_nand_driver。
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, }, };
当arch\arm\plat-s3c24xx\common-smdk.c里面注册有 "s3c2410-nand"的platform_device设备时,s3c2410_nand_driver里面的probe函数就会被调用。probe函数是我们下面分析的重点。
static int s3c24xx_nand_probe(struct platform_device *pdev, enum s3c_cpu_type cpu_type) { struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
/*to_nand_plat(pdev)函数的作用就是由pdev->device.driver_data(驱动的私有数据),即:plat指向驱动的私/*有数据 struct s3c2410_nand_info *info;
/*s3c2410_nand_info 是一个很重要的结构体,里面
*/ struct s3c2410_nand_mtd *nmtd; struct s3c2410_nand_set *sets; struct resource *res; int err = 0; int size; int nr_sets; int setno; info = kmalloc(sizeof(*info), GFP_KERNEL); memset(info, 0, sizeof(*info)); /*分配一个info结构体大小的内存空间,并初始化为0*/ platform_set_drvdata(pdev, info); /* 把pdev->device.driver_data指向info。注意,前面已经用*plat = to_nand_plat(pdev)函数,把plat指向驱动的私 /*有数据。这里,把私有数据指向info。
spin_lock_init(&info->controller.lock); init_waitqueue_head(&info->controller.wq); info->clk = clk_get(&pdev->dev, "nand"); clk_enable(info->clk); /* currently we assume we have the one resource */ res = pdev->resource; size = res->end - res->start + 1; info->area = request_mem_region(res->start, size, pdev->name); /*res指向描述设备的资源的数组。size得到大小。然后对info赋值,用request_mem_region申请资源。 info->device = &pdev->dev; info->platform = plat; info->regs = ioremap(res->start, size); info->cpu_type = cpu_type; /*对info进行赋值*/ err = s3c2410_nand_inithw(info); /*此函数是对硬件初始化,最主要的是会调用s3c2410_nand_setrate(struct s3c2410_nand_info *info),在此函数中会设置tacls, twrph0, twrph1,这些都与具体的硬件相关 sets = (plat != NULL) ? plat->sets : NULL; nr_sets = (plat != NULL) ? plat->nr_sets : 1; info->mtd_count = nr_sets; /*sets为一个数据结构,里面包含有mtd_partition,即为分区信息。nr_sets为分区的个数*/ /* allocate our information */ size = nr_sets * sizeof(*info->mtds); info->mtds = kmalloc(size, GFP_KERNEL);
memset(info->mtds, 0, size);
nmtd = info->mtds;
/*首先分配mtds内存空间,然后把nmtd指向mtds结构。mtds(内嵌于info中)和nmtd的结构类型相同,同为s3c2410_nand_mtd 类型。只不过mtds为nr_sets 个nmtd的合集。
看看struct s3c2410_nand_mtd { struct mtd_infomtd; struct nand_chipchip; struct s3c2410_nand_set*set; struct s3c2410_nand_info*info; int scan_res; };
在这个结构体中内嵌了nand_chip和mtd_info结构。
到这里,我们已经把nand_chip和mtd_info构造完成,并把他们都放在info结构中,同时也放有其他的一些资源信息。
下面就是对nand_chip进行设置。主要通过nand_scan_ident和nand_scan_tail来完成。 for (setno = 0; setno < nr_sets; setno++, nmtd++) { s3c2410_nand_init_chip(info, nmtd, sets); /* s3c2410_nand_init_chip
chip->write_buf = s3c2410_nand_write_buf; chip->read_buf = s3c2410_nand_read_buf; chip->select_chip = s3c2410_nand_select_chip;
设置nand_chip的操作函数,写出后面的操作函数也是我们的任务之一。
chip->priv = nmtd;
nand_chip的私有数据指向nmtd。
nmtd->scan_res = nand_scan_ident(&nmtd->mtd,(sets) ? sets->nr_chips : 1);
/*在nand_scan_ident中主要有两个函数调用:nand_set_defaults(chip, busw)和nand_get_flash_type
*/nand_set_defaults()函数是设置默认操作函数,当我们在前面s3c2410_nand_init_chip中没有设置的函数,系统就会自动设置为默认值。
nand_get_flash_type的作用就是得到flash类型,得到*maf_id和dev_id,然后比较dev_id和nand_flash_ids[i].id,得到flash的类型。 if (nmtd->scan_res == 0) { s3c2410_nand_update_chip(info, nmtd);
/*在这里面主要代码:
if (hardware_ecc) { if (chip->page_shift > 10) { chip->ecc.size = 256;// chip->ecc.bytes = 3; } else { chip->ecc.size = 512; chip->ecc.bytes = 3; chip->ecc.layout = &nand_hw_eccoob; }
ecc.size就是计算一次ECC的时候的大小,比如说,我的硬件只能算256个Byte的ECC.那512byte就要分两次来发送.
ecc.bytes就是算一次ECC有多少字节。
static struct nand_ecclayout nand_hw_eccoob = {
.eccbytes = 3,//用三个字节存放ECC
.eccpos = {0, 1, 2},//这三个字节为第0,1,2字节
.oobfree = {{8, 8}}//从第8字节开始的8字节空闲
};
*/ nand_scan_tail(&nmtd->mtd);
/*主要代码为:
chip->oob_poi = chip->buffers->databuf + mtd->writesize //oob_poi就是指向databuf的第512个字节.这个地址是放ECC数据的。
在s3c2410_nand_init_chip中有一句: chip->ecc.mode = NAND_ECC_HW;
所以:
chip->ecc.read_page = nand_read_page_hwecc;
chip->ecc.write_page = nand_write_page_hwecc;
……
这些都是对nand_chip 的ecc函数赋值。
*/ s3c2410_nand_add_partition(info, nmtd, sets);
/*static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info, struct s3c2410_nand_mtd *mtd, struct s3c2410_nand_set *set) { if (set == NULL) return add_mtd_device(&mtd->mtd); if (set->nr_partitions > 0 && set->partitions != NULL) { return add_mtd_partitions(&mtd->mtd, set->partitions, set->nr_partitions); } return add_mtd_device(&mtd->mtd); }
set里面包含了我们的分区信息:mtd_partition。当设置有分区时,就会用add_mtd_partitions添加mtd,否则,则用add_mtd_device添加。
add_mtd_partitions会调用add_one_partition,有多少个分区,就会调用多少次。
static struct mtd_part *add_one_partition(struct mtd_info *master, const struct mtd_partition *part, int partno,uint64_t cur_offset) { struct mtd_part *slave;//一个mtd_part代表一个分区 slave = kzalloc(sizeof(*slave), GFP_KERNEL); list_add(&slave->list, &mtd_partitions);//把这个分区加入链表mtd_partitions中
slave->mtd.type = master->type; slave->mtd.flags = master->flags & ~part->mask_flags; slave->mtd.size = part->size; slave->mtd.writesize = master->writesize; slave->mtd.oobsize = master->oobsize;
……//用我们前面nand_scan_tail函数构造的mtd_info,对这个slave进行初始化。
//最后调用:add_mtd_device(&slave->mtd),用我们构造的slave,注册到内核
}
int add_mtd_device(struct mtd_info *mtd)
{
/*主要代码:
mtd_table[i] = mtd;//存储mtd到mtd_table中
mtd->dev.class = mtd_class; mtd->dev.devt = MTD_DEVT(i);
/*MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2)
(index)*2是因为每向内核添加一个mtd_info需要创建两个设备节点:一个字符,一个块 */
device_register(&mtd->dev)
device_create(mtd_class, mtd->dev.parent,MTD_DEVT(i) + 1,NULL, "mtd%dro", i);
到此一个MTD设备的分区就添加到了内核。
}
*/
} if (sets != NULL) sets++; } err = s3c2410_nand_cpufreq_register(info); if (allow_clk_stop(info)) { dev_info(&pdev->dev, "clock idle support enabled\n"); clk_disable(info->clk);
} return 0; }
至此,probe函数差不多已经分析完成,只对驱动的框架做大概的分析,对一些函数的具体实现没有做过细的分析,细细推敲,还会发现很多问题,这就是以后的工作了。
虽然这个分析过程很复杂,但其实思想很简单。不外乎构造nand_chip结构,然后通过nand_scan_ident和nand_scan_tail来扫描,然后add_mtd_partitions或add_mtd_device来添加分区。我们写驱动,就是按照这个步骤来。