Chinaunix首页 | 论坛 | 博客
  • 博客访问: 145893
  • 博文数量: 101
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 9
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-17 08:11
文章分类
文章存档

2017年(91)

2016年(10)

我的朋友

分类: LINUX

2017-03-08 14:43:51

原文地址:sep4020的nand驱动开发 作者:myleeming

花了近三个星期,终于和阿虚一起将sep4020上的nand驱动给搞定了。

 

第一部分:MTD

首先是花了好几天来了解nand以及在linuxnand的开发,在linux中使用了一个mtd层来作为具体的硬件设备驱动和上层文件系统的桥梁。.mtd给出了系统中所有mtd设备(nandnordiskonchip)的统一组织方式。

使用mtd层的好处有:

1.我们要做mtd设备的驱动模块,所需要做的事情就是去填满那些公共接口函数的实际内容。有了mtd,在设计驱动模块的时候,不用去理会字符(块)设备驱动设计标准,因为所有这些复杂的与内核的交互接口机制mtd已经替我们做好了,我们只需要实现对物理设备的范围控制。——这是对于写驱动的人来说的方便。

2.上层应用只需要访问mtd抽象层提供的字符设备方式或者块设备方式来访问mtd设备,因此具体驱动对于上层应用来说是具有独立性的,即使底层驱动修改了,上层拥有也不需要改动。并且由于mtd抽象层,上层应用就可以避免直接对具体硬件操作,而是对mtd操作,这样的话这些应用就不是建立在某个具体的设备上,更好的实现了通用性和兼容性——这是对于上层应用来说的方便。

 

关于mtd层:

.mtd抽象层用一个数组struct mtd_info *mtd_table[MAX_MTD_DEVICES]保存系统中所有的设备,mtd设备利用struct mtd_info 这个结构来描述,该结构中描述了存储设备的基本信息和具体操作所需要的内核函数,mtd系统的那个机制主要就是围绕这个结构来实现的。下面简单介绍下这个结构:

struct mtd_info {

u_char type;          //MTD 设备类型

u_int32_t flags;    //MTD设备属性标志

u_int32_t size; // 标示了这个mtd设备的大小

u_int32_t erasesize; // MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小

u_int32_t oobblock;  // oob区在页内的位置,对于512字节一页的nand来说是512

u_int32_t oobsize;   // oob区的大小,对于512字节一页的nand来说是16

u_int32_t ecctype;

u_int32_t eccsize;

 

char *name;             //设备的名字

int index;                 //设备在MTD列表中的位置

 

struct nand_oobinfo oobinfo; //其中是oob区的信息,包括是否使用eccecc的大小

 

//以下是关于mtd的一些读写函数,将在nand_base中的nand_scan中将其重载

int (*erase)

int (*read)

int (*write)

int (*read_ecc)

int (*write_ecc)

int (*read_oob)

int (*read_oob)

void *priv;             //设备私有数据指针,对于nand Flash来说指向,nand芯片的结构

 

 

第二部分:具体的Nand Flash驱动

搞清楚了MTD,内核,nandflash设备驱动的关系后,现在就是如何编写针对我们这款处理器的驱动,首先介绍一下nandflash结构struct nand_chip

. struct nand_chip {

       void  __iomem      *IO_ADDR_R;  //这是nandflash的读写寄存器,对于我们的芯片是

       void  __iomem     *IO_ADDR_W;  //EMI_NAND_DATA

 

//以下都是nandflash的操作函数,这些函数将根据相应的配置进行重载,也是在nand_scan这个函数中,nand_scan这个函数非常重要,在下文讲详细阐述

       u_char    (*read_byte)(struct mtd_info *mtd);

       void        (*write_byte)(struct mtd_info *mtd, u_char byte);

       u16         (*read_word)(struct mtd_info *mtd);

       void        (*write_word)(struct mtd_info *mtd, u16 word);

       void        (*write_buf)(struct mtd_info *mtd, const u_char *buf, int len);

       void        (*read_buf)(struct mtd_info *mtd, u_char *buf, int len);

       int           (*verify_buf)(struct mtd_info *mtd, const u_char *buf, int len);

       void        (*select_chip)(struct mtd_info *mtd, int chip);

       int           (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);

       int           (*block_markbad)(struct mtd_info *mtd, loff_t ofs);

       void              (*hwcontrol)(struct mtd_info *mtd, int cmd);

       int                (*dev_ready)(struct mtd_info *mtd);

       void              (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);

       int          (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this, int state);

       int           (*calculate_ecc)(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code);

       int          (*correct_data)(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc);

       void        (*enable_hwecc)(struct mtd_info *mtd, int mode);

       void        (*erase_cmd)(struct mtd_info *mtd, int page);

       int           (*scan_bbt)(struct mtd_info *mtd);

       int           eccmode;   //ecc是软件校验?硬件校验?无?

       int          chip_delay;  //芯片时序延迟参数

       int          page_shift;  //页偏移,对于512/页的,一般是9

       u_char           *data_buf; //数据缓存区

(其它参数解释详解linux内核源码)

 

●其实我们需要做的事情,就是将上述这些函数,根据我们sep4020的特性,将其重载。

主要重载的函数有:

 

this->hwcontrol = sep4020_hwcontrol;

这是一个硬件操作函数,由于在nandflash中对寄存器的操作都是通过IO_ADDR_RIO_ADDR_W来实现的,而我们需要根据需要在地址寄存器,数据寄存器,命令寄存器,ID寄存器,状态寄存器之间切换,通过这个函数来实现对IO_ADDR_RIO_ADDR_W的变动。

 

this->dev_ready = sep4020_nand_dev_ready;

nandflash是否完成的函数,通过读取EMI_NAND_IDLE寄存器位来判断。

 

this->write_buf      = sep4020_nand_write_buf;

Nandflash的写函数,nandflash的最基本也是最重要的命令之一。

 

this->read_buf  = sep4020_nand_read_buf;

Nanflash的读函数,功能同上。

 

this->write_byte = sep4020_nand_write_byte;

this->read_byte =  sep4020_nand_read_byte;

实际这两个函数实现了读取和写入寄存器一个byte的功能,

 

this->eccmode = NAND_ECC_SOFT;//软件ecc校验

 

this->select_chip = sep4020_nand_select_chip;//无此功能,空留

 

this->cmdfunc = sep4020_nand_command;

linux中对nand的操作都是通过两个函数来实现的,一个是发命令也就是此处的cmdfuc,还有一个就是读写函数,由于sep4020nandflash控制器的特殊,需要将这个函数重载(下文会介绍我们nandflash的特性)

 

this->erase_cmd = sep4020_nand_cmd_erase;

擦除命令,理由同上。

 

关于nand_scan函数

Nand_scan是在初始化nand的时候对nand进行的一步非常好重要的操作,在nand_scan中会对我们所写的关于特定芯片的读写函数重载到nand_chip结构中去,并会将mtd_info结构体中的函数用nand的函数来重载,实现了mtd到底层驱动的联系。

并且在nand_scan函数中会通过读取nand芯片的设备号和厂家号自动在芯片列表中寻找相应的型号和参数,并将其注册进去。

 

第三部分:根据sep4020编写具体nand驱动

sep4020是一款非常好优秀的arm720T芯片,以下介绍一下sep4020nand驱动编写

 

1.       sep4020nand控制器只支持dma搬运,这也是考虑到一般对nand进行读写都是一页一页的,所以利用dma可以有效的提高效率。

DMA也带了一个比较重要的问题,DMA的源和目的需要用实地址,因为DMA不走cpu,在这点上我卡了好久,在linux中久了,什么都习惯性的填写虚地址,结果在测试的时候发现dma怎么都不工作,把所有的东西都看了又看,都没问题,郁闷的不行了,最后在张师兄的提醒下才发现DMA不能给虚地址,恍然大悟啊!

 

. 但是由于直接给DMA又带来了另外一个问题,在操作系统中,所有的地址都是虚拟地址,如何得到实际物理地址?我记得在当初在内核移植的时候写过一个函数:

#define __virt_to_bus(x) __virt_to_phys(x) ,跟进去一看解释是物理地址,虚拟地址的转换,此时我认为操作系统的地址转换就是通过这个来实现的,因此在这里我直接给DMA赋了一个没用到的物理空间0x30002000,根据上式则应该对应的虚拟地址是0xc0002000

我也这么做了,发现可以实现,但是总是会出现一些莫名其妙的错误,我和阿虚都快崩溃了,最后发现其实这种读法是不正确的,我们在进行同一页重复性读取实验中,10次中有一次不正确,排除了所有其他可能的因素,最后又怀疑到操作系统是不是真的这样将虚实地址的?操作系统可操作的地址空间应该有4G,如果都是这样联系虚实地址,那岂不是操作系统只能分配我们的SDRAM8M空间,因此这种想法是错误的。最后我们使用了专门用来分配DMA地址的函数dma_alloc_coherent,实验一切正确,更加验证了我们的想法。

由于第一次在操作系统中使用dma,我们边做边摸索,以后绝对不在dma上再吃这么多跟头了,呵呵。

 

3         sep4020中所有的寄存器都是32位的,这点也是和其他芯片的nand控制所不同的,打个比方:我们需要给nand控制器的地址寄存器一个地址,我们是直接将地址赋给这个32位的寄存器,但是对于其他的芯片(比如2410),它的地址寄存器用8位,需要你串行的将地址依次输入进去,这点是需要我们注意的。

还有就是我们的nand控制器的命令寄存器比较特殊,它有个使能位,芯片手册推荐配好地址寄存器后,再配命令寄存器,同时使能,但由于尽量不变动linux的内核源码,我使用的是先按照mtd设备中通用的做法,发命令,发地址,然后在读写函数的最后一条指令将nand使能。

 

4.       有一点比较重要,sep4020nand控制器只能528字节的写,而在linux中大量采用了先写512再写16,甚至有单独写16的函数,因此这里必须做改动。我们采用了先读后写的方法,比如写16字节(写16肯定是最后的00b区),我们先读出这一页的所有528字节,将前面的512取出再接上我们要写的16字节,这样凑满了528字节后写入。

为了实现在读写的速度更快,我们根据nand的特性进行分析,在写512字节的时候,那一页肯定是空白的,未被使用过的,因此它的oob区也肯定是全ff,因此在写512时我们将后面剩余的16字节自动填充ff,这样就省去了从nand读出的步骤,大大加快了nand写的速度。

 

5.       奇怪的write_byte

在我们调试的时候曾发现过一个问题,给nand发了一条写命令0x80,但是此时还未使能,但通过printk发现此时nand控制器一直处于忙状态,最后通过一步步的锁定,发现在给nand发了0x80给我们的com寄存器,但是此时printk这个寄存器的值竟然是0x80808080,而我们的第一位恰好是使能位,因此此时由于调用系统的write_bytecom寄存器从本应的0x00000080变成了0x80808080,导致了我们nand控制一直是忙,无法进行下面的操作。最后无奈之下重载了write_byteread_byte,问题就解决了,很奇怪的是我也跟进去看过系统的代码,和我的一样啊,怎么到它那儿就这样了呢?现在觉得有可能的是:write_byte的寄存器一般都是8位的,而我们的寄存器是32位的,因此系统连续赋了4次,这仅仅是我的一种猜测,等待以后验证。

 

6.       编写中碰上的一些非常傻的问题:

*unsigned long *EMI_NAND_COM EMI_NAND_COM混淆了,还是指针学的不好。

出现一些很古怪的问题的时候,一定要记得检查你的头文件

切记保证你的make menuconfig中的选项正确

 

7.不能将yaffs 中的Turn off debug chunk erase check选上,不然会出现mtd ecc chunk的问题

阅读(567) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~