[PATCH v1 02/12] mips: en75xx: add TPL and SPL early boot stages

AK Sharma <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
Add the TPL and SPL stages for the EN75xx. The TPL brings up the cache
and SoC straps, runs the vendor DDR calibration stage from flash via the
early SFC (serial-flash controller) reader, then the SPL loads U-Boot
proper.

The DDR calibration stage is a vendor artifact read from flash at boot
and is not shipped in-tree; see arch/mips/mach-en75xx/README.ddr-blob for
how to obtain it and its checksums.

Signed-off-by: AK Sharma <[email protected]>
---
 arch/mips/mach-en75xx/README.ddr-blob |  21 ++
 arch/mips/mach-en75xx/early_sfc.c     | 352 ++++++++++++++++++++++++++
 arch/mips/mach-en75xx/spl/Makefile    |   2 +
 arch/mips/mach-en75xx/spl/spl.c       |  47 ++++
 arch/mips/mach-en75xx/tpl/Makefile    |   3 +
 arch/mips/mach-en75xx/tpl/start.S     | 101 ++++++++
 arch/mips/mach-en75xx/tpl/tpl.c       | 158 ++++++++++++
 7 files changed, 684 insertions(+)
 create mode 100644 arch/mips/mach-en75xx/README.ddr-blob
 create mode 100644 arch/mips/mach-en75xx/early_sfc.c
 create mode 100644 arch/mips/mach-en75xx/spl/Makefile
 create mode 100644 arch/mips/mach-en75xx/spl/spl.c
 create mode 100644 arch/mips/mach-en75xx/tpl/Makefile
 create mode 100644 arch/mips/mach-en75xx/tpl/start.S
 create mode 100644 arch/mips/mach-en75xx/tpl/tpl.c

diff --git a/arch/mips/mach-en75xx/README.ddr-blob b/arch/mips/mach-en75xx/README.ddr-blob
new file mode 100644
index 00000000..b0074941
--- /dev/null
+++ b/arch/mips/mach-en75xx/README.ddr-blob
@@ -0,0 +1,21 @@
+EN75xx DDR calibration blob
+===========================
+
+The EN75xx TPL runs a small vendor DDR calibration stage that is NOT part
+of the U-Boot source tree. The TPL reads it from SPI-NAND at
+EN75XX_DDR_BLOB_OFFSET (0x8000) with the early SFC reader (see tpl/tpl.c)
+and executes it from FE-SRAM before DRAM is available.
+
+Obtain the blob by extracting the FE-SRAM DDR calibration stage
+("spram.bin") from the device's vendor bootloader and flashing it at
+EN75XX_DDR_BLOB_OFFSET.
+
+Expected images:
+
+  EN7512/EN7521  20336 bytes, linked/entered at 0x9fa32800
+                 sha256 792068a4573b20cb8c39cb27df06c45683c11034e6fd07da24ddf7084828faa5
+  EN7528         23152 bytes
+                 sha256 c2ff94b25100d46c0f2d8b42ebeb887bd6bfd0d49cd1ba5df61eea84ac227540
+
+Providing a source implementation of the DDR2/DDR3 calibration to remove
+this runtime dependency is a known TODO.
diff --git a/arch/mips/mach-en75xx/early_sfc.c b/arch/mips/mach-en75xx/early_sfc.c
new file mode 100644
index 00000000..39fc070d
--- /dev/null
+++ b/arch/mips/mach-en75xx/early_sfc.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Minimal EN7512/EN7521 serial-flash reader used before driver model.
+ * Reconstructed from the vendor TCBoot move_data stage.
+ */
+
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <asm/io.h>
+#include <mach/en75xx.h>
+
+#define SF_READ_IDLE_EN		0x004
+#define SF_MTX_MODE_TOG		0x014
+#define SF_RDCTL_FSM			0x018
+#define SF_MACMUX_SEL			0x01c
+#define SF_MANUAL_EN			0x020
+#define SF_MANUAL_OPFIFO_EMPTY		0x024
+#define SF_MANUAL_OPFIFO_WDATA		0x028
+#define SF_MANUAL_OPFIFO_FULL		0x02c
+#define SF_MANUAL_OPFIFO_WR		0x030
+#define SF_MANUAL_DFIFO_FULL		0x034
+#define SF_MANUAL_DFIFO_WDATA		0x038
+#define SF_MANUAL_DFIFO_EMPTY		0x03c
+#define SF_MANUAL_DFIFO_RD		0x040
+#define SF_MANUAL_DFIFO_RDATA		0x044
+#define SF_SI_CK_SEL			0x09c
+#define SF_STRAP			0x114
+
+#define SF_STRAP_ADDR_4B		BIT(0)
+#define SF_STRAP_SPI_NAND		BIT(1)
+#define SF_STRAP_DUMMY_APPEND		BIT(2)
+
+#define OP_CSH				0x00
+#define OP_CSL				0x01
+#define OP_CK				0x02
+#define OP_OUTS				0x08
+#define OP_INS				0x0c
+
+#define OP_SHIFT			9
+#define OP_CMD_MASK			0x1f
+#define OP_LEN_MASK			0x1ff
+
+#define SPIN_LIMIT			1000000
+#define NAND_PAGE_SIZE			2048
+#define NOR_READ_CHUNK			1024
+
+static inline void __iomem *sf_reg(u32 reg)
+{
+	return (void __iomem *)(EN75XX_SFC_BASE + reg);
+}
+
+static int sf_wait_eq(u32 reg, u32 expected)
+{
+	unsigned int timeout = SPIN_LIMIT;
+
+	while (timeout--) {
+		if (__raw_readl(sf_reg(reg)) == expected)
+			return 0;
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int sf_op(u32 op, u32 len)
+{
+	u32 val = ((op & OP_CMD_MASK) << OP_SHIFT) | (len & OP_LEN_MASK);
+	int ret;
+
+	ret = sf_wait_eq(SF_MANUAL_OPFIFO_FULL, 0);
+	if (ret)
+		return ret;
+
+	__raw_writel(val, sf_reg(SF_MANUAL_OPFIFO_WDATA));
+	__raw_writel(1, sf_reg(SF_MANUAL_OPFIFO_WR));
+
+	return sf_wait_eq(SF_MANUAL_OPFIFO_EMPTY, 1);
+}
+
+static int sf_put_byte(u8 val)
+{
+	int ret = sf_wait_eq(SF_MANUAL_DFIFO_FULL, 0);
+
+	if (ret)
+		return ret;
+
+	__raw_writel(val, sf_reg(SF_MANUAL_DFIFO_WDATA));
+	return 0;
+}
+
+static int sf_put_bytes(const u8 *buf, size_t len)
+{
+	size_t i;
+	int ret;
+
+	for (i = 0; i < len; i++) {
+		ret = sf_put_byte(buf[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int sf_get_byte(u8 *val)
+{
+	int ret = sf_wait_eq(SF_MANUAL_DFIFO_EMPTY, 0);
+
+	if (ret)
+		return ret;
+
+	*val = __raw_readl(sf_reg(SF_MANUAL_DFIFO_RDATA)) & 0xff;
+	__raw_writel(1, sf_reg(SF_MANUAL_DFIFO_RD));
+	return 0;
+}
+
+static int sf_finish(void)
+{
+	int ret;
+
+	ret = sf_op(OP_CSH, 1);
+	if (ret)
+		return ret;
+
+	return sf_op(OP_CK, 5);
+}
+
+int en75xx_sfc_init(void)
+{
+	int ret;
+
+	__raw_writel(0x9, sf_reg(SF_SI_CK_SEL));
+	__raw_writel(0, sf_reg(SF_READ_IDLE_EN));
+
+	ret = sf_wait_eq(SF_RDCTL_FSM, 0);
+	if (ret)
+		return ret;
+
+	__raw_writel(0x9, sf_reg(SF_MTX_MODE_TOG));
+	__raw_writel(1, sf_reg(SF_MACMUX_SEL));
+	__raw_writel(1, sf_reg(SF_MANUAL_EN));
+
+	return 0;
+}
+
+static int sf_nand_load_page(u32 page)
+{
+	u8 page_addr[] = { page >> 16, page >> 8, page };
+	u8 status;
+	unsigned int timeout = SPIN_LIMIT;
+	int ret;
+
+	ret = sf_op(OP_CSL, 1);
+	if (ret)
+		return ret;
+	ret = sf_op(OP_OUTS, 1);
+	if (ret)
+		return ret;
+	ret = sf_put_byte(0x13);
+	if (ret)
+		return ret;
+	ret = sf_op(OP_OUTS, ARRAY_SIZE(page_addr));
+	if (ret)
+		return ret;
+	ret = sf_put_bytes(page_addr, ARRAY_SIZE(page_addr));
+	if (ret)
+		return ret;
+	ret = sf_finish();
+	if (ret)
+		return ret;
+
+	while (timeout--) {
+		ret = sf_op(OP_CSL, 1);
+		if (ret)
+			return ret;
+		ret = sf_op(OP_OUTS, 1);
+		if (ret)
+			return ret;
+		ret = sf_put_byte(0x0f);
+		if (ret)
+			return ret;
+		ret = sf_op(OP_OUTS, 1);
+		if (ret)
+			return ret;
+		ret = sf_put_byte(0xc0);
+		if (ret)
+			return ret;
+		ret = sf_op(OP_INS, 1);
+		if (ret)
+			return ret;
+		ret = sf_get_byte(&status);
+		if (ret)
+			return ret;
+		ret = sf_finish();
+		if (ret)
+			return ret;
+		if (!(status & BIT(0)))
+			return 0;
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int sf_nand_read_cache(u32 column, u8 *dst, size_t len,
+			      bool dummy_append)
+{
+	u8 address[3];
+	size_t i;
+	int ret;
+
+	if (dummy_append) {
+		address[0] = column >> 8;
+		address[1] = column;
+		address[2] = 0;
+	} else {
+		address[0] = 0;
+		address[1] = column >> 8;
+		address[2] = column;
+	}
+
+	ret = sf_op(OP_CSL, 1);
+	if (ret)
+		return ret;
+	ret = sf_op(OP_OUTS, 1);
+	if (ret)
+		return ret;
+	ret = sf_put_byte(0x03);
+	if (ret)
+		return ret;
+	ret = sf_op(OP_OUTS, ARRAY_SIZE(address));
+	if (ret)
+		return ret;
+	ret = sf_put_bytes(address, ARRAY_SIZE(address));
+	if (ret)
+		return ret;
+
+	for (i = 0; i < len; i++) {
+		ret = sf_op(OP_INS, 1);
+		if (ret)
+			return ret;
+		ret = sf_get_byte(&dst[i]);
+		if (ret)
+			return ret;
+	}
+
+	return sf_finish();
+}
+
+static int sf_nor_read_once(u32 offset, u8 *dst, size_t len, bool addr4b,
+			    bool dummy_append)
+{
+	u8 address[4];
+	size_t addr_len = 0;
+	size_t i;
+	int ret;
+
+	if (addr4b)
+		address[addr_len++] = offset >> 24;
+	if (dummy_append) {
+		address[addr_len++] = offset >> 8;
+		address[addr_len++] = offset;
+		address[addr_len++] = 0;
+	} else {
+		address[addr_len++] = offset >> 16;
+		address[addr_len++] = offset >> 8;
+		address[addr_len++] = offset;
+	}
+
+	ret = sf_op(OP_CSL, 1);
+	if (ret)
+		return ret;
+	ret = sf_op(OP_OUTS, 1);
+	if (ret)
+		return ret;
+	ret = sf_put_byte(0x03);
+	if (ret)
+		return ret;
+
+	ret = sf_op(OP_OUTS, addr_len);
+	if (ret)
+		return ret;
+	ret = sf_put_bytes(address, addr_len);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < len; i++) {
+		ret = sf_op(OP_INS, 1);
+		if (ret)
+			return ret;
+		ret = sf_get_byte(&dst[i]);
+		if (ret)
+			return ret;
+	}
+
+	return sf_finish();
+}
+
+static int sf_nor_read(u32 offset, u8 *dst, size_t len, bool addr4b,
+		       bool dummy_append)
+{
+	while (len) {
+		size_t chunk = NOR_READ_CHUNK - (offset % NOR_READ_CHUNK);
+		int ret;
+
+		if (chunk > len)
+			chunk = len;
+		ret = sf_nor_read_once(offset, dst, chunk, addr4b,
+				       dummy_append);
+		if (ret)
+			return ret;
+		offset += chunk;
+		dst += chunk;
+		len -= chunk;
+	}
+
+	return 0;
+}
+
+int en75xx_sfc_read(u32 offset, void *dst, size_t len)
+{
+	u32 strap = __raw_readl(sf_reg(SF_STRAP));
+	u8 *buf = dst;
+	int ret;
+
+	if (!(strap & SF_STRAP_SPI_NAND))
+		return sf_nor_read(offset, buf, len, strap & SF_STRAP_ADDR_4B,
+				   strap & SF_STRAP_DUMMY_APPEND);
+
+	while (len) {
+		u32 page = offset / NAND_PAGE_SIZE;
+		u32 column = offset % NAND_PAGE_SIZE;
+		size_t chunk = NAND_PAGE_SIZE - column;
+
+		if (chunk > len)
+			chunk = len;
+
+		ret = sf_nand_load_page(page);
+		if (ret)
+			return ret;
+		ret = sf_nand_read_cache(column, buf, chunk,
+					 strap & SF_STRAP_DUMMY_APPEND);
+		if (ret)
+			return ret;
+
+		offset += chunk;
+		buf += chunk;
+		len -= chunk;
+	}
+
+	return 0;
+}
diff --git a/arch/mips/mach-en75xx/spl/Makefile b/arch/mips/mach-en75xx/spl/Makefile
new file mode 100644
index 00000000..d19fc6e6
--- /dev/null
+++ b/arch/mips/mach-en75xx/spl/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0+
+obj-y += spl.o
diff --git a/arch/mips/mach-en75xx/spl/spl.c b/arch/mips/mach-en75xx/spl/spl.c
new file mode 100644
index 00000000..a42b2837
--- /dev/null
+++ b/arch/mips/mach-en75xx/spl/spl.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <init.h>
+#include <spl.h>
+#include <spl_load.h>
+#include <mach/en75xx.h>
+
+void __noreturn board_init_f(ulong dummy)
+{
+	spl_init();
+
+	if (IS_ENABLED(CONFIG_SPL_SERIAL))
+		preloader_console_init();
+
+	board_init_r(NULL, 0);
+}
+
+static ulong en75xx_spl_read(struct spl_load_info *load, ulong offset,
+			     ulong count, void *buf)
+{
+	if (en75xx_sfc_read(offset, buf, count))
+		return 0;
+
+	return count;
+}
+
+static int en75xx_spl_load_image(struct spl_image_info *spl_image,
+				 struct spl_boot_device *bootdev)
+{
+	struct spl_load_info load;
+
+	if (en75xx_sfc_init())
+		return -EIO;
+
+	spl_load_init(&load, en75xx_spl_read, NULL, 1);
+	return spl_load(spl_image, bootdev, &load, 0,
+			EN75XX_UBOOT_IMAGE_OFFSET);
+}
+
+SPL_LOAD_IMAGE_METHOD("EN75XX SFC", 0, BOOT_DEVICE_BOARD,
+		      en75xx_spl_load_image);
+
+u32 spl_boot_device(void)
+{
+	return BOOT_DEVICE_BOARD;
+}
diff --git a/arch/mips/mach-en75xx/tpl/Makefile b/arch/mips/mach-en75xx/tpl/Makefile
new file mode 100644
index 00000000..da834c75
--- /dev/null
+++ b/arch/mips/mach-en75xx/tpl/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0+
+extra-y += start.o
+obj-y += tpl.o
diff --git a/arch/mips/mach-en75xx/tpl/start.S b/arch/mips/mach-en75xx/tpl/start.S
new file mode 100644
index 00000000..1c192e2e
--- /dev/null
+++ b/arch/mips/mach-en75xx/tpl/start.S
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#include <asm/asm.h>
+#include <asm/regdef.h>
+#include <asm/mipsregs.h>
+#include <mach/en75xx.h>
+
+	.set noreorder
+
+ENTRY(_start)
+	b	reset
+	 mtc0	zero, CP0_COUNT
+
+	/*
+	 * TCBoot image descriptor. The magic is byte-reversed in the vendor
+	 * format, while section offsets are stored as big-endian words.
+	 */
+	.org	0x0c
+	.byte	0x36, 0x35, 0x37, 0x38
+	.word	EN75XX_DDR_BLOB_OFFSET
+	.word	EN75XX_DDR_BLOB_OFFSET + EN75XX_DDR_BLOB_SIZE
+	.word	EN75XX_SPL_IMAGE_OFFSET
+	.word	EN75XX_UBOOT_IMAGE_OFFSET
+
+	/* The vendor reset code begins at offset 0x48. */
+	.org	0x48
+reset:
+	/* Disable interrupts and force KSEG0 uncached before DDR exists. */
+	mfc0	t0, CP0_STATUS
+	li	t1, ~(ST0_IE | ST0_KSU)
+	and	t0, t0, t1
+	li	t1, ST0_BEV | ST0_ERL
+	or	t0, t0, t1
+	mtc0	t0, CP0_STATUS
+	mtc0	zero, CP0_CAUSE
+	mtc0	zero, CP0_COMPARE
+
+	mfc0	t0, CP0_CONFIG
+	li	t1, ~7
+	and	t0, t0, t1
+	ori	t0, t0, 2
+	mtc0	t0, CP0_CONFIG
+	ehb
+
+	/* Arbiter and static-memory mappings copied from TCBoot. */
+	li	t0, EN75XX_SYSCTL_BASE
+	li	t1, 0x80071f1e
+	sw	t1, 0x20(t0)
+	li	t1, 0x00071f1f
+	sw	t1, 0x24(t0)
+	li	t1, 0x80050000
+	sw	t1, 0x34(t0)
+
+	li	t0, EN75XX_SMC_BASE
+	li	t1, 0x102d1040
+	sw	t1, 0x00(t0)
+	li	t1, 0x200028d0
+	sw	t1, 0x14(t0)
+
+	/* Disable the interrupt controller. */
+	li	t0, EN75XX_INTC_BASE
+	sw	zero, 0x00(t0)
+	sw	zero, 0x04(t0)
+
+	/* Enable PBUS access to the 48 KiB FE SRAM. */
+	li	t0, EN75XX_FE_SRAM_SEL
+	lw	t1, 0(t0)
+	ori	t1, t1, 1
+	sw	t1, 0(t0)
+	sync
+
+	li	sp, EN75XX_TPL_STACK_ADDR
+	li	fp, EN75XX_TPL_STACK_ADDR
+
+	la	t9, tpl_main
+	jalr	t9
+	 nop
+
+1:	b	1b
+	 nop
+END(_start)
+
+LEAF(en75xx_run_ddr_blob)
+	li	t0, EN75XX_TPL_SAVED_RA
+	sw	ra, 0(t0)
+
+	la	t1, 1f
+	li	t0, EN75XX_SCREG_WR0
+	sw	t1, 0(t0)
+	sync
+
+	li	t9, EN75XX_DDR_BLOB_ADDR
+	jr	t9
+	 nop
+1:
+	li	t0, EN75XX_SCREG_WR0
+	sw	zero, 0(t0)
+	li	t0, EN75XX_TPL_SAVED_RA
+	lw	ra, 0(t0)
+	jr	ra
+	 nop
+END(en75xx_run_ddr_blob)
diff --git a/arch/mips/mach-en75xx/tpl/tpl.c b/arch/mips/mach-en75xx/tpl/tpl.c
new file mode 100644
index 00000000..ce4ae6b6
--- /dev/null
+++ b/arch/mips/mach-en75xx/tpl/tpl.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <compiler.h>
+#include <linux/byteorder/generic.h>
+#include <linux/types.h>
+#include <asm/io.h>
+#include <mach/en75xx.h>
+
+#define IH_MAGIC	0x27051956
+#define IH_HDR_SIZE	64
+
+struct en75xx_legacy_header {
+	u32 magic;
+	u32 hcrc;
+	u32 time;
+	u32 size;
+	u32 load;
+	u32 ep;
+	u32 dcrc;
+	u8 os;
+	u8 arch;
+	u8 type;
+	u8 comp;
+	u8 name[32];
+};
+
+static u32 get_be32(u32 val)
+{
+	return be32_to_cpu(val);
+}
+
+/*
+ * 16550 register access differs by SoC endianness:
+ *   EN7512 (big-endian): 8-bit registers live in the top byte of each 32-bit
+ *   word, i.e. at byte offset (reg << 2) + 3.
+ *   EN7528 (little-endian, reg-io-width=4, reg-shift=2): 32-bit word access at
+ *   (reg << 2); the 8-bit value sits in the low byte.
+ */
+#if defined(CONFIG_SOC_EN7528)
+static inline void tpl_uart_wr(u32 reg, u32 val)
+{
+	__raw_writel(val, (void __iomem *)(EN75XX_UART0_BASE + (reg << 2)));
+}
+
+static inline u32 tpl_uart_rd(u32 reg)
+{
+	return __raw_readl((void __iomem *)(EN75XX_UART0_BASE + (reg << 2)));
+}
+#else
+static inline void tpl_uart_wr(u32 reg, u32 val)
+{
+	__raw_writeb(val, (void __iomem *)(EN75XX_UART0_BASE + (reg << 2) + 3));
+}
+
+static inline u32 tpl_uart_rd(u32 reg)
+{
+	return __raw_readb((void __iomem *)(EN75XX_UART0_BASE + (reg << 2) + 3));
+}
+#endif
+
+#define UART_RBR_THR	0	/* +0x00: receive/transmit, DLL when DLAB=1 */
+#define UART_IER_DLM	1	/* +0x04: interrupt enable, DLM when DLAB=1 */
+#define UART_IIR_FCR	2	/* +0x08: FIFO control */
+#define UART_LCR	3	/* +0x0c: line control (DLAB) */
+#define UART_MCR	4	/* +0x10: modem control */
+#define UART_LSR	5	/* +0x14: line status (bit5 = THR empty) */
+
+static void tpl_putc(u8 ch)
+{
+	while (!(tpl_uart_rd(UART_LSR) & 0x20))
+		;
+	tpl_uart_wr(UART_RBR_THR, ch);
+}
+
+static void tpl_hang(u8 code)
+{
+	tpl_putc(code);
+	tpl_putc('\r');
+	tpl_putc('\n');
+
+	for (;;)
+		;
+}
+
+static void tpl_uart_init(void)
+{
+#if defined(CONFIG_SOC_EN7528)
+	/*
+	 * EN7528 UART fractional divider (RE'd from the stock bootbase, live-
+	 * verified): 115200 8N1 = XYD 0xEA00FDE8 (X=round(baud*13/25)=0xEA00,
+	 * Y=65000), DLL=1, DLM=0. XYD is reg 11 @ +0x2c.
+	 */
+	tpl_uart_wr(UART_LCR, 0x80);		/* DLAB = 1 */
+	tpl_uart_wr(11, 0xea00fde8);		/* XYD fractional divider */
+	tpl_uart_wr(UART_RBR_THR, 0x01);	/* DLL = 1 */
+	tpl_uart_wr(UART_IER_DLM, 0x00);	/* DLM = 0 */
+	tpl_uart_wr(UART_LCR, 0x03);		/* 8N1, DLAB = 0 */
+	tpl_uart_wr(UART_IIR_FCR, 0x07);	/* enable + clear FIFOs */
+	tpl_uart_wr(UART_MCR, 0x00);
+	tpl_uart_wr(UART_IER_DLM, 0x00);
+#else
+	void __iomem *base = (void __iomem *)EN75XX_UART0_BASE;
+
+	__raw_writeb(0x80, base + 0x0f);
+	__raw_writel(0xea00fde8, base + 0x2c);
+	__raw_writeb(0x01, base + 0x03);
+	__raw_writeb(0x00, base + 0x07);
+	__raw_writeb(0x03, base + 0x0f);
+	__raw_writeb(0x0f, base + 0x0b);
+	__raw_writeb(0x00, base + 0x13);
+	__raw_writeb(0x00, base + 0x27);
+	__raw_writeb(0x00, base + 0x07);
+#endif
+}
+
+void __noreturn tpl_main(void)
+{
+	struct en75xx_legacy_header *hdr =
+		(struct en75xx_legacy_header *)EN75XX_SPL_HEADER_ADDR;
+	void (*entry)(void);
+	u32 load, size, ep;
+	int ret;
+
+	tpl_uart_init();
+	if (en75xx_sfc_init())
+		tpl_hang('I');
+
+	ret = en75xx_sfc_read(EN75XX_DDR_BLOB_OFFSET,
+			      (void *)EN75XX_DDR_BLOB_ADDR,
+			      EN75XX_DDR_BLOB_SIZE);
+	if (ret)
+		tpl_hang('F');
+
+	en75xx_run_ddr_blob();
+
+	ret = en75xx_sfc_read(EN75XX_SPL_IMAGE_OFFSET, hdr, IH_HDR_SIZE);
+	if (ret || get_be32(hdr->magic) != IH_MAGIC)
+		tpl_hang('H');
+
+	size = get_be32(hdr->size);
+	load = get_be32(hdr->load);
+	ep = get_be32(hdr->ep);
+
+	if (!size || (load & 0xe0000000) != 0x80000000 ||
+	    (ep & 0xe0000000) != 0x80000000 || load + size < load)
+		tpl_hang('L');
+
+	/* Write through KSEG1 so no dirty cache lines hide the SPL image. */
+	ret = en75xx_sfc_read(EN75XX_SPL_IMAGE_OFFSET + IH_HDR_SIZE,
+			      (void *)(load | 0x20000000), size);
+	if (ret)
+		tpl_hang('S');
+
+	__asm__ volatile("sync" : : : "memory");
+	entry = (void (*)(void))ep;
+	entry();
+	__builtin_unreachable();
+}
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.