zynq之nand-flash驱动

前面已经了解过了smc控制器的驱动,如果要想使用nand-flash,光配置好smc控制器还不够;对于Norflash、dram 之类的存储设备,CPU 可以直接通过地址总线对其进行访问,而 Nand Flash 没有这类的总线,只有 IO 接口,只能通过复用的 IO接口发送命令和地址,从而实现对 Nand Flash 内部数据进行访问。因此必然有这样一个文件来实现复杂的flash读写操作,这个文件就是pl35x_nand.c。

  • pl35x_nand驱动程序

按照阅读linux驱动程序的方法,来解读pl35x_nand.c文件。首先也是注册驱动,如果compatible能够与设备树对应上,则调用probe函数,先看下probe函数都干了啥?

static int pl35x_nand_probe(struct platform_device *pdev)
{
	struct pl35x_nand_info *xnand;
	struct mtd_info *mtd;
	struct nand_chip *nand_chip;
	struct resource *res;
	int ondie_ecc_state;

	xnand = devm_kzalloc(&pdev->dev, sizeof(*xnand), GFP_KERNEL);	//分配一个pl35x_nand_info结构体
	if (!xnand)
		return -ENOMEM;

	/* Map physical address of NAND flash */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);	//获取设备树中reg属性
	xnand->nand_base = devm_ioremap_resource(&pdev->dev, res);	//nand基地址映射
	if (IS_ERR(xnand->nand_base))
		return PTR_ERR(xnand->nand_base);

	nand_chip = &xnand->chip;
	mtd = nand_to_mtd(nand_chip);

	nand_set_controller_data(nand_chip, xnand);
	mtd->priv = nand_chip;
	mtd->owner = THIS_MODULE;
	mtd->name = PL35X_NAND_DRIVER_NAME;
	nand_set_flash_node(nand_chip, pdev->dev.of_node);

	/* Set address of NAND IO lines */
	nand_chip->IO_ADDR_R = xnand->nand_base;
	nand_chip->IO_ADDR_W = xnand->nand_base;

	/* Set the driver entry points for MTD */
	nand_chip->cmdfunc = pl35x_nand_cmd_function;		//发送命令给nand-flash
	nand_chip->dev_ready = pl35x_nand_device_ready;		//检查设备是否busy
	nand_chip->select_chip = pl35x_nand_select_chip;	//片选nand-flash

	/* If we don't set this delay driver sets 20us by default */
	nand_chip->chip_delay = 30;

	/* Buffer read/write routines */
	nand_chip->read_buf = pl35x_nand_read_buf;			//把chip数据读入缓存
	nand_chip->write_buf = pl35x_nand_write_buf;		//	把缓存数据写入chip

	/* Set the device option and flash width */
	nand_chip->options = NAND_BUSWIDTH_AUTO;
	nand_chip->bbt_options = NAND_BBT_USE_FLASH;

	platform_set_drvdata(pdev, xnand);

	ondie_ecc_state = pl35x_nand_detect_ondie_ecc(mtd);	//获取flash的ecc状态

	/* first scan to find the device and get the page size */
	if (nand_scan_ident(mtd, 1, NULL)) {				//扫描nand-flash芯片,获取页大小
		dev_err(&pdev->dev, "nand_scan_ident for NAND failed\n");
		return -ENXIO;
	}

	xnand->row_addr_cycles = nand_chip->onfi_params.addr_cycles & 0xF;
	xnand->col_addr_cycles =
				(nand_chip->onfi_params.addr_cycles >> 4) & 0xF;

	pl35x_nand_ecc_init(mtd, &nand_chip->ecc, ondie_ecc_state);//根据ecc模式初始化ecc信息
	if (nand_chip->options & NAND_BUSWIDTH_16)
		pl35x_smc_set_buswidth(PL35X_SMC_MEM_WIDTH_16);	//如果带宽是16位的,则初始化SMC控制器位16位,如果在设备树中没有指定带宽,则默认为8位

	/* second phase scan */
	if (nand_scan_tail(mtd)) {    //在这里实现了mtd对nand的读写操作,这是对应于块设备的操作函数,字符型设备的fops在mtdchar.c中实现的
		dev_err(&pdev->dev, "nand_scan_tail for NAND failed\n");
		return -ENXIO;
	}

	mtd_device_register(mtd, NULL, 0);		//解析分区,并注册mtd设备

	return 0;
}

probe函数中调用了nand_scan_tail(关键函数),这个函数实现了对flash的基本操作函数,这里就不分析这些write\read函数的实现了,具体的可自己看代码。但请注意:nand_scan_tail函数中定义的只是底层的flash操作函数,并不是对应VFS的file_operation接口。

probe函数的最后,会调用mtd_device_register来注册mtd设备:

mtd_device_register->mtd_device_parse_register会直接调用到mtdcore.c中的函数了。

int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
			      struct mtd_part_parser_data *parser_data,
			      const struct mtd_partition *parts,
			      int nr_parts)
{
	struct mtd_partitions parsed;
	int ret;

	mtd_set_dev_defaults(mtd);

	memset(&parsed, 0, sizeof(parsed));

	ret = parse_mtd_partitions(mtd, types, &parsed, parser_data);        //解析设备树中的partitions分区
	if ((ret < 0 || parsed.nr_parts == 0) && parts && nr_parts) {
		/* Fall back to driver-provided partitions */
		parsed = (struct mtd_partitions){
			.parts		= parts,
			.nr_parts	= nr_parts,
		};
	} else if (ret < 0) {
		/* Didn't come up with parsed OR fallback partitions */
		pr_info("mtd: failed to find partitions; one or more parsers reports errors (%d)\n",
			ret);
		/* Don't abort on errors; we can still use unpartitioned MTD */
		memset(&parsed, 0, sizeof(parsed));
	}

	ret = mtd_add_device_partitions(mtd, &parsed);    //向内核注册mtd设备
	if (ret)
		goto out;

	/*
	 * FIXME: some drivers unfortunately call this function more than once.
	 * So we have to check if we've already assigned the reboot notifier.
	 *
	 * Generally, we can make multiple calls work for most cases, but it
	 * does cause problems with parse_mtd_partitions() above (e.g.,
	 * cmdlineparts will register partitions more than once).
	 */
	WARN_ONCE(mtd->_reboot && mtd->reboot_notifier.notifier_call,
		  "MTD already registered\n");
	if (mtd->_reboot && !mtd->reboot_notifier.notifier_call) {
		mtd->reboot_notifier.notifier_call = mtd_reboot_notifier;
		register_reboot_notifier(&mtd->reboot_notifier);
	}

out:
	/* Cleanup any parsed partitions */
	mtd_part_parser_cleanup(&parsed);
	return ret;
}

最终在mtd_add_device_partitions中会调用到下图中的程序,所以,在/dev/目录下,我们可以看到类似mtd0、mtd1、mtd0ro、mtd1ro这些设备节点。

但看到现在,我们仍然没有看到驱动代码中实现的file_operation结构体。由于flash设备既可以作为字符型设备来访问,也可以作为块设备来访问。不管是作为哪种设备,都必须提供VFS相应的接口。如果是字符型设备,则需要在驱动中实现file_operation结构体,如果是作为块设备,则需要在驱动中实现mtd_blktrans_ops结构体。

对于用户空间的程序,一般对flash的操作,分为两种:一种是将flash作为字符型设备,利用open、write、read等函数来实现对flash的读写;还有一种就是将flash作为块设备,并将块设备上挂载文件系统,直接在文件系统中进行新建文件、删除文件等操作。不管是哪种操作,用户空间调用的都是VFS提供的接口,因此底层驱动必须要向VFS提供驱动接口,这样用户程序才可以正常使用flash设备。

那么字符型设备和块设备都是如何实现驱动对VFS的接口呢?其实,mtd有一套自己的驱动框架,不管是mtd块设备还是mtd字符设备,最终都会调用到对nand-flash的读写操作上。其各模块的调用顺序如下:

  • 将mtd作为块设备来使用

首先内核会自动加载mtdblock的驱动文件,在mtdblock.c中会注册块设备对应VFS的接口程序,并通过mtdcore.c路由到nand_base.c,nand_base.c中实现的就是nand-flash具体的读写操作,而nand_base.c中实现的操作是在pl35x_nand.c中调用的。

  • 将mtd作为字符设备来使用

首先内核会自动加载mtd_core的驱动文件,在mtd_core.c中会调用到mtd_char.c中的字符驱动注册函数,并在mtd_char.c中实现字符驱动对应于file_operation函数。而file_operation的成员函数又会通过mtd_core路由到nand_base.c中的函数,最终实现用户程序对flash的访问和操作。

现在可以梳理下mtd的驱动框架:

内核会自动加载mtdblock驱动(块设备注册)和mtd_core驱动(字符设备注册),然后根据你的nand-flash型号加载nand-flash驱动(pl35x_nand驱动),在pl35x_nand驱动中调用nand_base.c中的函数,而nand_base.c中定义了NAND 驱动中对NAND 芯片最基本的操作函数和操作流程,如擦除、读写page 、读写oob 等。当然这些函数都只是进行一些default 的操作,若你的系统在对NAND 操作时有一些特殊的动作,则需要在你自己的驱动代码中进行定义,然后Replace 这些default 的函数。

用户程序在将mtd设备作为块设备时,要想对块设备进行写操作,会先调用mtd_blktrans_ops结构体中的.writesect函数,并最终会调用到nand_base.c中的nand_write函数;

用户程序在讲mtd设备作为字符设备时,要想对字符设备进行写操作,会先调用file_operation结构体中的.write函数,并最终会调用到nand_base.c中的nand_write函数;

mtd驱动框架涉及的文件有:pl35x-smc.c、pl35x_nand.c  、nand_base.c 、mtdcore.c、mtdchar.c 、mtdblock.c 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值