本文的主要任务是实现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");四、编译测试
对上面这个驱动进行编译,得到模块文件,在内核中加载这个模块:
在加载时会输出一下信息:
在设备目录下产生如下设备节点:

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

943

被折叠的 条评论
为什么被折叠?



