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

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

内核版本:linux 5.2.8根文件系统:busybox 1.25.0u-boot:2016.05----------------------------------------------------------------------------------------------------------------------------

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

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

一、Nand Flash ID

1.1  K9F2G08U0C

K9F2G08U0C芯片读取ID,需要发送命令0x90,然后发送地址0x00,最后连续读取5个字节,就是Nand Flash的ID。

Device Marker Code Device Code(2nd Cycle) 3rd Cycle 4th Cycle 5th Cycle
K9F2G08U0C ECH DAH 10H 15H 44H
  Marker code Device Code

Internal Chip Number

Cell Type

Number of Simultaneously Proprammed Pages

Etc

Page Size

Block Size

Redundant Area Size

Organization,

Serial Access Minimum

Plane Number

Plane Size

从芯片的Datasheet我们可以找到这5个字节依次为0xEC、0xDA、0x10、0x15、0x44。0xEC表示厂家ID、0xDA表示设备ID.

1.1.1 3rd ID Data
  Description I/O7 I/O6 I/O 5 I/O4 I/O3 I/O2 I/O1 I/O0
Internal Chip Number

1

2

4

8

       

0    0

0    1

1    0

1    1

Cell Type

2 Level Cell4 Level Cell8 Level Cell16 Level Cell

     

0    0

0    1

1    0

1    1

 

Number ofSimultaneouslyProgrammed Pages

1

2

4

8

   

0    0

0    1

1    0

1    1

   

Interleave ProgramBetween multiple chips

Not Support

Support

 

0

1

     
Cache Program

Not Support

Support

0

1

       

由于我们所使用的的这款Nand Flash芯片第三个字节为0x10,对应二进制就是0001 0000B,所以:

  • Internal Chip Number:1,表示该Nand Flash内部是由1个芯片(chip)所组成的;
  • Cell Type:2 Level Cell,Level Cell又称为SLC(Single Layer Cell单层单元);2 Level Cell表示每个内存单元中有两种状态(电平等级),可表示1bit数据(0或者1);
  • Number of Simultaneously Programmed Pages:可以对几个页同时编程/写。此功能简单的说就是,一次性地写多个页的数据到对应的不同的页。
  • Interleave Program Between multiple chips:不支持;
  • Cache Program:不支持;
1.1.2 4th Data
  Description I/O7 I/O6 I/O5    I/O4 I/O3  I/O2 I/O1   I/O0

Page Size

1KB

2KB

4KB

8KB

         

0    0

0    1

1    0

1    1

Block Size

64KB

128KB

256KB

512KB

   

0    0

0    1

1    0

1    1

     

Redundant Area Size

(byte/512byte)

8

16

       

0

1

 
Organization

x8

x16

 

0

1

       
Serial Access Minimum

50ns/30ns

25ns

Reserved

Reserved

0

1

0

1

   

0

0

1

1

   

由于我们所使用的的这款Nand Flash芯片第四个字节为0x15,对应二进制就是0001 0101B,所以:

  • Page Size:2KB,页大小为2KB,即每次读/写最小单位为2KB;
  • Block Size:128KB,块大小为128KB,即每次擦除最小单位为128KB;
  • Redundant Area Size(OOB区域、或者Spare Area,用于ECC):16,即没512个字节冗余区域大小为16个字节,每页冗余区域为2KB/512*16=64字节;
  • Organization:x8,这里指的是bus width(数据线宽度)为8;
  • Serial Access Minimum:50ns/30ns;
1.1.3 5th Data
  Description I/O7 I/O6   I/O5   I/O4 I/O3   I/O2 I/O1 I/O0
Plane Number

1

2

4

8

   

0    0

0    1

1    0

1    1

   
Plane Size

64Mb

128Mb

256Mb

512Mb

1Gb

2Gb

4Gb

8Gb

 

0   0   0

0   0   1

0   1   0

0   1   1

1   0   0

1   0   1

1   1   0

1   1   1

   
Reserved   0     0 0

由于我们所使用的的这款Nand Flash芯片第五个字节为0x44,对应二进制就是0100 0100B,所以:

  • Plane Number:2,即每个chip包含两个plane;
  • Plane Size:1Gb,每个plane大小为1Gb=128MB;

所以每个chip是256MB。

1.2 在uboot中读取ID

向NFCONT寄存器写入0x01;使能Nand Flash、以及片选使能;NFCONT寄存器地址为0x4e000004;

向NFCMMD寄存器写入0x90;NFCMMD寄存器地址为0x4e000008;

向NFADDR寄存器写入0x00;NFADDR寄存器地址为0x4e00000c;

我们再来看一下uboot相关的命令:

  • mw:memory write, mw.b 写入一个字节、mw.w写入2个字节、mw.l写入一个4个字节;
  • md:memory display,md.b 读取一个字节、md.w读取2个字节、md.l读取一个4个字节;

给Mini2440开发板上电,在uboot中输入如下命令:                       ....

SMDK2440 # md.l 0x4e000004 1
4e000004: 00000003                               ....
SMDK2440 # mw.l 0x4e000004 1
SMDK2440 # mw.b 0x4e000008 0x90
SMDK2440 # mw.b 0x4e00000c 0x00
SMDK2440 # md.b 0x4e000010 1
4e000010: ec                                                 .
SMDK2440 # md.b 0x4e000010 1
4e000010: f1                                                 .
SMDK2440 # md.b 0x4e000010 1
4e000010: 00                                                 .
SMDK2440 # md.b 0x4e000010 1
4e000010: 95                                                 .
SMDK2440 # md.b 0x4e000010 1
4e000010: 40          

我们发现uboot命令读取到的ID好像除了第一个字节0xEC没问题,其他的四个字节都不太对。后来我才想起来,由于我最初的那块Mini2440开发板网络有问题,后来换了一环开发板,而这块开发板使用的是Nand Flash型号是K9F1G089U0B。

Device Marker Code Device Code(2nd Cycle) 3rd Cycle 4th Cycle 5th Cycle
K9F1G08U0B ECH F1H 00H 95H 40H

K9F1G089U0B内部包含1个chip,每个chip包含1个plane,每个plane大小为1Gb=128MB,所以每个chip大小为128MB,即K9F1G089U0B容量大小为128MB。页大小为2KB,块大小为128KB。

二、platform设备注册(s3c2410-nand)

接下来我们直接分析内核自带的Nand Flash驱动,其采用的也是platform设备驱动模型。

2.1 相关结构体

我们定位到include/linux/platform_data/mtd-nand-s3c2410.h头文件:

/**
 * struct s3c2410_nand_set - define a set of one or more nand chips
 * @flash_bbt:          Openmoko u-boot can create a Bad Block Table
 *                      Setting this flag will allow the kernel to
 *                      look for it at boot time and also skip the NAND
 *                      scan.
 * @options:            Default value to set into 'struct nand_chip' options.
 * @nr_chips:           Number of chips in this set
 * @nr_partitions:      Number of partitions pointed to by @partitions
 * @name:               Name of set (optional)
 * @nr_map:             Map for low-layer logical to physical chip numbers (option)
 * @partitions:         The mtd partition list
 *
 * define a set of one or more nand chips registered with an unique mtd. Also
 * allows to pass flag to the underlying NAND layer. 'disable_ecc' will trigger
 * a warning at boot time.
 */
struct s3c2410_nand_set {
        unsigned int            flash_bbt:1;

        unsigned int            options;
        int                     nr_chips;       // chip的个数
        int                     nr_partitions;  // 分区数目
        char                    *name;          // 集合的名称
        int                     *nr_map;        // 底层逻辑到物理的芯片数目  
        struct mtd_partition    *partitions;    // 分区表
        struct device_node      *of_node;
};

struct s3c2410_platform_nand {
        /* timing information for controller, all times in nanoseconds */

        int     tacls;  /* time for active CLE/ALE to nWE/nOE, Nand Flash时序参数TACLS */
        int     twrph0; /* active time for nWE/nOE,Nand Flash时序参数TWRPH0 */
        int     twrph1; /* time for release CLE/ALE from nWE/nOE inactive,Nand Flash时序参数TWRPH1 */

        unsigned int    ignore_unset_ecc:1;

        nand_ecc_modes_t        ecc_mode;  // ecc模式

        int                     nr_sets;  // nand set数目
        struct s3c2410_nand_set *sets;    // 指向nand set数组

        void                    (*select_chip)(struct s3c2410_nand_set *,  // 根据芯片编号选择有效nand set
                                               int chip);
};

这里定义了两个结构体s3c2410_nand_set、s3c2410_platform_nand:

  • s3c2410_nand_set:开发板所使用的的Nand Flash内部可能包含若干个chip,这里描述每个chip的分区信息;
  • s3c2410_platform_nand:定义了开发板所使用的的Nand Flash的描述信息,比如时序参数、以及ecc模式、nand set数组等;
2.2  结构体全局变量

我们定位到 arch/arm/mach-s3c24xx/common-smdk.c文件,在这个里面我们可以看到nand相关的信息定义:

/* NAND parititon from 2.4.18-swl5 */

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

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

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

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

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

2.3 smdk2440_machine_init

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

MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
        .atag_offset    = 0x100,

        .init_irq       = s3c2440_init_irq,
        .map_io         = smdk2440_map_io,
        .init_machine   = smdk2440_machine_init,
        .init_time      = smdk2440_init_time,
MACHINE_END

重点关注init_machine,init_machine中保存的是开发板资源注册的初始化代码。

static void __init smdk2440_machine_init(void)
{
        s3c24xx_fb_set_platdata(&smdk2440_fb_info);   
        s3c_i2c0_set_platdata(NULL);

        platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));  // s3c2440若干个platform设备注册 usb host controller、lcd、wdt等
        smdk_machine_init();  // s3c24x0系列若干个platform设备注册(通用)
}
2.4 smdk_machine_init

其中smdk_machine_init定义在arch/arm/mach-s3c24xx/common-smdk.c:

void __init smdk_machine_init(void)
{
        /* Configure the LEDs (even if we have no LED support)*/

        int ret = gpio_request_array(smdk_led_gpios,
                                     ARRAY_SIZE(smdk_led_gpios));
        if (!WARN_ON(ret < 0))
                gpio_free_array(smdk_led_gpios, ARRAY_SIZE(smdk_led_gpios));

        if (machine_is_smdk2443())
                smdk_nand_info.twrph0 = 50;

        s3c_nand_set_platdata(&smdk_nand_info);  // 设置smdk_nand_info->dev.platform_data=&smdk_nand_info

        platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs)); // 若干个platform设备注册

        s3c_pm_init();
}
2.4.1  s3c_nand_set_platdata

我们定位到s3c_nand_set_platdata函数,位于 arch/arm/plat-samsung/devs.c文件中,实际上在这个文件里根据我们内核编译配置的宏,注册不同的platform设备,比如这里我们定义了名字为"s3c2410-nand"的platform设备:

/* NAND */

#ifdef CONFIG_S3C_DEV_NAND
static struct resource s3c_nand_resource[] = {
        [0] = DEFINE_RES_MEM(S3C_PA_NAND, SZ_1M),   // 定义内存资源,起始地址0x4E000000(Nand Flash控制器相关寄存器基地址)、大小为1M
};

struct platform_device s3c_device_nand = {  // 定义platform设备
        .name           = "s3c2410-nand",
        .id             = -1,
        .num_resources  = ARRAY_SIZE(s3c_nand_resource),
        .resource       = s3c_nand_resource,
};

/*
 * s3c_nand_copy_set() - copy nand set data
 * @set: The new structure, directly copied from the old.
 *
 * Copy all the fields from the NAND set field from what is probably __initdata
 * to new kernel memory. The code returns 0 if the copy happened correctly or
 * an error code for the calling function to display.
 *
 * Note, we currently do not try and look to see if we've already copied the
 * data in a previous set.
 */
static int __init s3c_nand_copy_set(struct s3c2410_nand_set *set)  // 克隆nand set中数据,比如成员partitions、nr_map
{
        void *ptr;
        int size;

        size = sizeof(struct mtd_partition) * set->nr_partitions;  // 计算nand set中分区表大小
        if (size) {
                ptr = kmemdup(set->partitions, size, GFP_KERNEL); // 申请一块新内存,大小为size,并将set->partitions拷贝到新的内存
                set->partitions = ptr; // 指向新克隆的分区表

                if (!ptr)
                        return -ENOMEM;
        }

        if (set->nr_map && set->nr_chips) {  // 同理,克隆set->nr_map
                size = sizeof(int) * set->nr_chips;
                ptr = kmemdup(set->nr_map, size, GFP_KERNEL);
                set->nr_map = ptr;

                if (!ptr)
                        return -ENOMEM;
        }

        return 0;
}

void __init s3c_nand_set_platdata(struct s3c2410_platform_nand *nand)
{
        struct s3c2410_platform_nand *npd;
        int size;
        int ret;

        /* note, if we get a failure in allocation, we simply drop out of the
         * function. If there is so little memory available at initialisation
         * time then there is little chance the system is going to run.
         */

        npd = s3c_set_platdata(nand, sizeof(*npd), &s3c_device_nand);  // 设置smdk_nand_info->dev.platform_data=&smdk_nand_info
        if (!npd)
                return;

        /* now see if we need to copy any of the nand set data */

        size = sizeof(struct s3c2410_nand_set) * npd->nr_sets;  // 计算nand set数组大小
        if (size) {
                struct s3c2410_nand_set *from = npd->sets;  // nand set 数组指针
                struct s3c2410_nand_set *to;
                int i;

                to = kmemdup(from, size, GFP_KERNEL);   // 申请一块新内存,大小为size,并将nand set数组拷贝到新的内存
                npd->sets = to; /* set, even if we failed,npd->sets指向新克隆的nand set数组指针 */

                if (!to) {
                        printk(KERN_ERR "%s: no memory for sets\n", __func__);
                        return;
                }

                for (i = 0; i < npd->nr_sets; i++) { // 遍历每一个nand set
                        ret = s3c_nand_copy_set(to);  // 克隆nand set数据,比如成员partitions、nr_map
                        if (ret) {
                                printk(KERN_ERR "%s: failed to copy set %d\n",
                                __func__, i);
                                return;
                        }
                        to++;
                }
        }
}
#endif /* CONFIG_S3C_DEV_NAND */

在s3c_nand_set_platdata这里我们调用了s3c_set_platdata函数,该函数设置s3c_device_nand->dev.platform_data=&smdk_nand_info。

2.4.2 s3c_set_platdata

s3c_set_platdata定义在arch/arm/plat-samsung/platformdata.c文件中:

void __init *s3c_set_platdata(void *pd, size_t pdsize,   // pd = &smdk_nand_info , pdev = &s3c_device_nand
                              struct platform_device *pdev)
{
        void *npd;

        if (!pd) {   // 空校验
                /* too early to use dev_name(), may not be registered */
                printk(KERN_ERR "%s: no platform data supplied\n", pdev->name);
                return NULL;
        }

        npd = kmemdup(pd, pdsize, GFP_KERNEL); // 申请一块新内存,大小为pdsize,并将pd拷贝到新的内存
        if (!npd)
                return NULL;

        pdev->dev.platform_data = npd;
        return npd; // 返回新克隆的smdk_nand_info
}

这个函数主要是用来设置pdev->dev的platform_data成员,是个void *类型,可以给平台driver提供各种数据(比如:GPIO引脚等等)。

2.5 platform设备注册

我们已经定义了nand相关的platform_device设备s3c_device_nand,并进行了初始化,那platform设备啥时候注册的呢?

我们定位到smdk_machine_init中的如下函数:

platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs)); // 若干个platform设备注册

这里利用platform_add_devices进行若干个platform设备的注册,该函数还是通过调用platform_device_register实现platform设备注册:

/**
 * platform_add_devices - add a numbers of platform devices
 * @devs: array of platform devices to add
 * @num: number of platform devices in array
 */
int platform_add_devices(struct platform_device **devs, int num)
{
        int i, ret = 0;

        for (i = 0; i < num; i++) {
                ret = platform_device_register(devs[i]);
               
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Graceful_scenery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值