龙芯pmon 中Nand配置说明
龙芯pmon 中Nand配置说明以龙芯ls2k1000为例进行讲解ls2k1000 pmon源码:http://ftp.loongnix.org/embedd/ls2k/pmon-loongson3.tar.gzls2k1000 内核源码:http://ftp.loongnix.org/embedd/ls2k/linux-3.10.tar.gz一、代码介绍pmon支持两种接口类型的nand fla
龙芯pmon 中Nand配置说明
以龙芯ls2k1000为例进行讲解
ls2k1000 pmon源码:http://ftp.loongnix.org/embedd/ls2k/pmon-loongson3.tar.gz
ls2k1000 内核源码:http://ftp.loongnix.org/embedd/ls2k/linux-3.10.tar.gz
一、代码介绍
pmon支持两种接口类型的nand flash,一个是Nand控制器接口,一个是spi接口,两种接口的Nand Flash在pmon下框架结构是一样,这里只重点介绍Nand控制器接口的驱动程序(spi接口自行查看代码sys/dev/nand/spinand_lld.c,找到相关配置)。
pmon启动流程Targets/LS2K/ls2k/tgt_machdep.c里调用ls2k_nand_init(),该函数是NAND的初始化函数,位于sys/dev/nand/ls2k-nand.c,要使能该函数的调用,需要在文件Targets/LS2K/conf/ls2k里添加支持,如下所示:
select nand
option CONFIG_LS2K_NAND
以上配置添加后,Nand Flash的驱动程序会在pmon启动过程中进行调用,Nand Flash的驱动初始化主要完成以下几个部分:
1)、mtd结构的初始化
2)、Nand控制器的初始化
3)、Nand Flash 的识别
4)、建立分区
LS2K Nand控制器 的驱动位于sys/dev/nand/ls2k-nand.c,初始化接口为ls2k_nand_init,下面对该函数进行分析。
1、mtd结构的初始化
mtd结构初始化驱动初始化调用 ls2k_nand_init_mtd(),该函数名称虽然是init mtd,但主要是对mtd->priv进行初始化,该成员承载mtd层和Nand控制器的联系,使mtd层可以访问并使用Nand 控制器。代码如下所示:
static void ls2k_nand_init_mtd(struct mtd_info *mtd,
struct ls2k_nand_info *info)
{
struct nand_chip *this = &info->nand_chip;
this->options = 8;
this->waitfunc = ls2k_nand_waitfunc;
this->select_chip = ls2k_nand_select_chip;
this->dev_ready = ls2k_nand_dev_ready;
this->cmdfunc = ls2k_nand_cmdfunc;
this->read_word = ls2k_nand_read_word;
this->read_byte = ls2k_nand_read_byte;
this->read_buf = ls2k_nand_read_buf;
this->write_buf = ls2k_nand_write_buf;
this->verify_buf = ls2k_nand_verify_buf;
#if NNAND_BCH
#define BCH_BUG(a...) printf(a);while(1);
{
int bch = 4;
int writesize = 2048;
int oobsize = 64;
unsigned int eccsteps, eccbytes;
if (!mtd_nand_has_bch()) {
BCH_BUG("BCH ECC support is disabled\n");
}
/* use 512-byte ecc blocks */
eccsteps = writesize/512;
eccbytes = (bch*13+7)/8;
/* do not bother supporting small page devices */
if ((oobsize < 64) || !eccsteps) {
BCH_BUG("bch not available on small page devices\n");
}
if ((eccbytes*eccsteps+2) > oobsize) {
BCH_BUG("invalid bch value \n", bch);
}
this->ecc.mode = NAND_ECC_SOFT_BCH;
this->ecc.size = 512;
this->ecc.strength = bch;
this->ecc.bytes = eccbytes;
printk("using %u-bit/%u bytes BCH ECC\n", bch, this->ecc.size);
}
#else
this->ecc.mode = NAND_ECC_SOFT;
this->ecc.size = 256;
this->ecc.bytes = 3;
this->ecc.hwctl = ls2k_nand_ecc_hwctl;
this->ecc.calculate = ls2k_nand_ecc_calculate;
this->ecc.correct = ls2k_nand_ecc_correct;
#endif
}
Info->nand_chip对应的就是mtd->priv,在该函数里除了对需要的接口进行初始化外,最关键的是对ECC的代码选择,2K的Nand ECC代码有两种:NAND_ECC_SOFT 和NAND_ECC_SOFT_BCH两种,BCH校验相比普通ECC校验有更强的纠错能力,默认bch=4,表示每512字节,生成7 字节校验码,可以最多纠错4bit 错误,而普通的ECC ,每256字节,需要生成3字节的校验码,只能纠错1bit 错误。相对应的,高校验能力需要更多的校验码以使出错数据可以被检测到,这就需要更多代码复杂度和计算,需要的耗时相对普通ECC来说,时间更久。
如果要选择BCH校验,需要在文件Targets/LS2K/conf/ls2k里添加支持如下图所示,如果不用BCH算法,需要将该选项注释掉,注释掉后,代码使用普通ECC校验,同样的,如果不想使用ECC校验,需要将代码中的NAND_ECC_SOFT改为NAND_ECC_NONE。
select nand
option CONFIG_LS2K_NAND
select nand_bch #support bch ecc
2、Nand控制器的初始化
初始化完mtd结构体之后,驱动初始化调用ls2k_nand_init_info(),该函数主要是对Nand控制器进行初始化,代码如下所示:
static void ls2k_nand_init_info(struct ls2k_nand_info *info)
{
info->buf_start = 0;
info->buf_count = 0;
info->seqin_column = 0;
info->seqin_page_addr = 0;
spin_lock_init(&info->nand_lock);
writel(0x412, REG(NAND_TIM_REG));
writel(0x00440000, REG(NAND_CS_RDY_REG));
}
其中info结构体成员代表了数据读写时的起始位置,所有起始位置都从0开始,该函数另外一个关键地方,就是对Nand控制器的寄存器NAND_TIM_REG和NAND_CS_RDY_REG寄存器的初始化。
NAND_TIM_REG寄存器可以控制数据传输的和命令有效的时钟周期,这在一定程度上代表了数据传输的速度,但基于硬件环境和稳定性的考虑,不建议修改该寄存器的值,造成数据传输的不稳定。
NAND_CS_RDY_REG寄存器表示Nand Flash 片选信号的选择,一般情况下根据硬件设计规范,Flash的片选会接到cs0上,因此该寄存器也没必要修改,除非特殊设计cs接到其他引脚。
3、Nand Flash 的识别
当mtd的数据结构和Nand控制器初始化完成之后,驱动就可以对硬件上的Flash进行访问,但在进行实际数据的读写之前,驱动还要对Flash做进一步的识别,以了解其容量、页大小、块大小和OOB区大小等信息。
nand_scan() 完成对Flash的识别,程序调用关系如下:
nand_scan
|-- nand_scan_ident;
|-- nand_get_flash_type
nand_scan() 执行过程中会调用nand_get_flash_type(),该函数会读取Flash芯片的ID号,并于nand_flas数组项做匹配,匹配成功即可获取Flash信息。
nand_flash_ids位于sys/dev/nand/nand_ids.c,部分内容如下:
... ... ...
/* 8 Gigabit */
{"NAND 1GiB 1,8V 8-bit", 0xA3, 0, 1024, 0, LP_OPTIONS},
{"NAND 1GiB 3,3V 8-bit", 0xD3, 0, 1024, 0, LP_OPTIONS},
{"NAND 1GiB 1,8V 16-bit", 0xB3, 0, 1024, 0, LP_OPTIONS16},
{"NAND 1GiB 3,3V 16-bit", 0xC3, 0, 1024, 0, LP_OPTIONS16},
/* 16 Gigabit */
{"NAND 2GiB 1,8V 8-bit", 0xA5, 0, 2048, 0, LP_OPTIONS},
{"NAND 2GiB 3,3V 8-bit", 0xD5, 0, 2048, 0, LP_OPTIONS},
{"NAND 2GiB 1,8V 16-bit", 0xB5, 0, 2048, 0, LP_OPTIONS16},
{"NAND 2GiB 3,3V 16-bit", 0xC5, 0, 2048, 0, LP_OPTIONS16},
{"NAND 2GiB 3,3V 16-bit", 0x48, 4096, 2048, 128*4096, LP_OPTIONS},
... ... ...
该数组结构定义为:
struct nand_flash_dev {
char *name;
int id;
unsigned long pagesize;
unsigned long chipsize;
unsigned long erasesize;
unsigned long options;
};
当有新的Flash需要适配时,要根据设备手册,在该数组里添加ID信息和容量相关信息。
4、建立分区
Nand 初始化的最后会建立分区信息,如下所示:
mtd->name="nand-flash";
if(!nand_flash_add_parts(mtd,0)){
add_mtd_device(mtd,0,0,"total");
add_mtd_device(mtd,0,0x01400000,"kernel");
add_mtd_device(mtd,0x01400000,0x0,"os");
}
建立分区的函数是add_mtd_device(),但是驱动初始化函数会先调用nand_flash_add_parts()函数对环境变量mtdparts 进行判断,若设置了该环境变量则根据该变量设置分区,否则调用add_mtd_device()来指定分区。
环境变量mtdparts的赋值:
a、代码里
Targets/LS2K/include/pmon_target.h
#if NNAND
#define TGT_DEFENV {"mtdparts","nand-flash:30M@0(kernel),-(rootfs);spinand_flash:30M@0(kernel),-(rootfs)",0,&mtd_rescan}, \
{"bootdelay","3",0,0}
#else
#define TGT_DEFENV {"bootdelay","3",0,0}
#endif
b、pmon命令行下
请在PMON常用命令里查看,mtdparts的传参格式为:
mtdparts //查看分区
set mtdparts "mtdname:offset@size(partname)[option]" //修改分区
其中mtdname表示mtd设备名称,offset表示分区的起始地址,size表示分区大小,partname表示分区名称,option是可选项,表示分区的读写权限,可以为rw,ro。
二、Flash的速度测试
对Flash的读写速度测试,需要在内核下进行,因为页缓存和文件系统的影响,测试 是直接对mtdpart设备进行操作。
1、读速度测试
dd if=/dev/mtdpart0 of=/dev/null bs=2k count=8000
2、写速度测试
dd if=/dev/zero of=/dev/mtdpart0 bs=2k count=8000
三、Nand Ecc 测试说明
内核和Pmon没有单独的接口,可以用来验证Ecc,正常情况下Nand Flash也不会在人为让其存储数据发生位反转,因此正常手段达不到测试ECC的目的。
如果要测试ECC,需要在内核下进行,修改驱动程序,强制写错误数据,但不改变ECC编码,以此来达到模拟Nand Flash位反转的目的,代码位于内核源码 drivers/mtd/nand/ls-nand.c中,数据传输的接口函数ls_nand_cmdfunc(), 部分源码如下:
case NAND_CMD_SEQIN:
info->buf_count = oobsize + pagesize - column;
info->buf_start = 0;
info->seqin_column = column;
info->seqin_page_addr = page_addr;
break;
case NAND_CMD_PAGEPROG:
addrc = info->seqin_column;
addrr = info->seqin_page_addr;
op_num = info->buf_start;
param = ((pagesize + oobsize) << OP_SCOPE_SHIFT)
| (chip_cap << CHIP_CAP_SHIFT);
cmd = CMD_VALID | CMD_SPARE | CMD_WR_OP;
if (addrc < pagesize)
cmd |= CMD_MAIN;
nand_setup(info, cmd, addrc, addrr, param, op_num);
dma_cmd = DMA_INT_MASK | DMA_RD_WR;
dma_cnt = ALIGN_DMA(op_num);
dma_setup(info, dma_cmd, dma_cnt);
break;
case NAND_CMD_RESET:
nand_setup(info, (CMD_RESET | CMD_VALID), 0, 0, 0, 0);
wait_nand_done(info, STATUS_TIME_LOOP_R);
break;
修改如下:
case NAND_CMD_SEQIN:
info->buf_count = oobsize + pagesize - column;
info->buf_start = 0;
info->seqin_column = column;
info->seqin_page_addr = page_addr;
break;
case NAND_CMD_PAGEPROG:
addrc = info->seqin_column;
addrr = info->seqin_page_addr;
op_num = info->buf_start;
if(info->data_buff[0]&1)
info->data_buff[0] &=0xfe;
else
info->data_buff[0] |=0x1;
param = ((pagesize + oobsize) << OP_SCOPE_SHIFT)
| (chip_cap << CHIP_CAP_SHIFT);
cmd = CMD_VALID | CMD_SPARE | CMD_WR_OP;
if (addrc < pagesize)
cmd |= CMD_MAIN;
nand_setup(info, cmd, addrc, addrr, param, op_num);
dma_cmd = DMA_INT_MASK | DMA_RD_WR;
dma_cnt = ALIGN_DMA(op_num);
dma_setup(info, dma_cmd, dma_cnt);
break;
case NAND_CMD_RESET:
nand_setup(info, (CMD_RESET | CMD_VALID), 0, 0, 0, 0);
wait_nand_done(info, STATUS_TIME_LOOP_R);
break;
该修改只针对1bit,若要测试多位反转,可自行添加代码。当修改完成后,要验证代码的正确性,可以在内核下通过文件系统对任一mtd分区写入文件,再比较文件的md5值即可。
更多推荐
所有评论(0)