35.驱动--MTD子系统

目录

nand_flash:

nor_flash:


linux设备驱动(32)MTD子系统详解 - Action_er - 博客园MTD子系统分析(一)——MTD——简介 - 程序员大本营linux mtd 块设备,基于块设备子系统的MTD子系统(2.6.26)_寅成的博客-CSDN博客

nand_flash例子

24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动) - 诺谦 - 博客园

nor_flash例子

25.Linux-Nor Flash驱动(详解) - 诺谦 - 博客园

写写加深印象。

MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化硬件驱动框架。使用MTD子系统写flash设备驱动,非常快捷

MTD子系统驱动主要曾为两层:

1.MTD块设备层,这部分负责设备的建立,数据的读/写、优化处理等,这跟传统的块设备驱动类似。设备的建立,gendisk结构体的分配设置,队列的设置等,内核都会自动完成。这层使用mtd_info结构体描述分区

在/dev下创建字符设备节点(主设备号90)以及块设备节点(主设备号31), 用户通过访问此设备节点即可访问MTD字符设备或块设备。

mtd_info 结构体描述原始设备层的一个分区的结构, 描述一个设备或一个多分区设备中的一个分区。定义位于:include\linux\mtd\mtd.h

2.协议相关硬件层。nand_flash和nor_flash有不同的协议和硬件细节,这部分知道发什么,如发送什么命令可以识别、读写、擦除等操作,以及硬件该怎么发。

nand_flash有nand的协议,nor_flash有nor_flash的协议。用户得配置好这块,nor_flash使用map_info结构体,nand_flash使用nand_chip结构体,不同协议有不同的函数。通过对应的结构体和函数构造对应的操作环境。

MTD子系统的写法:

用户只需要完成协议相关硬件层的相关结构体的分配设置注册,并和mtd层关联即可

nand_flash:

第一步:分配一个nand_chip结构体

/*
此处定义的是一个nand_chip的结构体指针,仅仅是一个地址,
需要使用kzalloc动态分配内存空间,才可以保存数据
如果不动态分配,则应该struct nand_chip s3c_nand{……};直接分配实体
输入子系统等有专门的分配函数,所以不用kzalloc
*/
static struct nand_chip *s3c_nand;
/* 1. 分配一个nand_chip结构体 */
s3c_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);

第二步:分配获取nand flash 寄存器虚拟地址

/*s3c2440中nand_flash的寄存器地址,使用ioremap(),地址需要4字节连续
    如果寄存器地址有中断,需要动手补齐
*/
struct s3c_nand_regs {
	unsigned long nfconf  ;  //0x4E000000
	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  ;
};
/*把nand的物理地址映射到内核*/
static struct s3c_nand_regs *s3c_nand_regs;
s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c_nand_regs));

第三步:对nand_chip结构体设置,开启nand_flash控制器时钟,设置flash时序参数

/* 2. 设置nand_chip */
	/* 设置nand_chip是给nand_scan函数使用的, 如果不知道怎么设置, 先看nand_scan怎么使用 
	 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态的功能
	 */
	s3c_nand->select_chip = s3c2440_select_chip; //选中,需要用户实现
	s3c_nand->cmd_ctrl    = s3c2440_cmd_ctrl;    设置写command/address,需要用户实现
	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;         //设置软件ECC
	
	/* 3. 硬件相关的设置: 根据NAND FLASH的手册设置时间参数 */
	/* 使能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   0
    /*设置时序*/
	s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);

	/* NFCONT: 
	 * BIT1-设为1, 取消片选 
	 * BIT0-设为1, 使能NAND FLASH控制器
	 */
	s3c_nand_regs->nfcont = (1<<1) | (1<<0);
//需要用户自己实现的函数部分如下:
//1.选中芯片
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);
	}
}
/*2.命令/地址控制函数 */
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;
	}
}
/* 3.nand flash 设备就绪函数(获取RnB引脚状态 */
static int s3c2440_dev_ready(struct mtd_info *mtd)
{
	return (s3c_nand_regs->nfstat & (1<<0)); //获取RnB状态,0:busy  1:ready
}

第四步:分配mtd_info结构体,并与nand_chip关联

static struct mtd_info *s3c_mtd;
/* 分配mtd_info结构体,并把nand_chip结构体赋予mtd_info中的priv参量 */
	s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
	s3c_mtd->owner = THIS_MODULE;
	s3c_mtd->priv  = s3c_nand;

第五步:扫描nand_flash,并创建分区

关键函数是nand_scan()

/*分区的设置*/
static struct mtd_partition mynand_part[] = {
    [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,  //剩余全部空间
    }
};

/*6.扫描NAND*/
    if (nand_scan(my_mtd, 1)) {                   // 1:表示只扫描一个nand flash 设备
        res = -ENXIO;
        goto out;
    }
            
    /*7.添加分区,创建字符/块设备*/
    res = add_mtd_partitions(my_mtd, mynand_part, 4); 
    if(res)
    return 0;

out:
    del_mtd_partitions(my_mtd);                      //卸载分区,卸载字符/块设备
    kfree(my_mtd);                                      //释放mtd
    iounmap(my_regs);                                //释放nand flash寄存器
    kfree(mynand_chip);                             //释放nand_chip

第六步:出口处把所有分配的资源释放

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

nor_flash:

nor_flash的写法跟nand_flash类似,只不过nand使用nand_chip和nand_scan(),nor-flash使用的结构体是map_info,专用函数是do_map_probe()。

第一步:分配map_info结构体

static struct map_info *s3c_nor_map;
/* 1. 分配map_info结构体 */
s3c_nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);

第二步:对map_info结构体进行设置

/* 2. 设置: 物理基地址(phys), 大小(size), 位宽(bankwidth), 虚拟基地址(virt) */
	s3c_nor_map->name = "s3c_nor";
	s3c_nor_map->phys = 0;         //物理地址,s3c2440的nor是从0地址开始
	s3c_nor_map->size = 0x1000000; /* >= NOR的真正大小 */
	s3c_nor_map->bankwidth = 2;    16位宽
	s3c_nor_map->virt = ioremap(s3c_nor_map->phys, s3c_nor_map->size); //虚拟基地址
    simple_map_init(s3c_nor_map);//对结构体进行初始化

第三步:分配设置mtd_info结构体,与map_info联系起来

此处用函数do_map_probe,根据传入的参数匹配一个map_info对象的驱动,比如CFI接口或JEDEC接口的NOR Flash。

	static struct mtd_info *s3c_nor_mtd;
   /* 3. 使用: 调用NOR FLASH协议层提供的函数来识别 */
	printk("use cfi_probe\n");
	s3c_nor_mtd = do_map_probe("cfi_probe", s3c_nor_map); //尝试使用cfi接口模式
	if (!s3c_nor_mtd)
	{
		printk("use jedec_probe\n");
		s3c_nor_mtd = do_map_probe("jedec_probe", s3c_nor_map);  //尝试使用jedec接口模式
	}

	if (!s3c_nor_mtd)
	{		
		iounmap(s3c_nor_map->virt);
		kfree(s3c_nor_map);
		return -EIO;
	}
    s3c_nor_mtd->owner = THIS_MODULE;

此处的mtd_info结构体不用动态分配,因为do_map_probe会返回一个mtd_info结构体指针

第四步:进行分区

//分区设置,用户自己设置
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,
	}
};
/*4. 使用add_mtd_partitions()或者add_mtd_device()来创建MTD字符/块 设备*/
   add_mtd_partitions(s3c_nor_mtd, s3c_nor_parts, 2);

第五步:出口处释放所有资源

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值