三星的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来添加分区。我们写驱动,就是按照这个步骤来。