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