コード例 #1
0
ファイル: m1_nand.c プロジェクト: xbai043/zt280-kernel
static int m1_nand_boot_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int page)
{
	struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
	uint8_t *oob_buf = chip->oob_poi;
	unsigned nand_page_size = M1_BOOT_WRITE_SIZE;
	unsigned pages_per_blk_shift = (chip->phys_erase_shift - chip->page_shift);
	int user_byte_num = (((nand_page_size + chip->ecc.size - 1) / chip->ecc.size) * aml_chip->user_byte_mode);
	int error = 0, i = 0, stat = 0;

	memset(buf, 0xff, (1 << chip->page_shift));
	WARN_ON(!aml_chip->valid_chip[0]);
	if (aml_chip->valid_chip[i]) {

		if (!aml_chip->aml_nand_wait_devready(aml_chip, i)) {
			printk ("read couldn`t found selected chip: %d ready\n", i);
			error = -EBUSY;
			goto exit;
		}

		error = aml_chip->aml_nand_dma_read(aml_chip, buf, nand_page_size, aml_chip->bch_mode);
		if (error)
			goto exit;

		aml_chip->aml_nand_get_user_byte(aml_chip, oob_buf, user_byte_num);
		stat = aml_chip->aml_nand_hwecc_correct(aml_chip, buf, nand_page_size, oob_buf);
		if (stat < 0) {
			mtd->ecc_stats.failed++;
			printk("aml nand read data ecc failed at blk %d chip %d\n", (page >> pages_per_blk_shift), i);
		}
		else
コード例 #2
0
/*
 * The legacy NAND code saved the environment in the first NAND device i.e.,
 * nand_dev_desc + 0. This is also the behaviour using the new NAND code.
 */
int writeenv(size_t offset, u_char *buf)
{
	struct mtd_info *mtd;
	struct env_oobinfo_t *env_oobinfo;
	int error = 0;
	size_t addr = 0;
	size_t amount_saved = 0;
	size_t len;
	struct mtd_oob_ops aml_oob_ops;
	unsigned char *data_buf;
	unsigned char env_oob_buf[sizeof(struct env_oobinfo_t)];

	mtd = nand_info[nand_curr_device];
	if (mtd == NULL)
		return 1;

	struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
	data_buf = kzalloc(mtd->writesize, GFP_KERNEL);
	if (data_buf == NULL)
		return -ENOMEM;

	addr = offset;
	env_oobinfo = (struct env_oobinfo_t *)env_oob_buf;
	memcpy(env_oobinfo->name, ENV_NAND_MAGIC, 4);
	env_oobinfo->ec = aml_chip->aml_nandenv_info->env_valid_node->ec;
	env_oobinfo->timestamp = aml_chip->aml_nandenv_info->env_valid_node->timestamp;
	env_oobinfo->status_page = 1;

	while (amount_saved < CONFIG_ENV_SIZE ) {

		aml_oob_ops.mode = MTD_OOB_AUTO;
		aml_oob_ops.len = mtd->writesize;
		aml_oob_ops.ooblen = sizeof(struct env_oobinfo_t);
		aml_oob_ops.ooboffs = mtd->ecclayout->oobfree[0].offset;
		aml_oob_ops.datbuf = data_buf;
		aml_oob_ops.oobbuf = env_oob_buf;

		memset((unsigned char *)aml_oob_ops.datbuf, 0x0, mtd->writesize);
		len = min(mtd->writesize, CONFIG_ENV_SIZE - amount_saved);
		memcpy((unsigned char *)aml_oob_ops.datbuf, buf + amount_saved, len);

		error = mtd->write_oob(mtd, addr, &aml_oob_ops);
		if (error) {
			printf("blk check good but write failed: %llx, %d\n", offset, error);
			return 1;
		}

		addr += mtd->writesize;;
		amount_saved += mtd->writesize;
	}
	if (amount_saved < CONFIG_ENV_SIZE)
		return 1;

	kfree(data_buf);
	return 0;
}
コード例 #3
0
int saveenv(void)
{
	struct mtd_info * mtd=get_mtd_device_nm(NAND_NORMAL_NAME);
	if (IS_ERR(mtd))
		return 1;
	struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
	size_t total;
	size_t offset = CONFIG_ENV_OFFSET;
	int ret = 0;
	nand_erase_options_t nand_erase_options;

	offset = (1024 * aml_chip->page_size * (mtd->writesize / (aml_chip->plane_num * aml_chip->page_size)));
	if (CONFIG_ENV_OFFSET < offset)
		_debug ("env define offset must larger than 1024 page size: %d \n", mtd->writesize);
	else
		offset = CONFIG_ENV_OFFSET;

	env_ptr->flags++;
	total = CONFIG_ENV_SIZE;

	nand_erase_options.length = CONFIG_ENV_RANGE;
	nand_erase_options.quiet = 0;
	nand_erase_options.jffs2 = 0;
	nand_erase_options.scrub = 0;

	if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)
		return 1;
	if(gd->env_valid == 1) {
		puts ("Erasing redundant Nand...\n");
		nand_erase_options.offset = CONFIG_ENV_OFFSET_REDUND;
		if (nand_erase_opts(mtd, &nand_erase_options))
			return 1;

		puts ("Writing to redundant Nand... ");
		ret = writeenv(CONFIG_ENV_OFFSET_REDUND, (u_char *) env_ptr);
	} else {
		puts ("Erasing Nand...\n");
		nand_erase_options.offset = CONFIG_ENV_OFFSET;
		if (nand_erase_opts(mtd, &nand_erase_options))
			return 1;

		puts ("Writing to Nand... ");
		ret = writeenv(CONFIG_ENV_OFFSET, (u_char *) env_ptr);
	}
	if (ret) {
		puts("FAILED!\n");
		return 1;
	}

	puts ("done\n");
	gd->env_valid = (gd->env_valid == 2 ? 1 : 2);
	return ret;
}
コード例 #4
0
ファイル: cmd_nand.c プロジェクト: matt0526/matt_uboot
int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
{
	int i, dev, ret = 0;
	ulong addr;
	loff_t off, size;
	char *cmd, *s;
	nand_info_t *nand;
#ifdef CONFIG_SYS_NAND_QUIET
	int quiet = CONFIG_SYS_NAND_QUIET;
#else
	int quiet = 0;
#endif
	const char *quiet_str = getenv("quiet");

#if ((defined CONFIG_AML_NAND_KEY) || (defined MX_REVD) || (defined CONFIG_SECURE_NAND))
	int chip_num , tmp_chip_num, error;
	nand = nand_info[nand_curr_device];
	struct mtd_info *mtd =nand;
	struct aml_nand_chip *aml_chip = mtd_to_nand_chip(nand);
#endif

	/* at least two arguments please */
	if (argc < 2)
		goto usage;

	if (quiet_str)
		quiet = simple_strtoul(quiet_str, NULL, 0) != 0;

	cmd = argv[1];
#ifdef CONFIG_AML_NAND_KEY
	if (strcmp(cmd, "key") == 0){
		aml_chip->key_protect = 1;		//force nand key can be erased 
		return 0;
	}
#endif
#ifdef CONFIG_SECURE_NAND
	if (strcmp(cmd, "secure") == 0){
		aml_chip->secure_protect = 1;		//force nand key can be erased
		return 0;
	}
#endif

#ifdef CONFIG_SECURE_NAND
		if (strcmp(cmd, "secure") == 0){
			aml_chip->secure_protect = 1;		//force nand key can be erased 
			return 0;
		}
#endif

	if(strcmp(cmd, "exist") == 0){
		if(nand_info[1]){
			printf("nand exist return 0\n");
			return 0;
		}
		else{
			printf("nand exist return 1\n");
			return 1;
		}
	}
#ifdef MX_REVD
	if (strcmp(cmd, "errstat") == 0){
	    printk("checking chiprev here\n");
        if(aml_chip->err_sts == NAND_CHIP_REVB_HY_ERR){
            printk("Must use RevD chip for Hynix 26nm/20nm nand boot without SPI!!!\n");
            return NAND_CHIP_REVB_HY_ERR;
        }
		return 0;
	}
#endif
#ifdef CONFIG_SECURE_NAND
		if (strcmp(cmd, "secure") == 0){
			aml_chip->secure_protect = 1;		//force nand key can be erased 
			return 0;
		}
#endif

	if (strcmp(cmd, "info") == 0) {
	#ifdef CONFIG_AML_NAND_KEY
		aml_chip->key_protect = 0;		//protect nand key can not be erased
	#endif
	#ifdef CONFIG_SECURE_NAND
		aml_chip->secure_protect = 0;		//protect nand secure can not be erased
	#endif

		putc('\n');
		for (i = 0; i < CONFIG_SYS_MAX_NAND_DEVICE; i++) {
			nand = nand_info[i];
			if (!nand) {
				nand_init();
				if (!nand)
					return -1;
			}
			if (nand->name)
				nand_print_info(i);
		}
		return 0;
	}
	if (strcmp(cmd, "init") == 0) {
		nand_init();
		return 0;
	}	
//cmd for nand test , if nand is ok , then trigger power off
	if (strcmp(cmd, "test") == 0) {
		int ret=-1;
		puts("\ntest the nand flash ***\n");
		for (i = 0; i < CONFIG_SYS_MAX_NAND_DEVICE; i++) {
			nand = nand_info[i];
			if (!nand) {
				ret=nand_test_init();
				printf("\n***nand_test_init()in NAND DEVICE %d returned:%d***\n ", i,ret);
				if (ret)
					return -1;	
			}
		}		
		return 0;
	}

	if (strcmp(cmd, "scrub_detect") == 0) {

		if (nand_curr_device < 0 || nand_curr_device >= (CONFIG_SYS_MAX_NAND_DEVICE+2)) {
			puts("\nno devices available\n");
			return 1;
		}
		nand = nand_info[nand_curr_device];

		aml_nand_stupid_dectect_badblock(nand);
		return 0;

	}

	if (strcmp(cmd, "device") == 0) {

		if (argc < 3) {
			putc('\n');
			if ((nand_curr_device < 0) ||
			    (nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE))
				puts("no devices available\n");
			else
				nand_print_info(nand_curr_device);
			return 0;
		}
		dev = (int)simple_strtoul(argv[2], NULL, 10);
		if (dev < 0 || dev >= (CONFIG_SYS_MAX_NAND_DEVICE+1) || !nand_info[dev]->name) {
			puts("No such device\n");
			return 1;
		}
		printf("Device %d: %s", dev, nand_info[dev]->name);
		puts("... is now current device\n");
		nand_curr_device = dev;

#ifdef CONFIG_SYS_NAND_SELECT_DEVICE
		/*
		 * Select the chip in the board/cpu specific driver
		 */
		board_nand_select_device(nand_info[dev]->priv, dev);
#endif

		return 0;
	}

	if (strcmp(cmd, "bad") != 0 && strcmp(cmd, "erase") != 0 &&
	    strncmp(cmd, "dump", 4) != 0 &&
	    strncmp(cmd, "read", 4) != 0 && strncmp(cmd, "write", 5) != 0 &&
	    strcmp(cmd, "scrub") != 0 && strcmp(cmd, "markbad") != 0 &&
	    strcmp(cmd, "biterr") != 0 && strncmp(cmd, "rom_protect", 11) != 0 &&
	    strncmp(cmd, "wr_rd_cmp", 9) != 0 && strncmp(cmd, "rom_write", 9) != 0 && (strncmp(cmd, "rom_read", 8) != 0) &&
	    strcmp(cmd, "lock") != 0 && strcmp(cmd, "unlock") != 0 &&
	    strcmp(cmd, "factory_info") != 0 && strcmp(cmd, "show_para_page")&& strncmp(cmd, "scrub_safe", 10) != 0) //my_
	
			goto usage;

	/* the following commands operate on the current device */
	if (nand_curr_device < 0 || nand_curr_device >= (CONFIG_SYS_MAX_NAND_DEVICE+2)) {
		puts("\nno devices available\n");
		return 1;
	}

	nand = nand_info[nand_curr_device];
	if (!nand)
		return -1;

	if (strcmp(cmd, "bad") == 0) {
		printf("\nDevice %d bad blocks:\n", nand_curr_device);
		for (off = 0; off < nand->size; off += nand->erasesize)
			if (nand_block_isbad(nand, off))
				printf("  %09llx\n", off);
		return 0;
	}

	/*
	 * Syntax is:
	 *   0    1     2       3    4
	 *   nand erase [clean] [off size]
	 */
	if (strcmp(cmd, "erase") == 0 || strcmp(cmd, "scrub") == 0 || strcmp(cmd, "scrub_safe") == 0) { 
		nand_erase_options_t opts;
		int argc_cnt = 2;
        //printk("%s\n", argv[2]);
        /*
		if (isstring(argv[2])) {
			nand = get_mtd_device_nm(argv[2]);
			if (IS_ERR(nand)){
                printf("get nand device err\n");
				return 1;
			}
			argc_cnt++;
		}
		*/
		/* "clean" at index 2 means request to write cleanmarker */
		int clean = argc > argc_cnt && !strcmp("clean", argv[argc_cnt]);
		if (clean) 
			argc_cnt++;
		int o = argc_cnt;

		int scrub = !strncmp(cmd, "scrub",10);
		int scrub_safe =  !strncmp(cmd, "scrub_safe",10);
		
		if(scrub_safe)			
			printf("\nNAND %s: ", scrub_safe ? "scrub_safe" : "erase"); 
		else
			printf("\nNAND %s: ", scrub ? "scrub" : "erase");
		
		if (argv[argc_cnt])
		{
			if(!strcmp(argv[argc_cnt], "whole"))
			{
				off = 0;
				size = nand->size;
				printf("whole chip.\n");
			}
		}
		else
		{
			/* skip first two or three arguments, look for offset and size */
			if ((strcmp(cmd, "erase") == 0) && (argc < 3))
			{
				goto usage;
			}
                    if ((arg_off_size(argc - o, argv + o, nand, &off, &size) != 0))
                    {
                        return  1;
                    }
		}

		memset(&opts, 0, sizeof(opts));
		opts.offset = off;
		opts.length = size;
		opts.jffs2  = clean;
		opts.quiet  = quiet;

		if (scrub) {
			puts("Warning: "
			     "scrub option will erase all factory set "
			     "bad blocks!\n"
			     "         "
			     "There is no reliable way to recover them.\n"
			     "         "
			     "Use this command only for testing purposes "
			     "if you\n"
			     "         "
			     "are sure of what you are doing!\n"
			     "\nReally scrub this NAND flash? <y/N>\n");
			if(nand_protect)
			{
				if (getc() == 'y') {
					puts("y");
					if (getc() == '\r')
						opts.scrub = 1;
					else {
						puts("scrub aborted\n");
						return -1;
					}
				} else {
					puts("scrub aborted\n");
					return -1;
				}
			}
			else
			{
				opts.scrub = 1;
			}
		}
		else if(scrub_safe){
			puts("Warning: "
			     "scrub_safe option will erase all "
			     "bad blocks except factory bad blocks!\n");
			opts.scrub = 2; 	// indicate scrub_safe
		}
		ret = nand_erase_opts(nand, &opts);
		printf("%s\n", ret ? "ERROR" : "OK");
	#ifdef CONFIG_AML_NAND_KEY
		aml_chip->key_protect = 0;		//protect nand key can not be erased 
	#endif
	#ifdef CONFIG_SECURE_NAND
		aml_chip->secure_protect = 0;		//protect nand secure can not be erased 
	#endif

		return ret == 0 ? 0 : 1;
	}

	if (strncmp(cmd, "dump", 4) == 0) {
		if (argc < 3)
			goto usage;

		s = strchr(cmd, '.');
		//off = (loff_t)simple_strtoul(argv[2], NULL, 16);
		if (!(str2longlong(argv[2], (unsigned long long*)(&off))))
			return -1;

		if (s != NULL && strcmp(s, ".oob") == 0)
			ret = nand_dump(nand, off, 1);
		else
			ret = nand_dump(nand, off, 0);

		return ret == 0 ? 1 : 0;

	}

	if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {
		int read;

		if (argc < 4)
			goto usage;

		if (isstring(argv[2])) {
			nand = get_mtd_device_nm(argv[2]);
			if (IS_ERR(nand))
				goto usage;
			addr = (ulong)simple_strtoul(argv[3], NULL, 16);
			read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
			printf("\nNAND %s: %s ", read ? "read" : "write", argv[2]);
			if (argc == 4) {
				extern unsigned int get_mtd_size(char *name);
				off = 0;
				size = get_mtd_size(argv[2]);
			} else {
				if (arg_off_size(argc - 4, argv + 4, nand, &off, &size) != 0)
					return 1;
			}
		}
		else {
			addr = (ulong)simple_strtoul(argv[2], NULL, 16);
			read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
			printf("\nNAND %s: ", read ? "read" : "write");
			if (arg_off_size(argc - 3, argv + 3, nand, &off, &size) != 0)
				return 1;
		}

#ifdef CONFIG_AMLROM_NANDBOOT
	if((read==0) && ((off)<(1024* nand->writesize)) && (nand_curr_device == 0)){
		printf("offset 0x%llx in aml-boot area ,abort\n", off);
		return -1;
	}	
#endif


		s = strchr(cmd, '.');
		if (!s || !strcmp(s, ".jffs2") ||
		    !strcmp(s, ".e") || !strcmp(s, ".i")) {
			if (read)
				ret = nand_read_skip_bad(nand, off, &size,
							 (u_char *)addr, 0);
			else
				ret = nand_write_skip_bad(nand, off, &size,
							  (u_char *)addr, 0);
		} else if (!strcmp(s, ".oob")) {
			/* out-of-band data */
			mtd_oob_ops_t ops = {
				.oobbuf = (u8 *)addr,
				.ooblen = size,
				.mode = MTD_OOB_RAW
			};

			if (read)
				ret = nand->read_oob(nand, off, &ops);
			else
				ret = nand->write_oob(nand, off, &ops);
		} else if (!strcmp(s, ".raw")) {
コード例 #5
0
int readenv (size_t offset, u_char * buf)
{
	struct mtd_info *mtd;
	struct env_oobinfo_t *env_oobinfo;
	struct aml_nand_bbt_info *nand_bbt_info;
	int error = 0, start_blk, total_blk, i, j;
	size_t addr = 0;
	size_t amount_loaded = 0;
	size_t len;
	struct mtd_oob_ops aml_oob_ops;
	unsigned char *data_buf;
	unsigned char env_oob_buf[sizeof(struct env_oobinfo_t)];

	mtd = nand_info[nand_curr_device];
	if (mtd == NULL)
		return 1;

	struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
	if (!aml_chip->aml_nandenv_info->env_valid)
		return 2;

	addr = (1024 * mtd->writesize / aml_chip->plane_num);
	start_blk = addr / mtd->erasesize;
	total_blk = mtd->size / mtd->erasesize;
	addr = aml_chip->aml_nandenv_info->env_valid_node->phy_blk_addr;
	addr *= mtd->erasesize;
	addr += aml_chip->aml_nandenv_info->env_valid_node->phy_page_addr * mtd->writesize;

	data_buf = kzalloc(mtd->writesize, GFP_KERNEL);
	if (data_buf == NULL)
		return -ENOMEM;

	env_oobinfo = (struct env_oobinfo_t *)env_oob_buf;
	while (amount_loaded < CONFIG_ENV_SIZE ) {

		aml_oob_ops.mode = MTD_OOB_AUTO;
		aml_oob_ops.len = mtd->writesize;
		aml_oob_ops.ooblen = sizeof(struct env_oobinfo_t);
		aml_oob_ops.ooboffs = mtd->ecclayout->oobfree[0].offset;
		aml_oob_ops.datbuf = data_buf;
		aml_oob_ops.oobbuf = env_oob_buf;

		memset((unsigned char *)aml_oob_ops.datbuf, 0x0, mtd->writesize);
		memset((unsigned char *)aml_oob_ops.oobbuf, 0x0, aml_oob_ops.ooblen);

		error = mtd->read_oob(mtd, addr, &aml_oob_ops);
		if ((error != 0) && (error != -EUCLEAN)) {
			printf("blk check good but read failed: %llx, %d\n", (uint64_t)addr, error);
			return 1;
		}

		if (memcmp(env_oobinfo->name, ENV_NAND_MAGIC, 4)) 
			printf("invalid nand env magic: %llx\n", (uint64_t)offset);

		addr += mtd->writesize;
		len = min(mtd->writesize, CONFIG_ENV_SIZE - amount_loaded);
		memcpy(buf + amount_loaded, data_buf, len);
		amount_loaded += mtd->writesize;
	}
	if (amount_loaded < CONFIG_ENV_SIZE)
		return 1;

	nand_bbt_info = (struct aml_nand_bbt_info *)(env_ptr->data + default_environment_size);
	if ((!memcmp(nand_bbt_info->bbt_head_magic, BBT_HEAD_MAGIC, 4)) && (!memcmp(nand_bbt_info->bbt_tail_magic, BBT_TAIL_MAGIC, 4))) {
		for (i=start_blk; i<total_blk; i++) {
			aml_chip->block_status[i] = NAND_BLOCK_GOOD;
			for (j=0; j<MAX_BAD_BLK_NUM; j++) {
				if (nand_bbt_info->nand_bbt[j] == i) {
					aml_chip->block_status[i] = NAND_BLOCK_BAD;
					break;
				}
			}
		}
		memcpy((unsigned char *)aml_chip->aml_nandenv_info->nand_bbt_info.bbt_head_magic, (unsigned char *)nand_bbt_info, sizeof(struct aml_nand_bbt_info));
	}

	kfree(data_buf);
	return 0;
}
コード例 #6
0
/*
 * The legacy NAND code saved the environment in the first NAND device i.e.,
 * nand_dev_desc + 0. This is also the behaviour using the new NAND code.
 */
int writeenv(size_t offset, u_char *buf)
{
	struct mtd_info *mtd;
	struct env_oobinfo_t *env_oobinfo;
	int error = 0;
	size_t addr = 0;
	size_t amount_saved = 0;
	size_t len;
	struct mtd_oob_ops aml_oob_ops;
	unsigned char *data_buf;
	unsigned char env_oob_buf[sizeof(struct env_oobinfo_t)];

	mtd = nand_info[nand_curr_device];
	if (mtd == NULL)
		return 1;

	struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
	data_buf = kzalloc(mtd->writesize, GFP_KERNEL);
	if (data_buf == NULL)
		return -ENOMEM;

	addr = offset;
	env_oobinfo = (struct env_oobinfo_t *)env_oob_buf;
	memcpy(env_oobinfo->name, ENV_NAND_MAGIC, 4);
	env_oobinfo->ec = aml_chip->aml_nandenv_info->env_valid_node->ec;
	env_oobinfo->timestamp = aml_chip->aml_nandenv_info->env_valid_node->timestamp;
	env_oobinfo->status_page = 1;

	while (amount_saved < CONFIG_ENV_SIZE ) {

		aml_oob_ops.mode = MTD_OOB_AUTO;
		aml_oob_ops.len = mtd->writesize;
		aml_oob_ops.ooblen = sizeof(struct env_oobinfo_t);
		aml_oob_ops.ooboffs = mtd->ecclayout->oobfree[0].offset;
		aml_oob_ops.datbuf = data_buf;
		aml_oob_ops.oobbuf = env_oob_buf;

		memset((unsigned char *)aml_oob_ops.datbuf, 0x0, mtd->writesize);
		len = min(mtd->writesize, CONFIG_ENV_SIZE - amount_saved);
		memcpy((unsigned char *)aml_oob_ops.datbuf, buf + amount_saved, len);

		error = mtd->write_oob(mtd, addr, &aml_oob_ops);
		if (error) {
			printf("blk check good but write failed: %llx, %d\n", offset, error);
			return 1;
		}

		addr += mtd->writesize;;
		amount_saved += mtd->writesize;
	}
	if (amount_saved < CONFIG_ENV_SIZE)
		return 1;

	kfree(data_buf);
	return 0;
}
#ifdef CONFIG_ENV_OFFSET_REDUND
int saveenv(void)
{
	struct mtd_info * mtd=get_mtd_device_nm(NAND_NORMAL_NAME);
	if (IS_ERR(mtd))
		return 1;
	struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
	size_t total;
	size_t offset = CONFIG_ENV_OFFSET;
	int ret = 0;
	nand_erase_options_t nand_erase_options;

	offset = (1024 * aml_chip->page_size * (mtd->writesize / (aml_chip->plane_num * aml_chip->page_size)));
	if (CONFIG_ENV_OFFSET < offset)
		_debug ("env define offset must larger than 1024 page size: %d \n", mtd->writesize);
	else
		offset = CONFIG_ENV_OFFSET;

	env_ptr->flags++;
	total = CONFIG_ENV_SIZE;

	nand_erase_options.length = CONFIG_ENV_RANGE;
	nand_erase_options.quiet = 0;
	nand_erase_options.jffs2 = 0;
	nand_erase_options.scrub = 0;

	if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)
		return 1;
	if(gd->env_valid == 1) {
		puts ("Erasing redundant Nand...\n");
		nand_erase_options.offset = CONFIG_ENV_OFFSET_REDUND;
		if (nand_erase_opts(mtd, &nand_erase_options))
			return 1;

		puts ("Writing to redundant Nand... ");
		ret = writeenv(CONFIG_ENV_OFFSET_REDUND, (u_char *) env_ptr);
	} else {
		puts ("Erasing Nand...\n");
		nand_erase_options.offset = CONFIG_ENV_OFFSET;
		if (nand_erase_opts(mtd, &nand_erase_options))
			return 1;

		puts ("Writing to Nand... ");
		ret = writeenv(CONFIG_ENV_OFFSET, (u_char *) env_ptr);
	}
	if (ret) {
		puts("FAILED!\n");
		return 1;
	}

	puts ("done\n");
	gd->env_valid = (gd->env_valid == 2 ? 1 : 2);
	return ret;
}
#else /* ! CONFIG_ENV_OFFSET_REDUND */
int saveenv(void)
{
	struct mtd_info *mtd;
	struct aml_nand_bbt_info *nand_bbt_info;
	struct env_free_node_t *env_free_node, *env_tmp_node;
	int error = 0, pages_per_blk, i = 1;
	size_t addr = 0;
	struct erase_info aml_env_erase_info;

	mtd = nand_info[nand_curr_device];
	if (mtd == NULL)
		return 1;

	struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
	if (!aml_chip->aml_nandenv_info->env_init) 
		return 1;

	pages_per_blk = mtd->erasesize / mtd->writesize;
	if ((mtd->writesize < CONFIG_ENV_SIZE) && (aml_chip->aml_nandenv_info->env_valid == 1))
		i = (CONFIG_ENV_SIZE + mtd->writesize - 1) / mtd->writesize;
	
	if (aml_chip->aml_nandenv_info->env_valid) {
		aml_chip->aml_nandenv_info->env_valid_node->phy_page_addr += i;
		if ((aml_chip->aml_nandenv_info->env_valid_node->phy_page_addr + i) > pages_per_blk) {

			env_free_node = kzalloc(sizeof(struct env_free_node_t), GFP_KERNEL);
			if (env_free_node == NULL)
				return -ENOMEM;

			env_free_node->phy_blk_addr = aml_chip->aml_nandenv_info->env_valid_node->phy_blk_addr;
			env_free_node->ec = aml_chip->aml_nandenv_info->env_valid_node->ec;
			env_tmp_node = aml_chip->aml_nandenv_info->env_free_node;
			while (env_tmp_node->next != NULL) {
				env_tmp_node = env_tmp_node->next;
			}
			env_tmp_node->next = env_free_node;

			env_tmp_node = aml_chip->aml_nandenv_info->env_free_node;
			aml_chip->aml_nandenv_info->env_valid_node->phy_blk_addr = env_tmp_node->phy_blk_addr;
			aml_chip->aml_nandenv_info->env_valid_node->phy_page_addr = 0;
			aml_chip->aml_nandenv_info->env_valid_node->ec = env_tmp_node->ec;
			aml_chip->aml_nandenv_info->env_valid_node->timestamp += 1;
			aml_chip->aml_nandenv_info->env_free_node = env_tmp_node->next;
			kfree(env_tmp_node);
		}
	}
	else {

		env_tmp_node = aml_chip->aml_nandenv_info->env_free_node;
		aml_chip->aml_nandenv_info->env_valid_node->phy_blk_addr = env_tmp_node->phy_blk_addr;
		aml_chip->aml_nandenv_info->env_valid_node->phy_page_addr = 0;
		aml_chip->aml_nandenv_info->env_valid_node->ec = env_tmp_node->ec;
		aml_chip->aml_nandenv_info->env_valid_node->timestamp += 1;
		aml_chip->aml_nandenv_info->env_free_node = env_tmp_node->next;
		kfree(env_tmp_node);
	}

	addr = aml_chip->aml_nandenv_info->env_valid_node->phy_blk_addr;
	addr *= mtd->erasesize;
	addr += aml_chip->aml_nandenv_info->env_valid_node->phy_page_addr * mtd->writesize;
	if (aml_chip->aml_nandenv_info->env_valid_node->phy_page_addr == 0) {

		memset(&aml_env_erase_info, 0, sizeof(struct erase_info));
		aml_env_erase_info.mtd = mtd;
		aml_env_erase_info.addr = addr;
		aml_env_erase_info.len = mtd->erasesize;

		error = mtd->erase(mtd, &aml_env_erase_info);
		if (error) {
			printf("env free blk erase failed %d\n", error);
			mtd->block_markbad(mtd, addr);
			return error;
		}
		aml_chip->aml_nandenv_info->env_valid_node->ec++;
	}

	nand_bbt_info = &aml_chip->aml_nandenv_info->nand_bbt_info;
	if ((!memcmp(nand_bbt_info->bbt_head_magic, BBT_HEAD_MAGIC, 4)) && (!memcmp(nand_bbt_info->bbt_tail_magic, BBT_TAIL_MAGIC, 4))) {
		memcpy(env_ptr->data + default_environment_size, aml_chip->aml_nandenv_info->nand_bbt_info.bbt_head_magic, sizeof(struct aml_nand_bbt_info));
		env_crc_update ();
	}
	printf("Writing to Nand... \n");
	if (writeenv(addr, (u_char *) env_ptr)) {
		printf("FAILED!\n");
		return 1;
	}

	printf("Successful!\n");	
	return error;
}