----------------------------------------------------------------------------------------------------------------------------

内核版本: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
4 Level Cell
8 Level Cell
16 Level Cell

     

0    0

0    1

1    0

1    1

 

Number of
Simultaneously
Programmed Pages

1

2

4

8

   

0    0

0    1

1    0

1    1

   

Interleave Program
Between 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 setsn", __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 %dn",
                                __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 suppliedn", 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 clockn");
                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 %pn", 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 supportn");
                goto exit_error;
        }

        if (allow_clk_suspend(info)) {  // 0
                dev_info(&pdev->dev, "clock idle support enabledn");
                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 NANDn"); 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 %dn", 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 disabledn");
                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 ECCn");
                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 %dn",
                        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 ECCn");
                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 %06lxn", __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 foundn");
                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 detectedn", 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,%02xn",
                        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%02xn", maf_id, dev_id); pr_info("%s %sn", nand_manufacturer_name(manufacturer), mtd->name); pr_warn("bus width %d instead of %d bitsn", 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%02xn", maf_id, dev_id); pr_info("%s %sn", nand_manufacturer_name(manufacturer), chip->parameters.model); pr_info("%d MiB, %s, erase size: %d KiB, page size: %d, OOB size: %dn", (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;
};
View Code

在结构体数组nand_flash_ids[]中,预先定义了,目前所支持的很多类型Nand Flash的具体物理参数,主要是上面结构体中的页大小pagesize,芯片大小chipsize,块大小erasesize,而id变量表示此类型的芯片,用哪个数字来表示。

如果这个nand_ids.c里没有你的nand芯片,就要往这里添加我们所使用的Nand Flash芯片的信息结构体。

4.5 s3c2410_nand_add_partition

s3c2410_nand_add_partition定义在drivers/mtd/nand/raw/s3c2410.c:

static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
                                      struct s3c2410_nand_mtd *mtd,
                                      struct s3c2410_nand_set *set)
{
        if (set) {
                struct mtd_info *mtdinfo = nand_to_mtd(&mtd->chip);

                mtdinfo->name = set->name;

                return mtd_device_register(mtdinfo, set->partitions,  // 注册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]

参考文章

[1]十七、Linux驱动之nand flash驱动

[2]24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)

[3]linux MTD系统解析(转)

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/zyly/p/16767526.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!

相关课程