移植u-boot2012.04.1 -》2440 (三)nandflash 识别

来源:互联网 发布:台湾大学知乎 编辑:程序博客网 时间:2024/06/14 17:21

    board_init_r 函数中,两个重要的过程就是 norflash 的识别和 nandflash 的识别,norflash 的识别过程以及如何移植前边已经分析过,本文首先会分析 smdk2410 nandflash 的识别过程,根据 2410 与 2440 之间的差别,进行移植。

    在分析之前,先来回顾一下 nandflash 的操作。

一、nandflash 操作

  1、发命令


    CLE:commond latch enable 为高电平

    CE:片选 低电平

    ALE:address latch enable 为低电平

 2、发地址


    CLE:commond latch enable 为低电平

    CE:片选 低电平

    ALE:address latch enable 为高电平

    我的这个 nanflash 页大小为2K,地址发送需要5个周期

  3、发数据


    CLE:commond latch enable 为低电平

    CE:片选 低电平

    ALE:address latch enable 为低电平

  4、读ID


   先发出 0x90 命令,然后再发送地址 0x00 ,然后读5个周期,第一个周期是厂商ID,第二个周期是芯片ID 我这个是 0xDA,后面三个周期也是芯片相关的东西,比如页大小,重点看下 4th 读回的含义。


    正常我这个读出来是 0x95 1001 0101B,页大小 01 2KB,Block size 128KB,位宽 x8 等等

二、smdk2410 nandflash 识别过程

    前边移植过 norflash,它是先读ID,然后与已知的信息进行匹配,匹配成功则能成功识别,分析完 nandflash 的代码你会发现是一样的,先来看一下移植 Norflash 部分时,nandflash 部分的错误信息。


    搜索一下“NAND:”,发现在arch\arm\lib\board.c 中打印,紧跟着就是 nand_init()

nand_init()->static void nand_init_chip(int i){    struct mtd_info *mtd = &nand_info[i];    struct nand_chip *nand = &nand_chip[i];    mtd->priv = nand;//使 nand 作为 mtd 的私有数据    //IO_ADDR_R IO_ADDR_W 指向 nandflash 相关寄存器的基地址    nand->IO_ADDR_R = nand->IO_ADDR_W = (void  __iomem *)0x4E000000;    if (board_nand_init(nand))    if (nand_scan(mtd, 1))    nand_register(i);}

    这里定义了两个结构体,struct mtd_info 和 struct nand_chip 分别对应于两层,nand_chip 看到 "chip" 我们就应该知道它是"芯片"相关的底层函数,它知道操作哪些寄存器进行读写等,但是它却不知道发送读写哪些数据。相反 mtd_info 对应于上层的统一的接口,它知道读写哪些数据,间接调用底层 nand_chip 进行 nandflash 操作。这样分层是有好处的,对于大量的芯片,我们只需要创建或修改 nand_chip 结构,上层的 mtd 接口无需更改。

    drivers\mtd\nand\s3c2410_nand.c -> board_nand_init

int board_nand_init(struct nand_chip *nand){u_int32_t cfg;u_int8_t tacls, twrph0, twrph1;struct s3c24x0_clock_power *clk_power = s3c24x0_get_base_clock_power();struct s3c2440_nand *nand_reg = s3c2440_get_base_nand();// 我们在串口看到的打印信息debug("board_nand_init()\n");// 使能nandflash 时钟writel(readl(&clk_power->clkcon) | (1 << 4), &clk_power->clkcon);/* CONFIG_S3C24XX_CUSTOM_NAND_TIMING 没有定义 */#if defined(CONFIG_S3C24XX_CUSTOM_NAND_TIMING)tacls  = CONFIG_S3C24XX_TACLS;twrph0 = CONFIG_S3C24XX_TWRPH0;twrph1 =  CONFIG_S3C24XX_TWRPH1;#else// 执行这个分支,最好修改符合我们 nandflash 的时序tacls = 4;twrph0 = 8;twrph1 = 8;#endif// 根据上面的三个参数,配置 nfconf nandflash 控制寄存器cfg = S3C2410_NFCONF_EN;cfg |= S3C2410_NFCONF_TACLS(tacls - 1);cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);writel(cfg, &nand_reg->nfconf);/* initialize nand_chip data structure */nand->IO_ADDR_R = (void *)&nand_reg->nfdata;nand->IO_ADDR_W = (void *)&nand_reg->nfdata;nand->select_chip = NULL;/* read_buf and write_buf are default *//* read_byte and write_byte are default */#ifdef CONFIG_NAND_SPL// 这个宏没有定义nand->read_buf = nand_read_buf;#endif/* hwcontrol 是最底层的操作函数 */nand->cmd_ctrl = s3c2410_hwcontrol;nand->dev_ready = s3c2410_dev_ready;#ifdef CONFIG_S3C2410_NAND_HWECC// 硬件 ECC 没有定义nand->ecc.hwctl = s3c2410_nand_enable_hwecc;nand->ecc.calculate = s3c2410_nand_calculate_ecc;nand->ecc.correct = s3c2410_nand_correct_data;nand->ecc.mode = NAND_ECC_HW;nand->ecc.size = CONFIG_SYS_NAND_ECCSIZE;nand->ecc.bytes = CONFIG_SYS_NAND_ECCBYTES;#else// 执行这个分支,采用软件 ECCnand->ecc.mode = NAND_ECC_SOFT;#endif#ifdef CONFIG_S3C2410_NAND_BBT// 没有定义nand->options = NAND_USE_FLASH_BBT;#else// 执行这个分支nand->options = 0;#endifdebug("end of nand_init\n");return 0;}
    这个函数主要的工作就是配置寄存器,初始化 nandflash 了,对于 2410 仅仅配置 nfconf 寄存器就可以了,2440 还有 nfcont 寄存器。那么,先做如下修改(不同的nandflash的时序要求不一样,看看自己以前的nandflash 实验的参数):
#if defined(CONFIG_S3C24XX_CUSTOM_NAND_TIMING)tacls  = CONFIG_S3C24XX_TACLS;twrph0 = CONFIG_S3C24XX_TWRPH0;twrph1 =  CONFIG_S3C24XX_TWRPH1;#else//tacls = 4;//twrph0 = 8;//twrph1 = 8;tacls = 1;twrph0 = 1;twrph1 = 0;#endif//cfg = S3C2410_NFCONF_EN;//cfg |= S3C2410_NFCONF_TACLS(tacls - 1);//cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);//cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);cfg = (tacls << 12)|(twrph0 << 8)|(twrph1 << 4);//新增writel(cfg, &nand_reg->nfconf);cfg = (1<<4)|(1<<1)|(1<<0);//新增writel(cfg, &nand_reg->nfcont);
    这个函数除了配置 nfconf 和 nfcont 之外,还指定了两个底层的函数,首先是 s3c2410_hwcontrol
static void s3c2410_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl){struct nand_chip *chip = mtd->priv;struct s3c2440_nand *nand = s3c2440_get_base_nand();debug("hwcontrol(): 0x%02x 0x%02x\n", cmd, ctrl);if (ctrl & NAND_CTRL_CHANGE) {ulong IO_ADDR_W = (ulong)nand;if (!(ctrl & NAND_CLE))IO_ADDR_W |= S3C2410_ADDR_NCLE;if (!(ctrl & NAND_ALE))IO_ADDR_W |= S3C2410_ADDR_NALE;chip->IO_ADDR_W = (void *)IO_ADDR_W;if (ctrl & NAND_NCE)writel(readl(&nand->nfconf) & ~S3C2410_NFCONF_nFCE,       &nand->nfconf);elsewritel(readl(&nand->nfconf) | S3C2410_NFCONF_nFCE,       &nand->nfconf);}if (cmd != NAND_CMD_NONE)writeb(cmd, chip->IO_ADDR_W);}
   理解 uboot 作者的意图是至关重要的,首先看参数,cmd 命令,函数的最后写到 chip->IO_ADDR_W 里去,前面我们指定的 chip->IO_ADDR_W = (void *)&nand_reg->nfdata; 显然将命令发送到数据寄存器是错误,我们看到在发送之前 chip->IO_ADDR_W = (void *)IO_ADDR_W; IO_ADDR_W 这个变量是根据 ctrl 来计算的,它有以下取值

    ctrl :!NAND_CLE ,             S3C2410_ADDR_NCLE == 8    ->地址

    ctrl :!NAND_ALE ,              S3C2410_ADDR_NALE == 4    -> 指令

    ctrl : (!NAND_CLE) | (!NAND_ALE)    -> 8 | 4 == 12   -> 数据

struct s3c2410_nand {u32nfconf;u32nfcmd;u32nfaddr;u32nfdata;u32nfstat;u32nfecc;};
    2410寄存器的偏移量确实是吻合的,2410吻合了,2440呢?
struct s3c2440_nand {u32nfconf;u32nfcont;u32nfcmd;u32nfaddr;u32nfdata;u32nfeccd0;u32nfeccd1;u32nfeccd;u32nfstat;u32nfstat0;u32nfstat1;};
    显然,不修改的话,寄存器就都搞错了,作如下修改:
if (!(ctrl & NAND_CLE))IO_ADDR_W |= 12;<span style="white-space:pre"></span>//修改if (!(ctrl & NAND_ALE))IO_ADDR_W |= 8;         //<span style="font-family: Arial, Helvetica, sans-serif;">修改</span>if ((!(ctrl & NAND_CLE)) && (!(ctrl & NAND_ALE)))<span style="white-space:pre"></span>//新增加IO_ADDR_W = IO_ADDR_W + 4;    //8|12 == 12 != 16 因此 + 4
    紧接着看下边
if (ctrl & NAND_NCE)// 2410 nfconf bit 11 清零 选中片选writel(readl(&nand->nfconf) & ~S3C2410_NFCONF_nFCE,       &nand->nfconf);elsewritel(readl(&nand->nfconf) | S3C2410_NFCONF_nFCE,       &nand->nfconf);
    这个函数中还集成了片选和取消片选的功能,当 ctrl bit 0 为 1 时,选中片选,当然这也是 2410 的东西,做如下修改。
if (ctrl & NAND_NCE)writel(readl(&nand->nfcont) & ~(1<<1),        &nand->nfcont);elsewritel(readl(&nand->nfcont) | (1<<1),       &nand->nfcont);
   第二个函数是 s3c2410_dev_ready ,这个之前第一次编译时报错已经修改过了。    

    board_nand_init 函数结束,下面是 drivers\mtd\nand\nand.c -> nand_scan

int nand_scan(struct mtd_info *mtd, int maxchips){int ret;ret = nand_scan_ident(mtd, maxchips, NULL);if (!ret)ret = nand_scan_tail(mtd);return ret;}
    先来看第一个函数,drivers\mtd\nand\nand_base.c -> nand_scan_ident
int nand_scan_ident(struct mtd_info *mtd, int maxchips,    const struct nand_flash_dev *table){int i, busw, nand_maf_id, nand_dev_id;struct nand_chip *chip = mtd->priv;const struct nand_flash_dev *type;/* chip->options == 0 ,busw == 0 */busw = chip->options & NAND_BUSWIDTH_16;/* 设置默认的操作函数 */nand_set_defaults(chip, busw);/* 读芯片类型 */type = nand_get_flash_type(mtd, chip, busw,&nand_maf_id, &nand_dev_id, table);/* 找不到时,打印下面的信息,跟我们刚开始时一样 */if (IS_ERR(type)) {#ifndef CONFIG_SYS_NAND_QUIET_TESTprintk(KERN_WARNING "No NAND device found!!!\n");#endifchip->select_chip(mtd, -1);return PTR_ERR(type);}/* maxchips == 1 for 循环不执行 */for (i = 1; i < maxchips; i++) {...}#ifdef DEBUGif (i > 1)printk(KERN_INFO "%d NAND chips detected\n", i);#endif/* Store the number of chips and calc total size for mtd */chip->numchips = i;mtd->size = i * chip->chipsize;return 0;}
    drivers\mtd\nand\nand_base.c -> nand_set_defaults
static void nand_set_defaults(struct nand_chip *chip, int busw){/* check for proper chip_delay setup, set 20us if not */if (!chip->chip_delay)chip->chip_delay = 20;/* check, if a user supplied command function given */if (chip->cmdfunc == NULL)chip->cmdfunc = nand_command;/* check, if a user supplied wait function given */if (chip->waitfunc == NULL)chip->waitfunc = nand_wait;if (!chip->select_chip)chip->select_chip = nand_select_chip;if (!chip->read_byte)chip->read_byte = busw ? nand_read_byte16 : nand_read_byte;if (!chip->read_word)chip->read_word = nand_read_word;if (!chip->block_bad)chip->block_bad = nand_block_bad;if (!chip->block_markbad)chip->block_markbad = nand_default_block_markbad;if (!chip->write_buf)chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;if (!chip->read_buf)chip->read_buf = busw ? nand_read_buf16 : nand_read_buf;if (!chip->verify_buf)chip->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf;if (!chip->scan_bbt)chip->scan_bbt = nand_default_bbt;if (!chip->controller)chip->controller = &chip->hwcontrol;}
    在这里我们先看两个函数,nand_command 和 nand_select_chip ,关于读写的函数后边再说。
    drivers\mtd\nand\nand_base.c -> nand_select_chip

static void nand_select_chip(struct mtd_info *mtd, int chipnr){struct nand_chip *chip = mtd->priv;switch (chipnr) {case -1:chip->cmd_ctrl(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE);break;case 0:break;default:BUG();}}
    很有意思,nand_select_chip(,-1)取消片选,没有其它功能了,因为我们片选的功能在  s3c2410_hwcontrol 中实现了,因此它要不要其实都行,不做修改。
    drivers\mtd\nand\nand_base.c -> nand_command ,很有意思,我们以读ID为例,分析这个函数。
    chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);

static void nand_command(struct mtd_info *mtd, unsigned int command, int column, int page_addr){register struct nand_chip *chip = mtd->priv;int ctrl = NAND_CTRL_CLE | NAND_CTRL_CHANGE;uint32_t rst_sts_cnt = CONFIG_SYS_NAND_RESET_CNT;/* commmand == NAND_CMD_READID */if (command == NAND_CMD_SEQIN) {...}/* * ctrl ==  NAND_CTRL_CLE | NAND_CTRL_CHANGE * #define NAND_CTRL_CLE (NAND_NCE | NAND_CLE) * 选中片选,发送读ID 命令 0x90 */chip->cmd_ctrl(mtd, command, ctrl);ctrl = NAND_CTRL_ALE | NAND_CTRL_CHANGE;if (column != -1) {if (chip->options & NAND_BUSWIDTH_16)//8bit 不执行column >>= 1;/* * ctrl ==  NAND_CTRL_ALE | NAND_CTRL_CHANGE * 发送 0x00 到地址寄存器 */chip->cmd_ctrl(mtd, column, ctrl);ctrl &= ~NAND_CTRL_CHANGE;}if (page_addr != -1) {// 不执行...}/* 取消片选 */chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);.../* Apply this short delay always to ensure that we do wait tWB in * any case on any machine. */ndelay(100);nand_wait_ready(mtd);}
    看来 nand_command 对于我们读 ID 来说是不需要修改的,其实这个函数对应于小页的 nandflash ,后边正确识别之后会判断页大小,大页的 nandflash 会使用 nand_command_lp 函数。
    真正的识别过程从这里开始 drivers\mtd\nand\nand_base.c -> nand_get_flash_type

/* 读ID的过程刚分析完毕 */chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);/* Read manufacturer and device IDs */*maf_id = chip->read_byte(mtd);*dev_id = chip->read_byte(mtd);/* 有些时候读一次不靠谱,所以多读几次 */chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);printf("maf_id:%x, dev_id:%x\n", *maf_id, *dev_id);for (i = 0; i < 2; i++)id_data[i] = chip->read_byte(mtd);if (id_data[0] != *maf_id || id_data[1] != *dev_id) {printk(KERN_INFO "%s: second ID read did not match "       "%02x,%02x against %02x,%02x\n", __func__,       *maf_id, *dev_id, id_data[0], id_data[1]);return ERR_PTR(-ENODEV);}/* 所有已知的 nandflash 参数都在 nand_flash_ids 中 */if (!type)type = nand_flash_ids;/* 查找匹配 */for (; type->name != NULL; type++)if (*dev_id == type->id)break;
    根据芯片手册,我这款nandflash deviceid 是 0xDA ,nand_flash_ids 数组中已经存在了,如果没有的话,自己照葫芦画瓢添加。简单修改到这,make 烧写看看效果。

    OK,nandflash 的识别是已经没有问题了。执行nand dump nand read/write 等函数都没啥问题。

    在后边烧写 yaffs 文件系统时,发现这个版本的 uboot 对于 yaffs 文件系统的烧写是有 Bug 的,下面来分析一下。

    首先 nand write.yaffs 这个命令不被识别,在 smdk2440.h 中增加一个定义

        #define CONFIG_CMD_NAND_YAFFS

    增加完这个功能还没完事,烧写还有 bug 继续分析。 

    我们在命令行输入 nand dump/read/write/yaffs 等命令时,调用到 common\cmd_nand.c -> do_nand

    
               if (!strcmp(s, ".yaffs")) {if (read) {printf("Unknown nand command suffix '%s'.\n", s);return 1;}ret = nand_write_skip_bad(nand, off, &rwsize,(u_char *)addr, WITH_YAFFS_OOB);

    在 do_nand 函数中,如果我们输入了 nand write.yaffs 则会调用 nand_write_skip_bad 函数。

    drivers\mtd\nand\nand_util.c   -> nand_write_skip_bad

need_skip = check_skip_len(nand, offset, *length);if (need_skip < 0) {printf ("Attempt to write outside the flash area\n");*length = 0;return -EINVAL;}if (!need_skip && !(flags & WITH_DROP_FFS)) {rval = nand_write (nand, offset, length, buffer);if (rval == 0)return 0;*length = 0;printf ("NAND write to offset %llx failed %d\n",offset, rval);return rval;}

    如果 nandflash 中没有坏块,那么 if (!need_skip && !(flags & WITH_DROP_FFS))条件成立,则使用 nand_write 进行烧写,而且烧写完成之后直接 return。

static inline int nand_write(nand_info_t *info, loff_t ofs, size_t *len, u_char *buf){return info->write(info, ofs, *len, (size_t *)len, buf);}
static int nand_write(struct mtd_info *mtd, loff_t to, size_t len,  size_t *retlen, const uint8_t *buf){struct nand_chip *chip = mtd->priv;int ret;/* Do not allow writes past end of device */if ((to + len) > mtd->size)return -EINVAL;if (!len)return 0;nand_get_device(chip, mtd, FL_WRITING);chip->ops.len = len;chip->ops.datbuf = (uint8_t *)buf;chip->ops.oobbuf = NULL;ret = nand_do_write_ops(mtd, to, &chip->ops);*retlen = chip->ops.retlen;nand_release_device(mtd);return ret;}
    在 nand_write 函数中 chip->ops.oobbuf = NULL ,压根就不会写 oob ,因此没有坏快时直接用 nand_write 烧写是不行的。真正的烧写函数在后边。
if (flags & WITH_YAFFS_OOB) {int page, pages;size_t pagesize = nand->writesize;size_t pagesize_oob = pagesize + nand->oobsize;struct mtd_oob_ops ops;ops.len = pagesize;ops.ooblen = nand->oobsize;ops.mode = MTD_OOB_AUTO;ops.ooboffs = 0;pages = write_size / pagesize_oob;for (page = 0; page < pages; page++) {WATCHDOG_RESET();ops.datbuf = p_buffer;ops.oobbuf = ops.datbuf + pagesize;rval = nand->write_oob(nand, offset, &ops);if (!rval)break;offset += pagesize;p_buffer += pagesize_oob;}}
    因此,我们需要做的就是,及时没有坏块时也不直接用 nand_write 来烧写。

    修改 :if (!need_skip && !(flags & WITH_DROP_FFS))
    改为 :if (!need_skip && !(flags & WITH_DROP_FFS) &&!(flags & WITH_YAFFS_OOB))

    就算改完,实验过程中烧写瞬间完成,显然不靠谱!还需要修改一个地方

ops.len = pagesize;ops.ooblen = nand->oobsize;ops.mode = MTD_OOB_RAW;  //修改ops.ooboffs = 0;pages = write_size / pagesize_oob;for (page = 0; page < pages; page++) {WATCHDOG_RESET();ops.datbuf = p_buffer;ops.oobbuf = ops.datbuf + pagesize;rval = nand->write_oob(nand, offset, &ops);if (rval)  //修改break;offset += pagesize;p_buffer += pagesize_oob;}

    这才算大功告成。










1 0
原创粉丝点击