----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8根文件系统:busybox 1.25.0u-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 Cell4 Level Cell8 Level Cell16 Level Cell |
0 0 0 1 1 0 1 1 |
||||
Number ofSimultaneouslyProgrammed Pages |
1 2 4 8 |
0 0 0 1 1 0 1 1 |
||||
Interleave ProgramBetween multiple chips |
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]);