[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