基于JZ2440的NAND FLASH的驱动程序的实现

本文详细介绍了基于JZ2440开发板,针对K9F2G08U0A NAND FLASH的驱动程序实现过程,包括前期准备、NAND FLASH特性、驱动程序的步骤和具体实现。

本文的主要任务是实现JZ2440开发板板载的型号为K9F2G08U0A的容量为256MB、位宽为8位的NAND FLASH驱动程序的开发。


一、前期准备

开发板:JZ2440(ARM9)

内核版本:Linux-3.4.10

  NAND FLASH : K9F2G08U0A( 256MB   8bit   3.3V)

重新配置内核,去掉已有的NAND FLASH的驱动程序。


二、K9F2G08U0A大简单介绍

K9F2G08U0A三星电子设计的NAND FLASH类型的存储半导体,它的大小为256MB,位宽为8bit,支持ECC坏块检测机制。

1、原理图布局和基本引脚定义


各个引脚的具体定义:

        LDATA0~LDATA7:数据、地址、命令传输引脚,具体传输什么类型数据由ALE、CLE决定

        RnB :状态判断引脚,用它来判断flash是处于忙碌还是就绪状态

        CLE :命令锁存信号,为高电平时数据线上发送命令

        nFCE:片选信号,为低电平表示选中

        ALE:地址锁存引脚,为高电平时数据线上发送地址

        nFWE:写信号,为低电平时表示在数据线上写入

        nFRE:读信号,为低电平时表示从数据线上读取数据

2、存储单元基本布局


从上图可以看出,该NAND flash是按页来存储数据的,每一页的大小是2KB + 64Bytes,其中前面2KB是用来存放真正的数据的,后面的64Bytes数据作为ECC用来检测该页数据是否有效;每一个块包括64页数据。


三、驱动程序的实现

1、编写一个nand flash的驱动程序大致有如下步骤:

a、分配一个nand_chip的结构体变量

b、设置这个结构体的变量

c、执行硬件相关的操作

d、使用这个结构体变量,并添加设备分区

2、具体实现

2.1 基本工作,实现如下:

/* 定义一个结构体来描述s3c2440 nand 控制器的寄存器 */
struct yl_s3c_nand_regs {
	unsigned long nfconf  ;
	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驱动的操作 */
struct yl_nand_mtd{
	struct nand_chip *nand_chip;				// 定义一个nand_chip结构体的指针变量
	struct mtd_info *mtd_info;					// 定义一个mtd_info结构体的指针变量
	struct yl_s3c_nand_regs *s3c_nand_regs;		// 定义一个用来操作寄存器的结构体指针变量
	struct clk *clk;							// 定义一个操作时钟的结构体指针变量
};

/* 定义一个 yl_nand_mtd 结构体的全局变量 */
static struct yl_nand_mtd nand_mtd;
这部分的主要工作是为了方便整个驱动程序的开发,实现了硬件操作相关的前期准备工作,也定义了一个全局的结构体变量,方便驱动程序的编写。

2.2、硬件相关的操作

/* 0、硬件相关的操作 */
	/* 0.1 映射寄存器的地址 */
	nand_mtd.s3c_nand_regs = ioremap(0x4E000000, sizeof(struct yl_s3c_nand_regs));
	if (!nand_mtd.s3c_nand_regs) {
		printk("ioremap error!\n");
		ret = -ENOMEM;
		goto out1;
	}

	/* 0.2 打开nand控制器的时钟 */
	nand_mtd.clk = clk_get(NULL, "nand");
	if (IS_ERR(nand_mtd.clk)) {
		printk("clk_get error!\n");
		ret = -ENODEV;
		goto out2;
	}
	clk_enable(nand_mtd.clk);

	/* 0.3 设置nand控制器和nand芯片之间通信的基本时序要求 */
	/* 设置时序要求 */
#define tacls    0
#define twrph0   1
#define twrph1   0
	nand_mtd.s3c_nand_regs->nfconf = (tacls<<12) | (twrph0<<8) | (twrph1<<4);

	/* 设置nand控制器的状态: 取消片选,使能nand控制器 */
	nand_mtd.s3c_nand_regs->nfcont = (1<<1) | (1<<0);
这部分主要是映射寄存器的地址、打开nand控制器的时钟,设置nand控制器的操作时序。

2.3、分配一个nand_chip的结构体指针变量

/* 1、分配一个nand_chip结构体变量 */
	nand_mtd.nand_chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);
	if (!nand_mtd.nand_chip) {
		printk("kzalloc for nand_chip error!\n");
		ret = -ENOMEM;
		goto out3;
	}
2.4 设置上面分配的nand_chip结构体指针变量的成员
/* 2、设置这个结构体变量 */
	nand_mtd.nand_chip->select_chip = yl_s3c_select_chip;		// 设置片选信号的函数
	nand_mtd.nand_chip->cmd_ctrl    = yl_s3c_cmd_ctrl;			// 控制函数,来确定发送的数据是命令还是地址
	nand_mtd.nand_chip->dev_ready   = yl_s3c_dev_ready;			// 判断芯片是否准备好
	nand_mtd.nand_chip->IO_ADDR_R   = &nand_mtd.s3c_nand_regs->nfdata;	// 设置读数据的寄存器的地址
	nand_mtd.nand_chip->IO_ADDR_W   = &nand_mtd.s3c_nand_regs->nfdata;	// 设置写数据的寄存器的地址
	nand_mtd.nand_chip->ecc.mode	= NAND_ECC_SOFT;			// 设置ecc为软件校验模式
这部分主要实现了三个函数:yl_s3c_select_chip()设置片选,yl_s3c_cmd_ctrl()设置发送命令还是地址、yl_s3c_dev_ready()判断nand flash是否处于就绪状态,它们的具体实现如下:

设置片选:

/* 设置nand的片选函数 */
static void yl_s3c_select_chip(struct mtd_info *mtd, int chip)
{
	if (-1 == chip)	// 取消片选, nfcont寄存器bit[1] = 1
	{
		nand_mtd.s3c_nand_regs->nfcont |= (1<<1);		
	}
	else	// 选中,nfcont寄存器bit[1] = 0
	{
		nand_mtd.s3c_nand_regs->nfcont &= ~(1<<1);
	}
}
设置发送命令还是地址:

/* 命令设置函数,确定发送的数据是命令还是地址 */
static void yl_s3c_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
	if (ctrl & NAND_CLE)	// 发命令
	{
		nand_mtd.s3c_nand_regs->nfcmd = dat;
	}
	else	// 发地址
	{
		nand_mtd.s3c_nand_regs->nfaddr = dat;
	}
}
判断nand flash是否处于就绪状态:
/* 判断nand flash所处的状态 */
static int yl_s3c_dev_ready(struct mtd_info *mtd)
{
	// nfstat寄存器bit[0] = 0 忙碌,bit[0] = 1 就绪
	return (nand_mtd.s3c_nand_regs->nfstat & (1<<0));
}

2.5 使用上面实现的nand_chip结构体变量

/* 3、使用这个结构体变量 */
	/* 分配mtd_info,并把mtd_info与nand_chip挂钩起来 */
	nand_mtd.mtd_info = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
	if (!nand_mtd.mtd_info) {
		printk("kzalloc for mtd_info error!\n");
		ret = -ENOMEM;
		goto out4;
	}
	nand_mtd.mtd_info->owner = THIS_MODULE;
	nand_mtd.mtd_info->priv  = nand_mtd.nand_chip;

	/* 识别nand flash,构造mtd_info */
	nand_scan(nand_mtd.mtd_info, 1);
2.6 添加分区

/* 4、添加分区 */
	ret = mtd_device_parse_register(nand_mtd.mtd_info, NULL, NULL,
		yl_s3c_nand_partitions, ARRAY_SIZE(yl_s3c_nand_partitions));

为这块nand flash添加分区,这需要实现一个分区表yl_s3c_nand_partitions,它的具体实现如下:
/* 定义nand flash的分区表 */
static struct mtd_partition yl_s3c_nand_partitions[] = 
{
	[0] = {
		.name	= "bootloader",
		.size	= SZ_256K,
		.offset	= 0,
	},
	[1] = {
		.name	= "params",
		.offset = MTDPART_OFS_APPEND,
		.size	= SZ_128K,
	},
	[2] = {
		.name	= "kernel",
		.offset = MTDPART_OFS_APPEND,
		.size	= SZ_4M,
	},
	[3] = {
		.name	= "rootfs",
		.offset	= MTDPART_OFS_APPEND,
		.size	= MTDPART_SIZ_FULL,
	}
};
至此,整个驱动的大致实现过程就说完了。

整个驱动的完整实现如下所示:

#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/cpufreq.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 <plat/regs-nand.h>
#include <plat/nand.h>

/* 定义一个结构体来描述s3c2440 nand 控制器的寄存器 */
struct yl_s3c_nand_regs {
	unsigned long nfconf  ;
	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驱动的操作 */
struct yl_nand_mtd{
	struct nand_chip *nand_chip;				// 定义一个nand_chip结构体的指针变量
	struct mtd_info *mtd_info;					// 定义一个mtd_info结构体的指针变量
	struct yl_s3c_nand_regs *s3c_nand_regs;		// 定义一个用来操作寄存器的结构体指针变量
	struct clk *clk;							// 定义一个操作时钟的结构体指针变量
};

/* 定义一个 yl_nand_mtd 结构体的全局变量 */
static struct yl_nand_mtd nand_mtd;

/* 定义nand flash的分区表 */
static struct mtd_partition yl_s3c_nand_partitions[] = 
{
	[0] = {
		.name	= "bootloader",
		.size	= SZ_256K,
		.offset	= 0,
	},
	[1] = {
		.name	= "params",
		.offset = MTDPART_OFS_APPEND,
		.size	= SZ_128K,
	},
	[2] = {
		.name	= "kernel",
		.offset = MTDPART_OFS_APPEND,
		.size	= SZ_4M,
	},
	[3] = {
		.name	= "rootfs",
		.offset	= MTDPART_OFS_APPEND,
		.size	= MTDPART_SIZ_FULL,
	}
};

/* 设置nand的片选函数 */
static void yl_s3c_select_chip(struct mtd_info *mtd, int chip)
{
	if (-1 == chip)	// 取消片选, nfcont寄存器bit[1] = 1
	{
		nand_mtd.s3c_nand_regs->nfcont |= (1<<1);		
	}
	else	// 选中,nfcont寄存器bit[1] = 0
	{
		nand_mtd.s3c_nand_regs->nfcont &= ~(1<<1);
	}
}

/* 命令设置函数,确定发送的数据是命令还是地址 */
static void yl_s3c_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
	if (ctrl & NAND_CLE)	// 发命令
	{
		nand_mtd.s3c_nand_regs->nfcmd = dat;
	}
	else	// 发地址
	{
		nand_mtd.s3c_nand_regs->nfaddr = dat;
	}
}

/* 判断nand flash所处的状态 */
static int yl_s3c_dev_ready(struct mtd_info *mtd)
{
	// nfstat寄存器bit[0] = 0 忙碌,bit[0] = 1 就绪
	return (nand_mtd.s3c_nand_regs->nfstat & (1<<0));
}

/* 定义入口函数 */
static int __init yl_s3c_nand_init(void)
{
	int ret = 0;
	
	/* 0、硬件相关的操作 */
	/* 0.1 映射寄存器的地址 */
	nand_mtd.s3c_nand_regs = ioremap(0x4E000000, sizeof(struct yl_s3c_nand_regs));
	if (!nand_mtd.s3c_nand_regs) {
		printk("ioremap error!\n");
		ret = -ENOMEM;
		goto out1;
	}

	/* 0.2 打开nand控制器的时钟 */
	nand_mtd.clk = clk_get(NULL, "nand");
	if (IS_ERR(nand_mtd.clk)) {
		printk("clk_get error!\n");
		ret = -ENODEV;
		goto out2;
	}
	clk_enable(nand_mtd.clk);

	/* 0.3 设置nand控制器和nand芯片之间通信的基本时序要求 */
	/* 设置时序要求 */
#define tacls    0
#define twrph0   1
#define twrph1   0
	nand_mtd.s3c_nand_regs->nfconf = (tacls<<12) | (twrph0<<8) | (twrph1<<4);

	/* 设置nand控制器的状态: 取消片选,使能nand控制器 */
	nand_mtd.s3c_nand_regs->nfcont = (1<<1) | (1<<0);
	
	/* 1、分配一个nand_chip结构体变量 */
	nand_mtd.nand_chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);
	if (!nand_mtd.nand_chip) {
		printk("kzalloc for nand_chip error!\n");
		ret = -ENOMEM;
		goto out3;
	}

	/* 2、设置这个结构体变量 */
	nand_mtd.nand_chip->select_chip = yl_s3c_select_chip;		// 设置片选信号的函数
	nand_mtd.nand_chip->cmd_ctrl    = yl_s3c_cmd_ctrl;			// 控制函数,来确定发送的数据是命令还是地址
	nand_mtd.nand_chip->dev_ready   = yl_s3c_dev_ready;			// 判断芯片是否准备好
	nand_mtd.nand_chip->IO_ADDR_R   = &nand_mtd.s3c_nand_regs->nfdata;	// 设置读数据的寄存器的地址
	nand_mtd.nand_chip->IO_ADDR_W   = &nand_mtd.s3c_nand_regs->nfdata;	// 设置写数据的寄存器的地址
	nand_mtd.nand_chip->ecc.mode	= NAND_ECC_SOFT;			// 设置ecc为软件校验模式

	/* 3、使用这个结构体变量 */
	/* 分配mtd_info,并把mtd_info与nand_chip挂钩起来 */
	nand_mtd.mtd_info = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
	if (!nand_mtd.mtd_info) {
		printk("kzalloc for mtd_info error!\n");
		ret = -ENOMEM;
		goto out4;
	}
	nand_mtd.mtd_info->owner = THIS_MODULE;
	nand_mtd.mtd_info->priv  = nand_mtd.nand_chip;

	/* 识别nand flash,构造mtd_info */
	nand_scan(nand_mtd.mtd_info, 1);

	/* 4、添加分区 */
	ret = mtd_device_parse_register(nand_mtd.mtd_info, NULL, NULL,
		yl_s3c_nand_partitions, ARRAY_SIZE(yl_s3c_nand_partitions));	
	if(ret)	// 失败
	{
		printk("mtd_device_parse_register error!\n");
		goto out5;
	}
	else	// 成功
	{
		return 0;	
	}


out5:
	kfree(nand_mtd.mtd_info);			// 释放mtd_info分配的资源
out4:
	kfree(nand_mtd.nand_chip);			// 释放nand_chip分配的资源
out3:
	clk_disable(nand_mtd.clk);			// 失能时钟
	clk_put(nand_mtd.clk);				// 将时钟从内核时钟链表中移除
out2:
	iounmap(nand_mtd.s3c_nand_regs);	// 释放io映射的资源
out1:
	return ret;
}

/* 定义出口函数 */
static void __exit yl_s3c_nand_exit(void)
{
	/* 进行出口的一些操作,释放资源,解除绑定,取消注册 */
	mtd_device_unregister(nand_mtd.mtd_info);
	kfree(nand_mtd.mtd_info);
	kfree(nand_mtd.nand_chip);
	clk_disable(nand_mtd.clk);
	clk_put(nand_mtd.clk);
	iounmap(nand_mtd.s3c_nand_regs);
}

module_init(yl_s3c_nand_init);
module_exit(yl_s3c_nand_exit);

MODULE_LICENSE("GPL");

四、编译测试

对上面这个驱动进行编译,得到模块文件,在内核中加载这个模块:

在加载时会输出一下信息:



在设备目录下产生如下设备节点:



• 电源电压 - 1.65V ~ 1.95V - 2.70V ~ 3.60V • 组织结构 - 存储单元阵列: (256M + 8M) x 8bit - 数据寄存器: (2K + 64) x 8bit • 自动的编程(写入)和擦除 - 页编程: (2K + 64)Byte - 块擦除: (128K + 4K)Byte • 页读取操作 - 页面大小 : (2K + 64)Byte - 随机读取 : 25µs(最大.) - 串行访问 : 25ns(最小.) (*K9F2G08R0A: tRC = 42ns(最小)) • 快速编程周期时间 - 页编程时间: 200µs(典型值) - 块擦除时间: 1.5ms(典型值) • 命令/地址/数据复用I/O端口 • 硬件数据保护 • - 编程/擦除在电源转换分离 可靠的CMOS浮栅技术 -耐力: 100K编程/擦除周期(有1bit/512Byte ECC) 数据保存时间: 10 年 • 命令式操作 • 带有1bit/528Byte EDC的智能Copy-Back编程 • 唯一的ID版权保护 • 封装 - K9F2G08R0A-JCB0/JIB0 : 无铅封装 63 - Ball FBGA I (10 x 13 / 0.8 mm 间距) - K9F2G08U0A-PCB0/PIB0 : 无铅封装 48 - Pin TSOP I (12 x 20 / 0.5 mm 间距) - K9F2G08U0A-ICB0/IIB0 52 - Pin ULGA (12 x 17 / 1.00 mm 间距) 本文档提供的为256Mx8bit的版本,K9F2G08X0A是2G-bit大小的NAND Flash存储器,带有64Mbit额外数据区(OOB区、冗余区)。此 NAND存储颗粒为固态存储市场应用提供了最具成本效益的解决方案。编程(写入)操作可以在200µs(典型值)对大小为(2K+64)Byte的页 进行写入,擦除操作可以在1.5ms(典型值)擦除大小为(128K+4K)的块。读取数据寄存器的数据周期时间为25ns(1.8v设备为42ns) 每字 节。I/O端口可以作为地址和数据输入/输出,也可以作为命令输入。芯片上的写控制器自动完成所有的编程和擦除功能包括脉冲重复、 并内部核查和数据余量(如有需要)。即使是写入操作频繁的系统,也可以通过K9F2G08X0A采用实时映射算法的ECC(错误纠正码)来加 强多达的100K编程/擦除周期的可靠性,K9F2G08X0A是一个用于大型非易失性存储应用的最佳解决方案,例如固态文件存储和其他用 于非易失性要求的便携式存储应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值