在介绍Nand Flash块设备驱动之前,我们首先要了解Mini2440开发板所使用的K9F2G08U0C型号芯片,具体可以参考之前我们介绍的两篇博客:
一、读取ID
1.1 K9F2G08U0C
K9F2G08U0C芯片读取ID,需要发送命令0x90,然后发送地址0x00,最后连续读取5个字节,就是Nand Flash的ID。
从芯片的Datasheet我们可以找到这5个字节依次为0xEC、0xDA、0x10、0x15、0x44。0xEC表示厂家ID、0xDA表示设备ID。
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写入一个字、mw.l写入一个l长字;
- md:memory display,md.b 读取一个字节、md.w读取一个字、md.l读取一个l长字;
给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没问题,其他的四个字节都不太对。
二、platform设备注册(s3c2410-nand)
接下下来我们直接分析内核自带的Nand Flash驱动,其采用的也是platform设备驱动模型。
2.1 nand相关结构体
我们定位到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; // 芯片的数目 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); };
这里定义了s3c24xx系列SOC关于nand相关配置的结构体:
- s3c2410_nand_set:定义nand set;
- s3c2410_platform_nand:定义了nand相关信息;
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 = { // nand信息 .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)
既然注册了名字为"s3c2410-nand"的platform设备,那么名字为"s3c2410-nand"的platform驱动在哪里注册的呢?
其相关代码为drivers/mtd/nand/raw/s3c2410.c,为什么nand在mtd目录下?
因为MTD(Memory Technology Drivers)是用于访问memory设备( ROM 、 Flash)的Linux 的子系统, MTD 的主要目的是为了使新的memory设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。
3.1 入口和出口函数
我们可以在该文件定位到驱动模块的入口和出口:
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.1.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.1.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.2 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); if (info == NULL) { err = -ENOMEM; goto exit_error; } platform_set_drvdata(pdev, info); nand_controller_init(&info->controller); info->controller.ops = &s3c24xx_nand_controller_ops; /* get the clock source and enable it */ info->clk = devm_clk_get(&pdev->dev, "nand"); 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); 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); /* allocate and map the resource */ /* currently we assume we have the one resource */ res = pdev->resource; size = resource_size(res); info->device = &pdev->dev; info->platform = plat; info->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(info->regs)) { err = PTR_ERR(info->regs); goto exit_error; } dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs); if (!plat->sets || plat->nr_sets < 1) { err = -EINVAL; goto exit_error; } sets = plat->sets; nr_sets = plat->nr_sets; info->mtd_count = nr_sets; /* allocate our information */ size = nr_sets * sizeof(*info->mtds); info->mtds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); 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++) { struct mtd_info *mtd = nand_to_mtd(&nmtd->chip); 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); if (err) goto exit_error; s3c2410_nand_add_partition(info, nmtd, sets); } /* initialise the hardware */ err = s3c2410_nand_inithw(info); 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)) { 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; }
参考文章
[2]24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)
标签:info,set,struct,smdk,Flash,nand,platform,linux,驱动 From: https://www.cnblogs.com/zyly/p/16756273.html