三星提供的 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-Louis210.c 中的Louis210_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_HWECCchip->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;}#elsechip->ecc.mode = NAND_ECC_SOFT;#endifif (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(0xE020, 4);mp06 = (volatile unsigned long *)ioremap(0xE080, 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 文件系统只是暂时使用,后面会有专门的章节讲解制作文件系统。
可以看到共创了5个分区的设备,每个分区都包含了两个字符设备(mtd%d,mtd%dro)、一个块设备(mtdblock0).
其中MTD的块设备的主设备号为31,MTD的字符设备的主设备号为90