您的位置:首页 > 运维架构 > Linux

Linux NAND FLASH驱动程序分析(mini2440)

2011-06-18 10:34 423 查看
Linux NAND FLASH驱动程序分析(mini2440)

一、Linux-MTD Subsystem介绍
FLASH在嵌入式系统中是必不可少的,它是bootloader、linux内核和文件系统的最佳载体。在Linux内核中引入了MTD子系统为NOR
FLASH和NAND FLASH设备提供统一的接口,从而使得FLASH驱动的设计大为简化。

在引入MTD后Linux系统中FLASH设备驱动可分为四层,如图:



1. 硬件驱动层
FLASH硬件驱动层负责FLASH硬件设备的读、写、擦出,LINUX MTD设备的NOR FLASH驱动位于/driver/mtd/chips子目录下,NAND FLASH驱动则位于/driver/mtd/nand子目录下。
特定硬件层负责完成特定硬件的读写,例如2440的/driver/mtd/nand/s3c2410.c
通用驱动为所有nand的通用部分,实现nand设备发现,通用读写等./driver/mtd/nand/nand_base.c
2. MTD原始设备层:MTD原始设备层由两部分构成,一部分是MTD原始设备的通用代码(mtdcore.c、mtdpart.c),另一部分是各个 特定的FLASH的数据,例如分区。
3. MTD设备层:基于MTD原始设备,LINUX系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90),构成设备层。MTD字符设备在mtdchar.c实现,MTD块设备在mtdblock.c实现。
4. 设备节点:通过mknod在/dev子目录下建立MTD字符设备节点(主设备号为90)和块设备节点(主设备号为31),用户通过访问此设备节点即可访问MTD字符设备和块设备。
也可通过下图理解:





从上图可以看出,MTD设备层与原始设备层打交道。通过分析源代码我们可以知道当上层要求对FLASH进行读写时,它会像设备层发出请求,设备层的读写函数会调用原始设备层中的读写函数,即mtd_info结构体(mtd原始设备层中描述设备的专用结构体)中的读写函数,而mtd_info中的函数会调用nand_chip(nand硬件驱动层中描述设备的结构体,其中包含了针对特定设备的基本参数和设备操作函数)中的读写函数。所以一个flash硬件驱动程序时的步骤大致如下:
1. 如果FLASH要分区,则定义mtd_partition数组,将FLASH分区信息记录其中。
mtd_partition
friendly_arm_default_nand_part[],arch/arm/plat-s3c24xx/common-friendly-arm.c
2. 在模块加载时为每一个chip(主分区)分配mtd_info和nand_chip的内存,根据目标板nand 控制器的特殊情况初始化nand_chip中的实现对FLASH操作的成员函数,如hwcontrol()、dev_ready()、read_byte()、write_byte()等。填充mtd_info,并将其priv成员指向nand_chip。 driver/mtd/nand/s3c2410/c
3. 以mtd_info为参数调用nand_scan()函数探测NAND FLASH的存在。nand_scan()函数会从FLASH芯片中读取其参数,填充相应nand_chip成员。 driver/mtd/nand/nand_base/c
nand_scan()分为两步,首先是nand_scan_ident(),主要完成flashID的读取等,然后是nand_scan_tail(),主要完成chip结构体中未初始化函数的默认赋值,以及坏块的扫描。
4. 如果要分区,则以mtd_info和mtd_partition为参数调用add_mtd_partions(),添加分区信息。在这个函数里面会为每一个分区分配一个mtd_info结构体填充,并注册。

二、nand flash驱动程序实例分析
我们以2.6.29内核中mini2440的nand flash驱动程序为例来分析一下这个过程,这里的flash驱动被写成了platform驱动的形式。我们下面分析其过程:

1. 注册nand flash设备
nand flash分区:

//arch/arm/plat-s3c24xx/common-friendly-arm.c
static struct mtd_partition friendly_arm_default_nand_part[] = {
[0] = {
.name	= "supervivi",
.size	= 0x00060000,
.offset	= 0,
},
[1] = {
.name	= "Kernel",
.offset = 0x00060000,
.size	= 0x00200000,
},
[2] = {
.name	= "root",
.offset = 0x00260000,
.size	= 1024 * 1024 * 1024, //64U * 1024 * 1024 - 0x00260000,
},
[3] = {
.name	= "nand",
.offset = 0x00000000,
.size	= 1024 * 1024 * 1024, //64U * 1024 * 1024 - 0x00260000,
}
};
static struct s3c2410_nand_set friendly_arm_nand_sets[] = {
[0] = {
.name		= "NAND",
.nr_chips	= 1, //系统只有一个chip
.nr_partitions	= ARRAY_SIZE(friendly_arm_default_nand_part), // 4个分区
.partitions	= friendly_arm_default_nand_part,
},
};
static struct s3c2410_platform_nand friendly_arm_nand_info = {
.tacls		= 20,//2440 rNFCONF 需要设置的时钟信息
.twrph0		= 60,
.twrph1		= 20,
.nr_sets	= ARRAY_SIZE(friendly_arm_nand_sets),
.sets		= friendly_arm_nand_sets,
};//这里将许多数据作为platform_data传入包括chip数组


nand控制器资源
//arch/arm/plat-s3c24xx/devs.c
static struct resource s3c_nand_resource[] = {
[0] = {
.start = S3C24XX_PA_NAND,
.end   = S3C24XX_PA_NAND + S3C24XX_SZ_NAND - 1,
.flags = IORESOURCE_MEM,
}
};
struct platform_device s3c_device_nand = {
.name		  = "s3c2410-nand",
.id		  = -1,
.num_resources	  = ARRAY_SIZE(s3c_nand_resource),
.resource	  = s3c_nand_resource,
};
//注册nand flash作为platform device:
static struct platform_device __initdata *friendly_arm_devs[] = {
&s3c_device_nand,
&s3c_device_sdi,
&s3c_device_usbgadget,
};
void __init friendly_arm_machine_init(void)
{
/* Configure the LEDs (even if we have no LED support)*/
s3c_device_nand.dev.platform_data = &friendly_arm_nand_info;
//在nand的驱动程序的probe中会利用这个负责,以及后面添加分区是获取分区信息
platform_add_devices(friendly_arm_devs, ARRAY_SIZE(friendly_arm_devs));
s3c2410_pm_init();
}


2.注册nand flash driver
//linux/drivers/mtd/nand/s3c2410.c
static struct platform_driver s3c2440_nand_driver = {
.probe		= s3c2440_nand_probe,
.remove		= s3c2410_nand_remove,
.suspend		= s3c24xx_nand_suspend,
.resume		= s3c24xx_nand_resume,
.driver		= {
.name	= "s3c2440-nand",
.owner	= THIS_MODULE,
},
};
static int __init s3c2410_nand_init(void)
{
printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics/n");
platform_driver_register(&s3c2412_nand_driver);
platform_driver_register(&s3c2440_nand_driver);
return platform_driver_register(&s3c2410_nand_driver);
}


当platform_driver驱动被加载时或者是当platform_device被注册时,总线驱动程序

会查找与设备匹配的驱动程序,找到时设备驱动程序的probe函数会被调用,下面我们来分析一下在我们驱动程序中的probe函数

probe的具体调用流程见 Linux NAND FLASH驱动代码分析

static int s3c2440_nand_probe(struct platform_device *dev)
{
return s3c24xx_nand_probe(dev, TYPE_S3C2440);
}
/* s3c2410_nand_probe
*
* called by device layer when it finds a device matching
* one our driver can handled. This code checks to see if
* it can allocate all necessary resources then calls the
* nand layer to look for devices
*/
static int s3c24xx_nand_probe(struct platform_device *pdev,
enum s3c_cpu_type cpu_type)
{
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);
info = kmalloc(sizeof(*info), GFP_KERNEL); //分配s3c2410_nand_info内存
if (info == NULL) {
dev_err(&pdev->dev, "no memory for flash info/n");
err = -ENOMEM;
goto exit_error;
}
memset(info, 0, 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");
if (IS_ERR(info->clk)) {
dev_err(&pdev->dev, "failed to get clock/n");
err = -ENOENT;
goto exit_error;
}
clk_enable(info->clk);
/* allocate and map the resource */
/* 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);
if (info->area == NULL) {
dev_err(&pdev->dev, "cannot reserve register region/n");
err = -ENOENT;
goto exit_error;
}
info->device     = &pdev->dev;
info->platform   = plat;
info->regs       = ioremap(res->start, size);//存储Nand控制器的虚拟地址
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);//初始化硬件,设置TACLS,TWRPH0/1(2440-rNFCONF)并使能nand控制器
if (err != 0)
goto exit_error;
sets = (plat != NULL) ? plat->sets : NULL;
nr_sets = (plat != NULL) ? plat->nr_sets : 1;
info->mtd_count = nr_sets;//1
/* allocate our information */
size = nr_sets * sizeof(*info->mtds);//nr_sets = 1
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;
}
memset(info->mtds, 0, size);
/* initialise all possible chips */
nmtd = info->mtds;
for (setno = 0; setno < nr_sets; setno++, nmtd++) {//nr_sets = 1
pr_debug("initialising set %d (%p, info %p)/n", setno, nmtd, info);
//初始化nand_chip中的read_buf,write_buf等函数指针,ecc设置
s3c2410_nand_init_chip(info, nmtd, sets);
//nand_scan()的第一步(nand_base.c),read ID…
nmtd->scan_res = nand_scan_ident(&nmtd->mtd,
(sets) ? sets->nr_chips : 1);
if (nmtd->scan_res == 0) {
//如果使用硬件ecc,根据id信息中的page大小更新ecc设置
s3c2410_nand_update_chip(info, nmtd);
//nand_scan()的第二步(nand_base.c),给初始化的函数指针默认值,扫描坏块
nand_scan_tail(&nmtd->mtd);
s3c2410_nand_add_partition(info, nmtd, sets);//添加分区,想mtd注册分区信息
}
if (sets != NULL)
sets++;
}
err = s3c2410_nand_cpufreq_register(info);
if (err < 0) {
dev_err(&pdev->dev, "failed to init cpufreq support/n");
goto exit_error;
}
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: