Skip to content

XM530/XM550 SPI NAND: driver and images - #16

Merged
widgetii merged 3 commits into
openipc-xmfrom
xm/spinand
Oct 17, 2026
Merged

widgetii merged 3 commits into
openipc-xmfrom
xm/spinand

Conversation

@widgetii

Copy link
Copy Markdown
Member

SPI NAND for the XM530/XM550, on the same xmsfc v2 controller as NOR.

  • xmsfc: SPI NAND (drivers/mtd/nand/raw/xmsfc_nand.c, NAND_XMSFC): legacy raw NAND ported from the vendor xmsfc_v2_nand.c. Against the vendor driver:

    • cmdfunc on whole commands instead of byte-by-byte cmd_ctrl;
    • a full-ID table (W25N01GV, W25N02GV, GD5F1GQ4U) instead of the vendor's patch to nand_base.c;
    • the part's on-die ECC result (feature C0) reported through ecc.read_page, where the vendor ran NAND_ECC_NONE;
    • feature A0 cleared before each program and erase, as the vendor does.
  • openipc: NAND images. openipc/nand_config gives openipc-hisilicon's NAND layout and recovery:

    • partitions 768k(boot),256k(env),-(ubi), with the env at 0xc0000;
    • the kernel at /boot/uImage inside the UBIFS rootfs;
    • ur recovery that creates the UBI volumes.

    build.sh builds xm530/xm550 as -nor.bin and -nand.bin. BOARD_SIZE_LIMIT is 768 KiB less the header.

Unverified on silicon

Nothing documents how the XM530 boots from SPI NAND, and Xiongmai never built a NAND U-Boot from this SDK. The image follows the only evidence, the vendor xm530.h's unused NAND block: U-Boot is linked at 0x60001000 (the controller window), behind a 4 KiB header. That header starts with an absolute jump to U-Boot, so the image reaches it whether the ROM enters at the window or at its 0x08000000 alias. CLAUDE.md says so. The OpenIPC XM530 kernel also has no SPI NAND driver yet.

Checked

Under qemu-hisilicon's NAND model (u-boot#152), which implements the same guess:

  • xm530 and xm550 boot to the prompt and detect the 128 MiB part;
  • the env survives a reset in NAND;
  • UBI attaches, /boot/uImage loads from UBIFS, and the kernel starts with mem= first;
  • from an erased NAND, TFTP ur creates the UBI volumes, writes the rootfs and boots it after the reset.

NOR images are unchanged. Sizes: NOR about 194 KiB, NAND about 372 KiB.

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.
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-<soc>-{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.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add XM530/XM550 SPI NAND driver and boot images

✨ Enhancement ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add xmsfc v2 SPI NAND support with full-ID detection and on-die ECC reporting.
• Build separate XM530/XM550 NAND images with NAND environment, UBI, and UBIFS boot support.
• Document the unverified boot-ROM assumptions and the missing kernel-side NAND support.
Diagram

graph TD
  F["NAND config"] --> B["Build script"] --> I["NAND image"] --> E["UBI boot"] --> D["NAND driver"] --> C["xmsfc v2"] --> H["SPI NAND"]
  F --> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Integrate through the SPI NAND subsystem
  • ➕ Reuse SPI NAND identification, ECC conventions, and common MTD behavior.
  • ➖ Requires an xmsfc SPI-controller integration rather than the existing non-DM, controller-specific path; increases scope before hardware validation.

Recommendation: The legacy raw NAND adapter is a pragmatic fit for this controller and the existing non-DM platform. Keep the NAND images explicitly experimental: QEMU validates the proposed layout and flow, not the boot ROM's silicon behavior or a kernel-side NAND root mount.

Files changed (7) +502 / -10

Enhancement (2) +421 / -6
build.shBuild distinct NOR and NAND images for XM530/XM550 +28/-6

Build distinct NOR and NAND images for XM530/XM550

• Uses separate build directories and appends the NAND configuration fragment for NAND variants. Prepends a 4 KiB header containing an absolute jump to the NAND-linked U-Boot image; XM510 remains NOR-only.

build.sh

xmsfc_nand.cImplement SPI NAND transactions on xmsfc v2 +393/-0

Implement SPI NAND transactions on xmsfc v2

• Adds a legacy raw NAND adapter for page I/O, erase, identification, and three vendor-listed parts. Uses on-die ECC status for read reporting, clears protection before writes and erases, and registers the detected NAND device.

drivers/mtd/nand/raw/xmsfc_nand.c

Documentation (1) +42 / -4
CLAUDE.mdDocument NAND architecture, image assumptions, and validation limits +42/-4

Document NAND architecture, image assumptions, and validation limits

• Describes the new driver and build variants, the speculative 4 KiB boot header, NAND partition layout, and QEMU checks. Warns that silicon boot is unverified and the OpenIPC XM530 kernel lacks SPI NAND support.

CLAUDE.md

Other (4) +39 / -0
KconfigExpose the xmsfc v2 raw NAND driver +9/-0

Expose the xmsfc v2 raw NAND driver

• Adds NAND_XMSFC for XM platforms with the v2 controller and selects NAND self-initialization.

drivers/mtd/nand/raw/Kconfig

MakefileCompile the xmsfc NAND driver when enabled +1/-0

Compile the xmsfc NAND driver when enabled

• Adds xmsfc_nand.o to the raw NAND build under CONFIG_NAND_XMSFC.

drivers/mtd/nand/raw/Makefile

xm530.hSet the maximum NAND device count for XM530/XM550 +3/-0

Set the maximum NAND device count for XM530/XM550

• Defines one supported NAND device in the shared XM530/XM550 board configuration.

include/configs/xm530.h

nand_configConfigure NAND storage, boot address, and UBI commands +26/-0

Configure NAND storage, boot address, and UBI commands

• Disables NOR support for NAND builds and enables the NAND driver, NAND environment, partitions, UBI, and UBIFS. Sets the 0x60001000 link address, environment offset, and boot-partition size limit.

openipc/nand_config

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. NAND images enter code in the wrong state 🐞 Bug ≡ Correctness
Description
build.sh prepends an ARM-state ldr pc instruction that jumps to the even CONFIG_SYS_TEXT_BASE,
while the linked entry code is built in Thumb state. When the ROM executes that header, the jump
reaches the image entry without switching instruction state, before U-Boot can initialize or
relocate.
Code

build.sh[R53-55]

+hdr = bytearray(b'\xff' * 0x1000)
+hdr[0:8] = struct.pack('<II', 0xe51ff004, text)
+open(dst, 'wb').write(bytes(hdr) + open(src, 'rb').read())
Evidence
The common configuration enables Thumb builds, the ARM build flags select -mthumb, and the image
begins with the assembly entry. The new header instead loads the even text address with an ARM
instruction.

openipc/openipc_config[6-6]
arch/arm/config.mk[43-49]
arch/arm/lib/vectors.S[45-83]
build.sh[48-55]
openipc/nand_config[8-8]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The NAND header jumps in ARM state to a Thumb-built U-Boot entry, so the image cannot execute its entry code correctly.
## Fix Focus Areas
- build.sh[44-55]
- openipc/openipc_config[6-6]
## Recommended Fix
Make the header explicitly transfer into the instruction state used by the linked entry, and verify the resulting entry sequence for both proposed ROM addresses.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. NAND commands can poll the wrong status 🐞 Bug ≡ Correctness
Description
xmsfc_row_cmd() enables controller status polling without setting its poll-command register for
SPI NAND. The v2 NOR driver programs that register before polled operations, so a NAND boot
following a NOR-configured controller can poll a stale NOR opcode during page reads, programming, or
erases.
Code

drivers/mtd/nand/raw/xmsfc_nand.c[R146-150]

+	writel(cfg1, V2_CFG1);
+	writel(CFG2_POLL, V2_CFG2);
+	writel(cmd, V2_CMD);
+	writel(row, V2_ADDR);
+	xmsfc_run(1);
Evidence
The shared v2 controller has a poll-command register that the NOR driver writes before using
CFG2_POLL; the new NAND row path uses the same polling bit but never writes that register. NAND's
status command is distinct from NOR's.

drivers/mtd/spi/xmsfc.c[65-81]
drivers/mtd/spi/xmsfc.c[143-169]
drivers/mtd/nand/raw/xmsfc_nand.c[53-69]
drivers/mtd/nand/raw/xmsfc_nand.c[143-152]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Polled NAND row operations leave the controller's status-poll command at its previous value, which may be a NOR opcode.
## Fix Focus Areas
- drivers/mtd/nand/raw/xmsfc_nand.c[143-152]
- drivers/mtd/spi/xmsfc.c[143-169]
## Recommended Fix
Configure the controller's poll command and required SPI NAND status address before enabling hardware polling, or disable hardware polling and rely on correctly checked software status reads.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Timed-out NAND writes can appear successful ✓ Resolved
Description
xmsfc_wait_ready() returns normally when its polling limit expires, and xmsfc_row_cmd() provides
no timeout result to its callers. If a program or erase remains busy, the driver checks only the
program- or erase-failure bit and can return a successful status to the NAND core; page reads can
likewise consume a cache that is not ready.
Code

drivers/mtd/nand/raw/xmsfc_nand.c[R136-141]

+	for (i = 0; i < 100000; i++) {
+		if (!(xmsfc_get_feature(FEAT_STATUS) & STATUS_OIP))
+			return;
+		udelay(1);
+	}
+}
Evidence
The wait loop has no timeout indication; read, program, and erase continue after it, and
waitfunc() exposes only the cached result. The NAND core relies on that result to detect failed
page programs.

drivers/mtd/nand/raw/xmsfc_nand.c[132-169]
drivers/mtd/nand/raw/xmsfc_nand.c[191-205]
drivers/mtd/nand/raw/xmsfc_nand.c[273-278]
drivers/mtd/nand/raw/xmsfc_nand.c[321-324]
drivers/mtd/nand/raw/nand_base.c[1288-1311]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A NAND operation that remains busy past the software polling limit is reported as complete.
## Fix Focus Areas
- drivers/mtd/nand/raw/xmsfc_nand.c[132-152]
- drivers/mtd/nand/raw/xmsfc_nand.c[154-205]
## Recommended Fix
Return a timeout error from the readiness poll, propagate it through row operations, and report write or erase failures to the NAND core rather than treating a still-busy status as success.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. A stuck controller freezes NAND startup ✓ Resolved
Description
xmsfc_run() waits for the controller completion bit without a deadline or error path. If a NAND
transaction does not complete, including during the ID read used for device detection, U-Boot
remains in this loop rather than returning control or reporting a failed device.
Code

drivers/mtd/nand/raw/xmsfc_nand.c[R99-102]

+	writel(op, V2_OP);
+	while (!(readl(V2_ST) & BIT(op - 1)))
+		;
+}
Evidence
Every NAND controller transaction calls the unbounded loop, and device identification calls it
before NAND registration can finish.

drivers/mtd/nand/raw/xmsfc_nand.c[97-102]
drivers/mtd/nand/raw/xmsfc_nand.c[208-225]
drivers/mtd/nand/raw/xmsfc_nand.c[372-392]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A controller operation that never signals completion traps U-Boot in an unbounded polling loop.
## Fix Focus Areas
- drivers/mtd/nand/raw/xmsfc_nand.c[97-102]
- drivers/mtd/nand/raw/xmsfc_nand.c[208-225]
## Recommended Fix
Give controller completion polling a deadline and propagate its failure through command and detection paths so an unresponsive device is reported rather than hanging startup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. NAND users cannot update the kernel alone 🐞 Bug ≡ Correctness
Description
Enabling CONFIG_ENV_IS_IN_NAND selects a medium environment with no ukwrite, although the shared
uk command always runs ukwrite after downloading a kernel. On a NAND image, invoking run uk
downloads the file but cannot write it; NAND recovery instead replaces the rootfs volume containing
the kernel.
Code

openipc/nand_config[14]

+CONFIG_ENV_IS_IN_NAND=y
Evidence
The common command unconditionally calls ukwrite after a successful download, while the selected
NAND environment defines no such command and stores the kernel inside the UBIFS rootfs.

include/configs/openipc-common.h[77-90]
include/configs/openipc-common.h[139-155]
openipc/nand_config[14-14]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The shared kernel-update command invokes an undefined writer when the new NAND configuration selects the NAND environment.
## Fix Focus Areas
- openipc/nand_config[14-14]
- include/configs/openipc-common.h[77-90]
- include/configs/openipc-common.h[147-155]
## Recommended Fix
Provide an appropriate NAND-specific `uk` path for a kernel embedded in the rootfs, or remove and clearly replace the unsupported kernel-only command for NAND images.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can see how risky a PR is at a glance with blast radius labels

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread build.sh
Comment on lines +53 to +55
hdr = bytearray(b'\xff' * 0x1000)
hdr[0:8] = struct.pack('<II', 0xe51ff004, text)
open(dst, 'wb').write(bytes(hdr) + open(src, 'rb').read())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Nand images enter code in the wrong state 🐞 Bug ≡ Correctness

build.sh prepends an ARM-state ldr pc instruction that jumps to the even CONFIG_SYS_TEXT_BASE,
while the linked entry code is built in Thumb state. When the ROM executes that header, the jump
reaches the image entry without switching instruction state, before U-Boot can initialize or
relocate.
Agent Prompt
## Issue description
The NAND header jumps in ARM state to a Thumb-built U-Boot entry, so the image cannot execute its entry code correctly.
## Fix Focus Areas
- build.sh[44-55]
- openipc/openipc_config[6-6]
## Recommended Fix
Make the header explicitly transfer into the instruction state used by the linked entry, and verify the resulting entry sequence for both proposed ROM addresses.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread drivers/mtd/nand/raw/xmsfc_nand.c Outdated
Comment on lines +146 to +150
writel(cfg1, V2_CFG1);
writel(CFG2_POLL, V2_CFG2);
writel(cmd, V2_CMD);
writel(row, V2_ADDR);
xmsfc_run(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Nand commands can poll the wrong status 🐞 Bug ≡ Correctness

xmsfc_row_cmd() enables controller status polling without setting its poll-command register for
SPI NAND. The v2 NOR driver programs that register before polled operations, so a NAND boot
following a NOR-configured controller can poll a stale NOR opcode during page reads, programming, or
erases.
Agent Prompt
## Issue description
Polled NAND row operations leave the controller's status-poll command at its previous value, which may be a NOR opcode.
## Fix Focus Areas
- drivers/mtd/nand/raw/xmsfc_nand.c[143-152]
- drivers/mtd/spi/xmsfc.c[143-169]
## Recommended Fix
Configure the controller's poll command and required SPI NAND status address before enabling hardware polling, or disable hardware polling and rely on correctly checked software status reads.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread drivers/mtd/nand/raw/xmsfc_nand.c Outdated
Comment thread drivers/mtd/nand/raw/xmsfc_nand.c
Comment thread openipc/nand_config
CONFIG_NAND_XMSFC=y
CONFIG_CMD_NAND=y
CONFIG_CMD_NAND_TRIMFFS=y
CONFIG_ENV_IS_IN_NAND=y

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. Nand users cannot update the kernel alone 🐞 Bug ≡ Correctness

Enabling CONFIG_ENV_IS_IN_NAND selects a medium environment with no ukwrite, although the shared
uk command always runs ukwrite after downloading a kernel. On a NAND image, invoking run uk
downloads the file but cannot write it; NAND recovery instead replaces the rootfs volume containing
the kernel.
Agent Prompt
## Issue description
The shared kernel-update command invokes an undefined writer when the new NAND configuration selects the NAND environment.
## Fix Focus Areas
- openipc/nand_config[14-14]
- include/configs/openipc-common.h[77-90]
- include/configs/openipc-common.h[147-155]
## Recommended Fix
Provide an appropriate NAND-specific `uk` path for a kernel embedded in the rootfs, or remove and clearly replace the unsupported kernel-only command for NAND images.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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.
@widgetii
widgetii merged commit 74a9e92 into openipc-xm Oct 17, 2026
10 checks passed
@widgetii
widgetii deleted the xm/spinand branch October 17, 2026 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant