Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 移植u-boot2012.04.1 -》2440 (三)nandflash 識別

移植u-boot2012.04.1 -》2440 (三)nandflash 識別

編輯:關於Android編程

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 讀回的含義。

\

正常我這個讀出來是 0x951001 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	// 執行這個分支,采用軟件 ECC
	nand->ecc.mode = NAND_ECC_SOFT;
#endif

#ifdef CONFIG_S3C2410_NAND_BBT	// 沒有定義
	nand->options = NAND_USE_FLASH_BBT;
#else	// 執行這個分支
	nand->options = 0;
#endif

	debug("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);
		else
			writel(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 {
	u32	nfconf;
	u32	nfcmd;
	u32	nfaddr;
	u32	nfdata;
	u32	nfstat;
	u32	nfecc;
};
2410寄存器的偏移量確實是吻合的,2410吻合了,2440呢?
struct s3c2440_nand {
	u32	nfconf;
	u32	nfcont;
	u32	nfcmd;
	u32	nfaddr;
	u32	nfdata;
	u32	nfeccd0;
	u32	nfeccd1;
	u32	nfeccd;
	u32	nfstat;
	u32	nfstat0;
	u32	nfstat1;
};
顯然,不修改的話,寄存器就都搞錯了,作如下修改:
		if (!(ctrl & NAND_CLE))
			IO_ADDR_W |= 12;	//修改
		if (!(ctrl & NAND_ALE))
			IO_ADDR_W |= 8;         //修改
		if ((!(ctrl & NAND_CLE)) && (!(ctrl & NAND_ALE)))	//新增加
			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);
		else
			writel(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);
		else
			writel(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_TEST
		printk(KERN_WARNING "No NAND device found!!!\n");
#endif
		chip->select_chip(mtd, -1);
		return PTR_ERR(type);
	}

	/* maxchips == 1 for 循環不執行 */
	for (i = 1; i < maxchips; i++) {
		...
	}
#ifdef DEBUG
	if (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 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))

這才算大功告成。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved