linuxOS_D21X/source/uboot-2021.10/common/spl/spl_spi_nand.c
2024-11-29 16:13:46 +08:00

370 lines
9.2 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2011 OMICRON electronics GmbH
*
* based on drivers/mtd/nand/raw/nand_spl_load.c
*
* Copyright (C) 2011
* Heiko Schocher, DENX Software Engineering, hs@denx.de.
*
* Copyright (c) 2021, ArtInChip Technology Co., Ltd
* Author: Dehuang Wu <dehuang.wu@artinchip.com>
*/
#include <common.h>
#include <config.h>
#include <dm.h>
#include <dm/device-internal.h>
#include <image.h>
#include <spi.h>
#include <mtd.h>
#include <spi_flash.h>
#include <errno.h>
#include <spl.h>
#include <nand.h>
#include <ubispl.h>
#include <spl.h>
#include <asm/arch/boot_param.h>
DECLARE_GLOBAL_DATA_PTR;
static struct mtd_info *spi_mtd;
#ifdef CONFIG_SPL_UBI
static u8 *scratch_buf;
#endif
static struct mtd_info *spl_spi_nand_init(void)
{
struct mtd_info *mtd;
struct udevice *dev;
int err = -1;
if (spi_mtd)
return spi_mtd;
err = uclass_first_device(UCLASS_MTD, &dev);
if (err && !dev) {
pr_err("Find MTD device failed.\n");
return NULL;
}
device_probe(dev);
mtd = get_mtd_device(NULL, 0);
if (!mtd) {
pr_err("Get SPI NAND mtd device failed.\n");
return NULL;
}
spi_mtd = mtd;
return spi_mtd;
}
#ifdef CONFIG_SPL_UBI
/**
* nand_spl_read_block - Read data from physical eraseblock into a buffer
* @block: Number of the physical eraseblock
* @offset: Data offset from the start of @peb
* @len: Data size to read
* @dst: Address of the destination buffer
*
* This could be further optimized if we'd have a subpage read
* function in the simple code. On NAND which allows subpage reads
* this would spare quite some time to readout e.g. the VID header of
* UBI.
*
* Notes:
* @offset + @len are not allowed to be larger than a physical
* erase block. No sanity check done for simplicity reasons.
*
* To support runtime detected flash this needs to be extended by
* information about the actual flash geometry, but thats beyond the
* scope of this effort and for most applications where fast boot is
* required it is not an issue anyway.
*/
int nand_spl_read_block(int block, int offset, int len, void *dst)
{
int page, pagesize, ret, off_in_page, read;
struct mtd_info *mtd = spi_mtd;
size_t rdlen;
pagesize = mtd->writesize;
if (!scratch_buf) {
scratch_buf = malloc(pagesize);
if (!scratch_buf) {
pr_err("Malloc scratch buffer failed.\n");
return -1;
}
}
/* Calculate the real offset from start */
offset = block * mtd->erasesize + offset;
page = offset / pagesize;
/* Offset to the start of a flash page */
off_in_page = offset % pagesize;
while (len) {
/*
* Non page aligned reads go to the scratch buffer.
* Page aligned reads go directly to the destination.
*/
if (off_in_page || len < pagesize) {
ret = mtd_read(mtd, (page * pagesize), pagesize, &rdlen,
scratch_buf);
if (ret) {
pr_err("Failure while reading at offset 0x%x\n",
page * pagesize);
break;
}
read = min(len, pagesize - off_in_page);
memcpy(dst, scratch_buf + off_in_page, read);
off_in_page = 0;
} else {
ret = mtd_read(mtd, (page * pagesize), pagesize, &rdlen,
dst);
if (ret) {
pr_err("Failure while reading at offset 0x%x\n",
page * pagesize);
break;
}
read = (int)rdlen;
}
page++;
len -= read;
dst += read;
}
return 0;
}
int spl_ubi_load_image(struct spl_image_info *spl_image,
struct spl_boot_device *bootdev)
{
struct image_header *header;
struct ubispl_info info;
struct ubispl_load volumes[2];
struct mtd_info *mtd;
int ret = 1;
mtd = spl_spi_nand_init();
if (IS_ERR_OR_NULL(mtd)) {
pr_err("SPI NAND init failed for UBI load image.\n");
return -1;
}
switch (bootdev->boot_device) {
case BOOT_DEVICE_SPI:
info.read = nand_spl_read_block;
info.peb_size = mtd->erasesize;
break;
default:
goto out;
}
info.ubi = (struct ubi_scan_info *)CONFIG_SPL_UBI_INFO_ADDR;
info.fastmap = IS_ENABLED(CONFIG_MTD_UBI_FASTMAP);
info.peb_offset = CONFIG_SPL_UBI_PEB_OFFSET;
info.vid_offset = CONFIG_SPL_UBI_VID_OFFSET;
info.leb_start = CONFIG_SPL_UBI_LEB_START;
info.peb_count = CONFIG_SPL_UBI_MAX_PEBS - info.peb_offset;
#ifdef CONFIG_SPL_OS_BOOT
if (!spl_start_uboot()) {
volumes[0].vol_id = CONFIG_SPL_UBI_LOAD_KERNEL_ID;
volumes[0].load_addr = (void *)CONFIG_SYS_LOAD_ADDR;
volumes[1].vol_id = CONFIG_SPL_UBI_LOAD_ARGS_ID;
volumes[1].load_addr = (void *)CONFIG_SYS_SPL_ARGS_ADDR;
ret = ubispl_load_volumes(&info, volumes, 2);
if (!ret) {
header = (struct image_header *)volumes[0].load_addr;
spl_parse_image_header(spl_image, header);
puts("Linux loaded.\n");
goto out;
}
puts("Loading Linux failed, falling back to U-Boot.\n");
}
#endif
header = spl_get_load_buffer(-sizeof(*header), sizeof(header));
#ifdef CONFIG_SPL_UBI_LOAD_BY_VOLNAME
volumes[0].vol_id = -1;
strncpy(volumes[0].name,
CONFIG_SPL_UBI_LOAD_MONITOR_VOLNAME,
UBI_VOL_NAME_MAX + 1);
#else
volumes[0].vol_id = CONFIG_SPL_UBI_LOAD_MONITOR_ID;
#endif
volumes[0].load_addr = (void *)header;
ret = ubispl_load_volumes(&info, volumes, 1);
if (!ret)
spl_parse_image_header(spl_image, header);
out:
return ret;
}
#else
static bool aligned_with_block_size(struct mtd_info *mtd, u64 size)
{
return !do_div(size, mtd->erasesize);
}
static int spl_spi_nand_read(struct mtd_info *mtd, size_t from, size_t len,
size_t *retlen, u_char *buf)
{
size_t remaining, off, rdlen;
int ret = 0;
/* Search for the first good block after the given offset */
off = from;
remaining = len;
while (mtd_block_isbad(mtd, off))
off += mtd->erasesize;
while (remaining) {
/*
* aligned with block size and skip bad block
*/
if (aligned_with_block_size(mtd, off) &&
mtd_block_isbad(mtd, off)) {
off += mtd->erasesize;
continue;
}
/* Read one block per loop, so that it can skip bad blocks */
if (remaining > mtd->erasesize)
rdlen = mtd->erasesize;
else
rdlen = remaining;
ret = mtd_read(mtd, off, rdlen, &rdlen, buf);
if (ret) {
pr_err("Failure while reading at offset 0x%lx\n",
(unsigned long)off);
break;
}
off += rdlen;
remaining -= rdlen;
buf += rdlen;
}
*retlen = len - remaining;
return ret;
}
static ulong spl_spi_nand_fit_read(struct spl_load_info *load, ulong offs,
ulong size, void *dst)
{
struct mtd_info *mtd;
size_t retlen;
int ret;
mtd = (struct mtd_info *)load->dev;
ret = spl_spi_nand_read(mtd, offs, size, &retlen, (u_char *)dst);
if (!ret)
return (ulong)retlen;
else
return 0;
}
static int spl_spi_nand_load_image(struct spl_image_info *spl_image,
struct spl_boot_device *bootdev)
{
ulong offset = CONFIG_SYS_SPI_NAND_U_BOOT_OFFS;
int *src __attribute__((unused));
int *dst __attribute__((unused));
struct image_header *header;
struct mtd_info *mtd;
size_t retlen;
int err = -1;
mtd = spl_spi_nand_init();
if (IS_ERR_OR_NULL(mtd)) {
pr_err("MTD SPI NAND init failed., ret = %ld\n", PTR_ERR(mtd));
return err;
}
header = spl_get_load_buffer(-sizeof(*header), sizeof(*header));
#ifdef CONFIG_SPL_OS_BOOT
if (!spl_start_uboot()) {
/*
* load parameter image
* load to temp position since nand_spl_load_image reads
* a whole block which is typically larger than
* CONFIG_CMD_SPL_WRITE_SIZE therefore may overwrite
* following sections like BSS
*/
spl_spi_nand_read(mtd, CONFIG_CMD_SPL_NAND_OFS,
CONFIG_CMD_SPL_WRITE_SIZE, &retlen,
(u_char *)CONFIG_SYS_TEXT_BASE);
/* copy to destintion */
dst = (int *)CONFIG_SYS_SPL_ARGS_ADDR;
src = (int *)CONFIG_SYS_TEXT_BASE;
memcpy(dst, src, CONFIG_CMD_SPL_WRITE_SIZE);
/* load linux */
spl_spi_nand_read(mtd, CONFIG_SYS_NAND_SPL_KERNEL_OFFS,
sizeof(*header), &retlen, (u_char *)header);
err = spl_parse_image_header(spl_image, header);
if (err)
return err;
if (header->ih_os == IH_OS_LINUX) {
/* happy - was a linux */
err = spl_spi_nand_read(mtd,
CONFIG_SYS_NAND_SPL_KERNEL_OFFS,
spl_image->size, &retlen,
(u_char *)spl_image->load_addr);
/* Double check after linux image is loaded. */
if (spl_start_uboot())
goto force_uboot;
return err;
} else {
puts("The Expected Linux image was not "
"found. Please check your NAND "
"configuration.\n");
puts("Trying to start u-boot now...\n");
}
}
force_uboot:
#endif
offset = CONFIG_SYS_SPI_NAND_U_BOOT_OFFS;
err = spl_spi_nand_read(mtd, offset, sizeof(*header), &retlen,
(u_char *)header);
if (err) {
debug("Read image header failed.\n");
return err;
}
if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) &&
image_get_magic(header) == FDT_MAGIC) {
struct spl_load_info load;
debug("Found FIT\n");
load.dev = (void *)mtd;
load.priv = NULL;
load.filename = NULL;
load.bl_len = 1;
load.read = spl_spi_nand_fit_read;
return spl_load_simple_fit(spl_image, &load, offset, header);
} else {
err = spl_parse_image_header(spl_image, header);
if (err) {
debug("spl_parse_image_header failed.\n");
return err;
}
err = spl_spi_nand_read(mtd, offset, spl_image->size, &retlen,
(u_char *)spl_image->load_addr);
return err;
}
}
#endif
#ifdef CONFIG_SPL_UBI
/* Use priorty 0 to override other SPI device when this device is enabled. */
SPL_LOAD_IMAGE_METHOD("SPINAND_UBI", 0, BOOT_DEVICE_SPI, spl_ubi_load_image);
#else
SPL_LOAD_IMAGE_METHOD("SPINAND", 0, BOOT_DEVICE_SPI, spl_spi_nand_load_image);
#endif