linux驱动移植-linux块设备驱动Nand Flash
----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
在介绍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/common-smdk.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 <ben-linux@fluff.org> */ .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=&smdk_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_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 const struct of_device_id s3c24xx_nand_dt_ids[] = { // 设备树匹配使用 { .compatible = "samsung,s3c2410-nand", .data = &s3c2410_nand_devtype_data, }, { /* also compatible with s3c6400 */ .compatible = "samsung,s3c2412-nand", .data = &s3c2412_nand_devtype_data, }, { .compatible = "samsung,s3c2440-nand", .data = &s3c2440_nand_devtype_data, }, { /* sentinel */ } }; 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设备和驱动里的name并不一样,这里实际上是通过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); // 设置Info->spu_type = platform_get_device_id(pdev)->driver_data;这里赋值为TYPE_S3C2440 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 内存资源 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成员,设置硬件操作函数(读数据、写数据、写命令/地址、片选等)比如:chip->legacy(成员write_buf、read_buf、select_chip、cmd_ctrl、dev_ready、IO_ADDR_R、IO_ADDR_W);
- 调用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:chip成员中所有未初始化函数指针则使用nand_base.c中的默认函数,并扫描错误的块表;
- 调用s3c2410_nand_add_partition(info, nmtd, sets)进行MTD设备注册;
- 设置nand flash控制器时序参数:通过s3c2410_nand_inithw设置NFCONT、NFCONF寄存器;
由于s3c24xx_nand_probe比较复杂,所以单独一个小节分析这一块代码,如果对这块代码不感兴趣,看到这里就可以了。
3.4 结构体关系图
经过分析,我们绘制出s3c2410_nand_info、s3c2410_platform_nand、s3c2410_nand_set、s3c2410_nand_mtd、nand_chip之间的关系:
四、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成员,设置硬件操作函数(读数据、写数据、写命令/地址、片选等)比如:chip->legacy(成员write_buf、read_buf、select_chip、cmd_ctrl、dev_ready、IO_ADDR_R、IO_ADDR_W);
/** * 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->priv=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.3.7 s3c2440_nand_enable_hwecc
s3c2440_nand_enable_hwecc函数用于初始化ec:
static void s3c2440_nand_enable_hwecc(struct nand_chip *chip, int mode) { struct s3c2410_nand_info *info; unsigned long ctrl; info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip)); ctrl = readl(info->regs + S3C2440_NFCONT); writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT); }
4.3.8 s3c2440_nand_calculate_ecc
s3c2440_nand_calculate_ecc函数用于计算ecc:
static int s3c2440_nand_calculate_ecc(struct nand_chip *chip, const u_char *dat, u_char *ecc_code) { struct mtd_info *mtd = nand_to_mtd(chip); struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); unsigned long ecc = readl(info->regs + S3C2440_NFMECC0); ecc_code[0] = ecc; ecc_code[1] = ecc >> 8; ecc_code[2] = ecc >> 16; pr_debug("%s: returning ecc %06lx\n", __func__, ecc & 0xffffff); 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定义:
/* * 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; };
在结构体数组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, // 注册MTD块设备,有几个分区就会生成几个/dev/mtdblock%d文件 set->nr_partitions); } return -ENODEV; }
该函数最后调用了mtd_device_register进行MTD设备的注册。mtd_device_register函数主要实现多个分区(如常见的4个分区:u-boot、params、kernel、rootfs)创建,也就是多次调用add_mtd_device。
/* 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, }, };
五、测试内核Nand Flash驱动
5.1 配置内核
我们需要重新配置内核,使用内核自带的Nand Flash驱动,具体步骤如下。
我们可以在drivers/mtd/nand/raw/Kconfig文件中找到配置项MTD_NAND_S3C2410.
我们切换到linux内核目录下:
root@zhengyang:~# cd /work/sambashare/linux-5.2.8/
在linux内核根目录下执行,生成默认配置文件.config:
make distclean make s3c2440_defconfig # 这个是之前我之前配置的
进行内核配置:
root@zhengyang:/work/sambashare/linux-5.2.8# make menuconfig
然后输入/搜索配置项MTD_NAND_S3C2410:
因此我们需要配置MTD_RAW_NAND、MTD、MTD_NAND_S3C2410为Y。
同时还需要配置MTD块设备:Caching block device access to MTD devices设置为Y(会链接mtdblock.o、mtd_blkdevs.o文件)。
修改完配置后,保存文件,输入文件名s3c2440_defconfig,在当前路径下生成s3c2440_defconfig:存档:
mv s3c2440_defconfig ./arch/arm/configs/
5.1 编译内核
编译内核:make s3c2440_defconfig make V=1 uImage
将uImage复制到tftp服务器路径下:
root@zhengyang:/work/sambashare/linux-5.2.8# cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/
5.2 烧录内核
开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,可以通过print查看uboot中已经设置的环境变量。
设置开发板ip地址,从而可以使用网络服务:
SMDK2440 # set ipaddr 192.168.0.105 SMDK2440 # save Saving Environment to NAND... Erasing NAND... Erasing at 0x40000 -- 100% complete. Writing to NAND... OK SMDK2440 # ping 192.168.0.200 dm9000 i/o: 0x20000000, id: 0x90000a46 DM9000: running in 16 bit mode MAC: 08:00:3e:26:0a:5b operating at unknown: 0 mode Using dm9000 device host 192.168.0.200 is alive
设置tftp服务器地址,也就是我们ubuntu服务器地址:
set serverip 192.168.0.200 save
下载内核到内存,并写NAND FLASH:
tftp 30000000 uImage nand erase.part kernel nand write 30000000 kernel bootm
开发板内核启动后,输出Nand Flash相关信息:
------------------------------s3c24xx_nand_probe-------------------------- ----------cpu type------------ 2 nand: device found, Manufacturer ID: 0xec, Chip ID: 0xf1 nand: Samsung NAND 128MiB 3,3V 8-bit nand: 128 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB size: 64 s3c24xx-nand s3c2440-nand: ECC disabled nand: NAND_ECC_NONE selected by board driver. This is not recommended! Scanning device for bad blocks Creating 4 MTD partitions on "NAND": 0x000000000000-0x000000040000 : "u-boot" 0x000000040000-0x000000060000 : "params" 0x000000060000-0x000000460000 : "kernel" 0x000000460000-0x000008000000 : "rootfs" s3c24xx-nand s3c2440-nand: Tacls=2, 20ns Twrph0=6 60ns, Twrph1=2 20ns
可以看到这里先输出了厂家ID为0xec、然后输出了设备ID为0xf1。接着和内核支持的nand盘匹配成功,输出Nand信息:Samsung NAND 128MiB 3,3V 8-bit。
然后输出Nand芯片的信息,比如页大小、块大小、总容量大小等。然后创建分区,输出分区信息。
5.3 测试
查看MTD设备文件:
[root@zy:/]# ls -l /dev/mtd* crw-rw---- 1 0 0 90, 0 Jan 1 00:00 /dev/mtd0 crw-rw---- 1 0 0 90, 1 Jan 1 00:00 /dev/mtd0ro crw-rw---- 1 0 0 90, 2 Jan 1 00:00 /dev/mtd1 crw-rw---- 1 0 0 90, 3 Jan 1 00:00 /dev/mtd1ro crw-rw---- 1 0 0 90, 4 Jan 1 00:00 /dev/mtd2 crw-rw---- 1 0 0 90, 5 Jan 1 00:00 /dev/mtd2ro crw-rw---- 1 0 0 90, 6 Jan 1 00:00 /dev/mtd3 crw-rw---- 1 0 0 90, 7 Jan 1 00:00 /dev/mtd3ro brw-rw---- 1 0 0 31, 0 Jan 1 00:00 /dev/mtdblock0 brw-rw---- 1 0 0 31, 1 Jan 1 00:00 /dev/mtdblock1 brw-rw---- 1 0 0 31, 2 Jan 1 00:00 /dev/mtdblock2 brw-rw---- 1 0 0 31, 3 Jan 1 00:00 /dev/mtdblock3
可以看到总共有4个分区,对于每个分区分别创建了两个字符设备节点和一个块设备节点。
因为mtd层既提供了字符设备的操作接口(mtdchar.c), 也实现了块设备的操作接口(mtd_blkdevs.c)。
其中MTD块设备主设备号为31,次设备号从0开始,设备名称为mtdblock%d。
MTD字符设备主设备号为90,次设备号从0开始,设备名称为mtd%d、mtd%dro。
使用cat /proc/mtd,可以查看磁盘分区
[root@zy:/]# cat /proc/mtd dev: size erasesize name mtd0: 00040000 00020000 "u-boot" mtd1: 00020000 00020000 "params" mtd2: 00400000 00020000 "kernel" mtd3: 07ba0000 00020000 "rootfs"
使用cat /proc/partitions,可以查看全部分区信息:
[root@zy:/]# cat /proc/partitions major minor #blocks name 1 0 4096 ram0 1 1 4096 ram1 1 2 4096 ram2 1 3 4096 ram3 1 4 4096 ram4 1 5 4096 ram5 1 6 4096 ram6 1 7 4096 ram7 1 8 4096 ram8 1 9 4096 ram9 1 10 4096 ram10 1 11 4096 ram11 1 12 4096 ram12 1 13 4096 ram13 1 14 4096 ram14 1 15 4096 ram15 31 0 256 mtdblock0 31 1 128 mtdblock1 31 2 4096 mtdblock2 31 3 126592 mtdblock3
其中blocks表示分区的容量,每个blocks是1KB。
5.4 挂载MTD块设备
/dev/mtdblock3中存储的是我们的根文件系统rootfs.yaffs2,我们首先需要利用MiniTools工具将yaffs2根文件系统下载到Nand Flash 根文件系统所在分区中:
需要注意的是我们uboot启动配置中挂载的根文件系统为NFS:
set bootargs "noinitrd console=ttySAC0,115200 root=/dev/nfs rw nfsroot=192.168.0.200:/work/nfs_root/rootfs ip=192.168.0.105:192.168.0.200:192.168.0.1:255.255.255.0::eth0:off" save
启动开发板后,挂载根文件系统所在分区块设备到/mnt/路径:
[root@zy:/]# mount /dev/mtdblock3 /mnt/ yaffs: dev is 32505859 name is "mtdblock3" rw yaffs: passed flags ""
注意:如果挂载失败,考虑我们内核是否支持yaffs根文件系统,如果不支持,需要配置内核。
进入mnt,可以看到里面就是我们存在Nand Flash上的文件系统:
[root@zy:/]# ls -l /mnt total 24 drwxr-xr-x 1 0 0 2048 Feb 11 2022 bin drwxr-xr-x 1 0 0 2048 Feb 11 2022 dev -rwxr-xr-x 1 0 0 9928 Feb 11 2022 linuxrc drwx------ 1 0 0 2048 Jan 1 00:00 lost+found drwxr-xr-x 1 0 0 2048 Feb 7 2022 proc drwxr-xr-x 1 0 0 2048 Feb 11 2022 sbin drwxr-xr-x 1 0 0 2048 Feb 7 2022 sys drwxr-xr-x 1 0 0 2048 Feb 11 2022 usr
六、编写Nand Flash驱动
在之前的章节,我们已经分析了内核自带的Nand Flash驱动的源码。这里我们将总结一下驱动编写的步骤。
由于MTD设备驱动已经帮我实现了MTD块设备、以及MTD字符设备驱动的编写。而我们要做的主要就是:
- 分配nand_chip内存;
- 根据SOC Nand控制器初始化nand_chip成员,比如:chip->legacy(成员write_buf、read_buf、select_chip、cmd_ctrl、dev_ready、IO_ADDR_R、IO_ADDR_W)、chip->controller;
- 设置chip->priv为mtd_info;
-
以mtd_info为参数调用nand_scan探测Nand Flash,nand_scan会读取nand芯片ID:
- 初始化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;
- chip成员中所有未初始化函数指针则使用nand_base.c中的默认函数;
- 初始化chip->ecc各个成员(设置ecc模式及处理函数);
- mtd_info和mtd_partition为参数调用mtd_device_register()进行MTD设备注册;
6.1 项目结构
我们在/work/sambashare/drivers路径下创建项目17.nand_flash_dev,创建为nand_flash_dev.c。
6.2 模块入口函数
-
使用kzalloc函数为nand_chip动态申请内存,其中nand_chip.base.mtd包含了mtd_info结构体成员;
-
使用ioremap将Nand Flash控制器寄存器地址映射到虚地址空间;
-
设置mtd_info结构体成员;
-
设置nand_chip结构体成员;
-
获取nand时钟,设置info->clk;并使能nand时钟、Nanfd Flash控制器;
-
以mtd_info为参数调用nand_scan探测Nand Flash;
-
mtd_info和mtd_partition为参数调用mtd_device_register()进行MTD设备注册;
6.3 模块出口函数
- 卸载MTD设备;
- 取消寄存器地址映射;
- 释放nand_chip;
6.4 nand_flash_dev.c
#include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/io.h> #include <linux/ioport.h> #include <linux/platform_device.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/slab.h> #include <linux/clk.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/rawnand.h> #include <linux/mtd/nand_ecc.h> #include <linux/mtd/partitions.h> /* * Nand Flash控制器相关寄存器 */ struct nand_regs { unsigned long nfconf ; //0x4E000000 unsigned long nfcont ; unsigned long nfcmd ; unsigned long nfaddr ; unsigned long nfdata ; unsigned long nfeccd0 ; unsigned long nfeccd1 ; unsigned long nfeccd ; unsigned long nfstat ; unsigned long nfestat0; unsigned long nfestat1; unsigned long nfmecc0 ; unsigned long nfmecc1 ; unsigned long nfsecc ; unsigned long nfsblk ; unsigned long nfeblk ; }; /* 全局变量 */ static struct nand_regs *s3c_regs; static struct mtd_info *s3c_mtd; static struct nand_chip *s3c_nand_chip; /* 分区信息 */ static struct mtd_partition s3c_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 void s3c_nand_select_chip(struct nand_chip *this, int chip) { if(chip==-1) //CE Disable { s3c_regs->nfcont|=(0x01<<1); //bit1置1 } else //CE Enable { s3c_regs->nfcont&=~(0x01<<1); //NFCONT寄存器片选位设置位0,使能片选 } } /* * 用于向nand芯片发送命令/地址,第三个参数用来区分是发送的是命令还是地址。 */ static void s3c_nand_cmd_ctrl(struct nand_chip *chip, int cmd, unsigned int ctrl) { if (ctrl & NAND_CLE) //当前为command状态 , s3c_regs->nfcmd=cmd; else //当前为地址状态 s3c_regs->nfaddr=cmd; } /* * 用于获取nand的状态,0表示繁忙,1表示就绪: */ static int s3c_nand_devready(struct mtd_info *mtd) { return (s3c_regs->nfstat&0x01); //获取RnB状态,0:busy 1:ready } /* * 用于从nand读取len个长度字节,并保存到buf缓冲区中: */ static void s3c_nand_read_buf(struct nand_chip *this, u_char *buf, int len) { readsl(&s3c_regs->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(&s3c_regs->nfdata); // 按字节读取 } } /* * 用于将缓冲区buf中len个长度字节写入到nand */ static void s3c_nand_write_buf(struct nand_chip *this, const u_char *buf,int len) { writesl(&s3c_regs->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, s3c_regs->nfdata); } } /* *init入口函数 */ static int s3c_nand_init(void) { struct clk *nand_clk; int res; /*1.分配结构体:nand_chip */ s3c_nand_chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); s3c_mtd = &s3c_nand_chip->base.mtd; /*2.获取nand flash 寄存器虚拟地址*/ s3c_regs = ioremap(0x4E000000, sizeof(struct nand_regs)); /*3.设置mtd_info*/ s3c_mtd->owner = THIS_MODULE; s3c_mtd->priv = s3c_nand_chip; //私有数据 /*4.设置nand_chip*/ s3c_nand_chip->legacy.IO_ADDR_R = &s3c_regs->nfdata; //设置读data s3c_nand_chip->legacy.IO_ADDR_W = &s3c_regs->nfdata; //设置写data s3c_nand_chip->legacy.select_chip = s3c_nand_select_chip; //片选/取消片选 s3c_nand_chip->legacy.cmd_ctrl = s3c_nand_cmd_ctrl; s3c_nand_chip->legacy.dev_ready = s3c_nand_devready; s3c_nand_chip->legacy.read_buf = s3c_nand_read_buf; s3c_nand_chip->legacy.write_buf = s3c_nand_write_buf; s3c_nand_chip->ecc.mode = NAND_ECC_NONE; //关闭ECC s3c_nand_chip->legacy.chip_delay = 50; /*5.设置硬件相关*/ /*5.1使能nand flash 时钟*/ nand_clk = clk_get(NULL, "nand"); // 获取nand时钟,并赋值给GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0) // CLKCON寄存器bit[4],控制进入Nand FLash控制器模块的HCLK clk_prepare_enable(nand_clk); /*5.2设置时序*/ #define TACLS 0 //0nS #define TWRPH0 1 //15nS #define TWRPH1 0 //5nS s3c_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4); /*5.3 bit1:关闭片选 bit0:开启nand flash 控制器*/ s3c_regs->nfcont = (1<<1) | (1<<0); /*6.扫描NAND*/ if (nand_scan(s3c_nand_chip, 1)) { res = -ENXIO; goto out; } /*7.行MTD设备注册*/ res = mtd_device_register(s3c_mtd, s3c_nand_part, 4); if(!res) { return 0; } out: mtd_device_unregister(s3c_mtd); //MTD设备卸载 iounmap(s3c_regs); //释放Nand Flash控制器寄存器 kfree(s3c_nand_chip); //释放nand_chip return 0; } /* * exit出口函数 */ static void s3c_nand_exit(void) { mtd_device_unregister(s3c_mtd); //行MTD设备卸载 iounmap(s3c_regs); //释放nand flash寄存器 kfree(s3c_nand_chip); //释放nand_chip } module_init(s3c_nand_init); module_exit(s3c_nand_exit); MODULE_LICENSE("GPL");
6.5 Makefile
KERN_DIR :=/work/sambashare/linux-5.2.8 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += nand_flash_dev.o
6.6 测试
6.6.1 配置内核
我们需要重新配置内核,去掉默认的Nand Flash驱动。运行:
root@zhengyang:/work/sambashare/linux-5.2.8# make menuconfig
进入 Device Drivers-> Memory Technology Device (MTD) support-> Raw/Parallel NAND Device Support:
Samsung S3C NAND controller // 去掉这个配置
然后make uImage 编译内核,将uImage复制到tftp服务器路径下:
root@zhengyang:/work/sambashare/linux-5.2.8# cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/rootfs
6.6.2 烧录内核
开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,可以通过print查看uboot中已经设置的环境变量。
下载内核到内存,并写入Nand Flash:
tftp 30000000 uImage nand erase.part kernel nand write 30000000 kernel bootm
6.6.3 编译Nand Flash驱动
编译Nand Flash驱动,将 nand_flash_dev.ko拷贝到nfs根文件系统。
root@zhengyang:/work/sambashare/drivers/17.nand_flash_dev# cp /work/sambashare/drivers/17.nand_flash_dev/nand_flash_dev.ko /work/nfs_root/rootfs
重启开发板,加载驱动:
[root@zy:/]# insmod nand_flash_dev.ko nand_flash_dev: loading out-of-tree module taints kernel. nand: device found, Manufacturer ID: 0xec, Chip ID: 0xf1 nand: Samsung NAND 128MiB 3,3V 8-bit nand: 128 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB size: 64 nand: NAND_ECC_NONE selected by board driver. This is not recommended! Scanning device for bad blocks Creating 4 MTD partitions on "NAND 128MiB 3,3V 8-bit": 0x000000000000-0x000000040000 : "u-boot" 0x000000040000-0x000000060000 : "params" 0x000000060000-0x000000460000 : "kernel" 0x000000460000-0x000008000000 : "rootfs"
6.6.4 挂载MTD块设备
查看MTD设备文件:
[root@zy:/]# ls -l /dev/mtd* crw-rw---- 1 0 0 90, 0 Jan 1 00:00 /dev/mtd0 crw-rw---- 1 0 0 90, 1 Jan 1 00:00 /dev/mtd0ro crw-rw---- 1 0 0 90, 2 Jan 1 00:00 /dev/mtd1 crw-rw---- 1 0 0 90, 3 Jan 1 00:00 /dev/mtd1ro crw-rw---- 1 0 0 90, 4 Jan 1 00:00 /dev/mtd2 crw-rw---- 1 0 0 90, 5 Jan 1 00:00 /dev/mtd2ro crw-rw---- 1 0 0 90, 6 Jan 1 00:00 /dev/mtd3 crw-rw---- 1 0 0 90, 7 Jan 1 00:00 /dev/mtd3ro brw-rw---- 1 0 0 31, 0 Jan 1 00:00 /dev/mtdblock0 brw-rw---- 1 0 0 31, 1 Jan 1 00:00 /dev/mtdblock1 brw-rw---- 1 0 0 31, 2 Jan 1 00:00 /dev/mtdblock2 brw-rw---- 1 0 0 31, 3 Jan 1 00:00 /dev/mtdblock3
挂载块设备到/tmp目录,执行如下命令:
[root@zy:/]# mount /dev/mtdblock3 /tmp/ yaffs: dev is 32505859 name is "mtdblock3" rw yaffs: passed flags "" [root@zy:/]# ls -l /tmp total 24 drwxr-xr-x 1 0 0 2048 Feb 11 2022 bin drwxr-xr-x 1 0 0 2048 Feb 11 2022 dev -rwxr-xr-x 1 0 0 9928 Feb 11 2022 linuxrc drwx------ 1 0 0 2048 Jan 1 00:02 lost+found drwxr-xr-x 1 0 0 2048 Feb 7 2022 proc drwxr-xr-x 1 0 0 2048 Feb 11 2022 sbin drwxr-xr-x 1 0 0 2048 Feb 7 2022 sys drwxr-xr-x 1 0 0 2048 Feb 11 2022 usr
mount会自动获取该设备的文件类型。在tmp文件夹可以看到存储在Nand Flash中的文件系统。
七、代码下载
Young / s3c2440_project[drivers]
参考文章