blob: 1bed74ca458a085b4d0ddbf53fe08e34273e1695 [file] [log] [blame]
/*
* drivers/amlogic/mtd_old/env_old.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/crc32.h>
#include "aml_mtd.h"
#include "env_old.h"
#include<linux/cdev.h>
#include <linux/device.h>
#define ENV_NAME "nand_env"
static dev_t uboot_env_no;
struct cdev uboot_env;
struct device *uboot_dev;
struct class *uboot_env_class;
static struct aml_nand_chip *aml_chip_env;
static struct mtd_info *nand_env_mtd = NULL;
static unsigned default_environment_size =
(ENV_SIZE - sizeof(struct aml_nand_bbt_info));
/* protect flag inside */
static int rsv_protect = 1;
static inline void _aml_rsv_disprotect(void)
{
rsv_protect = 0;
}
static inline void _aml_rsv_protect(void)
{
rsv_protect = 1;
}
static inline int _aml_rsv_isprotect(void)
{
return rsv_protect;
}
int aml_nand_rsv_erase_protect(struct mtd_info *mtd, unsigned int block_addr)
{
if (!_aml_rsv_isprotect())
return 0;
#if 0
pr_err(" %s() %d: fixme\n", __func__, __LINE__);
if (aml_chip->aml_nandkey_info != NULL) {
if (aml_chip->aml_nandkey_info->valid)
if ((block_addr >= aml_chip->aml_nandkey_info->start_block)
&& (block_addr < aml_chip->aml_nandkey_info->end_block))
return -1; /*need skip key blocks*/
}
if (aml_chip->aml_nandbbt_info != NULL) {
if (aml_chip->aml_nandbbt_info->valid)
if ((block_addr >= aml_chip->aml_nandbbt_info->start_block)
&& (block_addr < aml_chip->aml_nandbbt_info->end_block))
return -1; /*need skip bbt blocks*/
}
#endif
return 0;
}
int aml_nand_read_env(struct mtd_info *mtd, size_t offset, u_char *buf)
{
struct env_oobinfo_t *env_oobinfo;
int error = 0, err, start_blk, total_blk;
size_t addr = 0;
size_t amount_loaded = 0;
size_t len;
//struct mtd_oob_ops aml_oob_ops;
struct mtd_oob_ops *aml_oob_ops = NULL;
u_char *data_buf = NULL;
u_char env_oob_buf[sizeof(struct env_oobinfo_t)];
struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
struct aml_nandenv_info_t *envinfo;
envinfo = aml_chip->aml_nandenv_info;
if (!envinfo->env_valid)
return 1;
addr = (1024 * mtd->writesize / aml_chip->plane_num);
#ifdef NEW_NAND_SUPPORT
if ((aml_chip->new_nand_info.type)
&& (aml_chip->new_nand_info.type < 10))
addr += RETRY_NAND_BLK_NUM* mtd->erasesize;
#endif
start_blk = addr / mtd->erasesize;
total_blk = mtd->size / mtd->erasesize;
addr = envinfo->env_valid_node->phy_blk_addr;
addr *= mtd->erasesize;
addr += envinfo->env_valid_node->phy_page_addr
* mtd->writesize;
data_buf = kzalloc(mtd->writesize, GFP_KERNEL);
if (data_buf == NULL) {
err = -ENOMEM;
goto exit;
}
aml_oob_ops = kzalloc(sizeof(struct mtd_oob_ops),
GFP_KERNEL);
if (aml_oob_ops == NULL) {
err = -ENOMEM;
goto exit;
}
env_oobinfo = (struct env_oobinfo_t *)env_oob_buf;
while (amount_loaded < CONFIG_ENV_SIZE ) {
aml_oob_ops->mode = MTD_OPS_AUTO_OOB;
aml_oob_ops->len = mtd->writesize;
aml_oob_ops->ooblen = sizeof(struct env_oobinfo_t);
aml_oob_ops->ooboffs = 0;
aml_oob_ops->datbuf = data_buf;
aml_oob_ops->oobbuf = env_oob_buf;
error = mtd->_read_oob(mtd, addr, aml_oob_ops);
if ((error != 0) && (error != -EUCLEAN)) {
pr_err("blk check good but read failed: %llx, %d\n",
(uint64_t)addr, error);
err = 1;
goto exit;
}
if (memcmp(env_oobinfo->name, ENV_NAND_MAGIC, 4))
pr_err("invalid env magic: %llx\n", (uint64_t)addr);
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) {
err = 1;
goto exit;
}
kfree(data_buf);
kfree(aml_oob_ops);
return 0;
exit:
if (aml_oob_ops) {
kfree(aml_oob_ops);
aml_oob_ops = NULL;
}
if (data_buf) {
kfree(data_buf);
data_buf = NULL;
}
return err;
}
static int aml_nand_write_env(struct mtd_info *mtd,
loff_t offset, u_char *buf)
{
struct env_oobinfo_t *env_oobinfo;
int error = 0, err;
loff_t addr = 0;
size_t amount_saved = 0;
size_t len;
struct mtd_oob_ops * aml_oob_ops;
u_char *data_buf = NULL;
u_char env_oob_buf[sizeof(struct env_oobinfo_t)];
struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
struct aml_nandenv_info_t *envinfo;
envinfo = aml_chip->aml_nandenv_info;
aml_oob_ops = kzalloc(sizeof(struct mtd_oob_ops),
GFP_KERNEL);
if (aml_oob_ops == NULL) {
err = -ENOMEM;
goto exit;
}
data_buf = kzalloc(mtd->writesize, GFP_KERNEL);
if (data_buf == NULL) {
err = -ENOMEM;
goto exit;
}
addr = offset;
env_oobinfo = (struct env_oobinfo_t *)env_oob_buf;
memcpy(env_oobinfo->name, ENV_NAND_MAGIC, 4);
env_oobinfo->ec = envinfo->env_valid_node->ec;
env_oobinfo->timestamp = envinfo->env_valid_node->timestamp;
env_oobinfo->status_page = 1;
while (amount_saved < CONFIG_ENV_SIZE ) {
aml_oob_ops->mode = MTD_OPS_AUTO_OOB;
aml_oob_ops->len = mtd->writesize;
aml_oob_ops->ooblen = sizeof(struct env_oobinfo_t);
aml_oob_ops->ooboffs = 0;
aml_oob_ops->datbuf = data_buf;
aml_oob_ops->oobbuf = env_oob_buf;
memset((u_char *)aml_oob_ops->datbuf,
0x0, mtd->writesize);
len = min(mtd->writesize,
CONFIG_ENV_SIZE - amount_saved);
memcpy((u_char *)aml_oob_ops->datbuf,
buf + amount_saved, len);
error = mtd->_write_oob(mtd, addr, aml_oob_ops);
if (error) {
pr_err("blk check good but write failed: %llx, %d\n",
(uint64_t)addr, error);
//return 1;
err = 1;
goto exit;
}
addr += mtd->writesize;;
amount_saved += mtd->writesize;
}
if (amount_saved < CONFIG_ENV_SIZE) {
err = 1;
goto exit;
}
kfree(data_buf);
kfree(aml_oob_ops);
return 0;
exit:
if (aml_oob_ops) {
kfree(aml_oob_ops);
aml_oob_ops = NULL;
}
if (data_buf) {
kfree(data_buf);
data_buf = NULL;
}
return err;
}
int aml_nand_save_env(struct mtd_info *mtd, u_char *buf)
{
struct aml_nand_bbt_info *nand_bbt_info;
struct env_free_node_t *env_free_node = NULL;
struct env_free_node_t *env_tmp_node = NULL;
int error = 0, pages_per_blk, i = 1;
loff_t addr = 0;
struct erase_info *aml_env_erase_info;
env_t *env_ptr = (env_t *)buf;
struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
struct aml_nandenv_info_t *envinfo;
envinfo = aml_chip->aml_nandenv_info;
if (!envinfo->env_init)
return 1;
aml_env_erase_info = kzalloc(sizeof(struct erase_info),
GFP_KERNEL);
if (aml_env_erase_info == NULL) {
pr_err("%s %d no mem for aml_env_erase_info\n",
__func__, __LINE__);
error = -ENOMEM;
goto exit;
}
pages_per_blk = mtd->erasesize / mtd->writesize;
if ((mtd->writesize < CONFIG_ENV_SIZE)
&& (envinfo->env_valid == 1))
i = (CONFIG_ENV_SIZE + mtd->writesize - 1) / mtd->writesize;
RE_SEARCH:
if (envinfo->env_valid) {
envinfo->env_valid_node->phy_page_addr += i;
if ((envinfo->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) {
pr_err("%s %d no mem for env_free_node\n",
__func__, __LINE__);
error = -ENOMEM;
goto exit;
}
env_free_node->phy_blk_addr =
envinfo->env_valid_node->phy_blk_addr;
env_free_node->ec = envinfo->env_valid_node->ec;
env_tmp_node = envinfo->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 = envinfo->env_free_node;
envinfo->env_valid_node->phy_blk_addr
= env_tmp_node->phy_blk_addr;
envinfo->env_valid_node->phy_page_addr = 0;
envinfo->env_valid_node->ec = env_tmp_node->ec;
envinfo->env_valid_node->timestamp += 1;
envinfo->env_free_node = env_tmp_node->next;
kfree(env_tmp_node);
}
}
else {
env_tmp_node = envinfo->env_free_node;
envinfo->env_valid_node->phy_blk_addr =
env_tmp_node->phy_blk_addr;
envinfo->env_valid_node->phy_page_addr = 0;
envinfo->env_valid_node->ec = env_tmp_node->ec;
envinfo->env_valid_node->timestamp += 1;
envinfo->env_free_node = env_tmp_node->next;
kfree(env_tmp_node);
}
addr = envinfo->env_valid_node->phy_blk_addr;
addr *= mtd->erasesize;
addr += envinfo->env_valid_node->phy_page_addr * mtd->writesize;
if (envinfo->env_valid_node->phy_page_addr == 0) {
error = mtd->_block_isbad(mtd, addr);
if (error != 0) {
/*bad block here, need fix it*/
/*because of env_blk list may*/
/*be include bad block,*/
/*so we need check it and done here*/
/* if don't, some bad blocks may be */
/* erase here and env will lost*/
/*or too much ecc error*/
pr_err("have bad block in env_blk list!!!!\n");
envinfo->env_valid_node->phy_page_addr
= pages_per_blk - i;
goto RE_SEARCH;
}
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) {
pr_err("env free blk erase failed %d\n", error);
mtd->_block_markbad(mtd, addr);
//return error;
goto exit;
}
envinfo->env_valid_node->ec++;
}
nand_bbt_info = &envinfo->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,
envinfo->nand_bbt_info.bbt_head_magic,
sizeof(struct aml_nand_bbt_info));
env_ptr->crc = (crc32((0 ^ 0xffffffffL),
env_ptr->data, ENV_SIZE) ^ 0xffffffffL);
}
if (aml_nand_write_env(mtd, addr, (u_char *) env_ptr)) {
pr_err("update nand env FAILED!\n");
error = 1;
goto exit;
}
kfree(aml_env_erase_info);
return error;
exit:
if (aml_env_erase_info) {
kfree(aml_env_erase_info);
aml_env_erase_info = NULL;
}
if (env_free_node) {
kfree(env_free_node);
env_free_node = NULL;
}
return error;
}
static int aml_nand_env_init(struct mtd_info *mtd)
{
struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
struct nand_chip *chip = &aml_chip->chip;
struct env_oobinfo_t *env_oobinfo;
struct env_free_node_t *env_free_node;
struct env_free_node_t *env_tmp_node = NULL;
struct env_free_node_t *env_prev_node = NULL;
int error = 0, ret = 0, env_page_num = 0;
int err, start_blk, total_blk, env_blk;
int i, j, pages_per_blk;
int bad_blk_cnt = 0, max_env_blk, phys_erase_shift;
loff_t offset;
u_char *data_buf = NULL;
u_char *good_addr;
int k, env_status = 0, scan_status = 0;
struct mtd_oob_ops *aml_oob_ops;
u_char env_oob_buf[sizeof(struct env_oobinfo_t)];
struct aml_nandenv_info_t *envinfo;
struct env_valid_node_t *vnode;
good_addr = kzalloc(256, GFP_KERNEL);
if (good_addr == NULL)
return -ENOMEM;
memset(good_addr, 0, 256);
aml_oob_ops = kzalloc(sizeof(struct mtd_oob_ops),
GFP_KERNEL);
if (aml_oob_ops == NULL) {
pr_err("%s %d no mem for aml_oob_ops\n",
__func__, __LINE__);
err = -ENOMEM;
goto exit;
}
data_buf = kzalloc(mtd->writesize, GFP_KERNEL);
if (data_buf == NULL) {
err = -ENOMEM;
goto exit;
}
aml_chip->aml_nandenv_info =
kzalloc(sizeof(struct aml_nandenv_info_t),
GFP_KERNEL);
if (aml_chip->aml_nandenv_info == NULL) {
err = -ENOMEM;
goto exit;
}
envinfo = aml_chip->aml_nandenv_info;
envinfo->mtd = mtd;
envinfo->env_valid_node = kzalloc(sizeof(struct env_valid_node_t),
GFP_KERNEL);
if (envinfo->env_valid_node == NULL) {
err = -ENOMEM;
goto exit;
}
vnode = envinfo->env_valid_node;
vnode->phy_blk_addr = -1;
env_page_num = CONFIG_ENV_SIZE /mtd->writesize;
phys_erase_shift = fls(mtd->erasesize) - 1;
max_env_blk = (NAND_MINI_PART_SIZE >> phys_erase_shift);
if (max_env_blk < 2)
max_env_blk = 2;
if (nand_boot_flag)
offset = (1024 * mtd->writesize / aml_chip->plane_num);
else {
default_environment_size = 0;
offset = 0;
}
#ifdef NEW_NAND_SUPPORT
if ((aml_chip->new_nand_info.type)
&& (aml_chip->new_nand_info.type < 10))
offset += RETRY_NAND_BLK_NUM * mtd->erasesize;
#endif
start_blk = (int)(offset >> phys_erase_shift);
total_blk = (int)(mtd->size >> phys_erase_shift);
pages_per_blk = (1 << (chip->phys_erase_shift - chip->page_shift));
env_oobinfo = (struct env_oobinfo_t *)env_oob_buf;
if ((default_environment_size + sizeof(struct aml_nand_bbt_info))
> ENV_SIZE)
total_blk = start_blk + max_env_blk;
env_blk = 0;
do {
offset = mtd->erasesize;
offset *= start_blk;
scan_status = 0;
error = mtd->_block_isbad(mtd, offset);
if (error) {
for (j=0; j<MAX_BAD_BLK_NUM; j++) {
if (envinfo->nand_bbt_info.nand_bbt[j] == 0) {
envinfo->nand_bbt_info.nand_bbt[j] =
start_blk;
bad_blk_cnt++;
break;
}
}
continue;
}
RE_ENV:
aml_oob_ops->mode = MTD_OPS_AUTO_OOB;
aml_oob_ops->len = mtd->writesize;
aml_oob_ops->ooblen = sizeof(struct env_oobinfo_t);
aml_oob_ops->ooboffs = 0;
aml_oob_ops->datbuf = data_buf;
aml_oob_ops->oobbuf = env_oob_buf;
memset((u_char *)aml_oob_ops->datbuf,
0x0, mtd->writesize);
memset((u_char *)aml_oob_ops->oobbuf,
0x0, aml_oob_ops->ooblen);
error = mtd->_read_oob(mtd, offset, aml_oob_ops);
if ((error != 0) && (error != -EUCLEAN)) {
pr_err("blk check good but read failed: %llx, %d\n",
(uint64_t)offset, error);
offset += CONFIG_ENV_SIZE;
if ((scan_status++ > 6)
|| (!((uint32_t)offset % mtd->erasesize))) {
pr_err("can not scan env blks\n");
scan_status = 0;
continue;
}
goto RE_ENV;
}
envinfo->env_init = 1;
if (!memcmp(env_oobinfo->name, ENV_NAND_MAGIC, 4)) {
envinfo->env_valid = 1;
if (vnode->phy_blk_addr >= 0) {
env_free_node =
kzalloc(sizeof(struct env_free_node_t),
GFP_KERNEL);
if (env_free_node == NULL) {
pr_err("%s %d no mem for env_free_node\n",
__func__, __LINE__);
err = -ENOMEM;
goto exit;
}
env_free_node->dirty_flag = 1;
if (env_oobinfo->timestamp
> vnode->timestamp) {
env_free_node->phy_blk_addr =
vnode->phy_blk_addr;
env_free_node->ec = vnode->ec;
vnode->phy_blk_addr = start_blk;
vnode->phy_page_addr = 0;
vnode->ec =
env_oobinfo->ec;
vnode->timestamp =
env_oobinfo->timestamp;
} else {
env_free_node->phy_blk_addr = start_blk;
env_free_node->ec = env_oobinfo->ec;
}
if (envinfo->env_free_node == NULL)
envinfo->env_free_node = env_free_node;
else {
env_tmp_node = envinfo->env_free_node;
while (env_tmp_node->next != NULL) {
env_tmp_node = env_tmp_node->next;
}
env_tmp_node->next = env_free_node;
}
} else {
vnode->phy_blk_addr = start_blk;
vnode->phy_page_addr = 0;
vnode->ec = env_oobinfo->ec;
vnode->timestamp = env_oobinfo->timestamp;
}
} else if (env_blk < max_env_blk) {
env_free_node = kzalloc(sizeof(struct env_free_node_t),
GFP_KERNEL);
if (!env_free_node) {
err = -ENOMEM;
goto exit;
}
env_free_node->phy_blk_addr = start_blk;
env_free_node->ec = env_oobinfo->ec;
if (envinfo->env_free_node == NULL)
envinfo->env_free_node = env_free_node;
else {
env_tmp_node = envinfo->env_free_node;
env_prev_node = env_tmp_node;
while (env_tmp_node != NULL) {
if (env_tmp_node->dirty_flag == 1)
break;
env_prev_node = env_tmp_node;
env_tmp_node = env_tmp_node->next;
}
if (env_prev_node == env_tmp_node) {
env_free_node->next = env_tmp_node;
envinfo->env_free_node = env_free_node;
}
else {
env_prev_node->next = env_free_node;
env_free_node->next = env_tmp_node;
}
}
}
env_blk++;
if ((env_blk >= max_env_blk) && (envinfo->env_valid == 1))
break;
} while ((++start_blk) < ENV_NAND_SCAN_BLK);
if (start_blk >= ENV_NAND_SCAN_BLK) {
memcpy(envinfo->nand_bbt_info.bbt_head_magic,
BBT_HEAD_MAGIC, 4);
memcpy(envinfo->nand_bbt_info.bbt_tail_magic,
BBT_TAIL_MAGIC, 4);
}
if (envinfo->env_valid == 1) {
aml_oob_ops->mode = MTD_OPS_AUTO_OOB;
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->ooboffs = 0;
aml_oob_ops->datbuf = data_buf;
aml_oob_ops->oobbuf = env_oob_buf;
for (i=0; i<pages_per_blk; i++) {
memset((u_char *)aml_oob_ops->datbuf,
0x0, mtd->writesize);
memset((u_char *)aml_oob_ops->oobbuf,
0x0, aml_oob_ops->ooblen);
offset = envinfo->env_valid_node->phy_blk_addr;
offset *= mtd->erasesize;
offset += i * mtd->writesize;
error = mtd->_read_oob(mtd, offset, aml_oob_ops);
if ((error != 0) && (error != -EUCLEAN)) {
pr_err("chk good read fail: %llx, %d\n",
(uint64_t)offset, error);
ret = -1;
continue;
}
if (!memcmp(env_oobinfo->name, ENV_NAND_MAGIC, 4)) {
good_addr[i] = 1;
envinfo->env_valid_node->phy_page_addr = i;
}
else
break;
}
}
if ((mtd->writesize < CONFIG_ENV_SIZE)
&& (envinfo->env_valid == 1)) {
i = envinfo->env_valid_node->phy_page_addr;
if ( ((i+1)%env_page_num) != 0 ) {
ret = -1;
pr_err("aml_nand_env_init : find env incomplete\n");
}
if (ret == -1) {
for ( i=0; i < (pages_per_blk / env_page_num); i++ ) {
env_status =0;
for (k = 0; k < env_page_num; k++) {
if (!good_addr[ k + i * env_page_num]) {
pr_err("find %d page env fail\n",
(k + i * env_page_num));
env_status = 1;
break;
}
}
if (!env_status) {
pr_err("find %d page env ok\n",
(i * env_page_num));
envinfo->env_valid_node->phy_page_addr =
k + i * env_page_num - 1;
}
}
}
i = (CONFIG_ENV_SIZE + mtd->writesize - 1) / mtd->writesize;
envinfo->env_valid_node->phy_page_addr -= (i - 1);
}
offset = envinfo->env_valid_node->phy_blk_addr;
offset *= mtd->erasesize;
offset += envinfo->env_valid_node->phy_page_addr * mtd->writesize;
pr_info("aml nand env valid addr: %llx\n", (uint64_t)offset);
pr_info("CONFIG_ENV_SIZE=0x%x\n", CONFIG_ENV_SIZE);
pr_info("ENV_SIZE=0x%x\n", ENV_SIZE);
pr_info("bbt=0x%x\n", sizeof(struct aml_nand_bbt_info));
pr_info("default_environment_size=0x%x\n",
default_environment_size);
kfree(data_buf);
kfree(aml_oob_ops);
return 0;
exit:
kfree(data_buf);
data_buf = NULL;
kfree(aml_oob_ops);
aml_oob_ops = NULL;
kfree(aml_chip->aml_nandenv_info->env_valid_node);
aml_chip->aml_nandenv_info->env_valid_node = NULL;
kfree(aml_chip->aml_nandenv_info);
aml_chip->aml_nandenv_info = NULL;
kfree(env_free_node);
env_free_node = NULL;
return err;
}
static int __attribute__((unused))
aml_nand_update_env(struct mtd_info *mtd)
{
struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
env_t *env_ptr;
loff_t offset;
int error = 0;
struct aml_nandenv_info_t *envinfo;
envinfo = aml_chip->aml_nandenv_info;
env_ptr = kzalloc(sizeof(env_t), GFP_KERNEL);
if (env_ptr == NULL)
return -ENOMEM;
if (envinfo->env_valid == 1) {
offset = envinfo->env_valid_node->phy_blk_addr;
offset *= mtd->erasesize;
offset +=
envinfo->env_valid_node->phy_page_addr * mtd->writesize;
error = aml_nand_read_env(mtd, offset, (u_char *)env_ptr);
if (error) {
pr_err("nand env read failed: %llx, %d\n",
(uint64_t)offset, error);
return error;
}
error = aml_nand_save_env(mtd, (u_char *)env_ptr);
if (error) {
pr_err("update env bbt failed %d\n", error);
return error;
}
}
return error;
}
/* unpack bbt from bbt stored */
int unpack_bbt(int start, int total,
struct aml_nand_chip *aml_chip,
struct aml_nand_bbt_info *bbtinfo)
{
int ret = 0;
int i, j;
uint16_t *bbt;
bbt = bbtinfo->nand_bbt;
for (i = start; i < total; i++) {
aml_chip->block_status[i] = NAND_BLOCK_GOOD;
for (j = 0; j < MAX_BAD_BLK_NUM; j++) {
if ((bbt[j] & 0x7fff) == i) {
if (bbt[j] & 0x8000) {
aml_chip->block_status[i]
= NAND_FACTORY_BAD;
pr_info("%s() fbb=%d, status[%d] =%d\n",
__func__,
i, i,
aml_chip->block_status[i]);
} else {
aml_chip->block_status[i]
= NAND_BLOCK_BAD;
pr_info("%s() used bad blk=%d\n",
__func__, i);
}
break;
}
}
}
return ret;
}
int aml_nand_env_check(struct mtd_info *mtd)
{
struct aml_nand_chip *aml_chip = mtd_to_nand_chip(mtd);
env_t *env_ptr;
int error = 0, start_blk, total_blk, phys_erase_shift;
loff_t offset;
struct aml_nandenv_info_t *envinfo;
struct aml_nand_bbt_info *bbtinfo;
struct aml_nand_bbt_info *bbt_env;
aml_chip->update_env_flag=0;
error = aml_nand_env_init(mtd);
if (error)
return error;
envinfo = aml_chip->aml_nandenv_info;
env_ptr = kzalloc(sizeof(env_t), GFP_KERNEL);
if (env_ptr == NULL)
return -ENOMEM;
if (envinfo->env_valid == 1) {
offset = envinfo->env_valid_node->phy_blk_addr;
offset *= mtd->erasesize;
offset += envinfo->env_valid_node->phy_page_addr
* mtd->writesize;
error = aml_nand_read_env(mtd,
offset,
(u_char *)env_ptr);
if (error) {
pr_err("%s() failed: %llx, %d\n",
__func__, (uint64_t)offset, error);
goto exit;
}
phys_erase_shift = fls(mtd->erasesize) - 1;
offset = (1024 * mtd->writesize / aml_chip->plane_num);
#ifdef NEW_NAND_SUPPORT
if ((aml_chip->new_nand_info.type)
&& (aml_chip->new_nand_info.type < 10))
offset += RETRY_NAND_BLK_NUM* mtd->erasesize;
#endif
start_blk = (int)(offset >> phys_erase_shift);
total_blk = (int)(mtd->size >> phys_erase_shift);
bbtinfo = (struct aml_nand_bbt_info *)
(env_ptr->data + default_environment_size);
if ((!memcmp(bbtinfo->bbt_head_magic, BBT_HEAD_MAGIC, 4))
&& (!memcmp(bbtinfo->bbt_tail_magic,
BBT_TAIL_MAGIC, 4))) {
pr_info("%s() %d: GOT bbt!\n", __func__, __LINE__);
/* fixme, */
unpack_bbt(start_blk, total_blk,
aml_chip, bbtinfo);
bbt_env = &envinfo->nand_bbt_info;
memcpy((u_char *)bbt_env->bbt_head_magic,
(u_char *)bbtinfo,
sizeof(struct aml_nand_bbt_info));
}
}
if (aml_chip->update_env_flag) {
error = aml_nand_save_env(mtd, (u_char *)env_ptr);
if (error) {
pr_err("nand env save failed: %d\n", error);
goto exit;
}
}
exit:
kfree(env_ptr);
return 0;
}
int aml_nand_scan_bbt(struct mtd_info *mtd)
{
return 0;
}
/********************************************************/
int amlnf_env_save(u8 *buf, int len)
{
u8 *env_buf = NULL;
int ret = 0;
pr_info("uboot env amlnf_env_save : ####\n");
if (aml_chip_env == NULL) {
pr_info("uboot env not init yet!,%s\n", __func__);
return -EFAULT;
}
if (len > CONFIG_ENV_SIZE) {
pr_info("uboot env data len too much,%s\n", __func__);
return -EFAULT;
}
env_buf = kzalloc(CONFIG_ENV_SIZE, GFP_KERNEL);
if (env_buf == NULL) {
/*pr_info("nand malloc for uboot env failed\n");*/
ret = -1;
goto exit_err;
}
memset(env_buf, 0, CONFIG_ENV_SIZE);
memcpy(env_buf, buf, len);
ret = aml_nand_save_env(nand_env_mtd, env_buf);
if (ret) {
pr_info("nand uboot env error,%s\n", __func__);
ret = -EFAULT;
goto exit_err;
}
exit_err:
kfree(env_buf);
env_buf = NULL;
return ret;
}
int amlnf_env_read(u8 *buf, int len)
{
u8 *env_buf = NULL;
int ret = 0;
pr_info("uboot env amlnf_env_read, len %d: ####\n", len);
if (len > CONFIG_ENV_SIZE) {
pr_info("uboot env data len too much, %s\n",
__func__);
return -EFAULT;
}
if (aml_chip_env == NULL) {
memset(buf, 0x0, len);
pr_info("uboot env arg_valid = 0 invalid, %s\n",
__func__);
return 0;
}
env_buf = kzalloc(CONFIG_ENV_SIZE, GFP_KERNEL);
if (env_buf == NULL) {
ret = -1;
goto exit_err;
}
memset(env_buf, 0, CONFIG_ENV_SIZE);
ret = aml_nand_read_env(nand_env_mtd, 0, env_buf);
if (ret) {
pr_info("nand uboot env error,%s\n",
__func__);
ret = -EFAULT;
goto exit_err;
}
memcpy(buf, env_buf, len);
exit_err:
kfree(env_buf);
env_buf = NULL;
return ret;
}
ssize_t env_show(struct class *class, struct class_attribute *attr,
char *buf)
{
pr_info("env_show : #####\n");
return 0;
}
ssize_t env_store(struct class *class, struct class_attribute *attr,
const char *buf, size_t count)
{
int ret = 0;
u8 *env_ptr = NULL;
pr_info("env_store : #####\n");
env_ptr = kzalloc(CONFIG_ENV_SIZE, GFP_KERNEL);
if (env_ptr == NULL)
return -ENOMEM;
ret = amlnf_env_read(env_ptr, CONFIG_ENV_SIZE);
if (ret) {
pr_info("nand_env_read: nand env read failed\n");
kfree(env_ptr);
return -EFAULT;
}
ret = amlnf_env_save(env_ptr, CONFIG_ENV_SIZE);
if (ret) {
pr_info("nand_env_read: nand env read failed\n");
kfree(env_ptr);
return -EFAULT;
}
pr_info("env_store : OK #####\n");
return count;
}
static CLASS_ATTR(env, 0644, env_show, env_store);
int uboot_env_open(struct inode *node, struct file *file)
{
return 0;
}
ssize_t uboot_env_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
u8 *env_ptr = NULL;
ssize_t read_size = 0;
int ret = 0;
if (*ppos == CONFIG_ENV_SIZE)
return 0;
if (*ppos >= CONFIG_ENV_SIZE) {
pr_info("nand env: data access out of space!\n");
return -EFAULT;
}
env_ptr = vmalloc(CONFIG_ENV_SIZE + 2048);
if (env_ptr == NULL)
return -ENOMEM;
/*amlnand_get_device(aml_chip_env, CHIP_READING);*/
ret = amlnf_env_read((u8 *)env_ptr, CONFIG_ENV_SIZE);
if (ret) {
pr_info("nand_env_read: nand env read failed:%d\n",
ret);
ret = -EFAULT;
goto exit;
}
if ((*ppos + count) > CONFIG_ENV_SIZE)
read_size = CONFIG_ENV_SIZE - *ppos;
else
read_size = count;
ret = copy_to_user(buf, (env_ptr + *ppos), read_size);
*ppos += read_size;
exit:
/*amlnand_release_device(aml_chip_env);*/
vfree(env_ptr);
return read_size;
}
ssize_t uboot_env_write(struct file *file,
const char __user *buf,
size_t count, loff_t *ppos)
{
u8 *env_ptr = NULL;
ssize_t write_size = 0;
int ret = 0;
if (*ppos == CONFIG_ENV_SIZE)
return 0;
if (*ppos >= CONFIG_ENV_SIZE) {
pr_info("nand env: data access out of space!\n");
return -EFAULT;
}
env_ptr = vmalloc(CONFIG_ENV_SIZE + 2048);
if (env_ptr == NULL)
return -ENOMEM;
/*not need nand_get_device here, mtd->_read_xx will done with it*/
/*nand_get_device(mtd, FL_WRITING);*/
ret = amlnf_env_read((u8 *)env_ptr, CONFIG_ENV_SIZE);
if (ret) {
pr_info("nand_env_read: nand env read failed\n");
ret = -EFAULT;
goto exit;
}
if ((*ppos + count) > CONFIG_ENV_SIZE)
write_size = CONFIG_ENV_SIZE - *ppos;
else
write_size = count;
ret = copy_from_user((env_ptr + *ppos), buf, write_size);
ret = amlnf_env_save(env_ptr, CONFIG_ENV_SIZE);
if (ret) {
pr_info("nand_env_read: nand env read failed\n");
ret = -EFAULT;
goto exit;
}
*ppos += write_size;
exit:
/*nand_release_device(mtd);*/
vfree(env_ptr);
return write_size;
}
long uboot_env_ioctl(struct file *file, u32 cmd, unsigned long args)
{
return 0;
}
static const struct file_operations uboot_env_ops = {
.open = uboot_env_open,
.read = uboot_env_read,
.write = uboot_env_write,
.unlocked_ioctl = uboot_env_ioctl,
};
int aml_ubootenv_init(struct aml_nand_chip *aml_chip)
{
int ret = 0;
u8 *env_buf = NULL;
aml_chip_env = aml_chip;
nand_env_mtd = aml_chip->mtd;
env_buf = kzalloc(CONFIG_ENV_SIZE, GFP_KERNEL);
if (env_buf == NULL) {
ret = -1;
goto exit_err;
}
memset(env_buf, 0x0, CONFIG_ENV_SIZE);
#ifndef AML_NAND_UBOOT
pr_info("%s: register env chardev\n", __func__);
ret = alloc_chrdev_region(&uboot_env_no, 0, 1, ENV_NAME);
if (ret < 0) {
pr_info("alloc uboot env dev_t no failed\n");
ret = -1;
goto exit_err;
}
cdev_init(&uboot_env, &uboot_env_ops);
uboot_env.owner = THIS_MODULE;
ret = cdev_add(&uboot_env, uboot_env_no, 1);
if (ret) {
pr_info("uboot env dev add failed\n");
ret = -1;
goto exit_err1;
}
uboot_env_class = class_create(THIS_MODULE, ENV_NAME);
if (IS_ERR(uboot_env_class)) {
pr_info("uboot env dev add failed\n");
ret = -1;
goto exit_err2;
}
ret = class_create_file(uboot_env_class, &class_attr_env);
if (ret) {
pr_info("uboot env dev add failed\n");
ret = -1;
goto exit_err2;
}
uboot_dev = device_create(uboot_env_class,
NULL,
uboot_env_no,
NULL,
ENV_NAME);
if (IS_ERR(uboot_dev)) {
pr_info("uboot env dev add failed\n");
ret = -1;
goto exit_err3;
}
pr_info("%s: register env chardev OK\n", __func__);
kfree(env_buf);
env_buf = NULL;
return ret;
exit_err3:
class_remove_file(uboot_env_class, &class_attr_env);
class_destroy(uboot_env_class);
exit_err2:
cdev_del(&uboot_env);
exit_err1:
unregister_chrdev_region(uboot_env_no, 1);
#endif /* AML_NAND_UBOOT */
exit_err:
kfree(env_buf);
env_buf = NULL;
return ret;
}