NAND flash驱动程序(1)

前言

       前面我们已经写过一个块设备驱动程序,用内存来模拟块设备。假如我们想写一个NAND FLASH驱动程序,又应该怎么做呢?我们先看一下kernel代码中别人现成的驱动程序,分析一下流程框架,总结出一个套路,我们就能开始写自己的驱动程序了。

       之前写过一个裸板的nand flash程序:NAND FLASH的读操作及原理,可以参考一下。

正文

       我参考的是这个驱动程序:drivers\mtd\nand\at91_nand.c。在调用platform_driver_register()平台注册函数后,如果找到device的name同样为"at91_nand",就会调用driver的probe函数(为什么会自动调用probe函数?可以参考我之前写的文章:设备、驱动、总线模型简介)。

static struct platform_driver at91_nand_driver = {
	.probe		= at91_nand_probe,
	.remove		= at91_nand_remove,
	.driver		= {
		.name	= "at91_nand",
		.owner	= THIS_MODULE,
	},
};

static int __init at91_nand_init(void)
{
	return platform_driver_register(&at91_nand_driver);
}

这里也顺便看一下相关设备资源的注册,其实就是NAND flash的需要设置的寄存器的首地址和结束地址

static struct resource nand_resources[] = {
	{
		.start	= NAND_BASE,
		.end	= NAND_BASE + SZ_8M - 1,
		.flags	= IORESOURCE_MEM,
	}
};

static struct platform_device at91rm9200_nand_device = {
	.name		= "at91_nand",
	.id		= -1,
	.dev		= {
				.platform_data	= &nand_data,
	},
	.resource	= nand_resources,
	.num_resources	= ARRAY_SIZE(nand_resources),
};



void __init at91_add_device_nand(struct at91_nand_data *data)
{
	...
	platform_device_register(&at91rm9200_nand_device);
}

假如已经调用到了probe函数,我们看一下注册函数里面大概做了什么

static int __init at91_nand_probe(struct platform_device *pdev)
{
    struct at91_nand_host *host;
    struct mtd_info *mtd;
    struct nand_chip *nand_chip; //分配nand_chip结构体
    
    mtd->priv = nand_chip;
    mtd = &host->mtd;
    nand_chip = &host->nand_chip;
    
    ... //设置nand_chip结构体
    
    if (host->board->bus_width_16)		/* 16-bit bus width */
		nand_chip->options |= NAND_BUSWIDTH_16;
    
    nand_scan(mtd, 1)
    add_mtd_partitions(mtd, partitions, num_partitions);
}

由上面的代码可以看出

(1)首先是分配了三个结构体:其中我们重点关注一下mtd_info和nand_chip结构体

(2)然后就是设置nand_chip结构体中的参数等等

(3)最后就是调用了nand_scan()和add_mtd_parttions()函数

下面我们先看一下nand_scan()中具体做了什么。

int nand_scan(struct mtd_info *mtd, int maxchips)
{
	int ret;

	...

	ret = nand_scan_ident(mtd, maxchips);
	if (!ret)
		ret = nand_scan_tail(mtd);
	return ret;
}

继续看一下nand_scan_ident()和nand_scan_tail()里面做了什么 

int nand_scan_ident(struct mtd_info *mtd, int maxchips)
{
    struct nand_chip *chip = mtd->priv;
    /* Get buswidth to select the correct functions */
    busw = chip->options & NAND_BUSWIDTH_16; //总线的宽度
    
    /* Set the default functions */
    nand_set_defaults(chip, busw); //设置一些默认的函数,比如选中芯片的函数、发命令函数、读取寄存器值的函数等等
    
    /* Read the flash type */
    type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id);
}
int nand_scan_tail(struct mtd_info *mtd)
{
    //内容比较多,但是总体就是设置mtd结构体中的读、写、擦除nand flash的操作函数等等
    mtd->erase = nand_erase;
    mtd->read = nand_read;
    mtd->write = nand_write;
}

       真正跟到代码里面的话,内容非常多,总结起来就是调用nand_scan()函数,完成一些读、写或者擦除nand flash等等的一些操作函数。设置完后,应该就是注册我们的设置的结构体了,下面继续看一下probe函数最后的add_mtd_partitions()里面的各种调用关系。

add_mtd_partitions(struct mtd_info *master, const struct mtd_partition *parts, int nbparts)
    //设置slave->mtd,比如读、写、擦除函数等等
    /* register our partition */
    add_mtd_device(&slave->mtd); //注册设置过的slave->mtd
        list_for_each(this, &mtd_notifiers) {// mtd_notifiers在哪设置?drivers/mtd/mtdchar.c,mtd_blkdev.c调用register_mtd_user
            struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
            not->add(mtd);
        }
        /* 其中,not->add函数可以分为两部分:
         * mtd_notify_add -- 字符设备部分
         * blktrans_notify_add -- 块设备部分
         */

先看一下字符设备部分:

static void mtd_notify_add(struct mtd_info* mtd)
{
	if (!mtd)
		return;

	class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2),
			    NULL, "mtd%d", mtd->index);

	class_device_create(mtd_class, NULL,
			    MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),
			    NULL, "mtd%dro", mtd->index);
}

是我们之前写字符设备常见的class_device_create函数,在/sys/class目录创建了相应的设备类目录/sys/class/mtd/

再看一下块设备部分:

static void blktrans_notify_add(struct mtd_info *mtd)
	list_for_each(this, &blktrans_majors) {
		struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);

		tr->add_mtd(tr, mtd); //调用到了drivers\mtd\mtdblock.c的mtdblock_add_mtd函数
	}

最后再进到mtdblock_add_mtd()函数里面的各种调用关系

mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
    add_mtd_blktrans_dev(dev);
        //下面就是我们熟悉的,块设备书写的流程了
        struct gendisk *gd;
        gd = alloc_disk(1 << tr->part_bits);
        add_disk(gd);

可以看出来,还是我们熟悉的块设备驱动程序的书写套路。

结语

       虽然前面写了很多的内容,而且kernel中代码的流程也非常的复杂,但其实很大一部分是前人为我们抽象出来的一套框架,我们需要做的主要是底层硬件的操作。

       在分析流程的时候,我们不难看出,我们要书写一个新的驱动程序,主要就参考probe函数中的部分就好了。下面给出一个别人总结的驱动框架流程图:

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值