首页 > 系统相关 >linux驱动移植-linux块设备驱动Nand Flash

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

时间:2022-10-05 23:00:07浏览次数:74  
标签:info set struct smdk Flash nand platform linux 驱动

在介绍Nand Flash块设备驱动之前,我们首先要了解Mini2440开发板所使用的K9F2G08U0C型号芯片,具体可以参考之前我们介绍的两篇博客:

一、读取ID

1.1  K9F2G08U0C

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

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

1.2 在uboot中读取ID

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

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

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

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

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

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

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

我们发现uboot命令读取到的ID好像除了第一个字节0xec没问题,其他的四个字节都不太对。

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

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

2.1 nand相关结构体

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

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

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

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

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

        unsigned int    ignore_unset_ecc:1;

        nand_ecc_modes_t        ecc_mode;  // ecc模式

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

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

这里定义了s3c24xx系列SOC关于nand相关配置的结构体:

  • s3c2410_nand_set:定义nand set;
  • s3c2410_platform_nand:定义了nand相关信息;

2.2  结构体全局变量

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

/* NAND parititon from 2.4.18-swl5 */

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

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

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

static struct s3c2410_platform_nand smdk_nand_info = { // nand信息
        .tacls          = 20,
        .twrph0         = 60,
        .twrph1         = 20,
        .nr_sets        = ARRAY_SIZE(smdk_nand_sets),
        .sets           = smdk_nand_sets,  // nand set数组指针
        .ecc_mode       = NAND_ECC_NONE,   // 关闭ecc校验
};

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

2.3 smdk2440_machine_init

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

MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <[email protected]> */
        .atag_offset    = 0x100,

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

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

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

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

2.4 smdk_machine_init

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

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

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

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

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

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

        s3c_pm_init();
}
2.4.1  s3c_nand_set_platdata

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

/* NAND */

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

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

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

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

                if (!ptr)
                        return -ENOMEM;
        }

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

                if (!ptr)
                        return -ENOMEM;
        }

        return 0;
}

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

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

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

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

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

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

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

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

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

2.4.2 s3c_set_platdata

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

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

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

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

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

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

2.5 platform设备注册

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

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

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

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

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

        for (i = 0; i < num; i++) {
                ret = platform_device_register(devs[i]);
                if (ret) {
                        while (--i >= 0)
                                platform_device_unregister(devs[i]);
                        break;
                }
        }

        return ret;
}

smdk_devs中就包含了s3c_device_nand:

/* devices we initialise */

static struct platform_device __initdata *smdk_devs[] = {
        &s3c_device_nand,
        &smdk_led4,
        &smdk_led5,
        &smdk_led6,
        &smdk_led7,
};

三、platform驱动注册(s3c2410-2410)

既然注册了名字为"s3c2410-nand"的platform设备,那么名字为"s3c2410-nand"的platform驱动在哪里注册的呢?

其相关代码为drivers/mtd/nand/raw/s3c2410.c,为什么nand在mtd目录下?

因为MTD(Memory Technology Drivers)是用于访问memory设备( ROM 、 Flash)的Linux 的子系统, MTD 的主要目的是为了使新的memory设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。

3.1 入口和出口函数

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

module_platform_driver(s3c24xx_nand_driver);

module_platform_driver宏展开后本质上就是:

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

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

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

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

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

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

/* driver device registration */

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

id_table匹配函数为platform_match_id:

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

3.2 s3c24xx_nand_probe

/* s3c24xx_nand_probe
 *
 * called by device layer when it finds a device matching
 * one our driver can handled. This code checks to see if
 * it can allocate all necessary resources then calls the
 * nand layer to look for devices
*/
static int s3c24xx_nand_probe(struct platform_device *pdev)
{
        struct s3c2410_platform_nand *plat;
        struct s3c2410_nand_info *info;
        struct s3c2410_nand_mtd *nmtd;
        struct s3c2410_nand_set *sets;
        struct resource *res;
        int err = 0;
        int size;
        int nr_sets;
        int setno;

        info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
        if (info == NULL) {
                err = -ENOMEM;
                goto exit_error;
        }

        platform_set_drvdata(pdev, info);

        nand_controller_init(&info->controller);
        info->controller.ops = &s3c24xx_nand_controller_ops;

        /* get the clock source and enable it */

        info->clk = devm_clk_get(&pdev->dev, "nand");
        if (IS_ERR(info->clk)) {
                dev_err(&pdev->dev, "failed to get clock\n");
                err = -ENOENT;
                goto exit_error;
        }

        s3c2410_nand_clk_set_state(info, CLOCK_ENABLE);

        if (pdev->dev.of_node)
                err = s3c24xx_nand_probe_dt(pdev);
        else
                err = s3c24xx_nand_probe_pdata(pdev);

        if (err)
                goto exit_error;

        plat = to_nand_plat(pdev);
        /* allocate and map the resource */

        /* currently we assume we have the one resource */
        res = pdev->resource;
        size = resource_size(res);

        info->device    = &pdev->dev;
        info->platform  = plat;

        info->regs = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(info->regs)) {
                err = PTR_ERR(info->regs);
                goto exit_error;
        }

        dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs);

        if (!plat->sets || plat->nr_sets < 1) {
                err = -EINVAL;
                goto exit_error;
        }

        sets = plat->sets;
        nr_sets = plat->nr_sets;

        info->mtd_count = nr_sets;

        /* allocate our information */

        size = nr_sets * sizeof(*info->mtds);
        info->mtds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
        if (info->mtds == NULL) {
                err = -ENOMEM;
                goto exit_error;
        }

        /* initialise all possible chips */

        nmtd = info->mtds;

        for (setno = 0; setno < nr_sets; setno++, nmtd++, sets++) {
                struct mtd_info *mtd = nand_to_mtd(&nmtd->chip);

                pr_debug("initialising set %d (%p, info %p)\n",
                         setno, nmtd, info);

                mtd->dev.parent = &pdev->dev;
                s3c2410_nand_init_chip(info, nmtd, sets);

                err = nand_scan(&nmtd->chip, sets ? sets->nr_chips : 1);
                if (err)
                        goto exit_error;

                s3c2410_nand_add_partition(info, nmtd, sets);
        }
        /* initialise the hardware */
        err = s3c2410_nand_inithw(info);
        if (err != 0)
                goto exit_error;

        err = s3c2410_nand_cpufreq_register(info);
        if (err < 0) {
                dev_err(&pdev->dev, "failed to init cpufreq support\n");
                goto exit_error;
        }

        if (allow_clk_suspend(info)) {
                dev_info(&pdev->dev, "clock idle support enabled\n");
                s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND);
        }

        return 0;

 exit_error:
        s3c24xx_nand_remove(pdev);

        if (err == 0)
                err = -EINVAL;
        return err;
}

参考文章

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

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

标签:info,set,struct,smdk,Flash,nand,platform,linux,驱动
From: https://www.cnblogs.com/zyly/p/16756273.html

相关文章

  • Linux新建用户设置root权限
    一、建立新用户1.登录root用户suroot2.创建新用户sudouseradd-r-m-s/bin/bash用户名其中参数的意义如下:        -r:建立系统账号        -m:......
  • 3.4.1.1单独创建一个驱动文件夹
    3.4.1的基础上,在drivers/char目录下,创建一个目录。mkdirhello_drive;把hello.c驱动文件复制到该目录下,创建一个Kconfig文件,touchKconfig;并编辑如下代码:......
  • linux --- 虚拟文件系统
    Linux内核包含了文件管理子系统组件,它主要实现了虚拟文件系统(VirtualFileSystem,VFS),虚拟文件系统屏蔽了各种硬件上的差异以及具体实现的细节,为所有的硬件设备提供统一的......
  • 纯编程式驱动IOC
    编程式创建beanpublicstaticvoidmain(String[]args)throwsException{AnnotationConfigApplicationContextctx=newAnnotationConfigApplicationContext();......
  • Linux系列
    入门到精通​01-Linux发展介绍​02-Linux系统安装​03-Linux-Shell​04-Linux文件管理​05-Linux文件编辑​06-Linux用户管理​07-Linux基本权限​08-Linu......
  • 如何将linux设置成网关
    如何将linux设置成网关打开网关linux的端口转发功能:echo'1'>/proc/sys/net/ipv4/ip_forward在VMWare中创建一个仅主机的内网:进入本机配置该网络IP分配为DHCP:......
  • Linux VI编辑器
    编辑保存退出基本命令就不多赘述了yy复制当前行(n)yy复制当前以下n行dd删除当前行(n)dd删除当前以下n行p粘贴/(搜索字符)搜索......
  • Linux-内存
    kswapd_init  kswapd_run    kswapd      kswapd_try_to_sleep        prepare_kswapd_sleep       ......
  • Docker专题-Linux下Docker安装及卸载
    ​​学习资料链接​​一、Docker安装1.选择国内的云服务商,这里选择阿里云为例curl-sSLhttp://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/internet......
  • 【linux基础】ubuntu系统NVIDIA驱动安装
    前言在安装GPU环境下的软件工具,特别是CUDA/CUDNN等,一定要先把GPU环境搭建好。NVIDIA驱动安装会遇到各种问题,真希望黄教主可以将各个工具如何安装使用讲解的更加细致、清楚一......