龙芯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_SOFTNAND_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_REGNAND_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值即可。

Logo

更多推荐