[Linux驱动]-----NAND FLASH

一、NAND原理及硬件操作

C: fopen,fread,fwrite
APP: open,read,write “1.txt”
------------------------------------------ 文件读写
文件系统:vfat,ext2,ext3,yaffs (把文件的读写转换成对扇区的读写)
------------------ll_rw_block-------------- 扇区读写
块设备驱动程序
硬件:硬盘、FLASH

1.1NAND FLASH硬件

在这里插入图片描述问1:原理图上 NAND FLASH 和 S3C2440 之间只有数据线(LDATA0~7),没有看到地址引脚,怎么传输地址(如何将地址信号告诉 NAND)?
答1:在 DATA0~DATA7 上既传输数据,又传输地址。用一个信号 ALE 分辨是“地址”还是“信号”,当 ALE 为高电平时传输的是地址。当 ALE 为低电平是传输的是数据
问2:要操作 NAND FLASH 需要先发出命令,只有 8 条DATA0~7 的数据线 怎么传入命令?
答2:在 DATA0~DATA7 上既传输数据,又传输地址,也传输命令。
当 ALE 为高电平时传输的是地址,
当 CLE 为高电平时传输的是命令,
当 ALE 和 CLE 都为低电平时传输的是数据。
问3:数据线既接到 NAND FLASH,也接到 NOR FLASH,还接到 SDRAM、DM9000 等等 那么怎么避免干扰
答3:这些设备,要访问之前必须"选中",没有选中的芯片不会工作,相当于没接一样,要“选中”这就是它们都有“片选” 信号
问4:假设烧写 NAND FLASH,把命令、地址、数据发给它之后 NAND FLASH 肯定不可能瞬间完成烧写的怎么判断烧写完成?
答4:通过状态引脚 RnB 来判断:它为高电平表示就绪,它为低电平表示正忙。
问 5.:怎么操作 NAND FLASH 呢?
答 5.:根据 NAND FLASH 的芯片手册,一般的过程是:
发出命令
发出地址
发出数据 或 读数据

1.2NAND结构

在这里插入图片描述NAND FLASH的结构是一页一页(一个扇区一个扇区)的结构,一页是 2KB,除了这 2KB 外还有 64B 的空间,这个 64B 区叫“OOB”(out of bank 叫作在 BANK 之外的东西)。这个 OOB 区不参与“统一编址”,就是说假若某一页 A 的地址是“2-2047”,那么 2048 这个地址不是在 OOB 区,而是在页 B 上(2048-4095)。
引入 OOB,是因为 NAND 有个缺点:位反转。如读一页数据时,里面很可能有某一位发生了位反转。本来值为 0,读出来为“1”。写的时候也有可能发生“位反转”,这样引入了“ECC”校验
解决位反转:
写时:
1,写一页数据。
2,用这一页数据生成 ECC 码(校验码)。
3,把 ECC 写入 OOB 里。
读时:
1,读整页的数据。
2,读 OOB 里的 ECC 码。3,通过读出来的一页数据算校验码。
4,比较从 OOB 里读出来的 ECC 码和通过读到的一页数据算出来的 ECC 码是否相同不同则是发生了位反转。ECC 码是特定设置的,可以通过它知道是哪一位发生了反转。

ECC 校验码,可以用硬件生成,也可以用软件生成。

二、NAND驱动编写

2.1init函数框架

static int __init s3c_nand_init(void)
{
    
    
    printk("s3c_nand_init NAND Driver, (c) 2020 Simtec Electronics\n");
    /* 1. 分配一个nand_chip结构体 */
    s3c_nand = kzalloc(sizeof(struct nand_chip),GFP_KERNEL);

    /* 2. 设置nand_chip */
    /* 设置nand_chip是给nand_scan函数使用的, 如果不知道怎么设置, 先看nand_scan怎么使用 
     * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态的功能
     */
    s3c_nand->select_chip = s3c2440_select_chip;
    s3c_nand->cmd_ctrl = s3c2440_nand_cmd_ctrl;
    s3c_nand->IO_ADDR_R   = "NFDATA的虚拟地址";
    s3c_nand->IO_ADDR_W   = "NFDATA的虚拟地址";
    s3c_nand->dev_ready = s3c2440_dev_ready;
    /* 3. 硬件相关的设置 */

    /* 4. 使用: nand_scan */
    s3c_mtd = kzalloc(sizeof(struct mtd_info),GFP_KERNEL);
    mtd->priv = nand_chip;
    mtd->owner = THIS_MODULE;

    nand_scan(s3c_mtd,1);   /* 识别NAND FLASH, 构造mtd_info */
    /* 5. add_mtd_partitions */
    return 0;
}

2.1.1设置nand_chip结构

通过分析"nand_scan"中使用 nand_chip的情况分析如何使用他,如下是nand_scan函数:

nand_scan   // drivers/mtd/nand/nand_base.c,构造mtd_info
nand_scan_ident
  busw = chip->options & NAND_BUSWIDTH_16;  //总线宽度即FLASH为8位还是16位
  nand_set_defaults //成员函数为空时默认函数,nandflash协议层
        if (!chip->select_chip) 
        chip->select_chip = nand_select_chip; // 默认值不适用 
        if (chip->cmdfunc == NULL)
        chip->cmdfunc = nand_command; 
        chip->cmd_ctrl(mtd, command, ctrl); 
        if (!chip->read_byte) 
        chip->read_byte = nand_read_byte; 
        readb(chip->IO_ADDR_R); 
        if (chip->waitfunc == NULL) 
        chip->waitfunc = nand_wait; 
        chip->dev_ready
    nand_get_flash_type //硬件相关层
        chip->select_chip(mtd, 0);
        chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
        *maf_id = chip->read_byte(mtd); //读厂家ID 
         dev_id = chip->read_byte(mtd); //读设备ID 
nand_scan_tail
    //ECC
    mtd->erase = nand_erase;
    mtd->read = nand_read;
    mtd->write = nand_write; 

片选芯片
chip->select_chip(mtd, 0)平台默认函数如下,并没有做其他操作,因此需要做一些客制化来片选目前的芯片。

static void nand_select_chip(struct mtd_info *mtd, int chipnr)
{
    
    
    struct nand_chip *chip = mtd->priv;

    switch (chipnr) {
    
    
    case -1:
        chip->cmd_ctrl(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE);
        break;
    case 0: //为0时为第一个芯片,里面什么也没做 
        break;

    default:
        BUG();
    }
}

看2440的NAND控制器可知,片选就是在"NFCONT"寄存器"Reg_nCE"bit1域设置为0,具体如下

static void s3c2440_select_chip(struct mtd_info *mtd, int chipnr)
{
    
    
    if(chipnr == -1)
    {
    
    
        s3c_nand_regs->nfcont |= (1<<1); 
        /* 取消选中: NFCONT[1]设为1 */
    }else{
    
    
        s3c_nand_regs->nfcont &= ~(1<<1);  
        /* 选中: NFCONT[1]设为0 */
    }
}

发命令/地址/数据
首先NAND FLASH取地址时,先看哪一页再看是页内的哪个地址
默认函数:chip->cmdfunc = nand_command;

void nand_command(struct mtd_info *mtd, unsigned int command, int column, int page_addr) 
//“column”:页内地址(页内哪一个地址) 
//“page_addr”:页地址
chip->cmd_ctrl(mtd, readcmd, ctrl);//参 2 为命令值或地址值,参 3 控制了是发命令还是发地址。

查2440 NAND控制器实现2440接口

static void s3c2440_nand_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;
    }
}

读数据:厂家ID、设备ID
默认函数:chip->read_byte = busw ? nand_read_byte16 : nand_read_byte
从原理图看,使用的是8bit的nand flash,故使用下面的函数

static uint8_t nand_read_byte(struct mtd_info *mtd)
{
    
    
    struct nand_chip *chip = mtd->priv;
    return readb(chip->IO_ADDR_R);
}

默认函数中需要提供:nand_chip->IO_ADDR_R
s3c_nand->IO_ADDR_R = &s3c_nand_regs->nfdata; //需要提供NFDATA寄存器的地址读数据
写数据
默认函数:chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;

static void nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
{
    
    
    int i;
    struct nand_chip *chip = mtd->priv;

    for (i = 0; i < len; i++)
        writeb(buf[i], chip->IO_ADDR_W);
}

判断状态
默认函数:chip->waitfunc = nand_wait
使用2440 nand 控制器中的’NFSTAT"寄存器

static int s3c2440_dev_ready(struct mtd_info *mtd) 
{
    
     
return (s3c_nand_regs->nfstat & (1<<0)); 
} 

2.1.2设置硬件状态

在这里插入图片描述从上图2440 nand控制器时序图可知
发出“CLE/ALE”后,要过“TACLS”时间才能开始发一个“nWE”写信号。“nWE”写信号变成高电平之后,还要过“TWRPH1”时间,“CLE/ALE”就变成低电平。
再看 NAND 芯片的时序图:

在这里插入图片描述
对照数据手册,计算出时间差

#define TACLS 0 //因为TACLS=0 
#define TWRPH0 1 //因为TWRPH0 >= 1 
#define TWRPH1 0 //因为TWRPH1 >= 0 
s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4); 

为了省电。内核启动时会将一些用不到的模块都关掉。要使用某模块就得使能CLKCON相关位域,开启CLKCON

clk = clk_get(NULL, "nand"); 
clk_enable(clk); /* CLKCON'bit[4] 设置为1*/ 

添加分区
若只是把 NAND 分成一个分区,则只要用“add_mtd_device(mtd_info 结构)”就可以。若要构造分区,则用“add_mtd_partitions()

/*
参 1,mtd_info 结构体。 
参 2,mtd_partition 结构指针。相当于一个结构数组。最终是数组,组元是结构体。 
参 3,就是参 2 这个 mtd_partition 结构数组的组元有多少项。 
*/
int add_mtd_partitions
(struct mtd_info *master,const struct mtd_partition *parts,int  nbparts)
{
    
    
static struct mtd_partition s3c_nand_parts[] = {
    
    
    [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,
    }
};

2.2exit函数

static void s3c_nand_exit(void)
{
    
    
    del_mtd_partitions(s3c_mtd);//清除分区结构数组
    kfree(s3c_mtd);
    iounmap(s3c_nand_regs);
    kfree(s3c_nand);
}

猜你喜欢

转载自blog.csdn.net/weixin_45281868/article/details/127914687