WDS2期第19课 3 NAND驱动程序编写 cpu与nand的引脚电平时序对应,add_mtd_partitions,nfs启动-加载nand驱动-格式化nand-挂接

本文详细介绍了NAND闪存驱动的初始化过程,包括设置电平维持时间、实现片选、命令控制、数据读取及状态判断等功能。通过代码示例展示了如何使用nand_scan函数识别NAND芯片,构造mtd_info结构体,并使用add_mtd_partitions进行分区。此外,还讲解了NAND位反转问题及ECC校验的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


参考:
drivers\mtd\nand\s3c2410.c
drivers\mtd\nand\at91_nand.c

设置电平维持时间的寄存器,需要将cpu和nand对应看

2440手册 ,P225,
在这里插入图片描述
2440手册nand P217,
在这里插入图片描述
NAND的手册K9F2G08U0C(NAND FLASH).pdf上 P19
在这里插入图片描述
NAND的手册K9F2G08U0C(NAND FLASH).pdf上, P12,
在这里插入图片描述
在这里插入图片描述

代码2 代码1是need_sth这里没有

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>

#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>

#include <asm/io.h>

#include <asm/arch/regs-nand.h>
#include <asm/arch/nand.h>

struct s3c2440_nand_regs {
    unsigned long nfconf  ; // 0x4E000000
    unsigned long nfcont  ; // 0x4E000004
    unsigned long nfcmd   ; // 0x4E000008
    unsigned long nfaddr  ; // 0x4E00000C
    unsigned long nfdata  ; // 0x4E000010
    unsigned long nfmecc0 ; // 0x4E000014
    unsigned long nfmecc1 ; // 0x4E000018
    unsigned long nfsecc  ; // 0x4E00001C
    unsigned long nfstat  ; // 0x4E000020
    unsigned long nfestat0; // 0x4E000024
    unsigned long nfestat1; // 0x4E000028
    unsigned long nfmecc0 ; // 0x4E00002C
    unsigned long nfmecc1 ; // 0x4E000030
    unsigned long nfsecc  ; // 0x4E000034
    unsigned long nfsblk  ; // 0x4E000038
    unsigned long nfeblk  ; // 0x4E00003C
};
static volatile struct s3c2440_nand_regs *s3c_nand_regs;
static struct nand_chip *s3c_nand;
static struct mtd_info *s3c_mtd;

// nand片选
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);
    }
}
// 发命令或者地址
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->nfcmd  = dat;
    }
}
// 判断状态
static int s3c2440_dev_ready(struct mtd_info *mtd)
{
    // 返回NFSTAT的bit0
    return (s3c_nand_regs->nfstat & (1<<0));
}

static int s3c_nand_init(void)
{
    struct clk *nand_clk;
    // 1.分配一个nand_chip结构体
	s3c_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);
    // 3.硬件相关操作 nand寄存器地址映射
    s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c2440_nand_regs));    
    // 2.设置nand_chip, nand_chip是给nand_scan函数用,
        // 可以先看nand_scan怎么用nand_chip的,再去设置
        // nand_chip应该提供 选中、发命令、地址、数据,读数据、判断状态 功能
    s3c_nand->select_chip = s3c2440_select_chip;// 默认的选择函数不能用需要自己写
    s3c_nand->cmdfunc   = s3c2440_cmd_ctrl;     // 默认的会调用nand_chip->cmdfunc,这里也需要构造
    s3c_nand->IO_ADDR_R = s3c_nand_regs->nfdata;// 读取数据 NFDATA的虚拟地址 
    s3c_nand->IO_ADDR_W = s3c_nand_regs->nfdata;// 写入数据
    s3c_nand->dev_ready = s3c2440_dev_ready;    // 判断状态
    // 3.硬件相关操作 电平维持时间
        // 使能nand控制器的时钟,后面才能读写寄存器
	nand_clk = clk_get(NULL, "nand");
	clk_enable(nand_clk); // CLKCON的bit4
        // HCLK=100MHz, 10ns
        // 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);
        // NFCONF 取消片选,使能nand控制器 
        //  bit1为1 取消片选
        //  bit0为1 使能nand控制器
    s3c_nand_regs->nfcont = (1<<1) | (1<<0);
    // 4.使用 nand_scan,nand_scan识别nand,构造mtd_info(包含读写擦除等方法)
    s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
    s3c_mtd->priv   = s3c_nand; // mtd与nand_chip联系
    s3c_mtd->owner  = THIS_MODULE; 
    nand_scan(s3c_mtd, 1); // 1个芯片

    // 5.add_mtd_partitions

    return 0;
}
static void s3c_nand_exit(void)
{
    kfree(s3c_mtd);
    iounmap(s3c_nand_regs);    
    kfree(s3c_nand);
}
module_init(s3c_nand_init);
module_exit(s3c_nand_exit);
MODULE_LICENSE("GPL");

实验2 nand识别 没有add_mtd_partitions函数

在这里插入图片描述
有个NAND_ECC_NONE警告。因为没有使用ECC校验。

nand位反转 解决办法ECC码

在这里插入图片描述

nand每页(2K)之外还有不参与编址的OOB区(64字节,out of bank),
在读每页数据时可能某位发生反转,需要用ECC校验。ECC不仅可以知道是否数据有误还能计算出具体的bit反转

解决反转

在写入时:

  1. 按page写入
  2. 生成ECC码
  3. 保存ECC码到OOB区

在读出数据时:

  1. 按page读取
  2. 读取OOB的ECC
  3. 计算page的ECC码
  4. 比较两个ECC
  5. 校验

解决位反转有硬件和软件的方法,这里用软件的方法,在程序2上添加一行代码行,s3c_nand->ecc.mode = NAND_ECC_SOFT;

    s3c_nand->select_chip = s3c2440_select_chip;// 默认的选择函数不能用需要自己写
    s3c_nand->cmdfunc   = s3c2440_cmd_ctrl;     // 默认的会调用nand_chip->cmdfunc,这里也需要构造
    s3c_nand->IO_ADDR_R = s3c_nand_regs->nfdata;// 读取数据 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 完善add_mtd_partitions函数

在这里插入图片描述

arch\arm\plat-s3c24xx\common-smdk.c分区名称操作
static struct mtd_partition smdk_default_nand_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,
	}
};

代码3,

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>

#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>

#include <asm/io.h>

#include <asm/arch/regs-nand.h>
#include <asm/arch/nand.h>

struct s3c2440_nand_regs {
    unsigned long nfconf  ; // 0x4E000000
    unsigned long nfcont  ; // 0x4E000004
    unsigned long nfcmd   ; // 0x4E000008
    unsigned long nfaddr  ; // 0x4E00000C
    unsigned long nfdata  ; // 0x4E000010
    unsigned long nfmecc0 ; // 0x4E000014
    unsigned long nfmecc1 ; // 0x4E000018
    unsigned long nfsecc  ; // 0x4E00001C
    unsigned long nfstat  ; // 0x4E000020
    unsigned long nfestat0; // 0x4E000024
    unsigned long nfestat1; // 0x4E000028
    unsigned long nfmecc0 ; // 0x4E00002C
    unsigned long nfmecc1 ; // 0x4E000030
    unsigned long nfsecc  ; // 0x4E000034
    unsigned long nfsblk  ; // 0x4E000038
    unsigned long nfeblk  ; // 0x4E00003C
};
static volatile struct s3c2440_nand_regs *s3c_nand_regs;
static struct nand_chip *s3c_nand;
static struct mtd_info *s3c_mtd; // mtd_info包含了nand的通用读取写入的方法
static struct mtd_partition s3c_nand_part[] = { // 用于nand分区的数组
	[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, // 剩下的所有都是该分区
	}
};

// nand片选
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);
    }
}
// 发命令或者地址
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->nfcmd  = dat;
    }
}
// 判断状态
static int s3c2440_dev_ready(struct mtd_info *mtd)
{
    // 返回NFSTAT的bit0
    return (s3c_nand_regs->nfstat & (1<<0));
}

static int s3c_nand_init(void)
{
    struct clk *nand_clk;
    // 1.分配一个nand_chip结构体
	s3c_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);
    // 3.硬件相关操作 nand寄存器地址映射
    s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c2440_nand_regs));    
    // 2.设置nand_chip, nand_chip是给nand_scan函数用,
        // 可以先看nand_scan怎么用nand_chip的,再去设置
        // nand_chip应该提供 选中、发命令、地址、数据,读数据、判断状态 功能
    s3c_nand->select_chip = s3c2440_select_chip;// 默认的选择函数不能用需要自己写
    s3c_nand->cmdfunc   = s3c2440_cmd_ctrl;     // 默认的会调用nand_chip->cmdfunc,这里也需要构造
    s3c_nand->IO_ADDR_R = s3c_nand_regs->nfdata;// 读取数据 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控制器的时钟,后面才能读写寄存器
	nand_clk = clk_get(NULL, "nand");
	clk_enable(nand_clk); // CLKCON的bit4
        // HCLK=100MHz, 10ns
        // 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);
        // NFCONF 取消片选,使能nand控制器 
        //  bit1为1 取消片选
        //  bit0为1 使能nand控制器
    s3c_nand_regs->nfcont = (1<<1) | (1<<0);
    // 4.使用 nand_scan,nand_scan识别nand,构造mtd_info(包含读写擦除等方法)
    s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
    s3c_mtd->priv   = s3c_nand; // mtd与nand_chip联系
    s3c_mtd->owner  = THIS_MODULE; 
    nand_scan(s3c_mtd, 1); // 1个芯片
    // 5.add_mtd_partitions
        // 1struct mtd_info 
        // 2struct mtd_partition *parts 分区结构体数据
        // 3 int nbparts 该结构体数组的项数
    add_mtd_partitions(s3c_mtd, s3c_nand_part, 4); // 需要对nand分区才用
    // add_mtd_device(); // 不对nand分区用add_mtd_device就行
    return 0;
}
static void s3c_nand_exit(void)
{
    del_mtd_partitions(s3c_mtd);
    kfree(s3c_mtd);
    iounmap(s3c_nand_regs);    
    kfree(s3c_nand);
}
module_init(s3c_nand_init);
module_exit(s3c_nand_exit);
MODULE_LICENSE("GPL");

测试代码3

  1. make menuconfig去掉内核原来的nand驱动程序,输入N不选,
    在这里插入图片描述
  2. make uImage 使用新的内核启动,并且用NFS启动,因为nand驱动没有了
    先进入uboot,
    然后设置参数,保存save,
    从网络文件系统下载uImages 无nand的,
    启动kernel,在启动信息中没有nand的信息了,
    在这里插入图片描述
  3. 插入nand驱动模块
    查看mtd设备,开始没有,加载nand驱动,在查看就有了mtd设备了,分区了,
    在这里插入图片描述
    查看新的分区能否挂接,能挂街上是因为之前的nand烧好了系统,而实际可能nand是空的,需要格式化,
    在这里插入图片描述
  4. 格式化nand
    需要专门的工具,mtd-utils-05.07.23.tar.bz2.
    在这里插入图片描述
    其中绿色文件就是mtd-utils工具生成的工具,
    在这里插入图片描述
    umount /mnt再擦除。用工具格式化nandroot分区,erase是擦除扇区,erase_all是擦除整个分区,擦除之和就是yaffs文件系统,
    在这里插入图片描述
  5. 挂接
    在这里插入图片描述
    然后就可以在/mnt创建文件了。创建之后重启试试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值