一,前言

这一篇主要总结Nor Flash驱动的工作方式和逻辑,熟悉nor flash驱动的框架,并分析了不同规范的Nor Flash芯片的识别过程,比如CFI规范和JEDEC规范的nor flash 。

二,硬件电路

2.1 脚位功能

LDATA0~LDATA15:数据传输
LADDR1~LADDR19:地址传输
nRESET:复位
LnOE:读状态信号
LnWE:写状态信号
nGCS0:片选信号
在这里插入图片描述

2.2 地址移位

S3c2440的LADDR1接到Nor flash的A0,所以2440向Nor flash发送其需要的地址时,实际应该发出“需要的地址”<< 1,才能让Nor flash真正收到其所需要的地址。比如Nor flash需要的地址是0x555,那么2440应该发出0x555<<1即0xaaa。
在这里插入图片描述

三,Nand Flash和Nor Flash的区别

NandNor
接口引脚少,数据和地址复用引脚多,数据和地址引脚分开
容量大,128M/256M/xG小,1M/2M/32M
读数据复杂简单,像内存一样读
价格便宜
硬件特性位反转,坏块无位反转
片上执行程序不可可在片上直接执行程序

四,Nor flash CFI规范和JEDEC规范

4.1 JEDEC规范

老式的Nor Flash一般是jedec规范,其一般只包含识别 ID、擦除芯片、烧写数据的命令。要想知道其容量大小等信息,就需要先读出其芯片id,然后到内核中的jedec_table数组中比较得到对应的芯片信息,比较麻烦。另外如果内核jedec_table数组中事先没有对应芯片id的信息,还需要先在该数组中添加。
jedec_table数组
在这里插入图片描述

4.2 CFI规范

目前的Nor Flash一般都支持CFI规范,其除了提供识别 ID、擦除芯片、烧写数据的命令之后,还提供了进入CFI模式的命令,进入CFI模式后就可以通过读取相应地址的数据获取芯片属性信息,如容量、电压等信息。
进入CFI模式(往地址0x55处写入0x98)
在这里插入图片描述

读取芯片容量(从地址0x27处读取容量大小 )
在这里插入图片描述

五,Nor flash驱动框架

在这里插入图片描述

六,Nor Flash驱动分析

6.1 配置Nor Flash驱动编译

make menuconfig

-> Device Drivers                                                                                               │   
   -> Memory Technology Device (MTD) support (MTD [=y])-> Mapping drivers for chip access    
    	<M> CFI Flash device in physical memory map       // 将驱动编译位内核模块,方便调试,设位y为编译进内核                                             │ │   
        (0x0) Physical start address of flash mapping     // 配置基地址                                          │ │   
 	    (0x1000000) Physical length of flash mapping      // 配置物理大小                                          │ │   
        (2)   Bank width in octets                        // 配置位宽  2*8=16

保存,退出make menuconfig后可以在linux-2.6.22.6/.config中看下如下信息

CONFIG_MTD_PHYSMAP=m
CONFIG_MTD_PHYSMAP_START=0x0
CONFIG_MTD_PHYSMAP_LEN=0x1000000
CONFIG_MTD_PHYSMAP_BANKWIDTH=2

6.2 Nor Flash驱动入口函数

// linux-2.6.22.6/drivers/mtd/maps/physmap.c
static int __init physmap_init(void)
{
	int err;

	err = platform_driver_register(&physmap_flash_driver);
#ifdef PHYSMAP_COMPAT
	if (err == 0)
		platform_device_register(&physmap_flash);
#endif

	return err;
}

// make menuconfig时配置生成的.config中,定义了CONFIG_MTD_PHYSMAP_LEN宏
// 所以这里将会定义#define PHYSMAP_COMPAT,
// 进而会在驱动入口函数中执行platform_device_register接口调用。
#ifdef CONFIG_MTD_PHYSMAP_LEN  
#if CONFIG_MTD_PHYSMAP_LEN != 0
#warning using PHYSMAP compat code
#define PHYSMAP_COMPAT
#endif
#endif


static struct platform_driver physmap_flash_driver = {
	.probe		= physmap_flash_probe,
	.remove		= physmap_flash_remove,
#ifdef CONFIG_PM
	.suspend	= physmap_flash_suspend,
	.resume		= physmap_flash_resume,
	.shutdown	= physmap_flash_shutdown,
#endif
	.driver		= {
		.name	= "physmap-flash",
	},
};

6.3 驱动probe函数分析

注册平台驱动,注册平台设备以及它们的匹配,最终调用驱动的probe函数的流程在之前的文章中分析过多次,这里不再赘述。直接进入probe函数分析。

struct physmap_flash_info {
	struct mtd_info		*mtd;
	struct map_info		map;   //需要定义一个map_info结构体
	struct resource		*res;
#ifdef CONFIG_MTD_PARTITIONS
	int			nr_parts;
	struct mtd_partition	*parts;
#endif
};

// 设备信息
static struct physmap_flash_data physmap_flash_data = {
	.width		= CONFIG_MTD_PHYSMAP_BANKWIDTH,
};

static struct resource physmap_flash_resource = {
	.start		= CONFIG_MTD_PHYSMAP_START,
	.end		= CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN - 1,
	.flags		= IORESOURCE_MEM,
};

static int physmap_flash_probe(struct platform_device *dev)
{
	struct physmap_flash_data *physmap_data;
	struct physmap_flash_info *info;
	const char **probe_type;
	int err;

	physmap_data = dev->dev.platform_data;
	if (physmap_data == NULL)
		return -ENODEV;

       	printk(KERN_NOTICE "physmap platform flash device: %.8llx at %.8llx\n",
	    (unsigned long long)(dev->resource->end - dev->resource->start + 1),
	    (unsigned long long)dev->resource->start);

	info = kzalloc(sizeof(struct physmap_flash_info), GFP_KERNEL);
	if (info == NULL) {
		err = -ENOMEM;
		goto err_out;
	}

	platform_set_drvdata(dev, info);

	info->res = request_mem_region(dev->resource->start,
			dev->resource->end - dev->resource->start + 1,
			dev->dev.bus_id);
	if (info->res == NULL) {
		dev_err(&dev->dev, "Could not reserve memory region\n");
		err = -ENOMEM;
		goto err_out;
	}

	// 设置map_info结构体
	info->map.name = dev->dev.bus_id;
	info->map.phys = dev->resource->start; // 起始地址
	info->map.size = dev->resource->end - dev->resource->start + 1; //大小
	info->map.bankwidth = physmap_data->width; // 位宽
	info->map.set_vpp = physmap_data->set_vpp;

	info->map.virt = ioremap(info->map.phys, info->map.size);  // 物理地址到虚拟地址的映射
	if (info->map.virt == NULL) {
		dev_err(&dev->dev, "Failed to ioremap flash region\n");
		err = EIO;
		goto err_out;
	}

	simple_map_init(&info->map);

	//  { "cfi_probe", "jedec_probe", "map_rom", NULL }; cfi规范、jedec规范、内存模拟的nor flash
	probe_type = rom_probe_types;
	for (; info->mtd == NULL && *probe_type != NULL; probe_type++)
		 // 识别指定规范的nor flash,获取mtd结构体
		info->mtd = do_map_probe(*probe_type, &info->map);
	if (info->mtd == NULL) {
		dev_err(&dev->dev, "map_probe failed\n");
		err = -ENXIO;
		goto err_out;
	}
	info->mtd->owner = THIS_MODULE;

#ifdef CONFIG_MTD_PARTITIONS
	// { "cmdlinepart", "RedBoot", NULL },添加cmdlinepart分区和RedBoot分区
	// 获取这个两个分区的信息,然后添加这俩个分区,并为其创建对应的字符设备节点和块设备节点
	err = parse_mtd_partitions(info->mtd, part_probe_types, &info->parts, 0);
	if (err > 0) {
		add_mtd_partitions(info->mtd, info->parts, err);
		return 0;
	}
	// 在设备信息中配置了分区的话,就添加对应的分区。这里没有配置,即physmap_data->nr_parts=0,没有要添加额外的分区。
	if (physmap_data->nr_parts) {
		printk(KERN_NOTICE "Using physmap partition information\n");
		add_mtd_partitions(info->mtd, physmap_data->parts,
						physmap_data->nr_parts);
		return 0;
	}
#endif
	// 添加主分区创建对应的字符设备节点和块设备节点。
	add_mtd_device(info->mtd);
	return 0;

err_out:
	physmap_flash_remove(dev);
	return err;
}

6.4 CFI规范Flash识别分析

6.4.1 CFI协议层

// cfi协议层
// linux-2.6.22.6/drivers/mtd/chips/chipreg.c
void register_mtd_chip_driver(struct mtd_chip_driver *drv)
{
	spin_lock(&chip_drvs_lock);
    // 将协议层接口放到chip_drvs_list链表中
	list_add(&drv->list, &chip_drvs_list);
	spin_unlock(&chip_drvs_lock);
}

//linux-2.6.22.6/drivers/mtd/chips/cfi_probe.c
// 内核初始化时被调用
static int __init cfi_probe_init(void)
{
	register_mtd_chip_driver(&cfi_chipdrv);
	return 0;
}

static struct mtd_chip_driver cfi_chipdrv = {
	.probe		= cfi_probe,
	.name		= "cfi_probe",
	.module		= THIS_MODULE
};

struct mtd_info *cfi_probe(struct map_info *map)
{
	/*
	 * Just use the generic probe stuff to call our CFI-specific
	 * chip_probe routine in all the possible permutations, etc.
	 */
	return mtd_do_chip_probe(map, &cfi_chip_probe);
}

static struct chip_probe cfi_chip_probe = {
	.name		= "CFI",
	.probe_chip	= cfi_probe_chip
};

6.4.2 CFI 规范Flash识别过程

// linux-2.6.22.6/drivers/mtd/chips/chipreg.c
// *probe_type--"cfi_probe"  
info->mtd = do_map_probe(*probe_type, &info->map) ->
    drv = get_mtd_chip_driver(name) ->
        ......
        // 从chip_drvs_list链表中取出对应协议层接口
        // chip_drvs_list链表在register_mtd_chip_driver接口中设置
        // register_mtd_chip_driver接口在协议层调用
    	// cfi_probe_init/jedec_probe_init/map_ram_init
        list_for_each(pos, &chip_drvs_list)
          this = list_entry(pos, typeof(*this), list); 
          if (!strcmp(this->name, name)) {
    			ret = this;
    			break;
			}
    	......
    	return ret;
	// 执行协议层的probe函数 即上面分析的cfi_probe接口
    drv->probe(map)-> //即cfi_probe(map)
        // cfi_chip_probe 结构体中有一个.probe_chip接口(cfi_probe_chip)
        mtd_do_chip_probe(map, &cfi_chip_probe)->
            genprobe_ident_chips(map, cp) ->
                genprobe_new_chip(map, cp, &cfi) ->
                    // 在该函数中,进入通过命令CFI模式,读取芯片信息
                    cp->probe_chip(map, 0, NULL, cfi) -> //即cfi_probe_chip
                        // 进入CFI模式
                        cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
						cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL);
						cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL);
                       	// 读取芯片信息,比如cfi->cfiq->P_ID = 0x0000 0002
                	    // 由芯片手册知
						// P_ID: Primary vendor command set and control interface ID code
						cfi_chip_setup(map, cfi) ->
                            ......
                            for (i=0; i<(sizeof(struct cfi_ident) + num_erase_regions * 4); i++)
								((unsigned char *)cfi->cfiq)[i] = cfi_read_query(map,base + (0x10 + i)*ofs_factor);
                            .....
        //  根据上面读取到的P_ID调用相应的接口申请并设置struct mtd_info结构体                               
        mtd = check_cmd_set(map, 1) ->
            ......
            case 0x0002:
			return cfi_cmdset_0002(map, primary) ->
                .......
                mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
            	if (!mtd) {
            		printk(KERN_WARNING "Failed to allocate memory for MTD device\n");
            		return NULL;
            	}
            	mtd->priv = map;
            	mtd->type = MTD_NORFLASH;
            
            	/* Fill in the default mtd operations */
            	mtd->erase   = cfi_amdstd_erase_varsize;
            	mtd->write   = cfi_amdstd_write_words;
            	mtd->read    = cfi_amdstd_read;
            	mtd->sync    = cfi_amdstd_sync;
            	mtd->suspend = cfi_amdstd_suspend;
            	mtd->resume  = cfi_amdstd_resume;
            	mtd->flags   = MTD_CAP_NORFLASH;
            	.......
            ......
        return mtd;

6.5 JEDEC规范Flash识别分析

6.5.1 JEDEC协议层

// jedec协议层
// linux-2.6.22.6/drivers/mtd/chips/chipreg.c
void register_mtd_chip_driver(struct mtd_chip_driver *drv)
{
	spin_lock(&chip_drvs_lock);
    // 将协议层接口放到chip_drvs_list链表中
	list_add(&drv->list, &chip_drvs_list);
	spin_unlock(&chip_drvs_lock);
}

// linux-2.6.22.6/drivers/mtd/chips/jedec_probe.c
static int __init jedec_probe_init(void)
{	
	register_mtd_chip_driver(&jedec_chipdrv);
	return 0;
}

static struct mtd_chip_driver jedec_chipdrv = {
	.probe	= jedec_probe,
	.name	= "jedec_probe",
	.module	= THIS_MODULE
};

static struct mtd_info *jedec_probe(struct map_info *map)
{
	/*
	 * Just use the generic probe stuff to call our CFI-specific
	 * chip_probe routine in all the possible permutations, etc.
	 */
	return mtd_do_chip_probe(map, &jedec_chip_probe);
}

static struct chip_probe jedec_chip_probe = {
	.name = "JEDEC",
	.probe_chip = jedec_probe_chip
};

6.5.2 JEDEC规范Flash识别过程

info->mtd = do_map_probe(*probe_type, &info->map) ->
    drv = get_mtd_chip_driver(name) ->
        ......
        // 从chip_drvs_list链表中取出对应协议层驱动
        // chip_drvs_list链表在register_mtd_chip_driver接口中设置
        // register_mtd_chip_driver接口在协议层调用
    	// cfi_probe_init/jedec_probe_init/map_ram_init
        list_for_each(pos, &chip_drvs_list)
          this = list_entry(pos, typeof(*this), list); 
          if (!strcmp(this->name, name)) {
    			ret = this;
    			break;
			}
    	......
    	return ret;
	// 执行协议层的probe函数 即上面分析的cfi_probe接口
    ret = drv->probe(map)-> //jedec_probe(map)
        // jedec_chip_probe 结构体中有一个.probe_chip接口(jedec_probe_chip)
        mtd_do_chip_probe(map, &jedec_chip_probe) -> 
            genprobe_ident_chips(map, cp) -> 
                genprobe_new_chip(map, cp, &cfi) ->
                    // 读取芯片的id,和jedec_table数组比较得到芯片信息
                    cp->probe_chip(map, 0, NULL, cfi) -> //即jedec_probe_chip
                        jedec_match( base, map, cfi, &jedec_table[i] )
                        cfi_jedec_setup(cfi, i) ->
                            p_cfi->cfiq->P_ID = jedec_table[index].CmdSet;
            // 根据读取到的p_cfi->cfiq->P_ID来调用对应接口申请并设置struct mtd_info
            mtd = check_cmd_set(map, 1)
                
            return mtd;
    return ret;

6.6 添加分区接口add_mtd_partitions分析

// 在physmap_flash_probe函数中调用,err为分区个数
add_mtd_partitions(info->mtd, info->parts, err) -> 
    ......
    for (i = 0; i < nbparts; i++)
        // 将该分区添加到mtd_partitions链表中,以便后面删除分区
        list_add(&slave->list, &mtd_partitions) 
        // 设置分区的struct mtd_info结构体
        ......
        slave->mtd.type = master->type;
		slave->mtd.flags = master->flags & ~parts[i].mask_flags;
		slave->mtd.size = parts[i].size;
		slave->mtd.writesize = master->writesize;
		slave->mtd.oobsize = master->oobsize;
		slave->mtd.oobavail = master->oobavail;
		slave->mtd.subpage_sft = master->subpage_sft;
    	......
        // 为分区创建对应的字符设备节点和块设备节点
        add_mtd_device(&slave->mtd);
    ......

6.7 physmap_flash_probe函数添加主分区

// 添加主分区创建对应的字符设备节点和块设备节点。
add_mtd_device(info->mtd);   // 和前一篇nand flash驱动一致,这里不再赘述

七,自行构建Nor Flash驱动


/*
 * 参考 drivers\mtd\maps\physmap.c
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/partitions.h>
#include <asm/io.h>

static struct map_info *s3c_nor_map;
static struct mtd_info *s3c_nor_mtd;

static struct mtd_partition s3c_nor_parts[] = {
	[0] = {
        .name   = "bootloader_nor",
        .size   = 0x00040000,
		.offset	= 0,
	},
	[1] = {
        .name   = "root_nor",
        .offset = MTDPART_OFS_APPEND,
        .size   = MTDPART_SIZ_FULL,
	}
};


static int s3c_nor_init(void)
{
	/* 1. 分配map_info结构体 */
	s3c_nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);;
	
	/* 2. 设置: 物理基地址(phys), 大小(size), 位宽(bankwidth), 虚拟基地址(virt) */
	s3c_nor_map->name = "s3c_nor";
	s3c_nor_map->phys = 0;
	s3c_nor_map->size = 0x1000000; /* >= NOR的真正大小 */
	s3c_nor_map->bankwidth = 2;
	s3c_nor_map->virt = ioremap(s3c_nor_map->phys, s3c_nor_map->size);

	simple_map_init(s3c_nor_map);
	
	/* 3. 使用: 调用NOR FLASH协议层提供的函数来识别 */
	printk("use cfi_probe\n");
	s3c_nor_mtd = do_map_probe("cfi_probe", s3c_nor_map);
	if (!s3c_nor_mtd)
	{
		printk("use jedec_probe\n");
		s3c_nor_mtd = do_map_probe("jedec_probe", s3c_nor_map);
	}

	if (!s3c_nor_mtd)
	{		
		iounmap(s3c_nor_map->virt);
		kfree(s3c_nor_map);
		return -EIO;
	}
	
	/* 4. add_mtd_partitions */
	add_mtd_partitions(s3c_nor_mtd, s3c_nor_parts, 2);
	
	return 0;
}

static void s3c_nor_exit(void)
{
	del_mtd_partitions(s3c_nor_mtd);
	iounmap(s3c_nor_map->virt);
	kfree(s3c_nor_map);
}

module_init(s3c_nor_init);
module_exit(s3c_nor_exit);

MODULE_LICENSE("GPL");

八,总结

Nor flash驱动构建一般分为以下几个步骤

  • 根据硬件电路和芯片书册设置struct map_info结构体。
  • 调用do_map_probe接口识别对应规范的Nor Flash芯片,并获取到一个对应芯片的struct mtd_info结构体。
  • 需要添加除主分区以外的子分区,则调用add_mtd_partitions接口添加。
  • 调用add_mtd_device接口创建主分区的字符设备节点以及块设备节点。块设备节点一般用于数据的读写,字符设备节点在ioctl接口中提供了各种对设备的操作,比如擦除分区。
Logo

更多推荐