学习笔记 --- LINUX MTD设备之NANDFLASH驱动分析

2023-12-12 00:58

本文主要是介绍学习笔记 --- LINUX MTD设备之NANDFLASH驱动分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前一篇文章分析了MTD的框架,添加一个MTD设备的方法:

1 填充mtd_info结构体

2 add_mtd_device添加MTD设备

那么这个mtd_info到底怎么填写,不同的设备有不同的操作方法,无论对于NANDFLASH的读写还是对于NORFLASH的读写都有一套自己的标准,这一套标准就叫做操作协议层,这一层内核已经帮我们实现了,这个协议层会帮我们填充好mtd_info结构体,而我们需要做的只是设置好协议层提供的接口。

NAND的对应的协议接口是nand_chip结构体。

我们以Atmel_nand.c为例分析,看他究竟干了什么事情:

static int __init atmel_nand_probe(struct platform_device *pdev)
{struct atmel_nand_host *host;struct mtd_info *mtd;struct nand_chip *nand_chip;struct resource *regs;struct resource *mem;int res;#ifdef CONFIG_MTD_PARTITIONSstruct mtd_partition *partitions = NULL;int num_partitions = 0;
#endifmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!mem) {printk(KERN_ERR "atmel_nand: can't get I/O resource mem\n");return -ENXIO;}/* Allocate memory for the device structure (and zero it) */host = kzalloc(sizeof(struct atmel_nand_host), GFP_KERNEL);if (!host) {printk(KERN_ERR "atmel_nand: failed to allocate device structure.\n");return -ENOMEM;}host->io_base = ioremap(mem->start, mem->end - mem->start + 1);if (host->io_base == NULL) {printk(KERN_ERR "atmel_nand: ioremap failed\n");res = -EIO;goto err_nand_ioremap;}mtd = &host->mtd;nand_chip = &host->nand_chip;host->board = pdev->dev.platform_data;host->dev = &pdev->dev;nand_chip->priv = host;	mtd->priv = nand_chip;  //nand_chip赋值给mtd私有数据指针,这里就设置好了mtd获取nand_chip的入口mtd->owner = THIS_MODULE; /* Set address of NAND IO lines */nand_chip->IO_ADDR_R = host->io_base;  //设置读IO寄存器地址nand_chip->IO_ADDR_W = host->io_base;  //设置写IO寄存器地址nand_chip->cmd_ctrl = atmel_nand_cmd_ctrl;  //设置cmd控制函数if (host->board->rdy_pin)nand_chip->dev_ready = atmel_nand_device_ready;   //设置nandflash忙等待函数regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);if (!regs && hard_ecc) {printk(KERN_ERR "atmel_nand: can't get I/O resource ""regs\nFalling back on software ECC\n");}nand_chip->ecc.mode = NAND_ECC_SOFT;	//使能ECC检查if (no_ecc)nand_chip->ecc.mode = NAND_ECC_NONE;if (hard_ecc && regs) {host->ecc = ioremap(regs->start, regs->end - regs->start + 1);if (host->ecc == NULL) {printk(KERN_ERR "atmel_nand: ioremap failed\n");res = -EIO;goto err_ecc_ioremap;}nand_chip->ecc.mode = NAND_ECC_HW;nand_chip->ecc.calculate = atmel_nand_calculate;nand_chip->ecc.correct = atmel_nand_correct;nand_chip->ecc.hwctl = atmel_nand_hwctl;nand_chip->ecc.read_page = atmel_nand_read_page;nand_chip->ecc.bytes = 4;}nand_chip->chip_delay = 20;		/* 20us command delay time */if (host->board->bus_width_16) {	/* 16-bit bus width */nand_chip->options |= NAND_BUSWIDTH_16;nand_chip->read_buf = atmel_read_buf16;nand_chip->write_buf = atmel_write_buf16;} else {nand_chip->read_buf = atmel_read_buf;    //设置读buf操作函数nand_chip->write_buf = atmel_write_buf;  //设置写buf操作函数}platform_set_drvdata(pdev, host);atmel_nand_enable(host);if (host->board->det_pin) {if (gpio_get_value(host->board->det_pin)) {printk(KERN_INFO "No SmartMedia card inserted.\n");res = ENXIO;goto err_no_card;}}if (on_flash_bbt) {printk(KERN_INFO "atmel_nand: Use On Flash BBT\n");nand_chip->options |= NAND_USE_FLASH_BBT;}/* first scan to find the device and get the page size */if (nand_scan_ident(mtd, 1)) {   //扫描nandflashres = -ENXIO;goto err_scan_ident;}if (nand_chip->ecc.mode == NAND_ECC_HW) {/* ECC is calculated for the whole page (1 step) */nand_chip->ecc.size = mtd->writesize;/* set ECC page size and oob layout */switch (mtd->writesize) {case 512:nand_chip->ecc.layout = &atmel_oobinfo_small;ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_528);break;case 1024:nand_chip->ecc.layout = &atmel_oobinfo_large;ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_1056);break;case 2048:nand_chip->ecc.layout = &atmel_oobinfo_large;ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_2112);break;case 4096:nand_chip->ecc.layout = &atmel_oobinfo_large;ecc_writel(host->ecc, MR, ATMEL_ECC_PAGESIZE_4224);break;default:/* page size not handled by HW ECC *//* switching back to soft ECC */nand_chip->ecc.mode = NAND_ECC_SOFT;nand_chip->ecc.calculate = NULL;nand_chip->ecc.correct = NULL;nand_chip->ecc.hwctl = NULL;nand_chip->ecc.read_page = NULL;nand_chip->ecc.postpad = 0;nand_chip->ecc.prepad = 0;nand_chip->ecc.bytes = 0;break;}}/* second phase scan */if (nand_scan_tail(mtd)) {   //完善mtd结构体,设置函数指针res = -ENXIO;goto err_scan_tail;}#ifdef CONFIG_MTD_PARTITIONS
#ifdef CONFIG_MTD_CMDLINE_PARTSmtd->name = "atmel_nand";num_partitions = parse_mtd_partitions(mtd, part_probes,&partitions, 0);
#endifif (num_partitions <= 0 && host->board->partition_info)partitions = host->board->partition_info(mtd->size,&num_partitions);if ((!partitions) || (num_partitions == 0)) {printk(KERN_ERR "atmel_nand: No partitions defined, or unsupported device.\n");res = ENXIO;goto err_no_partitions;}res = add_mtd_partitions(mtd, partitions, num_partitions);   //如果有分区,添加多个mtd设备
#elseres = add_mtd_device(mtd); //如果没分区直接添加mtd设备
#endifif (!res)return res;#ifdef CONFIG_MTD_PARTITIONS
err_no_partitions:
#endifnand_release(mtd);
err_scan_tail:
err_scan_ident:
err_no_card:atmel_nand_disable(host);platform_set_drvdata(pdev, NULL);if (host->ecc)iounmap(host->ecc);
err_ecc_ioremap:iounmap(host->io_base);
err_nand_ioremap:kfree(host);return res;
}

从这个例子可以看出NANFLASH驱动由下面两个部分组成:

1 两个重要结构体:

nand_chip

mtd_info

2 三个重点函数:

nand_scan_ident
nand_scan_tail

(上面两个合起来一步nand_scan)
add_mtd_device

总结驱动步骤:

一.分配并设置协议层接口 nand_chip

二.匹配协议 nand_scan(扫描设备并填充mtd_info)

三.添加mtd设备


所以一个MTD设备的主要区别在于匹配的协议,不同的设备对于传输的数据的协议不一样。

这里NAND的协议是通过nand_scan:

nand_scan包含nand_scan_ident与nand_scan_tail:

1.nand_scan_ident读取flash ID并建立MTD相应字段

int nand_scan_ident(struct mtd_info *mtd, int maxchips,struct nand_flash_dev *table)
{int i, busw, nand_maf_id, nand_dev_id;struct nand_chip *chip = mtd->priv;	//私有数据中获取nand_chipstruct nand_flash_dev *type;busw = chip->options & NAND_BUSWIDTH_16;	//获取总线宽度nand_set_defaults(chip, busw);	//设置nand_chip默认方法函数type = nand_get_flash_type(mtd, chip, busw,&nand_maf_id, &nand_dev_id, table);	//获取nand flash设备类型if (IS_ERR(type)) {if (!(chip->options & NAND_SCAN_SILENT_NODEV))printk(KERN_WARNING "No NAND device found.\n");chip->select_chip(mtd, -1);return PTR_ERR(type);}for (i = 1; i < maxchips; i++) {	//芯片个数chip->select_chip(mtd, i);	//调用select_chip方法选中芯片chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);	//调用NAND_CMD_RESET命令重启设备chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);	//调用NAND_CMD_READID命令读取设备IDif (nand_maf_id != chip->read_byte(mtd) || nand_dev_id != chip->read_byte(mtd))	//读厂商ID和设备IDbreak;}if (i > 1)printk(KERN_INFO "%d NAND chips detected\n", i);chip->numchips = i;	//芯片数mtd->size = i * chip->chipsize;	//MTD设备总容量return 0;
}
EXPORT_SYMBOL(nand_scan_ident);
1.1nand_set_defaults设置一些默认方法
static void nand_set_defaults(struct nand_chip *chip, int busw)
{if (!chip->chip_delay)chip->chip_delay = 20;if (chip->cmdfunc == NULL)chip->cmdfunc = nand_command;									//nand命令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;								//扫描bbtif (!chip->controller) {chip->controller = &chip->hwcontrol;spin_lock_init(&chip->controller->lock);init_waitqueue_head(&chip->controller->wq);}
}
1.2nand_get_flash_type获取nand flash类型

static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,struct nand_chip *chip,int busw,int *maf_id, int *dev_id,struct nand_flash_dev *type)
{int i, maf_idx;u8 id_data[8];int ret;chip->select_chip(mtd, 0);	//片选芯片chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);	//重启芯片chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);	//读芯片ID*maf_id = chip->read_byte(mtd);		//读工厂id*dev_id = chip->read_byte(mtd);		//读设备idchip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);	//读芯片IDfor (i = 0; i < 2; i++)id_data[i] = chip->read_byte(mtd);		//读取芯片IDif (id_data[0] != *maf_id || id_data[1] != *dev_id) {	//比较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);}if (!type)type = nand_flash_ids;for (; type->name != NULL; type++)	//获取匹配的设备if (*dev_id == type->id)break;chip->onfi_version = 0;if (!type->name || !type->pagesize) {	//检测是否onfi接口标准ret = nand_flash_detect_onfi(mtd, chip, busw);if (ret)goto ident_done;}chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);	//读芯片IDfor (i = 0; i < 8; i++)	//读取全部id信息id_data[i] = chip->read_byte(mtd);if (!type->name)return ERR_PTR(-ENODEV);if (!mtd->name)mtd->name = type->name;	//设置名字chip->chipsize = (uint64_t)type->chipsize << 20;	//获取芯片容量if (!type->pagesize && chip->init_size) {busw = chip->init_size(mtd, chip, id_data);	//初始化芯片容量} else if (!type->pagesize) {int extid;chip->cellinfo = id_data[2];extid = id_data[3];if (id_data[0] == id_data[6] && id_data[1] == id_data[7] && id_data[0] == NAND_MFR_SAMSUNG && (chip->cellinfo & NAND_CI_CELLTYPE_MSK) && id_data[5] != 0x00) {mtd->writesize = 2048 << (extid & 0x03);extid >>= 2;switch (extid & 0x03) {case 1:mtd->oobsize = 128;break;case 2:mtd->oobsize = 218;break;case 3:mtd->oobsize = 400;break;default:mtd->oobsize = 436;break;}extid >>= 2;mtd->erasesize = (128 * 1024) << (((extid >> 1) & 0x04) | (extid & 0x03));busw = 0;}else {mtd->writesize = 1024 << (extid & 0x03);extid >>= 2;mtd->oobsize = (8 << (extid & 0x01)) * (mtd->writesize >> 9);extid >>= 2;mtd->erasesize = (64 * 1024) << (extid & 0x03);extid >>= 2;busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;}}else {mtd->erasesize = type->erasesize;	//擦除尺寸mtd->writesize = type->pagesize;	//页尺寸mtd->oobsize = mtd->writesize / 32;	//oob区尺寸busw = type->options & NAND_BUSWIDTH_16;	//数据宽度if (*maf_id == NAND_MFR_AMD && id_data[4] != 0x00 && id_data[5] == 0x00 && id_data[6] == 0x00 && id_data[7] == 0x00 && mtd->writesize == 512) {mtd->erasesize = 128 * 1024;mtd->erasesize <<= ((id_data[3] & 0x03) << 1);}}chip->options &= ~NAND_CHIPOPTIONS_MSK;chip->options |= type->options & NAND_CHIPOPTIONS_MSK;if (*maf_id != NAND_MFR_SAMSUNG && !type->pagesize)chip->options &= ~NAND_SAMSUNG_LP_OPTIONS;
ident_done:chip->options |= NAND_NO_AUTOINCR;for (maf_idx = 0; nand_manuf_ids[maf_idx].id != 0x0; maf_idx++) {if (nand_manuf_ids[maf_idx].id == *maf_id)break;}if (busw != (chip->options & NAND_BUSWIDTH_16)) {printk(KERN_INFO "NAND device: Manufacturer ID: 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,*dev_id, nand_manuf_ids[maf_idx].name, mtd->name);printk(KERN_WARNING "NAND bus width %d instead %d bit\n",(chip->options & NAND_BUSWIDTH_16) ? 16 : 8,busw ? 16 : 8);return ERR_PTR(-EINVAL);}chip->page_shift = ffs(mtd->writesize) - 1;chip->pagemask = (chip->chipsize >> chip->page_shift) - 1;chip->bbt_erase_shift = chip->phys_erase_shift =ffs(mtd->erasesize) - 1;if (chip->chipsize & 0xffffffff)chip->chip_shift = ffs((unsigned)chip->chipsize) - 1;else {chip->chip_shift = ffs((unsigned)(chip->chipsize >> 32));chip->chip_shift += 32 - 1;}chip->badblockbits = 8;if (mtd->writesize > 512 || (busw & NAND_BUSWIDTH_16))	//设置坏块位置chip->badblockpos = NAND_LARGE_BADBLOCK_POS;elsechip->badblockpos = NAND_SMALL_BADBLOCK_POS;if ((chip->cellinfo & NAND_CI_CELLTYPE_MSK) && (*maf_id == NAND_MFR_SAMSUNG || *maf_id == NAND_MFR_HYNIX))chip->options |= NAND_BBT_SCANLASTPAGE;else if ((!(chip->cellinfo & NAND_CI_CELLTYPE_MSK) && (*maf_id == NAND_MFR_SAMSUNG || *maf_id == NAND_MFR_HYNIX || *maf_id == NAND_MFR_TOSHIBA || *maf_id == NAND_MFR_AMD)) || (mtd->writesize == 2048 && *maf_id == NAND_MFR_MICRON))chip->options |= NAND_BBT_SCAN2NDPAGE;if (!(busw & NAND_BUSWIDTH_16) && *maf_id == NAND_MFR_STMICRO && mtd->writesize == 2048) {chip->options |= NAND_BBT_SCANBYTE1AND6;chip->badblockpos = 0;}if (chip->options & NAND_4PAGE_ARRAY)chip->erase_cmd = multi_erase_cmd;elsechip->erase_cmd = single_erase_cmd;if (mtd->writesize > 512 && chip->cmdfunc == nand_command)chip->cmdfunc = nand_command_lp;printk(KERN_INFO "NAND device: Maf ID: 0x%02x,Chip ID: 0x%02x (%s, %s)\n erasesize: 0x%x, writesize: %d, oobsize: %d\n", *maf_id, *dev_id,nand_manuf_ids[maf_idx].name, chip->onfi_version ? type->name : chip->onfi_params.model,mtd->erasesize, mtd->writesize, mtd->oobsize);return type;
}
2.nand_scan_tail填充未初始化的功能函数默认函数并扫描坏块表

int nand_scan_tail(struct mtd_info *mtd)
{int i;struct nand_chip *chip = mtd->priv;if (!(chip->options & NAND_OWN_BUFFERS))chip->buffers = kmalloc(sizeof(*chip->buffers), GFP_KERNEL);if (!chip->buffers)return -ENOMEM;chip->oob_poi = chip->buffers->databuf + mtd->writesize;	//计算oob位置if (!chip->ecc.layout) {	//设置ecc区布局switch (mtd->oobsize) {case 8:chip->ecc.layout = &nand_oob_8;break;case 16:chip->ecc.layout = &nand_oob_16;break;case 64:chip->ecc.layout = &nand_oob_64;break;case 128:chip->ecc.layout = &nand_oob_128;break;default:printk(KERN_WARNING "No oob scheme defined for oobsize %d\n", mtd->oobsize);BUG();}}if (!chip->write_page)	//没有页写方法chip->write_page = nand_write_page;	//指定默认的页写方法switch (chip->ecc.mode) {	case NAND_ECC_HW_OOB_FIRST:if (!chip->ecc.calculate || !chip->ecc.correct || !chip->ecc.hwctl) {printk(KERN_WARNING "No ECC functions supplied; Hardware ECC not possible\n");BUG();}if (!chip->ecc.read_page)chip->ecc.read_page = nand_read_page_hwecc_oob_first;case NAND_ECC_HW:if (!chip->ecc.read_page)chip->ecc.read_page = nand_read_page_hwecc;if (!chip->ecc.write_page)chip->ecc.write_page = nand_write_page_hwecc;if (!chip->ecc.read_page_raw)chip->ecc.read_page_raw = nand_read_page_raw;if (!chip->ecc.write_page_raw)chip->ecc.write_page_raw = nand_write_page_raw;if (!chip->ecc.read_oob)chip->ecc.read_oob = nand_read_oob_std;if (!chip->ecc.write_oob)chip->ecc.write_oob = nand_write_oob_std;case NAND_ECC_HW_SYNDROME:if ((!chip->ecc.calculate || !chip->ecc.correct || !chip->ecc.hwctl) &&(!chip->ecc.read_page || chip->ecc.read_page == nand_read_page_hwecc ||!chip->ecc.write_page || chip->ecc.write_page == nand_write_page_hwecc)) {printk(KERN_WARNING "No ECC functions supplied; Hardware ECC not possible\n");BUG();}if (!chip->ecc.read_page)chip->ecc.read_page = nand_read_page_syndrome;if (!chip->ecc.write_page)chip->ecc.write_page = nand_write_page_syndrome;if (!chip->ecc.read_page_raw)chip->ecc.read_page_raw = nand_read_page_raw_syndrome;if (!chip->ecc.write_page_raw)chip->ecc.write_page_raw = nand_write_page_raw_syndrome;if (!chip->ecc.read_oob)chip->ecc.read_oob = nand_read_oob_syndrome;if (!chip->ecc.write_oob)chip->ecc.write_oob = nand_write_oob_syndrome;if (mtd->writesize >= chip->ecc.size)break;printk(KERN_WARNING "%d byte HW ECC not possible on %d byte page size, fallback to SW ECC\n",chip->ecc.size, mtd->writesize);chip->ecc.mode = NAND_ECC_SOFT;case NAND_ECC_SOFT:chip->ecc.calculate = nand_calculate_ecc;chip->ecc.correct = nand_correct_data;chip->ecc.read_page = nand_read_page_swecc;chip->ecc.read_subpage = nand_read_subpage;chip->ecc.write_page = nand_write_page_swecc;chip->ecc.read_page_raw = nand_read_page_raw;chip->ecc.write_page_raw = nand_write_page_raw;chip->ecc.read_oob = nand_read_oob_std;chip->ecc.write_oob = nand_write_oob_std;if (!chip->ecc.size)chip->ecc.size = 256;chip->ecc.bytes = 3;break;case NAND_ECC_NONE:printk(KERN_WARNING "NAND_ECC_NONE selected by board driver. This is not recommended !!\n");chip->ecc.read_page = nand_read_page_raw;chip->ecc.write_page = nand_write_page_raw;chip->ecc.read_oob = nand_read_oob_std;chip->ecc.read_page_raw = nand_read_page_raw;chip->ecc.write_page_raw = nand_write_page_raw;chip->ecc.write_oob = nand_write_oob_std;chip->ecc.size = mtd->writesize;chip->ecc.bytes = 0;break;default:printk(KERN_WARNING "Invalid NAND_ECC_MODE %d\n",chip->ecc.mode);BUG();}chip->ecc.layout->oobavail = 0;for (i = 0; chip->ecc.layout->oobfree[i].length && i < ARRAY_SIZE(chip->ecc.layout->oobfree); i++)chip->ecc.layout->oobavail += chip->ecc.layout->oobfree[i].length;mtd->oobavail = chip->ecc.layout->oobavail;chip->ecc.steps = mtd->writesize / chip->ecc.size;if (chip->ecc.steps * chip->ecc.size != mtd->writesize) {printk(KERN_WARNING "Invalid ecc parameters\n");BUG();}chip->ecc.total = chip->ecc.steps * chip->ecc.bytes;if (!(chip->options & NAND_NO_SUBPAGE_WRITE) && !(chip->cellinfo & NAND_CI_CELLTYPE_MSK)) {switch (chip->ecc.steps) {case 2:mtd->subpage_sft = 1;break;case 4:case 8:case 16:mtd->subpage_sft = 2;break;}}chip->subpagesize = mtd->writesize >> mtd->subpage_sft;chip->state = FL_READY;	//初始化状态chip->select_chip(mtd, -1);	//选中芯片chip->pagebuf = -1;mtd->type = MTD_NANDFLASH;	//MTD设备类型mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :MTD_CAP_NANDFLASH;mtd->erase = nand_erase;	//擦除方法mtd->point = NULL;mtd->unpoint = NULL;mtd->read = nand_read;	//读方法mtd->write = nand_write;	//写方法mtd->panic_write = panic_nand_write;mtd->read_oob = nand_read_oob;	//读oob区mtd->write_oob = nand_write_oob;	//写oob区mtd->sync = nand_sync;	//同步mtd->lock = NULL;mtd->unlock = NULL;mtd->suspend = nand_suspend;	//挂起mtd->resume = nand_resume;	//唤醒mtd->block_isbad = nand_block_isbad;	//坏块判断mtd->block_markbad = nand_block_markbad;	//标记坏块mtd->writebufsize = mtd->writesize;mtd->ecclayout = chip->ecc.layout;	//ecc布局if (chip->options & NAND_SKIP_BBTSCAN)return 0;return chip->scan_bbt(mtd);	//扫描,标记坏块
}
EXPORT_SYMBOL(nand_scan_tail);

3.调用add_mtd_device或调用add_mtd_partitions添加mtd device


综合上面写好框架:

/* 参考 * drivers\mtd\nand\s3c2410.c* drivers\mtd\nand\Atmel_nand.c*/#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>#include <asm/io.h>
#include <asm/arch/regs-nand.h>
#include <asm/arch/nand.h>static struct nand_chip *s3c_nand;
static struct mtd_info *s3c_mtd;static void s3c2440_select_chip(struct mtd_info *mtd, int chipnr)
{if (chipnr == -1){/* 取消选中: NFCONT[1]设为0 */}else{/* 选中: NFCONT[1]设为1 */}
}static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{if (ctrl & NAND_CLE){/* 发命令: NFCMMD=dat */}else{/* 发地址: NFADDR=dat */}
}static int s3c2440_dev_ready(struct mtd_info *mtd)
{return "NFSTAT的bit[0]";
}static int s3c_nand_init(void)
{/* 分配一个nand_chip结构体 */s3c_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);/* 设置nand_chip *//* 设置nand_chip是给nand_scan函数使用的, 如果不知道怎么设置, 先看nand_scan怎么使用 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态的功能*/s3c_nand->select_chip = s3c2440_select_chip;s3c_nand->cmd_ctrl    = s3c2440_cmd_ctrl;s3c_nand->IO_ADDR_R   = "NFDATA的虚拟地址";s3c_nand->IO_ADDR_W   = "NFDATA的虚拟地址";s3c_nand->dev_ready   = s3c2440_dev_ready;/* 硬件相关的设置 *//* 使用: nand_scan */s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);s3c_mtd->owner = THIS_MODULE;s3c_mtd->priv  = s3c_nand;nand_scan(s3c_mtd, 1);  /* 识别NAND FLASH, 填充mtd_info *//* 5. add_mtd_partitions */return 0;
}static void s3c_nand_exit(void)
{
}module_init(s3c_nand_init);
module_exit(s3c_nand_exit);MODULE_LICENSE("GPL");

最后根据芯片手册完成具体操作:

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>#include <asm/io.h>#include <asm/arch/regs-nand.h>
#include <asm/arch/nand.h>struct s3c_nand_regs {unsigned long nfconf  ;unsigned long nfcont  ;unsigned long nfcmd   ;unsigned long nfaddr  ;unsigned long nfdata  ;unsigned long nfeccd0 ;unsigned long nfeccd1 ;unsigned long nfeccd  ;unsigned long nfstat  ;unsigned long nfestat0;unsigned long nfestat1;unsigned long nfmecc0 ;unsigned long nfmecc1 ;unsigned long nfsecc  ;unsigned long nfsblk  ;unsigned long nfeblk  ;
};static struct nand_chip *s3c_nand;
static struct mtd_info *s3c_mtd;
static struct s3c_nand_regs *s3c_nand_regs;static struct mtd_partition s3c_nand_parts[] = {   //4个分区,分区地址和uboot对应[0] = {.name   = "bootloader",.size   = 0x00040000,.offset	= 0,},[1] = {.name   = "params",.offset = MTDPART_OFS_APPEND, //接着上面结束地址.size   = 0x00020000,},[2] = {.name   = "kernel",.offset = MTDPART_OFS_APPEND,.size   = 0x00200000,},[3] = {.name   = "root",.offset = MTDPART_OFS_APPEND,.size   = MTDPART_SIZ_FULL,}
};static void s3c2440_select_chip(struct mtd_info *mtd, int chipnr)
{if (chipnr == -1){/* 取消选中: NFCONT[1]设为1 */s3c_nand_regs->nfcont |= (1<<1);		}else{/* 选中: NFCONT[1]设为0 */s3c_nand_regs->nfcont &= ~(1<<1);}
}static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{if (ctrl & NAND_CLE){/* 发命令: NFCMMD=dat */s3c_nand_regs->nfcmd = dat;}else{/* 发地址: NFADDR=dat */s3c_nand_regs->nfaddr = dat;}
}static int s3c2440_dev_ready(struct mtd_info *mtd)
{return (s3c_nand_regs->nfstat & (1<<0));
}static int s3c_nand_init(void)
{struct clk *clk;/*  分配一个nand_chip结构体 */s3c_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c_nand_regs));/*  设置nand_chip *//* 设置nand_chip是给nand_scan函数使用的, 如果不知道怎么设置, 先看nand_scan怎么使用 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态的功能*/s3c_nand->select_chip = s3c2440_select_chip;s3c_nand->cmd_ctrl    = s3c2440_cmd_ctrl;s3c_nand->IO_ADDR_R   = &s3c_nand_regs->nfdata;s3c_nand->IO_ADDR_W   = &s3c_nand_regs->nfdata;s3c_nand->dev_ready   = s3c2440_dev_ready;s3c_nand->ecc.mode    = NAND_ECC_SOFT;/*  硬件相关的设置: 根据NAND FLASH的手册设置时间参数 *//* 使能NAND FLASH控制器的时钟 */clk = clk_get(NULL, "nand");clk_enable(clk);              /* CLKCON'bit[4] *//* HCLK=100MHz* TACLS:  发出CLE/ALE之后多长时间才发出nWE信号, 从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0* TWRPH0: nWE的脉冲宽度, HCLK x ( TWRPH0 + 1 ), 从NAND手册可知它要>=12ns, 所以TWRPH0>=1* TWRPH1: nWE变为高电平后多长时间CLE/ALE才能变为低电平, 从NAND手册可知它要>=5ns, 所以TWRPH1>=0*/
#define TACLS    0
#define TWRPH0   1
#define TWRPH1   0s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);/* NFCONT: * BIT1-设为1, 取消片选 * BIT0-设为1, 使能NAND FLASH控制器*/s3c_nand_regs->nfcont = (1<<1) | (1<<0);/*  分配mtd,并使用: nand_scan初始化mtd */s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);s3c_mtd->owner = THIS_MODULE;s3c_mtd->priv  = s3c_nand;nand_scan(s3c_mtd, 1);  /* 识别NAND FLASH, 构造mtd_info *//*  add_mtd_partitions */add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4);//add_mtd_device(s3c_mtd); //如果不需要分区,使用这个return 0;
}static void s3c_nand_exit(void)
{del_mtd_partitions(s3c_mtd);kfree(s3c_mtd);iounmap(s3c_nand_regs);kfree(s3c_nand);
}module_init(s3c_nand_init);
module_exit(s3c_nand_exit);MODULE_LICENSE("GPL");

这样就完成了一个完整的NANFLASH驱动(MTD设备端)。

————————————————————————————————————————————————————————

测试方法:
1. make menuconfig去掉内核自带的NAND FLASH驱动
-> Device Drivers
  -> Memory Technology Device (MTD) support
    -> NAND Device Support
   < >   NAND Flash support for S3C2410/S3C2440 SoC
2. make uImage
   使用新内核启动, 并且使用NFS作为根文件系统
3. insmod s3c_nand.ko
4. 格式化 (参考下面编译工具)
   flash_eraseall  /dev/mtd3  // yaffs
   
5. 挂接
   mount -t yaffs /dev/mtdblock3 /mnt
6. 在/mnt目录下建文件   


编译工具:
1. tar xjf mtd-utils-05.07.23.tar.bz2 
2. cd mtd-utils-05.07.23/util
修改Makefile:
#CROSS=arm-linux-
改为
CROSS=arm-linux-
3. make
4. cp flash_erase flash_eraseall /work/nfs_root/first_fs/bin/

————————————————————————————————————————————————————————————————

附NANFLASH操作原理:


问1. 原理图上NAND FLASH和S3C2440之间只有数据线,
     怎么传输地址?
答1.在DATA0~DATA7上既传输数据,又传输地址
     当ALE为高电平时传输的是地址

问2. 从NAND FLASH芯片手册可知,要操作NAND FLASH需要先发出命令
     怎么传入命令?
答2.在DATA0~DATA7上既传输数据,又传输地址,也传输命令
     当ALE为高电平时传输的是地址,
     当CLE为高电平时传输的是命令
     当ALE和CLE都为低电平时传输的是数据

问3. 数据线既接到NAND FLASH,也接到NOR FLASH,还接到SDRAM、DM9000等等
     那么怎么避免干扰?
答3. 这些设备,要访问之必须"选中",
     没有选中的芯片不会工作,相当于没接一样

问4. 假设烧写NAND FLASH,把命令、地址、数据发给它之后,
     NAND FLASH肯定不可能瞬间完成烧写的,
     怎么判断烧写完成?
答4. 通过状态引脚RnB来判断:它为高电平表示就绪,它为低电平表示正忙

问5. 怎么操作NAND FLASH呢?
答5. 根据NAND FLASH的芯片手册,一般的过程是:
     发出命令
     发出地址
     发出数据/读数据


NANFLASH与S3C2440 NANDFLASH控制器操作状态表:
NAND FLASH                                         S3C2440
发命令:

选中芯片                   
CLE设为高电平                                      NFCMMD=命令值     
在DATA0~DATA7上输出命令值
发出一个写脉冲
            
发地址:

选中芯片                                                 NFADDR=地址值
ALE设为高电平
在DATA0~DATA7上输出地址值
发出一个写脉冲


发数据 :

选中芯片                                                NFDATA=数据值
ALE,CLE设为低电平
在DATA0~DATA7上输出数据值
发出一个写脉冲


读数据:

选中芯片                                               val=NFDATA
发出读脉冲
读DATA0~DATA7的数据

————————————————————————————————————————————————————————————————————


这篇关于学习笔记 --- LINUX MTD设备之NANDFLASH驱动分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/482685

相关文章

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

Linux之软件包管理器yum详解

《Linux之软件包管理器yum详解》文章介绍了现代类Unix操作系统中软件包管理和包存储库的工作原理,以及如何使用包管理器如yum来安装、更新和卸载软件,文章还介绍了如何配置yum源,更新系统软件包... 目录软件包yumyum语法yum常用命令yum源配置文件介绍更新yum源查看已经安装软件的方法总结软

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Linux:alias如何设置永久生效

《Linux:alias如何设置永久生效》在Linux中设置别名永久生效的步骤包括:在/root/.bashrc文件中配置别名,保存并退出,然后使用source命令(或点命令)使配置立即生效,这样,别... 目录linux:alias设置永久生效步骤保存退出后功能总结Linux:alias设置永久生效步骤

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Linux Mint Xia 22.1重磅发布: 重要更新一览

《LinuxMintXia22.1重磅发布:重要更新一览》Beta版LinuxMint“Xia”22.1发布,新版本基于Ubuntu24.04,内核版本为Linux6.8,这... linux Mint 22.1「Xia」正式发布啦!这次更新带来了诸多优化和改进,进一步巩固了 Mint 在 Linux 桌面