kernel(三)NAND FLASH

        三星提供的 NAND FLASH 驱动为 drivers/mtd/nand/s3c2410.c,只支持 S3C2410/S3C2440/S3C2412。我们需要修改它,以支持 s5pv210。在这个驱动中,根据 CPU 类型来决定如何操作硬件。里面定义了一个枚举类型


用来表示 Cpu 类型,我们需要在里面添加 TYPE_S5PV210

三星公用的 NAND 平台设备在 arch/arm/plat-samsung/devs.c 中定义

这里的 nand 设备名称默认为 s3c2410-nand,我们需要在 mach-smdkv210.c 中的smdkv210_machine_init 函数中调用一个函数来设置这个 name 属性,把它设置为 s5pv210-nand,这样驱动中就会得到 CPU 类型为 TYPE_S5PV210
s3c_nand_setname("s5pv210-nand");

下面开始具体的移植
首先在
arch/arm/plat-samsung/include/plat/regs-nand.h 针对 s5pv210 添加 NAND 寄存器索引

/* add by zjh */
#define S5PV210_NFCONF           S3C2410_NFREG(0x00)
#define S5PV210_NFCONT           S3C2410_NFREG(0x04)
#define S5PV210_NFCMD            S3C2410_NFREG(0x08)
#define S5PV210_NFADDR           S3C2410_NFREG(0x0C)
#define S5PV210_NFDATA           S3C2410_NFREG(0x10)
#define S5PV210_NFSTAT           S3C2410_NFREG(0x28)

#define S5PV210_NFECC            S3C2410_NFREG(0x20000)
#define S5PV210_NFECCCONF        S3C2410_NFREG(0x00) + (S5PV210_NFECC)
#define S5PV210_NFECCCONT        S3C2410_NFREG(0x20) + (S5PV210_NFECC)
#define S5PV210_NFECCSTAT        S3C2410_NFREG(0x30) + (S5PV210_NFECC)
#define S5PV210_NFECCSECSTAT     S3C2410_NFREG(0x40) + (S5PV210_NFECC)
#define S5PV210_NFECCPRGECC0     S3C2410_NFREG(0x90) + (S5PV210_NFECC)
#define S5PV210_NFECCPRGECC1     S3C2410_NFREG(0x94) + (S5PV210_NFECC)
#define S5PV210_NFECCPRGECC2     S3C2410_NFREG(0x98) + (S5PV210_NFECC)
#define S5PV210_NFECCPRGECC3     S3C2410_NFREG(0x9C) + (S5PV210_NFECC)
#define S5PV210_NFECCERL0        S3C2410_NFREG(0xC0) + (S5PV210_NFECC)
#define S5PV210_NFECCERL1        S3C2410_NFREG(0xC4) + (S5PV210_NFECC)
#define S5PV210_NFECCERL2        S3C2410_NFREG(0xC8) + (S5PV210_NFECC)
#define S5PV210_NFECCERL3        S3C2410_NFREG(0xCC) + (S5PV210_NFECC)
#define S5PV210_NFECCERP0        S3C2410_NFREG(0xF0) + (S5PV210_NFECC)
#define S5PV210_NFECCERP1        S3C2410_NFREG(0xF4) + (S5PV210_NFECC)
然后修改 drivers/mtd/nand/s3c2410.c
static int s3c2410_nand_setrate(struct s3c2410_nand_info *info)
{
	struct s3c2410_platform_nand *plat = info->platform;
	int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;
	int tacls, twrph0, twrph1;
	unsigned long clkrate = clk_get_rate(info->clk);
	unsigned long uninitialized_var(set), cfg, uninitialized_var(mask);
	unsigned long flags;

	/* calculate the timing information for the controller */

	info->clk_rate = clkrate;
	clkrate /= 1000;	/* turn clock into kHz for ease of use */

	if (plat != NULL) {
		tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max);
		twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8);
		twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8);
	} else {
		/* default timings */
		tacls = tacls_max;
		twrph0 = 8;
		twrph1 = 8;
	}

	if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {
		dev_err(info->device, "cannot get suitable timings\n");
		return -EINVAL;
	}

	dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n",
		tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate),
						twrph1, to_ns(twrph1, clkrate));

	switch (info->cpu_type) {
	case TYPE_S3C2410:
		mask = (S3C2410_NFCONF_TACLS(3) |
			S3C2410_NFCONF_TWRPH0(7) |
			S3C2410_NFCONF_TWRPH1(7));
		set = S3C2410_NFCONF_EN;
		set |= S3C2410_NFCONF_TACLS(tacls - 1);
		set |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
		set |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);
		break;

	case TYPE_S3C2440:
	case TYPE_S3C2412:
		mask = (S3C2440_NFCONF_TACLS(tacls_max - 1) |
			S3C2440_NFCONF_TWRPH0(7) |
			S3C2440_NFCONF_TWRPH1(7));

		set = S3C2440_NFCONF_TACLS(tacls - 1);
		set |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
		set |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
		break;
		
	/* add by JerryGou */
	case TYPE_S5PV210:
		mask = (0xF << 12) | (0xF << 8) | (0xF << 4);

		set = (tacls + 1) << 12;
		set |= (twrph0 - 1 + 1) << 8;
		set |= (twrph1 - 1 + 1) << 4;
		break;
	/*add end*/
	
        default:
		BUG();
	}

	local_irq_save(flags);

	cfg = readl(info->regs + S3C2410_NFCONF);
	cfg &= ~mask;
	cfg |= set;
	writel(cfg, info->regs + S3C2410_NFCONF);

	local_irq_restore(flags);

	dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg);

	return 0;
}


static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
				   struct s3c2410_nand_mtd *nmtd,
				   struct s3c2410_nand_set *set)
{
	struct nand_chip *chip = &nmtd->chip;
	void __iomem *regs = info->regs;

	chip->write_buf    = s3c2410_nand_write_buf;
	chip->read_buf     = s3c2410_nand_read_buf;
	chip->select_chip  = s3c2410_nand_select_chip;
	chip->chip_delay   = 50;
	chip->priv	   = nmtd;
	chip->options	   = set->options;
	chip->controller   = &info->controller;

	switch (info->cpu_type) {
	case TYPE_S3C2410:
		chip->IO_ADDR_W = regs + S3C2410_NFDATA;
		info->sel_reg   = regs + S3C2410_NFCONF;
		info->sel_bit	= S3C2410_NFCONF_nFCE;
		chip->cmd_ctrl  = s3c2410_nand_hwcontrol;
		chip->dev_ready = s3c2410_nand_devready;
		break;

	case TYPE_S3C2440:
		chip->IO_ADDR_W = regs + S3C2440_NFDATA;
		info->sel_reg   = regs + S3C2440_NFCONT;
		info->sel_bit	= S3C2440_NFCONT_nFCE;
		chip->cmd_ctrl  = s3c2440_nand_hwcontrol;
		chip->dev_ready = s3c2440_nand_devready;
		chip->read_buf  = s3c2440_nand_read_buf;
		chip->write_buf	= s3c2440_nand_write_buf;
		break;

	case TYPE_S3C2412:
		chip->IO_ADDR_W = regs + S3C2440_NFDATA;
		info->sel_reg   = regs + S3C2440_NFCONT;
		info->sel_bit	= S3C2412_NFCONT_nFCE0;
		chip->cmd_ctrl  = s3c2440_nand_hwcontrol;
		chip->dev_ready = s3c2412_nand_devready;

		if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT)
			dev_info(info->device, "System booted from NAND\n");

		break;
	
	/* add by JerryGou */
	case TYPE_S5PV210:
		chip->IO_ADDR_W = regs + S5PV210_NFDATA;
		info->sel_reg   = regs + S5PV210_NFCONT;
		info->sel_bit	= (1 << 1);
		chip->cmd_ctrl  = s5pv210_nand_hwcontrol;
		chip->dev_ready = s5pv210_nand_devready;
		break;
	/*add end*/
	}

	chip->IO_ADDR_R = chip->IO_ADDR_W;

	nmtd->info	   = info;
	nmtd->mtd.priv	   = chip;
	nmtd->mtd.owner    = THIS_MODULE;
	nmtd->set	   = set;

#ifdef CONFIG_MTD_NAND_S3C2410_HWECC
	chip->ecc.calculate = s3c2410_nand_calculate_ecc;
	chip->ecc.correct   = s3c2410_nand_correct_data;
	chip->ecc.mode	    = NAND_ECC_HW;
	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;
	}
#else
	chip->ecc.mode	    = NAND_ECC_SOFT;
#endif

	if (set->ecc_layout != NULL)
		chip->ecc.layout = set->ecc_layout;

	if (set->disable_ecc)
		chip->ecc.mode	= NAND_ECC_NONE;

	switch (chip->ecc.mode) {
	case NAND_ECC_NONE:
		dev_info(info->device, "NAND ECC disabled\n");
		break;
	case NAND_ECC_SOFT:
		dev_info(info->device, "NAND soft ECC\n");
		break;
	case NAND_ECC_HW:
		dev_info(info->device, "NAND hardware ECC\n");
		break;
	default:
		dev_info(info->device, "NAND ECC UNKNOWN\n");
		break;
	}

	/* If you use u-boot BBT creation code, specifying this flag will
	 * let the kernel fish out the BBT from the NAND, and also skip the
	 * full NAND scan that can take 1/2s or so. Little things... */
	if (set->flash_bbt) {
		chip->bbt_options |= NAND_BBT_USE_FLASH;
		chip->options |= NAND_SKIP_BBTSCAN;
	}
}
我们要让 drivers/mtd/nand/s3c2410.c 被编译进内核,需要修改 drivers/mtd/nand/Kconfig  

        添加了 ARCH_S5PV210,如果不添加 ARCH_S5PV210,配置菜单不会出现这个选项。

drivers/mtd/nand/s3c2410.c 中通过名称“nand”获得时钟 info->clk = devm_clk_get(&pdev->dev, "nand");
s5pv210
的时钟定义在 arch/arm/mach-s5pv210/clock.c 中,这里面没有针对 nand 定义时钟,因此需
要添加,在
init_clocks_off 数组里面添加(参考 s5pv210 手册时钟章节)

arch/arm/plat-samsung/devs.c 中定义的 nand 平台设备如下:

这里用了一个宏 CONFIG_S3C_DEV_NAND,这个宏默认没有选中,因此需要修改
arch/arm/mach-s5pv210/Kconfig

在后面添加了 select S3C_DEV_NAND
另外在 nand 平台设备中使用了 S3C_PA_NAND,这个在 s5pv210 中也没定义,需要在
arch/arm/mach-s5pv210/include/mach/map.h 中定义

arch/arm/mach-s5pv210/mach-smdkv210.c 添加头文件

定义 nand 平台相关的数据

/* nand info (add by JerryGou) */
static struct mtd_partition smdk_default_nand_part[] = {
    [0] = {
        .name    = "bootloader",
        .size    = SZ_1M,
        .offset    = 0,
    },
    [1] = {
        .name    = "params",
        .offset = MTDPART_OFS_APPEND,
        .size    = SZ_1M,
    },
    [2] = {
        .name    = "log",
        .offset = MTDPART_OFS_APPEND,
        .size    = SZ_1M + SZ_2M,
    },
    [3] = {
        .name    = "kernel",
        .offset    = MTDPART_OFS_APPEND,
        .size    = SZ_1M + SZ_2M + SZ_2M,
    },

    [4] = {
        .name    = "rootfs",
        .offset = MTDPART_OFS_APPEND,
        .size    = MTDPART_SIZ_FULL,
    }
};

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

static struct s3c2410_platform_nand smdk_nand_info = {
    .tacls        = 12,
    .twrph0        = 12,
    .twrph1        = 5,
    .nr_sets    = ARRAY_SIZE(smdk_nand_sets),
    .sets        = smdk_nand_sets,
};
这里定义了 nand 的分区,要和 u-boot 中的分区一致,以及时序参数
static void s5pv210_nand_gpio_cfg(void)
{
	volatile unsigned long *mp01;
	volatile unsigned long *mp03;
	volatile unsigned long *mp06;
	
	mp01 = (volatile unsigned long *)ioremap(0xE02002E0, 4);
	mp03 = (volatile unsigned long *)ioremap(0xE0200320, 4);
	mp06 = (volatile unsigned long *)ioremap(0xE0200380, 4);
	
	*mp01 &= ~(0xFFFF << 8);
	*mp01 |= (0x3333 << 8);
	*mp03 = 0x22222222;
	*mp06 = 0x22222222;
	
	iounmap(mp01);
	iounmap(mp03);
	iounmap(mp06);
}
/*add end*/
这里定义了一个用于针对 nand 设置相关信号引脚的函数。
smdkv210_devices 设备列表中添加 nand 设备  

smdkv210_machine_init 函数中设置 nand


配置内核支持 nand

Device Drivers --->
    <*> Memory Technology Device (MTD) support --->
        <*> Caching block device access to MTD devices
            <*> NAND Device Support --->
                <*> NAND Flash support for Samsung S3C SoCs
执行 make uImage 编译内核,下载到内存运行


成功识别 nand。可以执行 cat /proc/mtd 查看分区信息

下面制作一个 jffs2 文件系统,将其烧写到 nand 进行测试。
制作 jffs2 文件系统需要用到 mtd-utils 中的工具,首先安装 mtd-utils
root@ubuntu:~/code# apt-get install mtd-utils
现在可以使用 mkfs.jffs2 制作 jffs2 文件系统了。
root@ubuntu:~/code# mkfs.jffs2 -d rootfs -o rootfs.jffs2 -s 2048 -e 0x20000 –n
-d:指定根文件系统目录
-o:指定输出文件
-s:指定页大小 2K(根据 NAND FLASH 芯片手册)
-e:指定块擦除大小 128K (根据 NAND FLASH 芯片手册)
-n:指定不要在每个擦除块上添加清除标记
把生成的 rootfs.jffs2 拷贝到 tftp 服务器目录,然后使用 u-boot 将其烧写到 nand 的 rootfs 分区
root@ubuntu:~/code# cp rootfs.jffs2 /mnt/hgfs/Bin/


设置启动参数 set bootargs root=/dev/mtdblock4 rootfstype=jffs2 console=ttySAC0,115200


root=/dev/mtdblock4 表示文件系统存放在第 4 个分区。
配置内核支持 jffs2 文件系统
File systems --->
    [*] Miscellaneous filesystems --->
        <*> Journalling Flash File System v2 (JFFS2) support
重新编译内核, 下载内核 uImage 启动

启动rootfs.jffs2后以下报错,原因是/rootfs/lib/* 中文件问题,可以下载以制作好的文件系统rootfs文件系统下载



出错, ECC 校验错误,这是由于 u-boot 使用 8 位硬件 Ecc 烧写文件系统,而内核使用软件 ECC 读取文件系统,两者不一致导致的。 暂时把 nand 驱动改为不使用 ECC,下节实现硬件 ECC修改 arch/arm/mach-s5pv210/mach-smdkv210.c 中的 smdk_nand_sets 结构体变量

disable_ecc 属性设置为真。
重新编译内核,运行测试

成功挂载 jffs2 文件系统,比较慢。
jffs2 文件系统只是暂时使用,后面会有专门的章节讲解制作文件系统。

猜你喜欢

转载自blog.csdn.net/jerrygou/article/details/80715632