編輯:關於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開發技巧——定制仿微信圖片裁剪控件》 的,先簡單介紹對上篇所封裝的裁剪控件的使用,再詳細說明如何使用它進行大圖裁剪
怎麼給手機qq設密碼手勢?下面小編就來告訴大家蘋果手機qq怎麼設置手勢密碼,感興趣的朋友就一起來看看吧!蘋果手機qq設置手勢密碼教程1.在你的手機上面登陸你
在平時工作中,camera模塊是經常進行調試修改的模塊,所以熟悉camera的工作流程以及工作原理將會大大的提供工作效率,但對於整個android系統camera是個十分
使用意圖調用內置應用程序 1、創建一個新的Android項目並命名為Intents,在main.xml文件中添加兩個Button: 2、