NAND FLASH 驱动

来源:互联网 发布:list和数组的区别 编辑:程序博客网 时间:2024/05/19 08:46

三星的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的时候的大小,比如说,我的硬件只能算256ByteECC.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来添加分区。我们写驱动,就是按照这个步骤来。

原创粉丝点击