u-boot: NAND 驱动简介
文章目录
- 1. 前言
- 2. NAND 初始化
- 3. 访问 NAND 设备
- 3.1 查看 NAND 设备信息
- 3.1.1 查看 NAND 设备基本信息
- 3.1.2 查看 NAND 设备 MTD 分区
- 3.1.3 查看 NAND 设备坏块
- 3.2 NAND 擦除操作
- 3.3 NAND 写操作
- 3.4 NAND 读操作
- 3.5 其它 NAND 操作
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. NAND 初始化
下面以 Micron MT29F2G08AAD
型号的 Nand Flash
为例,说明 ARMv7
架构下 U-Boot
对 Nand Flash 设备的初始化
过程。首先,可以将相关代码划分为 U-Boot NAND 驱动硬件无关通用部分
和 硬件相关的 NAND FLASH 以及其 控制器 驱动
两部分。另外,抽象层次上,U-Boot
将 Nor Flash,Nand Flash
等类型设备,统一抽象为 MTD(Memory Technology Device)
类型设备,对这些类型设备的访问,都是通过 MTD 接口来间接进行。
board_init_r() /* common/board_r.c *//* 特定 板型 初始化:这里重点关注 NAND 控制器 的 初始化 */board_init() /* board/myirtech/myd_c335x/myd_c335x.c */...gpmc_init();/* putting a blanket check on GPMC based on ZeBu for now */gpmc_cfg = (struct gpmc *)GPMC_BASE;/* NAND 控制器 的一些寄存器配置 */...initr_nand()nand_init() /* drivers/mtd/nand/nand.c */nand_init_chip()struct mtd_info *mtd;#ifndef CONFIG_DM_NANDstruct nand_chip *nand = &nand_chip[i];ulong base_addr = base_address[i];#endif...mtd = &nand_info[i]; /* NAND 设备 的 MTD 数据对象 */mtd->priv = nand; /* MTD NAND 设备数据 */nand->IO_ADDR_R = nand->IO_ADDR_W = (void __iomem *)base_addr; /* NAND 设备 IO 地址空间 *//* 1. 特定 板型 的 NAND 初始化 */if (board_nand_init(nand)) /* drivers/mtd/nand/omap_gpmc.c */return;/* 2. 扫描识别并配置 NAND 控制器 上 挂接的 NAND 设备 */if (nand_scan(mtd, maxchips))return;/* 3. 注册 扫描到的、挂接在 NAND 控制器 上 NAND 设备 */nand_register(i);/* 1. 特定 板型 的 NAND 初始化 */
board_nand_init(nand).../** xloader/Uboot's gpmc configuration would have configured GPMC for* nand type of memory. The following logic scans and latches on to the* first CS with NAND type memory.* TBD: need to make this logic generic to handle multiple CS NAND* devices.*/while (cs < GPMC_MAX_CS) {/* Check if NAND type is set */if ((readl(&gpmc_cfg->cs[cs].config1) & 0xC00) == 0x800) {/* Found it!! */break;}cs++;}...nand->IO_ADDR_R = (void __iomem *)&gpmc_cfg->cs[cs].nand_dat;nand->IO_ADDR_W = (void __iomem *)&gpmc_cfg->cs[cs].nand_cmd;...nand->priv = &omap_nand_info[cs];nand->cmd_ctrl = omap_nand_hwcontrol;nand->options |= NAND_NO_PADDING | NAND_CACHEPRG;nand->chip_delay = 100;nand->ecc.layout = &omap_ecclayout; /* NAND ECC 数据 layout */...nand->options &= ~NAND_BUSWIDTH_16; /* 8 位数据宽度 */.../* select ECC scheme */
#if defined(CONFIG_NAND_OMAP_ECCSCHEME)/* NAND ECC 模式选择 */err = omap_select_ecc_scheme(nand, CONFIG_NAND_OMAP_ECCSCHEME,CONFIG_SYS_NAND_PAGE_SIZE, CONFIG_SYS_NAND_OOBSIZE);...switch (ecc_scheme) {...case OMAP_ECC_BCH8_CODE_HW: /* ECC 使用 硬件 BCH8 码 */#ifdef CONFIG_NAND_OMAP_ELM.../* intialize ELM for ECC error detection */elm_init(); /* ECC 错误检测硬件模块 ELM 初始化 */.../* populate ecc specific fields */nand->ecc.mode = NAND_ECC_HW; /* 硬件 ECC */nand->ecc.strength = 8;nand->ecc.size = SECTOR_BYTES;nand->ecc.bytes = 14;/* ECC 操作接口 */nand->ecc.hwctl = omap_enable_hwecc;nand->ecc.correct = omap_correct_data_bch;nand->ecc.calculate = omap_calculate_ecc;nand->ecc.read_page = omap_read_page_bch;...#else...#endif...}...info->ecc_scheme = ecc_scheme; /* OMAP_ECC_BCH8_CODE_HW */return 0;
#else...
#endif#ifdef CONFIG_NAND_OMAP_GPMC_PREFETCHnand->read_buf = omap_nand_read_prefetch;
#else...
#endifnand->dev_ready = omap_dev_ready;return 0;/* 2. 扫描识别并配置 NAND 控制器 上 挂接的 NAND 设备 */
nand_scan(mtd, maxchips) /* drivers/mtd/nand/nand_base.c */.../* 扫描识别 NAND 设备 和 参数, 设置操作接口 等 */ret = nand_scan_ident(mtd, maxchips, NULL);...struct nand_chip *chip = mtd->priv;struct nand_flash_dev *type;/* Set the default functions */nand_set_defaults(chip, chip->options & NAND_BUSWIDTH_16); /* 设置 NAND 设备缺省操作接口(读写等) */.../* 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 called twice, pointers that depend on busw may need to be reset */if (!chip->read_byte || chip->read_byte == nand_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 == nand_write_buf)chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;if (!chip->write_byte || chip->write_byte == nand_write_byte)chip->write_byte = busw ? nand_write_byte16 : nand_write_byte;if (!chip->read_buf || chip->read_buf == nand_read_buf)chip->read_buf = busw ? nand_read_buf16 : nand_read_buf;if (!chip->scan_bbt)chip->scan_bbt = nand_default_bbt; /* 坏块 选择/建立 接口 */.../* Read the flash type *//* 识别 NAND 设备类型 */type = nand_get_flash_type(mtd, chip, &nand_maf_id,&nand_dev_id, table);...u8 id_data[8];/* Select the device */chip->select_chip(mtd, 0);/** Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx)* after power-up.*/chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); /* 复位 NAND 设备 ID *//* Send the command for reading device ID */chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); /* 发送读取 NAND 设备 ID 命令 *//* Read manufacturer and device IDs */*maf_id = chip->read_byte(mtd); /* 读取 NAND 设备 制造商 ID */*dev_id = chip->read_byte(mtd); /* 读取 NAND 设备 ID */chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); /* 读取 NAND 芯片 ID *//* Read entire ID string */for (i = 0; i < 8; i++)id_data[i] = chip->read_byte(mtd);...if (!type)type = nand_flash_ids; /* 预定义的 NAND 设备列表: drivers/mtd/nand/nand_ids.c *//** 对比 读取到的 NAND 设备 ID 和 预定义表 nand_flash_ids[], * 看是否能找到匹配的表项.*/for (; type->name != NULL; type++) {if (is_full_id_nand(type)) { /* 对于 全 ID 标识的设备, 进行全 ID 匹配 */if (find_full_id_nand(mtd, chip, type, id_data, &busw)) /* 如果 是 设备全 ID 匹配, */goto ident_done; /* 完成识别工作 */} else if (*dev_id == type->dev_id) { /* 如果 不是 设备全 ID 匹配, 而是 设备 ID 匹配 */break; /* 做进一步的识别工作(如符合 ONFI/JEDEC 规范的设备匹配工作) */}}/* 可能满足 ONFI/JEDEC 规范 的 设备 识别 */chip->onfi_version = 0;if (!type->name || !type->pagesize) {/* Check if the chip is ONFI compliant */if (nand_flash_detect_onfi(mtd, chip, &busw)) /* 识别到 符合 ONFI 接口规范 的 NAND 设备 */goto ident_done; /* 完成识别工作 *//* Check if the chip is JEDEC compliant */if (nand_flash_detect_jedec(mtd, chip, &busw)) /* 识别到 符合 JEDEC 接口规范 的 NAND 设备 */goto ident_done;}/* * 目前无法识别设备:* . 无法从预定义 NAND 芯片列表 nand_flash_ids[] 匹配设备* . 设备不符合 ONFI/JEDEC 接口规范*/if (!type->name)return ERR_PTR(-ENODEV);if (!mtd->name)mtd->name = type->name; /* 设置 MTD NAND 设备名称 *//* 设置 NAND 设备容量 */chip->chipsize = (uint64_t)type->chipsize << 20;/* 设置 NAND 的 page size, OOB size, erase size */if (!type->pagesize && chip->init_size) {/* Set the pagesize, oobsize, erasesize by the driver */busw = chip->init_size(mtd, chip, id_data);} else if (!type->pagesize) {/* Decode parameters from extended ID */nand_decode_ext_id(mtd, chip, id_data, &busw);} else {nand_decode_id(mtd, chip, type, id_data, &busw);}/* Get chip options */chip->options |= type->options; /* 设置 NAND 设备选项 */...ident_done:.../* 坏块管理相关配置 */nand_decode_bbm_options(mtd, chip, id_data);int maf_id = id_data[0];/* Set the bad block position */if (mtd->writesize > 512 || (chip->options & NAND_BUSWIDTH_16))chip->badblockpos = NAND_LARGE_BADBLOCK_POS;elsechip->badblockpos = NAND_SMALL_BADBLOCK_POS;/** Bad block marker is stored in the last page of each block on Samsung* and Hynix MLC devices; stored in first two pages of each block on* Micron devices with 2KiB pages and on SLC Samsung, Hynix, Toshiba,* AMD/Spansion, and Macronix. All others scan only the first page.*/if (!nand_is_slc(chip) &&(maf_id == NAND_MFR_SAMSUNG ||maf_id == NAND_MFR_HYNIX))/* 三星 和 海力士 的 非 SLC 类型设备, 坏块标记存储在每个 block 的最后一个 page */chip->bbt_options |= NAND_BBT_SCANLASTPAGE;else if ((nand_is_slc(chip) &&(maf_id == NAND_MFR_SAMSUNG ||maf_id == NAND_MFR_HYNIX ||maf_id == NAND_MFR_TOSHIBA ||maf_id == NAND_MFR_AMD ||maf_id == NAND_MFR_MACRONIX)) ||(mtd->writesize == 2048 &&maf_id == NAND_MFR_MICRON))/* 一些厂家的 SLC 类型设备,坏块标记存储在 block 的 第1 和 第2 个 page */chip->bbt_options |= NAND_BBT_SCAN2NDPAGE;...chip->badblockbits = 8;chip->erase = single_erase; /* 设置 擦除 接口 *//* Do not replace user supplied command function! */if (mtd->writesize > 512 && chip->cmdfunc == nand_command)chip->cmdfunc = nand_command_lp; /* 覆盖命令为 大 page 接口 *//* 报告设备信息 (需要开启 CONFIG_MTD_DEBUG) */pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n",*maf_id, *dev_id);...pr_info("%d MiB, %s, erase size: %d KiB, page size: %d, OOB size: %d\n",(int)(chip->chipsize >> 20), nand_is_slc(chip) ? "SLC" : "MLC",mtd->erasesize >> 10, mtd->writesize, mtd->oobsize);return type; /* 返回是被的 NAND 设备类型 *//* 通过 NAND 控制器,选择 NAND 芯片 */chip->select_chip(mtd, -1);/* Store the number of chips and calc total size for mtd */chip->numchips = i;mtd->size = i * chip->chipsize; /* 记录 MTD NAND 设备容量 */return 0;if (!ret)/** 扫描识别收尾工作: * 从 前面 扫描识别到的 NAND 设备 和 参数,* 为 NAND 设备建立缓冲,设置 NAND 设备对应的 MTD NAND 设备对象。*/ret = nand_scan_tail(mtd);int i;struct nand_chip *chip = mtd->priv;struct nand_ecc_ctrl *ecc = &chip->ecc;struct nand_buffers *nbuf;/* 为 NAND 设备 创建 缓冲: ECC, 数据 */if (!(chip->options & NAND_OWN_BUFFERS)) {nbuf = kzalloc(sizeof(struct nand_buffers), GFP_KERNEL);chip->buffers = nbuf;} else {if (!chip->buffers)return -ENOMEM;}/* Set the internal oob buffer location, just after the page data *//* 和 NAND 页面存储一样, OOB 缓冲 紧邻 page 缓冲之后 */chip->oob_poi = chip->buffers->databuf + mtd->writesize;...switch (ecc->mode) {...case NAND_ECC_HW: /* 设置没有配置的 ECC 接口 */if (!ecc->read_page)ecc->read_page = nand_read_page_hwecc;if (!ecc->write_page)ecc->write_page = nand_write_page_hwecc;if (!ecc->read_page_raw)ecc->read_page_raw = nand_read_page_raw;if (!ecc->write_page_raw)ecc->write_page_raw = nand_write_page_raw;if (!ecc->read_oob)ecc->read_oob = nand_read_oob_std;if (!ecc->write_oob)ecc->write_oob = nand_write_oob_std;if (!ecc->read_subpage)ecc->read_subpage = nand_read_subpage;if (!ecc->write_subpage)ecc->write_subpage = nand_write_subpage_hwecc;...}/* For many systems, the standard OOB write also works for raw */if (!ecc->read_oob_raw)ecc->read_oob_raw = ecc->read_oob;if (!ecc->write_oob_raw)ecc->write_oob_raw = ecc->write_oob;// 一些其它 ECC 相关配置.../* Initialize state */chip->state = FL_READY; /* NAND 设备标记为 READY 状态 */.../* Fill in remaining MTD driver data */mtd->type = nand_is_slc(chip) ? MTD_NANDFLASH : MTD_MLCNANDFLASH; /* 设置 MTD NAND 设备类型: SLC 或 MLC */mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :MTD_CAP_NANDFLASH; /* MTD NAND 读写属性设置:只读、可读写 *//* 设置 MTD NAND 设备接口: 调用 NAND 设备接口 */mtd->_erase = nand_erase;mtd->_read = nand_read;mtd->_write = nand_write;mtd->_panic_write = panic_nand_write;mtd->_read_oob = nand_read_oob;mtd->_write_oob = nand_write_oob;mtd->_sync = nand_sync;mtd->_lock = NULL;mtd->_unlock = NULL;mtd->_block_isreserved = nand_block_isreserved;mtd->_block_isbad = nand_block_isbad;mtd->_block_markbad = nand_block_markbad;mtd->writebufsize = mtd->writesize;// MTD NAND ECC 相关设置...return 0;return ret;/* 3. 注册 扫描到的、挂接在 NAND 控制器 上 NAND 设备 */
nand_register(i);struct mtd_info *mtd;mtd = get_nand_dev_by_index(devnum);sprintf(dev_name[devnum], "nand%d", devnum);mtd->name = dev_name[devnum];#ifdef CONFIG_MTD_DEVICE/** Add MTD device so that we can reference it later* via the mtdcore infrastructure (e.g. ubi).*/add_mtd_device(mtd);
#endiftotal_nand_size += mtd->size / 1024;if (nand_curr_device == -1)nand_curr_device = devnum; /* 设置当前 NAND 设备编号 */return 0;
3. 访问 NAND 设备
3.1 查看 NAND 设备信息
3.1.1 查看 NAND 设备基本信息
U-Boot
提供一些 nand info
命令,可以查看 NAND
设备信息:
# nand infoDevice 0: nand0, sector size 128 KiBPage size 2048 bOOB size 64 bErase size 131072 bsubpagesize 512 boptions 0x4000000cbbt options 0x 8000
从上面看到,Nand Flash 设备:
o page 是 2KB 大小
o page 后跟的 OOB(Spare area) 是 64 Bytes
o 擦除 size 是 128KB,也就是 block size
3.1.2 查看 NAND 设备 MTD 分区
前面有说过,U-Boot 将 NAND 抽象为 MTD 设备进行访问,通过 mtdparts
命令,可以查看 NAND 设备的分区信息:
# mtdpartsdevice nand0 <nand.0>, # parts = 11#: name size offset mask_flags0: NAND.SPL 0x00020000 0x00000000 01: NAND.SPL.backup1 0x00020000 0x00020000 02: NAND.SPL.backup2 0x00020000 0x00040000 03: NAND.SPL.backup3 0x00020000 0x00060000 04: NAND.u-boot-spl-os 0x00040000 0x00080000 05: NAND.u-boot 0x00100000 0x000c0000 06: NAND.u-boot-env 0x00020000 0x001c0000 07: NAND.u-boot-env.backup10x00020000 0x001e0000 08: NAND.kernel 0x00800000 0x00200000 09: NAND.rootfs 0x0d600000 0x00a00000 0
10: NAND.userdata 0x02000000 0x0e000000 0active partition: nand0,0 - (NAND.SPL) 0x00020000 @ 0x00000000defaults:
mtdids : nand0=nand.0
mtdparts: mtdparts=nand.0:128k(NAND.SPL),128k(NAND.SPL.backup1),128k(NAND.SPL.backup2),128k(NAND.SPL.backup3),256k(NAND.u-boot-spl-os),1m(NAND.u-boot),128k(NAND.u-boot-env),128k(NAND.u-boot-env.backup1),8m(NAND.kernel),214m(NAND.rootfs),-(NAND.userdata)
3.1.3 查看 NAND 设备坏块
# nand badDevice 0 bad blocks:03e40000086c0000
发现了两个坏块,数据标记了它们的字节偏移
位置。
3.2 NAND 擦除操作
通过 nand erase
命令,可以对 NAND 发起对 NAND 的擦除
操作:
# nand erase c0000 100000
其中参数 c0000
是要擦除的起始位置
,相对于 NAND 设备开始位置的字节偏移
;100000
是要擦除的长度
字节数。这两个参数都是十六进制数字
。来看一下擦除过程的具体实现:
do_nand() /* cmd/nand.c */...nand_info_t *nand;...int dev = nand_curr_device; /* 默认选择当前 NAND 设备进行操作 */......cmd = argv[1]; /* "erase" */...nand = get_nand_dev_by_index(dev); /* 获取 NAND 设备 */if (strncmp(cmd, "erase", 5) == 0 || strncmp(cmd, "scrub", 5) == 0) {nand_erase_options_t opts;......printf("\nNAND %s: ", cmd); /* "NAND erase: " *//* 解析 擦除起始位置 和 长度 参数 到 @off 和 @size */if (mtd_arg_off_size(argc - o, argv + o, &dev, &off, &size,&maxsize, MTD_DEV_TYPE_NAND,nand->size) != 0)return 1;/* 切换到 要操作 的 目标 NAND 设备 */if (set_dev(dev))return 1;nand = get_nand_dev_by_index(dev);memset(&opts, 0, sizeof(opts));opts.offset = off;opts.length = size;...ret = nand_erase_opts(nand, &opts); /* 擦除操作 */...}ret = nand_erase_opts(nand, &opts); /* drivers/mtd/nand/nand_util.c */.../** @erase_length:要擦除的 block 数目* @erased_length: 已经擦除的 block 数目*/unsigned long erase_length, erased_length; /* in blocks */...erase_length = lldiv(opts->length + meminfo->erasesize - 1,meminfo->erasesize); /* 向上对齐到 erase size (block 大小) */...for (erased_length = 0;erased_length < erase_length;erase.addr += meminfo->erasesize) {...if (!opts->scrub) {/* 检查位于 @ofs 位置的 block 是不是坏块 */int ret = mtd_block_isbad(meminfo, erase.addr);if (ret > 0) { /* 坏块 */...if (!opts->spread)erased_length++; /* 非 nand erase.spread 命令, 坏块也计入擦除 block 数目 */continue; /* 跳过坏块: 坏块不做擦除动作,擦除坏块是非法操作 */}}erased_length++; /* 已擦除 block 数目 +1 */result = mtd_erase(meminfo, &erase); /* 擦除当前块 */...return mtd->_erase(mtd, instr);nand_erase()return nand_erase_nand(mtd, instr, 0);...struct nand_chip *chip = mtd->priv; /* MTD 转入 NAND 层操作 */.../* Grab the lock and see if the device is available */nand_get_device(mtd, FL_ERASING);/* Shift to get first page */page = (int)(instr->addr >> chip->page_shift); /* 擦除位置转换为 page 位置 */chipnr = (int)(instr->addr >> chip->chip_shift);/* Calculate pages in each block */pages_per_block = 1 << (chip->phys_erase_shift - chip->page_shift);/* Select the NAND device */chip->select_chip(mtd, chipnr);/* Loop through the pages */len = instr->len;instr->state = MTD_ERASING;while (len) {...status = chip->erase(mtd, page & chip->pagemask);single_erase()struct nand_chip *chip = mtd->priv;/* Send commands to erase a block *//* 发送 page 擦除操作命令 */chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);/* 等待擦除操作完成 */return chip->waitfunc(mtd, chip);/* Increment page address and decrement length *//* 移向下一个 page */len -= (1ULL << chip->phys_erase_shift);page += pages_per_block;...}instr->state = MTD_ERASE_DONE;...}
3.3 NAND 写操作
假定以命令 nand write 0x82000000 c0000 ${filesize}
发起写操作
。写操作
基本流程和擦除操作差不多,来看细节:
do_nand()...if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {...addr = (ulong)simple_strtoul(argv[2], NULL, 16); // 0x82000000read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */printf("\nNAND %s: ", read ? "read" : "write");s = strchr(cmd, '.');if (s && !strcmp(s, ".raw")) { // nand write.raw...} else {// @off = 0xc0000// @size = ${filesize}if (mtd_arg_off_size(argc - 3, argv + 3, &dev, &off,&size, &maxsize,MTD_DEV_TYPE_NAND,nand->size) != 0)return 1;if (set_dev(dev))return 1;...rwsize = size;if (!s || !strcmp(s, ".jffs2") ||!strcmp(s, ".e") || !strcmp(s, ".i")) {if (read)ret = nand_read_skip_bad(nand, off, &rwsize,NULL, maxsize,(u_char *)addr); /* 读取,会自动跳过坏块 */elseret = nand_write_skip_bad(nand, off, &rwsize,NULL, maxsize,(u_char *)addr,WITH_WR_VERIFY); /* 写入,会自动跳过坏块 */}}nand = get_nand_dev_by_index(dev);}nand_write_skip_bad()...size_t left_to_write = *length; /* 要写入的长度, 字节数 */...int need_skip;blocksize = nand->erasesize;/* 检查写入过程中, 要跳过的坏块数目 */need_skip = check_skip_len(nand, offset, *length, &used_for_write);...while (left_to_write > 0) {size_t block_offset = offset & (nand->erasesize - 1); /* 计算 block 内偏移 */.../* 写入时跳过坏块 */if (nand_block_isbad(nand, offset & ~(nand->erasesize - 1))) {printf("Skip bad block 0x%08llx\n",offset & ~(nand->erasesize - 1));offset += nand->erasesize - block_offset;continue;}/* 调整 写入 大小 */if (left_to_write < (blocksize - block_offset))write_size = left_to_write;elsewrite_size = blocksize - block_offset;/* 写入 block *//* * 注意前后的两个 nand_write() 不是同一个函数: * . 前一个 nand_write() 是 MTD 驱动层的 接口* . 后一个是 NAND 驱动层的 接口*/rval = nand_write(nand, offset, &truncated_write_size, p_buffer); /* drivers/mtd/mtcore.c */mtd_write()mtd->_write(mtd, to, len, retlen, buf);nand_write() /* drivers/mtd/nand/nand_base.c */struct mtd_oob_ops ops;nand_get_device(mtd, FL_WRITING);memset(&ops, 0, sizeof(ops));ops.len = len;ops.datbuf = (uint8_t *)buf;ops.mode = MTD_OPS_PLACE_OOB;ret = nand_do_write_ops(mtd, to, &ops); /* NAND 设备写操作 */*retlen = ops.retlen;nand_release_device(mtd);return ret;/* 写入后,再读回来验证一下数据是否正确 */if ((flags & WITH_WR_VERIFY) && !rval)rval = nand_verify(nand, offset,truncated_write_size, p_buffer);offset += write_size;p_buffer += write_size;...left_to_write -= write_size;}ret = nand_do_write_ops(mtd, to, &ops); /* NAND 设备写操作 */...struct nand_chip *chip = mtd->priv;uint32_t writelen = ops->len;......while (1) {int bytes = mtd->writesize; /* 按 page 写入 */...uint8_t *wbuf = buf; /* 数据 */...if (unlikely(oob)) {...} else {/* We still need to erase leftover OOB data */memset(chip->oob_poi, 0xff, mtd->oobsize); /* 没有指定 OOB 数据, 则将 Spare area 全部填充 0xff */}ret = chip->write_page(mtd, chip, column, bytes, wbuf,oob_required, page, cached,(ops->mode == MTD_OPS_RAW)); /* 写入一个 page 的数据 */nand_write_page()...chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);if (unlikely(raw))status = chip->ecc.write_page_raw(mtd, chip, buf, oob_required);else if (subpage)status = chip->ecc.write_subpage(mtd, chip, offset, data_len, buf, oob_required);else /* 这里只考虑这一种情形 */status = chip->ecc.write_page(mtd, chip, buf, oob_required);nand_write_page_hwecc()int i, eccsize = chip->ecc.size;int eccbytes = chip->ecc.bytes;int eccsteps = chip->ecc.steps;uint8_t *ecc_calc = chip->buffers->ecccalc;const uint8_t *p = buf; /* 要写入的数据 */uint32_t *eccpos = chip->ecc.layout->eccpos;/* 写数据到 NAND, 每次写 @eccsize 个字节 (@eccsize 是计算 ECC 的单元) */for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {chip->ecc.hwctl(mtd, NAND_ECC_WRITE);chip->write_buf(mtd, p, eccsize); /* 将数据写入到 NAND */chip->ecc.calculate(mtd, p, &ecc_calc[i]); /* 计算数据 @p 的 ECC, 记录到 ecc_calc[i] */}for (i = 0; i < chip->ecc.total; i++)chip->oob_poi[eccpos[i]] = ecc_calc[i];chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); /* 将 ECC 数据写入到 Spare area */return 0;/* 写完一个 page */cached = 0;if (!cached || !NAND_HAS_CACHEPROG(chip)) {chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);status = chip->waitfunc(mtd, chip);} else {...}return 0;...writelen -= bytes;if (!writelen)break;...}...
3.4 NAND 读操作
可以通过 nand read 0x82000000 200000 800000
发起对 NAND 的读
操作:将 NAND 从 0x200000
位置开始的 0x800000
个字节,读取到内存地址 0x82000000
开始的位置。NAND 读操作入口函数为 nand_read_skip_bad()
,其逻辑和 nand_write_skip_bad()
非常相似,在此不再赘述。值得一提的是,nand_read_skip_bad()
读操作,会跳过坏块。
3.5 其它 NAND 操作
nand device // 输出当前 NAND 设备信息
nand device <dev> // 切换到 NAND 设备 dev
nand dump[.oob] <offset> // 导出 NAND page 和 OOB, 带 .oob 后缀仅导出 OOB
......