MTD(Memory Technology Device)即常說的Flash等使用存儲芯片的存儲設備,MTD子系統對應的是塊設備驅動框架中的設備驅動層,能夠說,MTD就是針對Flash設備設計的標準化硬件驅動框架。本文基於3.14內核,討論MTD驅動框架。node
爲了實現上述的框架, 內核中使用了以下類和API, 這些幾乎是開發一個MTD驅動必須的編程
自己是沒有list_head來供內核管理,對mtd_info對象的管理是經過mtd_part來實現的。mtd_info對象屬於原始設備層,裏面的不少函數接口內核已經實現了。mtd_info中的read()/write()等操做是MTD設備驅動要實現的主要函數,在NORFlash或NANDFlash中的驅動代碼中幾乎看不到mtd_info的成員函數,即這些函數對於Flash芯片是透明的,由於Linux在MTD的下層實現了針對NORFlash和NANDFlash的通用的mtd_info函數。api
114 struct mtd_info { 115 u_char type; 116 uint32_t flags; 117 uint64_t size; // Total size of the MTD 118 123 uint32_t erasesize; 131 uint32_t writesize; 132 142 uint32_t writebufsize; 143 144 uint32_t oobsize; // Amount of OOB data per block (e.g. 16) 145 uint32_t oobavail; // Available OOB bytes per block 146 151 unsigned int erasesize_shift; 152 unsigned int writesize_shift; 153 /* Masks based on erasesize_shift and writesize_shift */ 154 unsigned int erasesize_mask; 155 unsigned int writesize_mask; 156 164 unsigned int bitflip_threshold; 165 166 // Kernel-only stuff starts here. 167 const char *name; 168 int index; 169 170 /* ECC layout structure pointer - read only! */ 171 struct nand_ecclayout *ecclayout; 172 173 /* the ecc step size. */ 174 unsigned int ecc_step_size; 175 176 /* max number of correctible bit errors per ecc step */ 177 unsigned int ecc_strength; 178 179 /* Data for variable erase regions. If numeraseregions is zero, 180 * it means that the whole device has erasesize as given above. 181 */ 182 int numeraseregions; 183 struct mtd_erase_region_info *eraseregions; 184 185 /* 186 * Do not call via these pointers, use corresponding mtd_*() 187 * wrappers instead. 188 */ 189 int (*_erase) (struct mtd_info *mtd, struct erase_info *instr); 190 int (*_point) (struct mtd_info *mtd, loff_t from, size_t len, 191 size_t *retlen, void **virt, resource_size_t *phys); 192 int (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len); 193 unsigned long (*_get_unmapped_area) (struct mtd_info *mtd, 194 unsigned long len, 195 unsigned long offset, 196 unsigned long flags); 197 int (*_read) (struct mtd_info *mtd, loff_t from, size_t len, 198 size_t *retlen, u_char *buf); 199 int (*_write) (struct mtd_info *mtd, loff_t to, size_t len, 200 size_t *retlen, const u_char *buf); 201 int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len, 202 size_t *retlen, const u_char *buf); 203 int (*_read_oob) (struct mtd_info *mtd, loff_t from, 204 struct mtd_oob_ops *ops); 205 int (*_write_oob) (struct mtd_info *mtd, loff_t to, 206 struct mtd_oob_ops *ops); 207 int (*_get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, 208 size_t len); 209 int (*_read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, 210 size_t len, size_t *retlen, u_char *buf); 211 int (*_get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, 212 size_t len); 213 int (*_read_user_prot_reg) (struct mtd_info *mtd, loff_t from, 214 size_t len, size_t *retlen, u_char *buf); 215 int (*_write_user_prot_reg) (struct mtd_info *mtd, loff_t to, 216 size_t len, size_t *retlen, u_char *buf); 217 int (*_lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, 218 size_t len); 219 int (*_writev) (struct mtd_info *mtd, const struct kvec *vecs, 220 unsigned long count, loff_t to, size_t *retlen); 221 void (*_sync) (struct mtd_info *mtd); 222 int (*_lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len); 223 int (*_unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len); 224 int (*_is_locked) (struct mtd_info *mtd, loff_t ofs, uint64_t len); 225 int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs); 226 int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs); 227 int (*_suspend) (struct mtd_info *mtd); 228 void (*_resume) (struct mtd_info *mtd); 229 /* 230 * If the driver is something smart, like UBI, it may need to maintain 231 * its own reference counting. The below functions are only for driver. 232 */ 233 int (*_get_device) (struct mtd_info *mtd); 234 void (*_put_device) (struct mtd_info *mtd); 235 236 /* Backing device capabilities for this device 237 * - provides mmap capabilities 238 */ 239 struct backing_dev_info *backing_dev_info; 240 241 struct notifier_block reboot_notifier; /* default mode before reboot */ 242 243 /* ECC status information */ 244 struct mtd_ecc_stats ecc_stats; 245 /* Subpage shift (NAND) */ 246 int subpage_sft; 247 248 void *priv; 249 250 struct module *owner; 251 struct device dev; 252 int usecount; 253 };
struct mtd_info
--115-->MTD設備類型,有MTD_RAM,MTD_ROM、MTD_NORFLASH、MTD_NAND_FLASH
--116-->讀寫及權限標誌位,有MTD_WRITEABLE、MTD_BIT_WRITEABLE、MTD_NO_ERASE、MTD_UP_LOCK
--117-->MTD設備的大小
--123-->主要的擦除塊大小,NandFlash就是"塊"的大小
--131-->最小可寫字節數,NandFlash通常對應"頁"的大小
--144-->一個block中的OOB字節數
--145-->一個block中可用oob的字節數
--171-->ECC佈局結構體指針
--190-->針對eXecute-In-Place,即XIP
--192-->若是這個指針爲空,不容許XIP
--197-->讀函數指針
--199-->寫函數指針
--248-->私有數據數組
內核管理分區的鏈表節點,經過它來實現對mtd_info對象的管理。app
41 struct mtd_part { 42 struct mtd_info mtd; 43 struct mtd_info *master; 44 uint64_t offset; 45 struct list_head list; 46 };
struct mtd_part
--42-->對應的mtd_info對象
--43-->父對象指針
--44-->偏移量
--45-->鏈表節點框架
描述一個分區ide
39 struct mtd_partition { 40 const char *name; /* identifier string */ 41 uint64_t size; /* partition size */ 42 uint64_t offset; /* offset within the master MTD space */ 43 uint32_t mask_flags; /* master MTD flags to mask out for this partition */ 44 struct nand_ecclayout *ecclayout; /* out of band layout for this partition (NAND only) */ 45 };
mtd_partition
--40-->分區名
--41-->分區大小,使用MTDPART_SIZ_FULL表示使用所有空間
--42-->分區在master設備中的偏移量。MTDPART_OFS_APPEND表示從上一個分區結束的地方開始,MTDPART_OFS_NXTBLK表示從下一個擦除塊開始; MTDPART_OFS_RETAIN表示儘量向後偏,把size大小的空間留下便可
--43-->權限掩碼,MTD_WRITEABLE表示將父設備的只讀選項變成可寫(可寫分區要求size和offset要erasesize對齊,eg MTDPART_OFS_NEXTBLK)
--44-->NANDFlash的OOB佈局,OOB是NANDFlash中頗有用空間,好比yaffs2就須要將壞塊信息存儲在OOB區域函數
鏈表頭,將全部的mtd_partition鏈接起來。佈局
36 /* Our partition linked list */ 37 static LIST_HEAD(mtd_partitions);
下圖是關鍵API的調用關係。ui
mtd_add_partition()
└── add_mtd_device()
add_mtd_partitions()
└── add_mtd_device()
分配並初始化一個mtd對象。
367 334 int add_mtd_device(struct mtd_info *mtd) 335 { 336 struct mtd_notifier *not; 337 int i, error; 338 339 if (!mtd->backing_dev_info) { 340 switch (mtd->type) { 341 case MTD_RAM: 342 mtd->backing_dev_info = &mtd_bdi_rw_mappable; 343 break; 344 case MTD_ROM: 345 mtd->backing_dev_info = &mtd_bdi_ro_mappable; 346 break; 347 default: 348 mtd->backing_dev_info = &mtd_bdi_unmappable; 349 break; 350 } 351 } 355 356 i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL); 357 if (i < 0) 358 goto fail_locked; 359 360 mtd->index = i; 361 mtd->usecount = 0; 362 363 /* default value if not set by driver */ 364 if (mtd->bitflip_threshold == 0) 365 mtd->bitflip_threshold = mtd->ecc_strength; 366 367 if (is_power_of_2(mtd->erasesize)) 368 mtd->erasesize_shift = ffs(mtd->erasesize) - 1; 369 else 370 mtd->erasesize_shift = 0; 371 372 if (is_power_of_2(mtd->writesize)) 373 mtd->writesize_shift = ffs(mtd->writesize) - 1; 374 else 375 mtd->writesize_shift = 0; 376 377 mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1; 378 mtd->writesize_mask = (1 << mtd->writesize_shift) - 1; 379 380 /* Some chips always power up locked. Unlock them now */ 381 if ((mtd->flags & MTD_WRITEABLE) && (mtd->flags & MTD_POWERUP_LOCK)) { 382 error = mtd_unlock(mtd, 0, mtd->size); 387 } 388 392 mtd->dev.type = &mtd_devtype; 393 mtd->dev.class = &mtd_class; 394 mtd->dev.devt = MTD_DEVT(i); 395 dev_set_name(&mtd->dev, "mtd%d", i); 396 dev_set_drvdata(&mtd->dev, mtd); 397 if (device_register(&mtd->dev) != 0) 399 400 if (MTD_DEVT(i)) 401 device_create(&mtd_class, mtd->dev.parent, 402 MTD_DEVT(i) + 1, 403 NULL, "mtd%dro", i); 408 list_for_each_entry(not, &mtd_notifiers, list) 409 not->add(mtd); 417 return 0; 424 }
add_mtd_device()
--395-->設置MTD設備的名字
--396-->設置私有數據,將mtd地址藏到device->device_private->void* driver_data
--408-->遍歷全部的mtd_notifier,將其添加到通知鏈
經過將一個mtd_part對象註冊到內核,將mtd_info對象註冊到內核,即爲一個設備添加一個分區。
537 int mtd_add_partition(struct mtd_info *master, const char *name, 538 long long offset, long long length) 539 { 540 struct mtd_partition part; 541 struct mtd_part *p, *new; 542 uint64_t start, end; 543 int ret = 0; 545 /* the direct offset is expected */ 546 if (offset == MTDPART_OFS_APPEND || 547 offset == MTDPART_OFS_NXTBLK) 548 return -EINVAL; 549 550 if (length == MTDPART_SIZ_FULL) 551 length = master->size - offset; 552 553 if (length <= 0) 554 return -EINVAL; 555 556 part.name = name; 557 part.size = length; 558 part.offset = offset; 559 part.mask_flags = 0; 560 part.ecclayout = NULL; 561 562 new = allocate_partition(master, &part, -1, offset); 563 if (IS_ERR(new)) 564 return PTR_ERR(new); 565 566 start = offset; 567 end = offset + length; 568 569 mutex_lock(&mtd_partitions_mutex); 570 list_for_each_entry(p, &mtd_partitions, list) 571 if (p->master == master) { 572 if ((start >= p->offset) && 573 (start < (p->offset + p->mtd.size))) 574 goto err_inv; 575 576 if ((end >= p->offset) && 577 (end < (p->offset + p->mtd.size))) 578 goto err_inv; 579 } 580 581 list_add(&new->list, &mtd_partitions); 582 mutex_unlock(&mtd_partitions_mutex); 583 584 add_mtd_device(&new->mtd); 585 586 return ret; 591 }
添加一個分區表到內核,一個MTD設備一個分區表
626 int add_mtd_partitions(struct mtd_info *master, 627 const struct mtd_partition *parts, 628 int nbparts) 629 { 630 struct mtd_part *slave; 631 uint64_t cur_offset = 0; 632 int i; 636 for (i = 0; i < nbparts; i++) { 637 slave = allocate_partition(master, parts + i, i, cur_offset); 642 list_add(&slave->list, &mtd_partitions); 645 add_mtd_device(&slave->mtd); 647 cur_offset = slave->offset + slave->mtd.size; 648 } 649 650 return 0; 651 }
MTD設備提供了字符設備和塊設備兩種接口,對於字符設備接口,在"drivers/mtd/mtdchar.c"中實現了,好比,用戶程序能夠直接經過ioctl()回調相應的驅動實現。其中下面的幾個是這些操做中經常使用的結構,這些結構是對用戶空間開放的,相似於輸入子系統中的input_event結構。
//include/uapi/mtd/mtd-abi.h 125 struct mtd_info_user { 126 __u8 type; 127 __u32 flags; 128 __u32 size; /* Total size of the MTD */ 129 __u32 erasesize; 130 __u32 writesize; 131 __u32 oobsize; /* Amount of OOB data per block (e.g. 16) */ 132 __u64 padding; /* Old obsolete field; do not use */ 133 };
描述NandFlash的OOB(Out Of Band)信息。
35 struct mtd_oob_buf { 36 __u32 start; 37 __u32 length; 38 unsigned char __user *ptr; 39 };
25 struct erase_info_user { 26 __u32 start; 27 __u32 length; 28 };
mtd_oob_buf oob; erase_info_user erase; mtd_info_user meminfo; /* 得到設備信息 */ if(0 != ioctl(fd, MEMGETINFO, &meminfo)) perror("MEMGETINFO"); /* 擦除塊 */ if(0 != ioctl(fd, MEMERASE, &erase)) perror("MEMERASE"); /* 讀OOB */ if(0 != ioctl(fd, MEMREADOOB, &oob)) perror("MEMREADOOB"); /* 寫OOB??? */ if(0 != ioctl(fd, MEMWRITEOOB, &oob)) perror("MEMWRITEOOB"); /* 檢查壞塊 */ if(blockstart != (ofs & (~meminfo.erase + 1))){ blockstart = ofs & (~meminfo.erasesize + 1); if((badblock = ioctl(fd, MEMGETBADBLOCK, &blockstart)) < 0) perror("MEMGETBADBLOCK"); else if(badblock) /* 壞塊代碼 */ else /* 好塊代碼 */ }
NANDFlash和NORFlash都是基於MTD框架編寫的,因爲MTD框架中通用代碼已經在內核中實現了,因此驅動開發主要是進行MTD框架中的的開發。
下圖就是NORFlash驅動在MTD驅動框架中的位置
基於上述的MTD框架, Flash驅動都變的十分的簡單, 由於當下Flash的操做接口已經很統一, a, 相應的代碼在"drivers/mtd/chips"中文件實現,因此在設備驅動層, 留給驅動工程師的工做就大大的減小了。
基於MTD子系統開發NOR FLash驅動,只須要構造一個map_info類型的對象並調用do_map_probe()來匹配內核中已經寫好的驅動,好比CFI接口的驅動或JEDEC接口的驅動。當下編寫一個NorFlash驅動的工做流程以下
208 struct map_info { 209 const char *name; 210 unsigned long size; 211 resource_size_t phys; 212 #define NO_XIP (-1UL) 214 void __iomem *virt; 215 void *cached; 217 int swap; /* this mapping's byte-swapping requirement */ 218 int bankwidth; 243 void (*set_vpp)(struct map_info *, int); 245 unsigned long pfow_base; 246 unsigned long map_priv_1; 247 unsigned long map_priv_2; 248 struct device_node *device_node; 249 void *fldrv_priv; 250 struct mtd_chip_driver *fldrv; 251 };
struct map_info
--210-->NOR Flash設備的容量
--211-->NOR Flash在物理地址空間中的地址
--214-->由物理地址映射的虛擬地址
--218-->總線寬度,NOR Flash是有地址總線的,因此才能片上執行,通常都是8位或16位寬
構造好一個map_info對象以後,接下來的工做就是匹配驅動+註冊分區表
這個API用來根據傳入的參數匹配一個map_info對象的驅動,好比CFI接口或JEDEC接口的NOR Flash。這個函數的接口以下:
struct mtd_info *do_map_probe(const char *name, struct map_info *map)
對於經常使用的NorFlash標準,這個函數的調用方式以下:
do_map_probe("cfi_probe", &xxx_map_info); do_map_probe("jedec_probe",&xxx_map_info); do_map_probe("map_rom",&xxx_map_info);
匹配了設備驅動,能夠發現一個map_info對象中沒有mtd_partitions相關的信息,對於一個NOR Flash的分區信息,須要經過do_map_probe返回的mtd_info對象來註冊到內核。這裏咱們能夠先調用parse_mtd_partitions()查看Flash上已有的分區信息,獲取了分區信息以後再調用add_mtd_partitions()將分區信息寫入內核
#define WINDOW_SIZE ... #define WINDOW_ADDR ... static struct map_info xxx_map = { .name = "xxx flash", .size = WINDOW_SIZE, .bankwidth = 1, .phys = WINDOW_ADDR, }; static struct mtd_partition xxx_partitions[] = { .name = "Drive A", .offset = 0, .size = 0x0e000, }; #define NUM_PARTITIONS ARRAY_SIZE(xxx_partitions) static struct mtd_info *mymtd; static int __init init_xxx_map(void) { int rc = 0; xxx_map.virt = ioremap_nocache(xxx_map.phys, xxx_map.size); if(!xxx_map.virt){ printk(KERN_ERR"Failed to ioremap_nocache\n"); rc = -EIO; goto err2; } simple_map_init(&xxx_map); mymtd = do_map_probe("jedec_probe", &xxx_map); if(!mymtd){ rc = -ENXIO; goto err1; } mymtd->owner = THIS_MODULE; add_mtd_partitions(mymtd, xxx_partitions, NUM_PARTITIONS); return 0; err1: map_destroy(mymtd); iounmap(xxx_map.virt); err2: return rc; } static void __exit cleanup_xxx_map(void) { if(mymtd){ del_mtd_partitions(mymtd); map_destroy(mymtd); } if(xxx_map.virt){ iounmap(xxx_map.virt); xxx_map.virt = NULL; } }
下圖就是基於MTD框架的NandFlash驅動的位置。
Nand Flash和NOR Flash相似,內核中已經在"drivers/mtd/nand/nand_base.c"中實現了通用的驅動程序,驅動開發中不須要再實現mtd_info中的read, write, read_oob, write_oob等接口,只須要構造並註冊一個nand_chip對象, 這個對象主要描述了一片flash芯片的相關信息,包括地址信息,讀寫方法,ECC模式,硬件控制等一系列底層機制。當下,編寫一個NandFlash驅動的工做流程以下:
這個結構描述一個NAND Flash設備,一般藏在mtd_info->priv中,以便在回調其中的接口的時候能夠找到nand_chip對象。
547 struct nand_chip { 548 void __iomem *IO_ADDR_R; 549 void __iomem *IO_ADDR_W; 550 551 uint8_t (*read_byte)(struct mtd_info *mtd); 578 579 int chip_delay; 580 unsigned int options; 581 unsigned int bbt_options; 582 583 int page_shift; 584 int phys_erase_shift; 585 int bbt_erase_shift; 586 int chip_shift; 587 int numchips; 588 uint64_t chipsize; 589 int pagemask; 590 int pagebuf; 591 unsigned int pagebuf_bitflips; 592 int subpagesize; 593 uint8_t bits_per_cell; 594 uint16_t ecc_strength_ds; 595 uint16_t ecc_step_ds; 596 int badblockpos; 597 int badblockbits; 598 599 int onfi_version; 600 struct nand_onfi_params onfi_params; 601 602 int read_retries; 603 604 flstate_t state; 605 606 uint8_t *oob_poi; 607 struct nand_hw_control *controller; 608 609 struct nand_ecc_ctrl ecc; 610 struct nand_buffers *buffers; 611 struct nand_hw_control hwcontrol; 612 613 uint8_t *bbt; 614 struct nand_bbt_descr *bbt_td; 615 struct nand_bbt_descr *bbt_md; 616 617 struct nand_bbt_descr *badblock_pattern; 618 619 void *priv; 620 };
struct nand_chip
--609-->NAND芯片的OOB分佈和模式,若是不賦值,則會使用內核默認的OOB
--580-->與具體的NAND 芯片相關的一些選項,如NAND_BUSWIDTH_16 等,能夠參考<Linux/mtd/nand.h>
--583-->用位表示的NAND 芯片的page 大小,如某片NAND 芯片的一個page 有512 個字節,那麼page_shift 就是9 ;
--584-->用位表示的NAND 芯片的每次可擦除的大小,如某片NAND 芯片每次可擦除16K 字節( 一般就是一個block 的大小) ,那麼phys_erase_shift 就是14 ;
--585-->用位表示的bad block table 的大小,一般一個bbt 佔用一個block ,因此bbt_erase_shift 一般與phys_erase_shift 相等;
--587-->表示系統中有多少片NAND 芯片;
--588-->NAND 芯片的大小;
--589-->計算page number 時的掩碼,老是等於chipsize/page 大小 - 1 ;
--590-->用來保存當前讀取的NAND 芯片的page number ,這樣一來,下次讀取的數據若仍是屬於同一個page ,就沒必要再從NAND 芯片讀取了,而是從data_buf 中直接獲得;
--596-->表示壞塊信息保存在oob 中的第幾個字節。對於絕大多數的NAND 芯片,若page size> 512,那麼壞塊信息從Byte 0 開始存儲,不然就存儲在Byte 5 ,即第六個字節。
--619-->私有數據
準備好了一個nand_chip,接下來的工做就是匹配驅動+註冊分區表。
NAND flash使用nand_scan()來匹配驅動,這個函數會讀取NAND芯片的ID,並根據mtd->priv即nand_chip中的成員初始化mtd_info。若是要分區,則以mtd_info和mtd_partition爲參數調用add_mtd_partitions來添加分區信息。
int nand_scan(struct mtd_info *mtd, int maxchips)
#define CHIP_PHYSICAL_ADDRESS ... #define NUM_PARTITIONS 2 static struct mtd_partition partition_info[] = { { .name = "Flash partition 1", .info = 0, .size = 8 * 1024 * 1024, }, { .name = "Flash partition 2", offset = MTDPART_OFS_NEXT, size = MTDPART_SIZ_FULL, }, }; int __init board_init(void) { struct nand_chip *this; int err = 0; /* 爲MTD設備對象和nand_chip分配內存 */ board_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip),GFP_KERNEL); if(!board_mtd){ printk("Unable to allocate NAND MTD device structure\n"); err = -ENOMEM; goto out; } /* 初始化結構體 */ memset((char *)board_mtd, 0 ,sizeof(struct mtd_info) + sizeof(struct nand_chip)); /* 映射物理地址 */ baseaddr = (unsigned long) ioremap(CHIP_PHYSICAL_ADDRESS,1024); if(!baseaddr){ printk("Ioremap to access NAND Chip failed\n"); err = -EIO; goto out_mtd; } /* 獲取私有數據(nand_chip)指針 */ this = (struct nand_chip *)(&board_mtd[1]); /* 將nand_chip賦予mtd_info私有指針 */ board_mtd->priv = this; /* 設置NAND Flash的IO基地址 */ this->IO_ADDR_R = baseaddr; this->IO_ADDR_W = baseaddr; /* 硬件控制函數 */ this->cmd_ctrl = board_hwcontrol; /* 初始化設備ready函數 */ this->dev_ready = board_dev_ready; /* 掃描以肯定設備的存在 */ if(nand_scan(board_mtd, 1)){ err = -ENXIO; goto = out_ior; } /* 添加分區 */ add_mtd_partitions(board_mtd,partition_info,NUM_PARTITIONS); goto out; out_ior: iounmap((void *)baseaddr); out_mtd: kfree(board_mtd); out: return err; } static void __exit board_cleanup(void) { /* 釋放資源,註銷設備 */ nand_release(board_mtd); /* unmap物理地址 */ iounmap((void *)baseaddr); /* 釋放MTD設備結構體 */ kfree(board_mtd); } /* 硬件控制 */ static void board_hwcontrol(struct mtd_info *mtd, int dat,unsigned int ctrl) { ... if(ctrl & NAND_CTRL_CHANGE){ if(ctrl & NAND_NCE){ } } ... } /*返回ready狀態*/ static int board_dev_ready(struct mtd_info *mtd) { return xxx_read_ready_bit(); }