編輯:關於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 ,關於讀寫的函數後邊再說。
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 中實現了,因此它要不要其實都行,不做修改。
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 函數。
/* 讀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))
這才算大功告成。
Android開發技巧——大圖裁剪
本篇內容是接上篇《Android開發技巧——定制仿微信圖片裁剪控件》 的,先簡單介紹對上篇所封裝的裁剪控件的使用,再詳細說明如何使用它進行大圖裁剪
怎麼給手機qq設手勢密碼 蘋果手機qq設置手勢密碼教程
怎麼給手機qq設密碼手勢?下面小編就來告訴大家蘋果手機qq怎麼設置手勢密碼,感興趣的朋友就一起來看看吧!蘋果手機qq設置手勢密碼教程1.在你的手機上面登陸你
camer驅動模塊加載分析
在平時工作中,camera模塊是經常進行調試修改的模塊,所以熟悉camera的工作流程以及工作原理將會大大的提供工作效率,但對於整個android系統camera是個十分
Android學習筆記(十七)——使用意圖調用內置應用程序
使用意圖調用內置應用程序 1、創建一個新的Android項目並命名為Intents,在main.xml文件中添加兩個Button: 2、