• 欢迎光临~

linux驱动移植-linux块设备驱动Nand Flash

开发技术 开发技术 2022-10-07 次浏览

在介绍Nand Flash块设备驱动之前,首先你需要了解S3C2440这款SOC关于Nand Flash控制器的知识,同时需要对Mini2440开发板所使用的K9F2G08U0C型号芯片有所了解,因为这一节我们不会过多介绍这些内容。

具体可以参考之前我们介绍的两篇博客:

  • Mini2440裸机开发之Nand Flash 基础
  • Mini2440裸机开发之Nand Flash 编程

一、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/mach-smdk2440.c文件,在这个里面我们可以看到nand相关的信息定义:

/* NAND parititon from 2.4.18-swl5 */

static struct mtd_partition smdk_default_nand_part[] = {  // 分区表
       [0] = {
        .name    = "u-boot",
        .size    = SZ_256K,
        .offset    = 0,
    },
    [1] = {
        .name    = "params",
        .size    = SZ_128K,
        .offset    = MTDPART_OFS_APPEND,
    },
    [2] = {
        .name    = "kernel",
        /* 5 megabytes, for a kernel with no modules
         * or a uImage with a ramdisk attached */
        .size    = SZ_4M,
        .offset    = MTDPART_OFS_APPEND,
    },
    [3] = {
        .name    = "rootfs",
        .offset    = MTDPART_OFS_APPEND,
        .size    = MTDPART_SIZ_FULL,
    },
};

static struct s3c2410_nand_set smdk_nand_sets[] = { // nand set数组
        [0] = {
                .name           = "NAND",
                .nr_chips       = 1,
                .nr_partitions  = ARRAY_SIZE(smdk_default_nand_part),
                .partitions     = smdk_default_nand_part,
        },
};

/* choose a set of timings which should suit most 512Mbit
 * chips and beyond.
*/

static struct s3c2410_platform_nand smdk_nand_info = { // 开发板所使用Nnand Flash的描述信息
        .tacls          = 20,
        .twrph0         = 60,
        .twrph1         = 20,
        .nr_sets        = ARRAY_SIZE(smdk_nand_sets),
        .sets           = smdk_nand_sets,  // nand set数组指针
        .ecc_mode       = NAND_ECC_NONE,   // 关闭ecc校验
};

可以看到这里声明了全局变量smdk_default_nand_part、smdk_nand_sets、smdk_nand_info并进行了初始化,如果我们想支持我们开发板所使用的的Nand Flash的话,实际上只要修改这些配置信息即可。

2.3 smdk2440_machine_init

linux内核启动的时候会根据uboot中设置的机器id执行相应的初始化工作,比如.init_machine、.init_irq,我们首先定位到arch/arm/mach-s3c24xx/mach-smdk2440.c:

MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <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=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 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_chip 

nand_chip是一个比较重要的数据结构,MTD使用nand_chip来表示一个Nand Flash内部的芯片,该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。其定义在include/linux/mtd/rawnand.h:

/**
 * struct nand_chip - NAND Private Flash Chip Data
 * @base:               Inherit from the generic NAND device
 * @legacy:             All legacy fields/hooks. If you develop a new driver,
 *                      don't even try to use any of these fields/hooks, and if
 *                      you're modifying an existing driver that is using those
 *                      fields/hooks, you should consider reworking the driver
 *                      avoid using them.
 * @setup_read_retry:   [FLASHSPECIFIC] flash (vendor) specific function for
 *                      setting the read-retry mode. Mostly needed for MLC NAND.
 * @ecc:                [BOARDSPECIFIC] ECC control structure
 * @buf_align:          minimum buffer alignment required by a platform
 * @oob_poi:            "poison value buffer," used for laying out OOB data
 *                      before writing
 * @page_shift:         [INTERN] number of address bits in a page (column
 *                      address bits).
 * @phys_erase_shift:   [INTERN] number of address bits in a physical eraseblock
 * @bbt_erase_shift:    [INTERN] number of address bits in a bbt entry
 * @chip_shift:         [INTERN] number of address bits in one chip
 * @options:            [BOARDSPECIFIC] various chip options. They can partly
 *                      be set to inform nand_scan about special functionality.
 *                      See the defines for further explanation.
 * @bbt_options:        [INTERN] bad block specific options. All options used
 *                      here must come from bbm.h. By default, these options
 *                      will be copied to the appropriate nand_bbt_descr's.
 * @badblockpos:        [INTERN] position of the bad block marker in the oob
 *                      area.
 * @badblockbits:       [INTERN] minimum number of set bits in a good block's
 *                      bad block marker position; i.e., BBM == 11110111b is
 *                      not bad when badblockbits == 7
 * @onfi_timing_mode_default: [INTERN] default ONFI timing mode. This field is
 *                            set to the actually used ONFI mode if the chip is
 *                            ONFI compliant or deduced from the datasheet if
 *                            the NAND chip is not ONFI compliant.
 * @pagemask:           [INTERN] page number mask = number of (pages / chip) - 1
 * @data_buf:           [INTERN] buffer for data, size is (page size + oobsize).
 * @pagecache:          Structure containing page cache related fields
 * @pagecache.bitflips: Number of bitflips of the cached page
 * @pagecache.page:     Page number currently in the cache. -1 means no page is
 *                      currently cached
 * @subpagesize:        [INTERN] holds the subpagesize
 * @id:                 [INTERN] holds NAND ID
 * @parameters:         [INTERN] holds generic parameters under an easily
 *                      readable form.
 * @data_interface:     [INTERN] NAND interface timing information
 * @cur_cs:             currently selected target. -1 means no target selected,
 *                      otherwise we should always have cur_cs >= 0 &&
 *                      cur_cs < nanddev_ntargets(). NAND Controller drivers
 *                      should not modify this value, but they're allowed to
 *                      read it.
 * @read_retries:       [INTERN] the number of read retry modes supported
 * @lock:               lock protecting the suspended field. Also used to
 *                      serialize accesses to the NAND device.
 * @suspended:          set to 1 when the device is suspended, 0 when it's not.
 * @bbt:                [INTERN] bad block table pointer
 * @bbt_td:             [REPLACEABLE] bad block table descriptor for flash
 *                      lookup.
 * @bbt_md:             [REPLACEABLE] bad block table mirror descriptor
 * @badblock_pattern:   [REPLACEABLE] bad block scan pattern used for initial
 *                      bad block scan.
 * @controller:         [REPLACEABLE] a pointer to a hardware controller
 *                      structure which is shared among multiple independent
 *                      devices.
 * @priv:               [OPTIONAL] pointer to private chip data
 * @manufacturer:       [INTERN] Contains manufacturer information
 * @manufacturer.desc:  [INTERN] Contains manufacturer's description
 * @manufacturer.priv:  [INTERN] Contains manufacturer private information
 */
struct nand_chip {
        struct nand_device base;  // 可以看作mtd_info子类

        struct nand_legacy legacy;  // 硬件操作函数

        int (*setup_read_retry)(struct nand_chip *chip, int retry_mode);

        unsigned int options;  // 与具体的nand芯片相关的一些选项,如NAND_BUSWIDTH_16等
        unsigned int bbt_options;

        int page_shift;       // 用来表示nand芯片的page大小,如某nand芯片的一个page有512个字节,那么该值就是9
        int phys_erase_shift; // 用来表示nand芯片每次可擦除的大小,如某nand芯片每次可擦除16kb(通常为一个block大小),那么该值就是14
        int bbt_erase_shift;  // 用来表示bad block table的大小,通常bbt占用一个block,所以该值通常和phys_erase_shift相同
        int chip_shift;       // 使用位表示nand芯片的容量
        int pagemask;         // nand总容量/每页字节数 - 1    得到页掩码
        u8 *data_buf;

        struct {
                unsigned int bitflips;
                int page;
        } pagecache;

        int subpagesize;
        int onfi_timing_mode_default;
        unsigned int badblockpos;
        int badblockbits;

        struct nand_id id;  // 保存从nand读取到的设备id信息,包含厂家ID、设备ID等
        struct nand_parameters parameters;

        struct nand_data_interface data_interface;

        int cur_cs;  // 当前选中的目标

        int read_retries;

        struct mutex lock;
        unsigned int suspended : 1;

        uint8_t *oob_poi;
        struct nand_controller *controller; // nand controller

        struct nand_ecc_ctrl ecc; // ecc校验结构体,里面有大量函数进行ecc校验
        unsigned long buf_align;

        uint8_t *bbt;
        struct nand_bbt_descr *bbt_td;
        struct nand_bbt_descr *bbt_md;

        struct nand_bbt_descr *badblock_pattern;

        void *priv;

        struct {
                const struct nand_manufacturer *desc;
                void *priv;
        } manufacturer;   // 厂家ID信息
};
3.1.5 struct nand_legacy

nand_legacy该结构体就是保存与Nand Flash芯片硬件控制相关的函数:

/**
 * struct nand_legacy - NAND chip legacy fields/hooks
 * @IO_ADDR_R: address to read the 8 I/O lines of the flash device
 * @IO_ADDR_W: address to write the 8 I/O lines of the flash device
 * @select_chip: select/deselect a specific target/die
 * @read_byte: read one byte from the chip
 * @write_byte: write a single byte to the chip on the low 8 I/O lines
 * @write_buf: write data from the buffer to the chip
 * @read_buf: read data from the chip into the buffer
 * @cmd_ctrl: hardware specific function for controlling ALE/CLE/nCE. Also used
 *            to write command and address
 * @cmdfunc: hardware specific function for writing commands to the chip.
 * @dev_ready: hardware specific function for accessing device ready/busy line.
 *             If set to NULL no access to ready/busy is available and the
 *             ready/busy information is read from the chip status register.
 * @waitfunc: hardware specific function for wait on ready.
 * @block_bad: check if a block is bad, using OOB markers
 * @block_markbad: mark a block bad
 * @set_features: set the NAND chip features
 * @get_features: get the NAND chip features
 * @chip_delay: chip dependent delay for transferring data from array to read
 *              regs (tR).
 * @dummy_controller: dummy controller implementation for drivers that can
 *                    only control a single chip
 *
 * If you look at this structure you're already wrong. These fields/hooks are
 * all deprecated.
 */
struct nand_legacy {
        void __iomem *IO_ADDR_R;   // 设置为数据寄存器地址 NFDATA
        void __iomem *IO_ADDR_W;   // 设置为数据今存其地址 NFDATA
        void (*select_chip)(struct nand_chip *chip, int cs);  // 片选/取消片选
        u8 (*read_byte)(struct nand_chip *chip);              // 读取一个字节数据
        void (*write_byte)(struct nand_chip *chip, u8 byte);   // 写入一个字节数据
        void (*write_buf)(struct nand_chip *chip, const u8 *buf, int len);  // 写入len个长度字节
        void (*read_buf)(struct nand_chip *chip, u8 *buf, int len);         // 读取len个长度字节
        void (*cmd_ctrl)(struct nand_chip *chip, int dat, unsigned int ctrl);  // 写命令/地址
        void (*cmdfunc)(struct nand_chip *chip, unsigned command, int column,  // 发送写数据命令 传入列地址、页地址
                        int page_addr);
        int (*dev_ready)(struct nand_chip *chip); // 获取nand状态 繁忙/就绪  
        int (*waitfunc)(struct nand_chip *chip);  // 等待nand就绪
        int (*block_bad)(struct nand_chip *chip, loff_t ofs);  // 检测是否有坏块
        int (*block_markbad)(struct nand_chip *chip, loff_t ofs);  // 标记坏块
        int (*set_features)(struct nand_chip *chip, int feature_addr,
                            u8 *subfeature_para);
        int (*get_features)(struct nand_chip *chip, int feature_addr,
                            u8 *subfeature_para);
        int chip_delay;           // 延迟时间
        struct nand_controller dummy_controller;
};
3.1.6  struct nand_ecc_ctrl
/**
 * struct nand_ecc_ctrl - Control structure for ECC
 * @mode:       ECC mode
 * @algo:       ECC algorithm
 * @steps:      number of ECC steps per page
 * @size:       data bytes per ECC step
 * @bytes:      ECC bytes per step
 * @strength:   max number of correctible bits per ECC step
 * @total:      total number of ECC bytes per page
 * @prepad:     padding information for syndrome based ECC generators
 * @postpad:    padding information for syndrome based ECC generators
 * @options:    ECC specific options (see NAND_ECC_XXX flags defined above)
 * @priv:       pointer to private ECC control data
 * @calc_buf:   buffer for calculated ECC, size is oobsize.
 * @code_buf:   buffer for ECC read from flash, size is oobsize.
 * @hwctl:      function to control hardware ECC generator. Must only
 *              be provided if an hardware ECC is available
 * @calculate:  function for ECC calculation or readback from ECC hardware
 * @correct:    function for ECC correction, matching to ECC generator (sw/hw).
 *              Should return a positive number representing the number of
 *              corrected bitflips, -EBADMSG if the number of bitflips exceed
 *              ECC strength, or any other error code if the error is not
 *              directly related to correction.
 *              If -EBADMSG is returned the input buffers should be left
 *              untouched.
 * @read_page_raw:      function to read a raw page without ECC. This function
 *                      should hide the specific layout used by the ECC
 *                      controller and always return contiguous in-band and
 *                      out-of-band data even if they're not stored
 *                      contiguously on the NAND chip (e.g.
 *                      NAND_ECC_HW_SYNDROME interleaves in-band and
 *                      out-of-band data).
 * @write_page_raw:     function to write a raw page without ECC. This function
 *                      should hide the specific layout used by the ECC
 *                      controller and consider the passed data as contiguous
 *                      in-band and out-of-band data. ECC controller is
 *                      responsible for doing the appropriate transformations
 *                      to adapt to its specific layout (e.g.
 *                      NAND_ECC_HW_SYNDROME interleaves in-band and
 *                      out-of-band data).
 * @read_page:  function to read a page according to the ECC generator
 *              requirements; returns maximum number of bitflips corrected in
 *              any single ECC step, -EIO hw error
 * @read_subpage:       function to read parts of the page covered by ECC;
 *                      returns same as read_page()
 * @write_subpage:      function to write parts of the page covered by ECC.
 * @write_page: function to write a page according to the ECC generator
 *              requirements.
 * @write_oob_raw:      function to write chip OOB data without ECC
 * @read_oob_raw:       function to read chip OOB data without ECC
 * @read_oob:   function to read chip OOB data
 * @write_oob:  function to write chip OOB data
 */
struct nand_ecc_ctrl {
        nand_ecc_modes_t mode;
        enum nand_ecc_algo algo;
        int steps;
        int size;
        int bytes;
        int total;
        int strength;
        int prepad;
        int postpad;
        unsigned int options;
        void *priv;
        u8 *calc_buf;
        u8 *code_buf;
        void (*hwctl)(struct nand_chip *chip, int mode);
        int (*calculate)(struct nand_chip *chip, const uint8_t *dat,
                         uint8_t *ecc_code);
        int (*correct)(struct nand_chip *chip, uint8_t *dat, uint8_t *read_ecc,
                       uint8_t *calc_ecc);
        int (*read_page_raw)(struct nand_chip *chip, uint8_t *buf,
                             int oob_required, int page);
        int (*write_page_raw)(struct nand_chip *chip, const uint8_t *buf,
                              int oob_required, int page);
        int (*read_page)(struct nand_chip *chip, uint8_t *buf,
                         int oob_required, int page);
        int (*read_subpage)(struct nand_chip *chip, uint32_t offs,
                            uint32_t len, uint8_t *buf, int page);
        int (*write_subpage)(struct nand_chip *chip, uint32_t offset,
                             uint32_t data_len, const uint8_t *data_buf,
                             int oob_required, int page);
        int (*write_page)(struct nand_chip *chip, const uint8_t *buf,
                          int oob_required, int page);
        int (*write_oob_raw)(struct nand_chip *chip, int page);
        int (*read_oob_raw)(struct nand_chip *chip, int page);
        int (*read_oob)(struct nand_chip *chip, int page);
        int (*write_oob)(struct nand_chip *chip, int page);
};
3.1.7  nstruct and_manufacturer

nand_manufacturer保存生产厂家信息,定义在drivers/mtd/nand/raw/internals.h:

/*
 * NAND Flash Manufacturer ID Codes
 */
#define NAND_MFR_AMD            0x01
#define NAND_MFR_ATO            0x9b
#define NAND_MFR_EON            0x92
#define NAND_MFR_ESMT           0xc8
#define NAND_MFR_FUJITSU        0x04
#define NAND_MFR_HYNIX          0xad
#define NAND_MFR_INTEL          0x89
#define NAND_MFR_MACRONIX       0xc2
#define NAND_MFR_MICRON         0x2c
#define NAND_MFR_NATIONAL       0x8f
#define NAND_MFR_RENESAS        0x07
#define NAND_MFR_SAMSUNG        0xec   // 三星厂家
#define NAND_MFR_SANDISK        0x45
#define NAND_MFR_STMICRO        0x20
#define NAND_MFR_TOSHIBA        0x98
#define NAND_MFR_WINBOND        0xef

/**
 * struct nand_manufacturer_ops - NAND Manufacturer operations
 * @detect: detect the NAND memory organization and capabilities
 * @init: initialize all vendor specific fields (like the ->read_retry()
 *        implementation) if any.
 * @cleanup: the ->init() function may have allocated resources, ->cleanup()
 *           is here to let vendor specific code release those resources.
 * @fixup_onfi_param_page: apply vendor specific fixups to the ONFI parameter
 *                         page. This is called after the checksum is verified.
 */
struct nand_manufacturer_ops {
        void (*detect)(struct nand_chip *chip);
        int (*init)(struct nand_chip *chip);
        void (*cleanup)(struct nand_chip *chip);
        void (*fixup_onfi_param_page)(struct nand_chip *chip,
                                      struct nand_onfi_params *p);
};

/**
 * struct nand_manufacturer - NAND Flash Manufacturer structure
 * @name: Manufacturer name
 * @id: manufacturer ID code of device.
 * @ops: manufacturer operations
 */
struct nand_manufacturer {
        int id;   // 厂家ID
        char *name;  // 厂家名字
        const struct nand_manufacturer_ops *ops; // 操作函数
};
3.1.8 struct nand_memory_organization

nand_memory_organization存储的是Nand Flash内存模型,定义在include/linux/mtd/nand.h文件中:

/**
 * struct nand_memory_organization - Memory organization structure
 * @bits_per_cell: number of bits per NAND cell
 * @pagesize: page size
 * @oobsize: OOB area size
 * @pages_per_eraseblock: number of pages per eraseblock
 * @eraseblocks_per_lun: number of eraseblocks per LUN (Logical Unit Number)
 * @max_bad_eraseblocks_per_lun: maximum number of eraseblocks per LUN
 * @planes_per_lun: number of planes per LUN
 * @luns_per_target: number of LUN per target (target is a synonym for die)
 * @ntargets: total number of targets exposed by the NAND device
 */
struct nand_memory_organization {
        unsigned int bits_per_cell;  // 每个内存单元包含多少位
        unsigned int pagesize;  // 页大小
        unsigned int oobsize;   // oob大小
        unsigned int pages_per_eraseblock;  // 每个Block包含多少页
        unsigned int eraseblocks_per_lun;   // 每个LUN包含多少Block
        unsigned int max_bad_eraseblocks_per_lun;   
        unsigned int planes_per_lun;    // 每个LUN包含多少个plane
        unsigned int luns_per_target;   // 1
        unsigned int ntargets;  //  target和die、LUN、chip是同义词,Nand Flash内部包含多少个target
};

3.2 入口和出口函数

我们可以在该文件定位到驱动模块的入口和出口

module_platform_driver(s3c24xx_nand_driver);

module_platform_driver宏展开后本质上就是:

module_init(s3c24xx_nand_driver_init); 
module_exit(s3c24xx_nand_driver_exit); 
static int __init s3c24xx_nand_driver_init(void)
{
     platform_driver_register(s3c24xx_nand_driver);
}

static void __exit s3c24xx_nand_driver_exit(void)
{
     platform_driver_unregister(s3c24xx_nand_driver);
}
 

看到这里是不是有点意外,这里是通过platform_driver_register函数注册了一个platform驱动。

在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是s3c24xx_nand_probe函数。

static struct platform_driver s3c24xx_nand_driver = {
        .probe          = s3c24xx_nand_probe,
        .remove         = s3c24xx_nand_remove,
        .suspend        = s3c24xx_nand_suspend,
        .resume         = s3c24xx_nand_resume,
        .id_table       = s3c24xx_driver_ids,
        .driver         = {
                .name   = "s3c24xx-nand",
                .of_match_table = s3c24xx_nand_dt_ids,
        },
};
3.2.1 s3c24xx_driver_ids

由于platform设备和驱动里的nand并不一样,这里实际上是通过id_table匹配成功:

/* driver device registration */

static const struct platform_device_id s3c24xx_driver_ids[] = {
        {
                .name           = "s3c2410-nand",
                .driver_data    = TYPE_S3C2410,
        }, {
                .name           = "s3c2440-nand",
                .driver_data    = TYPE_S3C2440,
        }, {
                .name           = "s3c2412-nand",
                .driver_data    = TYPE_S3C2412,
        }, {
                .name           = "s3c6400-nand",
                .driver_data    = TYPE_S3C2412, /* compatible with 2412 */
        },
        { }
};
3.2.2 platform_match_id

id_table匹配函数为platform_match_id:

static const struct platform_device_id *platform_match_id(
                        const struct platform_device_id *id,
                        struct platform_device *pdev)
{
        while (id->name[0]) {
                if (strcmp(pdev->name, id->name) == 0) {
                        pdev->id_entry = id;
                        return id;
                }
                id++;
        }
        return NULL;
}

3.3 s3c24xx_nand_probe

/* s3c24xx_nand_probe
 *
 * called by device layer when it finds a device matching
 * one our driver can handled. This code checks to see if
 * it can allocate all necessary resources then calls the
 * nand layer to look for devices
*/
static int s3c24xx_nand_probe(struct platform_device *pdev)
{
        struct s3c2410_platform_nand *plat;
        struct s3c2410_nand_info *info;  // 比较重要的一个结构体,后面会为其动态申请一块内存,并初始化其成员变量
        struct s3c2410_nand_mtd *nmtd;
        struct s3c2410_nand_set *sets;
        struct resource *res;
        int err = 0;
        int size;
        int nr_sets;
        int setno;

        info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);  // 动态申请内存空间,内存大小为sizeof(*info),并赋值给info,该内存随着pdev->dev设备的卸载而由系统释放
        if (info == NULL) {
                err = -ENOMEM;
                goto exit_error;
        }

        platform_set_drvdata(pdev, info);  // pdev->dev.driver_data=info

        nand_controller_init(&info->controller);     // 初始化互斥锁 info->controller.lock
        info->controller.ops = &s3c24xx_nand_controller_ops;

        /* get the clock source and enable it */

        info->clk = devm_clk_get(&pdev->dev, "nand");  // 获取nand时钟,并赋值给info->clk   GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0),  CLKCON寄存器bit[4],控制进入Nand FLash控制器模块的HCLK
        if (IS_ERR(info->clk)) {
                dev_err(&pdev->dev, "failed to get 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);

        if (err)
                goto exit_error;

        plat = to_nand_plat(pdev);  // struct plfatform_device转为struct s3c2410_platform_nand
        /* allocate and map the resource */

        /* currently we assume we have the one resource */
        res = pdev->resource;          // 获取platform_device I/O内存资源
        size = resource_size(res);     // 1MB

        info->device    = &pdev->dev;   // 初始化info->device
        info->platform  = plat;  //  初始化info->platform

        info->regs = devm_ioremap_resource(&pdev->dev, res);  // 将nand flash控制器寄存器基地址映射到虚地址空间,并赋值给info->regs
        if (IS_ERR(info->regs)) {
                err = PTR_ERR(info->regs);
                goto exit_error;
        }

        dev_dbg(&pdev->dev, "mapped registers at %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成员,设置硬件操作函数(读数据、写数据、写命令/地址、片选等);
    • 调用nand_scan(&nmtd->chip, sets ? sets->nr_chips : 1)扫描nand,该函数进行了以下操作:
      • nand_scan_ident:函数获取nand ID信息,然后判断该类型的nand芯片内核是否支持,如果支持的话获取芯片存储的出厂信息,然后初始化chip->base.mtd(成员writesize、oobsize、erasesize等)、chip->base.memorg(成员bits_per_cell、pagesize、oobsize、pages_per_eraseblock、planes_per_lun、luns_per_target、ntatgets等)、chip->options、chip->base.eccreq;
      • nand_attach如果定义了chip->controller->ops->attach_chip函数,执行chip->controller->ops->attach_chip(chip),最终执行了s3c2410_nand_attach_chip(chip函数);该函数主要是初始化ECC engine,即初始化chip->ecc各个成员;
      • nand_scan_tail:使用默认函数填满了所有未初始化函数指针,并扫描错误的块表;
    • 调用s3c2410_nand_add_partition(info, nmtd, sets)进行MTD设备注册;
  • 设置nand flash控制器时序参数:通过s3c2410_nand_inithw设置NFCONT、NFCONF寄存器;

由于s3c24xx_nand_probe比较复杂,所以单独一个小节分析这一块代码,如果对这块代码不感兴趣,看到这里就可以了。

四、s3c24xx_nand_probe

4.1 nand_to_mtd

nand_to_mtd定义在include/linux/mtd/rawnand.h:

static inline struct mtd_info *nand_to_mtd(struct nand_chip *chip)
{
        return &chip->base.mtd;
}

4.2 s3c2410_nand_clk_set_state

s3c2410_nand_clk_set_state定义在drivers/mtd/nand/raw/s3c2410.c,用来设置nand时钟状态:

/**
 * s3c2410_nand_clk_set_state - Enable, disable or suspend NAND clock.
 * @info: The controller instance.
 * @new_state: State to which clock should be set.
 */
static void s3c2410_nand_clk_set_state(struct s3c2410_nand_info *info,
                enum s3c_nand_clk_state new_state)
{
        if (!allow_clk_suspend(info) && new_state == CLOCK_SUSPEND)  // 如果不允许时钟挂起,并且设置new_state == CLOCK_SUSPEND直接返回
                return;

        if (info->clk_state == CLOCK_ENABLE) {      // 时钟已经使能
                if (new_state != CLOCK_ENABLE)      // 设置为其他 
                        clk_disable_unprepare(info->clk); // 禁止时钟
        } else {
                if (new_state == CLOCK_ENABLE)   // 使能时钟
                        clk_prepare_enable(info->clk);
        }

        info->clk_state = new_state;
}

4.3 s3c2410_nand_init_chip

s3c2410_nand_init_chip函数用来初始化nmtd->chip成员变量:

/**
 * s3c2410_nand_init_chip - initialise a single instance of an chip
 * @info: The base NAND controller the chip is on.
 * @nmtd: The new controller MTD instance to fill in.
 * @set: The information passed from the board specific platform data.
 *
 * Initialise the given @nmtd from the information in @info and @set. This
 * readies the structure for use with the MTD layer functions by ensuring
 * all pointers are setup and the necessary control routines selected.
 */
static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
                                   struct s3c2410_nand_mtd *nmtd,
                                   struct s3c2410_nand_set *set)
{
        struct device_node *np = info->device->of_node;
        struct nand_chip *chip = &nmtd->chip;
        void __iomem *regs = info->regs;  // 获取nand flash控制器寄存器基址

        nand_set_flash_node(chip, set->of_node);

// 设置nand chip硬件操作函数 chip
->legacy.write_buf = s3c2410_nand_write_buf; chip->legacy.read_buf = s3c2410_nand_read_buf; chip->legacy.select_chip = s3c2410_nand_select_chip; chip->legacy.chip_delay = 50; nand_set_controller_data(chip, nmtd); chip->options = set->options; chip->controller = &info->controller; /* * let's keep behavior unchanged for legacy boards booting via pdata and * auto-detect timings only when booting with a device tree. */ if (!np) chip->options |= NAND_KEEP_TIMINGS; switch (info->cpu_type) { //CPU类型 case TYPE_S3C2410: // 0 默认这个?如果走了这里,很明显时有问题的 chip->legacy.IO_ADDR_W = regs + S3C2410_NFDATA; info->sel_reg = regs + S3C2410_NFCONF; // 设置选择的寄存器为配置寄存器 info->sel_bit = S3C2410_NFCONF_nFCE; // 1<<11 chip->legacy.cmd_ctrl = s3c2410_nand_hwcontrol; chip->legacy.dev_ready = s3c2410_nand_devready; break; case TYPE_S3C2440: // 2 chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA; // 设置为数据寄存器 info->sel_reg = regs + S3C2440_NFCONT; // 设置选择的寄存器为控制寄存器 info->sel_bit = S3C2440_NFCONT_nFCE; // 设置选择的位位bit[1] 1<< 1 片选位 chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol; chip->legacy.dev_ready = s3c2440_nand_devready; chip->legacy.read_buf = s3c2440_nand_read_buf; chip->legacy.write_buf = s3c2440_nand_write_buf; break; case TYPE_S3C2412: //1 chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA; info->sel_reg = regs + S3C2440_NFCONT; info->sel_bit = S3C2412_NFCONT_nFCE0; chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol; chip->legacy.dev_ready = s3c2412_nand_devready; if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT) dev_info(info->device, "System booted from 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.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定义:

linux驱动移植-linux块设备驱动Nand Flashlinux驱动移植-linux块设备驱动Nand Flash
/*
 * A helper for defining older NAND chips where the second ID byte fully
 * defined the chip, including the geometry (chip size, eraseblock size, page
 * size). All these chips have 512 bytes NAND page size.
 */
#define LEGACY_ID_NAND(nm, devid, chipsz, erasesz, opts)          
        { .name = (nm), {{ .dev_id = (devid) }}, .pagesize = 512, 
          .chipsize = (chipsz), .erasesize = (erasesz), .options = (opts) }

/*
 * A helper for defining newer chips which report their page size and
 * eraseblock size via the extended ID bytes.
 *
 * The real difference between LEGACY_ID_NAND and EXTENDED_ID_NAND is that with
 * EXTENDED_ID_NAND, manufacturers overloaded the same device ID so that the
 * device ID now only represented a particular total chip size (and voltage,
 * buswidth), and the page size, eraseblock size, and OOB size could vary while
 * using the same device ID.
 */
#define EXTENDED_ID_NAND(nm, devid, chipsz, opts)                      
        { .name = (nm), {{ .dev_id = (devid) }}, .chipsize = (chipsz), 
          .options = (opts) }

#define NAND_ECC_INFO(_strength, _step) 
                        { .strength_ds = (_strength), .step_ds = (_step) }
#define NAND_ECC_STRENGTH(type)         ((type)->ecc.strength_ds)
#define NAND_ECC_STEP(type)             ((type)->ecc.step_ds)
/**
 * struct nand_flash_dev - NAND Flash Device ID Structure
 * @name: a human-readable name of the NAND chip
 * @dev_id: the device ID (the second byte of the full chip ID array)
 * @mfr_id: manufecturer ID part of the full chip ID array (refers the same
 *          memory address as ``id[0]``)
 * @dev_id: device ID part of the full chip ID array (refers the same memory
 *          address as ``id[1]``)
 * @id: full device ID array
 * @pagesize: size of the NAND page in bytes; if 0, then the real page size (as
 *            well as the eraseblock size) is determined from the extended NAND
 *            chip ID array)
 * @chipsize: total chip size in MiB
 * @erasesize: eraseblock size in bytes (determined from the extended ID if 0)
 * @options: stores various chip bit options
 * @id_len: The valid length of the @id.
 * @oobsize: OOB size
 * @ecc: ECC correctability and step information from the datasheet.
 * @ecc.strength_ds: The ECC correctability from the datasheet, same as the
 *                   @ecc_strength_ds in nand_chip{}.
 * @ecc.step_ds: The ECC step required by the @ecc.strength_ds, same as the
 *               @ecc_step_ds in nand_chip{}, also from the datasheet.
 *               For example, the "4bit ECC for each 512Byte" can be set with
 *               NAND_ECC_INFO(4, 512).
 * @onfi_timing_mode_default: the default ONFI timing mode entered after a NAND
 *                            reset. Should be deduced from timings described
 *                            in the datasheet.
 *
 */
struct nand_flash_dev {
        char *name;
        union {
                struct {
                        uint8_t mfr_id;
                        uint8_t dev_id;
                };
                uint8_t id[NAND_MAX_ID_LEN];
        };
        unsigned int pagesize;
        unsigned int chipsize;
        unsigned int erasesize;
        unsigned int options;
        uint16_t id_len;
        uint16_t oobsize;
        struct {
                uint16_t strength_ds;
                uint16_t step_ds;
        } ecc;
        int onfi_timing_mode_default;
};
View Code

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

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

4.5 s3c2410_nand_add_partition

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

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

                mtdinfo->name = set->name;

                return mtd_device_register(mtdinfo, set->partitions,
                                           set->nr_partitions);
        }

        return -ENODEV;
}

该函数最后调用了mtd_device_register进行MTD设备的注册。

参考文章

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

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

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

程序员灯塔
转载请注明原文链接:linux驱动移植-linux块设备驱动Nand Flash
喜欢 (0)