mail archive of the barebox mailing list
 help / color / mirror / Atom feed
From: Sascha Hauer <s.hauer@pengutronix.de>
To: BAREBOX <barebox@lists.infradead.org>
Subject: [PATCH 20/27] ARM: pxa: add a NAND first stage loader
Date: Sun, 16 Aug 2026 19:56:40 +0200	[thread overview]
Message-ID: <20260816-pxa3xx-v1-20-f3c3d7a6c43f@pengutronix.de> (raw)
In-Reply-To: <20260816-pxa3xx-v1-0-f3c3d7a6c43f@pengutronix.de>

The PXA3xx Boot ROM reads a NTIM header from the start of the boot
device, copies the OBM listed in it into internal SRAM and jumps to it.
It does not come back, so the OBM has to fetch the next image itself.

Add the pieces a PXA3xx board needs to be that OBM: pxa_nand_load_image()
reads the NTIM from flash rather than relying on the copy the Boot ROM
left in its scratch area, looks up an image by id and copies exactly as
many bytes as the header says.

Bad blocks are skipped, so the flash offsets in the NTIM count good
blocks only rather than addressing the device directly. That is what
mtd_peb_write_file() produces when a board writes such an image, so the
two ends agree on where an image is without either of them having to
record where the bad blocks are. Getting at the marker needs SPARE_EN in
NDCR, which makes the controller hand the spare area out along with the
page; with ECC_EN clear that is all 64 bytes of it, and the first is the
marker on a large page device. Only the first page of a block is checked:
vendors put the marker in the first page, the second or the last
depending on the part, and reading the first is what a first stage can
afford.

The NTIM itself is not covered by any of that, and does not need to be:
scripts/pxa-image puts it and the OBM in the first erase block, which the
chip guarantees to be good.

Everything here runs from internal SRAM before DRAM is usable for
anything but the destination, so it uses no global data and no stack
beyond what the entry function set up.

Alongside it goes the image rule that turns a PBL image into the layout
the Boot ROM expects, driven by scripts/pxa-image.

Assisted-by: Claude Opus 5
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 arch/arm/mach-pxa/Makefile     |   2 +
 arch/arm/mach-pxa/xload-nand.c | 255 +++++++++++++++++++++++++++++++++++++++++
 images/Makefile                |   1 +
 images/Makefile.pxa            |  18 +++
 include/mach/pxa/xload.h       |  42 +++++++
 5 files changed, 318 insertions(+)

diff --git a/arch/arm/mach-pxa/Makefile b/arch/arm/mach-pxa/Makefile
index fa0793d8a9..4069a47251 100644
--- a/arch/arm/mach-pxa/Makefile
+++ b/arch/arm/mach-pxa/Makefile
@@ -6,3 +6,5 @@ obj-y += common.o
 obj-y += devices.o
 
 obj-$(CONFIG_ARCH_PXA3XX) += mfp-pxa3xx.o pxa3xx.o
+
+pbl-$(CONFIG_ARCH_PXA3XX) += xload-nand.o
diff --git a/arch/arm/mach-pxa/xload-nand.c b/arch/arm/mach-pxa/xload-nand.c
new file mode 100644
index 0000000000..c03f0e52cd
--- /dev/null
+++ b/arch/arm/mach-pxa/xload-nand.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Load barebox from NAND in the PXA3xx first stage.
+ *
+ * The Boot ROM reads the NTIM header from the start of NAND, copies the OBM
+ * listed in it into internal SRAM and jumps there. It does not come back, so
+ * the OBM has to fetch the next image itself.
+ *
+ * Everything here runs from internal SRAM before DRAM is usable for anything
+ * but the destination, so it uses no global data and no stack beyond what the
+ * entry function set up.
+ *
+ * The NTIM itself is in the first erase block, which the chip guarantees to be
+ * good, so it is always where the Boot ROM and this code expect it. Everything
+ * behind it is read past bad blocks.
+ */
+
+#include <common.h>
+#include <linux/sizes.h>
+#include <asm/io.h>
+#include <mach/pxa/xload.h>
+
+#define NAND_BASE		0x43100000
+
+#define NDCR			0x00
+#define NDTR0CS0		0x04
+#define NDTR1CS0		0x0c
+#define NDSR			0x14
+#define NDDB			0x40
+#define NDCB0			0x48
+
+#define NDCR_SPARE_EN		(1 << 31)
+#define NDCR_ND_RUN		(1 << 28)
+
+#define NDSR_WRCMDREQ		(1 << 0)
+#define NDSR_RDDREQ		(1 << 1)
+
+/*
+ * Values taken from the vendor OBM. NDCR configures a x8 device with 2K
+ * pages; the timings are the ones the previous stage is known to work with.
+ *
+ * SPARE_EN is ours: it makes the controller hand out the spare area along
+ * with the data, which is where the bad block marker is. With ECC_EN clear
+ * that is all 64 bytes of it.
+ */
+#define NDCR_INIT		(0x01045fff | NDCR_SPARE_EN)
+#define NDTR0CS0_INIT		0x00161c1c
+#define NDTR1CS0_INIT		0x0f3d00f2
+
+/*
+ * Read command for a 2K page device: 0x00 followed by 0x30, five address
+ * cycles, double byte command.
+ */
+#define NDCB0_READ_PAGE		0x000d3000
+
+#define NAND_PAGE_SIZE		2048
+#define NAND_OOB_SIZE		64
+#define NAND_BLOCK_SIZE		SZ_128K
+
+/* A device with more bad blocks than this in front of the image is broken. */
+#define NAND_MAX_BAD_BLOCKS	16
+
+static void pxa_nand_init(void __iomem *base)
+{
+	int i;
+
+	writel(0, base + NDCR);
+	writel(NDTR0CS0_INIT, base + NDTR0CS0);
+	writel(NDTR1CS0_INIT, base + NDTR1CS0);
+	writel(0x00000fff, base + NDSR);
+	writel(NDCR_INIT, base + NDCR);
+
+	/* Drain whatever the controller has left in the data buffer. */
+	for (i = 0; i < 16; i++)
+		readl(base + NDDB);
+}
+
+/*
+ * Read one page. 'offset' is a physical byte offset into the device and has to
+ * be page aligned. @buf takes the data and @oob the spare area; either may be
+ * NULL to read past that part of the page without keeping it. The controller
+ * hands out both whatever we do with them, so they have to be read either way.
+ */
+static void pxa_nand_read_page(void __iomem *base, u32 offset, void *buf,
+			       void *oob)
+{
+	u32 *dest;
+	u32 addr;
+	int i;
+
+	writel(readl(base + NDCR) | NDCR_ND_RUN, base + NDCR);
+
+	while (!(readl(base + NDSR) & NDSR_WRCMDREQ))
+		;
+
+	readl(base + NDDB);
+	readl(base + NDDB);
+
+	/* column in the low bits, page number from bit 16 upwards */
+	addr = (offset & 0x7ff) | ((offset << 5) & 0xffff0000);
+
+	writel(NDCB0_READ_PAGE, base + NDCB0);
+	writel(addr, base + NDCB0);
+	writel(0, base + NDCB0);
+
+	while (!(readl(base + NDSR) & NDSR_RDDREQ))
+		;
+	writel(NDSR_RDDREQ, base + NDSR);
+
+	dest = buf;
+	for (i = 0; i < NAND_PAGE_SIZE / 4; i++) {
+		u32 val = readl(base + NDDB);
+
+		if (dest)
+			*dest++ = val;
+	}
+
+	/* The spare area follows the data. */
+	dest = oob;
+	for (i = 0; i < NAND_OOB_SIZE / 4; i++) {
+		u32 val = readl(base + NDDB);
+
+		if (dest)
+			*dest++ = val;
+	}
+}
+
+static bool pxa_nand_block_is_bad(void __iomem *base, u32 offset)
+{
+	/* pxa_nand_read_page() fills this a word at a time */
+	u8 oob[NAND_OOB_SIZE] __aligned(4);
+
+	pxa_nand_read_page(base, offset, NULL, oob);
+
+	/*
+	 * The bad block marker is the first byte of the spare area on a large
+	 * page device.
+	 */
+	return oob[0] != 0xff;
+}
+
+/*
+ * Read @size bytes from @offset, which is an offset into the good blocks of
+ * the device rather than into the device itself: bad blocks are skipped and do
+ * not count towards it. That is the same thing mtd_peb_write_file() does when
+ * the update handler writes the image, so the two agree on where an image is
+ * without either of them having to record where the bad blocks are.
+ *
+ * Returns false if the image could not be read.
+ */
+static bool pxa_nand_read(void __iomem *base, u32 offset, void *buf, u32 size)
+{
+	unsigned int skipped = 0;
+	u32 phys = 0, log = 0;
+
+	while (size) {
+		/*
+		 * Only the first page of a block carries the marker, so this
+		 * is the one point where a block can be rejected.
+		 */
+		if (!(phys & (NAND_BLOCK_SIZE - 1)) &&
+		    pxa_nand_block_is_bad(base, phys)) {
+			if (++skipped > NAND_MAX_BAD_BLOCKS)
+				return false;
+			phys += NAND_BLOCK_SIZE;
+			continue;
+		}
+
+		/* Not at the image yet, so there is nothing to keep. */
+		if (log < offset) {
+			phys += NAND_PAGE_SIZE;
+			log += NAND_PAGE_SIZE;
+			continue;
+		}
+
+		pxa_nand_read_page(base, phys, buf, NULL);
+		buf += NAND_PAGE_SIZE;
+		phys += NAND_PAGE_SIZE;
+		log += NAND_PAGE_SIZE;
+		size -= min_t(u32, size, NAND_PAGE_SIZE);
+	}
+
+	return true;
+}
+
+/**
+ * pxa_nand_load_image - load the next image out of NAND
+ * @image_id:	NTIM id of the image to load, eg NTIM_ID_BOOT
+ *
+ * Reads the NTIM header from the start of the device, looks up the entry for
+ * @image_id and copies that image to the load address the header gives. The
+ * flash offset in that entry counts good blocks only; see pxa_nand_read().
+ *
+ * The header is read from flash rather than taken from the address the Boot
+ * ROM loaded it to, so that this does not depend on the ROM's scratch area
+ * still being intact.
+ *
+ * What this expects of the flash. None of it is discovered: the register
+ * values at the top of this file are one board's, recovered from its vendor
+ * OBM, and the geometry is hardcoded to match them, so a device that differs
+ * in any of these needs more than new timings.
+ *
+ *  - 2048 byte pages. That is NDCR PAGE_SZ, the five address cycles and the
+ *    double byte command in NDCB0_READ_PAGE, and the 11 column bits in the
+ *    address. A 512 byte page device needs all four changed.
+ *
+ *  - An 8 bit bus, ie NDCR DWIDTH_M and DWIDTH_C clear.
+ *
+ *  - 64 pages to an erase block, ie NDCR PG_PER_BLK set. NAND_BLOCK_SIZE has
+ *    to say the same thing and nothing checks that it does.
+ *
+ *  - 64 bytes of spare area, which is what the controller hands out for a 2K
+ *    page with SPARE_EN set and ECC_EN clear.
+ *
+ *  - The bad block marker in the first byte of the spare area of a block's
+ *    first page. Parts that mark the second or the last page instead are not
+ *    noticed.
+ *
+ * Return: the load address of the image, or NULL if it could not be loaded
+ */
+void *pxa_nand_load_image(u32 image_id)
+{
+	void __iomem *base = IOMEM(NAND_BASE);
+	u8 page[NAND_PAGE_SIZE] __aligned(4);
+	struct ntim_header *hdr;
+	struct ntim_image *img;
+	u32 i, num_images;
+
+	pxa_nand_init(base);
+	pxa_nand_read_page(base, 0, page, NULL);
+
+	hdr = (void *)page;
+	if (hdr->identifier != NTIM_ID_TIMH ||
+	    hdr->oem_unique_id != NTIM_OEM_UNIQUE_ID)
+		return NULL;
+
+	num_images = hdr->num_images;
+	if (!num_images || num_images > NTIM_MAX_IMAGES)
+		return NULL;
+
+	img = (void *)(hdr + 1);
+
+	for (i = 0; i < num_images; i++) {
+		if (img[i].image_id != image_id)
+			continue;
+
+		if (!pxa_nand_read(base, img[i].flash_entry_addr,
+				   (void *)img[i].load_addr, img[i].image_size))
+			return NULL;
+
+		return (void *)img[i].load_addr;
+	}
+
+	return NULL;
+}
diff --git a/images/Makefile b/images/Makefile
index 91425dd3a0..f8e853418d 100644
--- a/images/Makefile
+++ b/images/Makefile
@@ -183,6 +183,7 @@ include $(srctree)/images/Makefile.malta
 include $(srctree)/images/Makefile.mvebu
 include $(srctree)/images/Makefile.mxs
 include $(srctree)/images/Makefile.omap3
+include $(srctree)/images/Makefile.pxa
 include $(srctree)/images/Makefile.rockchip
 include $(srctree)/images/Makefile.exynos
 include $(srctree)/images/Makefile.sandbox
diff --git a/images/Makefile.pxa b/images/Makefile.pxa
new file mode 100644
index 0000000000..023aec428d
--- /dev/null
+++ b/images/Makefile.pxa
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# barebox image generation Makefile for PXA images
+#
+
+# A first stage image: the NTIM header the Boot ROM reads from NAND, the OBM
+# it loads into internal SRAM, and barebox for the OBM to load into DRAM.
+quiet_cmd_pxa_image = PXAIMG  $@
+      cmd_pxa_image = $(objtree)/scripts/pxa-image \
+	-b $< -f $(obj)/$(PXA_PAYLOAD_$(@F)) -o $@ \
+	$(if $(PXA_OBM_OFFSET_$(@F)),-O $(PXA_OBM_OFFSET_$(@F))) \
+	$(if $(PXA_OBM_LOAD_$(@F)),-L $(PXA_OBM_LOAD_$(@F))) \
+	$(if $(PXA_BOOT_OFFSET_$(@F)),-F $(PXA_BOOT_OFFSET_$(@F))) \
+	$(if $(PXA_BOOT_LOAD_$(@F)),-A $(PXA_BOOT_LOAD_$(@F))) \
+	$(if $(PXA_MAX_SIZE_$(@F)),-M $(PXA_MAX_SIZE_$(@F)))
+
+$(obj)/%.pxaimg: $(obj)/% $(obj)/$$(PXA_PAYLOAD_$$(@F)) FORCE
+	$(call if_changed,pxa_image)
diff --git a/include/mach/pxa/xload.h b/include/mach/pxa/xload.h
new file mode 100644
index 0000000000..d24b180cbf
--- /dev/null
+++ b/include/mach/pxa/xload.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __MACH_PXA_XLOAD_H
+#define __MACH_PXA_XLOAD_H
+
+#include <linux/types.h>
+
+#define NTIM_ID_TIMH		0x54494d48	/* 'TIMH' */
+#define NTIM_ID_OBMI		0x4f424d49	/* 'OBMI' */
+#define NTIM_ID_BOOT		0x424f4f54	/* 'BOOT' */
+#define NTIM_OEM_UNIQUE_ID	0xcafeaffe
+
+#define NTIM_MAX_IMAGES		8
+
+/*
+ * Non-Trusted Image Module, the header the PXA3xx Boot ROM reads from the
+ * start of the boot device. Written by scripts/pxa-image.
+ */
+struct ntim_header {
+	u32 version;
+	u32 identifier;
+	u32 trusted;
+	u32 issue_date;
+	u32 oem_unique_id;
+	u32 reserved[5];
+	u32 flash_info;
+	u32 num_images;
+	u32 num_keys;
+	u32 size_of_reserved;
+};
+
+struct ntim_image {
+	u32 image_id;
+	u32 next_image_id;
+	u32 flash_entry_addr;
+	u32 load_addr;
+	u32 image_size;
+	u32 reserved[10];
+};
+
+void *pxa_nand_load_image(u32 image_id);
+
+#endif /* __MACH_PXA_XLOAD_H */

-- 
2.47.3




  parent reply	other threads:[~2026-08-16 18:15 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-16 17:56 [PATCH 00/27] ARM: Add pxa3xx and Raumfeld Speaker support Sascha Hauer
2026-08-16 17:56 ` [PATCH 01/27] ARM: pxa: remove PXA25x and PXA27x support Sascha Hauer
2026-08-16 17:56 ` [PATCH 02/27] video: remove the PXA framebuffer driver Sascha Hauer
2026-08-16 17:56 ` [PATCH 03/27] ARM: cache: drive the XSC3 cache with the ARMv4 functions Sascha Hauer
2026-08-16 17:56 ` [PATCH 04/27] mci: pxamci: get the clock from the clk API Sascha Hauer
2026-08-16 17:56 ` [PATCH 05/27] pwm: pxa: " Sascha Hauer
2026-08-16 17:56 ` [PATCH 06/27] serial: " Sascha Hauer
2026-08-16 17:56 ` [PATCH 07/27] clk: pxa: add a device tree clock driver for PXA3xx Sascha Hauer
2026-08-16 17:56 ` [PATCH 08/27] mtd: nand: nand_mrvl_nfc: honour marvell,nand-keep-config Sascha Hauer
2026-08-16 17:56 ` [PATCH 09/27] mtd: nand: nand_mrvl_nfc: support the nand-controller bindings Sascha Hauer
2026-08-16 17:56 ` [PATCH 10/27] mtd: nand: mrvl_nfc: keep the ready latch across a STATUS command Sascha Hauer
2026-08-16 17:56 ` [PATCH 11/27] mtd: nand: mrvl_nfc: do not report a command timeout as an error Sascha Hauer
2026-08-16 17:56 ` [PATCH 12/27] mci: pxamci: probe from the device tree Sascha Hauer
2026-08-16 17:56 ` [PATCH 13/27] serial: pxa: add device tree support Sascha Hauer
2026-08-16 17:56 ` [PATCH 14/27] serial: pxa: provide the Linux console name Sascha Hauer
2026-08-16 17:56 ` [PATCH 15/27] gpio: pxa: add a driver and switch the architecture to GPIOLIB Sascha Hauer
2026-08-16 17:56 ` [PATCH 16/27] ARM: pxa: add DEBUG_LL support Sascha Hauer
2026-08-16 17:56 ` [PATCH 17/27] ARM: pxa: let the board select the SoC Sascha Hauer
2026-08-16 17:56 ` [PATCH 18/27] ARM: pxa: enable device tree support Sascha Hauer
2026-08-16 17:56 ` [PATCH 19/27] scripts: add pxa-image Sascha Hauer
2026-08-16 17:56 ` Sascha Hauer [this message]
2026-08-16 17:56 ` [PATCH 21/27] filetype: detect PXA3xx NTIM images Sascha Hauer
2026-08-16 17:56 ` [PATCH 22/27] ARM: pxa: add a barebox update handler for NAND Sascha Hauer
2026-08-16 17:56 ` [PATCH 23/27] clocksource: add a driver for the PXA OS timer and its watchdog Sascha Hauer
2026-08-16 17:56 ` [PATCH 24/27] ARM: pxa: move over to MULTIARCH Sascha Hauer
2026-08-16 17:56 ` [PATCH 25/27] ARM: pxa: reset straight away and without complaining Sascha Hauer
2026-08-16 17:56 ` [PATCH 26/27] ARM: pxa: add Raumfeld Speaker board support Sascha Hauer
2026-08-16 17:56 ` [PATCH 27/27] ARM: multi_v5_v6_defconfig: enable PXA support Sascha Hauer
2026-08-17  7:35 ` [PATCH 00/27] ARM: Add pxa3xx and Raumfeld Speaker support Ahmad Fatoum
2026-08-19  9:26 ` Sascha Hauer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260816-pxa3xx-v1-20-f3c3d7a6c43f@pengutronix.de \
    --to=s.hauer@pengutronix.de \
    --cc=barebox@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox