From 8d00362b89543c4035bd8077f0c72d18e52f5f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Fri, 14 Aug 2026 14:11:20 -0400 Subject: [PATCH] Read the TCM configuration off the part, and corrected what we thought we knew The BSP has recorded since bring-up that the TCMs are inaccessible at reset "because nothing has programmed the TCM region registers yet". Reading those registers shows the reason was wrong. ATCM 0x0000011F 64KB, 1 wait state, ENABLED at EL2 and EL1/EL0 BTCM 0x00000014 16KB, 0 wait states, disabled CTCM 0x00000114 16KB, 1 wait state, disabled Every BASEADDRESS field is zero, so ATCM is live as 64KB at address 0x00000000, not at the 0x30000000 the reference manual documents. The reads that faulted were of an address the TCM is not at. ATCM's enables reset set because CFGTCMBOOTx is tied high on this part, which the Cortex-R52 TRM gives as the one exception to "at reset all bits are 0 apart from SIZE and WAITSTATES". BTCM and CTCM really are disabled. The sizes and wait states match the S32Z2 reference manual exactly -- TCMA 64KB with one wait state, TCMB 16KB with none, TCMC 16KB with one -- so the TRM's field layout, NXP's documented configuration and the silicon all agree. That agreement is the point of reading before writing. ECC is implemented and enabled: IMP_MEMPROTCTLR reads 0x00000011, both RAMPROTIMP and RAMPROTEN set. TRM 6.2.2 therefore applies rather than being hypothetical: a TCM location must be written before it is read, or the read reports an error -- which looks exactly like "the TCM is not accessible" and sends the reader back to region registers that were already correct. The preload widths differ too, ATCM needing 64-bit aligned STRD or STM where BTCM and CTCM accept 32-bit stores, so a C loop over unsigned int would leave ATCM's check bits invalid. tcm.c reads and decodes only; nothing is programmed here. The layouts in tcm.h are quoted from TRM r1p3 section 3.3.94 table 3-136 and section 3.3.76 table 3-114, not inferred from a neighbouring register: BASEADDRESS is [31:13] where IMP_PERIPHPREGIONR uses [31:12], and assuming the analogy would have been wrong by one bit in the same way the PRBAR shift was. The boot image reports all of it and flags any size that disagrees with the reference manual, so a part configured differently says so rather than being silently assumed to match this one. Assisted-by: Claude Code (Opus 5) --- .../example_build/s32z280_evb/CMakeLists.txt | 5 + .../gnu/example_build/s32z280_evb/bsp_boot.c | 52 ++++++ .../gnu/example_build/s32z280_evb/platform.h | 26 ++- .../s32z280_evb/readme_s32z280.txt | 21 ++- .../gnu/example_build/s32z280_evb/tcm.c | 154 ++++++++++++++++++ .../gnu/example_build/s32z280_evb/tcm.h | 117 +++++++++++++ 6 files changed, 365 insertions(+), 10 deletions(-) create mode 100644 ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c create mode 100644 ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt b/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt index f36ee715e..2545b3d06 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt @@ -35,6 +35,7 @@ add_executable(s32z280_boot.elf EXCLUDE_FROM_ALL ${EVB_DIR}/gicv3.c ${EVB_DIR}/irq_dispatch.c ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c ) target_include_directories(s32z280_boot.elf PRIVATE ${EVB_DIR}) @@ -67,6 +68,7 @@ add_executable(s32z280_demo.elf EXCLUDE_FROM_ALL ${EVB_DIR}/irq_dispatch.c ${EVB_DIR}/gic_probe.c ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c ${EVB_DIR}/demo_s32z280.c ) @@ -106,6 +108,7 @@ if(TX_R52_ENABLE_VFP) ${EVB_DIR}/irq_dispatch.c ${EVB_DIR}/gic_probe.c ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c ${EVB_DIR}/demo_vfp_s32z280.c ) @@ -143,6 +146,7 @@ if(TX_R52_ENABLE_IRQ_NESTING) ${EVB_DIR}/irq_dispatch.c ${EVB_DIR}/gic_probe.c ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c ${EVB_DIR}/demo_nesting_s32z280.c ) @@ -180,6 +184,7 @@ if(TX_R52_ENABLE_FIQ_NESTING) ${EVB_DIR}/irq_dispatch.c ${EVB_DIR}/gic_probe.c ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c ${EVB_DIR}/demo_fiq_s32z280.c ) diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c index d97ec37a4..8968dfebd 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c @@ -60,6 +60,7 @@ #include "mpu.h" #include "gicv3.h" #include "cache.h" +#include "tcm.h" /* Captured at EL2 by entry.S, before the drop to EL1. */ @@ -346,6 +347,57 @@ void bsp_main(void) linflexd_puts("P1 RTU0.GPR CFG_CNTDV via LLPP\n"); report("CFG_CNTDV", *(volatile unsigned int *)S32Z_RTU0_GPR_CFG_CNTDV); + /* --- TCM: what the hardware reports about itself --------------------- + Reads only. platform.h has recorded since bring-up that the TCM windows + fault at reset, and the Cortex-R52 TRM says why: every bit of the region + registers resets to zero apart from SIZE and WAITSTATES, so the banks sit + at base 0 with both enables clear. This confirms that on the part rather + than trusting it, and cross-checks the reported sizes against the S32Z2 + reference manual, which documents TCMA 64KB, TCMB 16KB and TCMC 16KB. + + Nothing is written here. Enabling TCM also requires initialising the ECC + bits before any read (TRM 6.2.2), with ATCM needing 64-bit aligned stores + where BTCM and CTCM accept 32-bit, so the enable belongs in its own step + once these values are known. */ + + linflexd_puts("T1 TCM configuration as reported by the core\n"); + { + static const char *const bank_name[3] = { "ATCM", "BTCM", "CTCM" }; + static const unsigned long expect_bytes[3] = { 0x10000UL, 0x4000UL, 0x4000UL }; + unsigned int i; + + for (i = 0U; i < 3U; i++) + { + tcm_bank_t bank; + + tcm_read_bank(i, &bank); + + linflexd_puts(bank_name[i]); + linflexd_puts(" raw=0x"); + linflexd_put_hex32((unsigned int) bank.raw); + linflexd_puts(" base=0x"); + linflexd_put_hex32((unsigned int) bank.base); + linflexd_puts(" size=0x"); + linflexd_put_hex32((unsigned int) bank.size_bytes); + linflexd_puts(" ws="); + linflexd_putc((char) ('0' + (char) bank.wait_states)); + linflexd_puts(" en2="); + linflexd_putc((char) ('0' + (char) bank.enable_el2)); + linflexd_puts(" en10="); + linflexd_putc((char) ('0' + (char) bank.enable_el10)); + linflexd_puts((bank.size_bytes == expect_bytes[i]) + ? " size matches the RM\n" + : " SIZE DISAGREES WITH THE RM\n"); + } + + report("MEMPROTCTLR", (unsigned int) tcm_read_memprotctlr()); + linflexd_puts("ECC implemented = "); + linflexd_putc((char) ('0' + (char) tcm_ecc_implemented())); + linflexd_puts(", enabled = "); + linflexd_putc((char) ('0' + (char) tcm_ecc_enabled())); + linflexd_putc('\n'); + } + /* --- MPU, then the GIC ------------------------------------------------ The GIC needs its region mapped Device nGnRnE (Cortex-R52 TRM), which needs the MPU on. Markers around the enable: if the console survives it, diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/platform.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/platform.h index b9bca7dfa..64034344c 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/platform.h +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/platform.h @@ -47,11 +47,27 @@ /* self-booting image loaded from flash would still need the data */ /* alias, since the core's own data side may not write through AXI-F. */ /* */ -/* The TCMs (0x30000000, 0x30100000, 0x30200000) are NOT accessible */ -/* at reset -- reads fault -- because nothing has programmed the TCM */ -/* region registers yet. They are therefore unusable for early boot */ -/* and are deliberately absent from this map. The DDR window at */ -/* 0x7A000000 is likewise dark until DDR is initialised. */ +/* The TCMs need care, and the earlier note here was wrong about why. */ +/* Reads of 0x30000000, 0x30100000 and 0x30200000 do fault, but not */ +/* because the TCMs are disabled. Reading the region registers on */ +/* this part gives: */ +/* */ +/* ATCM 0x0000011F 64KB, 1 wait state, ENABLED at EL2 and EL1/0 */ +/* BTCM 0x00000014 16KB, 0 wait states, disabled */ +/* CTCM 0x00000114 16KB, 1 wait state, disabled */ +/* */ +/* Every BASEADDRESS field is zero, so ATCM is live as 64KB at address */ +/* 0x00000000 -- not at 0x30000000. The faulting reads were of an */ +/* address the TCM is not at. ATCM's enables reset set because */ +/* CFGTCMBOOTx is tied high on this part (Cortex-R52 TRM r1p3 3.3.94). */ +/* The sizes and wait states match the S32Z2 reference manual exactly. */ +/* */ +/* ECC is implemented and enabled: IMP_MEMPROTCTLR reads 0x00000011, */ +/* so RAMPROTIMP and RAMPROTEN are both set. TRM 6.2.2 therefore */ +/* applies -- a TCM location must be written before it is read, and */ +/* ATCM needs 64-bit aligned stores where BTCM and CTCM accept 32-bit. */ +/* */ +/* The DDR window at 0x7A000000 is dark until DDR is initialised. */ /* */ /**************************************************************************/ diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/readme_s32z280.txt b/ports/cortex_r52/gnu/example_build/s32z280_evb/readme_s32z280.txt index f2a3d1b05..d46dfa721 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/readme_s32z280.txt +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/readme_s32z280.txt @@ -73,11 +73,22 @@ model and not an R52 at all. Data, bss and the per-mode stacks live in RTU-local data SRAM at 0x31780000 (256 KB); 0x31800000 gives 2 MB if an image outgrows it. - The TCMs at 0x30000000/0x30100000/0x30200000 are NOT accessible at reset -- - reads fault -- until their region registers are programmed, so nothing - needed for early boot can live there. The DDR window at 0x7A000000 is - likewise dark until DDR is initialised. Both are absent from link.lds on - purpose. + Reads of 0x30000000/0x30100000/0x30200000 do fault, but the earlier note + here had the reason wrong. Reading the region registers on this part shows + ATCM already ENABLED at both exception levels, 64 KB with one wait state, + and every BASEADDRESS field zero -- so ATCM is live at address 0x00000000, + not at 0x30000000. The faulting reads were of an address the TCM is not + at. ATCM's enables reset set because CFGTCMBOOTx is tied high here. BTCM + (16 KB, 0 wait states) and CTCM (16 KB, 1 wait state) really are disabled. + Sizes and wait states agree with the S32Z2 reference manual exactly. + + ECC is implemented and enabled -- IMP_MEMPROTCTLR reads 0x11 -- so per + Cortex-R52 TRM 6.2.2 a TCM location must be written before it is read, and + ATCM needs 64-bit aligned stores where BTCM and CTCM accept 32-bit. Using + TCM therefore takes more than setting an enable bit. + + The DDR window at 0x7A000000 is dark until DDR is initialised. Both TCM + and DDR are absent from link.lds for now. 5. What this image reports diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c new file mode 100644 index 000000000..44f140e91 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c @@ -0,0 +1,154 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* tcm.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Reads what the TCM hardware reports about itself. See tcm.h for */ +/* the register layouts and where they come from; nothing here is */ +/* inferred from a neighbouring register's shape. */ +/* */ +/* Reading only. Programming a base address and setting the enables */ +/* comes after these values have been seen and agree with both the */ +/* Cortex-R52 TRM and the S32Z2 reference manual, because a disagreement*/ +/* would mean one of the two documents does not describe this part and */ +/* guessing which would be the whole PRBAR mistake again. */ +/* */ +/* MISRA C:2012 deviations (justified) */ +/* */ +/* Dir 4.3 -- assembly encapsulated one operation per function. */ +/* */ +/**************************************************************************/ + +#include "tcm.h" + +#define TCM_SIZE_SHIFT 2U +#define TCM_SIZE_MASK 0x1FUL +#define TCM_WAITSTATES_SHIFT 8U +#define TCM_ENABLE_EL2 (1UL << 1) +#define TCM_ENABLE_EL10 (1UL << 0) +#define TCM_BASE_SHIFT 13U /* TRM table 3-136: [31:13] */ + +#define MEMPROT_RAMPROTIMP (1UL << 4) +#define MEMPROT_RAMPROTEN (1UL << 0) + + +static unsigned long read_atcmregionr(void) +{ + unsigned long value; + __asm volatile ("mrc p15, 0, %0, c9, c1, 0" : "=r" (value)); + return value; +} + +static unsigned long read_btcmregionr(void) +{ + unsigned long value; + __asm volatile ("mrc p15, 0, %0, c9, c1, 1" : "=r" (value)); + return value; +} + +static unsigned long read_ctcmregionr(void) +{ + unsigned long value; + __asm volatile ("mrc p15, 0, %0, c9, c1, 2" : "=r" (value)); + return value; +} + + +/* SIZE is an encoding, not a shift count: 4 means 8KB and each step doubles, + so 8KB << (field - 4). Anything outside the documented range yields 0 + rather than a plausible-looking wrong number. */ + +static unsigned long decode_size(unsigned int field) +{ + unsigned long bytes = 0UL; + + if ((field >= 4U) && (field <= 11U)) + { + bytes = 0x2000UL << (field - 4U); + } + + return bytes; +} + + +void tcm_read_bank(unsigned int index, tcm_bank_t *bank_ptr) +{ + unsigned long raw; + + if (bank_ptr == 0) + { + return; + } + + switch (index) + { + case 0U: raw = read_atcmregionr(); break; + case 1U: raw = read_btcmregionr(); break; + case 2U: raw = read_ctcmregionr(); break; + default: raw = 0UL; break; + } + + bank_ptr->raw = raw; + bank_ptr->base = raw & ~((1UL << TCM_BASE_SHIFT) - 1UL); + bank_ptr->size_field = (unsigned int) ((raw >> TCM_SIZE_SHIFT) & TCM_SIZE_MASK); + bank_ptr->size_bytes = decode_size(bank_ptr->size_field); + bank_ptr->wait_states = (unsigned int) ((raw >> TCM_WAITSTATES_SHIFT) & 1UL); + bank_ptr->enable_el2 = ((raw & TCM_ENABLE_EL2) != 0UL) ? 1U : 0U; + bank_ptr->enable_el10 = ((raw & TCM_ENABLE_EL10) != 0UL) ? 1U : 0U; +} + + +unsigned long tcm_read_memprotctlr(void) +{ + unsigned long value; + + /* Note opc1 is 1 here, not 0 as for the region registers. */ + + __asm volatile ("mrc p15, 1, %0, c9, c1, 2" : "=r" (value)); + return value; +} + + +unsigned int tcm_ecc_implemented(void) +{ + return ((tcm_read_memprotctlr() & MEMPROT_RAMPROTIMP) != 0UL) ? 1U : 0U; +} + + +unsigned int tcm_ecc_enabled(void) +{ + unsigned long ctlr = tcm_read_memprotctlr(); + + /* RAMPROTEN is ignored when RAMPROTIMP is 0: the TRM says the design + behaves as if it were 0, so report it that way rather than echoing a + bit that has no effect. */ + + if ((ctlr & MEMPROT_RAMPROTIMP) == 0UL) + { + return 0U; + } + + return ((ctlr & MEMPROT_RAMPROTEN) != 0UL) ? 1U : 0U; +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h new file mode 100644 index 000000000..0c90f71e0 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h @@ -0,0 +1,117 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/**************************************************************************/ +/* */ +/* BOARD SUPPORT RELEASE */ +/* */ +/* tcm.h Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Tightly-coupled memory on the S32Z280. This header is the reading */ +/* half: what the hardware reports about itself. Enabling comes next */ +/* and depends on what these reads say. */ +/* */ +/* WHERE THE FACTS COME FROM */ +/* */ +/* Cortex-R52 TRM r1p3 (100026_0103_00_en), section 3.3.94, table */ +/* 3-136. IMP_ATCMREGIONR, IMP_BTCMREGIONR and IMP_CTCMREGIONR are */ +/* at MRC/MCR p15, 0, , c9, c1, {0,1,2} and are laid out: */ +/* */ +/* [31:13] BASEADDRESS bits [31:13] of the TCM base address */ +/* [12:9] RES0 */ +/* [8] WAITSTATES wait states for TCM accesses */ +/* [7] RES0 */ +/* [6:2] SIZE size indicator, read-only in effect */ +/* [1] ENABLEEL2 enable at EL2 */ +/* [0] ENABLEEL10 enable at EL1 and EL0 */ +/* */ +/* ⚠ BASEADDRESS is [31:13], not [31:12]. IMP_PERIPHPREGIONR uses */ +/* [31:12] and inferring the same here would be wrong by one bit -- */ +/* the same class of mistake that made PRBAR silently drop XN. The */ +/* base is therefore 8KB-aligned, not 4KB. */ +/* */ +/* SIZE encodings: 0 none, 4 8KB, 5 16KB, 6 32KB, 7 64KB, 8 128KB, */ +/* 9 256KB, 10 512KB, 11 1MB. */ +/* */ +/* "At reset all bits are 0 apart from SIZE and WAITSTATES", unless */ +/* CFGTCMBOOTx is high, which resets the ATCM enables to 1. So a */ +/* part that has not been told otherwise has every TCM based at 0 with */ +/* both enables clear, which is exactly why reads of the documented */ +/* TCM windows fault on this board. */ +/* */ +/* IMP_MEMPROTCTLR is at p15, 1, , c9, c1, 2 (TRM 3.3.76, table */ +/* 3-114): RAMPROTIMP [4] says whether RAM protection exists at all, */ +/* RAMPROTEN [0] whether it is on, and RAMPROTEN is ignored when */ +/* RAMPROTIMP is 0. */ +/* */ +/* S32Z2 reference manual: TCMA 64KB with 1 wait state, TCMB 16KB with */ +/* 0, TCMC 16KB with 1, per core. Those give expected SIZE values of */ +/* 7, 5 and 5, which is a cross-check between the two documents and */ +/* the silicon rather than a restatement of either. */ +/* */ +/* WHY ENABLING IS NOT ENOUGH */ +/* */ +/* TRM section 6.2.2: the ECC bits in TCM RAM are not initialised by */ +/* the processor, and a location must be written before it is read */ +/* with ECC enabled. An uninitialised read reports an error, which */ +/* looks exactly like "the TCM is not accessible" and would send the */ +/* reader back to these region registers, which were already right. */ +/* */ +/* The preload widths are not uniform: BTCM and CTCM take STR, STRD */ +/* or STM at 32-bit alignment, but ATCM needs STRD or STM at 64-bit */ +/* alignment. A C loop storing 32-bit words would leave ATCM's check */ +/* bits invalid. */ +/* */ +/**************************************************************************/ + +#ifndef TCM_H +#define TCM_H + +/* One bank's reported configuration, decoded from its region register. */ + +typedef struct +{ + unsigned long raw; /* register as read */ + unsigned long base; /* BASEADDRESS << 13 */ + unsigned int size_field; /* SIZE, as encoded */ + unsigned long size_bytes; /* decoded, 0 if the encoding is unknown */ + unsigned int wait_states; + unsigned int enable_el2; + unsigned int enable_el10; +} tcm_bank_t; + +/* Read and decode one bank. Index 0 is ATCM, 1 BTCM, 2 CTCM; any other + index yields a zeroed structure. */ + +void tcm_read_bank(unsigned int index, tcm_bank_t *bank_ptr); + +/* IMP_MEMPROTCTLR, raw. */ + +unsigned long tcm_read_memprotctlr(void); + +/* Whether RAM protection is implemented, and whether it is enabled. Both are + decoded from IMP_MEMPROTCTLR, and the second is meaningless when the first + is zero -- the TRM says the design behaves as if RAMPROTEN were 0. */ + +unsigned int tcm_ecc_implemented(void); +unsigned int tcm_ecc_enabled(void); + +#endif