在介绍Nand Flash块设备驱动之前,首先你需要了解S3C2440这款SOC关于Nand Flash控制器的知识,同时需要对Mini2440开发板所使用的K9F2G08U0C型号芯片有所了解,因为这一节我们不会过多介绍这些内容。
具体可以参考之前我们介绍的两篇博客:
一、Nand Flash ID
1.1 K9F2G08U0C
K9F2G08U0C芯片读取ID,需要发送命令0x90,然后发送地址0x00,最后连续读取5个字节,就是Nand Flash的ID。
Device | Marker Code | Device Code(2nd Cycle) | 3rd Cycle | 4th Cycle | 5th Cycle |
K9F2G08U0C | ECH | DAH | 10H | 15H | 44H |
Marker code | Device Code |
Internal Chip Number Cell Type Number of Simultaneously Proprammed Pages Etc |
Page Size Block Size Redundant Area Size Organization, Serial Access Minimum |
Plane Number Plane Size |
从芯片的Datasheet我们可以找到这5个字节依次为0xEC、0xDA、0x10、0x15、0x44。0xEC表示厂家ID、0xDA表示设备ID.
1.1.1 3rd ID Data
Description | I/O7 | I/O6 | I/O 5 I/O4 | I/O3 I/O2 | I/O1 I/O0 | |
Internal Chip Number |
1 2 4 8 |
0 0 0 1 1 0 1 1 |
||||
Cell Type |
2 Level Cell |
0 0 0 1 1 0 1 1 |
||||
Number of |
1 2 4 8 |
0 0 0 1 1 0 1 1 |
||||
Interleave Program |
Not Support Support |
0 1 |
||||
Cache Program |
Not Support Support |
0 1 |
由于我们所使用的的这款Nand Flash芯片第三个字节为0x10,对应二进制就是0001 0000B,所以:
- Internal Chip Number:1,表示该Nand Flash内部是由1个芯片(chip)所组成的;
- Cell Type:2 Level Cell,Level Cell又称为SLC(Single Layer Cell单层单元);2 Level Cell表示每个内存单元中有两种状态(电平等级),可表示1bit数据(0或者1);
- Number of Simultaneously Programmed Pages:可以对几个页同时编程/写。此功能简单的说就是,一次性地写多个页的数据到对应的不同的页。
- Interleave Program Between multiple chips:不支持;
- Cache Program:不支持;
1.1.2 4th Data
Description | I/O7 | I/O6 | I/O5 I/O4 | I/O3 | I/O2 | I/O1 I/O0 | |
Page Size |
1KB 2KB 4KB 8KB |
0 0 0 1 1 0 1 1 |
|||||
Block Size |
64KB 128KB 256KB 512KB |
0 0 0 1 1 0 1 1 |
|||||
Redundant Area Size (byte/512byte) |
8 16 |
0 1 |
|||||
Organization |
x8 x16 |
0 1 |
|||||
Serial Access Minimum |
50ns/30ns 25ns Reserved Reserved |
0 1 0 1 |
0 0 1 1 |
由于我们所使用的的这款Nand Flash芯片第四个字节为0x15,对应二进制就是0001 0101B,所以:
- Page Size:2KB,页大小为2KB,即每次读/写最小单位为2KB;
- Block Size:128KB,块大小为128KB,即每次擦除最小单位为128KB;
- Redundant Area Size(OOB区域、或者Spare Area,用于ECC):16,即没512个字节冗余区域大小为16个字节,每页冗余区域为2KB/512*16=64字节;
- Organization:x8,这里指的是bus width(数据线宽度)为8;
- Serial Access Minimum:50ns/30ns;
1.1.3 5th Data
Description | I/O7 | I/O6 I/O5 I/O4 | I/O3 I/O2 | I/O1 | I/O0 | |
Plane Number |
1 2 4 8 |
0 0 0 1 1 0 1 1 |
||||
Plane Size |
64Mb 128Mb 256Mb 512Mb 1Gb 2Gb 4Gb 8Gb |
0 0 0 0 0 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1 1 1 |
|
|||
Reserved | 0 | 0 | 0 |
由于我们所使用的的这款Nand Flash芯片第五个字节为0x44,对应二进制就是0100 0100B,所以:
- Plane Number:2,即每个chip包含两个plane;
- Plane Size:1Gb,每个plane大小为1Gb=128MB;
所以每个chip是256MB。
1.2 在uboot中读取ID
向NFCONT寄存器写入0x01;使能Nand Flash、以及片选使能;NFCONT寄存器地址为0x4e000004;
向NFCMMD寄存器写入0x90;NFCMMD寄存器地址为0x4e000008;
向NFADDR寄存器写入0x00;NFADDR寄存器地址为0x4e00000c;
我们再来看一下uboot相关的命令:
- mw:memory write, mw.b 写入一个字节、mw.w写入2个字节、mw.l写入一个4个字节;
- md:memory display,md.b 读取一个字节、md.w读取2个字节、md.l读取一个4个字节;
给Mini2440开发板上电,在uboot中输入如下命令: ....
SMDK2440 # md.l 0x4e000004 1 4e000004: 00000003 .... SMDK2440 # mw.l 0x4e000004 1 SMDK2440 # mw.b 0x4e000008 0x90 SMDK2440 # mw.b 0x4e00000c 0x00 SMDK2440 # md.b 0x4e000010 1 4e000010: ec . SMDK2440 # md.b 0x4e000010 1 4e000010: f1 . SMDK2440 # md.b 0x4e000010 1 4e000010: 00 . SMDK2440 # md.b 0x4e000010 1 4e000010: 95 . SMDK2440 # md.b 0x4e000010 1 4e000010: 40
我们发现uboot命令读取到的ID好像除了第一个字节0xEC没问题,其他的四个字节都不太对。后来我才想起来,由于我最初的那块Mini2440开发板网络有问题,后来换了一环开发板,而这块开发板使用的是Nand Flash型号是K9F1G089U0B。
Device | Marker Code | Device Code(2nd Cycle) | 3rd Cycle | 4th Cycle | 5th Cycle |
K9F1G08U0B | ECH | F1H | 00H | 95H | 40H |
K9F1G089U0B内部包含1个chip,每个chip包含1个plane,每个plane大小为1Gb=128MB,所以每个chip大小为128MB,即K9F1G089U0B容量大小为128MB。页大小为2KB,块大小为128KB。
二、platform设备注册(s3c2410-nand)
接下下来我们直接分析内核自带的Nand Flash驱动,其采用的也是platform设备驱动模型。
2.1 相关结构体
我们定位到include/linux/platform_data/mtd-nand-s3c2410.h头文件:
/** * struct s3c2410_nand_set - define a set of one or more nand chips * @flash_bbt: Openmoko u-boot can create a Bad Block Table * Setting this flag will allow the kernel to * look for it at boot time and also skip the NAND * scan. * @options: Default value to set into 'struct nand_chip' options. * @nr_chips: Number of chips in this set * @nr_partitions: Number of partitions pointed to by @partitions * @name: Name of set (optional) * @nr_map: Map for low-layer logical to physical chip numbers (option) * @partitions: The mtd partition list * * define a set of one or more nand chips registered with an unique mtd. Also * allows to pass flag to the underlying NAND layer. 'disable_ecc' will trigger * a warning at boot time. */ struct s3c2410_nand_set { unsigned int flash_bbt:1; unsigned int options; int nr_chips; // chip的个数 int nr_partitions; // 分区数目 char *name; // 集合的名称 int *nr_map; // 底层逻辑到物理的芯片数目 struct mtd_partition *partitions; // 分区表 struct device_node *of_node; }; struct s3c2410_platform_nand { /* timing information for controller, all times in nanoseconds */ int tacls; /* time for active CLE/ALE to nWE/nOE, Nand Flash时序参数TACLS */ int twrph0; /* active time for nWE/nOE,Nand Flash时序参数TWRPH0 */ int twrph1; /* time for release CLE/ALE from nWE/nOE inactive,Nand Flash时序参数TWRPH1 */ unsigned int ignore_unset_ecc:1; nand_ecc_modes_t ecc_mode; // ecc模式 int nr_sets; // nand set数目 struct s3c2410_nand_set *sets; // 指向nand set数组 void (*select_chip)(struct s3c2410_nand_set *, // 根据芯片编号选择有效nand set int chip); };
这里定义了两个结构体s3c2410_nand_set、s3c2410_platform_nand:
- s3c2410_nand_set:开发板所使用的的Nand Flash内部可能包含若干个chip,这里描述每个chip的分区信息;
- s3c2410_platform_nand:定义了开发板所使用的的Nand Flash的描述信息,比如时序参数、以及ecc模式、nand set数组等;
2.2 结构体全局变量
我们定位到 arch/arm/mach-s3c24xx/mach-smdk2440.c文件,在这个里面我们可以看到nand相关的信息定义:
/* NAND parititon from 2.4.18-swl5 */ static struct mtd_partition smdk_default_nand_part[] = { // 分区表 [0] = { .name = "u-boot", .size = SZ_256K, .offset = 0, }, [1] = { .name = "params", .size = SZ_128K, .offset = MTDPART_OFS_APPEND, }, [2] = { .name = "kernel", /* 5 megabytes, for a kernel with no modules * or a uImage with a ramdisk attached */ .size = SZ_4M, .offset = MTDPART_OFS_APPEND, }, [3] = { .name = "rootfs", .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, }, }; static struct s3c2410_nand_set smdk_nand_sets[] = { // nand set数组 [0] = { .name = "NAND", .nr_chips = 1, .nr_partitions = ARRAY_SIZE(smdk_default_nand_part), .partitions = smdk_default_nand_part, }, }; /* choose a set of timings which should suit most 512Mbit * chips and beyond. */ static struct s3c2410_platform_nand smdk_nand_info = { // 开发板所使用Nnand Flash的描述信息 .tacls = 20, .twrph0 = 60, .twrph1 = 20, .nr_sets = ARRAY_SIZE(smdk_nand_sets), .sets = smdk_nand_sets, // nand set数组指针 .ecc_mode = NAND_ECC_NONE, // 关闭ecc校验 };
可以看到这里声明了全局变量smdk_default_nand_part、smdk_nand_sets、smdk_nand_info并进行了初始化,如果我们想支持我们开发板所使用的的Nand Flash的话,实际上只要修改这些配置信息即可。
2.3 smdk2440_machine_init
linux内核启动的时候会根据uboot中设置的机器id执行相应的初始化工作,比如.init_machine、.init_irq,我们首先定位到arch/arm/mach-s3c24xx/mach-smdk2440.c:
MACHINE_START(S3C2440, "SMDK2440") /* Maintainer: Ben Dooks <[email protected]> */ .atag_offset = 0x100, .init_irq = s3c2440_init_irq, .map_io = smdk2440_map_io, .init_machine = smdk2440_machine_init, .init_time = smdk2440_init_time, MACHINE_END
重点关注init_machine,init_machine中保存的是开发板资源注册的初始化代码。
static void __init smdk2440_machine_init(void) { s3c24xx_fb_set_platdata(&smdk2440_fb_info); s3c_i2c0_set_platdata(NULL); platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)); // s3c2440若干个platform设备注册 usb host controller、lcd、wdt等 smdk_machine_init(); // s3c24x0系列若干个platform设备注册(通用) }
2.4 smdk_machine_init
其中smdk_machine_init定义在arch/arm/mach-s3c24xx/common-smdk.c:
void __init smdk_machine_init(void) { /* Configure the LEDs (even if we have no LED support)*/ int ret = gpio_request_array(smdk_led_gpios, ARRAY_SIZE(smdk_led_gpios)); if (!WARN_ON(ret < 0)) gpio_free_array(smdk_led_gpios, ARRAY_SIZE(smdk_led_gpios)); if (machine_is_smdk2443()) smdk_nand_info.twrph0 = 50; s3c_nand_set_platdata(&smdk_nand_info); // 设置smdk_nand_info->dev.platform_data=&smdk_nand_info platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs)); // 若干个platform设备注册 s3c_pm_init(); }
2.4.1 s3c_nand_set_platdata
我们定位到s3c_nand_set_platdata函数,位于 arch/arm/plat-samsung/devs.c文件中,实际上在这个文件里根据我们内核编译配置的宏,注册不同的platform设备,比如这里我们定义了名字为"s3c2410-nand"的platform设备:
/* NAND */ #ifdef CONFIG_S3C_DEV_NAND static struct resource s3c_nand_resource[] = { [0] = DEFINE_RES_MEM(S3C_PA_NAND, SZ_1M), // 定义起始地址资源 0x4E000000(Nand Flash控制器相关寄存器基地址)、大小为1M }; struct platform_device s3c_device_nand = { // 定义platform设备 .name = "s3c2410-nand", .id = -1, .num_resources = ARRAY_SIZE(s3c_nand_resource), .resource = s3c_nand_resource, }; /* * s3c_nand_copy_set() - copy nand set data * @set: The new structure, directly copied from the old. * * Copy all the fields from the NAND set field from what is probably __initdata * to new kernel memory. The code returns 0 if the copy happened correctly or * an error code for the calling function to display. * * Note, we currently do not try and look to see if we've already copied the * data in a previous set. */ static int __init s3c_nand_copy_set(struct s3c2410_nand_set *set) // 克隆nand set中数据,比如成员partitions、nr_map { void *ptr; int size; size = sizeof(struct mtd_partition) * set->nr_partitions; // 计算nand set中分区表大小 if (size) { ptr = kmemdup(set->partitions, size, GFP_KERNEL); // 申请一块新内存,大小为size,并将set->partitions拷贝到新的内存 set->partitions = ptr; // 指向新克隆的分区表 if (!ptr) return -ENOMEM; } if (set->nr_map && set->nr_chips) { // 同理,克隆set->nr_map size = sizeof(int) * set->nr_chips; ptr = kmemdup(set->nr_map, size, GFP_KERNEL); set->nr_map = ptr; if (!ptr) return -ENOMEM; } return 0; } void __init s3c_nand_set_platdata(struct s3c2410_platform_nand *nand) { struct s3c2410_platform_nand *npd; int size; int ret; /* note, if we get a failure in allocation, we simply drop out of the * function. If there is so little memory available at initialisation * time then there is little chance the system is going to run. */ npd = s3c_set_platdata(nand, sizeof(*npd), &s3c_device_nand); // 设置smdk_nand_info->dev.platform_data=&smdk_nand_info if (!npd) return; /* now see if we need to copy any of the nand set data */ size = sizeof(struct s3c2410_nand_set) * npd->nr_sets; // 计算nand set数组大小 if (size) { struct s3c2410_nand_set *from = npd->sets; // nand set 数组指针 struct s3c2410_nand_set *to; int i; to = kmemdup(from, size, GFP_KERNEL); // 申请一块新内存,大小为size,并将nand set数组拷贝到新的内存 npd->sets = to; /* set, even if we failed,npd->sets指向新克隆的nand set数组指针 */ if (!to) { printk(KERN_ERR "%s: no memory for sets\n", __func__); return; } for (i = 0; i < npd->nr_sets; i++) { // 遍历每一个nand set ret = s3c_nand_copy_set(to); // 克隆nand set数据,比如成员partitions、nr_map if (ret) { printk(KERN_ERR "%s: failed to copy set %d\n", __func__, i); return; } to++; } } } #endif /* CONFIG_S3C_DEV_NAND */
在s3c_nand_set_platdata这里我们调用了s3c_set_platdata函数,该函数设置s3c_device_nand->dev.platform_data=s&mdk_nand_info。
2.4.2 s3c_set_platdata
s3c_set_platdata定义在arch/arm/plat-samsung/platformdata.c文件中:
void __init *s3c_set_platdata(void *pd, size_t pdsize, // pd = &smdk_nand_info , pdev = &s3c_device_nand struct platform_device *pdev) { void *npd; if (!pd) { // 空校验 /* too early to use dev_name(), may not be registered */ printk(KERN_ERR "%s: no platform data supplied\n", pdev->name); return NULL; } npd = kmemdup(pd, pdsize, GFP_KERNEL); // 申请一块新内存,大小为pdsize,并将pd拷贝到新的内存 if (!npd) return NULL; pdev->dev.platform_data = npd; return npd; // 返回新克隆的smdk_nand_info }
这个函数主要是用来设置pdev->dev的platform_data成员,是个void *类型,可以给平台driver提供各种数据(比如:GPIO引脚等等)。
2.5 platform设备注册
我们已经定义了nand相关的platform_device设备s3c_device_nand,并进行了初始化,那platform设备啥时候注册的呢?
我们定位到smdk_machine_init中的如下函数:
platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs)); // 若干个platform设备注册
这里利用platform_add_devices进行若干个platform设备的注册,该函数还是通过调用platform_device_register实现platform设备注册:
/** * platform_add_devices - add a numbers of platform devices * @devs: array of platform devices to add * @num: number of platform devices in array */ int platform_add_devices(struct platform_device **devs, int num) { int i, ret = 0; for (i = 0; i < num; i++) { ret = platform_device_register(devs[i]); if (ret) { while (--i >= 0) platform_device_unregister(devs[i]); break; } } return ret; }
smdk_devs中就包含了s3c_device_nand:
/* devices we initialise */ static struct platform_device __initdata *smdk_devs[] = { &s3c_device_nand, &smdk_led4, &smdk_led5, &smdk_led6, &smdk_led7, };
三、platform驱动注册(s3c2410-2410)
3.1 相关结构体
3.1.1 struct s3c2410_nand_info
struct s3c2410_nand_info用于描述某个型号的Nand Flash,其包含了struct nand_controller、struct s3c2410_nand mtd以及struct s3c2410_platform _nand信息,定义在drivers/mtd/nand/raw/s3c2410.c文件:
/** * struct s3c2410_nand_info - NAND controller state. * @mtds: An array of MTD instances on this controoler. * @platform: The platform data for this board. * @device: The platform device we bound to. * @clk: The clock resource for this controller. * @regs: The area mapped for the hardware registers. * @sel_reg: Pointer to the register controlling the NAND selection. * @sel_bit: The bit in @sel_reg to select the NAND chip. * @mtd_count: The number of MTDs created from this controller. * @save_sel: The contents of @sel_reg to be saved over suspend. * @clk_rate: The clock rate from @clk. * @clk_state: The current clock state. * @cpu_type: The exact type of this controller. */ struct s3c2410_nand_info { /* mtd info */ struct nand_controller controller; struct s3c2410_nand_mtd *mtds; // mtd数组指针 struct s3c2410_platform_nand *platform; // 开发板所使用的的Nand Flash的描述信息 /* device info */ struct device *device; // 设备基类 struct clk *clk; // 时钟 void __iomem *regs; // nand flash控制器寄存器基地址(虚拟地址) void __iomem *sel_reg; // 当前选择的寄存器 如:NFCONF、NFCONT、NFCMMD、NFADDR、NFDATA、NFSTAT int sel_bit; // 当前选择的寄存器bit int mtd_count; // mtd数组长度 unsigned long save_sel; unsigned long clk_rate; // 时钟频率 enum s3c_nand_clk_state clk_state; // 当前nand时钟状态 CLOCK_ENABLE、CLOCK_DISABLE、CLOCK_SUSPEND enum s3c_cpu_type cpu_type; // cpu类型 #ifdef CONFIG_ARM_S3C24XX_CPUFREQ struct notifier_block freq_transition; #endif };
3.1.2 struct s3c2410_nand_mtd
/** * struct s3c2410_nand_mtd - driver MTD structure * @mtd: The MTD instance to pass to the MTD layer. * @chip: The NAND chip information. * @set: The platform information supplied for this set of NAND chips. * @info: Link back to the hardware information. */ struct s3c2410_nand_mtd { struct nand_chip chip; // nand chip struct s3c2410_nand_set *set; // nand set struct s3c2410_nand_info *info; };
3.1.3 struct nand_controller
nand_controller定义在include/linux/mtd/rawnand.h,用来描述Nand Flash控制器。
/** * struct nand_controller_ops - Controller operations * * @attach_chip: this method is called after the NAND detection phase after * flash ID and MTD fields such as erase size, page size and OOB * size have been set up. ECC requirements are available if * provided by the NAND chip or device tree. Typically used to * choose the appropriate ECC configuration and allocate * associated resources. * This hook is optional. * @detach_chip: free all resources allocated/claimed in * nand_controller_ops->attach_chip(). * This hook is optional. * @exec_op: controller specific method to execute NAND operations. * This method replaces chip->legacy.cmdfunc(), * chip->legacy.{read,write}_{buf,byte,word}(), * chip->legacy.dev_ready() and chip->legacy.waifunc(). * @setup_data_interface: setup the data interface and timing. If * chipnr is set to %NAND_DATA_IFACE_CHECK_ONLY this * means the configuration should not be applied but * only checked. * This hook is optional. */ struct nand_controller_ops { int (*attach_chip)(struct nand_chip *chip); void (*detach_chip)(struct nand_chip *chip); int (*exec_op)(struct nand_chip *chip, const struct nand_operation *op, bool check_only); int (*setup_data_interface)(struct nand_chip *chip, int chipnr, const struct nand_data_interface *conf); }; /** * struct nand_controller - Structure used to describe a NAND controller * * @lock: lock used to serialize accesses to the NAND controller * @ops: NAND controller operations. */ struct nand_controller { struct mutex lock; // 互斥锁,用来串行访问Nand Flash控制器 const struct nand_controller_ops *ops; // Nand Flash控制器操作集 };
3.1.4 struct nand_chip
nand_chip是一个比较重要的数据结构,MTD使用nand_chip来表示一个Nand Flash内部的芯片,该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。其定义在include/linux/mtd/rawnand.h:
/** * struct nand_chip - NAND Private Flash Chip Data * @base: Inherit from the generic NAND device * @legacy: All legacy fields/hooks. If you develop a new driver, * don't even try to use any of these fields/hooks, and if * you're modifying an existing driver that is using those * fields/hooks, you should consider reworking the driver * avoid using them. * @setup_read_retry: [FLASHSPECIFIC] flash (vendor) specific function for * setting the read-retry mode. Mostly needed for MLC NAND. * @ecc: [BOARDSPECIFIC] ECC control structure * @buf_align: minimum buffer alignment required by a platform * @oob_poi: "poison value buffer," used for laying out OOB data * before writing * @page_shift: [INTERN] number of address bits in a page (column * address bits). * @phys_erase_shift: [INTERN] number of address bits in a physical eraseblock * @bbt_erase_shift: [INTERN] number of address bits in a bbt entry * @chip_shift: [INTERN] number of address bits in one chip * @options: [BOARDSPECIFIC] various chip options. They can partly * be set to inform nand_scan about special functionality. * See the defines for further explanation. * @bbt_options: [INTERN] bad block specific options. All options used * here must come from bbm.h. By default, these options * will be copied to the appropriate nand_bbt_descr's. * @badblockpos: [INTERN] position of the bad block marker in the oob * area. * @badblockbits: [INTERN] minimum number of set bits in a good block's * bad block marker position; i.e., BBM == 11110111b is * not bad when badblockbits == 7 * @onfi_timing_mode_default: [INTERN] default ONFI timing mode. This field is * set to the actually used ONFI mode if the chip is * ONFI compliant or deduced from the datasheet if * the NAND chip is not ONFI compliant. * @pagemask: [INTERN] page number mask = number of (pages / chip) - 1 * @data_buf: [INTERN] buffer for data, size is (page size + oobsize). * @pagecache: Structure containing page cache related fields * @pagecache.bitflips: Number of bitflips of the cached page * @pagecache.page: Page number currently in the cache. -1 means no page is * currently cached * @subpagesize: [INTERN] holds the subpagesize * @id: [INTERN] holds NAND ID * @parameters: [INTERN] holds generic parameters under an easily * readable form. * @data_interface: [INTERN] NAND interface timing information * @cur_cs: currently selected target. -1 means no target selected, * otherwise we should always have cur_cs >= 0 && * cur_cs < nanddev_ntargets(). NAND Controller drivers * should not modify this value, but they're allowed to * read it. * @read_retries: [INTERN] the number of read retry modes supported * @lock: lock protecting the suspended field. Also used to * serialize accesses to the NAND device. * @suspended: set to 1 when the device is suspended, 0 when it's not. * @bbt: [INTERN] bad block table pointer * @bbt_td: [REPLACEABLE] bad block table descriptor for flash * lookup. * @bbt_md: [REPLACEABLE] bad block table mirror descriptor * @badblock_pattern: [REPLACEABLE] bad block scan pattern used for initial * bad block scan. * @controller: [REPLACEABLE] a pointer to a hardware controller * structure which is shared among multiple independent * devices. * @priv: [OPTIONAL] pointer to private chip data * @manufacturer: [INTERN] Contains manufacturer information * @manufacturer.desc: [INTERN] Contains manufacturer's description * @manufacturer.priv: [INTERN] Contains manufacturer private information */ struct nand_chip { struct nand_device base; // 可以看作mtd_info子类 struct nand_legacy legacy; // 硬件操作函数 int (*setup_read_retry)(struct nand_chip *chip, int retry_mode); unsigned int options; // 与具体的nand芯片相关的一些选项,如NAND_BUSWIDTH_16等 unsigned int bbt_options; int page_shift; // 用来表示nand芯片的page大小,如某nand芯片的一个page有512个字节,那么该值就是9 int phys_erase_shift; // 用来表示nand芯片每次可擦除的大小,如某nand芯片每次可擦除16kb(通常为一个block大小),那么该值就是14 int bbt_erase_shift; // 用来表示bad block table的大小,通常bbt占用一个block,所以该值通常和phys_erase_shift相同 int chip_shift; // 使用位表示nand芯片的容量 int pagemask; // nand总容量/每页字节数 - 1 得到页掩码 u8 *data_buf; struct { unsigned int bitflips; int page; } pagecache; int subpagesize; int onfi_timing_mode_default; unsigned int badblockpos; int badblockbits; struct nand_id id; // 保存从nand读取到的设备id信息,包含厂家ID、设备ID等 struct nand_parameters parameters; struct nand_data_interface data_interface; int cur_cs; // 当前选中的目标 int read_retries; struct mutex lock; unsigned int suspended : 1; uint8_t *oob_poi; struct nand_controller *controller; // nand controller struct nand_ecc_ctrl ecc; // ecc校验结构体,里面有大量函数进行ecc校验 unsigned long buf_align; uint8_t *bbt; struct nand_bbt_descr *bbt_td; struct nand_bbt_descr *bbt_md; struct nand_bbt_descr *badblock_pattern; void *priv; struct { const struct nand_manufacturer *desc; void *priv; } manufacturer; // 厂家ID信息 };
3.1.5 struct nand_legacy
nand_legacy该结构体就是保存与Nand Flash芯片硬件控制相关的函数:
/** * struct nand_legacy - NAND chip legacy fields/hooks * @IO_ADDR_R: address to read the 8 I/O lines of the flash device * @IO_ADDR_W: address to write the 8 I/O lines of the flash device * @select_chip: select/deselect a specific target/die * @read_byte: read one byte from the chip * @write_byte: write a single byte to the chip on the low 8 I/O lines * @write_buf: write data from the buffer to the chip * @read_buf: read data from the chip into the buffer * @cmd_ctrl: hardware specific function for controlling ALE/CLE/nCE. Also used * to write command and address * @cmdfunc: hardware specific function for writing commands to the chip. * @dev_ready: hardware specific function for accessing device ready/busy line. * If set to NULL no access to ready/busy is available and the * ready/busy information is read from the chip status register. * @waitfunc: hardware specific function for wait on ready. * @block_bad: check if a block is bad, using OOB markers * @block_markbad: mark a block bad * @set_features: set the NAND chip features * @get_features: get the NAND chip features * @chip_delay: chip dependent delay for transferring data from array to read * regs (tR). * @dummy_controller: dummy controller implementation for drivers that can * only control a single chip * * If you look at this structure you're already wrong. These fields/hooks are * all deprecated. */ struct nand_legacy { void __iomem *IO_ADDR_R; // 设置为数据寄存器地址 NFDATA void __iomem *IO_ADDR_W; // 设置为数据今存其地址 NFDATA void (*select_chip)(struct nand_chip *chip, int cs); // 片选/取消片选 u8 (*read_byte)(struct nand_chip *chip); // 读取一个字节数据 void (*write_byte)(struct nand_chip *chip, u8 byte); // 写入一个字节数据 void (*write_buf)(struct nand_chip *chip, const u8 *buf, int len); // 写入len个长度字节 void (*read_buf)(struct nand_chip *chip, u8 *buf, int len); // 读取len个长度字节 void (*cmd_ctrl)(struct nand_chip *chip, int dat, unsigned int ctrl); // 写命令/地址 void (*cmdfunc)(struct nand_chip *chip, unsigned command, int column, // 发送写数据命令 传入列地址、页地址 int page_addr); int (*dev_ready)(struct nand_chip *chip); // 获取nand状态 繁忙/就绪 int (*waitfunc)(struct nand_chip *chip); // 等待nand就绪 int (*block_bad)(struct nand_chip *chip, loff_t ofs); // 检测是否有坏块 int (*block_markbad)(struct nand_chip *chip, loff_t ofs); // 标记坏块 int (*set_features)(struct nand_chip *chip, int feature_addr, u8 *subfeature_para); int (*get_features)(struct nand_chip *chip, int feature_addr, u8 *subfeature_para); int chip_delay; // 延迟时间 struct nand_controller dummy_controller; };
3.1.6 struct nand_ecc_ctrl
/** * struct nand_ecc_ctrl - Control structure for ECC * @mode: ECC mode * @algo: ECC algorithm * @steps: number of ECC steps per page * @size: data bytes per ECC step * @bytes: ECC bytes per step * @strength: max number of correctible bits per ECC step * @total: total number of ECC bytes per page * @prepad: padding information for syndrome based ECC generators * @postpad: padding information for syndrome based ECC generators * @options: ECC specific options (see NAND_ECC_XXX flags defined above) * @priv: pointer to private ECC control data * @calc_buf: buffer for calculated ECC, size is oobsize. * @code_buf: buffer for ECC read from flash, size is oobsize. * @hwctl: function to control hardware ECC generator. Must only * be provided if an hardware ECC is available * @calculate: function for ECC calculation or readback from ECC hardware * @correct: function for ECC correction, matching to ECC generator (sw/hw). * Should return a positive number representing the number of * corrected bitflips, -EBADMSG if the number of bitflips exceed * ECC strength, or any other error code if the error is not * directly related to correction. * If -EBADMSG is returned the input buffers should be left * untouched. * @read_page_raw: function to read a raw page without ECC. This function * should hide the specific layout used by the ECC * controller and always return contiguous in-band and * out-of-band data even if they're not stored * contiguously on the NAND chip (e.g. * NAND_ECC_HW_SYNDROME interleaves in-band and * out-of-band data). * @write_page_raw: function to write a raw page without ECC. This function * should hide the specific layout used by the ECC * controller and consider the passed data as contiguous * in-band and out-of-band data. ECC controller is * responsible for doing the appropriate transformations * to adapt to its specific layout (e.g. * NAND_ECC_HW_SYNDROME interleaves in-band and * out-of-band data). * @read_page: function to read a page according to the ECC generator * requirements; returns maximum number of bitflips corrected in * any single ECC step, -EIO hw error * @read_subpage: function to read parts of the page covered by ECC; * returns same as read_page() * @write_subpage: function to write parts of the page covered by ECC. * @write_page: function to write a page according to the ECC generator * requirements. * @write_oob_raw: function to write chip OOB data without ECC * @read_oob_raw: function to read chip OOB data without ECC * @read_oob: function to read chip OOB data * @write_oob: function to write chip OOB data */ struct nand_ecc_ctrl { nand_ecc_modes_t mode; enum nand_ecc_algo algo; int steps; int size; int bytes; int total; int strength; int prepad; int postpad; unsigned int options; void *priv; u8 *calc_buf; u8 *code_buf; void (*hwctl)(struct nand_chip *chip, int mode); int (*calculate)(struct nand_chip *chip, const uint8_t *dat, uint8_t *ecc_code); int (*correct)(struct nand_chip *chip, uint8_t *dat, uint8_t *read_ecc, uint8_t *calc_ecc); int (*read_page_raw)(struct nand_chip *chip, uint8_t *buf, int oob_required, int page); int (*write_page_raw)(struct nand_chip *chip, const uint8_t *buf, int oob_required, int page); int (*read_page)(struct nand_chip *chip, uint8_t *buf, int oob_required, int page); int (*read_subpage)(struct nand_chip *chip, uint32_t offs, uint32_t len, uint8_t *buf, int page); int (*write_subpage)(struct nand_chip *chip, uint32_t offset, uint32_t data_len, const uint8_t *data_buf, int oob_required, int page); int (*write_page)(struct nand_chip *chip, const uint8_t *buf, int oob_required, int page); int (*write_oob_raw)(struct nand_chip *chip, int page); int (*read_oob_raw)(struct nand_chip *chip, int page); int (*read_oob)(struct nand_chip *chip, int page); int (*write_oob)(struct nand_chip *chip, int page); };
3.1.7 nstruct and_manufacturer
nand_manufacturer保存生产厂家信息,定义在drivers/mtd/nand/raw/internals.h:
/* * NAND Flash Manufacturer ID Codes */ #define NAND_MFR_AMD 0x01 #define NAND_MFR_ATO 0x9b #define NAND_MFR_EON 0x92 #define NAND_MFR_ESMT 0xc8 #define NAND_MFR_FUJITSU 0x04 #define NAND_MFR_HYNIX 0xad #define NAND_MFR_INTEL 0x89 #define NAND_MFR_MACRONIX 0xc2 #define NAND_MFR_MICRON 0x2c #define NAND_MFR_NATIONAL 0x8f #define NAND_MFR_RENESAS 0x07 #define NAND_MFR_SAMSUNG 0xec // 三星厂家 #define NAND_MFR_SANDISK 0x45 #define NAND_MFR_STMICRO 0x20 #define NAND_MFR_TOSHIBA 0x98 #define NAND_MFR_WINBOND 0xef /** * struct nand_manufacturer_ops - NAND Manufacturer operations * @detect: detect the NAND memory organization and capabilities * @init: initialize all vendor specific fields (like the ->read_retry() * implementation) if any. * @cleanup: the ->init() function may have allocated resources, ->cleanup() * is here to let vendor specific code release those resources. * @fixup_onfi_param_page: apply vendor specific fixups to the ONFI parameter * page. This is called after the checksum is verified. */ struct nand_manufacturer_ops { void (*detect)(struct nand_chip *chip); int (*init)(struct nand_chip *chip); void (*cleanup)(struct nand_chip *chip); void (*fixup_onfi_param_page)(struct nand_chip *chip, struct nand_onfi_params *p); }; /** * struct nand_manufacturer - NAND Flash Manufacturer structure * @name: Manufacturer name * @id: manufacturer ID code of device. * @ops: manufacturer operations */ struct nand_manufacturer { int id; // 厂家ID char *name; // 厂家名字 const struct nand_manufacturer_ops *ops; // 操作函数 };
3.1.8 struct nand_memory_organization
nand_memory_organization存储的是Nand Flash内存模型,定义在include/linux/mtd/nand.h文件中:
/** * struct nand_memory_organization - Memory organization structure * @bits_per_cell: number of bits per NAND cell * @pagesize: page size * @oobsize: OOB area size * @pages_per_eraseblock: number of pages per eraseblock * @eraseblocks_per_lun: number of eraseblocks per LUN (Logical Unit Number) * @max_bad_eraseblocks_per_lun: maximum number of eraseblocks per LUN * @planes_per_lun: number of planes per LUN * @luns_per_target: number of LUN per target (target is a synonym for die) * @ntargets: total number of targets exposed by the NAND device */ struct nand_memory_organization { unsigned int bits_per_cell; // 每个内存单元包含多少位 unsigned int pagesize; // 页大小 unsigned int oobsize; // oob大小 unsigned int pages_per_eraseblock; // 每个Block包含多少页 unsigned int eraseblocks_per_lun; // 每个LUN包含多少Block unsigned int max_bad_eraseblocks_per_lun; unsigned int planes_per_lun; // 每个LUN包含多少个plane unsigned int luns_per_target; // 1 unsigned int ntargets; // target和die、LUN、chip是同义词,Nand Flash内部包含多少个target };
3.2 入口和出口函数
我们可以在该文件定位到驱动模块的入口和出口:
module_platform_driver(s3c24xx_nand_driver);
module_platform_driver宏展开后本质上就是:
module_init(s3c24xx_nand_driver_init); module_exit(s3c24xx_nand_driver_exit); static int __init s3c24xx_nand_driver_init(void) { platform_driver_register(s3c24xx_nand_driver); } static void __exit s3c24xx_nand_driver_exit(void) { platform_driver_unregister(s3c24xx_nand_driver); }
看到这里是不是有点意外,这里是通过platform_driver_register函数注册了一个platform驱动。
在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是s3c24xx_nand_probe函数。
static struct platform_driver s3c24xx_nand_driver = { .probe = s3c24xx_nand_probe, .remove = s3c24xx_nand_remove, .suspend = s3c24xx_nand_suspend, .resume = s3c24xx_nand_resume, .id_table = s3c24xx_driver_ids, .driver = { .name = "s3c24xx-nand", .of_match_table = s3c24xx_nand_dt_ids, }, };
3.2.1 s3c24xx_driver_ids
由于platform设备和驱动里的nand并不一样,这里实际上是通过id_table匹配成功:
/* driver device registration */ static const struct platform_device_id s3c24xx_driver_ids[] = { { .name = "s3c2410-nand", .driver_data = TYPE_S3C2410, }, { .name = "s3c2440-nand", .driver_data = TYPE_S3C2440, }, { .name = "s3c2412-nand", .driver_data = TYPE_S3C2412, }, { .name = "s3c6400-nand", .driver_data = TYPE_S3C2412, /* compatible with 2412 */ }, { } };
3.2.2 platform_match_id
id_table匹配函数为platform_match_id:
static const struct platform_device_id *platform_match_id( const struct platform_device_id *id, struct platform_device *pdev) { while (id->name[0]) { if (strcmp(pdev->name, id->name) == 0) { pdev->id_entry = id; return id; } id++; } return NULL; }
3.3 s3c24xx_nand_probe
/* s3c24xx_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) { struct s3c2410_platform_nand *plat; 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; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); // 动态申请内存空间,内存大小为sizeof(*info),并赋值给info,该内存随着pdev->dev设备的卸载而由系统释放 if (info == NULL) { err = -ENOMEM; goto exit_error; } platform_set_drvdata(pdev, info); // pdev->dev.driver_data=info nand_controller_init(&info->controller); // 初始化互斥锁 info->controller.lock info->controller.ops = &s3c24xx_nand_controller_ops; /* get the clock source and enable it */ info->clk = devm_clk_get(&pdev->dev, "nand"); // 获取nand时钟,并赋值给info->clk GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0), CLKCON寄存器bit[4],控制进入Nand FLash控制器模块的HCLK if (IS_ERR(info->clk)) { dev_err(&pdev->dev, "failed to get clock\n"); err = -ENOENT; goto exit_error; } s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); // nand时钟使能 if (pdev->dev.of_node) // 关联的设备树节点 err = s3c24xx_nand_probe_dt(pdev); else err = s3c24xx_nand_probe_pdata(pdev); if (err) goto exit_error; plat = to_nand_plat(pdev); // struct plfatform_device转为struct s3c2410_platform_nand /* allocate and map the resource */ /* currently we assume we have the one resource */ res = pdev->resource; // 获取platform_device I/O内存资源 size = resource_size(res); // 1MB info->device = &pdev->dev; // 初始化info->device info->platform = plat; // 初始化info->platform info->regs = devm_ioremap_resource(&pdev->dev, res); // 将nand flash控制器寄存器基地址映射到虚地址空间,并赋值给info->regs if (IS_ERR(info->regs)) { err = PTR_ERR(info->regs); goto exit_error; } dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs); // 输出nand flash控制器寄存器基址在虚拟内存的地址 if (!plat->sets || plat->nr_sets < 1) { // nand set不存在 err = -EINVAL; goto exit_error; } sets = plat->sets; // 获取nand set数组指针 nr_sets = plat->nr_sets; // 获取nand set数组长度 info->mtd_count = nr_sets; // 设置nand set数组长度 /* allocate our information */ size = nr_sets * sizeof(*info->mtds); // 计算nand set数组占用内存大小 info->mtds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); // 分配新的内存,内存大小为size,并赋值给info->mtds,该内存随着pdev->dev设备的卸载而由系统释放 if (info->mtds == NULL) { err = -ENOMEM; goto exit_error; } /* initialise all possible chips */ nmtd = info->mtds; for (setno = 0; setno < nr_sets; setno++, nmtd++, sets++) { // 遍历nand set数组,并使用sets(数组指针)初始化nmtd(数组指针) struct mtd_info *mtd = nand_to_mtd(&nmtd->chip); // 获取MTD原始设备 pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info); mtd->dev.parent = &pdev->dev; s3c2410_nand_init_chip(info, nmtd, sets); // 初始化芯片 err = nand_scan(&nmtd->chip, sets ? sets->nr_chips : 1); // 扫描nand if (err) goto exit_error; s3c2410_nand_add_partition(info, nmtd, sets); // 新增mtd分区 } /* initialise the hardware */ err = s3c2410_nand_inithw(info); // 初始化nand flash控制器,设置TACLS、TWRPH0、TWRPH1时序参数 if (err != 0) goto exit_error; 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_suspend(info)) { // 0 dev_info(&pdev->dev, "clock idle support enabled\n"); s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); // 时钟挂起 } return 0; exit_error: s3c24xx_nand_remove(pdev); if (err == 0) err = -EINVAL; return err; }
这段代码是在太长了,我直接挑重点说:
- 分配一个s3c2410_nand_info结构体变量info;
- 设置info:
- 获取nand时钟,设置info->clk;并nand时钟使能;
- 初始化成员info->device、info->platform;
- 获取nand flash控制器寄存器基地址映射到虚地址空间的地址,设置info->regs;
- 设置info->mtd_count,info->mtds;
- 遍历nand set数组:
- 调用s3c2410_nand_init_chip(info, nmtd, sets)初始化芯片,主要就是初始化nmtd->chip成员,设置硬件操作函数(读数据、写数据、写命令/地址、片选等);
- 调用nand_scan(&nmtd->chip, sets ? sets->nr_chips : 1)扫描nand,该函数进行了以下操作:
- nand_scan_ident:函数获取nand ID信息,然后判断该类型的nand芯片内核是否支持,如果支持的话获取芯片存储的出厂信息,然后初始化chip->base.mtd(成员writesize、oobsize、erasesize等)、chip->base.memorg(成员bits_per_cell、pagesize、oobsize、pages_per_eraseblock、planes_per_lun、luns_per_target、ntatgets等)、chip->options、chip->base.eccreq;
- nand_attach:如果定义了chip->controller->ops->attach_chip函数,执行chip->controller->ops->attach_chip(chip),最终执行了s3c2410_nand_attach_chip(chip函数);该函数主要是初始化ECC engine,即初始化chip->ecc各个成员;
- nand_scan_tail:使用默认函数填满了所有未初始化函数指针,并扫描错误的块表;
- 调用s3c2410_nand_add_partition(info, nmtd, sets)进行MTD设备注册;
- 设置nand flash控制器时序参数:通过s3c2410_nand_inithw设置NFCONT、NFCONF寄存器;
由于s3c24xx_nand_probe比较复杂,所以单独一个小节分析这一块代码,如果对这块代码不关系,看到这里就可以了。
四、s3c24xx_nand_probe
4.1 nand_to_mtd
nand_to_mtd定义在include/linux/mtd/rawnand.h:
static inline struct mtd_info *nand_to_mtd(struct nand_chip *chip) { return &chip->base.mtd; }
4.2 s3c2410_nand_clk_set_state
s3c2410_nand_clk_set_state定义在drivers/mtd/nand/raw/s3c2410.c,用来设置nand时钟状态:
/** * s3c2410_nand_clk_set_state - Enable, disable or suspend NAND clock. * @info: The controller instance. * @new_state: State to which clock should be set. */ static void s3c2410_nand_clk_set_state(struct s3c2410_nand_info *info, enum s3c_nand_clk_state new_state) { if (!allow_clk_suspend(info) && new_state == CLOCK_SUSPEND) // 如果不允许时钟挂起,并且设置new_state == CLOCK_SUSPEND直接返回 return; if (info->clk_state == CLOCK_ENABLE) { // 时钟已经使能 if (new_state != CLOCK_ENABLE) // 设置为其他 clk_disable_unprepare(info->clk); // 禁止时钟 } else { if (new_state == CLOCK_ENABLE) // 使能时钟 clk_prepare_enable(info->clk); } info->clk_state = new_state; }
4.3 s3c2410_nand_init_chip
s3c2410_nand_init_chip函数用来初始化nmtd->chip成员变量:
/** * s3c2410_nand_init_chip - initialise a single instance of an chip * @info: The base NAND controller the chip is on. * @nmtd: The new controller MTD instance to fill in. * @set: The information passed from the board specific platform data. * * Initialise the given @nmtd from the information in @info and @set. This * readies the structure for use with the MTD layer functions by ensuring * all pointers are setup and the necessary control routines selected. */ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, struct s3c2410_nand_mtd *nmtd, struct s3c2410_nand_set *set) { struct device_node *np = info->device->of_node; struct nand_chip *chip = &nmtd->chip; void __iomem *regs = info->regs; // 获取nand flash控制器寄存器基址 nand_set_flash_node(chip, set->of_node);
// 设置nand chip硬件操作函数 chip->legacy.write_buf = s3c2410_nand_write_buf; chip->legacy.read_buf = s3c2410_nand_read_buf; chip->legacy.select_chip = s3c2410_nand_select_chip; chip->legacy.chip_delay = 50; nand_set_controller_data(chip, nmtd); chip->options = set->options; chip->controller = &info->controller; /* * let's keep behavior unchanged for legacy boards booting via pdata and * auto-detect timings only when booting with a device tree. */ if (!np) chip->options |= NAND_KEEP_TIMINGS; switch (info->cpu_type) { //CPU类型 case TYPE_S3C2410: // 0 默认这个?如果走了这里,很明显时有问题的 chip->legacy.IO_ADDR_W = regs + S3C2410_NFDATA; info->sel_reg = regs + S3C2410_NFCONF; // 设置选择的寄存器为配置寄存器 info->sel_bit = S3C2410_NFCONF_nFCE; // 1<<11 chip->legacy.cmd_ctrl = s3c2410_nand_hwcontrol; chip->legacy.dev_ready = s3c2410_nand_devready; break; case TYPE_S3C2440: // 2 chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA; // 设置为数据寄存器 info->sel_reg = regs + S3C2440_NFCONT; // 设置选择的寄存器为控制寄存器 info->sel_bit = S3C2440_NFCONT_nFCE; // 设置选择的位位bit[1] 1<< 1 片选位 chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol; chip->legacy.dev_ready = s3c2440_nand_devready; chip->legacy.read_buf = s3c2440_nand_read_buf; chip->legacy.write_buf = s3c2440_nand_write_buf; break; case TYPE_S3C2412: //1 chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA; info->sel_reg = regs + S3C2440_NFCONT; info->sel_bit = S3C2412_NFCONT_nFCE0; chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol; chip->legacy.dev_ready = s3c2412_nand_devready; if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT) dev_info(info->device, "System booted from NAND\n"); break; } chip->legacy.IO_ADDR_R = chip->legacy.IO_ADDR_W; // 设置位数据寄存器 nmtd->info = info; nmtd->set = set; chip->ecc.mode = info->platform->ecc_mode; // 设置ecc mode /* * If you use u-boot BBT creation code, specifying this flag will * let the kernel fish out the BBT from the NAND. */ if (set->flash_bbt) chip->bbt_options |= NAND_BBT_USE_FLASH; }
这里我们关注一下CPU类型位TYPE_S3C4440时的nand芯片硬件操作相关的函数,这些函数均定义在drivers/mtd/nand/raw/s3c2410.c。
4.3.1 s3c2410_nand_select_chip
s3c2410_nand_select_chip函数用于使能/禁止片选,即配置NFCONT bit[1]为0,如果chip=-1,则禁止片选,否则使能片选。
/** * s3c2410_nand_select_chip - select the given nand chip * @this: NAND chip object. * @chip: The chip number. * * This is called by the MTD layer to either select a given chip for the * @mtd instance, or to indicate that the access has finished and the * chip can be de-selected. * * The routine ensures that the nFCE line is correctly setup, and any * platform specific selection code is called to route nFCE to the specific * chip. */ static void s3c2410_nand_select_chip(struct nand_chip *this, int chip) { struct s3c2410_nand_info *info; struct s3c2410_nand_mtd *nmtd; unsigned long cur; nmtd = nand_get_controller_data(this); info = nmtd->info; if (chip != -1) s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); // nand时钟使能 cur = readl(info->sel_reg); // 读取配置寄存器NFCONT if (chip == -1) { // chip = -1,取消片选 cur |= info->sel_bit; // | 1<<1 } else { // 使能片选 if (nmtd->set != NULL && chip > nmtd->set->nr_chips) { // chip编号无效 dev_err(info->device, "invalid chip %d\n", chip); return; } if (info->platform != NULL) { if (info->platform->select_chip != NULL) (info->platform->select_chip) (nmtd->set, chip); } cur &= ~info->sel_bit; // NFCONT寄存器片选位设置位0,使能片选 } writel(cur, info->sel_reg); // 更新NFCONT寄存器值,使能/禁止片选 if (chip == -1) s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); // nand时钟挂起 }
4.3.2 s3c2440_nand_hwcontrol
s3c2440_nand_hwcontrol函数用于向nand芯片发送命令/地址,第三个参数用来区分是发送的是命令还是地址。
/* command and control functions */ static void s3c2440_nand_hwcontrol(struct nand_chip *chip, int cmd, unsigned int ctrl) { struct mtd_info *mtd = nand_to_mtd(chip); struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); if (cmd == NAND_CMD_NONE) return; if (ctrl & NAND_CLE) writeb(cmd, info->regs + S3C2440_NFCMD); // 直接将cmd写入命令寄存器 else writeb(cmd, info->regs + S3C2440_NFADDR); // 直接将cmd写入地址寄存器 }
4.3.3 s3c2440_nand_devready
s3c2410_nand_devready函数用于获取nand的状态,0表示繁忙,1表示就绪:
static int s3c2440_nand_devready(struct nand_chip *chip) { struct mtd_info *mtd = nand_to_mtd(chip); struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY; //读取NFSTAT寄存器,判断bit[0]是否为1 0 繁忙 1就绪 }
4.3.4 s3c2440_nand_read_buf
s3c2440_nand_read_buf函数用于从nand读取len个长度字节,并保存到buf缓冲区中:
static void s3c2440_nand_read_buf(struct nand_chip *this, u_char *buf, int len) { struct mtd_info *mtd = nand_to_mtd(this); struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); readsl(info->regs + S3C2440_NFDATA, buf, len >> 2); // 读取NFDATA寄存器的值,读取长度位len >> 2,按字访问 /* cleanup if we've got less than a word to do */ if (len & 3) { // 处理长度非4整数倍情况 buf += len & ~3; for (; len & 3; len--) *buf++ = readb(info->regs + S3C2440_NFDATA); // 按字节读取 } }
4.3.5 s3c2440_nand_write_buf
s3c2440_nand_write_buf函数用于将缓冲区buf中len个长度字节写入到nand:
static void s3c2440_nand_write_buf(struct nand_chip *this, const u_char *buf, int len) { struct mtd_info *mtd = nand_to_mtd(this); struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); writesl(info->regs + S3C2440_NFDATA, buf, len >> 2); //写入NFDATA寄存器,写入长度为len >> 2,按字写入 /* cleanup any fractional write */ if (len & 3) { // 处理长度非4整数倍情况 buf += len & ~3; for (; len & 3; len--, buf++) // 按字节写入 writeb(*buf, info->regs + S3C2440_NFDATA); } }
4.3.6 s3c2410_nand_attach_chip
s3c2410_nand_attach_chip函数用于初始化ECC engine:
/** * s3c2410_nand_attach_chip - Init the ECC engine after NAND scan * @chip: The NAND chip * * This hook is called by the core after the identification of the NAND chip, * once the relevant per-chip information is up to date.. This call ensure that * we update the internal state accordingly. * * The internal state is currently limited to the ECC state information. */ static int s3c2410_nand_attach_chip(struct nand_chip *chip) { struct mtd_info *mtd = nand_to_mtd(chip); struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); switch (chip->ecc.mode) { // 获取ecc模式 case NAND_ECC_NONE: // 关闭ECC dev_info(info->device, "ECC disabled\n"); break; case NAND_ECC_SOFT: // 软件ECC /* * This driver expects Hamming based ECC when ecc_mode is set * to NAND_ECC_SOFT. Force ecc.algo to NAND_ECC_HAMMING to * avoid adding an extra ecc_algo field to * s3c2410_platform_nand. */ chip->ecc.algo = NAND_ECC_HAMMING; dev_info(info->device, "soft ECC\n"); break; case NAND_ECC_HW: // 硬件ECC chip->ecc.calculate = s3c2410_nand_calculate_ecc; chip->ecc.correct = s3c2410_nand_correct_data; chip->ecc.strength = 1; 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: chip->ecc.hwctl = s3c2412_nand_enable_hwecc; chip->ecc.calculate = s3c2412_nand_calculate_ecc; break; case TYPE_S3C2440: chip->ecc.hwctl = s3c2440_nand_enable_hwecc; chip->ecc.calculate = s3c2440_nand_calculate_ecc; break; } dev_dbg(info->device, "chip %p => page shift %d\n", chip, chip->page_shift); /* change the behaviour depending on whether we are using * the large or small page nand device */ if (chip->page_shift > 10) { chip->ecc.size = 256; chip->ecc.bytes = 3; } else { chip->ecc.size = 512; chip->ecc.bytes = 3; mtd_set_ooblayout(nand_to_mtd(chip), &s3c2410_ooblayout_ops); } dev_info(info->device, "hardware ECC\n"); break; default: dev_err(info->device, "invalid ECC mode!\n"); return -EINVAL; } if (chip->bbt_options & NAND_BBT_USE_FLASH) chip->options |= NAND_SKIP_BBTSCAN; return 0; }
4.4 nand_scan
nand_scan函数位于include/linux/mtd/rawnand.h:
static inline int nand_scan(struct nand_chip *chip, unsigned int max_chips) // max_chips等于nand set数组长度 { return nand_scan_with_ids(chip, max_chips, NULL); }
4.4.1 nand_scan_with_ids
nand_scan_with_ids函数通过名字我们大致就可以了解到其主要进行Nand Flash型号的识别工作,匹配成功后,进行chip必要参数初始化,定义在drivers/mtd/nand/raw/nand_base.c:
/** * nand_scan_with_ids - [NAND Interface] Scan for the NAND device * @chip: NAND chip object * @maxchips: number of chips to scan for. * @ids: optional flash IDs table * * This fills out all the uninitialized function pointers with the defaults. * The flash ID is read and the mtd/chip structures are filled with the * appropriate values. */ int nand_scan_with_ids(struct nand_chip *chip, unsigned int maxchips, struct nand_flash_dev *ids) { int ret; if (!maxchips) return -EINVAL; ret = nand_scan_ident(chip, maxchips, ids); // nand_scan第一阶段 这个函数比较复杂,识别Nand Flash芯片,并进行chip必要参数初始化,比如厂商ID、设备ID、页大小、块大小、每块页数、容量等等 if (ret) return ret; ret = nand_attach(chip); // 初始化chip->ecc各个成员 if (ret) goto cleanup_ident; ret = nand_scan_tail(chip); // nand_scan第二阶段,使用默认函数填满了所有未初始化函数指针,并扫描错误的块表 if (ret) goto detach_chip; return 0; detach_chip: nand_detach(chip); cleanup_ident: nand_scan_ident_cleanup(chip); return ret; }
4.4.2 nand_scan_ident
nand_scan_ident定义在drivers/mtd/nand/raw/nand_base.c:
/** * nand_scan_ident - Scan for the NAND device * @chip: NAND chip object * @maxchips: number of chips to scan for * @table: alternative NAND ID table * * This is the first phase of the normal nand_scan() function. It reads the * flash ID and sets up MTD fields accordingly. * * This helper used to be called directly from controller drivers that needed * to tweak some ECC-related parameters before nand_scan_tail(). This separation * prevented dynamic allocations during this phase which was unconvenient and * as been banned for the benefit of the ->init_ecc()/cleanup_ecc() hooks. */ static int nand_scan_ident(struct nand_chip *chip, unsigned int maxchips, struct nand_flash_dev *table) { struct mtd_info *mtd = nand_to_mtd(chip); struct nand_memory_organization *memorg; int nand_maf_id, nand_dev_id; unsigned int i; int ret; memorg = nanddev_get_memorg(&chip->base); // 获取chip内存模型,有关page_size、oob_size等信息,这个memory会在下面补充填充 /* Assume all dies are deselected when we enter nand_scan_ident(). */ chip->cur_cs = -1; mutex_init(&chip->lock); // 互斥锁 /* Enforce the right timings for reset/detection */ onfi_fill_data_interface(chip, NAND_SDR_IFACE, 0); ret = nand_dt_init(chip); // 初始化chip成员chip->ecc、chip->options、chip->bbt_options, 设备树要怎么写就看这个函数 if (ret) return ret; if (!mtd->name && mtd->dev.parent) mtd->name = dev_name(mtd->dev.parent); /* Set the default functions */ nand_set_defaults(chip); ret = nand_legacy_check_hooks(chip); // 检查是否设置了chip->legacy.cmdfunc、chip->legacy.select_chip、chip->legacy.cmd_ctrl等函数 if (ret) return ret; memorg->ntargets = maxchips; /* Read the flash type */ ret = nand_detect(chip, table); // 获取nand ID信息,然后查表判断该类型的nand芯片是否支持,如果支持的话获取芯片内存存储的出厂信息,然后初始化chip->base.mtd、chip->base.memorg成员 if (ret) { if (!(chip->options & NAND_SCAN_SILENT_NODEV)) pr_warn("No NAND device found\n"); nand_deselect_target(chip); return ret; } nand_maf_id = chip->id.data[0]; // 厂商ID nand_dev_id = chip->id.data[1]; // 设备ID nand_deselect_target(chip); // 如果定义了chip->legacy.select_chip,执行该函数chip->legacy.select_chip(chip,-1),即禁止片选(配置NFCONT bit[1]=1) /* Check for a chip array */ for (i = 1; i < maxchips; i++) { // Nand Flash内部存在多个chip情景 u8 id[2]; /* See comment in nand_get_flash_type for reset */ ret = nand_reset(chip, i); if (ret) break; nand_select_target(chip, i); /* Send the command for reading device ID */ ret = nand_readid_op(chip, 0, id, sizeof(id)); if (ret) break; /* Read manufacturer and device IDs */ if (nand_maf_id != id[0] || nand_dev_id != id[1]) { nand_deselect_target(chip); break; } nand_deselect_target(chip); } if (i > 1) pr_info("%d chips detected\n", i); /* Store the number of chips and calc total size for mtd */ memorg->ntargets = i; // 设置Nand Flash内部chip个数 mtd->size = i * nanddev_target_size(&chip->base); // return 0; }
4.4.3 nand_detect
nand_detect函数获取nand ID信息,然后判断该类型的nand芯片内核是否支持,如果支持的话获取芯片存储的出厂信息,然后初始化chip->base.mtd(成员writesize、oobsize、erasesize等)、chip->base.memorg(成员bits_per_cell、pagesize、oobsize、pages_per_eraseblock、planes_per_lun、luns_per_target、ntatgets等)、chip->options、chip->base.eccreq;定义在drivers/mtd/nand/raw/nand_base.c:
/* * Get the flash and manufacturer id and lookup if the type is supported. */ static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type) { const struct nand_manufacturer *manufacturer; // 用于保存nand生产厂商信息 struct mtd_info *mtd = nand_to_mtd(chip); // 获取chip->base.mtd struct nand_memory_organization *memorg; int busw, ret; u8 *id_data = chip->id.data; // 获取nand id数组指针 u8 maf_id, dev_id; u64 targetsize; /* * Let's start by initializing memorg fields that might be left * unassigned by the ID-based detection logic. */ memorg = nanddev_get_memorg(&chip->base); // chip->base.memorg memorg->planes_per_lun = 1; memorg->luns_per_target = 1; /* * Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx) * after power-up. */ ret = nand_reset(chip, 0); // 不用关系 忽略 if (ret) return ret; /* Select the device */ nand_select_target(chip, 0); // 使能片选 /* Send the command for reading device ID */ ret = nand_readid_op(chip, 0, id_data, 2); // 进行读取nand id操作,调用chip->legacy.cmdfunc函数:发送命令NAND_CMD_READID(宏的值位0x90),发送地址0x00(第二个参数),并读取两个2字节 if (ret) return ret; /* Read manufacturer and device IDs */ maf_id = id_data[0]; // 厂家ID dev_id = id_data[1]; // 设备ID /* * Try again to make sure, as some systems the bus-hold or other * interface concerns can cause random data which looks like a * possibly credible NAND flash to appear. If the two results do * not match, ignore the device completely. */ /* Read entire ID string */ ret = nand_readid_op(chip, 0, id_data, sizeof(chip->id.data)); // 再次读取,这次读取8个字节,确保两次读取的一致 if (ret) return ret; if (id_data[0] != maf_id || id_data[1] != dev_id) { // 两次读取结果不一致 pr_info("second ID read did not match %02x,%02x against %02x,%02x\n", maf_id, dev_id, id_data[0], id_data[1]); return -ENODEV; } chip->id.len = nand_id_len(id_data, ARRAY_SIZE(chip->id.data)); // 获取读取到的id长度,比如我们的ID只有5个字节,如果读取了8个字节,后三个字节会和前3个字节重复 /* Try to identify manufacturer */ manufacturer = nand_get_manufacturer(maf_id); // 根据厂家ID获取厂家信息,这里匹配{NAND_MFR_SAMSUNG, "Samsung", &samsung_nand_manuf_ops} chip->manufacturer.desc = manufacturer; if (!type) // 初始化type type = nand_flash_ids; /* * Save the NAND_BUSWIDTH_16 flag before letting auto-detection logic * override it. * This is required to make sure initial NAND bus width set by the * NAND controller driver is coherent with the real NAND bus width * (extracted by auto-detection code). */ busw = chip->options & NAND_BUSWIDTH_16; // 设置数据总线宽度为16位标志 /* * The flag is only set (never cleared), reset it to its default value * before starting auto-detection. */ chip->options &= ~NAND_BUSWIDTH_16; // 取消数据总线宽度16位标志 for (; type->name != NULL; type++) { if (is_full_id_nand(type)) { // 如果type指定了完整的id,通过判定type->id_len不为0,id_len表示type->id的有效长度 if (find_full_id_nand(chip, type)) // 比较type->id和chip->id.data数组前type->id_len字节是否相等,如果相等说明内核已经支持了该nand设备
// 1. 使用type初始化chip->base.mtd成员writesize、erasesize、oobsize等
// 2. 使用type初始化chip->base.memory成员pagesize、pages_per_eraseblock、oobsize、bit_per_cell、earseblocks_per_lun
// 3. 使用type初始化chip->options、chip->base.eccreq、chip->onfi_timing_mode_default、chip->parameters.model、 goto ident_done; } else if (dev_id == type->dev_id) { // 只匹配设备ID,实际上走的这里, 匹配了EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xF1, 128, LP_OPTIONS) break; } } if (!type->name || !type->pagesize) { // 由于type没有指定页大小,所以进入 /* Check if the chip is ONFI compliant */ ret = nand_onfi_detect(chip); // 检查是否符合ONFO标准,通过命令Read Parameter Page获取芯片内存存储的出厂信息如果支持的话,如果成功的话,执行了如下操作,并返回1
// 1.初始化chip->base.memory成员pagesize、pages_per_eraseblock、oobsize、luns_per_target、planes_per_lun、bit_per_cell、earseblocks_per_lun
max_bad_eraseblocks_per_lun
// 2.初始化chip->base.mtd成员writesize、erasesize、oobsize等
// 3.初始化始化chip->options、chip->base.eccreq(成员strength、step_size)、chip->onfi_timing_mode_default、chip->parameters(成员model、
supports_set_get_features、get_feature_list、set_feature_list、onfi)
if (ret < 0) return ret; else if (ret) // 符合,直接退出 goto ident_done; /* Check if the chip is JEDEC compliant */ ret = nand_jedec_detect(chip); // 检查是否符合JEDEC标准,这个过程和nand_onfi_detect类似 这里应该也是不支持的,返回0 if (ret < 0) return ret; else if (ret) // 符合、直接退出 goto ident_done; } if (!type->name) return -ENODEV; chip->parameters.model = kstrdup(type->name, GFP_KERNEL); // 设置model,指向一个字符串,保存的是名称 if (!chip->parameters.model) // 内存申请失败 return -ENOMEM; if (!type->pagesize) // 由于type没有指定页大小,所以进入 nand_manufacturer_detect(chip); // 执行了chip->manufacturer.desc->ops->detect(chip)函数,即samsung_nand_decode_id(chip)函数,该函数会调用nand_decode_ext_id()
// 解析chip->id.data[2]或者chip->id.data[3]得到cell type、pagesize、oobsize、blocksize
初始化chip->base.memory成员bits_per_cell、pagesize、oobsize、pages_per_eraseblock
初始化chip->base.mtd成员writesize、oobsize、erasesize else nand_decode_id(chip, type); /* Get chip options */ chip->options |= type->options; memorg->eraseblocks_per_lun = // number of eraseblocks per LUN (Logical Unit Number) DIV_ROUND_DOWN_ULL((u64)type->chipsize << 20, memorg->pagesize * // page size memorg->pages_per_eraseblock); // number of pages per eraseblock ident_done: if (!mtd->name) // 设置MTD设备名称 mtd->name = chip->parameters.model; if (chip->options & NAND_BUSWIDTH_AUTO) { WARN_ON(busw & NAND_BUSWIDTH_16); nand_set_defaults(chip); } else if (busw != (chip->options & NAND_BUSWIDTH_16)) { /* * Check, if buswidth is correct. Hardware drivers should set * chip correct! */ pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n", maf_id, dev_id); pr_info("%s %s\n", nand_manufacturer_name(manufacturer), mtd->name); pr_warn("bus width %d instead of %d bits\n", busw ? 16 : 8, (chip->options & NAND_BUSWIDTH_16) ? 16 : 8); ret = -EINVAL; goto free_detect_allocation; } nand_decode_bbm_options(chip); /* Calculate the address shift from the page size */ chip->page_shift = ffs(mtd->writesize) - 1; // 将页大小使用位表示 比如页2048,对应11 /* Convert chipsize to number of pages per chip -1 */ targetsize = nanddev_target_size(&chip->base); // chip->base.mtd.size nand设备总容量 chip->pagemask = (targetsize >> chip->page_shift) - 1; // nand总容量/每页字节数 - 1 得到页掩码 chip->bbt_erase_shift = chip->phys_erase_shift = // 将擦除单位大小使用位表示,比如16kb,对应14 ffs(mtd->erasesize) - 1; if (targetsize & 0xffffffff) chip->chip_shift = ffs((unsigned)targetsize) - 1; else { chip->chip_shift = ffs((unsigned)(targetsize >> 32)); chip->chip_shift += 32 - 1; } if (chip->chip_shift - chip->page_shift > 16) chip->options |= NAND_ROW_ADDR_3; chip->badblockbits = 8; nand_legacy_adjust_cmdfunc(chip); pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n", maf_id, dev_id); pr_info("%s %s\n", nand_manufacturer_name(manufacturer), chip->parameters.model); pr_info("%d MiB, %s, erase size: %d KiB, page size: %d, OOB size: %d\n", (int)(targetsize >> 20), nand_is_slc(chip) ? "SLC" : "MLC", mtd->erasesize >> 10, mtd->writesize, mtd->oobsize); return 0; free_detect_allocation: kfree(chip->parameters.model); return ret; }
内核所支持的nand都在drivers/mtd/nand/raw/nand_ids.c:
/* * The chip ID list: * name, device ID, page size, chip size in MiB, eraseblock size, options * * If page size and eraseblock size are 0, the sizes are taken from the * extended chip ID. */ struct nand_flash_dev nand_flash_ids[] = { /* * Some incompatible NAND chips share device ID's and so must be * listed by full ID. We list them first so that we can easily identify * the most specific match. */ {"TC58NVG0S3E 1G 3.3V 8-bit", { .id = {0x98, 0xd1, 0x90, 0x15, 0x76, 0x14, 0x01, 0x00} }, SZ_2K, SZ_128, SZ_128K, 0, 8, 64, NAND_ECC_INFO(1, SZ_512), 2 }, {"TC58NVG2S0F 4G 3.3V 8-bit", { .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x15, 0x01, 0x08} }, SZ_4K, SZ_512, SZ_256K, 0, 8, 224, NAND_ECC_INFO(4, SZ_512) }, {"TC58NVG2S0H 4G 3.3V 8-bit", { .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x16, 0x08, 0x00} }, SZ_4K, SZ_512, SZ_256K, 0, 8, 256, NAND_ECC_INFO(8, SZ_512) }, {"TC58NVG3S0F 8G 3.3V 8-bit", { .id = {0x98, 0xd3, 0x90, 0x26, 0x76, 0x15, 0x02, 0x08} }, SZ_4K, SZ_1K, SZ_256K, 0, 8, 232, NAND_ECC_INFO(4, SZ_512) }, {"TC58NVG5D2 32G 3.3V 8-bit", { .id = {0x98, 0xd7, 0x94, 0x32, 0x76, 0x56, 0x09, 0x00} }, SZ_8K, SZ_4K, SZ_1M, 0, 8, 640, NAND_ECC_INFO(40, SZ_1K) }, {"TC58NVG6D2 64G 3.3V 8-bit", { .id = {0x98, 0xde, 0x94, 0x82, 0x76, 0x56, 0x04, 0x20} }, SZ_8K, SZ_8K, SZ_2M, 0, 8, 640, NAND_ECC_INFO(40, SZ_1K) }, {"SDTNRGAMA 64G 3.3V 8-bit", { .id = {0x45, 0xde, 0x94, 0x93, 0x76, 0x50} }, SZ_16K, SZ_8K, SZ_4M, 0, 6, 1280, NAND_ECC_INFO(40, SZ_1K) }, {"H27UCG8T2ATR-BC 64G 3.3V 8-bit", { .id = {0xad, 0xde, 0x94, 0xda, 0x74, 0xc4} }, SZ_8K, SZ_8K, SZ_2M, NAND_NEED_SCRAMBLING, 6, 640, NAND_ECC_INFO(40, SZ_1K), 4 }, LEGACY_ID_NAND("NAND 4MiB 5V 8-bit", 0x6B, 4, SZ_8K, SP_OPTIONS), // 参数依次为name、设备ID、chip总容量(单位MB)、擦除单位(块大小)、选项 LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE3, 4, SZ_8K, SP_OPTIONS), LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE5, 4, SZ_8K, SP_OPTIONS), LEGACY_ID_NAND("NAND 8MiB 3,3V 8-bit", 0xD6, 8, SZ_8K, SP_OPTIONS), LEGACY_ID_NAND("NAND 8MiB 3,3V 8-bit", 0xE6, 8, SZ_8K, SP_OPTIONS), ...... LEGACY_ID_NAND("NAND 256MiB 3,3V 8-bit", 0x71, 256, SZ_16K, SP_OPTIONS), /* * These are the new chips with large page size. Their page size and * eraseblock size are determined from the extended ID bytes. */ /* 512 Megabit */ EXTENDED_ID_NAND("NAND 64MiB 1,8V 8-bit", 0xA2, 64, LP_OPTIONS), // 参数依次为name、设备ID、chip总容量(单位MB)、选项 这里没有指定页大小、块大小等信息,所以需要读取额外的ID信息来获取 EXTENDED_ID_NAND("NAND 64MiB 1,8V 8-bit", 0xA0, 64, LP_OPTIONS), EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xF2, 64, LP_OPTIONS), EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xD0, 64, LP_OPTIONS), EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xF0, 64, LP_OPTIONS), EXTENDED_ID_NAND("NAND 64MiB 1,8V 16-bit", 0xB2, 64, LP_OPTIONS16), EXTENDED_ID_NAND("NAND 64MiB 1,8V 16-bit", 0xB0, 64, LP_OPTIONS16), EXTENDED_ID_NAND("NAND 64MiB 3,3V 16-bit", 0xC2, 64, LP_OPTIONS16), EXTENDED_ID_NAND("NAND 64MiB 3,3V 16-bit", 0xC0, 64, LP_OPTIONS16), /* 1 Gigabit */ EXTENDED_ID_NAND("NAND 128MiB 1,8V 8-bit", 0xA1, 128, LP_OPTIONS), EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xF1, 128, LP_OPTIONS), // 我们所使用的的nand会匹配这个 EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xD1, 128, LP_OPTIONS), EXTENDED_ID_NAND("NAND 128MiB 1,8V 16-bit", 0xB1, 128, LP_OPTIONS16), EXTENDED_ID_NAND("NAND 128MiB 3,3V 16-bit", 0xC1, 128, LP_OPTIONS16), EXTENDED_ID_NAND("NAND 128MiB 1,8V 16-bit", 0xAD, 128, LP_OPTIONS16), /* 2 Gigabit */ EXTENDED_ID_NAND("NAND 256MiB 1,8V 8-bit", 0xAA, 256, LP_OPTIONS), EXTENDED_ID_NAND("NAND 256MiB 3,3V 8-bit", 0xDA, 256, LP_OPTIONS), EXTENDED_ID_NAND("NAND 256MiB 1,8V 16-bit", 0xBA, 256, LP_OPTIONS16), EXTENDED_ID_NAND("NAND 256MiB 3,3V 16-bit", 0xCA, 256, LP_OPTIONS16), ... ... /* 128 Gigabit */ ... /* 256 Gigabit */ ... /* 512 Gigabit */ ... {NULL} };
其中宏以及结构体nand_flash_dev定义:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* * A helper for defining older NAND chips where the second ID byte fully * defined the chip, including the geometry (chip size, eraseblock size, page * size). All these chips have 512 bytes NAND page size. */ #define LEGACY_ID_NAND(nm, devid, chipsz, erasesz, opts) \ { .name = (nm), {{ .dev_id = (devid) }}, .pagesize = 512, \ .chipsize = (chipsz), .erasesize = (erasesz), .options = (opts) } /* * A helper for defining newer chips which report their page size and * eraseblock size via the extended ID bytes. * * The real difference between LEGACY_ID_NAND and EXTENDED_ID_NAND is that with * EXTENDED_ID_NAND, manufacturers overloaded the same device ID so that the * device ID now only represented a particular total chip size (and voltage, * buswidth), and the page size, eraseblock size, and OOB size could vary while * using the same device ID. */ #define EXTENDED_ID_NAND(nm, devid, chipsz, opts) \ { .name = (nm), {{ .dev_id = (devid) }}, .chipsize = (chipsz), \ .options = (opts) } #define NAND_ECC_INFO(_strength, _step) \ { .strength_ds = (_strength), .step_ds = (_step) } #define NAND_ECC_STRENGTH(type) ((type)->ecc.strength_ds) #define NAND_ECC_STEP(type) ((type)->ecc.step_ds) /** * struct nand_flash_dev - NAND Flash Device ID Structure * @name: a human-readable name of the NAND chip * @dev_id: the device ID (the second byte of the full chip ID array) * @mfr_id: manufecturer ID part of the full chip ID array (refers the same * memory address as ``id[0]``) * @dev_id: device ID part of the full chip ID array (refers the same memory * address as ``id[1]``) * @id: full device ID array * @pagesize: size of the NAND page in bytes; if 0, then the real page size (as * well as the eraseblock size) is determined from the extended NAND * chip ID array) * @chipsize: total chip size in MiB * @erasesize: eraseblock size in bytes (determined from the extended ID if 0) * @options: stores various chip bit options * @id_len: The valid length of the @id. * @oobsize: OOB size * @ecc: ECC correctability and step information from the datasheet. * @ecc.strength_ds: The ECC correctability from the datasheet, same as the * @ecc_strength_ds in nand_chip{}. * @ecc.step_ds: The ECC step required by the @ecc.strength_ds, same as the * @ecc_step_ds in nand_chip{}, also from the datasheet. * For example, the "4bit ECC for each 512Byte" can be set with * NAND_ECC_INFO(4, 512). * @onfi_timing_mode_default: the default ONFI timing mode entered after a NAND * reset. Should be deduced from timings described * in the datasheet. * */ struct nand_flash_dev { char *name; union { struct { uint8_t mfr_id; uint8_t dev_id; }; uint8_t id[NAND_MAX_ID_LEN]; }; unsigned int pagesize; unsigned int chipsize; unsigned int erasesize; unsigned int options; uint16_t id_len; uint16_t oobsize; struct { uint16_t strength_ds; uint16_t step_ds; } ecc; int onfi_timing_mode_default; };View Code
在结构体数组nand_flash_ids[]中,预先定义了,目前所支持的很多类型Nand Flash的具体物理参数,主要是上面结构体中的页大小pagesize,芯片大小chipsize,块大小erasesize,而id变量表示此类型的芯片,用哪个数字来表示。
如果这个nand_ids.c里没有你的nand芯片,就要往这里添加我们所使用的Nand Flash芯片的信息结构体。
4.5 s3c2410_nand_add_partition
s3c2410_nand_add_partition定义在drivers/mtd/nand/raw/s3c2410.c:
static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info, struct s3c2410_nand_mtd *mtd, struct s3c2410_nand_set *set) { if (set) { struct mtd_info *mtdinfo = nand_to_mtd(&mtd->chip); mtdinfo->name = set->name; return mtd_device_register(mtdinfo, set->partitions, set->nr_partitions); } return -ENODEV; }
该函数最后调用了mtd_device_register进行MTD设备的注册。
参考文章
[2]24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)
标签:info,struct,int,chip,Flash,NAND,nand,linux,驱动 From: https://www.cnblogs.com/zyly/p/16767526.html