503 lines
11 KiB
C
503 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Use bbt partition to manage bad blocks for fast boot in nand
|
|
*
|
|
* Authors:
|
|
* Copyright (c) 2022, ArtInChip Technology Co., Ltd
|
|
* Author: Xuan Wen <xuan.wen@artinchip.com>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <bootstage.h>
|
|
#include <image.h>
|
|
#include <asm/cache.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/nand.h>
|
|
#include <command.h>
|
|
#include <console.h>
|
|
#include <env.h>
|
|
#include <log.h>
|
|
#include <linux/err.h>
|
|
#include <mtd.h>
|
|
#include <dm.h>
|
|
#include <dm/uclass.h>
|
|
#include <dm/device-internal.h>
|
|
#include <artinchip_spinand.h>
|
|
|
|
#ifdef CONFIG_AUTO_CALCULATE_PART_CONFIG
|
|
#include <generated/image_cfg_part_config.h>
|
|
#endif
|
|
|
|
/* check nand bad block data in spinand bbt part */
|
|
static int check_nand_part_bad_block(struct bad_block_ctl *ctl)
|
|
{
|
|
u8 cmp_sum = 0;
|
|
int i;
|
|
int ret = 0;
|
|
|
|
if (ctl->start != 0xaa55 || ctl->end != 0x55aa) {
|
|
pr_info("bbt:ctl->start = 0x%x, ctl->end = 0x%x\n",
|
|
ctl->start, ctl->end);
|
|
return 1;
|
|
}
|
|
|
|
if (ctl->len % 512) {
|
|
pr_info("bbt:ctl->len = 0x%x\n", ctl->len);
|
|
return 1;
|
|
}
|
|
|
|
for (i = 0; i < ctl->len; i++)
|
|
cmp_sum += ctl->data[i];
|
|
|
|
ret = cmp_sum == ctl->sum ? 0 : 1;
|
|
if (ret)
|
|
pr_info("bbt:cmp_sum = 0x%x, sum = 0x%x\n",
|
|
cmp_sum, ctl->sum);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void sync_bbt(struct spinand_device *spinand,
|
|
struct bad_block_ctl *bad_block)
|
|
{
|
|
struct nand_device *nand = spinand_to_nand(spinand);
|
|
int i;
|
|
|
|
for (i = 0; i < bad_block->len; i++)
|
|
nanddev_bbt_set_block_status(nand, i, bad_block->data[i]);
|
|
}
|
|
|
|
#ifdef CONFIG_SPL_BUILD
|
|
void spl_nand_bbt_init(struct spinand_device *spinand)
|
|
{
|
|
struct nand_device *nand = spinand_to_nand(spinand);
|
|
unsigned long offset, remain;
|
|
size_t end;
|
|
size_t rdlen, ret;
|
|
struct bad_block_ctl *bad_block;
|
|
|
|
bad_block = kmalloc(sizeof(*bad_block), GFP_KERNEL);
|
|
if (!bad_block)
|
|
return;
|
|
|
|
memset(bad_block, 0, sizeof(struct bad_block_ctl));
|
|
|
|
offset = CONFIG_NAND_BBT_OFFSET;
|
|
remain = sizeof(struct bad_block_ctl);
|
|
end = offset + CONFIG_NAND_BBT_RANGE;
|
|
|
|
while (spinand_block_isbad(spinand, offset))
|
|
offset += nand->info->erasesize;
|
|
|
|
if (offset >= end) {
|
|
pr_err("can't find good block for bbt");
|
|
return;
|
|
}
|
|
|
|
spl_spi_nand_read(spinand, offset, remain, &rdlen, (void *)bad_block);
|
|
if (remain != rdlen) {
|
|
pr_err("Tiny SPI NAND read failed.\n");
|
|
return;
|
|
}
|
|
|
|
ret = check_nand_part_bad_block(bad_block);
|
|
if (!ret) {
|
|
pr_notice("enable nand bbt\n");
|
|
sync_bbt(spinand, bad_block);
|
|
}
|
|
}
|
|
#else
|
|
static void parse_mtd_device_bad_block(struct bad_block_ctl *bbt_buf)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < bbt_buf->len; i++)
|
|
bbt_buf->sum += bbt_buf->data[i];
|
|
}
|
|
|
|
static void get_mtd_device_bad_block(struct mtd_info *mtd)
|
|
{
|
|
struct spinand_device *spinand = mtd_to_spinand(mtd);
|
|
u64 offs = 0;
|
|
|
|
aic_clear_nand_bbt();
|
|
spinand->bbt_buf->len = mtd->size / mtd->erasesize;
|
|
|
|
while (offs < mtd->size) {
|
|
mtd_block_isbad(mtd, offs);
|
|
offs += mtd->erasesize;
|
|
}
|
|
|
|
parse_mtd_device_bad_block(spinand->bbt_buf);
|
|
}
|
|
|
|
static int get_nand_part_bad_block(struct mtd_info *mtd, u8 *buf)
|
|
{
|
|
loff_t offset = CONFIG_NAND_BBT_OFFSET;
|
|
size_t end = offset + CONFIG_NAND_BBT_RANGE;
|
|
size_t rdlen, remain;
|
|
u_char *char_ptr;
|
|
|
|
remain = sizeof(struct bad_block_ctl);
|
|
char_ptr = buf;
|
|
|
|
while ((remain > 0) && (offset < end)) {
|
|
if (mtd_block_isbad(mtd, offset)) {
|
|
offset += mtd->erasesize;
|
|
continue;
|
|
}
|
|
|
|
mtd_read(mtd, offset, remain, &rdlen, char_ptr);
|
|
if (remain != rdlen) {
|
|
pr_err("remain=0x%lx,rdlen=0x%lx\n", remain, rdlen);
|
|
return 1;
|
|
}
|
|
|
|
offset += rdlen;
|
|
remain -= rdlen;
|
|
char_ptr += rdlen;
|
|
}
|
|
|
|
if (offset >= end) {
|
|
pr_err("can't find good block for bbt\n");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_nand_part_bad_block(struct mtd_info *mtd,
|
|
struct bad_block_ctl *bad_ctl)
|
|
{
|
|
loff_t offset = CONFIG_NAND_BBT_OFFSET;
|
|
size_t end = offset + CONFIG_NAND_BBT_RANGE;
|
|
size_t wrlen, remain;
|
|
u_char *char_ptr;
|
|
struct erase_info instr = {.callback = NULL,};
|
|
|
|
instr.len = CONFIG_NAND_BBT_RANGE;
|
|
instr.addr = CONFIG_NAND_BBT_OFFSET;
|
|
instr.mtd = mtd;
|
|
|
|
if (mtd_erase(mtd, &instr)) {
|
|
pr_err("SPINAND: erase failed at 0x%08llx\n", instr.addr);
|
|
return 1;
|
|
}
|
|
|
|
remain = sizeof(struct bad_block_ctl);
|
|
char_ptr = (u_char *)bad_ctl;
|
|
|
|
while ((remain > 0) && (offset < end)) {
|
|
if (mtd_block_isbad(mtd, offset)) {
|
|
offset += mtd->erasesize;
|
|
continue;
|
|
}
|
|
|
|
mtd_write(mtd, offset, remain, &wrlen, char_ptr);
|
|
if (remain != wrlen) {
|
|
pr_err("remain=0x%lx, rdlen=0x%lx\n", remain, wrlen);
|
|
return 1;
|
|
}
|
|
|
|
offset += wrlen;
|
|
remain -= wrlen;
|
|
char_ptr += wrlen;
|
|
}
|
|
|
|
if (offset >= end) {
|
|
pr_err("can't find good block in bbt\n");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void update_nand_part_bad_block(struct bad_block_ctl *ctl,
|
|
struct bad_block_ctl *bad_ctl)
|
|
{
|
|
char *buf = (char *)bad_ctl->data;
|
|
char *cmp_buf = (char *)ctl->data;
|
|
|
|
bad_ctl->start = 0xaa55;
|
|
bad_ctl->len = ctl->len;
|
|
bad_ctl->sum = ctl->sum;
|
|
|
|
memcpy(buf, cmp_buf, bad_ctl->len);
|
|
|
|
bad_ctl->end = 0x55aa;
|
|
}
|
|
|
|
/*
|
|
* return:0 is equal
|
|
*/
|
|
static int cmp_mtd_and_bad_block(struct bad_block_ctl *ctl,
|
|
struct bad_block_ctl *bad_ctl)
|
|
{
|
|
char *buf;
|
|
char *cmp_buf;
|
|
int ret = 1;
|
|
|
|
if (bad_ctl->len == ctl->len && bad_ctl->sum == ctl->sum) {
|
|
buf = (char *)bad_ctl->data;
|
|
cmp_buf = (char *)ctl->data;
|
|
|
|
ret = memcmp(buf, cmp_buf, bad_ctl->len);
|
|
if (ret)
|
|
pr_notice("bad block in bbt part and spinand is diff\n");
|
|
return ret;
|
|
}
|
|
|
|
pr_notice("bad_ctl->len = 0x%x, ctl->len = 0x%x\n", bad_ctl->len,
|
|
ctl->len);
|
|
pr_notice("bad_ctl->sum= 0x%x,ctl->sum= 0x%x\n", bad_ctl->sum,
|
|
ctl->sum);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void aic_clear_nand_bbt(void)
|
|
{
|
|
int ret = 0;
|
|
struct mtd_info *mtd;
|
|
struct udevice *dev;
|
|
struct nand_device *nand;
|
|
|
|
ret = uclass_first_device(UCLASS_MTD, &dev);
|
|
if (ret && !dev) {
|
|
pr_err("Find MTD device failed.\n");
|
|
return;
|
|
}
|
|
|
|
device_probe(dev);
|
|
|
|
mtd = get_mtd_device(NULL, 0);
|
|
if (IS_ERR_OR_NULL(mtd)) {
|
|
pr_err("There is no mtd device.\n");
|
|
return;
|
|
}
|
|
|
|
nand = mtd_to_nanddev(mtd);
|
|
nanddev_bbt_clean(nand);
|
|
|
|
put_mtd_device(mtd);
|
|
}
|
|
|
|
static int init_nand_part_bad_block(struct spinand_device *spinand, struct bad_block_ctl *bad_block_buf)
|
|
{
|
|
struct mtd_info *mtd = spinand_to_mtd(spinand);
|
|
int ret = 0;
|
|
|
|
if (!spinand->bbt_buf) {
|
|
spinand->bbt_buf = kmalloc(sizeof(*spinand->bbt_buf), GFP_KERNEL);
|
|
if (!spinand->bbt_buf) {
|
|
pr_err("Kmalloc bbt buf failed.\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
memset((char *)spinand->bbt_buf, 0, sizeof(*spinand->bbt_buf));
|
|
|
|
/* save bad block information from spinand to buf */
|
|
get_mtd_device_bad_block(mtd);
|
|
update_nand_part_bad_block(spinand->bbt_buf, bad_block_buf);
|
|
ret = set_nand_part_bad_block(mtd, bad_block_buf);
|
|
if (ret)
|
|
return -1;
|
|
|
|
memset(bad_block_buf, 0, sizeof(struct bad_block_ctl));
|
|
|
|
ret = get_nand_part_bad_block(mtd, (u8 *)bad_block_buf);
|
|
if (ret)
|
|
return -1;
|
|
|
|
ret = check_nand_part_bad_block(bad_block_buf);
|
|
if (!ret) {
|
|
ret = cmp_mtd_and_bad_block(spinand->bbt_buf, bad_block_buf);
|
|
if (!ret) {
|
|
pr_notice("bbt part data is already up to date!\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void aic_nand_bbt_init(struct spinand_device *spinand)
|
|
{
|
|
struct bad_block_ctl *bbt_ctl;
|
|
struct mtd_info *mtd = spinand_to_mtd(spinand);
|
|
int ret;
|
|
|
|
bbt_ctl = kmalloc(sizeof(*bbt_ctl), GFP_KERNEL);
|
|
if (!bbt_ctl)
|
|
return;
|
|
|
|
memset(bbt_ctl, 0, sizeof(*bbt_ctl));
|
|
|
|
ret = get_nand_part_bad_block(mtd, (u8 *)bbt_ctl);
|
|
if (ret) {
|
|
pr_err("SPI NAND read failed.\n");
|
|
return;
|
|
}
|
|
|
|
ret = check_nand_part_bad_block(bbt_ctl);
|
|
if (!ret) {
|
|
pr_notice("enable nand bbt ...\n");
|
|
sync_bbt(spinand, bbt_ctl);
|
|
} else {
|
|
pr_notice("There are not bbt ctrl data on Flash, init it.\n");
|
|
init_nand_part_bad_block(spinand, bbt_ctl);
|
|
}
|
|
|
|
kfree(bbt_ctl);
|
|
}
|
|
|
|
int aic_nand_bbt_update(struct spinand_device *spinand)
|
|
{
|
|
struct bad_block_ctl *bbt_ctl;
|
|
struct mtd_info *mtd = spinand_to_mtd(spinand);
|
|
int ret;
|
|
|
|
bbt_ctl = kmalloc(sizeof(struct bad_block_ctl), GFP_KERNEL);
|
|
if (!bbt_ctl)
|
|
return -1;
|
|
|
|
memset(bbt_ctl, 0, sizeof(struct bad_block_ctl));
|
|
|
|
if (!spinand->bbt_buf) {
|
|
spinand->bbt_buf = kmalloc(sizeof(*spinand->bbt_buf), GFP_KERNEL);
|
|
if (!spinand->bbt_buf) {
|
|
pr_err("Kmalloc bbt buf failed.\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
memset((char *)spinand->bbt_buf, 0, sizeof(*spinand->bbt_buf));
|
|
/* save bad block information from spinand to buf */
|
|
get_mtd_device_bad_block(mtd);
|
|
|
|
update_nand_part_bad_block(spinand->bbt_buf, bbt_ctl);
|
|
ret = set_nand_part_bad_block(mtd, bbt_ctl);
|
|
if (ret)
|
|
return -1;
|
|
|
|
memset(bbt_ctl, 0, sizeof(struct bad_block_ctl));
|
|
ret = get_nand_part_bad_block(mtd, (u8 *)bbt_ctl);
|
|
if (ret)
|
|
return -1;
|
|
|
|
ret = check_nand_part_bad_block(bbt_ctl);
|
|
if (!ret) {
|
|
ret = cmp_mtd_and_bad_block(spinand->bbt_buf, bbt_ctl);
|
|
if (!ret)
|
|
pr_notice("bbt part data is already up to date!\n");
|
|
}
|
|
|
|
kfree(bbt_ctl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_nand_bbt(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
int ret = 0;
|
|
struct bad_block_ctl *bad_block_buf;
|
|
struct mtd_info *mtd;
|
|
struct udevice *dev;
|
|
struct spinand_device *spinand;
|
|
|
|
if (argc > 1)
|
|
return CMD_RET_USAGE;
|
|
|
|
bad_block_buf = kmalloc(sizeof(*bad_block_buf), GFP_KERNEL);
|
|
if (!bad_block_buf) {
|
|
pr_err("Kmalloc bad block buf failed.\n");
|
|
return CMD_RET_FAILURE;
|
|
}
|
|
|
|
memset((char *)bad_block_buf, 0, sizeof(*bad_block_buf));
|
|
|
|
ret = uclass_first_device(UCLASS_MTD, &dev);
|
|
if (ret && !dev) {
|
|
pr_err("Find MTD device failed.\n");
|
|
ret = -ENODEV;
|
|
goto get_mtd_device_err;
|
|
}
|
|
|
|
device_probe(dev);
|
|
|
|
mtd = get_mtd_device(NULL, 0);
|
|
if (IS_ERR_OR_NULL(mtd)) {
|
|
pr_err("There is no mtd device.\n");
|
|
goto get_mtd_device_err;
|
|
}
|
|
|
|
spinand = mtd_to_spinand(mtd);
|
|
|
|
spinand->bbt_buf = kmalloc(sizeof(*spinand->bbt_buf), GFP_KERNEL);
|
|
if (!spinand->bbt_buf) {
|
|
pr_err("Kmalloc bbt buf failed.\n");
|
|
goto kmalloc_bbt_buf_err;
|
|
}
|
|
|
|
memset((char *)spinand->bbt_buf, 0, sizeof(*spinand->bbt_buf));
|
|
|
|
/* save bad block information from spinand to buf */
|
|
get_mtd_device_bad_block(mtd);
|
|
|
|
ret = get_nand_part_bad_block(mtd, (u8 *)bad_block_buf);
|
|
if (ret)
|
|
goto get_nand_part_err;
|
|
|
|
ret = check_nand_part_bad_block(bad_block_buf);
|
|
if (!ret) {
|
|
ret = cmp_mtd_and_bad_block(spinand->bbt_buf, bad_block_buf);
|
|
if (!ret) {
|
|
pr_notice("bbt part data is already up to date!\n");
|
|
goto get_nand_part_err;
|
|
}
|
|
pr_notice("find new bad block in spinand!\n");
|
|
}
|
|
|
|
pr_notice("data in bbt part is not valid!\n");
|
|
|
|
update_nand_part_bad_block(spinand->bbt_buf, bad_block_buf);
|
|
|
|
ret = set_nand_part_bad_block(mtd, bad_block_buf);
|
|
if (ret)
|
|
goto get_nand_part_err;
|
|
|
|
memset(bad_block_buf, 0, sizeof(struct bad_block_ctl));
|
|
|
|
ret = get_nand_part_bad_block(mtd, (u8 *)bad_block_buf);
|
|
if (ret)
|
|
goto get_nand_part_err;
|
|
|
|
ret = check_nand_part_bad_block(bad_block_buf);
|
|
if (!ret) {
|
|
ret = cmp_mtd_and_bad_block(spinand->bbt_buf, bad_block_buf);
|
|
if (!ret) {
|
|
pr_notice("bbt part data is already up to date!\n");
|
|
goto get_nand_part_err;
|
|
}
|
|
}
|
|
|
|
pr_err("spinand write or read fail!\n");
|
|
|
|
get_nand_part_err:
|
|
kfree(spinand->bbt_buf);
|
|
kmalloc_bbt_buf_err:
|
|
put_mtd_device(mtd);
|
|
get_mtd_device_err:
|
|
kfree(bad_block_buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
U_BOOT_CMD(nand_bbt, 1, 0, do_nand_bbt,
|
|
"fresh all the bad block information to the nand partition bbt",
|
|
"\n"
|
|
);
|
|
#endif
|