From 114eb362d8363e7fefe2947a34c6218fd7341a21 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sat, 17 Oct 2026 10:55:57 +0300 Subject: [PATCH 1/3] xmsfc: add SPI NAND on the v2 controller (XM530/XM550) The XM530/XM550 flash controller also drives SPI NAND: OP1 runs page read, program execute and block erase with the row in ADDR, OP2 the reset, and OP3/OP4 move the ID, the features and a whole page through the controller's 4 KiB RAM at its column offsets. Ported from the vendor U-Boot 2014.04 xmsfc_v2_nand.c as a legacy raw NAND driver (NAND_XMSFC): - cmdfunc on whole commands instead of the vendor's byte-by-byte cmd_ctrl assembly; - the parts of the vendor table (W25N01GV, W25N02GV, GD5F1GQ4U) as full-ID nand_flash_dev entries for nand_scan_ident(), instead of the vendor's patch to nand_base.c; - the part's on-die ECC result (feature C0) reported through ecc.read_page as corrected bitflips or a failure, where the vendor ran NAND_ECC_NONE; - feature A0 cleared before each program and erase, as the vendor does. Under qemu-hisilicon's NAND model: the 128 MiB part is detected, the environment saves to NAND, UBI attaches and a UBIFS rootfs mounts. --- drivers/mtd/nand/raw/Kconfig | 9 + drivers/mtd/nand/raw/Makefile | 1 + drivers/mtd/nand/raw/xmsfc_nand.c | 393 ++++++++++++++++++++++++++++++ 3 files changed, 403 insertions(+) create mode 100644 drivers/mtd/nand/raw/xmsfc_nand.c diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index ce67d1abde25..a83de6db7ada 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -446,6 +446,15 @@ config NAND_MXIC help This selects the Macronix raw NAND controller driver. +config NAND_XMSFC + bool "Xiongmai xmsfc v2 SPI NAND" + depends on ARCH_XM && XM_SFC_V2 + select SYS_NAND_SELF_INIT + help + SPI NAND behind the XM530/XM550 flash controller, as a raw NAND + device: the controller runs the SPI NAND commands, the part's + on-die ECC corrects. + config NAND_ZYNQ bool "Support for Zynq Nand controller" select SPL_SYS_NAND_SELF_INIT diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index a398aa9d8864..6aa79e17385f 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_NAND_PLAT) += nand_plat.o obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o obj-$(CONFIG_NAND_MXIC) += mxic_nand.o obj-$(CONFIG_NAND_ZYNQ) += zynq_nand.o +obj-$(CONFIG_NAND_XMSFC) += xmsfc_nand.o obj-$(CONFIG_NAND_STM32_FMC2) += stm32_fmc2_nand.o obj-$(CONFIG_CORTINA_NAND) += cortina_nand.o obj-$(CONFIG_ROCKCHIP_NAND) += rockchip_nfc.o diff --git a/drivers/mtd/nand/raw/xmsfc_nand.c b/drivers/mtd/nand/raw/xmsfc_nand.c new file mode 100644 index 000000000000..da27ad876c11 --- /dev/null +++ b/drivers/mtd/nand/raw/xmsfc_nand.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SPI NAND on the Xiongmai XM530/XM550 flash controller (xmsfc v2). + * + * The controller runs SPI NAND commands itself through four operations: + * OP1 an addressed command without data (page read to the chip's cache, + * program execute, block erase, the row in ADDR), OP2 a bare command + * (reset), OP3 a read into the 4 KiB RAM at 0x100F6000 or R_SR_ID, OP4 a + * write out of the RAM or SRW. A page and its spare area pass through the + * RAM whole, at their column offsets. + * + * From the vendor U-Boot 2014.04 drivers/mtd/nand/xmsfc_v2_nand.c, as a + * legacy raw NAND driver: cmdfunc instead of the vendor's byte-by-byte + * cmd_ctrl, a full-ID table instead of a patch to nand_base.c, and the + * part's on-die ECC status (feature C0) reported through ecc.read_page. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define V2_CTRL (XM_SFC_BASE + 0x00) +#define V2_CFG1 (XM_SFC_BASE + 0x04) +#define V2_CFG2 (XM_SFC_BASE + 0x08) +#define V2_ADDR (XM_SFC_BASE + 0x0C) +#define V2_OP (XM_SFC_BASE + 0x10) +#define V2_CMD (XM_SFC_BASE + 0x14) +#define V2_SRW (XM_SFC_BASE + 0x18) +#define V2_RW_NUM (XM_SFC_BASE + 0x1C) +#define V2_TDEL (XM_SFC_BASE + 0x28) +#define V2_ST (XM_SFC_BASE + 0x2C) +#define V2_R_SR_ID (XM_SFC_BASE + 0x30) +#define V2_RAM_SIZE 0x1000 + +/* + * CFG1: bit 0 WREN first, bit 1 the reset delay, bit 2 address, bit 3 + * dummy byte. CFG2: bit 0 data through the RAM, bit 1 poll until ready. + */ +#define CFG1_WREN BIT(0) +#define CFG1_TDEL BIT(1) +#define CFG1_ADDR BIT(2) +#define CFG1_DUMMY BIT(3) +#define CFG2_RAM BIT(0) +#define CFG2_POLL BIT(1) + +#define SPINAND_GET_FEATURE 0x0F +#define SPINAND_SET_FEATURE 0x1F +#define SPINAND_PROG_EXEC 0x10 +#define SPINAND_PAGE_READ 0x13 +#define SPINAND_PROG_LOAD 0x02 +#define SPINAND_READ_CACHE 0x03 +#define SPINAND_READ_ID 0x9F +#define SPINAND_BLOCK_ERASE 0xD8 +#define SPINAND_RESET 0xFF + +#define FEAT_PROT 0xA0 +#define FEAT_STATUS 0xC0 +#define STATUS_OIP BIT(0) +#define STATUS_E_FAIL BIT(2) +#define STATUS_P_FAIL BIT(3) +#define STATUS_ECC_MASK (3 << 4) +#define STATUS_ECC_FIXED BIT(4) + +/* The NAND bus: pads 93, 94 and 96-99 (NOR uses 9-14) */ +static const u8 xmsfc_nand_pads[] = { 93, 94, 96, 97, 98, 99 }; + +/* Parts in the vendor table (xmsfc_nand_params.c), by their full ID */ +static struct nand_flash_dev xmsfc_nand_ids[] = { + { "W25N01GV", { .id = { 0xef, 0xaa, 0x21 } }, + SZ_2K, SZ_128, SZ_128K, 0, 3, 64 }, + { "W25N02GV", { .id = { 0xef, 0xab, 0x21 } }, + SZ_2K, SZ_256, SZ_128K, 0, 3, 64 }, + { "GD5F1GQ4U", { .id = { 0xc8, 0xd1 } }, + SZ_2K, SZ_128, SZ_128K, 0, 2, 128 }, + { NULL } +}; + +struct xmsfc_nand { + struct nand_chip chip; + u8 buf[V2_RAM_SIZE]; /* the page and its spare area */ + unsigned int pos; /* read_byte/read_buf/write_buf position */ + int page; /* page of the pending program */ + u8 id[8]; + u8 status; /* feature C0 after the last page read */ + unsigned int last_cmd; +}; + +static struct xmsfc_nand xmsfc_nand; + +static void xmsfc_run(u32 op) +{ + writel(op, V2_OP); + while (!(readl(V2_ST) & BIT(op - 1))) + ; +} + +static void xmsfc_bus_mode(void) +{ + writel(CFG1_ADDR, V2_CFG1); + writel(CFG2_POLL, V2_CFG2); +} + +static u8 xmsfc_get_feature(u8 reg) +{ + writel(CFG1_ADDR, V2_CFG1); + writel(0, V2_CFG2); + writel(SPINAND_GET_FEATURE, V2_CMD); + writel(reg, V2_ADDR); + writel(0, V2_RW_NUM); + xmsfc_run(3); + return readl(V2_R_SR_ID) & 0xff; +} + +static void xmsfc_set_feature(u8 reg, u8 val) +{ + writel(CFG1_ADDR, V2_CFG1); + writel(0, V2_CFG2); + writel(SPINAND_SET_FEATURE, V2_CMD); + writel(reg, V2_ADDR); + writel(val, V2_SRW); + writel(0, V2_RW_NUM); + xmsfc_run(4); +} + +static void xmsfc_wait_ready(void) +{ + int i; + + for (i = 0; i < 100000; i++) { + if (!(xmsfc_get_feature(FEAT_STATUS) & STATUS_OIP)) + return; + udelay(1); + } +} + +/* An addressed command without data: page read, program execute, erase */ +static void xmsfc_row_cmd(u8 cmd, u32 row, u32 cfg1) +{ + writel(cfg1, V2_CFG1); + writel(CFG2_POLL, V2_CFG2); + writel(cmd, V2_CMD); + writel(row, V2_ADDR); + xmsfc_run(1); + xmsfc_wait_ready(); +} + +static void xmsfc_read_page(struct mtd_info *mtd, int page) +{ + struct xmsfc_nand *host = &xmsfc_nand; + const unsigned int len = mtd->writesize + mtd->oobsize; + + xmsfc_row_cmd(SPINAND_PAGE_READ, page, CFG1_ADDR); + host->status = xmsfc_get_feature(FEAT_STATUS); + + writel(CFG1_ADDR | CFG1_DUMMY, V2_CFG1); + writel(CFG2_RAM, V2_CFG2); + writel(SPINAND_READ_CACHE, V2_CMD); + writel(0, V2_ADDR); + writel(len - 1, V2_RW_NUM); + xmsfc_run(3); + memcpy_fromio(host->buf, (void *)XM_SFC_RAM, len); + xmsfc_bus_mode(); +} + +static int xmsfc_program_page(struct mtd_info *mtd, int page) +{ + struct xmsfc_nand *host = &xmsfc_nand; + const unsigned int len = mtd->writesize + mtd->oobsize; + unsigned int i; + u8 status; + + /* Unprotect, as the vendor does before every program and erase */ + xmsfc_set_feature(FEAT_PROT, 0); + + for (i = 0; i < len; i += 4) + writel(get_unaligned_le32(host->buf + i), XM_SFC_RAM + i); + writel(CFG1_WREN | CFG1_ADDR, V2_CFG1); + writel(CFG2_RAM, V2_CFG2); + writel(SPINAND_PROG_LOAD, V2_CMD); + writel(0, V2_ADDR); + writel(len - 1, V2_RW_NUM); + xmsfc_run(4); + + xmsfc_row_cmd(SPINAND_PROG_EXEC, page, CFG1_ADDR); + status = xmsfc_get_feature(FEAT_STATUS); + xmsfc_bus_mode(); + return status & STATUS_P_FAIL ? NAND_STATUS_FAIL : 0; +} + +static int xmsfc_erase_block(int page) +{ + u8 status; + + xmsfc_set_feature(FEAT_PROT, 0); + xmsfc_row_cmd(SPINAND_BLOCK_ERASE, page, CFG1_WREN | CFG1_ADDR); + status = xmsfc_get_feature(FEAT_STATUS); + xmsfc_bus_mode(); + return status & STATUS_E_FAIL ? NAND_STATUS_FAIL : 0; +} + +static void xmsfc_read_id(void) +{ + struct xmsfc_nand *host = &xmsfc_nand; + u32 id; + + /* 9F, one address (dummy) byte, three ID bytes: first byte highest */ + writel(CFG1_ADDR, V2_CFG1); + writel(0, V2_CFG2); + writel(SPINAND_READ_ID, V2_CMD); + writel(0, V2_ADDR); + writel(2, V2_RW_NUM); + xmsfc_run(3); + id = readl(V2_R_SR_ID); + memset(host->id, 0, sizeof(host->id)); + host->id[0] = id >> 16; + host->id[1] = id >> 8; + host->id[2] = id; + xmsfc_bus_mode(); +} + +static void xmsfc_reset(void) +{ + writel(CFG1_TDEL, V2_CFG1); + writel(0, V2_CFG2); + writel(0x19700, V2_TDEL); + writel(SPINAND_RESET, V2_CMD); + xmsfc_run(2); + xmsfc_wait_ready(); + xmsfc_bus_mode(); +} + +static int xmsfc_status; + +static void xmsfc_cmdfunc(struct mtd_info *mtd, unsigned int command, + int column, int page) +{ + struct xmsfc_nand *host = &xmsfc_nand; + + host->last_cmd = command; + switch (command) { + case NAND_CMD_RESET: + xmsfc_reset(); + break; + case NAND_CMD_READID: + xmsfc_read_id(); + host->pos = 0; + break; + case NAND_CMD_PARAM: + /* No ONFI parameter page on SPI NAND */ + memset(host->buf, 0, 256); + host->pos = 0; + break; + case NAND_CMD_READ0: + xmsfc_read_page(mtd, page); + host->pos = column; + break; + case NAND_CMD_READOOB: + xmsfc_read_page(mtd, page); + host->pos = mtd->writesize + column; + break; + case NAND_CMD_SEQIN: + memset(host->buf, 0xff, mtd->writesize + mtd->oobsize); + host->page = page; + host->pos = column; + break; + case NAND_CMD_PAGEPROG: + xmsfc_status = xmsfc_program_page(mtd, host->page); + break; + case NAND_CMD_ERASE1: + xmsfc_status = xmsfc_erase_block(page); + break; + case NAND_CMD_ERASE2: + case NAND_CMD_STATUS: + break; + default: + printf("xmsfc-nand: command 0x%x not supported\n", command); + break; + } +} + +static u8 xmsfc_read_byte(struct mtd_info *mtd) +{ + struct xmsfc_nand *host = &xmsfc_nand; + + switch (host->last_cmd) { + case NAND_CMD_READID: + return host->pos < sizeof(host->id) ? host->id[host->pos++] : 0; + case NAND_CMD_STATUS: + /* Write protect off, ready, and the last program/erase result */ + return NAND_STATUS_WP | NAND_STATUS_READY | xmsfc_status; + default: + return host->pos < sizeof(host->buf) ? host->buf[host->pos++] : 0xff; + } +} + +static void xmsfc_read_buf(struct mtd_info *mtd, u8 *buf, int len) +{ + struct xmsfc_nand *host = &xmsfc_nand; + + len = min_t(int, len, sizeof(host->buf) - host->pos); + memcpy(buf, host->buf + host->pos, len); + host->pos += len; +} + +static void xmsfc_write_buf(struct mtd_info *mtd, const u8 *buf, int len) +{ + struct xmsfc_nand *host = &xmsfc_nand; + + len = min_t(int, len, sizeof(host->buf) - host->pos); + memcpy(host->buf + host->pos, buf, len); + host->pos += len; +} + +static int xmsfc_waitfunc(struct mtd_info *mtd, struct nand_chip *chip) +{ + return NAND_STATUS_WP | NAND_STATUS_READY | xmsfc_status; +} + +static void xmsfc_select_chip(struct mtd_info *mtd, int cs) +{ +} + +/* The part corrects on die; report what feature C0 said about the page */ +static int xmsfc_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip, + u8 *buf, int oob_required, int page) +{ + struct xmsfc_nand *host = &xmsfc_nand; + + chip->read_buf(mtd, buf, mtd->writesize); + if (oob_required) + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + switch (host->status & STATUS_ECC_MASK) { + case 0: + return 0; + case STATUS_ECC_FIXED: + mtd->ecc_stats.corrected++; + return 1; + default: + mtd->ecc_stats.failed++; + return 0; + } +} + +static int xmsfc_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const u8 *buf, int oob_required, int page) +{ + chip->write_buf(mtd, buf, mtd->writesize); + if (oob_required) + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + return 0; +} + +void board_nand_init(void) +{ + struct nand_chip *chip = &xmsfc_nand.chip; + struct mtd_info *mtd = nand_to_mtd(chip); + int i; + + for (i = 0; i < ARRAY_SIZE(xmsfc_nand_pads); i++) + writel(XM_GPIO_DRIVE_8MA | XM_GPIO_MUX2_EN, + XM_GPIO_PAD(xmsfc_nand_pads[i])); + writel(0x00, V2_CTRL); /* NAND */ + + chip->cmdfunc = xmsfc_cmdfunc; + chip->read_byte = xmsfc_read_byte; + chip->read_buf = xmsfc_read_buf; + chip->write_buf = xmsfc_write_buf; + chip->waitfunc = xmsfc_waitfunc; + chip->select_chip = xmsfc_select_chip; + chip->options = NAND_NO_SUBPAGE_WRITE; + + if (nand_scan_ident(mtd, 1, xmsfc_nand_ids)) + return; + + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.size = mtd->writesize; + chip->ecc.bytes = 0; + chip->ecc.strength = 1; + chip->ecc.read_page = xmsfc_ecc_read_page; + chip->ecc.write_page = xmsfc_ecc_write_page; + + if (nand_scan_tail(mtd)) + return; + nand_register(0, mtd); +} From c8787f56493759116ba76c4b22b5ebbfc9a083fe Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sat, 17 Oct 2026 10:55:57 +0300 Subject: [PATCH 2/3] openipc: build XM530 and XM550 SPI NAND images openipc/nand_config, appended for NAND images, swaps the NOR driver for NAND_XMSFC, puts the environment at 0xc0000 and enables MTD partitions, UBI and UBIFS -- the layout and recovery of openipc-hisilicon's NAND targets: 768k(boot), 256k(env), the rest one UBI device with the kernel at /boot/uImage inside the UBIFS rootfs. build.sh builds xm530 and xm550 twice, into output/u-boot--{nor,nand}.bin. How the XM530 boots from SPI NAND is not documented, and Xiongmai never built a NAND U-Boot from this SDK. The image follows the only evidence, the vendor xm530.h's unused SPI NAND block: U-Boot is linked at 0x60001000, the controller window, behind a 4 KiB header, which here starts with an absolute jump to it, so the image reaches U-Boot whether the ROM enters at the window or at its 0x08000000 alias. Unverified on silicon; CLAUDE.md says so. The images are 372 KiB of the 768 KiB boot partition. Under qemu-hisilicon's NAND model, which implements the same guess: xm530 and xm550 boot to the prompt, load /boot/uImage from a UBIFS rootfs and start the kernel with mem= first; from an erased NAND, TFTP recovery (ur) creates the UBI volumes, writes the rootfs and boots it after the reset. The OpenIPC XM530 kernel itself has no SPI NAND driver, so it cannot mount that root yet. --- CLAUDE.md | 46 +++++++++++++++++++++++++++++++++++++---- build.sh | 34 ++++++++++++++++++++++++------ include/configs/xm530.h | 3 +++ openipc/nand_config | 26 +++++++++++++++++++++++ 4 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 openipc/nand_config diff --git a/CLAUDE.md b/CLAUDE.md index 32fdc36a5e40..e37177571f31 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,6 +42,7 @@ git diff --diff-filter=M v2022.10 HEAD # the mainline files we patched | SoC: PLL and DDR bring-up, timer, reset, DRAM size, SDHCI glue | `arch/arm/mach-xm/` (`ARCH_XM`; `TARGET_XM510`/`XM530`/`XM550`; register map in `include/mach/xm.h`) | | Boards: `board_init`, `misc_init_r` → `openipc_helper()`, `board_eth_init` with the PHY reset / MAC glue | `board/xiongmai/{xm510,xm530}/`. XM550 uses the xm530 board with `XM_DDR3` | | Memory map, console, timer | `include/configs/{xm510,xm530}.h`, each ending in `#include "openipc-common.h"` | +| SPI NAND on the v2 controller (XM530/550): raw NAND, full-ID table, on-die ECC status through `ecc.read_page` | `drivers/mtd/nand/raw/xmsfc_nand.c` (`NAND_XMSFC`) | | SPI NOR controller, v1 (XM510) and v2 (XM530/550), non-DM | `drivers/mtd/spi/xmsfc.c`. It provides `spi_flash_probe()` and fills `mtd->_read/_write/_erase`, so mainline's non-DM `spi_flash_*()` wrappers, `sf` and env-in-SPI-flash work without the SPI and spi-nor layers | | OpenIPC boot layer | `common/openipc.c` and `include/openipc.h` (copied from `openipc-hisilicon`, NAND gate rekeyed to `CONFIG_ENV_IS_IN_NAND`), `include/configs/openipc-common.h` (XM-only rewrite, same env commands) | | Kconfig fragments and the build | `openipc/`, `build.sh` | @@ -50,6 +51,7 @@ git diff --diff-filter=M v2022.10 HEAD # the mainline files we patched - `arch/arm/{Kconfig,Makefile}`: the `ARCH_XM` hook. - `common/{Kconfig,Makefile}`: `OPENIPC`, `PRODUCT_SOC`, `PRODUCT_SOCMODEL`. +- `drivers/mtd/nand/raw/{Kconfig,Makefile}`: the `NAND_XMSFC` hook. - `drivers/mtd/spi/{Kconfig,Makefile}`, `cmd/Kconfig` (`CMD_SF`) and `env/Kconfig` (`ENV_IS_IN_SPI_FLASH`): accept `XMSFC_SPI_NOR`. - `drivers/net/Kconfig`: `NETDEVICES` selects `DM_ETH` only when `DM` is on. @@ -94,6 +96,34 @@ position-independent fixups. The only RAM before DDR is a 4 KiB SRAM at driver restores it (`xmsfc_bus_mode()`) after every v2 operation, and only ever runs after relocation, from DDR. +## SPI NAND (XM530/XM550): built on a guess + +Xiongmai never shipped a NAND U-Boot from this SDK. There is a +`CONFIG_SYS_USE_SPI_NAND` block in the vendor `xm530.h`, but no board target, +and nothing documents how the boot ROM starts from SPI NAND. + +**What the NAND image assumes:** + +- the controller's window serves the NAND page data linearly, as in NOR + bus-read mode; +- U-Boot is linked at 0x60001000 (the vendor's NAND `TEXT_BASE`), behind a + 4 KiB header; +- the header's first word is `ldr pc, [pc, #-4]` followed by that address, so + it reaches U-Boot whether the ROM starts at the window or the 0x08000000 + alias (`build.sh`). + +**What it carries:** + +- layout `768k(boot),256k(env),-(ubi)`; +- the environment at 0xc0000; +- the kernel at `/boot/uImage` inside the UBIFS `rootfs` volume; +- the same recovery as on `openipc-hisilicon` NAND. + +Only qemu-hisilicon's NAND model, which implements the same guess, has run +it. The OpenIPC XM530 kernel has no SPI NAND driver either, so a NAND image +loads and starts the kernel, which then cannot mount its root. Treat NAND as +unverified on silicon, and say so wherever it is offered. + ## Build ```sh @@ -101,10 +131,11 @@ bash build.sh # xm510 xm530 xm550; continues past failures, exits bash build.sh xm530 # one or more SoCs (CROSS_COMPILE defaults to arm-linux-gnueabi-) ``` -- Output: `output/u-boot--nor.bin`, built out of tree in - `output/build-/`. -- `.config` is `openipc/openipc_config` followed by `openipc/_config`, then - `olddefconfig`. A later line wins. +- Output: `output/u-boot--nor.bin`, plus `-nand.bin` for xm530/xm550, + built out of tree in `output/build--/`. +- `.config` is `openipc/openipc_config`, then `openipc/_config`, then + `openipc/nand_config` for NAND images, then `olddefconfig`. A later line + wins. The "override" warnings are that layering. - There are no `configs/*_defconfig` for these targets. - The host toolchain must match CI (Ubuntu `gcc-arm-linux-gnueabi`). A Docker image with it works: @@ -131,6 +162,13 @@ with the reset vector at the window or alias, exactly as silicon does. bootloaders leave it, with per-block locks on. `xmsfc_unprotect()` must clear it before writes land. +- **NAND:** `-global xm-sfc.nand-file=`, with no `flash-file`. + - The raw image is pages of 2048 + 64 bytes, U-Boot at page 0, and + `ubinize`'s output at 1 MiB. + - It must reach the prompt, save the env, mount the rootfs and start the + kernel. + - From an erased NAND, TFTP recovery must create the UBI volumes and boot. + **What qemu does not check:** DDR training values, the v2 bus-read restore and PHY/MDIO timing are acked instantly. A change to those needs a real board. diff --git a/build.sh b/build.sh index 143166daad4e..34f7a462453b 100755 --- a/build.sh +++ b/build.sh @@ -27,21 +27,43 @@ mkconfig() { # linked, with no compressed loader in front of it. build_xm() { soc=$1 - builddir=${BOOT_SRCDIR}/output/build-${soc} + kind=$2 + builddir=${BOOT_SRCDIR}/output/build-${soc}-${kind} make_opts="-C ${BOOT_SRCDIR} O=${builddir} -j$(nproc)" + fragments="openipc/openipc_config openipc/${soc}_config" + [ "${kind}" = nand ] && fragments="${fragments} openipc/nand_config" rm -rf "${builddir}" - mkconfig "${builddir}" openipc/openipc_config "openipc/${soc}_config" && \ + mkconfig "${builddir}" ${fragments} && \ make ${make_opts} olddefconfig /dev/null && \ - make ${make_opts} &2 return 1 ;; diff --git a/include/configs/xm530.h b/include/configs/xm530.h index 579123382012..3ee82dea5361 100644 --- a/include/configs/xm530.h +++ b/include/configs/xm530.h @@ -26,6 +26,9 @@ #define CONFIG_SYS_TIMER_COUNTER XM_TIMER_VALUE #define CONFIG_SYS_TIMER_COUNTS_DOWN +/* SPI NAND images (openipc/nand_config) */ +#define CONFIG_SYS_MAX_NAND_DEVICE 1 + #include "openipc-common.h" #endif diff --git a/openipc/nand_config b/openipc/nand_config new file mode 100644 index 000000000000..3662092db64c --- /dev/null +++ b/openipc/nand_config @@ -0,0 +1,26 @@ +# SPI NAND (XM530/XM550): appended after the SoC fragment for NAND images. +# The same controller drives NOR or NAND, so the NOR driver goes. +# CONFIG_XMSFC_SPI_NOR is not set +# CONFIG_CMD_SF is not set +# CONFIG_ENV_IS_IN_SPI_FLASH is not set +# Linked where the vendor links a NAND U-Boot: the v2 window, behind a +# 4 KiB header (build.sh) whose first word jumps here. +CONFIG_SYS_TEXT_BASE=0x60001000 +CONFIG_MTD=y +CONFIG_MTD_RAW_NAND=y +CONFIG_NAND_XMSFC=y +CONFIG_CMD_NAND=y +CONFIG_CMD_NAND_TRIMFFS=y +CONFIG_ENV_IS_IN_NAND=y +CONFIG_ENV_OFFSET=0xc0000 +CONFIG_ENV_SIZE=0x40000 +CONFIG_ENV_SECT_SIZE=0x20000 +# 768 KiB boot partition, less the 4 KiB header build.sh puts in front +CONFIG_BOARD_SIZE_LIMIT=782336 +CONFIG_MTD_PARTITIONS=y +CONFIG_CMD_MTDPARTS=y +CONFIG_MTD_UBI=y +CONFIG_CMD_UBI=y +CONFIG_CMD_UBIFS=y +CONFIG_RBTREE=y +CONFIG_LZO=y From ee9742519e543b9d09d596b05b21030bd0a0cce3 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sat, 17 Oct 2026 12:21:39 +0300 Subject: [PATCH 3/3] xmsfc-nand: time out instead of hanging, and report it Review follow-ups: - every controller operation gets a 100 ms deadline: a stuck controller is reported instead of hanging boot, and a failed ID read leaves no ID, so nand_scan_ident() finds no chip; - the busy poll (feature C0 OIP) also stops at 100 ms and returns an error; a program or erase that times out reports NAND_STATUS_FAIL, and a page read that times out comes back as uncorrectable instead of stale data. Rechecked under qemu-hisilicon's NAND model: xm530/xm550 smoke (two boots, env in NAND) and the erased-NAND TFTP recovery to kernel start. --- drivers/mtd/nand/raw/xmsfc_nand.c | 78 +++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/drivers/mtd/nand/raw/xmsfc_nand.c b/drivers/mtd/nand/raw/xmsfc_nand.c index da27ad876c11..5cebc1936840 100644 --- a/drivers/mtd/nand/raw/xmsfc_nand.c +++ b/drivers/mtd/nand/raw/xmsfc_nand.c @@ -94,11 +94,22 @@ struct xmsfc_nand { static struct xmsfc_nand xmsfc_nand; -static void xmsfc_run(u32 op) +/* + * Run one controller operation; a controller that never finishes is + * reported, not waited on forever + */ +static int xmsfc_run(u32 op) { + ulong start = get_timer(0); + writel(op, V2_OP); - while (!(readl(V2_ST) & BIT(op - 1))) - ; + while (!(readl(V2_ST) & BIT(op - 1))) { + if (get_timer(start) > 100) { + printf("xmsfc-nand: operation %u timed out\n", op); + return -ETIMEDOUT; + } + } + return 0; } static void xmsfc_bus_mode(void) @@ -129,26 +140,34 @@ static void xmsfc_set_feature(u8 reg, u8 val) xmsfc_run(4); } -static void xmsfc_wait_ready(void) +/* + * Wait for the part to finish (feature C0 OIP clear), at most 100 ms -- + * a block erase takes a few + */ +static int xmsfc_wait_ready(void) { - int i; + ulong start = get_timer(0); - for (i = 0; i < 100000; i++) { + do { if (!(xmsfc_get_feature(FEAT_STATUS) & STATUS_OIP)) - return; + return 0; udelay(1); - } + } while (get_timer(start) < 100); + printf("xmsfc-nand: still busy after 100 ms\n"); + return -ETIMEDOUT; } /* An addressed command without data: page read, program execute, erase */ -static void xmsfc_row_cmd(u8 cmd, u32 row, u32 cfg1) +static int xmsfc_row_cmd(u8 cmd, u32 row, u32 cfg1) { + int ret; + writel(cfg1, V2_CFG1); writel(CFG2_POLL, V2_CFG2); writel(cmd, V2_CMD); writel(row, V2_ADDR); - xmsfc_run(1); - xmsfc_wait_ready(); + ret = xmsfc_run(1); + return ret ? ret : xmsfc_wait_ready(); } static void xmsfc_read_page(struct mtd_info *mtd, int page) @@ -156,7 +175,13 @@ static void xmsfc_read_page(struct mtd_info *mtd, int page) struct xmsfc_nand *host = &xmsfc_nand; const unsigned int len = mtd->writesize + mtd->oobsize; - xmsfc_row_cmd(SPINAND_PAGE_READ, page, CFG1_ADDR); + if (xmsfc_row_cmd(SPINAND_PAGE_READ, page, CFG1_ADDR)) { + /* Report the page as uncorrectable rather than stale data */ + memset(host->buf, 0xff, len); + host->status = STATUS_ECC_MASK; + xmsfc_bus_mode(); + return; + } host->status = xmsfc_get_feature(FEAT_STATUS); writel(CFG1_ADDR | CFG1_DUMMY, V2_CFG1); @@ -164,8 +189,12 @@ static void xmsfc_read_page(struct mtd_info *mtd, int page) writel(SPINAND_READ_CACHE, V2_CMD); writel(0, V2_ADDR); writel(len - 1, V2_RW_NUM); - xmsfc_run(3); - memcpy_fromio(host->buf, (void *)XM_SFC_RAM, len); + if (xmsfc_run(3)) { + memset(host->buf, 0xff, len); + host->status = STATUS_ECC_MASK; + } else { + memcpy_fromio(host->buf, (void *)XM_SFC_RAM, len); + } xmsfc_bus_mode(); } @@ -186,9 +215,11 @@ static int xmsfc_program_page(struct mtd_info *mtd, int page) writel(SPINAND_PROG_LOAD, V2_CMD); writel(0, V2_ADDR); writel(len - 1, V2_RW_NUM); - xmsfc_run(4); - - xmsfc_row_cmd(SPINAND_PROG_EXEC, page, CFG1_ADDR); + if (xmsfc_run(4) || + xmsfc_row_cmd(SPINAND_PROG_EXEC, page, CFG1_ADDR)) { + xmsfc_bus_mode(); + return NAND_STATUS_FAIL; + } status = xmsfc_get_feature(FEAT_STATUS); xmsfc_bus_mode(); return status & STATUS_P_FAIL ? NAND_STATUS_FAIL : 0; @@ -199,7 +230,10 @@ static int xmsfc_erase_block(int page) u8 status; xmsfc_set_feature(FEAT_PROT, 0); - xmsfc_row_cmd(SPINAND_BLOCK_ERASE, page, CFG1_WREN | CFG1_ADDR); + if (xmsfc_row_cmd(SPINAND_BLOCK_ERASE, page, CFG1_WREN | CFG1_ADDR)) { + xmsfc_bus_mode(); + return NAND_STATUS_FAIL; + } status = xmsfc_get_feature(FEAT_STATUS); xmsfc_bus_mode(); return status & STATUS_E_FAIL ? NAND_STATUS_FAIL : 0; @@ -216,9 +250,13 @@ static void xmsfc_read_id(void) writel(SPINAND_READ_ID, V2_CMD); writel(0, V2_ADDR); writel(2, V2_RW_NUM); - xmsfc_run(3); - id = readl(V2_R_SR_ID); memset(host->id, 0, sizeof(host->id)); + if (xmsfc_run(3)) { + /* No ID: nand_scan_ident() then finds no chip */ + xmsfc_bus_mode(); + return; + } + id = readl(V2_R_SR_ID); host->id[0] = id >> 16; host->id[1] = id >> 8; host->id[2] = id;