tan-cli 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tan/__init__.py +0 -0
- tan/__main__.py +120 -0
- tan/cli.py +939 -0
- tan/command_table.py +265 -0
- tan/commands/__init__.py +8 -0
- tan/commands/bootstrap_cmd.py +4856 -0
- tan/commands/bootstrap_patches.py +164 -0
- tan/commands/bootstrap_toolchain_fetch.py +155 -0
- tan/commands/bootstrap_toolchain_pin.py +36 -0
- tan/commands/build/__init__.py +1 -0
- tan/commands/build/configure_inputs.py +277 -0
- tan/commands/build/execute.py +1852 -0
- tan/commands/build/host_python.py +59 -0
- tan/commands/build/link_stale.py +78 -0
- tan/commands/build/manifest.py +428 -0
- tan/commands/build/materialise.py +81 -0
- tan/commands/build/token_substitution.py +381 -0
- tan/commands/build/toolchain.py +482 -0
- tan/commands/build/user_defines_wipe.py +115 -0
- tan/commands/build/workspace_patches.py +65 -0
- tan/commands/build_cmd.py +2236 -0
- tan/commands/build_output.py +331 -0
- tan/commands/clean_cmd.py +1183 -0
- tan/commands/completion_cmd.py +786 -0
- tan/commands/debug_config_cmd.py +2425 -0
- tan/commands/diff_cmd.py +940 -0
- tan/commands/doctor_cmd.py +4767 -0
- tan/commands/examples_cmd.py +644 -0
- tan/commands/explain_cmd.py +1319 -0
- tan/commands/faultdecode_cmd.py +931 -0
- tan/commands/flash_atoc_guard.py +240 -0
- tan/commands/flash_cmd.py +5731 -0
- tan/commands/flash_mram_guard.py +150 -0
- tan/commands/flash_ram.py +578 -0
- tan/commands/flash_raw.py +583 -0
- tan/commands/generate_cmd.py +1456 -0
- tan/commands/image_cmd.py +753 -0
- tan/commands/init_cmd.py +1740 -0
- tan/commands/inspect_cmd.py +377 -0
- tan/commands/kconfig_cmd.py +734 -0
- tan/commands/model_cmd.py +1514 -0
- tan/commands/model_device_cmd.py +351 -0
- tan/commands/model_device_live.py +234 -0
- tan/commands/model_host_cmd.py +430 -0
- tan/commands/model_zoo_cmd.py +363 -0
- tan/commands/monitor_actions.py +125 -0
- tan/commands/monitor_cmd.py +626 -0
- tan/commands/monitor_session.py +505 -0
- tan/commands/new_som_cmd.py +1522 -0
- tan/commands/pinmux_cmd.py +568 -0
- tan/commands/presets_cmd.py +1123 -0
- tan/commands/probe_cmd.py +421 -0
- tan/commands/reset_cmd.py +354 -0
- tan/commands/reset_confirm.py +168 -0
- tan/commands/run_cmd.py +637 -0
- tan/commands/scaffold_cmd.py +490 -0
- tan/commands/sdk_cmd.py +1723 -0
- tan/commands/size_cmd.py +1020 -0
- tan/commands/support_bundle_cmd.py +1073 -0
- tan/commands/trace_cmd.py +341 -0
- tan/commands/validate_cmd.py +2009 -0
- tan/commands/west_forward_cmd.py +736 -0
- tan/commands/workspace_patch_check.py +296 -0
- tan/core/__init__.py +1 -0
- tan/core/alp_conf_pregen.py +100 -0
- tan/core/artifact_provenance.py +240 -0
- tan/core/atoc_guard.py +276 -0
- tan/core/atoc_guard_messages.py +232 -0
- tan/core/atoc_replacement.py +118 -0
- tan/core/atomic_write.py +178 -0
- tan/core/board_context.py +351 -0
- tan/core/board_diagnostic.py +111 -0
- tan/core/board_files.py +221 -0
- tan/core/board_validator.py +542 -0
- tan/core/board_validator_compat.py +196 -0
- tan/core/board_validator_run.py +129 -0
- tan/core/board_validator_schema.py +186 -0
- tan/core/board_validator_skew.py +46 -0
- tan/core/board_yaml_edit.py +295 -0
- tan/core/board_yaml_pos.py +118 -0
- tan/core/bootstrap.py +2572 -0
- tan/core/build_plan.py +625 -0
- tan/core/consent.py +104 -0
- tan/core/console_filter.py +245 -0
- tan/core/debug_launch.py +1803 -0
- tan/core/devicetree_lint.py +168 -0
- tan/core/dir_removal.py +229 -0
- tan/core/doctor_git.py +172 -0
- tan/core/doctor_libraries.py +253 -0
- tan/core/doctor_render.py +185 -0
- tan/core/doctor_scope.py +66 -0
- tan/core/doctor_setools.py +237 -0
- tan/core/doctor_stale.py +180 -0
- tan/core/document_guards.py +723 -0
- tan/core/dp_id.py +97 -0
- tan/core/elf_load.py +63 -0
- tan/core/error_catalog.py +171 -0
- tan/core/example_catalog.py +499 -0
- tan/core/example_facets.py +151 -0
- tan/core/faultdecode.py +521 -0
- tan/core/flash_plan.py +2501 -0
- tan/core/flow_d_report.py +381 -0
- tan/core/flow_d_uptime.py +164 -0
- tan/core/fs_confine.py +130 -0
- tan/core/global_flags.py +230 -0
- tan/core/host_python.py +210 -0
- tan/core/image_bundle.py +247 -0
- tan/core/inert.py +147 -0
- tan/core/jlink_binary.py +128 -0
- tan/core/jlink_probe.py +481 -0
- tan/core/jsonc_splice.py +404 -0
- tan/core/launch_provenance.py +257 -0
- tan/core/link_refusal.py +76 -0
- tan/core/metadata_schema.py +376 -0
- tan/core/model_check.py +227 -0
- tan/core/model_device.py +449 -0
- tan/core/model_doctor.py +198 -0
- tan/core/model_host.py +48 -0
- tan/core/model_list.py +112 -0
- tan/core/model_zoo.py +353 -0
- tan/core/module_template.py +340 -0
- tan/core/mram_link.py +87 -0
- tan/core/new_som_ospi.py +94 -0
- tan/core/os_class.py +159 -0
- tan/core/patch_paths.py +87 -0
- tan/core/pending.py +40 -0
- tan/core/plain_zephyr_plan.py +138 -0
- tan/core/plan_exec.py +373 -0
- tan/core/plan_tokens.py +600 -0
- tan/core/probe.py +155 -0
- tan/core/probe_plan.py +237 -0
- tan/core/proxy.py +168 -0
- tan/core/publish.py +31 -0
- tan/core/python_floor.py +178 -0
- tan/core/ram_run.py +694 -0
- tan/core/ram_watch.py +231 -0
- tan/core/raw_write.py +160 -0
- tan/core/reset_plan.py +56 -0
- tan/core/run.py +161 -0
- tan/core/scaffold.py +2203 -0
- tan/core/scaffold_selftest_identity.py +225 -0
- tan/core/sdk_default_registry.py +565 -0
- tan/core/sdk_discovery.py +996 -0
- tan/core/sdk_removal.py +465 -0
- tan/core/serial_actions.py +175 -0
- tan/core/serial_capture.py +185 -0
- tan/core/serial_url.py +45 -0
- tan/core/setools.py +608 -0
- tan/core/setools_scratch.py +243 -0
- tan/core/shapes.py +194 -0
- tan/core/size.py +610 -0
- tan/core/som_buildability.py +204 -0
- tan/core/som_schema_version.py +62 -0
- tan/core/subprocess_env.py +201 -0
- tan/core/sysbuild_image.py +94 -0
- tan/core/system_manifest.py +827 -0
- tan/core/text_layout.py +73 -0
- tan/core/timestamp.py +182 -0
- tan/core/tool_lookup.py +383 -0
- tan/core/toolchain_pin.py +117 -0
- tan/core/toolchain_provision.py +1224 -0
- tan/core/trusted_path.py +134 -0
- tan/core/uboot_breakin.py +154 -0
- tan/core/uri_reference.py +415 -0
- tan/core/user_defines.py +152 -0
- tan/core/venv.py +332 -0
- tan/core/west_patches.py +143 -0
- tan/core/west_workspace_refusal.py +159 -0
- tan/core/zephyr_env.py +124 -0
- tan/env.py +185 -0
- tan/envelope.py +647 -0
- tan/exit_codes.py +12 -0
- tan/lazy_typer.py +185 -0
- tan/model/__init__.py +2 -0
- tan/model/_gen_fixture.py +132 -0
- tan/model/adapters/__init__.py +116 -0
- tan/model/adapters/cpu.py +26 -0
- tan/model/adapters/deepx.py +94 -0
- tan/model/adapters/drpai.py +242 -0
- tan/model/adapters/ethos_u.py +951 -0
- tan/model/adapters/executorch.py +55 -0
- tan/model/analyze.py +694 -0
- tan/model/build.py +304 -0
- tan/model/check.py +682 -0
- tan/model/manifest.py +625 -0
- tan/model/measure.py +216 -0
- tan/model/npu_ops_validate.py +130 -0
- tan/model/package.py +125 -0
- tan/model/perf.py +635 -0
- tan/model/perf_apply.py +721 -0
- tan/model/prep.py +193 -0
- tan/model/sram_fit.py +502 -0
- tan/model/targets.py +374 -0
- tan/model/tensorio.py +287 -0
- tan/net.py +73 -0
- tan/output_format.py +213 -0
- tan/planner/__init__.py +219 -0
- tan/planner/aperture.py +202 -0
- tan/planner/buildplan.py +1092 -0
- tan/planner/camera_owner.py +192 -0
- tan/planner/cameras.py +132 -0
- tan/planner/carveout.py +755 -0
- tan/planner/cli.py +274 -0
- tan/planner/headers.py +315 -0
- tan/planner/kconfig.py +2435 -0
- tan/planner/kconfig_symbols.py +473 -0
- tan/planner/libraries.py +677 -0
- tan/planner/link_target.py +237 -0
- tan/planner/linux_ownership.py +216 -0
- tan/planner/loader.py +1525 -0
- tan/planner/manifest.py +116 -0
- tan/planner/memory.py +161 -0
- tan/planner/memregion.py +56 -0
- tan/planner/models.py +533 -0
- tan/planner/orchestrator.py +874 -0
- tan/planner/ownership.py +203 -0
- tan/planner/partition.py +1160 -0
- tan/planner/paths.py +32 -0
- tan/planner/plain_slice.py +139 -0
- tan/planner/project_emit/__init__.py +60 -0
- tan/planner/project_emit/bom_netlist.py +375 -0
- tan/planner/project_emit/dts.py +580 -0
- tan/planner/project_emit/hw_info.py +221 -0
- tan/planner/project_emit/native_sim.py +136 -0
- tan/planner/project_emit/west_libs.py +213 -0
- tan/planner/project_loader.py +264 -0
- tan/planner/sdk_capability.py +203 -0
- tan/planner/sdk_compat.py +395 -0
- tan/planner/secure.py +350 -0
- tan/planner/slugs.py +394 -0
- tan/planner/som_metadata.py +325 -0
- tan/planner/strict_loaders.py +131 -0
- tan/planner/template.py +1037 -0
- tan/planner/template_pins.py +535 -0
- tan/planner/template_rewrite.py +714 -0
- tan/planner/topology.py +208 -0
- tan/planner/validate.py +365 -0
- tan/planner/whole_device_alias.py +67 -0
- tan/planner/zephyr_board.py +3510 -0
- tan/planner_cli.py +97 -0
- tan/planner_emit.py +617 -0
- tan/planner_root.py +119 -0
- tan/soc_ref.py +58 -0
- tan/templates/__init__.py +34 -0
- tan/templates/vendored/MANIFEST.md +1862 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
- tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
- tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
- tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
- tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
- tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
- tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
- tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
- tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
- tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
- tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
- tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
- tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
- tan/version.py +102 -0
- tan_cli-0.7.0.dist-info/METADATA +125 -0
- tan_cli-0.7.0.dist-info/RECORD +330 -0
- tan_cli-0.7.0.dist-info/WHEEL +5 -0
- tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
- tan_cli-0.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,3510 @@
|
|
|
1
|
+
# Copyright (c) 2026 Alp Lab AB
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""
|
|
4
|
+
`--emit zephyr-board` -- generate the per-core Zephyr board tree
|
|
5
|
+
(`board.yml`, `Kconfig.alp_<board>`, the twister board metadata `.yaml`,
|
|
6
|
+
and -- where the underlying hardware facts already exist in metadata --
|
|
7
|
+
the `_defconfig`, pinctrl `.dtsi`, `Kconfig.defconfig`, and board `.dts`)
|
|
8
|
+
straight from the SoM preset + SoC JSON, instead of hand-authoring them
|
|
9
|
+
under `zephyr/boards/alp/<board>/` (issue #523).
|
|
10
|
+
|
|
11
|
+
RELOCATED (was alp-sdk `scripts/gen_zephyr_board.py`). Reached as:
|
|
12
|
+
|
|
13
|
+
tan generate --target zephyr-board --core <core_id> \\
|
|
14
|
+
--output build/boards/<board_dir>/
|
|
15
|
+
|
|
16
|
+
Unlike every other `--emit` mode this one writes a *directory* of files,
|
|
17
|
+
not a single stream -- `tan.planner_emit.render_tree` / `write_tree` handle
|
|
18
|
+
that distinction (as `alp_project.py` did); this module only returns
|
|
19
|
+
``{relative_path: content}`` (paths relative to the board-tree root, e.g.
|
|
20
|
+
``"alp_e1m_aen801_m55_hp/board.yml"``).
|
|
21
|
+
|
|
22
|
+
Two mechanical changes came with the move, both on failure paths only: the SoM
|
|
23
|
+
preset and the pinmux YAML are read through `tan.planner`'s own loader, so a
|
|
24
|
+
missing preset or an unparseable YAML raises `OrchestratorError` where alp-sdk's
|
|
25
|
+
wrapper called `sys.exit` -- a raise is reportable as a coded envelope, and a
|
|
26
|
+
`SystemExit` inside `tan`'s own process is not. The generated bytes are
|
|
27
|
+
unchanged, pinned by `tests/parity/test_planner_emit_parity.py`.
|
|
28
|
+
|
|
29
|
+
A third is import-shaped: alp-sdk's `scripts/sentinels.py` has no counterpart
|
|
30
|
+
under `tan/planner/`, so the `is_tbd()` this module calls is spelled as the
|
|
31
|
+
local `_is_tbd` below -- same normalisation, same semantics.
|
|
32
|
+
|
|
33
|
+
Scope (issue #523, first slice; extended by #655 slice 1 and alp-sdk#1964,
|
|
34
|
+
both hand-ported here as tan-cli#1156):
|
|
35
|
+
|
|
36
|
+
- Alif Ensemble (the `aen` family, e.g. E1M-AEN801 m55_hp/m55_he) is
|
|
37
|
+
fully generated: every file in the hand-authored board tree except
|
|
38
|
+
`board.cmake` and `Kconfig` (see NOT GENERATED below) is produced
|
|
39
|
+
here, byte-identical to the committed tree -- proven by alp-sdk's
|
|
40
|
+
`tests/scripts/test_gen_zephyr_board.py`, whose `HAND_MAINTAINED`
|
|
41
|
+
exemption set is exactly those two names.
|
|
42
|
+
|
|
43
|
+
Its pinctrl `.dtsi` and `.dts` take a THIRD source alongside the SoM
|
|
44
|
+
preset + SoC JSON: `metadata/e1m_modules/aen/on-module-links.yaml`,
|
|
45
|
+
the authority for pads that reach no E1M edge pin (BRD_I2C on
|
|
46
|
+
P7_0/P7_1 and the RV-3028 alarm on P15_0) and for the devices that
|
|
47
|
+
hang off them. `metadata/pinmux/aen.yaml` cannot carry those: it is
|
|
48
|
+
a generated projection of the EDGE pad TSVs, so an on-module pad has
|
|
49
|
+
no row there by construction. Same shape, and same reasoning, as
|
|
50
|
+
`supervisor-links.yaml` below.
|
|
51
|
+
- Renesas RZ/V2N-family boards (`v2n` / `v2n-m1`, e.g. E1M-V2N101,
|
|
52
|
+
E1M-V2M101 `m33_sm`) generate the family-agnostic files (`board.yml`,
|
|
53
|
+
`Kconfig.alp_<board>`, the twister `.yaml`) PLUS the pinctrl `.dtsi`
|
|
54
|
+
and `_defconfig`, sourced from
|
|
55
|
+
`metadata/e1m_modules/v2n/supervisor-links.yaml` (#655) -- a
|
|
56
|
+
hand-authored, family-scoped source for the Renesas-side pin
|
|
57
|
+
assignments of the on-module GD32G553 supervisor bridge (SCI7
|
|
58
|
+
Simple-SPI, RIIC8/BRD_I2C, the disabled SCI0 console).
|
|
59
|
+
|
|
60
|
+
That source was deliberately NOT placed under
|
|
61
|
+
`metadata/pinmux/<family>.yaml`, despite that tree already carrying
|
|
62
|
+
every fact these two files need: `metadata/pinmux/*.yaml` is itself
|
|
63
|
+
GENERATED DO-NOT-EDIT output of `scripts/gen_pinmux_capability.py`
|
|
64
|
+
(from the family's TSVs), so it cannot also be a hand-authored
|
|
65
|
+
generation SOURCE without becoming circular (a generator reading its
|
|
66
|
+
own generated output as ground truth). `supervisor-links.yaml`
|
|
67
|
+
instead REFERENCES `metadata/pinmux/v2n.yaml` by
|
|
68
|
+
(silicon_peripheral, silicon_pad) -- alp-sdk's `scripts/
|
|
69
|
+
validate_metadata.py` cross-checks every row resolves to exactly one
|
|
70
|
+
`owner: "renesas"` pad there -- rather than re-declaring pad<->signal
|
|
71
|
+
authority a second time.
|
|
72
|
+
|
|
73
|
+
The board `.dts` is ALSO generated as of #655 slice 2 (alp-sdk
|
|
74
|
+
`e4f2f60e4`): `emit_zephyr_board()`'s `v2n`/`v2n-m1` branch calls
|
|
75
|
+
`_v2n_dts()`, which reuses the upstream RZ/V2N SoC devicetree and adds
|
|
76
|
+
the same on-module GD32G553 supervisor links `_v2n_pinctrl_dtsi()`
|
|
77
|
+
reads. A SoM preset's `topology.m33_sm.openamp_ipc` (boolean, absent
|
|
78
|
+
means false) gates the OpenAMP/MHU-B reserved-memory block and the
|
|
79
|
+
CAN-FD-unavailable analysis -- E1M-V2N101 and E1M-V2M101 both set it
|
|
80
|
+
(the same RZ/V2N die and MHU-B, #1948); a preset that leaves it false
|
|
81
|
+
gets neither block. `Kconfig.defconfig` remains
|
|
82
|
+
the one hand-authored file in this family (no metadata source for it
|
|
83
|
+
yet).
|
|
84
|
+
|
|
85
|
+
NOT GENERATED (any family): `board.cmake` and a bare `Kconfig`.
|
|
86
|
+
BOTH must be copied across by hand when a generated board tree is
|
|
87
|
+
used as the board directory -- nothing in the emit, the CLI output,
|
|
88
|
+
or the JSON envelope says so, which is why they are named here.
|
|
89
|
+
|
|
90
|
+
- `board.cmake`. The hand-authored AEN pair (HP/HE) intentionally
|
|
91
|
+
carries asymmetric prose -- one board's file hosts the full
|
|
92
|
+
SETOOLS/J-Link bench-bring-up runbook, the sibling's just points at
|
|
93
|
+
it -- which is a documentation choice, not a hardware fact, so
|
|
94
|
+
there is nothing in metadata for it to derive from.
|
|
95
|
+
Flasher/debugger `board_runner_args()` wiring stays hand-authored
|
|
96
|
+
until that asymmetry is either resolved (duplicate the full
|
|
97
|
+
rationale in both) or the prose itself is promoted into a metadata
|
|
98
|
+
field. Without it a board has no runner, so `west flash` has
|
|
99
|
+
nothing to flash with.
|
|
100
|
+
- `Kconfig` (the bare name, NOT the generated `Kconfig.<board>` /
|
|
101
|
+
`Kconfig.defconfig`). It sets
|
|
102
|
+
`CPU_HAS_CUSTOM_FIXED_SOC_MPU_REGIONS default y`, which
|
|
103
|
+
`zephyr/CMakeLists.txt` uses to pull in the custom E8 MPU region
|
|
104
|
+
table (`zephyr/soc-bridge/alif/mpu_regions_e8.c`) via
|
|
105
|
+
`zephyr_sources_ifdef`. Zephyr's `hwm_v2.cmake` unconditionally
|
|
106
|
+
`osource`s a bare `Kconfig` in a board directory whether or not it
|
|
107
|
+
exists, so it survives regeneration exactly as `board.cmake` does
|
|
108
|
+
-- and its absence is SILENT: the build succeeds and falls back to
|
|
109
|
+
Zephyr's generic 2-region FLASH_0/SRAM_0 map, whose whole-flash
|
|
110
|
+
FLASH_0 region is unsafe on Flow D (production MRAM boot).
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
from __future__ import annotations
|
|
114
|
+
|
|
115
|
+
import re
|
|
116
|
+
import textwrap
|
|
117
|
+
from pathlib import Path
|
|
118
|
+
from typing import Any, NoReturn
|
|
119
|
+
|
|
120
|
+
# NO module-scope `import json` any more (tan-cli#1162): `_load_soc_spec` was
|
|
121
|
+
# its only user and now reads through `document_guards.read_json_mapping`.
|
|
122
|
+
from tan.core.document_guards import DocumentGuards
|
|
123
|
+
|
|
124
|
+
from .loader import _load_yaml
|
|
125
|
+
from .project_loader import _resolve_sku
|
|
126
|
+
from .sdk_capability import (
|
|
127
|
+
AEN_ATOC_RESERVATION,
|
|
128
|
+
AEN_SOM_POWER_DOMAINS,
|
|
129
|
+
AEN_ZEPHYR_PERIPHERALS_DTSI,
|
|
130
|
+
require_capability,
|
|
131
|
+
)
|
|
132
|
+
from .som_metadata import resolve_soc_path
|
|
133
|
+
from .whole_device_alias import is_whole_device_alias
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _is_tbd(value: Any) -> bool:
|
|
137
|
+
"""True iff @p value is the `TBD` placeholder sentinel.
|
|
138
|
+
|
|
139
|
+
RELOCATED spelling of alp-sdk `scripts/sentinels.py::is_tbd` (alp-sdk
|
|
140
|
+
#1048), which has no counterpart under `tan/planner/`: it is imported
|
|
141
|
+
upstream from both `scripts/` and `alp_orchestrate/`, and relocating it
|
|
142
|
+
would add a module to the hand-port audit for one three-line function.
|
|
143
|
+
Compares case- and surrounding-whitespace-insensitively (`"tbd"`,
|
|
144
|
+
`"Tbd"`, `" TBD "` all match) but only on an exact match after that
|
|
145
|
+
normalisation -- `"TBD_RESERVED"` is a real value, not a placeholder.
|
|
146
|
+
"""
|
|
147
|
+
return isinstance(value, str) and value.strip().upper() == "TBD"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
_COPYRIGHT_C = (
|
|
151
|
+
"/*\n"
|
|
152
|
+
" * Copyright (c) 2026 Alp Lab AB\n"
|
|
153
|
+
" * SPDX-License-Identifier: Apache-2.0\n"
|
|
154
|
+
)
|
|
155
|
+
_COPYRIGHT_HASH = (
|
|
156
|
+
"# Copyright (c) 2026 Alp Lab AB\n"
|
|
157
|
+
"# SPDX-License-Identifier: Apache-2.0\n"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class ZephyrBoardEmitError(ValueError):
|
|
162
|
+
"""Raised when metadata needed for a requested board is missing."""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
#: The malformed-document register, bound to THIS module's curated class.
|
|
166
|
+
_GUARDS = DocumentGuards(ZephyrBoardEmitError)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------
|
|
170
|
+
# Family-agnostic naming + resolution
|
|
171
|
+
# ---------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _load_soc_spec(sku_preset: dict[str, Any], metadata_root: Path) -> dict[str, Any]:
|
|
175
|
+
"""The SoC JSON this SoM's `silicon:` ref names, as a mapping.
|
|
176
|
+
|
|
177
|
+
RELOCATED divergence from alp-sdk's own `scripts/alp_orchestrate/
|
|
178
|
+
zephyr_board.py` (tan-cli#1162): upstream still spells the last two
|
|
179
|
+
lines as an `is_file()` pre-flight plus a bare `json.loads(soc_path.
|
|
180
|
+
read_text(...))`. Read, parse and shape-check now go through
|
|
181
|
+
`tan/core/document_guards.py`'s register, for the reasons
|
|
182
|
+
`read_json_mapping` documents.
|
|
183
|
+
|
|
184
|
+
NOT on the `tan build` path, contrary to tan-cli#1162's own body, and
|
|
185
|
+
established by walking callers rather than assumed: the only caller is
|
|
186
|
+
`emit_zephyr_board` (this module), reached only through
|
|
187
|
+
`planner_emit.py:287`, so only from `tan generate --emit zephyr-board`.
|
|
188
|
+
`loader.py:385` and `secure.py:102` take `_aen_role_slot0_map` and two
|
|
189
|
+
constants from this module, never this function. The handler that
|
|
190
|
+
actually catches what this raises is `generate_cmd.py:890`'s
|
|
191
|
+
`except BaseException` -- coded, not a bare traceback, but naming the
|
|
192
|
+
exception class instead of the file.
|
|
193
|
+
|
|
194
|
+
`no SoC spec at <path>` is preserved byte for byte through `absent=`;
|
|
195
|
+
the shapes the pre-flight folded into it (a directory, a denied mode, a
|
|
196
|
+
symlink loop) now say `cannot read`, which is true where it was not.
|
|
197
|
+
"""
|
|
198
|
+
silicon = sku_preset.get("silicon")
|
|
199
|
+
if not silicon:
|
|
200
|
+
raise ZephyrBoardEmitError(f"SoM {sku_preset.get('sku')!r} declares no silicon:")
|
|
201
|
+
soc_path = resolve_soc_path(silicon, metadata_root)
|
|
202
|
+
if soc_path is None:
|
|
203
|
+
raise ZephyrBoardEmitError(f"silicon ref {silicon!r} is not a triple-colon string")
|
|
204
|
+
return _GUARDS.read_json_mapping(
|
|
205
|
+
soc_path, what="SoC spec", absent=f"no SoC spec at {soc_path}")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _resolve_variant(sku_preset: dict[str, Any], soc_spec: dict[str, Any]) -> dict[str, Any]:
|
|
209
|
+
"""Resolve the SoM preset's silicon variant against the SoC JSON.
|
|
210
|
+
|
|
211
|
+
A DECLARED `silicon_variant:` is authoritative and must name a real
|
|
212
|
+
`variants[].order_code` -- `docs/porting-new-som.md` says so. When
|
|
213
|
+
it names none this raises instead of falling through to the
|
|
214
|
+
`alp_module_skus` reverse lookup: silently answering with a
|
|
215
|
+
different variant is how a typo (`...LS0` -> `...LSO`) or an
|
|
216
|
+
un-published silicon revision emits a whole board tree named after
|
|
217
|
+
a part number the preset does not declare, carrying the OLD
|
|
218
|
+
variant's `mram_mb` / DTCM size / board-target name -- exit 0, no
|
|
219
|
+
diagnostic. Every other missing fact in this module raises; so
|
|
220
|
+
does this one. The reverse lookup stays for presets that declare
|
|
221
|
+
no variant at all (or `TBD`).
|
|
222
|
+
"""
|
|
223
|
+
declared = sku_preset.get("silicon_variant")
|
|
224
|
+
variants = soc_spec.get("variants") or []
|
|
225
|
+
if declared and not _is_tbd(declared):
|
|
226
|
+
for v in variants:
|
|
227
|
+
if v.get("order_code") == declared:
|
|
228
|
+
return v
|
|
229
|
+
known = ", ".join(str(v.get("order_code")) for v in variants) or "<none>"
|
|
230
|
+
raise ZephyrBoardEmitError(
|
|
231
|
+
f"SoM {sku_preset.get('sku')!r} declares silicon_variant "
|
|
232
|
+
f"{declared!r}, which is not an order_code in "
|
|
233
|
+
f"{soc_spec.get('ref')} variants[] ({known}) -- fix the preset, "
|
|
234
|
+
"or add the variant to the SoC JSON if this is new silicon")
|
|
235
|
+
sku = sku_preset.get("sku")
|
|
236
|
+
for v in variants:
|
|
237
|
+
if sku in (v.get("alp_module_skus") or []):
|
|
238
|
+
return v
|
|
239
|
+
raise ZephyrBoardEmitError(
|
|
240
|
+
f"can't resolve a silicon_variant for {sku_preset.get('sku')!r}")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _find_core(soc_spec: dict[str, Any], core_id: str) -> dict[str, Any]:
|
|
244
|
+
for c in soc_spec.get("cores", []):
|
|
245
|
+
if c.get("id") == core_id:
|
|
246
|
+
return c
|
|
247
|
+
raise ZephyrBoardEmitError(
|
|
248
|
+
f"core {core_id!r} not found in {soc_spec.get('ref')} cores[]")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _board_dir_name(sku: str, core_id: str) -> str:
|
|
252
|
+
"""`E1M-AEN801` + `m55_hp` -> `alp_e1m_aen801_m55_hp`."""
|
|
253
|
+
if not sku.startswith("E1M-"):
|
|
254
|
+
raise ZephyrBoardEmitError(f"unrecognised SKU prefix: {sku!r}")
|
|
255
|
+
slug = sku[len("E1M-"):].lower()
|
|
256
|
+
return f"alp_e1m_{slug}_{core_id}"
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _board_naming(
|
|
260
|
+
sku_preset: dict[str, Any], core_id: str, variant: dict[str, Any], core: dict[str, Any],
|
|
261
|
+
) -> tuple[str, str, str, str]:
|
|
262
|
+
"""Return (dir_name, cluster, variant_tag, basename)."""
|
|
263
|
+
dir_name = _board_dir_name(sku_preset["sku"], core_id)
|
|
264
|
+
cluster = core.get("zephyr_cpucluster")
|
|
265
|
+
if not cluster:
|
|
266
|
+
raise ZephyrBoardEmitError(
|
|
267
|
+
f"core {core_id!r} has no zephyr_cpucluster in its SoC spec entry -- "
|
|
268
|
+
"add one before generating this board (see soc-spec-v1.schema.json)")
|
|
269
|
+
variant_tag = (variant.get("zephyr_soc_variant") or variant["order_code"]).lower()
|
|
270
|
+
basename = f"{dir_name}_{variant_tag}_{cluster}"
|
|
271
|
+
return dir_name, cluster, variant_tag, basename
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _topology_entry(sku_preset: dict[str, Any], core_id: str) -> dict[str, Any]:
|
|
275
|
+
topo = (sku_preset.get("topology") or {}).get(core_id)
|
|
276
|
+
if not topo:
|
|
277
|
+
raise ZephyrBoardEmitError(
|
|
278
|
+
f"SoM {sku_preset.get('sku')!r} has no topology.{core_id} entry")
|
|
279
|
+
return topo
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _generated_banner(
|
|
283
|
+
style: str, sku: str, soc_json_rel: str, extra_source: str | None = None,
|
|
284
|
+
) -> list[str]:
|
|
285
|
+
"""3-line "auto-generated, do not edit" banner, matching the wording
|
|
286
|
+
convention `gen_soc_caps.py` uses for `include/alp/soc_caps.h`.
|
|
287
|
+
|
|
288
|
+
*extra_source* names an additional generation-authority file beyond the
|
|
289
|
+
SoM preset + SoC JSON -- the V2N/V2M pinctrl.dtsi/_defconfig read
|
|
290
|
+
`metadata/e1m_modules/v2n/supervisor-links.yaml` for every pad, PFC
|
|
291
|
+
triple and I2C address they emit, and a banner that omits it sends a
|
|
292
|
+
maintainer to two files that carry neither."""
|
|
293
|
+
c = " *" if style == "c" else "#"
|
|
294
|
+
sku_rel = f"metadata/e1m_modules/{sku}.yaml"
|
|
295
|
+
sources = f"{sku_rel} + {soc_json_rel}"
|
|
296
|
+
if extra_source:
|
|
297
|
+
sources += f" + {extra_source}"
|
|
298
|
+
return [
|
|
299
|
+
f"{c} Auto-generated by `scripts/alp_project.py --emit zephyr-board` from",
|
|
300
|
+
f"{c} {sources}.",
|
|
301
|
+
f"{c} DO NOT EDIT BY HAND -- regenerate.",
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _with_generated_banner(
|
|
306
|
+
content: str, style: str, sku: str, soc_json_rel: str,
|
|
307
|
+
extra_source: str | None = None,
|
|
308
|
+
) -> str:
|
|
309
|
+
"""Insert the generated-file banner into *content*.
|
|
310
|
+
|
|
311
|
+
- When *content* already opens with the standard Copyright/SPDX
|
|
312
|
+
header (every AEN file; the V2N-family Kconfig/twister files),
|
|
313
|
+
the banner is inserted right after the SPDX line, ahead of the
|
|
314
|
+
file's own prose -- purely additive, no existing line moves or
|
|
315
|
+
disappears.
|
|
316
|
+
- When *content* has no header at all (the V2N-family `board.yml`,
|
|
317
|
+
matching its committed hand-authored original -- see `_board_yml`),
|
|
318
|
+
the banner is prepended as a leading comment block instead.
|
|
319
|
+
"""
|
|
320
|
+
lines = content.split("\n")
|
|
321
|
+
banner = _generated_banner(style, sku, soc_json_rel, extra_source)
|
|
322
|
+
if style == "c" and len(lines) >= 3 and lines[0] == "/*" and lines[1].startswith(" * Copyright"):
|
|
323
|
+
lines[3:3] = banner
|
|
324
|
+
return "\n".join(lines)
|
|
325
|
+
if style == "hash" and lines and lines[0].startswith("# Copyright"):
|
|
326
|
+
lines[2:2] = banner
|
|
327
|
+
return "\n".join(lines)
|
|
328
|
+
# No pre-existing header: prepend the banner as its own block.
|
|
329
|
+
return "\n".join(banner) + "\n" + content
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _sku_family_slug(sku: str) -> str:
|
|
333
|
+
"""AEN/V2N/V2M -- see `som_metadata._sku_family` (kept independent here, as
|
|
334
|
+
it was in alp-sdk, so this module carries no import-cycle risk on the fuller
|
|
335
|
+
SKU-family table; the two must agree, pinned by the test)."""
|
|
336
|
+
m = re.match(r"^E1M-(AEN|V2N|V2M)", sku)
|
|
337
|
+
if not m:
|
|
338
|
+
raise ZephyrBoardEmitError(f"unrecognised SoM SKU pattern: {sku!r}")
|
|
339
|
+
return {"AEN": "aen", "V2N": "v2n", "V2M": "v2n-m1"}[m.group(1)]
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# ---------------------------------------------------------------------
|
|
343
|
+
# Family-agnostic files: board.yml, Kconfig.alp_<board>, twister .yaml
|
|
344
|
+
# ---------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _board_yml(dir_name: str, full_name: str, variant_tag: str, family: str) -> str:
|
|
348
|
+
# Hand-authored inconsistency across families: AEN's board.yml carries
|
|
349
|
+
# the copyright header, the V2N-family one doesn't. Preserved exactly
|
|
350
|
+
# rather than "fixed", per the byte-equivalence pin -- see the
|
|
351
|
+
# `--emit zephyr-board` module docstring.
|
|
352
|
+
header = _COPYRIGHT_HASH if family == "aen" else ""
|
|
353
|
+
return (
|
|
354
|
+
header +
|
|
355
|
+
"board:\n"
|
|
356
|
+
f" name: {dir_name}\n"
|
|
357
|
+
f" full_name: {full_name}\n"
|
|
358
|
+
" vendor: alp\n"
|
|
359
|
+
" socs:\n"
|
|
360
|
+
f" - name: {variant_tag}\n"
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _kconfig_board(dir_name: str, variant_tag: str, cluster: str) -> str:
|
|
365
|
+
soc_sym = f"SOC_{variant_tag.upper()}_{cluster.upper()}"
|
|
366
|
+
board_sym = dir_name.upper()
|
|
367
|
+
return (
|
|
368
|
+
_COPYRIGHT_HASH +
|
|
369
|
+
"\n"
|
|
370
|
+
f"config BOARD_{board_sym}\n"
|
|
371
|
+
f"\tselect {soc_sym}\n"
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _aen_dtcm_kib(variant: dict[str, Any], core_id: str) -> int:
|
|
376
|
+
role = core_id.split("_")[-1].upper() # "m55_hp" -> "HP"
|
|
377
|
+
banks = variant.get("sram_banks_kb") or {}
|
|
378
|
+
for name, size in banks.items():
|
|
379
|
+
if name.endswith(f"_{role}_DTCM"):
|
|
380
|
+
return int(size)
|
|
381
|
+
raise ZephyrBoardEmitError(
|
|
382
|
+
f"variant {variant.get('order_code')} sram_banks_kb has no *_{role}_DTCM entry")
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _twister_yaml(
|
|
386
|
+
dir_name: str, variant_tag: str, cluster: str, twister_name: str,
|
|
387
|
+
family: str, sku_preset: dict[str, Any], core_id: str, variant: dict[str, Any],
|
|
388
|
+
) -> str:
|
|
389
|
+
identifier = f"{dir_name}/{variant_tag}/{cluster}"
|
|
390
|
+
has_supervisor_mcu = bool((sku_preset.get("on_module") or {}).get("supervisor_mcu"))
|
|
391
|
+
|
|
392
|
+
if family == "aen":
|
|
393
|
+
ram_kib = _aen_dtcm_kib(variant, core_id)
|
|
394
|
+
flash_kib = round(float(variant["mram_mb"]) * 1024)
|
|
395
|
+
return (
|
|
396
|
+
f"identifier: {identifier}\n"
|
|
397
|
+
f"name: {twister_name}\n"
|
|
398
|
+
"type: mcu\n"
|
|
399
|
+
"arch: arm\n"
|
|
400
|
+
"vendor: alp\n"
|
|
401
|
+
"toolchain:\n"
|
|
402
|
+
" - zephyr\n"
|
|
403
|
+
" - gnuarmemb\n"
|
|
404
|
+
"supported:\n"
|
|
405
|
+
" - uart\n"
|
|
406
|
+
f"ram: {ram_kib}\n"
|
|
407
|
+
f"flash: {flash_kib}\n"
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# Non-AEN families: no on-die MRAM/TCM concept surfaced here (the A55
|
|
411
|
+
# cluster boots Linux from eMMC/xSPI; the M33 board file has no
|
|
412
|
+
# zephyr,flash of its own), so no ram:/flash:. supported: lists only
|
|
413
|
+
# what the board dts leaves `status = "okay"` (audit CM33-14): the GD32
|
|
414
|
+
# bridge's SCI7 Simple-SPI plus its GPIO chip-select port. RIIC8
|
|
415
|
+
# (A55/Linux-exclusive) and the sci0 console UART are disabled, so
|
|
416
|
+
# i2c/uart are not advertised. Same PCB, same GD32 bridge on every
|
|
417
|
+
# current V2N-family SoM (porting-a-new-som "same-PCB families" rule).
|
|
418
|
+
supported = ["gpio", "spi"] if has_supervisor_mcu else []
|
|
419
|
+
lines = [
|
|
420
|
+
f"identifier: {identifier}",
|
|
421
|
+
f"name: {twister_name}",
|
|
422
|
+
"type: mcu",
|
|
423
|
+
"arch: arm",
|
|
424
|
+
"toolchain:",
|
|
425
|
+
" - zephyr",
|
|
426
|
+
" - gnuarmemb",
|
|
427
|
+
"supported:" if supported else "supported: []",
|
|
428
|
+
]
|
|
429
|
+
lines += [f" - {p}" for p in supported]
|
|
430
|
+
lines.append("vendor: alp")
|
|
431
|
+
return "\n".join(lines) + "\n"
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# ---------------------------------------------------------------------
|
|
435
|
+
# Alif Ensemble (aen) family -- fully generated
|
|
436
|
+
# ---------------------------------------------------------------------
|
|
437
|
+
|
|
438
|
+
# Fixed MCUboot flash-partition sizes for the AEN sysbuild profile
|
|
439
|
+
# (MCUboot + a signed application image, swap-using-scratch). These are
|
|
440
|
+
# an SDK build-policy choice, not a per-chip hardware fact -- every AEN
|
|
441
|
+
# SKU's on-die MRAM is split the same way regardless of total size, so
|
|
442
|
+
# they stay generator constants rather than metadata fields; only the
|
|
443
|
+
# two symmetric image slots derive from the variant's actual MRAM size.
|
|
444
|
+
_AEN_MCUBOOT_KIB = 64
|
|
445
|
+
_AEN_SCRATCH_KIB = 64
|
|
446
|
+
_AEN_STORAGE_KIB = 96
|
|
447
|
+
|
|
448
|
+
# The SE-owned band at the very TOP of the App MRAM window (alp-sdk#1289).
|
|
449
|
+
#
|
|
450
|
+
# Alif's SETOOLS (`app-gen-toc` / `app-write-mram`) does not place the ATOC
|
|
451
|
+
# application table at a fixed address: it top-anchors the generated package
|
|
452
|
+
# at the App MRAM window end and grows DOWNWARD, sized to the package. There
|
|
453
|
+
# is therefore no compile-time constant to read -- the placement happens at
|
|
454
|
+
# provisioning time, not link time -- so the layout has to reserve a band
|
|
455
|
+
# rather than a specific address.
|
|
456
|
+
#
|
|
457
|
+
# 32 KiB covers the worst case with margin. Three independent figures, all
|
|
458
|
+
# measured/derived against the same window top 0x80580000:
|
|
459
|
+
#
|
|
460
|
+
# 5552 B observed on the E1M-AEN801 bench 2026-08-08 -- ATOC magic
|
|
461
|
+
# `ckBS` (0x53426B63) read at 0x8057EA50
|
|
462
|
+
# 13552 B an E7 SETOOLS transcript in the Alif DFP
|
|
463
|
+
# (alifsemi/alif_ensemble-cmsis-dfp, docs/Overview.md:193-224):
|
|
464
|
+
# package at 0x8057cb10
|
|
465
|
+
# 23696 B worst case computed from SETOOLS' own build/app-package-map.txt
|
|
466
|
+
# sizing rule -- 2560 B per signed user image + 2880 B fixed
|
|
467
|
+
# tool-managed + (48 + 32*N) TOC -- at the 8-image maximum. That
|
|
468
|
+
# rule reproduces the 5552 B observed above exactly.
|
|
469
|
+
#
|
|
470
|
+
# 32768 - 23696 = 9072 B spare at 8 images. UNVERIFIED: no documented
|
|
471
|
+
# maximum TOC entry count could be found, so the 8-image worst case is an
|
|
472
|
+
# assumption, not a specification -- widen this band rather than narrowing
|
|
473
|
+
# it if a larger configuration ever appears.
|
|
474
|
+
#
|
|
475
|
+
# Taken OUT of storage (128 -> 96 + 32) rather than out of the image slots
|
|
476
|
+
# on purpose: `_AEN_MCUBOOT_KIB + _AEN_SCRATCH_KIB + _AEN_STORAGE_KIB +
|
|
477
|
+
# _AEN_ATOC_KIB` still sums to the old 256 KiB, so `image_kib` below is
|
|
478
|
+
# unchanged and no committed AEN board's slot geometry moves.
|
|
479
|
+
_AEN_ATOC_KIB = 32
|
|
480
|
+
|
|
481
|
+
# Per-role documentation asymmetries in the committed AEN board tree.
|
|
482
|
+
# These are prose choices (which sibling's ITCM comment got the
|
|
483
|
+
# bench-verified annotation), not hardware facts -- like the comment
|
|
484
|
+
# scaffolding in alp_project_emit._emit_dts_overlay / _emit_hw_info_h,
|
|
485
|
+
# they live as generator template text keyed by role rather than as
|
|
486
|
+
# metadata.
|
|
487
|
+
_AEN_BENCH_KNOWN_ITCM = {"hp": False, "he": True}
|
|
488
|
+
|
|
489
|
+
# Base of the Alif Ensemble on-die App MRAM window. Family-wide, not
|
|
490
|
+
# per-part: upstream Zephyr declares `mram: flash@80000000` in EVERY
|
|
491
|
+
# Ensemble SoC dtsi (dts/arm/alif/ensemble/ae1c1f4051920ph0.dtsi,
|
|
492
|
+
# ae402fa0e5597le0.dtsi, ae612fa0e5597ls0.dtsi, ae822fa0e5597ls0.dtsi),
|
|
493
|
+
# and E1M-AEN801.yaml's `memory_map:` anchors its `mcuboot` region at the
|
|
494
|
+
# same 0x80000000. It is the soc-nv-flash child's offset-0 origin, so a
|
|
495
|
+
# SoM whose map disagrees is REFUSED rather than emitted -- see
|
|
496
|
+
# _aen_flash_partitions.
|
|
497
|
+
_AEN_MRAM_BASE = 0x80000000
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _aen_part(soc_spec: dict[str, Any]) -> str:
|
|
501
|
+
"""The Ensemble part designator -- `"E8"`, `"E3"`, ... -- from the
|
|
502
|
+
SoC JSON's required `part` field.
|
|
503
|
+
|
|
504
|
+
Was a generator constant hardcoded to E8, which put "Alif Ensemble
|
|
505
|
+
E8" and `<alif/ensemble_e8_peripherals.dtsi>` into EVERY `E1M-AEN*`
|
|
506
|
+
board tree regardless of silicon. `part` is `required` by
|
|
507
|
+
soc-spec-v1.schema.json, so there is nothing to guess.
|
|
508
|
+
"""
|
|
509
|
+
part = soc_spec.get("part")
|
|
510
|
+
if not part or _is_tbd(part):
|
|
511
|
+
raise ZephyrBoardEmitError(
|
|
512
|
+
f"SoC spec {soc_spec.get('ref')} declares no usable `part` -- "
|
|
513
|
+
"the AEN board tree names it in prose and derives the SoC "
|
|
514
|
+
"peripherals overlay from it")
|
|
515
|
+
return str(part)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def _aen_family_display(soc_spec: dict[str, Any]) -> str:
|
|
519
|
+
"""Vendor-short + family + part, as used in AEN board-tree prose
|
|
520
|
+
("Alif Ensemble E8").
|
|
521
|
+
|
|
522
|
+
`Alif` is fixed by the code path -- the `aen` family slug IS Alif
|
|
523
|
+
Ensemble -- because soc_spec's own `vendor:` field spells out the
|
|
524
|
+
legal name "Alif Semiconductor" for provenance blocks. `family`
|
|
525
|
+
("Ensemble") and `part` ("E8") are read from the SoC JSON, both
|
|
526
|
+
`required` by the schema.
|
|
527
|
+
"""
|
|
528
|
+
family = soc_spec.get("family")
|
|
529
|
+
if not family or _is_tbd(family):
|
|
530
|
+
raise ZephyrBoardEmitError(
|
|
531
|
+
f"SoC spec {soc_spec.get('ref')} declares no usable `family`")
|
|
532
|
+
return f"Alif {family} {_aen_part(soc_spec)}"
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def _aen_peripherals_dtsi(soc_spec: dict[str, Any], metadata_root: Path) -> str:
|
|
536
|
+
"""The `#include <...>` path of THIS SoC's Zephyr peripherals overlay.
|
|
537
|
+
|
|
538
|
+
Read from the SoC JSON's `zephyr_peripherals_dtsi`, never derived
|
|
539
|
+
from the part name: the overlay declares the SoC's actual peripheral
|
|
540
|
+
and NPU node set (the E8's includes `ethosu85`; an E3 has 2x U55 and
|
|
541
|
+
no U85), so including the wrong family's file produces a board that
|
|
542
|
+
builds and then misbehaves on silicon. alp-sdk ships exactly one
|
|
543
|
+
today (`zephyr/dts/alif/ensemble_e8_peripherals.dtsi`), so every
|
|
544
|
+
other Ensemble part must add its own and declare it here rather than
|
|
545
|
+
inherit the E8's.
|
|
546
|
+
|
|
547
|
+
`zephyr_peripherals_dtsi` is itself a ported requirement (alp-sdk#1352,
|
|
548
|
+
tan-cli#591) that no released alp-sdk carries yet: an absent value
|
|
549
|
+
checks `require_capability` FIRST, so a checkout that predates the
|
|
550
|
+
field raises `SdkTooOldError` naming that, rather than the generic
|
|
551
|
+
authoring-gap message below, which only fires once the checkout is
|
|
552
|
+
proven new enough to know the field at all.
|
|
553
|
+
"""
|
|
554
|
+
dtsi = soc_spec.get("zephyr_peripherals_dtsi")
|
|
555
|
+
if not dtsi or _is_tbd(dtsi):
|
|
556
|
+
require_capability(metadata_root, AEN_ZEPHYR_PERIPHERALS_DTSI)
|
|
557
|
+
raise ZephyrBoardEmitError(
|
|
558
|
+
f"SoC spec {soc_spec.get('ref')} "
|
|
559
|
+
f"({_aen_part(soc_spec)}) has no `zephyr_peripherals_dtsi` -- "
|
|
560
|
+
"the AEN board .dts must include this SoC's own peripherals "
|
|
561
|
+
"overlay, and alp-sdk ships one only for the E8 "
|
|
562
|
+
"(zephyr/dts/alif/ensemble_e8_peripherals.dtsi). Add the "
|
|
563
|
+
"overlay under zephyr/dts/alif/ and declare it in the SoC "
|
|
564
|
+
"JSON before generating this board; do NOT fall back to "
|
|
565
|
+
"another part's file, whose peripheral and NPU node set is "
|
|
566
|
+
"different silicon")
|
|
567
|
+
return str(dtsi)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _aen_require_disjoint_slot0(
|
|
571
|
+
sku: str, sku_preset: "dict[str, Any]",
|
|
572
|
+
memory_map: "list[dict[str, Any]] | None",
|
|
573
|
+
) -> None:
|
|
574
|
+
"""Refuse a DUAL-M55 AEN SoM that declares no `<role>_slot0` window (#1446).
|
|
575
|
+
|
|
576
|
+
`_aen_role_slot0_map` returns None when no role declares one, which drops
|
|
577
|
+
the caller onto the stock SYMMETRIC layout -- `m55_he` and `m55_hp` slot0
|
|
578
|
+
at the same address, so flashing one core silently clobbers the other's
|
|
579
|
+
window. That is the #1069 defect, and it is how E1M-AEN301/401/501/601/701
|
|
580
|
+
shipped until alp-sdk#1445 gave them explicit maps.
|
|
581
|
+
|
|
582
|
+
The half-authored case (one role declares a window, its sibling does not)
|
|
583
|
+
already raises inside `_aen_role_slot0_map`. This closes the fully
|
|
584
|
+
unauthored case, which was silent.
|
|
585
|
+
|
|
586
|
+
Single-M55 SoMs are unaffected: with no sibling core there is nothing to
|
|
587
|
+
clobber, so the symmetric layout stays correct for them.
|
|
588
|
+
|
|
589
|
+
Costs nothing on the current corpus -- after alp-sdk#1445 every AEN SoM
|
|
590
|
+
declares disjoint windows, so this never fires today. Its value is the
|
|
591
|
+
NEXT dual-M55 AEN SoM, which is refused at authoring time instead of
|
|
592
|
+
discovered by someone flashing one core and losing the other's slot0 on
|
|
593
|
+
a bench.
|
|
594
|
+
"""
|
|
595
|
+
if memory_map and any(
|
|
596
|
+
isinstance(r.get("name"), str) and r["name"].endswith("_slot0")
|
|
597
|
+
for r in memory_map
|
|
598
|
+
):
|
|
599
|
+
return
|
|
600
|
+
cores = sku_preset.get("topology") or {}
|
|
601
|
+
m55 = sorted(c for c in cores if c in ("m55_he", "m55_hp"))
|
|
602
|
+
if len(m55) < 2:
|
|
603
|
+
return
|
|
604
|
+
raise ZephyrBoardEmitError(
|
|
605
|
+
f"SoM {sku!r} has two M55 cores ({', '.join(m55)}) but its preset "
|
|
606
|
+
f"declares no per-core `<role>_slot0` region, so both would boot from "
|
|
607
|
+
f"the SAME MRAM slot0 address -- flashing one core silently corrupts "
|
|
608
|
+
f"the other's slot0 window (#1069). Declare disjoint `he_slot0` / "
|
|
609
|
+
f"`hp_slot0` regions in this SoM preset's `memory_map:`; "
|
|
610
|
+
f"metadata/e1m_modules/E1M-AEN801.yaml is the shape to copy (#1446)."
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def _aen_role_slot0_map(
|
|
615
|
+
memory_map: "list[dict[str, Any]] | None", role: str,
|
|
616
|
+
) -> "dict[str, Any] | None":
|
|
617
|
+
"""Return this role's disjoint-slot0 memory_map entry, or None.
|
|
618
|
+
|
|
619
|
+
Dual-M55 AEN SoMs (#1069: both cores boot from the same physical App
|
|
620
|
+
MRAM) declare a `memory_map:` block in their SoM preset with one region
|
|
621
|
+
per core named `<role>_slot0` (`accessible_from: [m55_<role>]` only).
|
|
622
|
+
A SoM with only ONE M55 has no sibling to collide with and keeps the
|
|
623
|
+
stock symmetric two-slot layout -- see _aen_flash_partitions below.
|
|
624
|
+
|
|
625
|
+
#1446: this used to say the stock layout also covered "single-M55
|
|
626
|
+
aen401/aen601". Both of those are DUAL-M55 -- measured from their own
|
|
627
|
+
presets, E1M-AEN401 is [m55_he, m55_hp] and E1M-AEN601 is
|
|
628
|
+
[a32_cluster, m55_he, m55_hp] -- so that sentence was the justification
|
|
629
|
+
for a silent fallback that put two cores' slot0 at one address, and it
|
|
630
|
+
is why five shipping SoMs carried a clobbering layout until
|
|
631
|
+
alp-sdk#1445. `_aen_require_disjoint_slot0` now refuses that state
|
|
632
|
+
outright; this function keeps returning None only for the genuinely
|
|
633
|
+
single-M55 case.
|
|
634
|
+
|
|
635
|
+
Returning None is only legitimate when NO role declares a slot0
|
|
636
|
+
window. A map that declares a SIBLING core's `<role>_slot0` but not
|
|
637
|
+
this one's is a half-authored map, and answering None for it drops
|
|
638
|
+
the caller onto the stock symmetric layout, whose `slot0_partition`
|
|
639
|
+
lands exactly on top of the sibling's declared window -- silently
|
|
640
|
+
undoing the #1069 disjoint-window fix and re-creating the
|
|
641
|
+
bench-confirmed corruption it exists to prevent. So that state
|
|
642
|
+
RAISES.
|
|
643
|
+
"""
|
|
644
|
+
if not memory_map:
|
|
645
|
+
return None
|
|
646
|
+
suffix = "_slot0"
|
|
647
|
+
declared_roles = [
|
|
648
|
+
str(r["name"])[: -len(suffix)]
|
|
649
|
+
for r in memory_map
|
|
650
|
+
if isinstance(r.get("name"), str) and r["name"].endswith(suffix)
|
|
651
|
+
]
|
|
652
|
+
if not declared_roles:
|
|
653
|
+
return None
|
|
654
|
+
core_id = f"m55_{role}"
|
|
655
|
+
for region in memory_map:
|
|
656
|
+
if region.get("name") != f"{role}{suffix}":
|
|
657
|
+
continue
|
|
658
|
+
if region.get("accessible_from") != [core_id]:
|
|
659
|
+
raise ZephyrBoardEmitError(
|
|
660
|
+
f"AEN memory_map region '{role}{suffix}' must declare "
|
|
661
|
+
f"accessible_from: [{core_id}] (a disjoint per-core slot0 "
|
|
662
|
+
f"window is reachable from exactly one core); it declares "
|
|
663
|
+
f"{region.get('accessible_from')!r}")
|
|
664
|
+
return region
|
|
665
|
+
raise ZephyrBoardEmitError(
|
|
666
|
+
f"AEN memory_map declares a disjoint slot0 window for "
|
|
667
|
+
f"{sorted(declared_roles)} but none for role {role!r} -- add a "
|
|
668
|
+
f"'{role}{suffix}' region (accessible_from: [{core_id}]). "
|
|
669
|
+
"Falling back to the stock symmetric layout here would place this "
|
|
670
|
+
"core's slot0_partition on top of the sibling's declared window "
|
|
671
|
+
"(#1069)")
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def _aen_check_extents(
|
|
675
|
+
partitions: "list[tuple[str, int, int]]", total_kib: int, where: str,
|
|
676
|
+
) -> None:
|
|
677
|
+
"""Every emitted partition must fit inside the flash node it is emitted
|
|
678
|
+
into, and no two may overlap.
|
|
679
|
+
|
|
680
|
+
Neither branch used to check this. The stock branch's
|
|
681
|
+
`offset != total_kib * 1024` sum assertion is NOT a fits-in-MRAM
|
|
682
|
+
check -- all five stock sizes derive from `total_kib`, so it is a
|
|
683
|
+
tautology that can never fire. The disjoint branch copied `base` /
|
|
684
|
+
`size_kib` verbatim out of the SoM preset and checked only that each
|
|
685
|
+
region existed with an integer `base`, so a mis-authored map emitted
|
|
686
|
+
a `partition@` node outside its own `mram_storage` `reg` -- MCUboot's
|
|
687
|
+
flash_map and the alif MRAM driver would then compute erase/program
|
|
688
|
+
addresses past the end of the device.
|
|
689
|
+
"""
|
|
690
|
+
limit = total_kib * 1024
|
|
691
|
+
ordered = sorted(partitions, key=lambda p: p[1])
|
|
692
|
+
prev_label, prev_end = None, 0
|
|
693
|
+
for label, off, size_kib in ordered:
|
|
694
|
+
if not isinstance(size_kib, int) or size_kib <= 0:
|
|
695
|
+
raise ZephyrBoardEmitError(
|
|
696
|
+
f"{where}: partition {label!r} has a non-positive size "
|
|
697
|
+
f"({size_kib!r} KiB)")
|
|
698
|
+
end = off + size_kib * 1024
|
|
699
|
+
if off < 0 or end > limit:
|
|
700
|
+
raise ZephyrBoardEmitError(
|
|
701
|
+
f"{where}: partition {label!r} spans "
|
|
702
|
+
f"0x{off:x}..0x{end:x} but the mram_storage flash node is "
|
|
703
|
+
f"only {total_kib} KiB (0x0..0x{limit:x}) -- the partition "
|
|
704
|
+
"would sit outside the flash device it is declared in")
|
|
705
|
+
if off < prev_end:
|
|
706
|
+
raise ZephyrBoardEmitError(
|
|
707
|
+
f"{where}: partition {label!r} at 0x{off:x} overlaps "
|
|
708
|
+
f"{prev_label!r}, which ends at 0x{prev_end:x}")
|
|
709
|
+
prev_label, prev_end = label, end
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _aen_check_map_overlaps(
|
|
713
|
+
memory_map: "list[dict[str, Any]]", mram_base: int, total_kib: int,
|
|
714
|
+
) -> None:
|
|
715
|
+
"""Cross-check the WHOLE declared `memory_map:`, not just the regions
|
|
716
|
+
this core's partition table happens to name.
|
|
717
|
+
|
|
718
|
+
The sibling core's `<role>_slot0` window is not a partition here, so
|
|
719
|
+
_aen_check_extents cannot see a map that overlaps it; this can.
|
|
720
|
+
Regions with a non-integer `base` (a `TBD` sentinel) are skipped --
|
|
721
|
+
they are declarations of intent, not placements. No shipped AEN
|
|
722
|
+
preset authors one today (#2053 resolved the last holdout,
|
|
723
|
+
`mram_main`, on all seven), but the skip stays live for any future
|
|
724
|
+
region or preset that does.
|
|
725
|
+
|
|
726
|
+
A region whose resolved extent equals the App MRAM window EXACTLY
|
|
727
|
+
(the whole-device alias -- `mram_main`, on every AEN preset since
|
|
728
|
+
#2053 resolved its `base` off the `"TBD"` sentinel) is excluded from
|
|
729
|
+
the pairwise overlap comparison below -- it deliberately spans the
|
|
730
|
+
same window `mcuboot` /
|
|
731
|
+
`he_slot0` / `hp_slot0` / `reserved` / `storage` / `atoc` subdivide,
|
|
732
|
+
so comparing it against its own partitions would flag every one of
|
|
733
|
+
them. `whole_device_alias.is_whole_device_alias()` carries the
|
|
734
|
+
identical "extent == aperture exactly" predicate `classify_region()`
|
|
735
|
+
and `is_partition_inside_aperture()` (`tan/planner/aperture.py`)
|
|
736
|
+
already use for this same case --
|
|
737
|
+
imported by both from one sibling module, whose own docstring
|
|
738
|
+
records why it stays a separate file, so those two can't drift
|
|
739
|
+
(alp-sdk#2073). Upstream additionally records that alp-sdk's own
|
|
740
|
+
`check_atoc_reservation.py` hand-writes this same comparison instead
|
|
741
|
+
of importing it; tan relocates no counterpart of that gate, so that
|
|
742
|
+
gap is alp-sdk's alone and the call sites named above are the whole
|
|
743
|
+
surface here. The exclusion is bounds-checked like every other row
|
|
744
|
+
and applies to nothing looser than an exact match: a region that
|
|
745
|
+
merely CONTAINS another without matching the aperture exactly still
|
|
746
|
+
overlaps and is still refused below; and more than one row matching
|
|
747
|
+
the aperture exactly is refused outright, not silently allowed to
|
|
748
|
+
co-exist unchecked against each other.
|
|
749
|
+
"""
|
|
750
|
+
placed = [
|
|
751
|
+
(str(r.get("name")), r["base"], int(r["size_kib"]))
|
|
752
|
+
for r in memory_map
|
|
753
|
+
if isinstance(r.get("base"), int) and isinstance(r.get("size_kib"), int)
|
|
754
|
+
]
|
|
755
|
+
limit = mram_base + total_kib * 1024
|
|
756
|
+
aperture = (mram_base, limit)
|
|
757
|
+
for name, base, size_kib in placed:
|
|
758
|
+
end = base + size_kib * 1024
|
|
759
|
+
if base < mram_base or end > limit:
|
|
760
|
+
raise ZephyrBoardEmitError(
|
|
761
|
+
f"AEN memory_map region {name!r} spans "
|
|
762
|
+
f"0x{base:x}..0x{end:x}, outside the {total_kib} KiB App "
|
|
763
|
+
f"MRAM window 0x{mram_base:x}..0x{limit:x} declared by this "
|
|
764
|
+
"variant's mram_mb")
|
|
765
|
+
aliases = [
|
|
766
|
+
(name, base, size_kib) for name, base, size_kib in placed
|
|
767
|
+
if is_whole_device_alias((base, base + size_kib * 1024), aperture)
|
|
768
|
+
]
|
|
769
|
+
if len(aliases) > 1:
|
|
770
|
+
alias_names = ", ".join(repr(name) for name, _b, _s in aliases)
|
|
771
|
+
raise ZephyrBoardEmitError(
|
|
772
|
+
f"AEN memory_map declares {len(aliases)} regions ({alias_names}) "
|
|
773
|
+
f"whose extent equals the {total_kib} KiB App MRAM window "
|
|
774
|
+
f"0x{mram_base:x}..0x{limit:x} exactly -- only one whole-device "
|
|
775
|
+
"alias to the App MRAM window is allowed; rename or remove the "
|
|
776
|
+
"duplicate(s).")
|
|
777
|
+
overlap_candidates = [row for row in placed if row not in aliases]
|
|
778
|
+
overlap_candidates.sort(key=lambda r: r[1])
|
|
779
|
+
for (a_name, a_base, a_kib), (b_name, b_base, _b_kib) in zip(
|
|
780
|
+
overlap_candidates, overlap_candidates[1:]):
|
|
781
|
+
a_end = a_base + a_kib * 1024
|
|
782
|
+
if b_base < a_end:
|
|
783
|
+
raise ZephyrBoardEmitError(
|
|
784
|
+
f"AEN memory_map regions {a_name!r} (ends 0x{a_end:x}) and "
|
|
785
|
+
f"{b_name!r} (starts 0x{b_base:x}) overlap")
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def _aen_slot0_sizes_display(memory_map: "list[dict[str, Any]]") -> str:
|
|
789
|
+
"""`"HE 2688 KiB + HP 2688 KiB"` -- every declared per-core code slot,
|
|
790
|
+
in `memory_map:` declaration order.
|
|
791
|
+
|
|
792
|
+
The #1069 rationale sentence in the generated `.dts` used to carry
|
|
793
|
+
the AEN801-only literals "both 2688 KiB code slots fit the 5632 KiB
|
|
794
|
+
App MRAM alongside the ~2.6 MiB NPU MRAM-model budget" unconditionally,
|
|
795
|
+
~100 lines above a partition map derived from this same
|
|
796
|
+
`memory_map:` -- so any dual-M55 AEN SoM with different slot sizes
|
|
797
|
+
emitted a board file stating its MRAM budget two contradictory ways,
|
|
798
|
+
with the prose (the part an engineer sizing an NPU model reads)
|
|
799
|
+
being the wrong one. The `~2.6 MiB` figure is a policy number from
|
|
800
|
+
`zephyr/sysbuild/aen/README.md` that no generator input carries, so
|
|
801
|
+
it is not interpolated -- the sentence points at the README instead.
|
|
802
|
+
"""
|
|
803
|
+
suffix = "_slot0"
|
|
804
|
+
parts = [
|
|
805
|
+
f"{str(r['name'])[: -len(suffix)].upper()} {r['size_kib']} KiB"
|
|
806
|
+
for r in memory_map
|
|
807
|
+
if isinstance(r.get("name"), str) and r["name"].endswith(suffix)
|
|
808
|
+
]
|
|
809
|
+
return " + ".join(parts)
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def _aen_raise_missing_region(
|
|
813
|
+
region_name: str, label: str, metadata_root: Path,
|
|
814
|
+
) -> NoReturn:
|
|
815
|
+
"""Raise the right refusal for a missing disjoint-slot0 region.
|
|
816
|
+
|
|
817
|
+
`atoc` is a ported requirement (alp-sdk#1289, tan-cli#591) that no
|
|
818
|
+
released alp-sdk carries yet: checked FIRST, via `require_capability`
|
|
819
|
+
against the bound checkout's OWN `metadata/quality-tasks-v1.json`
|
|
820
|
+
registry -- never against the shape of THIS SoM preset's map. A
|
|
821
|
+
checkout that doesn't know the concept raises `SdkTooOldError` naming
|
|
822
|
+
it; only once the checkout is proven new enough does an absent `atoc`
|
|
823
|
+
region fall through to the generic authoring-gap message below, same as
|
|
824
|
+
every other region name.
|
|
825
|
+
|
|
826
|
+
This used to be a heuristic instead: "every OTHER required region is
|
|
827
|
+
present and placed, so this preset is a complete pre-#1289 layout" --
|
|
828
|
+
inferring the checkout's vintage from the shape of one board's map.
|
|
829
|
+
Imprecise by construction (a hand-edited preset that merely LOOKS
|
|
830
|
+
pre-#1289-shaped hit the same wording as a genuinely old checkout);
|
|
831
|
+
`require_capability` answers the real question directly instead.
|
|
832
|
+
"""
|
|
833
|
+
if region_name == "atoc":
|
|
834
|
+
require_capability(metadata_root, AEN_ATOC_RESERVATION)
|
|
835
|
+
raise ZephyrBoardEmitError(
|
|
836
|
+
f"AEN disjoint-slot0 memory_map is missing an integer-`base` "
|
|
837
|
+
f"region named {region_name!r} (needed for {label!r}) -- this SoM "
|
|
838
|
+
"preset's `memory_map:` is incomplete; add the region, or drop the "
|
|
839
|
+
"per-core `<role>_slot0` windows to fall back to the stock "
|
|
840
|
+
"symmetric two-slot layout")
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def _aen_flash_partitions(
|
|
844
|
+
total_kib: int, metadata_root: Path, role: str | None = None,
|
|
845
|
+
memory_map: "list[dict[str, Any]] | None" = None,
|
|
846
|
+
) -> "tuple[int, list[tuple[str, int, int]]]":
|
|
847
|
+
"""Return (mram_base, [(label, offset_bytes, size_kib), ...]).
|
|
848
|
+
|
|
849
|
+
*metadata_root* is threaded through to `_aen_raise_missing_region`,
|
|
850
|
+
which needs it to answer "does the bound alp-sdk checkout know about
|
|
851
|
+
`atoc` at all" before blaming this SoM preset for omitting it
|
|
852
|
+
(tan-cli#591).
|
|
853
|
+
|
|
854
|
+
*mram_base* is the absolute address the emitted `mram_storage`
|
|
855
|
+
soc-nv-flash child is anchored at, i.e. the origin every returned
|
|
856
|
+
offset is relative to. It is returned rather than re-derived by the
|
|
857
|
+
caller so the DTS node address and the partition offsets can never
|
|
858
|
+
disagree -- `_aen_dts` used to write the node address as a hardcoded
|
|
859
|
+
`0x80000000` literal while the offsets came from the metadata base.
|
|
860
|
+
|
|
861
|
+
Stock layout (no per-role memory_map entry for *role*): the
|
|
862
|
+
original symmetric two-slot MCUboot swap-using-scratch map --
|
|
863
|
+
mcuboot, image-0 (primary/code-partition), image-1 (secondary,
|
|
864
|
+
OTA), scratch, storage -- unchanged. This now applies only to a
|
|
865
|
+
genuinely single-M55 AEN SoM that declares no `<role>_slot0`
|
|
866
|
+
region. E1M-AEN401 and E1M-AEN601 are DUAL-M55 (see
|
|
867
|
+
`_aen_role_slot0_map` above) and have generated the disjoint-slot0
|
|
868
|
+
layout below since alp-sdk#1445 -- `_aen_require_disjoint_slot0` refuses
|
|
869
|
+
to let a dual-M55 SoM fall back to this stock layout at all
|
|
870
|
+
(alp-sdk#1482, tan-cli#896: docstring-only re-sync, "still what
|
|
871
|
+
every single-M55 AEN SKU (aen401, aen601) generates" was stale
|
|
872
|
+
prose, not a behavioural gap -- both SKUs already generated the
|
|
873
|
+
disjoint layout below).
|
|
874
|
+
|
|
875
|
+
Disjoint-slot0 layout (*role*'s `<role>_slot0` memory_map entry is
|
|
876
|
+
declared, #1069): mcuboot, this role's own slot0 (labelled
|
|
877
|
+
"image-0" so downstream label maps / the `image_kib` lookup in
|
|
878
|
+
_aen_dts don't need a second code path), reserved (the ex-scratch
|
|
879
|
+
headroom, unused -- OTA is deferred since a swap-sized second slot
|
|
880
|
+
on both cores no longer fits the MRAM budget), storage, atoc. No
|
|
881
|
+
image-1: this is CONFIG_SINGLE_APPLICATION_SLOT=y (see
|
|
882
|
+
zephyr/sysbuild/aen/README.md), the only silicon-proven MCUboot
|
|
883
|
+
mode on this part. The OTHER role's slot0 lives outside this
|
|
884
|
+
table entirely (a disjoint physical window this board never
|
|
885
|
+
touches) -- see metadata/e1m_modules/E1M-AEN801.yaml `memory_map:`
|
|
886
|
+
for the full 6-region physical map.
|
|
887
|
+
|
|
888
|
+
BOTH layouts end in `atoc` (alp-sdk#1289) -- the SE-owned band SETOOLS
|
|
889
|
+
top-anchors the ATOC application table into. It is a declared,
|
|
890
|
+
named region precisely so no app can mount NVS or a filesystem on
|
|
891
|
+
top of the boot table: before this, the last region was `storage`
|
|
892
|
+
in both branches, and on the bench a slot0 app happily erased
|
|
893
|
+
inside it while the live ATOC sat 0x1EA50 further up the SAME
|
|
894
|
+
partition. Keep `atoc` last in both paths; see _AEN_ATOC_KIB for
|
|
895
|
+
the sizing evidence.
|
|
896
|
+
"""
|
|
897
|
+
slot0_region = _aen_role_slot0_map(memory_map, role) if role else None
|
|
898
|
+
if slot0_region is not None:
|
|
899
|
+
by_name = {r["name"]: r for r in memory_map}
|
|
900
|
+
mcuboot_region = by_name.get("mcuboot")
|
|
901
|
+
if mcuboot_region is None or not isinstance(mcuboot_region.get("base"), int):
|
|
902
|
+
raise ZephyrBoardEmitError(
|
|
903
|
+
"AEN disjoint-slot0 memory_map is missing an integer-`base` "
|
|
904
|
+
"'mcuboot' region -- its base anchors the soc-nv-flash "
|
|
905
|
+
"child's offset-0 origin")
|
|
906
|
+
mram_base = mcuboot_region["base"]
|
|
907
|
+
if mram_base != _AEN_MRAM_BASE:
|
|
908
|
+
raise ZephyrBoardEmitError(
|
|
909
|
+
f"AEN memory_map anchors 'mcuboot' at 0x{mram_base:x}, but "
|
|
910
|
+
f"the Ensemble App MRAM window starts at "
|
|
911
|
+
f"0x{_AEN_MRAM_BASE:x} (upstream Zephyr declares `mram: "
|
|
912
|
+
"flash@80000000` in every ensemble SoC dtsi). The mcuboot "
|
|
913
|
+
"base IS the soc-nv-flash child's offset-0 origin, so a map "
|
|
914
|
+
"that starts elsewhere would place every partition at the "
|
|
915
|
+
"wrong absolute address")
|
|
916
|
+
_aen_check_map_overlaps(memory_map, mram_base, total_kib)
|
|
917
|
+
out: list[tuple[str, int, int]] = []
|
|
918
|
+
for label, region_name in (
|
|
919
|
+
("mcuboot", "mcuboot"),
|
|
920
|
+
("image-0", f"{role}_slot0"),
|
|
921
|
+
("reserved", "reserved"),
|
|
922
|
+
("storage", "storage"),
|
|
923
|
+
("atoc", "atoc"),
|
|
924
|
+
):
|
|
925
|
+
region = by_name.get(region_name)
|
|
926
|
+
if region is None or not isinstance(region.get("base"), int):
|
|
927
|
+
_aen_raise_missing_region(region_name, label, metadata_root)
|
|
928
|
+
out.append((label, region["base"] - mram_base, region["size_kib"]))
|
|
929
|
+
_aen_check_extents(out, total_kib, "AEN disjoint-slot0 memory_map")
|
|
930
|
+
return mram_base, out
|
|
931
|
+
|
|
932
|
+
reserved = (_AEN_MCUBOOT_KIB + _AEN_SCRATCH_KIB + _AEN_STORAGE_KIB
|
|
933
|
+
+ _AEN_ATOC_KIB)
|
|
934
|
+
remaining = total_kib - reserved
|
|
935
|
+
if remaining <= 0 or remaining % 2:
|
|
936
|
+
raise ZephyrBoardEmitError(
|
|
937
|
+
f"AEN MRAM size {total_kib} KiB doesn't split evenly into two "
|
|
938
|
+
f"image slots after reserving {reserved} KiB for "
|
|
939
|
+
"mcuboot/scratch/storage/atoc")
|
|
940
|
+
image_kib = remaining // 2
|
|
941
|
+
|
|
942
|
+
offset = 0
|
|
943
|
+
out: list[tuple[str, int, int]] = []
|
|
944
|
+
for label, size_kib in (
|
|
945
|
+
("mcuboot", _AEN_MCUBOOT_KIB),
|
|
946
|
+
("image-0", image_kib),
|
|
947
|
+
("image-1", image_kib),
|
|
948
|
+
("image-scratch", _AEN_SCRATCH_KIB),
|
|
949
|
+
("storage", _AEN_STORAGE_KIB),
|
|
950
|
+
# LAST, and must stay last: this is the band SETOOLS top-anchors the
|
|
951
|
+
# ATOC into (alp-sdk#1289). The `offset != total_kib * 1024`
|
|
952
|
+
# assertion below is what keeps it flush against the window top.
|
|
953
|
+
("atoc", _AEN_ATOC_KIB),
|
|
954
|
+
):
|
|
955
|
+
out.append((label, offset, size_kib))
|
|
956
|
+
offset += size_kib * 1024
|
|
957
|
+
if offset != total_kib * 1024:
|
|
958
|
+
raise ZephyrBoardEmitError("AEN flash partition arithmetic didn't sum to total")
|
|
959
|
+
_aen_check_extents(out, total_kib, "AEN stock two-slot layout")
|
|
960
|
+
return _AEN_MRAM_BASE, out
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def _aen_console_pinmux_rows(metadata_root: Path) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
964
|
+
"""Return (rx_row, tx_row) for the E1M `UART0` pad from `pinmux/aen.yaml`."""
|
|
965
|
+
pm = _load_yaml(metadata_root / "pinmux" / "aen.yaml")
|
|
966
|
+
rows = {r.get("e1m_function"): r for r in (pm.get("pads") or [])
|
|
967
|
+
if r.get("e1m_function") in ("UART0_TX", "UART0_RX")}
|
|
968
|
+
if "UART0_RX" not in rows or "UART0_TX" not in rows:
|
|
969
|
+
raise ZephyrBoardEmitError(
|
|
970
|
+
"metadata/pinmux/aen.yaml has no UART0_TX/UART0_RX rows for the "
|
|
971
|
+
"E1M console pad")
|
|
972
|
+
return rows["UART0_RX"], rows["UART0_TX"]
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
def _pin_macro(row: dict[str, Any]) -> str:
|
|
976
|
+
return f"PIN_{row['silicon_pad']}__{row['silicon_peripheral']}"
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
def _uart_node_label(row: dict[str, Any]) -> str:
|
|
980
|
+
m = re.match(r"^([A-Za-z]+\d+)", row["silicon_peripheral"])
|
|
981
|
+
if not m:
|
|
982
|
+
raise ZephyrBoardEmitError(
|
|
983
|
+
"can't derive a UART node label from silicon_peripheral "
|
|
984
|
+
f"{row['silicon_peripheral']!r}")
|
|
985
|
+
return m.group(1).lower()
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
# ---------------------------------------------------------------------
|
|
989
|
+
# AEN on-module (non-edge) links: BRD_I2C + its devices, the RTC alarm
|
|
990
|
+
# ---------------------------------------------------------------------
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def _load_aen_on_module_links(metadata_root: Path) -> dict[str, Any]:
|
|
994
|
+
"""Load + shape-check `metadata/e1m_modules/aen/on-module-links.yaml`.
|
|
995
|
+
|
|
996
|
+
The AEN counterpart of `_load_supervisor_links()`, and the ONLY
|
|
997
|
+
authority for pads that reach no E1M edge pin: `metadata/pinmux/aen.yaml`
|
|
998
|
+
is a generated projection of the EDGE pad TSVs and carries no row for
|
|
999
|
+
P7_0 / P7_1 (BRD_I2C) or P15_0 (RTC_ALARM), which run SoC <-> on-module
|
|
1000
|
+
chip and never leave the module. `e1m_i2c0` (SoC I2C2) is the one
|
|
1001
|
+
exception -- its pads DO have an edge row (it also reaches the EVK
|
|
1002
|
+
carrier's sensor bus), but its DT-node shape lives here too because the
|
|
1003
|
+
SoM's own 24C128 manifest EEPROM sits on the same physical bus (see the
|
|
1004
|
+
`e1m_i2c0` entry's own comment in on-module-links.yaml). Family-scoped
|
|
1005
|
+
-- one file backs every AEN SKU and both M55 cores.
|
|
1006
|
+
"""
|
|
1007
|
+
path = metadata_root / "e1m_modules" / "aen" / "on-module-links.yaml"
|
|
1008
|
+
if not path.is_file():
|
|
1009
|
+
raise ZephyrBoardEmitError(
|
|
1010
|
+
f"no {path} -- the AEN pinctrl.dtsi/.dts emitters need the "
|
|
1011
|
+
"on-module pad + device source (BRD_I2C, e1m_i2c0, the RV-3028 "
|
|
1012
|
+
"alarm)")
|
|
1013
|
+
doc = _load_yaml(path)
|
|
1014
|
+
links = doc.get("on_module_links")
|
|
1015
|
+
if not isinstance(links, dict):
|
|
1016
|
+
raise ZephyrBoardEmitError(f"{path} has no on_module_links: block")
|
|
1017
|
+
for key in ("brd_i2c", "e1m_i2c0", "rtc_alarm"):
|
|
1018
|
+
if key not in links:
|
|
1019
|
+
raise ZephyrBoardEmitError(
|
|
1020
|
+
f"{path} on_module_links: is missing {key!r}")
|
|
1021
|
+
risk = links["rtc_alarm"].get("risk")
|
|
1022
|
+
if risk is not None and not isinstance(risk, dict):
|
|
1023
|
+
raise ZephyrBoardEmitError(
|
|
1024
|
+
f"{path} on_module_links.rtc_alarm.risk must be a map keyed by "
|
|
1025
|
+
"SoC `part` designator (e.g. {\"E8\": \"...\"}), not a bare "
|
|
1026
|
+
"string -- a part-scoped risk note needs the part key (#1988)")
|
|
1027
|
+
bench_validation = links["e1m_i2c0"].get("bench_validation")
|
|
1028
|
+
if bench_validation is not None and not isinstance(bench_validation, dict):
|
|
1029
|
+
raise ZephyrBoardEmitError(
|
|
1030
|
+
f"{path} on_module_links.e1m_i2c0.bench_validation must be a map "
|
|
1031
|
+
"keyed by SoC `part` designator (e.g. {\"E8\": \"...\"}), not a "
|
|
1032
|
+
"bare string -- a part-scoped bench note needs the part key "
|
|
1033
|
+
"(#2046, following #1988's pattern)")
|
|
1034
|
+
return links
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
def _c_comment(text: str, indent: str, width: int = 79) -> list[str]:
|
|
1038
|
+
"""Reflow a metadata `evidence:` / `risk:` string into a C block comment.
|
|
1039
|
+
|
|
1040
|
+
Keeps the prose in the YAML (one source per hardware fact) instead of
|
|
1041
|
+
duplicating it as template text here.
|
|
1042
|
+
"""
|
|
1043
|
+
body = " ".join(text.split())
|
|
1044
|
+
prefix = f"{indent} * "
|
|
1045
|
+
wrapped = textwrap.wrap(body, width=width - len(prefix)) or [""]
|
|
1046
|
+
return [f"{indent}/*"] + [f"{prefix}{line}".rstrip() for line in wrapped] + [f"{indent} */"]
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def _dt_property(name: str, value: Any) -> str:
|
|
1050
|
+
"""One devicetree property line from a metadata `properties:` entry."""
|
|
1051
|
+
if isinstance(value, bool):
|
|
1052
|
+
if not value:
|
|
1053
|
+
raise ZephyrBoardEmitError(
|
|
1054
|
+
f"on-module-links.yaml property {name!r}: `false` has no "
|
|
1055
|
+
"devicetree meaning -- omit the property instead")
|
|
1056
|
+
return f"{name};"
|
|
1057
|
+
if isinstance(value, int):
|
|
1058
|
+
return f"{name} = <{value}>;"
|
|
1059
|
+
if isinstance(value, str):
|
|
1060
|
+
return f'{name} = "{value}";'
|
|
1061
|
+
raise ZephyrBoardEmitError(
|
|
1062
|
+
f"on-module-links.yaml property {name!r}: unsupported value type "
|
|
1063
|
+
f"{type(value).__name__}")
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
def _aen_i2c_device_nodes(
|
|
1067
|
+
bus: dict[str, Any], links: dict[str, Any], indent: str = "\t",
|
|
1068
|
+
) -> list[str]:
|
|
1069
|
+
"""The `&i2c0` child nodes for the on-module devices, from metadata.
|
|
1070
|
+
|
|
1071
|
+
Only devices the metadata actually lists get a node -- the DNP=1 OPTIGA
|
|
1072
|
+
Trust M is absent from `devices:` for exactly that reason, so it can
|
|
1073
|
+
never be emitted as a phantom node.
|
|
1074
|
+
"""
|
|
1075
|
+
lines: list[str] = []
|
|
1076
|
+
for dev in bus.get("devices") or []:
|
|
1077
|
+
lines.append("")
|
|
1078
|
+
if dev.get("evidence"):
|
|
1079
|
+
lines += _c_comment(dev["evidence"], indent)
|
|
1080
|
+
lines.append(f"{indent}{dev['node_label']}: {dev['node_name']} {{")
|
|
1081
|
+
lines.append(f'{indent}\tcompatible = "{dev["compatible"]}";')
|
|
1082
|
+
lines.append(f"{indent}\treg = <{dev['address_7bit']:#04x}>;")
|
|
1083
|
+
for name, value in (dev.get("properties") or {}).items():
|
|
1084
|
+
lines.append(f"{indent}\t{_dt_property(name, value)}")
|
|
1085
|
+
int_link = dev.get("int_gpio_link")
|
|
1086
|
+
if int_link:
|
|
1087
|
+
alarm = links[int_link]
|
|
1088
|
+
flag = "GPIO_ACTIVE_LOW" if alarm.get("active_low") else "GPIO_ACTIVE_HIGH"
|
|
1089
|
+
lines.append(
|
|
1090
|
+
f"{indent}\tint-gpios = <&{alarm['gpio_node']} "
|
|
1091
|
+
f"{alarm['gpio_pin']} {flag}>;")
|
|
1092
|
+
lines.append(f"{indent}}};")
|
|
1093
|
+
return lines
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
def _aen_pinctrl_dtsi(
|
|
1097
|
+
role: str, sku_display: str, rx_row: dict[str, Any], tx_row: dict[str, Any],
|
|
1098
|
+
family_display: str, links: dict[str, Any], part: str,
|
|
1099
|
+
) -> str:
|
|
1100
|
+
other_role = "he" if role == "hp" else "hp"
|
|
1101
|
+
rx_macro, tx_macro = _pin_macro(rx_row), _pin_macro(tx_row)
|
|
1102
|
+
uart_node = _uart_node_label(rx_row)
|
|
1103
|
+
|
|
1104
|
+
if role == "hp":
|
|
1105
|
+
confirm_block = (
|
|
1106
|
+
f" * (In an {other_role.upper()}+{role.upper()} AMP image the two cores share this single physical console;\n"
|
|
1107
|
+
" * during bring-up each core is flashed/run independently.) Confirmed against\n"
|
|
1108
|
+
" * the SoM netlist + metadata/e1m_modules/aen/from-alif.tsv.\n"
|
|
1109
|
+
)
|
|
1110
|
+
else:
|
|
1111
|
+
confirm_block = (
|
|
1112
|
+
" * Confirmed against the SoM netlist + metadata/e1m_modules/aen/from-alif.tsv\n"
|
|
1113
|
+
f" * (rows {tx_row['e1m_pad']} = {tx_row['e1m_function']} = "
|
|
1114
|
+
f"{tx_row['silicon_peripheral']}/{tx_row['silicon_pad']}, "
|
|
1115
|
+
f"{rx_row['e1m_pad']} = {rx_row['e1m_function']} = "
|
|
1116
|
+
f"{rx_row['silicon_peripheral']}/{rx_row['silicon_pad']}).\n"
|
|
1117
|
+
)
|
|
1118
|
+
|
|
1119
|
+
return (
|
|
1120
|
+
_COPYRIGHT_C +
|
|
1121
|
+
" *\n"
|
|
1122
|
+
f" * {sku_display} ({family_display}) Cortex-M55-{role.upper()} carrier pin control.\n"
|
|
1123
|
+
" *\n"
|
|
1124
|
+
" * Console: the E1M edge \"UART0\" maps to Alif "
|
|
1125
|
+
f"{uart_node.upper()} ({rx_row['silicon_pad']} RX_A / "
|
|
1126
|
+
f"{tx_row['silicon_pad']} TX_A) on\n"
|
|
1127
|
+
" * the AEN module PCB; the Alp E1M-EVK carrier wires its USB-UART console to it.\n"
|
|
1128
|
+
+ confirm_block +
|
|
1129
|
+
" *\n"
|
|
1130
|
+
" *\n"
|
|
1131
|
+
" * BRD_I2C (SoC I2C0, function C) is wired here too: it is an ON-MODULE bus\n"
|
|
1132
|
+
" * carrying SoM-resident parts (RTC + temperature sensor), present on every\n"
|
|
1133
|
+
" * board built from this SoM -- not a per-app choice, so it belongs in the\n"
|
|
1134
|
+
" * board layer rather than in each consumer's overlay. Pads, macros and pad\n"
|
|
1135
|
+
" * config come from metadata/e1m_modules/aen/on-module-links.yaml.\n"
|
|
1136
|
+
" *\n"
|
|
1137
|
+
" * SoC I2C2 (function C, portable alp-i2c0) is wired here too, for the same\n"
|
|
1138
|
+
" * reason: the SoM's own 24C128 manifest EEPROM is bridge/DNP-selected onto\n"
|
|
1139
|
+
" * it, so every board built from this SoM needs the controller enabled --\n"
|
|
1140
|
+
" * even though the same pads also carry the EVK carrier's sensor bus once\n"
|
|
1141
|
+
" * they leave the module (docs/bring-up-aen.md Sec 5.1).\n"
|
|
1142
|
+
" *\n"
|
|
1143
|
+
" * Remaining GPIO / SPI / Ethernet pin groups are added alongside their\n"
|
|
1144
|
+
" * drivers (the alp-sdk Alif peripheral drivers).\n"
|
|
1145
|
+
" */\n"
|
|
1146
|
+
"\n"
|
|
1147
|
+
"#include <zephyr/dt-bindings/pinctrl/alif-ensemble-pinctrl.h>\n"
|
|
1148
|
+
"\n"
|
|
1149
|
+
"&pinctrl {\n"
|
|
1150
|
+
f"\tpinctrl_{uart_node}: pinctrl_{uart_node} {{\n"
|
|
1151
|
+
"\t\tgroup0 {\n"
|
|
1152
|
+
f"\t\t\tpinmux = <{rx_macro}>;\n"
|
|
1153
|
+
"\t\t\tinput-enable;\n"
|
|
1154
|
+
"\t\t};\n"
|
|
1155
|
+
"\n"
|
|
1156
|
+
"\t\tgroup1 {\n"
|
|
1157
|
+
f"\t\t\tpinmux = <{tx_macro}>;\n"
|
|
1158
|
+
"\t\t};\n"
|
|
1159
|
+
"\t};\n"
|
|
1160
|
+
"\n"
|
|
1161
|
+
+ _aen_i2c_pinctrl_group(links) +
|
|
1162
|
+
"\n"
|
|
1163
|
+
+ _aen_e1m_i2c0_pinctrl_group(links, part) +
|
|
1164
|
+
"};\n"
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
def _aen_i2c_pinctrl_group(links: dict[str, Any]) -> str:
|
|
1169
|
+
"""The BRD_I2C pinctrl group (+ the RTC alarm pad), from metadata.
|
|
1170
|
+
|
|
1171
|
+
The alarm pad rides in THIS group rather than one of its own because
|
|
1172
|
+
`snps,designware-gpio` (the lpgpio controller's binding) has no
|
|
1173
|
+
`pinctrl-0` property: on Alif the pad input buffer is a PADCTRL bit only
|
|
1174
|
+
pinctrl can set, gpio_dw drives direction in the controller and never
|
|
1175
|
+
touches the pad, so an un-referenced group would simply never be applied
|
|
1176
|
+
and the alarm line would read 0 forever. Same pattern as P2_6 (the
|
|
1177
|
+
CC3501E READY input) riding in `pinctrl_spi1`.
|
|
1178
|
+
"""
|
|
1179
|
+
bus = links["brd_i2c"]
|
|
1180
|
+
alarm = links["rtc_alarm"]
|
|
1181
|
+
sda = _pin_by_peripheral(bus["pins"], "I2C0_SDA_C")
|
|
1182
|
+
scl = _pin_by_peripheral(bus["pins"], "I2C0_SCL_C")
|
|
1183
|
+
return (
|
|
1184
|
+
"\t/*\n"
|
|
1185
|
+
f"\t * {sda['net'].rsplit('_', 1)[0]} = SoC {bus['peripheral']} function C: "
|
|
1186
|
+
f"{sda['silicon_pad']} {sda['net'].rsplit('_', 1)[-1]} / "
|
|
1187
|
+
f"{scl['silicon_pad']} {scl['net'].rsplit('_', 1)[-1]},\n"
|
|
1188
|
+
"\t * the on-module housekeeping bus (see the board .dts for the parts on it).\n"
|
|
1189
|
+
"\t *\n"
|
|
1190
|
+
"\t * BOTH pins get input-enable, in one group -- Alif's own DFP sets\n"
|
|
1191
|
+
f"\t * PADCTRL_READ_ENABLE on {sda['silicon_pad']} AND {scl['silicon_pad']}. "
|
|
1192
|
+
"With it on SDA only, the\n"
|
|
1193
|
+
"\t * i2c_dw controller cannot SENSE SCL (clock-stretch detect / arbitration)\n"
|
|
1194
|
+
"\t * and NACKs every address for a reason that looks electrical but is not.\n"
|
|
1195
|
+
"\t *\n"
|
|
1196
|
+
"\t * bias-pull-up is DSC=1, a REAL pull-up: pinctrl_soc.h's field comment\n"
|
|
1197
|
+
"\t * (soc/alif/ensemble/pinctrl_soc.h:24) and its ALIF_PINCTRL_BIAS_CFG()\n"
|
|
1198
|
+
"\t * macro (:44-48) both map bias-pull-up -> DSC=1 unconditionally -- there\n"
|
|
1199
|
+
"\t * is NO inversion between the devicetree property and the DSC field, on\n"
|
|
1200
|
+
"\t * this bus or the e1m_i2c0 one below (#2046 corrected that group's\n"
|
|
1201
|
+
"\t * comment, which used to claim the opposite). Do NOT copy the\n"
|
|
1202
|
+
"\t * I2C2/EEPROM overlay's bias-pull-down: DSC=2 is a pull-DOWN, harmless\n"
|
|
1203
|
+
"\t * there only because that bus has external carrier pull-ups (R137/R144).\n"
|
|
1204
|
+
"\t * This net has NO external pull-up at all, so a pull-down would park both\n"
|
|
1205
|
+
"\t * lines low and look like a busy bus.\n"
|
|
1206
|
+
"\t *\n"
|
|
1207
|
+
"\t * drive-open-drain is deliberately NOT set, so bit 23 [DRV] stays 0 =\n"
|
|
1208
|
+
"\t * PUSH-PULL: the pad actively drives the high phase instead of relying on\n"
|
|
1209
|
+
"\t * a pull-up this net does not have. input-schmitt-enable + max\n"
|
|
1210
|
+
"\t * drive-strength/slew-rate cover the resulting slow edges. Bench-proven\n"
|
|
1211
|
+
"\t * 2026-09-05 on E1M-AEN801 2626-R2: every non-response is a clean rc=-5\n"
|
|
1212
|
+
"\t * (-EIO) NACK, zero -ETIMEDOUT, zero \"User Abort\".\n"
|
|
1213
|
+
"\t */\n"
|
|
1214
|
+
f"\t{bus['pinctrl_group_label']}: {bus['pinctrl_group_label']} {{\n"
|
|
1215
|
+
"\t\tgroup0 {\n"
|
|
1216
|
+
f"\t\t\tpinmux = <{_pin_macro(sda)}>, <{_pin_macro(scl)}>;\n"
|
|
1217
|
+
"\t\t\tinput-enable;\n"
|
|
1218
|
+
"\t\t\tbias-pull-up;\n"
|
|
1219
|
+
"\t\t\tinput-schmitt-enable;\n"
|
|
1220
|
+
"\t\t\tdrive-strength = <12>;\n"
|
|
1221
|
+
"\t\t\tslew-rate = \"fast\";\n"
|
|
1222
|
+
"\t\t};\n"
|
|
1223
|
+
"\n"
|
|
1224
|
+
"\t\t/*\n"
|
|
1225
|
+
f"\t\t * {alarm['net']}: the RV-3028 /INT line on {alarm['silicon_pad']} "
|
|
1226
|
+
f"({alarm['gpio_node']} bit {alarm['gpio_pin']}).\n"
|
|
1227
|
+
"\t\t * Muxed + input-enabled here because snps,designware-gpio has no\n"
|
|
1228
|
+
"\t\t * pinctrl-0 of its own (see this function's docstring); without it the\n"
|
|
1229
|
+
"\t\t * pad input buffer stays off and the alarm reads 0 forever.\n"
|
|
1230
|
+
"\t\t */\n"
|
|
1231
|
+
"\t\tgroup1 {\n"
|
|
1232
|
+
f"\t\t\tpinmux = <{_pin_macro(alarm)}>;\n"
|
|
1233
|
+
"\t\t\tinput-enable;\n"
|
|
1234
|
+
"\t\t};\n"
|
|
1235
|
+
"\t};\n"
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
def _aen_e1m_i2c0_pinctrl_group(links: dict[str, Any], part: str) -> str:
|
|
1240
|
+
"""The `e1m_i2c0` (SoC I2C2) pinctrl group, from metadata.
|
|
1241
|
+
|
|
1242
|
+
Unlike `_aen_i2c_pinctrl_group()` this bus carries NO alarm-style
|
|
1243
|
+
passenger pad and gets NO device child nodes in `_aen_e1m_i2c0_dts()`
|
|
1244
|
+
below (its only genuinely on-module device, the 24C128 EEPROM, is
|
|
1245
|
+
addressed by 7-bit address over the bus, not a DT device node -- see
|
|
1246
|
+
the `e1m_i2c0` entry's own comment in on-module-links.yaml).
|
|
1247
|
+
|
|
1248
|
+
`bias-pull-down` here is intentional and matches Alif's own reference
|
|
1249
|
+
I2C pinctrl (metadata says why). `part` (the Ensemble part designator
|
|
1250
|
+
this board tree is for, e.g. "E8") picks which of on_module_links'
|
|
1251
|
+
`e1m_i2c0.bench_validation` map's citations -- if any -- this comment
|
|
1252
|
+
carries: the only bench run on record is against the E8, and #2046
|
|
1253
|
+
found that citation being emitted unqualified into every OTHER AEN
|
|
1254
|
+
part's board tree (E3 included) as the same class of leak #1988 fixed
|
|
1255
|
+
for `rtc_alarm.risk`. Unlike that fix, a part with no entry here does
|
|
1256
|
+
NOT get silence -- #2046 is explicit that the citation must not be
|
|
1257
|
+
silently dropped, only qualified, because the emitted PAD VALUE
|
|
1258
|
+
(bias-pull-down) is unchanged and still correct for every part.
|
|
1259
|
+
"""
|
|
1260
|
+
bus = links["e1m_i2c0"]
|
|
1261
|
+
sda = _pin_by_peripheral(bus["pins"], "I2C2_SDA_C")
|
|
1262
|
+
scl = _pin_by_peripheral(bus["pins"], "I2C2_SCL_C")
|
|
1263
|
+
bench = (bus.get("bench_validation") or {})
|
|
1264
|
+
this_part_bench = bench.get(part)
|
|
1265
|
+
if this_part_bench:
|
|
1266
|
+
bench_text = f"input-enable + bias-pull-down: {this_part_bench}"
|
|
1267
|
+
else:
|
|
1268
|
+
e8_bench = bench.get("E8", "")
|
|
1269
|
+
bench_text = (
|
|
1270
|
+
"input-enable + bias-pull-down: bench-validated ONLY on the E8 "
|
|
1271
|
+
f"-- {e8_bench} This board tree is for the {part}; that bench "
|
|
1272
|
+
f"run has NOT been independently repeated on the {part}'s own "
|
|
1273
|
+
"silicon (#2046, following the #1988 per-part-evidence "
|
|
1274
|
+
"pattern). The config below is still what this board tree "
|
|
1275
|
+
"emits -- on_module_links.yaml is one file for every AEN SKU "
|
|
1276
|
+
f"and no {part}-specific bench run exists yet to confirm or "
|
|
1277
|
+
"override it -- but that silence is not proof either way."
|
|
1278
|
+
)
|
|
1279
|
+
# Reflowed to house-style comment width (metadata prose has no line
|
|
1280
|
+
# breaks of its own to preserve, unlike the hand-wrapped paragraphs
|
|
1281
|
+
# below) -- the raw bench_validation string can run well past 79
|
|
1282
|
+
# columns unwrapped, same reason _c_comment() exists for evidence/risk
|
|
1283
|
+
# strings elsewhere in this file.
|
|
1284
|
+
bench_lines = [line + "\n" for line in _c_comment(bench_text, "\t")[1:-1]]
|
|
1285
|
+
return (
|
|
1286
|
+
"\t/*\n"
|
|
1287
|
+
f"\t * e1m_i2c0 = SoC {bus['peripheral']} function C: {sda['silicon_pad']} SDA / "
|
|
1288
|
+
f"{scl['silicon_pad']} SCL,\n"
|
|
1289
|
+
"\t * portable alp-i2c0 (E1M edge bus 0 -- ALP_E1M_I2C0 /\n"
|
|
1290
|
+
"\t * EVK_I2C_BUS_SENSORS). Carries the SoM's own 24C128 manifest EEPROM;\n"
|
|
1291
|
+
"\t * the same pads also reach the EVK carrier's sensor bus once they leave\n"
|
|
1292
|
+
"\t * the module (docs/bring-up-aen.md Sec 5.1 -- two separate buses).\n"
|
|
1293
|
+
"\t *\n"
|
|
1294
|
+
+ "".join(bench_lines) +
|
|
1295
|
+
"\t *\n"
|
|
1296
|
+
"\t * bias-pull-down really is a pull-down (DSC=2), and bias-pull-up really\n"
|
|
1297
|
+
"\t * would be a pull-up (DSC=1) -- pinctrl_soc.h does NOT invert the two.\n"
|
|
1298
|
+
"\t * Its ALIF_PINCTRL_BIAS_CFG() macro (soc/alif/ensemble/pinctrl_soc.h:44-48)\n"
|
|
1299
|
+
"\t * maps bias-pull-up -> DSC=1 and bias-pull-down -> DSC=2 unconditionally,\n"
|
|
1300
|
+
"\t * exactly as the field's own comment at :24 describes. An earlier version\n"
|
|
1301
|
+
"\t * of this comment (and of examples/aen/aen-eeprom-manifest's overlay) blamed\n"
|
|
1302
|
+
"\t * bias-pull-up's reported dead bus on that encoding \"reading inverted vs\n"
|
|
1303
|
+
"\t * the Alif pad hardware\" -- pinctrl_soc.h has no such inversion, so that\n"
|
|
1304
|
+
"\t * explanation is RETRACTED (#2046), and the dead-bus report it was invented\n"
|
|
1305
|
+
"\t * to explain is UNCONFIRMED, not disproven: nobody has re-run bias-pull-up\n"
|
|
1306
|
+
"\t * here in a controlled bench trial since. What IS established, matching\n"
|
|
1307
|
+
"\t * the BRD_I2C group's own reasoning above: this bus, unlike BRD_I2C, has\n"
|
|
1308
|
+
"\t * the EVK carrier's R137/R144 pull-ups stuffed, so a real internal\n"
|
|
1309
|
+
"\t * pull-down is harmless -- the strong external pull-up dominates a weak\n"
|
|
1310
|
+
"\t * internal one -- which is why bias-pull-down is bench-proven correct as\n"
|
|
1311
|
+
"\t * emitted. Do NOT change it to bias-pull-up on the strength of either\n"
|
|
1312
|
+
"\t * reading; that needs a fresh register-level bench comparison, not a theory.\n"
|
|
1313
|
+
"\t */\n"
|
|
1314
|
+
f"\t{bus['pinctrl_group_label']}: {bus['pinctrl_group_label']} {{\n"
|
|
1315
|
+
"\t\tgroup0 {\n"
|
|
1316
|
+
# SCL, SDA -- matches the bench-validated overlay's own pinmux
|
|
1317
|
+
# order verbatim (examples/aen/aen-i2c2-eeprom-regcheck and
|
|
1318
|
+
# aen-eeprom-manifest), not the SDA-first order the BRD_I2C group
|
|
1319
|
+
# above happens to use.
|
|
1320
|
+
f"\t\t\tpinmux = <{_pin_macro(scl)}>, <{_pin_macro(sda)}>;\n"
|
|
1321
|
+
"\t\t\tinput-enable;\n"
|
|
1322
|
+
"\t\t\tbias-pull-down;\n"
|
|
1323
|
+
"\t\t};\n"
|
|
1324
|
+
"\t};\n"
|
|
1325
|
+
)
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
def _aen_brd_i2c_dts(links: dict[str, Any], part: str) -> list[str]:
|
|
1329
|
+
"""`&i2c0` (BRD_I2C) + its on-module device nodes, `&lpgpio`, and the
|
|
1330
|
+
aliases that make all three reachable portably.
|
|
1331
|
+
|
|
1332
|
+
Board layer, not a per-app overlay and not the SoC peripherals dtsi:
|
|
1333
|
+
these parts are soldered on the E1M-AEN SoM, so every app built for this
|
|
1334
|
+
board should get them for free -- while
|
|
1335
|
+
`zephyr/dts/alif/ensemble_e8_peripherals.dtsi` describes the E8 DIE, which
|
|
1336
|
+
every AEN SKU shares whether or not it carries these chips.
|
|
1337
|
+
|
|
1338
|
+
*part* (the SoC JSON's `part`, e.g. ``"E8"``, ``"E3"``) gates the
|
|
1339
|
+
alarm-node risk comment below -- see that block for why.
|
|
1340
|
+
|
|
1341
|
+
RELOCATED divergence, RETIRED (alp-sdk#1988 / tan-cli#493): this used to
|
|
1342
|
+
gate on a hardcoded ``if part == "E8"`` against a plain-string
|
|
1343
|
+
`on_module_links.rtc_alarm.risk` (tan's own independent fix for the same
|
|
1344
|
+
hazard `_aen_brd_i2c_dts` names -- an E8-only DFP warning bleeding into
|
|
1345
|
+
an E3/E4/E6 board tree). alp-sdk's own fix for the same issue landed
|
|
1346
|
+
later and reshapes the metadata field itself into a per-part map
|
|
1347
|
+
(``risk: {E8: "..."}``) rather than gating in code -- ported here to
|
|
1348
|
+
match: an unstated part gets no paragraph, and any future DFP that
|
|
1349
|
+
confirms the same quirk is added to the map, not to a code branch.
|
|
1350
|
+
"""
|
|
1351
|
+
bus = links["brd_i2c"]
|
|
1352
|
+
alarm = links["rtc_alarm"]
|
|
1353
|
+
sda = _pin_by_peripheral(bus["pins"], "I2C0_SDA_C")
|
|
1354
|
+
scl = _pin_by_peripheral(bus["pins"], "I2C0_SCL_C")
|
|
1355
|
+
devices = bus.get("devices") or []
|
|
1356
|
+
|
|
1357
|
+
lines = [
|
|
1358
|
+
"/*",
|
|
1359
|
+
f" * BRD_I2C -- the on-module housekeeping bus: SoC {bus['peripheral']} in its function-C",
|
|
1360
|
+
f" * muxing ({sda['silicon_pad']} SDA / {scl['silicon_pad']} SCL), NOT the slave-only LPI2C0 it was",
|
|
1361
|
+
" * believed to be before #1848. Driven by UPSTREAM",
|
|
1362
|
+
' * Zephyr i2c_dw ("snps,designware-i2c"), ADR 0017 Tier-1 -- no vendored code.',
|
|
1363
|
+
" *",
|
|
1364
|
+
" * ISOLATED: R93/R94 (the 0-ohm bridge to the I2C2/EEPROM segment) are DNP, so",
|
|
1365
|
+
" * the 24C128 manifest EEPROM is NOT on this bus -- it stays on i2c2. The",
|
|
1366
|
+
" * OPTIGA Trust M (IC1, @0x30) is DNP=1 on this batch and so has NO node here;",
|
|
1367
|
+
" * its silence is a negative control, not a fault.",
|
|
1368
|
+
" *",
|
|
1369
|
+
" * Both device drivers are upstream and Kconfig `default y` on their own",
|
|
1370
|
+
" * DT_HAS_*_ENABLED, so an app needs only CONFIG_RTC / CONFIG_SENSOR in its",
|
|
1371
|
+
" * prj.conf -- the board layer deliberately forces neither.",
|
|
1372
|
+
" */",
|
|
1373
|
+
f"&{bus['dt_label']} {{",
|
|
1374
|
+
'\tstatus = "okay";',
|
|
1375
|
+
f"\tpinctrl-0 = <&{bus['pinctrl_group_label']}>;",
|
|
1376
|
+
'\tpinctrl-names = "default";',
|
|
1377
|
+
f"\tclock-frequency = <{bus['clock_frequency_macro']}>;",
|
|
1378
|
+
]
|
|
1379
|
+
lines += _aen_i2c_device_nodes(bus, links)
|
|
1380
|
+
lines += ["};", ""]
|
|
1381
|
+
|
|
1382
|
+
lines += [
|
|
1383
|
+
"/*",
|
|
1384
|
+
f" * {alarm['gpio_node']} -- needed so the RV-3028 alarm on {alarm['silicon_pad']} "
|
|
1385
|
+
f"({alarm['net']}, bit {alarm['gpio_pin']} =>",
|
|
1386
|
+
f" * IRQ {alarm['gpio_irq']}) is usable as an interrupt source. Active LOW,",
|
|
1387
|
+
" * open-drain, pulled up on-module by R98 (100k to +1V8, FITTED). The pad",
|
|
1388
|
+
" * itself is muxed in the pinctrl dtsi's BRD_I2C group (that binding has no",
|
|
1389
|
+
" * pinctrl-0 of its own).",
|
|
1390
|
+
]
|
|
1391
|
+
# The port-15 control-register warning, straight from the metadata's own
|
|
1392
|
+
# per-part `risk:` map (alp-sdk#1988) -- evidenced only against this
|
|
1393
|
+
# board's own SoC part's DFP: on-module-links.yaml's `risk:` names only
|
|
1394
|
+
# "The AE822 DFP" -- the E8 part's datasheet -- so a family-scoped
|
|
1395
|
+
# emission bled E8 register-layout guidance into an E1M-AEN301 (E3)
|
|
1396
|
+
# board tree (tan-cli#493's
|
|
1397
|
+
# test_a_non_e8_aen_sku_includes_its_own_peripherals_overlay caught it).
|
|
1398
|
+
# A part with no entry in the map gets no paragraph: an unstated DFP
|
|
1399
|
+
# citation is not evidence for this silicon.
|
|
1400
|
+
risk_text = (alarm.get("risk") or {}).get(part)
|
|
1401
|
+
if risk_text:
|
|
1402
|
+
lines += [" *"]
|
|
1403
|
+
lines += _c_comment(risk_text, "")[1:-1]
|
|
1404
|
+
lines += [
|
|
1405
|
+
" */",
|
|
1406
|
+
f"&{alarm['gpio_node']} {{",
|
|
1407
|
+
'\tstatus = "okay";',
|
|
1408
|
+
"};",
|
|
1409
|
+
"",
|
|
1410
|
+
]
|
|
1411
|
+
|
|
1412
|
+
alias_lines = [f"\t\t{bus['alias']} = &{bus['dt_label']};"]
|
|
1413
|
+
for dev in devices:
|
|
1414
|
+
if dev.get("alias"):
|
|
1415
|
+
alias_lines.append(f"\t\t{dev['alias']} = &{dev['node_label']};")
|
|
1416
|
+
lines += [
|
|
1417
|
+
"/*",
|
|
1418
|
+
" * Portable aliases. The bus alias is what alp_i2c_open(.bus_id = N)",
|
|
1419
|
+
" * resolves (src/backends/i2c/zephyr_drv.c -> DT_ALIAS(alp_i2cN)), matching",
|
|
1420
|
+
" * the on-module EEPROM's own convention on i2c2; the device aliases let an",
|
|
1421
|
+
" * example say DT_ALIAS(rtc) / DT_ALIAS(alp_temp0) instead of hardcoding",
|
|
1422
|
+
" * this board's node labels.",
|
|
1423
|
+
" */",
|
|
1424
|
+
"/ {",
|
|
1425
|
+
"\taliases {",
|
|
1426
|
+
*alias_lines,
|
|
1427
|
+
"\t};",
|
|
1428
|
+
"};",
|
|
1429
|
+
"",
|
|
1430
|
+
]
|
|
1431
|
+
return lines
|
|
1432
|
+
|
|
1433
|
+
|
|
1434
|
+
def _aen_e1m_i2c0_dts(links: dict[str, Any]) -> list[str]:
|
|
1435
|
+
"""`&i2c2` (e1m_i2c0, portable alp-i2c0) + its bus alias.
|
|
1436
|
+
|
|
1437
|
+
No device child nodes (see `_aen_e1m_i2c0_pinctrl_group()`'s
|
|
1438
|
+
docstring): the only genuinely on-module part on this bus, the 24C128
|
|
1439
|
+
manifest EEPROM, is addressed by 7-bit address over the bus rather
|
|
1440
|
+
than a DT device node, the same way
|
|
1441
|
+
examples/aen/aen-i2c2-eeprom-regcheck and
|
|
1442
|
+
examples/aen/aen-eeprom-manifest already open it. The EVK carrier's
|
|
1443
|
+
sensor/IO-expander/INA236 parts that also ride this bus once it
|
|
1444
|
+
leaves the module are the EVK board layer's concern, not this SoM
|
|
1445
|
+
board layer's.
|
|
1446
|
+
"""
|
|
1447
|
+
bus = links["e1m_i2c0"]
|
|
1448
|
+
sda = _pin_by_peripheral(bus["pins"], "I2C2_SDA_C")
|
|
1449
|
+
scl = _pin_by_peripheral(bus["pins"], "I2C2_SCL_C")
|
|
1450
|
+
|
|
1451
|
+
return [
|
|
1452
|
+
"/*",
|
|
1453
|
+
f" * e1m_i2c0 -- SoC {bus['peripheral']} in its function-C muxing "
|
|
1454
|
+
f"({sda['silicon_pad']} SDA / {scl['silicon_pad']} SCL), portable",
|
|
1455
|
+
f" * {bus['alias']} (E1M edge bus 0). Carries the SoM's own 24C128 manifest",
|
|
1456
|
+
" * EEPROM; the same physical bus also reaches the EVK carrier's sensor bus",
|
|
1457
|
+
" * once the pads leave the module (docs/bring-up-aen.md Sec 5.1). Driven by",
|
|
1458
|
+
' * UPSTREAM Zephyr i2c_dw ("snps,designware-i2c"), ADR 0017 Tier-1 -- no',
|
|
1459
|
+
" * vendored code.",
|
|
1460
|
+
" */",
|
|
1461
|
+
f"&{bus['dt_label']} {{",
|
|
1462
|
+
'\tstatus = "okay";',
|
|
1463
|
+
f"\tpinctrl-0 = <&{bus['pinctrl_group_label']}>;",
|
|
1464
|
+
'\tpinctrl-names = "default";',
|
|
1465
|
+
f"\tclock-frequency = <{bus['clock_frequency_macro']}>;",
|
|
1466
|
+
"};",
|
|
1467
|
+
"",
|
|
1468
|
+
"/*",
|
|
1469
|
+
" * Portable alias for e1m_i2c0. alp_i2c_open(.bus_id = ALP_E1M_I2C0)",
|
|
1470
|
+
" * resolves this via src/backends/i2c/zephyr_drv.c -> DT_ALIAS(alp_i2c0);",
|
|
1471
|
+
" * matches the EEPROM manifest reader's own",
|
|
1472
|
+
" * CONFIG_ALP_SDK_HW_INFO_EEPROM_I2C_BUS_ID=0 convention.",
|
|
1473
|
+
" */",
|
|
1474
|
+
"/ {",
|
|
1475
|
+
"\taliases {",
|
|
1476
|
+
f"\t\t{bus['alias']} = &{bus['dt_label']};",
|
|
1477
|
+
"\t};",
|
|
1478
|
+
"};",
|
|
1479
|
+
"",
|
|
1480
|
+
]
|
|
1481
|
+
|
|
1482
|
+
|
|
1483
|
+
def _aen_ethos_u(soc_spec: dict[str, Any]) -> tuple[str, str] | None:
|
|
1484
|
+
"""The Arm Ethos-U accelerator this SoC's silicon carries, as
|
|
1485
|
+
``(accelerator_config_kconfig, dt_node_label)`` -- or ``None`` when the
|
|
1486
|
+
SoC has no NPU.
|
|
1487
|
+
|
|
1488
|
+
A SoC carrying more than one Ethos-U variant (the E8 has 2x U55 + 1x U85)
|
|
1489
|
+
builds the core driver for its MOST capable NPU -- the U85 -- because the
|
|
1490
|
+
upstream Arm Ethos-U driver compiles exactly one accelerator config per
|
|
1491
|
+
image (``ETHOS_U_NPU_CONFIG`` is a Kconfig ``choice``). Vela targets must
|
|
1492
|
+
match (``--accelerator-config ethos-u85-256`` for the U85 case).
|
|
1493
|
+
|
|
1494
|
+
Used only to flip the (already-declared, disabled) NPU DT node to
|
|
1495
|
+
``status="okay"`` on the generated silicon board -- harmless on its own
|
|
1496
|
+
(``CONFIG_ETHOS_U`` is ``default n``, so no driver binds until an inference
|
|
1497
|
+
app selects it via ``ALP_SDK_INFERENCE_BACKEND_ETHOS_U_AEN``).
|
|
1498
|
+
"""
|
|
1499
|
+
caps = soc_spec.get("capabilities") or {}
|
|
1500
|
+
if (caps.get("ethos_u85_count") or 0) > 0:
|
|
1501
|
+
return ("ETHOS_U85_256", "ethosu85")
|
|
1502
|
+
if (caps.get("ethos_u55_count") or 0) > 0:
|
|
1503
|
+
return ("ETHOS_U55_256", "ethosu55")
|
|
1504
|
+
return None
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
def _aen_defconfig(
|
|
1508
|
+
uart_node: str, rx_row: dict[str, Any], tx_row: dict[str, Any],
|
|
1509
|
+
slot0_base: int,
|
|
1510
|
+
) -> str:
|
|
1511
|
+
# CONFIG_USE_DT_CODE_PARTITION is the Kconfig half of the very
|
|
1512
|
+
# `zephyr,code-partition = &slot0_partition;` chosen that _aen_dts()
|
|
1513
|
+
# (below) lays down for every board this function serves -- the two are
|
|
1514
|
+
# emitted as a pair, from the same MRAM partition map, so the board can
|
|
1515
|
+
# never carry the DT fact without the Kconfig that honours it. Without
|
|
1516
|
+
# it Zephyr leaves FLASH_LOAD_OFFSET at 0 and the image links at the MRAM
|
|
1517
|
+
# base 0x80000000, on top of the boot partition: the reset vector comes
|
|
1518
|
+
# out 0x8000xxxx, which `alif_flash` rejects ("unrecognised reset vector
|
|
1519
|
+
# ... expected a slot0-XIP image") and which faults if it is written
|
|
1520
|
+
# anyway. Set here, in the board layer, rather than in each app's
|
|
1521
|
+
# prj.conf, because the link offset is a property of THIS BOARD's flash
|
|
1522
|
+
# map, not of any app -- so it reaches every consumer of the board
|
|
1523
|
+
# equally (plain `west build`, twister, the board.yaml planner, `tan`).
|
|
1524
|
+
# It is also where upstream Zephyr boards with a fixed bootloader slot
|
|
1525
|
+
# set it (`grep -l USE_DT_CODE_PARTITION=y zephyr/boards/**/*_defconfig`).
|
|
1526
|
+
#
|
|
1527
|
+
# A Flow C ITCM RAM-run deliberately undoes it (a board defconfig is the
|
|
1528
|
+
# lowest-precedence Kconfig layer): `scripts/bench/aen/aen-flowc-itcm.conf`
|
|
1529
|
+
# + `aen-flowc-itcm.overlay`. MCUboot's own image is unaffected -- it
|
|
1530
|
+
# re-points the chosen at &boot_partition via mcuboot's app.overlay.
|
|
1531
|
+
return (
|
|
1532
|
+
_COPYRIGHT_HASH +
|
|
1533
|
+
"\n"
|
|
1534
|
+
"CONFIG_ARM_MPU=y\n"
|
|
1535
|
+
"CONFIG_HW_STACK_PROTECTION=y\n"
|
|
1536
|
+
"\n"
|
|
1537
|
+
"# Link the image into the board's `zephyr,code-partition`\n"
|
|
1538
|
+
f"# (slot0_partition, MRAM 0x{slot0_base:08x}) instead of the MRAM base --\n"
|
|
1539
|
+
"# without it the reset vector lands at 0x8000xxxx and no AEN flash\n"
|
|
1540
|
+
"# flow accepts the image. Flow C (ITCM RAM-run) overrides it via\n"
|
|
1541
|
+
"# scripts/bench/aen/aen-flowc-itcm.conf.\n"
|
|
1542
|
+
"CONFIG_USE_DT_CODE_PARTITION=y\n"
|
|
1543
|
+
"\n"
|
|
1544
|
+
# Silicon pads read from metadata/pinmux/aen.yaml, exactly as the
|
|
1545
|
+
# sibling .dts and -pinctrl.dtsi do -- they used to be the string
|
|
1546
|
+
# literal "P3_4/P3_5", the AEN801 console pads, on every SKU.
|
|
1547
|
+
f"# Console: Alif {uart_node.upper()} (E1M edge \"UART0\", "
|
|
1548
|
+
f"{rx_row['silicon_pad']}/{tx_row['silicon_pad']}) "
|
|
1549
|
+
"-- NS16550-class UART.\n"
|
|
1550
|
+
"CONFIG_SERIAL=y\n"
|
|
1551
|
+
"CONFIG_CONSOLE=y\n"
|
|
1552
|
+
"CONFIG_UART_CONSOLE=y\n"
|
|
1553
|
+
"CONFIG_UART_INTERRUPT_DRIVEN=y\n"
|
|
1554
|
+
"CONFIG_UART_NS16550=y\n"
|
|
1555
|
+
)
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
# Board-level LOG_MODE default for every AEN board (alp-sdk issue #1373,
|
|
1559
|
+
# ported from alp-sdk#1407). Emitted into the generated `Kconfig.defconfig`;
|
|
1560
|
+
# the two hand-authored AEN board trees (e1m_aen401_m55_hp, e1m_aen601_m55_hp)
|
|
1561
|
+
# carry a byte-identical copy on the alp-sdk side, kept in step by that repo's
|
|
1562
|
+
# own `tests/scripts/test_gen_zephyr_board.py`.
|
|
1563
|
+
#
|
|
1564
|
+
# WHY: Zephyr's own `choice LOG_MODE` default is LOG_MODE_DEFERRED
|
|
1565
|
+
# (subsys/logging/Kconfig.mode), which no AEN board ever set or unset -- it
|
|
1566
|
+
# is inherited. Deferred mode hands every record to the LOG_PROCESS_THREAD
|
|
1567
|
+
# (CONFIG_LOG_PROCESS_THREAD is `default y`), and CONFIG_LOG_PRINTK is
|
|
1568
|
+
# `default y if PRINTK`, so printk() -- including the Alp SDK boot banner --
|
|
1569
|
+
# is deferred with it. The AEN bench procedure deliberately runs apps whose
|
|
1570
|
+
# main() never yields (`for (;;) { k_busy_wait(1000); }`) because an idling
|
|
1571
|
+
# M55 makes the Secure Enclave gate the DAP and the SE-UART together. A
|
|
1572
|
+
# main() that never yields never lets the log thread run: with
|
|
1573
|
+
# CONFIG_LOG_PROCESS_THREAD_CUSTOM_PRIORITY=n (the default) log_core.c runs
|
|
1574
|
+
# that thread at K_LOWEST_APPLICATION_THREAD_PRIO (= CONFIG_NUM_PREEMPT_
|
|
1575
|
+
# PRIORITIES - 1), strictly below main's CONFIG_MAIN_THREAD_PRIORITY=0, and
|
|
1576
|
+
# time-slicing rotates only among READY threads of EQUAL priority -- so a
|
|
1577
|
+
# busy-looping main() at 0 starves it outright and a healthy, fault-free
|
|
1578
|
+
# board emits ZERO bytes. Measured on E1M-AEN801 silicon: PC inside
|
|
1579
|
+
# `z_impl_k_busy_wait`, IPSR = 000 (NoException), CFSR @ 0xE000ED28 =
|
|
1580
|
+
# 00000000, zero UART bytes in a 15 s capture; the same source with
|
|
1581
|
+
# LOG_MODE_MINIMAL prints the banner and both LOG_INF lines. LOG_MODE_MINIMAL
|
|
1582
|
+
# formats and writes in the calling context, so it cannot be starved by a
|
|
1583
|
+
# non-yielding main().
|
|
1584
|
+
#
|
|
1585
|
+
# WHY A CHOICE `default` AND NOT `CONFIG_LOG_MODE_MINIMAL=y` IN THE BOARD
|
|
1586
|
+
# `_defconfig`: the `_defconfig` form assigns the symbol unconditionally,
|
|
1587
|
+
# including on the 47 `CONFIG_LOG=n` fragments under examples/aen/, where
|
|
1588
|
+
# the choice is invisible. Zephyr's scripts/kconfig/kconfig.py then runs
|
|
1589
|
+
# check_assigned_choice_values() and prints "The choice symbol
|
|
1590
|
+
# LOG_MODE_MINIMAL ... was selected (set =y), but no symbol ended up as the
|
|
1591
|
+
# choice selection" on every one of those builds. A Kconfig.defconfig
|
|
1592
|
+
# default is inert when LOG=n. Precedence is unchanged either way:
|
|
1593
|
+
# Kconfig.zephyr sources the board's Kconfig.defconfig ahead of subsys/
|
|
1594
|
+
# Kconfig precisely so board defaults outrank upstream ones, and an app that
|
|
1595
|
+
# wants the deferred backend pipeline still overrides this with
|
|
1596
|
+
# CONFIG_LOG_MODE_DEFERRED=y in its own prj.conf.
|
|
1597
|
+
_AEN_LOG_MODE_DEFAULT = (
|
|
1598
|
+
"# Logging: default to LOG_MODE_MINIMAL, not Zephyr's inherited\n"
|
|
1599
|
+
"# LOG_MODE_DEFERRED. Deferred mode needs CONFIG_LOG_PROCESS_THREAD to run,\n"
|
|
1600
|
+
"# and the AEN bench procedure runs apps whose main() never yields (a\n"
|
|
1601
|
+
"# non-yielding busy loop is what keeps the Secure Enclave from gating the\n"
|
|
1602
|
+
"# DAP and the SE-UART -- see docs/debugging-aen.md section 4). A\n"
|
|
1603
|
+
"# non-yielding main() starves the log thread, and because CONFIG_LOG_PRINTK\n"
|
|
1604
|
+
"# routes printk() through the same queue the Alp SDK banner disappears too:\n"
|
|
1605
|
+
"# a running, fault-free board prints ZERO bytes. Measured on E1M-AEN801\n"
|
|
1606
|
+
"# silicon (issue #1373) -- PC inside z_impl_k_busy_wait, IPSR = 000,\n"
|
|
1607
|
+
"# CFSR @ 0xE000ED28 = 00000000, and no UART output at all. Minimal mode\n"
|
|
1608
|
+
"# formats in the calling context, so the same app prints. An app that\n"
|
|
1609
|
+
"# wants the deferred pipeline (backends, runtime filtering, timestamps)\n"
|
|
1610
|
+
"# overrides this with CONFIG_LOG_MODE_DEFERRED=y in its prj.conf.\n"
|
|
1611
|
+
"choice LOG_MODE\n"
|
|
1612
|
+
"\tdefault LOG_MODE_MINIMAL\n"
|
|
1613
|
+
"endchoice\n"
|
|
1614
|
+
"\n"
|
|
1615
|
+
)
|
|
1616
|
+
|
|
1617
|
+
|
|
1618
|
+
def _aen_kconfig_defconfig(dir_name: str, role: str, part: str) -> str:
|
|
1619
|
+
board_sym = dir_name.upper()
|
|
1620
|
+
role_u = role.upper()
|
|
1621
|
+
# NUM_IRQS=480 is an Ensemble-FAMILY constant, not a per-part one:
|
|
1622
|
+
# upstream Zephyr defaults it to 480 for every RTSS cluster it ships
|
|
1623
|
+
# (soc/alif/ensemble/{e1c,e4,e6,e8}/Kconfig.defconfig.*_rtss_{hp,he}).
|
|
1624
|
+
# The part designator in the sentence is NOT -- it used to be the
|
|
1625
|
+
# literal "E8" on every E1M-AEN* board.
|
|
1626
|
+
return (
|
|
1627
|
+
_COPYRIGHT_HASH +
|
|
1628
|
+
"\n"
|
|
1629
|
+
f"if BOARD_{board_sym}\n"
|
|
1630
|
+
"\n"
|
|
1631
|
+
f"# The Ensemble {part} RTSS-{role_u} has CONFIG_NUM_IRQS=480, so the ARMv8-M vector table is\n"
|
|
1632
|
+
"# (16 + 480) * 4 = 1984 bytes and the hardware VTOR must be 2 KiB (0x800) aligned.\n"
|
|
1633
|
+
"# For an MCUboot chain-loaded image the linker therefore places _vector_table at\n"
|
|
1634
|
+
"# slot_base + 0x800, but Zephyr hard-defaults the MCUboot header (ROM_START_OFFSET,\n"
|
|
1635
|
+
"# which is also imgtool --header-size) to 0x200 under BOOTLOADER_MCUBOOT and makes\n"
|
|
1636
|
+
"# it non-promptable. MCUboot then jumps to slot_base + 0x200 (header end) and reads\n"
|
|
1637
|
+
"# a garbage SP/PC from the 0x600 padding gap -> instant HardFault -> reset/reboot\n"
|
|
1638
|
+
"# loop, never reaching the app. Bump the header reservation to match the vector\n"
|
|
1639
|
+
"# alignment so the jump target == _vector_table. (Affects only chain-loaded images;\n"
|
|
1640
|
+
"# MCUboot itself builds with BOOTLOADER_MCUBOOT=n.)\n"
|
|
1641
|
+
"config ROM_START_OFFSET\n"
|
|
1642
|
+
"\tdefault 0x800 if BOOTLOADER_MCUBOOT\n"
|
|
1643
|
+
"\n"
|
|
1644
|
+
"# alp-sdk's own chips/tmp112/tmp112.c defines tmp112_init(), and upstream\n"
|
|
1645
|
+
"# Zephyr's zephyr/drivers/sensor/ti/tmp112/tmp112.c defines the SAME symbol\n"
|
|
1646
|
+
"# for the SAME part. The two only collide at LINK time, and only when an\n"
|
|
1647
|
+
"# app pulls in both -- which CONFIG_SENSOR=y does for free on this board,\n"
|
|
1648
|
+
"# because the ti,tmp112 devicetree node below auto-selects upstream's\n"
|
|
1649
|
+
"# driver regardless of whether the app uses it (#2043). Board-scope the\n"
|
|
1650
|
+
"# fix instead of leaving every app to rediscover it: default the upstream\n"
|
|
1651
|
+
"# driver OFF here. This is not a permanent no -- Kconfig.zephyr sources a\n"
|
|
1652
|
+
"# board's Kconfig.defconfig ahead of subsys/Kconfig (see the LOG_MODE\n"
|
|
1653
|
+
"# comment below for the same precedence fact), so this default only\n"
|
|
1654
|
+
"# supplies a value when nothing else assigns the symbol. An app that\n"
|
|
1655
|
+
"# genuinely wants the upstream driver is therefore expected to win by\n"
|
|
1656
|
+
"# setting CONFIG_TMP112=y explicitly in its own prj.conf -- ordinary\n"
|
|
1657
|
+
"# Kconfig behaviour for a user assignment versus a `default` -- and that\n"
|
|
1658
|
+
"# is exactly why examples/aen/aen-temp-sensor sets it by name.\n"
|
|
1659
|
+
"#\n"
|
|
1660
|
+
"# CONFIRMED ON THE REAL BOARD TARGET, not just by mechanism. `west build\n"
|
|
1661
|
+
"# -p always -b alp_e1m_aen801_m55_he/ae822fa0e5597ls0/rtss_he\n"
|
|
1662
|
+
"# examples/aen/aen-temp-sensor` links\n"
|
|
1663
|
+
"# zephyr/drivers/sensor/ti/tmp112/libdrivers__sensor__ti__tmp112.a (step\n"
|
|
1664
|
+
"# 139/148), and the resolved .config carries CONFIG_TMP112=y,\n"
|
|
1665
|
+
"# CONFIG_SENSOR=y and CONFIG_DT_HAS_TI_TMP112_ENABLED=y with this board's\n"
|
|
1666
|
+
"# `default n` in force -- the explicit CONFIG_TMP112=y in\n"
|
|
1667
|
+
"# examples/aen/aen-temp-sensor/prj.conf really does win, on silicon-\n"
|
|
1668
|
+
"# targeted Kconfig, not only in theory. If that build ever fails on\n"
|
|
1669
|
+
"# CONFIG_TMP112, this derivation is what needs revisiting, not the\n"
|
|
1670
|
+
"# build.\n"
|
|
1671
|
+
"config TMP112\n"
|
|
1672
|
+
"\tdefault n\n"
|
|
1673
|
+
"\n"
|
|
1674
|
+
+ _AEN_LOG_MODE_DEFAULT +
|
|
1675
|
+
f"endif # BOARD_{board_sym}\n"
|
|
1676
|
+
)
|
|
1677
|
+
|
|
1678
|
+
|
|
1679
|
+
#: Per-device state -> the singular ("is ...") / plural ("are ...") verb
|
|
1680
|
+
#: phrase both `_aen_ospi_device_clause()` and `_aen_ospi_population_clause()`
|
|
1681
|
+
#: read, so the two forms can't drift apart (alp-sdk#2062 review round 4).
|
|
1682
|
+
_AEN_OSPI_STATE_PHRASE = {
|
|
1683
|
+
"populated": "populated",
|
|
1684
|
+
"not_populated": "not populated",
|
|
1685
|
+
"optional": "BOM-optional, not assumed populated",
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
#: Why this SDK's AEN board never uses the OSPI0 NOR/HyperRAM for XIP boot,
|
|
1689
|
+
#: EVEN when a preset populates (or might populate) them -- alp-sdk#2062
|
|
1690
|
+
#: review round 3: "OSPI_XIP_SER does not exist on this die" is a true, cited
|
|
1691
|
+
#: fact (Alif `soc_features.h` `SOC_FEAT_OSPI_HAS_XIP_SER 0` for AE822), but it
|
|
1692
|
+
#: is NOT proof XIP is impossible on this silicon -- Alif's own
|
|
1693
|
+
#: `ospi_psram_xip.c` compiles `ospi_control_xip_ss()` out under that same
|
|
1694
|
+
#: guard and STILL calls `aes_enable_xip()`; `flash_ospi_alif.c` documents
|
|
1695
|
+
#: AE822 doing XiP "through a single FIFO location". The real reasons this
|
|
1696
|
+
#: driver stack never attempts it: hal_alif's OWN `alif_hal_ospi_xip_enable()`
|
|
1697
|
+
#: unconditionally targets the absent `XIP_SER` register (a bus fault, not a
|
|
1698
|
+
#: graceful no-op), and `flash_ospi_alif.c`'s `flash_driver_api` -- read/
|
|
1699
|
+
#: read_jedec_id/sfdp_read, bench-verified on E1M-AEN803's fitted NOR -- never
|
|
1700
|
+
#: calls it either (#915) -- so there is no in-tree driver path that would use
|
|
1701
|
+
#: OSPI0 for XIP today regardless of population. Never follow the die-level
|
|
1702
|
+
#: fact alone with a "so" -- always cite this instead.
|
|
1703
|
+
_AEN_OSPI_XIP_GAP = (
|
|
1704
|
+
"hal_alif's alif_hal_ospi_xip_enable() targets the XIP_SER register, "
|
|
1705
|
+
"absent on this die, and flash_ospi_alif.c's flash_driver_api never "
|
|
1706
|
+
"calls it -- #915")
|
|
1707
|
+
|
|
1708
|
+
|
|
1709
|
+
def _aen_ospi_device_state(
|
|
1710
|
+
dev: "dict[str, Any] | None") -> tuple[str, "str | None"]:
|
|
1711
|
+
"""(state, chip-or-None) for one `on_module` OSPI-family device dict
|
|
1712
|
+
(`ospi_memories.ospi0` or `hyperram`). *dev* is `None` when the block
|
|
1713
|
+
itself is ABSENT from the preset -- the schema only requires
|
|
1714
|
+
`on_module.silicon`, so omitting `ospi_memories:`/`hyperram:` entirely
|
|
1715
|
+
is valid, a different fact from a declared-but-empty block (alp-sdk#2062
|
|
1716
|
+
review round 4: the old code collapsed both to `{}` via `... or {}`,
|
|
1717
|
+
so an undeclared device hit the `assembled` default same as a
|
|
1718
|
+
declared one with the key merely omitted, and got printed as
|
|
1719
|
+
"populated"). `state` is `"absent"` for that case; callers must leave
|
|
1720
|
+
an absent device out of the clause text entirely.
|
|
1721
|
+
|
|
1722
|
+
For a DECLARED block, `state` is one of `"populated"`, `"not_populated"`,
|
|
1723
|
+
`"optional"` (alp-sdk#2062 review round 2 -- `assembled` is a TRI-STATE
|
|
1724
|
+
field, not a bool: `"optional"` is Python-truthy, so a naive
|
|
1725
|
+
`bool(dev.get("assembled"))` printed "populated" for the AEN301-701
|
|
1726
|
+
SKUs' genuinely-unresolved BOM question; a MISSING `assembled` key on a
|
|
1727
|
+
DECLARED device means populated per the schema default `true` (see the
|
|
1728
|
+
bug that default itself caused, documented at `metadata/e1m_modules/
|
|
1729
|
+
E1M-AEN801.yaml`'s own `hyperram:` comment), not "not populated"). `chip`
|
|
1730
|
+
is `None` when the field is `TBD`/absent so callers never print a
|
|
1731
|
+
placeholder part name -- `_is_tbd()` is this module's own relocated
|
|
1732
|
+
spelling of the helper `alp_orchestrate` uses for the same job."""
|
|
1733
|
+
if dev is None:
|
|
1734
|
+
return "absent", None
|
|
1735
|
+
chip = dev.get("chip")
|
|
1736
|
+
if not chip or _is_tbd(chip):
|
|
1737
|
+
chip = None
|
|
1738
|
+
assembled = dev.get("assembled", True)
|
|
1739
|
+
if assembled == "optional":
|
|
1740
|
+
return "optional", chip
|
|
1741
|
+
return ("populated" if assembled else "not_populated"), chip
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
def _aen_ospi_device_clause(label: str, state: str, chip: "str | None") -> str:
|
|
1745
|
+
"""One device's own clause, e.g. `"OSPI0 NOR (IS25WX256-JHLE) is
|
|
1746
|
+
populated"` / `"HyperRAM is not populated"` / `"OSPI0 NOR is
|
|
1747
|
+
BOM-optional, not assumed populated"`. Never called with `state ==
|
|
1748
|
+
"absent"` -- callers exclude an absent device before reaching here."""
|
|
1749
|
+
named = f"{label} ({chip})" if chip else label
|
|
1750
|
+
return f"{named} is {_AEN_OSPI_STATE_PHRASE[state]}"
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
def _aen_ospi_population_clause(
|
|
1754
|
+
sku_preset: dict[str, Any]) -> tuple[str, str, int]:
|
|
1755
|
+
"""Return `(clause, category, populated_count)` describing the SoM
|
|
1756
|
+
preset's own OSPI0 NOR + HyperRAM population, read from `on_module.
|
|
1757
|
+
ospi_memories.ospi0` / `on_module.hyperram` rather than a hardcoded
|
|
1758
|
+
claim (alp-sdk#2062: this used to hardcode Macronix/Winbond as
|
|
1759
|
+
"BOM-optional and NOT populated on the current batch" for every AEN SKU
|
|
1760
|
+
alike -- both the board-header banner and the MRAM-partition-map comment
|
|
1761
|
+
now call THIS one function, so they cannot disagree with each other or
|
|
1762
|
+
with the real preset the way a board emitted for a from-scratch
|
|
1763
|
+
"populated" preset showed they could: the old banner said populated,
|
|
1764
|
+
the old partition-map comment still said "not populated on this
|
|
1765
|
+
batch" two paragraphs later in the SAME file).
|
|
1766
|
+
|
|
1767
|
+
An ABSENT device block (see `_aen_ospi_device_state()`) is left out of
|
|
1768
|
+
*clause* entirely -- it is never named "not populated", since there is
|
|
1769
|
+
no declared device to describe.
|
|
1770
|
+
|
|
1771
|
+
`category` is `"populated"` if either device is definitely populated,
|
|
1772
|
+
`"optional"` if neither is definite but at least one MIGHT be
|
|
1773
|
+
(`assembled: "optional"`), else `"not_populated"` (every declared
|
|
1774
|
+
device is `assembled: false`, and an absent block contributes
|
|
1775
|
+
nothing). `populated_count` is how many of the (present) devices are
|
|
1776
|
+
definitely `"populated"` -- callers need it for singular/plural
|
|
1777
|
+
agreement when exactly one of two declared devices is populated."""
|
|
1778
|
+
on_module = sku_preset.get("on_module") or {}
|
|
1779
|
+
ospi_memories = on_module.get("ospi_memories")
|
|
1780
|
+
ospi0 = ospi_memories.get("ospi0") if ospi_memories else None
|
|
1781
|
+
hyperram = on_module.get("hyperram")
|
|
1782
|
+
nor_state, nor_chip = _aen_ospi_device_state(ospi0)
|
|
1783
|
+
ram_state, ram_chip = _aen_ospi_device_state(hyperram)
|
|
1784
|
+
|
|
1785
|
+
devices = [(label, state, chip) for label, state, chip in (
|
|
1786
|
+
("OSPI0 NOR", nor_state, nor_chip), ("HyperRAM", ram_state, ram_chip))
|
|
1787
|
+
if state != "absent"]
|
|
1788
|
+
populated_count = sum(1 for _, state, _ in devices if state == "populated")
|
|
1789
|
+
|
|
1790
|
+
if not devices:
|
|
1791
|
+
return ("no OSPI0 NOR or HyperRAM is declared on this SoM preset",
|
|
1792
|
+
"not_populated", 0)
|
|
1793
|
+
|
|
1794
|
+
states = {state for _, state, _ in devices}
|
|
1795
|
+
if "populated" in states:
|
|
1796
|
+
category = "populated"
|
|
1797
|
+
elif "optional" in states:
|
|
1798
|
+
category = "optional"
|
|
1799
|
+
else:
|
|
1800
|
+
category = "not_populated"
|
|
1801
|
+
|
|
1802
|
+
if len(devices) == 2 and devices[0][1] == devices[1][1]:
|
|
1803
|
+
(nor_label, state, nchip), (ram_label, _, rchip) = devices
|
|
1804
|
+
nor_named = f"{nor_label} ({nchip})" if nchip else nor_label
|
|
1805
|
+
ram_named = f"{ram_label} ({rchip})" if rchip else ram_label
|
|
1806
|
+
clause = f"{nor_named} + {ram_named} are {_AEN_OSPI_STATE_PHRASE[state]}"
|
|
1807
|
+
else:
|
|
1808
|
+
clause = "; ".join(_aen_ospi_device_clause(label, state, chip)
|
|
1809
|
+
for label, state, chip in devices)
|
|
1810
|
+
return clause, category, populated_count
|
|
1811
|
+
|
|
1812
|
+
|
|
1813
|
+
def _aen_ospi_storage_banner_lines(sku_preset: dict[str, Any]) -> list[str]:
|
|
1814
|
+
"""Describe the SoM's OSPI0 NOR + HyperRAM for the board-header banner
|
|
1815
|
+
-- see `_aen_ospi_population_clause()`, the shared source this and the
|
|
1816
|
+
MRAM-partition-map comment both read, and `_AEN_OSPI_XIP_GAP` for why
|
|
1817
|
+
a populated (or possibly-populated) device still never sees XIP boot
|
|
1818
|
+
here."""
|
|
1819
|
+
clause, category, populated_count = _aen_ospi_population_clause(sku_preset)
|
|
1820
|
+
if category == "populated":
|
|
1821
|
+
# Singular/plural: "neither" only holds when BOTH declared devices
|
|
1822
|
+
# are actually populated (alp-sdk#2062 review round 4) -- a mixed
|
|
1823
|
+
# preset (one populated, one not/absent) gets the singular form.
|
|
1824
|
+
who = "neither is" if populated_count == 2 else "the populated device is not"
|
|
1825
|
+
tail = f"{clause}; {who} used for XIP boot here ({_AEN_OSPI_XIP_GAP});"
|
|
1826
|
+
elif category == "optional":
|
|
1827
|
+
tail = (f"{clause}, so no external XIP / flash device is assumed -- "
|
|
1828
|
+
f"even if fitted, the same gap applies ({_AEN_OSPI_XIP_GAP});")
|
|
1829
|
+
else:
|
|
1830
|
+
tail = f"{clause}, so there is no external XIP / flash device;"
|
|
1831
|
+
wrapped = textwrap.wrap(tail, width=68, break_on_hyphens=False)
|
|
1832
|
+
return [
|
|
1833
|
+
" * - runs boot + storage from on-die MRAM only. The SoM's",
|
|
1834
|
+
*(f" * {line}" for line in wrapped),
|
|
1835
|
+
]
|
|
1836
|
+
|
|
1837
|
+
|
|
1838
|
+
def _aen_mram_only_comment_lines(sku_preset: dict[str, Any], tail: str) -> list[str]:
|
|
1839
|
+
"""The MRAM partition-map comment's own "why MRAM-only" sentence,
|
|
1840
|
+
from the same `_aen_ospi_population_clause()` the banner reads
|
|
1841
|
+
(alp-sdk#2062 review round 2: this used to hardcode "the SoM OSPI NOR +
|
|
1842
|
+
HyperRAM are not populated on this batch" unconditionally, so a preset
|
|
1843
|
+
that DOES populate them got a board tree contradicting its own banner
|
|
1844
|
+
two paragraphs up). *tail* is the caller's own ending clause (what lives
|
|
1845
|
+
in MRAM as a result); MRAM-only itself holds regardless of population
|
|
1846
|
+
-- see `_AEN_OSPI_XIP_GAP`; the die-level XIP_SER absence alone does
|
|
1847
|
+
NOT imply it (round 3)."""
|
|
1848
|
+
clause, category, _populated_count = _aen_ospi_population_clause(sku_preset)
|
|
1849
|
+
if category == "populated":
|
|
1850
|
+
sentence = (f"MRAM-only regardless: {clause}; {_AEN_OSPI_XIP_GAP} "
|
|
1851
|
+
f"(see the banner above), so {tail}")
|
|
1852
|
+
elif category == "optional":
|
|
1853
|
+
sentence = (f"MRAM-only: {clause}; even if fitted, the same gap "
|
|
1854
|
+
f"applies ({_AEN_OSPI_XIP_GAP}, see the banner above), "
|
|
1855
|
+
f"so {tail}")
|
|
1856
|
+
else:
|
|
1857
|
+
sentence = f"MRAM-only: {clause}, so {tail}"
|
|
1858
|
+
return [f" * {line}" for line in textwrap.wrap(sentence, width=76, break_on_hyphens=False)]
|
|
1859
|
+
|
|
1860
|
+
|
|
1861
|
+
def _aen_dts(
|
|
1862
|
+
sku: str, sku_preset: dict[str, Any], core_id: str, soc_spec: dict[str, Any],
|
|
1863
|
+
variant: dict[str, Any],
|
|
1864
|
+
dir_name: str, basename: str, rx_row: dict[str, Any], tx_row: dict[str, Any],
|
|
1865
|
+
metadata_root: Path, links: dict[str, Any],
|
|
1866
|
+
ethos_u: tuple[str, str] | None = None,
|
|
1867
|
+
memory_map: "list[dict[str, Any]] | None" = None,
|
|
1868
|
+
power_domains: "dict[str, Any] | None" = None,
|
|
1869
|
+
wired_pads: "list[dict[str, Any]] | None" = None,
|
|
1870
|
+
) -> str:
|
|
1871
|
+
role = core_id.split("_")[-1] # "hp" / "he"
|
|
1872
|
+
role_u = role.upper()
|
|
1873
|
+
other_role = "he" if role == "hp" else "hp"
|
|
1874
|
+
other_role_u = other_role.upper()
|
|
1875
|
+
other_core_id = f"m55_{other_role}"
|
|
1876
|
+
|
|
1877
|
+
core = _find_core(soc_spec, core_id)
|
|
1878
|
+
other_core = _find_core(soc_spec, other_core_id)
|
|
1879
|
+
|
|
1880
|
+
itcm_base, dtcm_base = core["itcm_global_base"], core["dtcm_global_base"]
|
|
1881
|
+
other_itcm, other_dtcm = other_core["itcm_global_base"], other_core["dtcm_global_base"]
|
|
1882
|
+
|
|
1883
|
+
display_variant = variant["order_code"]
|
|
1884
|
+
slug = sku[len("E1M-"):].lower()
|
|
1885
|
+
compatible = f"alp,e1m-{slug}-m55-{role}"
|
|
1886
|
+
model = f"Alp {sku} Cortex-M55 {role_u} carrier"
|
|
1887
|
+
uart_node = _uart_node_label(rx_row)
|
|
1888
|
+
|
|
1889
|
+
part = _aen_part(soc_spec)
|
|
1890
|
+
family_display = _aen_family_display(soc_spec)
|
|
1891
|
+
peripherals_dtsi = _aen_peripherals_dtsi(soc_spec, metadata_root)
|
|
1892
|
+
|
|
1893
|
+
total_kib = round(float(variant["mram_mb"]) * 1024)
|
|
1894
|
+
mram_base, partitions = _aen_flash_partitions(
|
|
1895
|
+
total_kib, metadata_root, role, memory_map)
|
|
1896
|
+
image_kib = dict((label, size) for label, _off, size in partitions)["image-0"]
|
|
1897
|
+
disjoint_slot0 = _aen_role_slot0_map(memory_map, role) is not None
|
|
1898
|
+
|
|
1899
|
+
bench_known = _AEN_BENCH_KNOWN_ITCM[role]
|
|
1900
|
+
itcm_known_suffix = " (also bench-known)" if bench_known else ""
|
|
1901
|
+
|
|
1902
|
+
lines: list[str] = []
|
|
1903
|
+
lines += [
|
|
1904
|
+
"/*",
|
|
1905
|
+
" * Copyright (c) 2026 Alp Lab AB",
|
|
1906
|
+
" * SPDX-License-Identifier: Apache-2.0",
|
|
1907
|
+
" *",
|
|
1908
|
+
f" * {sku} ({family_display}, {display_variant}) Cortex-M55-{role_u} carrier,",
|
|
1909
|
+
" * for the Alp E1M-EVK.",
|
|
1910
|
+
" *",
|
|
1911
|
+
f" * Reuses the upstream Alif {part} SoC + RTSS-{role_u} cluster devicetree and:",
|
|
1912
|
+
" * - retargets the console from the DevKit's UART2 to the E1M carrier console",
|
|
1913
|
+
f" * (Alif {uart_node.upper()}, {rx_row['silicon_pad']}/{tx_row['silicon_pad']} -- the E1M edge \"UART0\");",
|
|
1914
|
+
*_aen_ospi_storage_banner_lines(sku_preset),
|
|
1915
|
+
" * - lays down a production MCUboot partition map in MRAM.",
|
|
1916
|
+
]
|
|
1917
|
+
if disjoint_slot0:
|
|
1918
|
+
lines += [
|
|
1919
|
+
" *",
|
|
1920
|
+
f" * NOTE (AMP, #1069): {role_u}'s slot0 lives in its OWN disjoint MRAM window,",
|
|
1921
|
+
f" * physically separate from {other_role_u}'s -- both cores' images can be",
|
|
1922
|
+
" * resident in MRAM at once (see the partition map below and",
|
|
1923
|
+
f" * metadata/e1m_modules/{sku}.yaml `memory_map:`). OTA is DEFERRED",
|
|
1924
|
+
" * (CONFIG_SINGLE_APPLICATION_SLOT=y, no secondary/scratch slot): the",
|
|
1925
|
+
f" * per-core code slots ({_aen_slot0_sizes_display(memory_map)}) share the",
|
|
1926
|
+
f" * {total_kib} KiB App MRAM with boot, storage and the NPU MRAM-model",
|
|
1927
|
+
" * budget -- see zephyr/sysbuild/aen/README.md for the sizing rationale.",
|
|
1928
|
+
]
|
|
1929
|
+
elif role == "hp":
|
|
1930
|
+
lines += [
|
|
1931
|
+
" *",
|
|
1932
|
+
" * NOTE (AMP): this per-core board takes the full MRAM view for single-image",
|
|
1933
|
+
f" * bring-up (one core flashed/run at a time). An {other_role_u}+{role_u} co-resident AMP image",
|
|
1934
|
+
" * partitions MRAM between the two cores at the system-integration layer",
|
|
1935
|
+
" * (sysbuild); see docs/heterogeneous-builds.md.",
|
|
1936
|
+
]
|
|
1937
|
+
lines += [
|
|
1938
|
+
" *",
|
|
1939
|
+
f" * Inter-core IPC (the APSS<->RTSS-{role_u} MHUv2 doorbell pair) + the remaining",
|
|
1940
|
+
" * on-module peripheral buses (GPIO / SPI / Ethernet) are added alongside the",
|
|
1941
|
+
" * alp-sdk Alif peripheral drivers; this base wires boot, console and the",
|
|
1942
|
+
" * on-module BRD_I2C housekeeping bus (RTC + temperature sensor, below).",
|
|
1943
|
+
" */",
|
|
1944
|
+
"",
|
|
1945
|
+
"/dts-v1/;",
|
|
1946
|
+
"",
|
|
1947
|
+
f"#include <alif/ensemble/{display_variant.lower()}.dtsi>",
|
|
1948
|
+
f"#include <alif/ensemble/common/ensemble_rtss_{role}.dtsi>",
|
|
1949
|
+
f"#include <{peripherals_dtsi}>",
|
|
1950
|
+
"#include <zephyr/dt-bindings/gpio/gpio.h>",
|
|
1951
|
+
"#include <zephyr/dt-bindings/i2c/i2c.h>",
|
|
1952
|
+
f'#include "{dir_name}-pinctrl.dtsi"',
|
|
1953
|
+
"",
|
|
1954
|
+
"/ {",
|
|
1955
|
+
f'\tmodel = "{model}";',
|
|
1956
|
+
f'\tcompatible = "{compatible}";',
|
|
1957
|
+
"",
|
|
1958
|
+
"\tchosen {",
|
|
1959
|
+
"\t\tzephyr,flash = &mram_storage;",
|
|
1960
|
+
"\t\t/*",
|
|
1961
|
+
"\t\t * MCUboot's flash_map_extended.c derives FLASH_DEVICE_BASE /",
|
|
1962
|
+
"\t\t * FLASH_DEVICE_ID from zephyr,flash-controller (the controller",
|
|
1963
|
+
"\t\t * node, NOT the soc-nv-flash child) -- mirrors the fork's",
|
|
1964
|
+
"\t\t * e1.dtsi chosen. Without it MCUboot's flash_map fails to link.",
|
|
1965
|
+
"\t\t */",
|
|
1966
|
+
"\t\tzephyr,flash-controller = &mram;",
|
|
1967
|
+
"\t\tzephyr,sram = &dtcm;",
|
|
1968
|
+
f"\t\tzephyr,console = &{uart_node};",
|
|
1969
|
+
f"\t\tzephyr,shell-uart = &{uart_node};",
|
|
1970
|
+
"\t\tzephyr,code-partition = &slot0_partition;",
|
|
1971
|
+
"\t};",
|
|
1972
|
+
"};",
|
|
1973
|
+
"",
|
|
1974
|
+
"/*",
|
|
1975
|
+
" * ITCM/DTCM global_base -- central CPU-local -> system-bus address translation.",
|
|
1976
|
+
" *",
|
|
1977
|
+
" * hal_alif's soc_memory_map.h local_to_global() translates a CPU-local TCM",
|
|
1978
|
+
" * pointer into the address other bus masters (the Ethos-U NPU AXI master, the",
|
|
1979
|
+
" * SD/DMA controllers) must be programmed with, by reading",
|
|
1980
|
+
" * DT_PROP(DT_NODELABEL(itcm|dtcm), global_base). Upstream Zephyr's",
|
|
1981
|
+
f" * arm,itcm/arm,dtcm bindings (ensemble_rtss_{role}.dtsi, included above) declare no",
|
|
1982
|
+
" * such property, so re-`compatible` the nodes onto the alp-sdk",
|
|
1983
|
+
" * alif,itcm/alif,dtcm bindings (zephyr/dts/bindings/memory-controllers/), which",
|
|
1984
|
+
" * require global_base. Done here in the per-core board (not a per-example",
|
|
1985
|
+
f" * overlay) so every {role_u} app on this board inherits the same translation.",
|
|
1986
|
+
" *",
|
|
1987
|
+
" * Values transcribed from the Apache-2.0 zephyr_alif fork",
|
|
1988
|
+
f" * dts/arm/alif/ensemble/common/ensemble_rtss_{role}.dtsi:",
|
|
1989
|
+
f" * itcm global_base = {hex(itcm_base)}{itcm_known_suffix}",
|
|
1990
|
+
f" * dtcm global_base = {hex(dtcm_base)}",
|
|
1991
|
+
f" * These are the RTSS-{role_u} global TCM aliases; the RTSS-{other_role_u} core uses a different",
|
|
1992
|
+
f" * window ({hex(other_itcm)} / {hex(other_dtcm)}), set in the _{other_role} board dts.",
|
|
1993
|
+
" */",
|
|
1994
|
+
"&itcm {",
|
|
1995
|
+
f'\t/* fork ensemble_rtss_{role}.dtsi: itcm compatible = "alif,itcm" */',
|
|
1996
|
+
'\tcompatible = "alif,itcm";',
|
|
1997
|
+
f"\t/* fork ensemble_rtss_{role}.dtsi: itcm global_base = <{hex(itcm_base)}> */",
|
|
1998
|
+
f"\tglobal_base = <{hex(itcm_base)}>;",
|
|
1999
|
+
"};",
|
|
2000
|
+
"",
|
|
2001
|
+
"&dtcm {",
|
|
2002
|
+
f'\t/* fork ensemble_rtss_{role}.dtsi: dtcm compatible = "alif,dtcm" */',
|
|
2003
|
+
'\tcompatible = "alif,dtcm";',
|
|
2004
|
+
f"\t/* fork ensemble_rtss_{role}.dtsi: dtcm global_base = <{hex(dtcm_base)}> */",
|
|
2005
|
+
f"\tglobal_base = <{hex(dtcm_base)}>;",
|
|
2006
|
+
"};",
|
|
2007
|
+
"",
|
|
2008
|
+
"/*",
|
|
2009
|
+
" * On-die MRAM as a real flash controller (ADR 0017 Tier-2).",
|
|
2010
|
+
" *",
|
|
2011
|
+
f" * Upstream Zephyr's {part} SoC dtsi exposes the MRAM only as a memory region",
|
|
2012
|
+
f' * (`mram: flash@{mram_base:x}`, compatible "zephyr,memory-region","soc-nv-flash"):',
|
|
2013
|
+
" * XIP-readable but with NO flash driver, so FLASH_HAS_DRIVER_ENABLED stays",
|
|
2014
|
+
" * unset and MCUboot's flash_map has nothing to read/write slot0 with.",
|
|
2015
|
+
" *",
|
|
2016
|
+
" * We retarget the upstream `&mram` node into the fork's flash-controller",
|
|
2017
|
+
" * hierarchy WITHOUT editing upstream zephyr/: override `&mram`'s compatible to",
|
|
2018
|
+
' * the controller compatible "alif,mram-flash-controller" (which the in-tree',
|
|
2019
|
+
" * flash_mram_alif driver binds, setting FLASH_HAS_DRIVER_ENABLED), and add a",
|
|
2020
|
+
" * `mram_storage` soc-nv-flash child carrying the program/erase geometry the",
|
|
2021
|
+
" * driver reads (DT_NODELABEL(mram_storage): erase-block-size / write-block-size",
|
|
2022
|
+
" * / reg). This mirrors the fork e1.dtsi node structure exactly (controller",
|
|
2023
|
+
f" * `mram_flash@{mram_base:x}` + `mram_storage@{mram_base:x}` soc-nv-flash child); the only",
|
|
2024
|
+
" * difference is we reuse the upstream `mram` nodelabel as the controller rather",
|
|
2025
|
+
" * than declaring a fresh `mram_flash` node, since upstream owns the node.",
|
|
2026
|
+
]
|
|
2027
|
+
partitions_total_kib = sum(size for _label, _off, size in partitions)
|
|
2028
|
+
if disjoint_slot0:
|
|
2029
|
+
lines += [
|
|
2030
|
+
" *",
|
|
2031
|
+
f" * MRAM partition map (this {role_u} core's {partitions_total_kib} KiB view of the",
|
|
2032
|
+
f" * {total_kib} KiB App MRAM) for the sysbuild/aen MCUboot profile (MCUboot + a",
|
|
2033
|
+
" * signed application image, CONFIG_SINGLE_APPLICATION_SLOT=y -- no OTA/swap",
|
|
2034
|
+
" * slot, deferred per #1069). Offsets are relative to the soc-nv-flash child",
|
|
2035
|
+
f" * base (MRAM 0x{mram_base:x}). {other_role_u}'s disjoint slot0 window is NOT a",
|
|
2036
|
+
" * partition in this table (it's outside this core's own view); see",
|
|
2037
|
+
f" * metadata/e1m_modules/{sku}.yaml `memory_map:` for the full physical map:",
|
|
2038
|
+
" *",
|
|
2039
|
+
]
|
|
2040
|
+
else:
|
|
2041
|
+
if role == "hp":
|
|
2042
|
+
lines += [
|
|
2043
|
+
f" * Kept byte-for-byte identical to the {other_role_u}-sibling board so a future HE+HP AMP",
|
|
2044
|
+
" * sysbuild sees one MRAM partition map.",
|
|
2045
|
+
]
|
|
2046
|
+
lines += [
|
|
2047
|
+
" *",
|
|
2048
|
+
f" * MRAM partition map ({total_kib} KiB total) for the sysbuild/aen MCUboot profile",
|
|
2049
|
+
" * (MCUboot + a signed application image, swap-using-scratch). Offsets are",
|
|
2050
|
+
f" * relative to the soc-nv-flash child base (MRAM 0x{mram_base:x}):",
|
|
2051
|
+
" *",
|
|
2052
|
+
]
|
|
2053
|
+
labels_display = {
|
|
2054
|
+
"mcuboot": "mcuboot ",
|
|
2055
|
+
"image-0": "image-0 ",
|
|
2056
|
+
"image-1": "image-1 ",
|
|
2057
|
+
"image-scratch": "scratch ",
|
|
2058
|
+
"reserved": "reserved",
|
|
2059
|
+
"storage": "storage ",
|
|
2060
|
+
"atoc": "atoc ",
|
|
2061
|
+
}
|
|
2062
|
+
trailers = {
|
|
2063
|
+
"image-0": " (primary slot, code-partition)",
|
|
2064
|
+
"image-1": " (secondary slot for OTA)",
|
|
2065
|
+
"reserved": " (ex-scratch; unused, OTA deferred)",
|
|
2066
|
+
"storage": " (settings / NVS)",
|
|
2067
|
+
"atoc": " (SE-owned: SETOOLS top-anchors the ATOC here -- do NOT write, #1289)",
|
|
2068
|
+
}
|
|
2069
|
+
for label, off, size in partitions:
|
|
2070
|
+
lines.append(
|
|
2071
|
+
f" * {labels_display[label]} 0x{off:06x} {size} KiB{trailers.get(label, '')}"
|
|
2072
|
+
)
|
|
2073
|
+
if disjoint_slot0:
|
|
2074
|
+
lines += [
|
|
2075
|
+
f" * = {partitions_total_kib} KiB (of {total_kib} KiB App MRAM total)",
|
|
2076
|
+
" *",
|
|
2077
|
+
*_aen_mram_only_comment_lines(
|
|
2078
|
+
sku_preset,
|
|
2079
|
+
"boot, this core's own slot0, reserved headroom, and "
|
|
2080
|
+
"storage all live in MRAM."),
|
|
2081
|
+
" */",
|
|
2082
|
+
]
|
|
2083
|
+
else:
|
|
2084
|
+
lines += [
|
|
2085
|
+
f" * = {total_kib} KiB",
|
|
2086
|
+
" *",
|
|
2087
|
+
*_aen_mram_only_comment_lines(
|
|
2088
|
+
sku_preset,
|
|
2089
|
+
"all of boot, both image slots, scratch, and storage live "
|
|
2090
|
+
"in MRAM."),
|
|
2091
|
+
" */",
|
|
2092
|
+
]
|
|
2093
|
+
lines += [
|
|
2094
|
+
"&mram {",
|
|
2095
|
+
'\tcompatible = "alif,mram-flash-controller";',
|
|
2096
|
+
"\t#address-cells = <1>;",
|
|
2097
|
+
"\t#size-cells = <1>;",
|
|
2098
|
+
"\t/*",
|
|
2099
|
+
"\t * Drop the upstream memory-region properties: this node is no longer a",
|
|
2100
|
+
'\t * "zephyr,memory-region" (it is a flash controller now), and the',
|
|
2101
|
+
"\t * controller addresses its soc-nv-flash child by absolute `reg`, not via",
|
|
2102
|
+
"\t * `ranges`. flash-controller.yaml / base.yaml declare neither property,",
|
|
2103
|
+
"\t * so they must be removed or DT binding-check fails.",
|
|
2104
|
+
"\t */",
|
|
2105
|
+
"\t/delete-property/ zephyr,memory-region;",
|
|
2106
|
+
"\t/delete-property/ ranges;",
|
|
2107
|
+
"",
|
|
2108
|
+
# Node address + `reg` base come from the SAME `mram_base` the
|
|
2109
|
+
# partition offsets above are relative to -- they used to be a
|
|
2110
|
+
# hardcoded 0x80000000 literal while the offsets were computed from
|
|
2111
|
+
# the metadata-declared `mcuboot` base, an invariant documented only
|
|
2112
|
+
# in an error string and never enforced.
|
|
2113
|
+
f"\tmram_storage: mram_storage@{mram_base:x} {{",
|
|
2114
|
+
'\t\tcompatible = "soc-nv-flash";',
|
|
2115
|
+
f"\t\treg = <{hex(mram_base)} DT_SIZE_K({total_kib})>;",
|
|
2116
|
+
"\t\terase-block-size = <1024>;",
|
|
2117
|
+
"\t\twrite-block-size = <16>;",
|
|
2118
|
+
"",
|
|
2119
|
+
"\t\tpartitions {",
|
|
2120
|
+
'\t\t\tcompatible = "fixed-partitions";',
|
|
2121
|
+
"\t\t\t#address-cells = <1>;",
|
|
2122
|
+
"\t\t\t#size-cells = <1>;",
|
|
2123
|
+
"",
|
|
2124
|
+
]
|
|
2125
|
+
|
|
2126
|
+
partition_node_labels = {
|
|
2127
|
+
"mcuboot": "boot_partition",
|
|
2128
|
+
"image-0": "slot0_partition",
|
|
2129
|
+
"image-1": "slot1_partition",
|
|
2130
|
+
"image-scratch": "scratch_partition",
|
|
2131
|
+
"reserved": "reserved_partition",
|
|
2132
|
+
"storage": "storage_partition",
|
|
2133
|
+
"atoc": "atoc_partition",
|
|
2134
|
+
}
|
|
2135
|
+
partition_dt_labels = {
|
|
2136
|
+
"mcuboot": "mcuboot",
|
|
2137
|
+
"image-0": "image-0",
|
|
2138
|
+
"image-1": "image-1",
|
|
2139
|
+
"image-scratch": "image-scratch",
|
|
2140
|
+
"reserved": "reserved",
|
|
2141
|
+
"storage": "storage",
|
|
2142
|
+
"atoc": "atoc",
|
|
2143
|
+
}
|
|
2144
|
+
for i, (label, off, size) in enumerate(partitions):
|
|
2145
|
+
node = partition_node_labels[label]
|
|
2146
|
+
dt_label = partition_dt_labels[label]
|
|
2147
|
+
lines += [
|
|
2148
|
+
f"\t\t\t{node}: partition@{off:x} {{",
|
|
2149
|
+
f'\t\t\t\tlabel = "{dt_label}";',
|
|
2150
|
+
f"\t\t\t\treg = <{hex(off)} DT_SIZE_K({size})>;",
|
|
2151
|
+
"\t\t\t};",
|
|
2152
|
+
]
|
|
2153
|
+
if i != len(partitions) - 1:
|
|
2154
|
+
lines.append("")
|
|
2155
|
+
|
|
2156
|
+
lines += [
|
|
2157
|
+
"\t\t};",
|
|
2158
|
+
"\t};",
|
|
2159
|
+
"};",
|
|
2160
|
+
"",
|
|
2161
|
+
f"/* Carrier console: Alif {uart_node.upper()} on {rx_row['silicon_pad']} (RX) / "
|
|
2162
|
+
f"{tx_row['silicon_pad']} (TX), 115200 8N1. */",
|
|
2163
|
+
f"&{uart_node} {{",
|
|
2164
|
+
'\tstatus = "okay";',
|
|
2165
|
+
f"\tpinctrl-0 = <&pinctrl_{uart_node}>;",
|
|
2166
|
+
'\tpinctrl-names = "default";',
|
|
2167
|
+
"\tcurrent-speed = <115200>;",
|
|
2168
|
+
"};",
|
|
2169
|
+
"",
|
|
2170
|
+
]
|
|
2171
|
+
lines += _aen_brd_i2c_dts(links, part)
|
|
2172
|
+
lines += _aen_e1m_i2c0_dts(links)
|
|
2173
|
+
if power_domains:
|
|
2174
|
+
lines += _aen_som_power_dts(sku_preset, power_domains, links, wired_pads or [])
|
|
2175
|
+
|
|
2176
|
+
if ethos_u is not None:
|
|
2177
|
+
_accel, node = ethos_u
|
|
2178
|
+
# Enable the Arm Ethos-U NPU node this SoC carries. The full node
|
|
2179
|
+
# (reg/interrupts/compatible/secure-enable) is already declared
|
|
2180
|
+
# status="disabled" in this SoC's peripherals dtsi (the
|
|
2181
|
+
# `zephyr_peripherals_dtsi` included above -- e.g.
|
|
2182
|
+
# zephyr/dts/alif/ensemble_e8_peripherals.dtsi for the E8); a silicon
|
|
2183
|
+
# board just flips it on. Harmless -- CONFIG_ETHOS_U is `default n`,
|
|
2184
|
+
# so no driver binds until an inference app pulls it in via
|
|
2185
|
+
# ALP_SDK_INFERENCE_BACKEND_ETHOS_U_AEN (select ETHOS_U if
|
|
2186
|
+
# DT_HAS_ARM_ETHOS_U_ENABLED). A native_sim build uses the native_sim
|
|
2187
|
+
# board (no such node) and so stays on the tflite-micro stub kernel.
|
|
2188
|
+
lines += [
|
|
2189
|
+
f"/* Arm Ethos-U NPU: enable the {node} node declared in the SoC "
|
|
2190
|
+
"peripherals dtsi (see ALP_SDK_INFERENCE_BACKEND_ETHOS_U_AEN). */",
|
|
2191
|
+
f"&{node} {{",
|
|
2192
|
+
'\tstatus = "okay";',
|
|
2193
|
+
"};",
|
|
2194
|
+
"",
|
|
2195
|
+
]
|
|
2196
|
+
return "\n".join(lines)
|
|
2197
|
+
|
|
2198
|
+
|
|
2199
|
+
def _load_aen_wired_lpgpio_pads(metadata_root: Path) -> list[dict[str, Any]]:
|
|
2200
|
+
"""`wired_lpgpio_pads:` of on-module-links.yaml: LPGPIO pads the module wires
|
|
2201
|
+
to an on-module chip without a power-domain control (the OSPI INTn / RESETn
|
|
2202
|
+
nets). `check_som_power_domains.py` pins each to its TSV row."""
|
|
2203
|
+
doc = _load_yaml(metadata_root / "e1m_modules" / "aen" / "on-module-links.yaml")
|
|
2204
|
+
pads = doc.get("wired_lpgpio_pads") or []
|
|
2205
|
+
for p in pads:
|
|
2206
|
+
if not re.fullmatch(r"P15_[0-7]", str(p.get("silicon_pad", ""))):
|
|
2207
|
+
raise ZephyrBoardEmitError(
|
|
2208
|
+
f"wired_lpgpio_pads entry {p.get('signal')!r}: silicon_pad "
|
|
2209
|
+
f"{p.get('silicon_pad')!r} is not an LPGPIO pad P15_0..P15_7")
|
|
2210
|
+
return pads
|
|
2211
|
+
|
|
2212
|
+
|
|
2213
|
+
def _load_aen_power_domains(metadata_root: Path) -> dict[str, Any]:
|
|
2214
|
+
"""Load + shape-check `power_domains:` of
|
|
2215
|
+
`metadata/e1m_modules/aen/on-module-links.yaml` (v2, alp-sdk#2784).
|
|
2216
|
+
|
|
2217
|
+
The JSON schema (`on-module-links-v2.schema.json`) pins the shape in
|
|
2218
|
+
`validate_metadata.py`; this refuses only what the emitter itself cannot
|
|
2219
|
+
work without, so a hand-made `--metadata-root` copy fails loudly instead
|
|
2220
|
+
of emitting a board with no SoM power node.
|
|
2221
|
+
"""
|
|
2222
|
+
path = metadata_root / "e1m_modules" / "aen" / "on-module-links.yaml"
|
|
2223
|
+
doc = _load_yaml(path)
|
|
2224
|
+
if doc.get("schemaVersion") != "on-module-links-v2":
|
|
2225
|
+
# tan-side (tan-cli#591 pattern): a bound checkout that predates
|
|
2226
|
+
# alp-sdk#2784 is an SDK floor, not this file's authoring gap.
|
|
2227
|
+
require_capability(metadata_root, AEN_SOM_POWER_DOMAINS)
|
|
2228
|
+
raise ZephyrBoardEmitError(
|
|
2229
|
+
f"{path} schemaVersion is {doc.get('schemaVersion')!r}, expected "
|
|
2230
|
+
"'on-module-links-v2' (the `power_domains:` block, alp-sdk#2784)")
|
|
2231
|
+
domains = doc.get("power_domains")
|
|
2232
|
+
if not isinstance(domains, dict) or not domains:
|
|
2233
|
+
raise ZephyrBoardEmitError(f"{path} has no power_domains: block")
|
|
2234
|
+
return domains
|
|
2235
|
+
|
|
2236
|
+
|
|
2237
|
+
def _aen_power_domain_presence(
|
|
2238
|
+
domain: dict[str, Any], on_module: dict[str, Any],
|
|
2239
|
+
) -> "tuple[str, str | None] | None":
|
|
2240
|
+
"""`(state, chip)` for a power domain on THIS SoM preset, or `None` when
|
|
2241
|
+
the domain is absent. `state` is `"populated"` or `"optional"`.
|
|
2242
|
+
|
|
2243
|
+
Presence is never assumed from the family: it is read from the SKU's own
|
|
2244
|
+
`on_module` block (`presence.on_module_key`, a dotted path). A string
|
|
2245
|
+
value (`wifi_ble: cc3501e`) is present unless it is a TBD placeholder; a
|
|
2246
|
+
block with the tri-state `assembled:` follows that field via
|
|
2247
|
+
`_aen_ospi_device_state()` (false -> absent, "optional" -> BOM variant).
|
|
2248
|
+
`presence.family_invariant` is for the one domain no SKU preset describes.
|
|
2249
|
+
"""
|
|
2250
|
+
presence = domain["presence"]
|
|
2251
|
+
if presence.get("family_invariant"):
|
|
2252
|
+
return "populated", domain.get("chip")
|
|
2253
|
+
node: Any = on_module
|
|
2254
|
+
for part in presence["on_module_key"].split("."):
|
|
2255
|
+
node = node.get(part) if isinstance(node, dict) else None
|
|
2256
|
+
if node is None:
|
|
2257
|
+
return None
|
|
2258
|
+
if isinstance(node, dict):
|
|
2259
|
+
state, chip = _aen_ospi_device_state(node)
|
|
2260
|
+
if state == "not_populated":
|
|
2261
|
+
return None
|
|
2262
|
+
return state, chip or domain.get("chip")
|
|
2263
|
+
if not node or _is_tbd(node):
|
|
2264
|
+
return None
|
|
2265
|
+
return "populated", str(node)
|
|
2266
|
+
|
|
2267
|
+
|
|
2268
|
+
#: `alp,stop-hold` strength order: the weakest control decides.
|
|
2269
|
+
_AEN_STOP_HOLD_RANK = {"no": 0, "unproven": 1, "yes": 2}
|
|
2270
|
+
|
|
2271
|
+
#: Control role -> the DT gpio property name `alp,som-power-domain` uses.
|
|
2272
|
+
_AEN_POWER_ROLE_PROP = {
|
|
2273
|
+
"reset": "reset-gpios",
|
|
2274
|
+
"rail_enable": "enable-gpios",
|
|
2275
|
+
"powerdown": "powerdown-gpios",
|
|
2276
|
+
"enable": "enable-gpios",
|
|
2277
|
+
"wake_in": "wake-gpios",
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
|
|
2281
|
+
def _aen_som_power_dts(
|
|
2282
|
+
sku_preset: dict[str, Any], domains: dict[str, Any],
|
|
2283
|
+
links: dict[str, Any], wired_pads: list[dict[str, Any]],
|
|
2284
|
+
) -> list[str]:
|
|
2285
|
+
"""The `alp,som-power` node: one `alp,som-power-domain` child per domain
|
|
2286
|
+
this SKU actually carries (alp-sdk#2784, metadata-only -- nothing in the
|
|
2287
|
+
SDK drives these pads yet).
|
|
2288
|
+
|
|
2289
|
+
Pad, polarity and action facts come from `power_domains:` in
|
|
2290
|
+
on-module-links.yaml; whether the domain exists on this SKU comes from the
|
|
2291
|
+
SKU's own `on_module:` block (`_aen_power_domain_presence()`).
|
|
2292
|
+
"""
|
|
2293
|
+
on_module = sku_preset.get("on_module") or {}
|
|
2294
|
+
device_labels = {
|
|
2295
|
+
d["node_label"]
|
|
2296
|
+
for d in (links["brd_i2c"].get("devices") or []) if d.get("node_label")}
|
|
2297
|
+
children: list[str] = []
|
|
2298
|
+
for name, dom in domains.items():
|
|
2299
|
+
found = _aen_power_domain_presence(dom, on_module)
|
|
2300
|
+
if found is None:
|
|
2301
|
+
continue
|
|
2302
|
+
state, chip = found
|
|
2303
|
+
label = dom.get("device_label")
|
|
2304
|
+
if label and label not in device_labels:
|
|
2305
|
+
raise ZephyrBoardEmitError(
|
|
2306
|
+
f"power_domains.{name}.device_label {label!r} is not a "
|
|
2307
|
+
"node_label under on_module_links.brd_i2c.devices")
|
|
2308
|
+
action = dom["default_action"]
|
|
2309
|
+
actions = dom["actions"]
|
|
2310
|
+
if action not in actions:
|
|
2311
|
+
raise ZephyrBoardEmitError(
|
|
2312
|
+
f"power_domains.{name}.default_action {action!r} is not in "
|
|
2313
|
+
"its actions:")
|
|
2314
|
+
by_signal = {c["signal"]: c for c in dom["controls"]}
|
|
2315
|
+
# The hold level that matters is the default action's own pad; a
|
|
2316
|
+
# command-only action falls back to the domain's pads (weakest wins).
|
|
2317
|
+
ctl = by_signal.get(actions[action].get("control", ""))
|
|
2318
|
+
held = ([ctl] if ctl else
|
|
2319
|
+
[c for c in dom["controls"] if c["direction"] == "out"])
|
|
2320
|
+
stop_hold = (min((c["holds_through_stop"] for c in held),
|
|
2321
|
+
key=_AEN_STOP_HOLD_RANK.__getitem__)
|
|
2322
|
+
if held else "none")
|
|
2323
|
+
node = name.replace("_", "-")
|
|
2324
|
+
child = [""]
|
|
2325
|
+
child += _c_comment(dom["evidence"], "\t\t")
|
|
2326
|
+
child += [f"\t\tsom_pd_{name}: {node} {{",
|
|
2327
|
+
'\t\t\tcompatible = "alp,som-power-domain";',
|
|
2328
|
+
f'\t\t\talp,role = "{name}";']
|
|
2329
|
+
seen_props: set[str] = set()
|
|
2330
|
+
for c in dom["controls"]:
|
|
2331
|
+
prop = _AEN_POWER_ROLE_PROP[c["role"]]
|
|
2332
|
+
if prop in seen_props:
|
|
2333
|
+
raise ZephyrBoardEmitError(
|
|
2334
|
+
f"power_domains.{name}: two controls map to {prop}")
|
|
2335
|
+
seen_props.add(prop)
|
|
2336
|
+
flag = ("GPIO_ACTIVE_LOW" if c["polarity"] == "active_low"
|
|
2337
|
+
else "GPIO_ACTIVE_HIGH")
|
|
2338
|
+
child.append(
|
|
2339
|
+
f"\t\t\t{prop} = <&{c['gpio_node']} {c['gpio_pin']} {flag}>;"
|
|
2340
|
+
f" /* {c['signal']} {c['silicon_pad']} */")
|
|
2341
|
+
if dom.get("secondary_rtc"):
|
|
2342
|
+
child.append("\t\t\talp,primary-rtc;")
|
|
2343
|
+
if label:
|
|
2344
|
+
child.append(f"\t\t\talp,device = <&{label}>;")
|
|
2345
|
+
if chip:
|
|
2346
|
+
child.append(f'\t\t\talp,chip = "{chip}";')
|
|
2347
|
+
child += [
|
|
2348
|
+
f'\t\t\talp,default-action = "{action}";',
|
|
2349
|
+
"\t\t\talp,default-modes = "
|
|
2350
|
+
+ ", ".join(f'"{m}"' for m in dom["default_modes"]) + ";",
|
|
2351
|
+
f'\t\t\talp,stop-hold = "{stop_hold}";',
|
|
2352
|
+
f'\t\t\talp,restore = "{dom["restore"]["method"]}";']
|
|
2353
|
+
if actions.get("rail_off", {}).get("opt_in"):
|
|
2354
|
+
child.append("\t\t\talp,rail-off-opt-in;")
|
|
2355
|
+
if state == "optional":
|
|
2356
|
+
child.append("\t\t\talp,bom-optional;")
|
|
2357
|
+
deps = sorted({d["kind"] for d in dom.get("dependents") or []})
|
|
2358
|
+
if deps:
|
|
2359
|
+
child.append("\t\t\talp,dependents = "
|
|
2360
|
+
+ ", ".join(f'"{d}"' for d in deps) + ";")
|
|
2361
|
+
child.append("\t\t};")
|
|
2362
|
+
children += child
|
|
2363
|
+
|
|
2364
|
+
# Every output control pad the layer drives, muxed and pad-configured by the
|
|
2365
|
+
# `alp,som-power` node's own pinctrl-0 state. Pad config is input-enable +
|
|
2366
|
+
# schmitt (0x23 with the default 4 mA drive on the LP pads: the value the
|
|
2367
|
+
# bench-proven CC3501E bring-up writes by hand) so the output driver is on
|
|
2368
|
+
# and the pad can be read back. Port 15 is the LPGPIO island. The group sets
|
|
2369
|
+
# mux / pad configuration only, never direction or level (those are GPIO-port
|
|
2370
|
+
# state), so applying it to a pad whose domain is KEEP_ALIVE changes nothing.
|
|
2371
|
+
pads: list[tuple[int, int]] = []
|
|
2372
|
+
for name, dom in domains.items():
|
|
2373
|
+
if _aen_power_domain_presence(dom, on_module) is None:
|
|
2374
|
+
continue
|
|
2375
|
+
for c in dom["controls"]:
|
|
2376
|
+
m = re.fullmatch(r"P(\d+)_(\d)", c["silicon_pad"])
|
|
2377
|
+
if c["direction"] != "out" or m is None:
|
|
2378
|
+
continue
|
|
2379
|
+
pad = (int(m.group(1)), int(m.group(2)))
|
|
2380
|
+
if pad not in pads:
|
|
2381
|
+
pads.append(pad)
|
|
2382
|
+
pads.sort()
|
|
2383
|
+
pinmux = ", ".join(
|
|
2384
|
+
f"<PIN_P{port}_{pin}__{'LPGPIO' if port == 15 else 'GPIO'}>"
|
|
2385
|
+
for port, pin in pads)
|
|
2386
|
+
|
|
2387
|
+
# LPGPIO pads the module wires without a domain control, so a wake-pad
|
|
2388
|
+
# claim is per board (src/backends/power/som_power.c reads this property).
|
|
2389
|
+
wired_line = []
|
|
2390
|
+
if wired_pads:
|
|
2391
|
+
cells = " ".join(str(int(p["silicon_pad"][4:])) for p in wired_pads)
|
|
2392
|
+
wired_line = [
|
|
2393
|
+
"\t\t/* SoM-wired LPGPIO pads without a domain node (alif-ospi.tsv):",
|
|
2394
|
+
"\t\t * " + ", ".join(f"{p['signal']} {p['silicon_pad']}" for p in wired_pads) + ".",
|
|
2395
|
+
"\t\t * Never an application wake pad. */",
|
|
2396
|
+
f"\t\talp,wired-lpgpio-pads = <{cells}>;"]
|
|
2397
|
+
|
|
2398
|
+
lines = [
|
|
2399
|
+
"/*",
|
|
2400
|
+
" * SoM power domains (alp-sdk#2784): which on-module chips this SKU carries,",
|
|
2401
|
+
" * the SoC pads that gate them, and the default quiesce action per domain.",
|
|
2402
|
+
" * Presence follows this SKU's `on_module` block; pads, polarity and actions",
|
|
2403
|
+
" * come from `power_domains:` in metadata/e1m_modules/aen/on-module-links.yaml.",
|
|
2404
|
+
" * The SoM power layer (src/backends/power/som_power.c) reads this node and",
|
|
2405
|
+
" * applies its pinctrl-0 state before it first drives a pad. `alp,stop-hold` is",
|
|
2406
|
+
" * \"unproven\" for every LPGPIO (P15_n) pad until the STOP bench shows the output",
|
|
2407
|
+
" * holds.",
|
|
2408
|
+
" */",
|
|
2409
|
+
"&pinctrl {",
|
|
2410
|
+
"\tpinctrl_som_power: pinctrl_som_power {",
|
|
2411
|
+
"\t\tgroup0 {",
|
|
2412
|
+
f"\t\t\tpinmux = {pinmux};",
|
|
2413
|
+
"\t\t\tinput-enable;",
|
|
2414
|
+
"\t\t\tinput-schmitt-enable;",
|
|
2415
|
+
"\t\t};",
|
|
2416
|
+
"\t};",
|
|
2417
|
+
"};",
|
|
2418
|
+
"",
|
|
2419
|
+
"/ {",
|
|
2420
|
+
"\tsom_power: som-power {",
|
|
2421
|
+
'\t\tcompatible = "alp,som-power";',
|
|
2422
|
+
"\t\tpinctrl-0 = <&pinctrl_som_power>;",
|
|
2423
|
+
'\t\tpinctrl-names = "default";',
|
|
2424
|
+
*wired_line,
|
|
2425
|
+
*children,
|
|
2426
|
+
"\t};",
|
|
2427
|
+
"};",
|
|
2428
|
+
"",
|
|
2429
|
+
]
|
|
2430
|
+
return lines
|
|
2431
|
+
|
|
2432
|
+
|
|
2433
|
+
# ---------------------------------------------------------------------
|
|
2434
|
+
# V2N-family files: pinctrl.dtsi, _defconfig (#655)
|
|
2435
|
+
# ---------------------------------------------------------------------
|
|
2436
|
+
|
|
2437
|
+
|
|
2438
|
+
def _load_supervisor_links_doc(metadata_root: Path) -> dict[str, Any]:
|
|
2439
|
+
"""Load `metadata/e1m_modules/v2n/supervisor-links.yaml` as a whole doc.
|
|
2440
|
+
|
|
2441
|
+
Family-scoped (see the module docstring): this one file backs both the
|
|
2442
|
+
`v2n` and `v2n-m1` families' pinctrl.dtsi / _defconfig, so it is not
|
|
2443
|
+
looked up per-SoM the way `_aen_console_pinmux_rows()` reads
|
|
2444
|
+
`pinmux/aen.yaml`.
|
|
2445
|
+
"""
|
|
2446
|
+
path = metadata_root / "e1m_modules" / "v2n" / "supervisor-links.yaml"
|
|
2447
|
+
if not path.is_file():
|
|
2448
|
+
raise ZephyrBoardEmitError(
|
|
2449
|
+
f"no {path} -- the V2N/V2M pinctrl.dtsi/_defconfig emitters need "
|
|
2450
|
+
"the on-module GD32G553 supervisor pin-wiring source (#655)")
|
|
2451
|
+
return _load_yaml(path)
|
|
2452
|
+
|
|
2453
|
+
|
|
2454
|
+
def _v2n_declared_families(metadata_root: Path) -> list[str]:
|
|
2455
|
+
"""The `families:` list `supervisor-links.yaml` declares it backs.
|
|
2456
|
+
|
|
2457
|
+
Load-bearing: `emit_zephyr_board()`'s V2N/V2M branch gates on this list
|
|
2458
|
+
instead of a hardcoded `("v2n", "v2n-m1")` tuple, so dropping a family
|
|
2459
|
+
here (e.g. removing `v2n-m1`) actually stops that family's
|
|
2460
|
+
pinctrl.dtsi/_defconfig from being generated rather than silently
|
|
2461
|
+
continuing on a stale local constant nothing else reads.
|
|
2462
|
+
"""
|
|
2463
|
+
doc = _load_supervisor_links_doc(metadata_root)
|
|
2464
|
+
families = doc.get("families")
|
|
2465
|
+
if not isinstance(families, list) or not families:
|
|
2466
|
+
raise ZephyrBoardEmitError(
|
|
2467
|
+
"metadata/e1m_modules/v2n/supervisor-links.yaml has no "
|
|
2468
|
+
"non-empty families: list")
|
|
2469
|
+
return families
|
|
2470
|
+
|
|
2471
|
+
|
|
2472
|
+
def _load_supervisor_links(metadata_root: Path) -> dict[str, Any]:
|
|
2473
|
+
"""Load + shape-check the `supervisor_links:` block of
|
|
2474
|
+
`metadata/e1m_modules/v2n/supervisor-links.yaml`."""
|
|
2475
|
+
doc = _load_supervisor_links_doc(metadata_root)
|
|
2476
|
+
links = doc.get("supervisor_links")
|
|
2477
|
+
if not isinstance(links, dict):
|
|
2478
|
+
raise ZephyrBoardEmitError(
|
|
2479
|
+
"metadata/e1m_modules/v2n/supervisor-links.yaml has no "
|
|
2480
|
+
"supervisor_links: block")
|
|
2481
|
+
for key in ("console", "gd32_spi", "brd_i2c"):
|
|
2482
|
+
if key not in links:
|
|
2483
|
+
raise ZephyrBoardEmitError(
|
|
2484
|
+
"metadata/e1m_modules/v2n/supervisor-links.yaml "
|
|
2485
|
+
f"supervisor_links: is missing {key!r}")
|
|
2486
|
+
return links
|
|
2487
|
+
|
|
2488
|
+
|
|
2489
|
+
def _pin_by_peripheral(pins: list[dict[str, Any]], peripheral: str) -> dict[str, Any]:
|
|
2490
|
+
for p in pins:
|
|
2491
|
+
if p.get("silicon_peripheral") == peripheral:
|
|
2492
|
+
return p
|
|
2493
|
+
raise ZephyrBoardEmitError(
|
|
2494
|
+
f"no {peripheral!r} row in this link's pins[] "
|
|
2495
|
+
"(supervisor-links.yaml / on-module-links.yaml)")
|
|
2496
|
+
|
|
2497
|
+
|
|
2498
|
+
def _rzv_pinmux(row: dict[str, Any]) -> str:
|
|
2499
|
+
return f"RZV_PINMUX({row['pfc_port']}, {row['pfc_pin']}, {row['pfc_func']})"
|
|
2500
|
+
|
|
2501
|
+
|
|
2502
|
+
#: RZ/V2N PFC alternate-function role -> mnemonic suffix for the gd32_spi
|
|
2503
|
+
#: link's `silicon_peripheral` roles ("GD32_SPI.MOSI" -> "MOSI", etc.).
|
|
2504
|
+
#: SCLK is the one exception to a literal pass-through: the RZ/V2N PFC
|
|
2505
|
+
#: table names that alternate function "SCK", not "SCLK".
|
|
2506
|
+
_V2N_SPI_ROLE_MNEMONIC = {"MOSI": "MOSI", "MISO": "MISO", "SCLK": "SCK"}
|
|
2507
|
+
|
|
2508
|
+
|
|
2509
|
+
def _v2n_sci_channel(dt_label: str) -> str:
|
|
2510
|
+
"""`"sci7"` -> `"7"` -- the SCI channel digit `gd32_spi.dt_label`
|
|
2511
|
+
encodes, used to build the PFC alternate-function mnemonics
|
|
2512
|
+
(`TXD7_MOSI7`, `RXD7_MISO7`, `SCK7`) instead of hardcoding them."""
|
|
2513
|
+
m = re.match(r"^sci([0-9]+)$", dt_label)
|
|
2514
|
+
if not m:
|
|
2515
|
+
raise ZephyrBoardEmitError(
|
|
2516
|
+
f"gd32_spi.dt_label {dt_label!r} is not sci<N> -- can't derive "
|
|
2517
|
+
"the RZ/V2N PFC alternate-function mnemonics from it")
|
|
2518
|
+
return m.group(1)
|
|
2519
|
+
|
|
2520
|
+
|
|
2521
|
+
def _v2n_spi_pfc_mnemonic(row: dict[str, Any], channel: str) -> str:
|
|
2522
|
+
"""RZ/V2N PFC alternate-function mnemonic for a `gd32_spi` pin row,
|
|
2523
|
+
e.g. (`silicon_peripheral="GD32_SPI.MOSI"`, channel `"7"`) -> `"MOSI7"`.
|
|
2524
|
+
"""
|
|
2525
|
+
role = row["silicon_peripheral"].rsplit(".", 1)[-1]
|
|
2526
|
+
return f"{_V2N_SPI_ROLE_MNEMONIC[role]}{channel}"
|
|
2527
|
+
|
|
2528
|
+
|
|
2529
|
+
def _v2n_assignable_m33(
|
|
2530
|
+
metadata_root: Path, soc_spec: dict[str, Any],
|
|
2531
|
+
) -> list[tuple[str, dict[str, Any], list[tuple[dict[str, Any], tuple[str, int, int]]]]]:
|
|
2532
|
+
"""(instance, entry, [(row, (port, pin, func))]) for every `assignable:`
|
|
2533
|
+
instance of core-ownership.yaml that carries an `m33:` devicetree block --
|
|
2534
|
+
the peripherals the CM33 board tree declares `disabled`, left for a
|
|
2535
|
+
project's `--emit dts-overlay` / `zephyr-conf` to enable (board.yaml
|
|
2536
|
+
`ownership:`). The PFC function codes are the SoC JSON's
|
|
2537
|
+
`linux_dt[soc_instance].pinmux` -- one silicon-fact source for both cores."""
|
|
2538
|
+
from .ownership import instance_pfc
|
|
2539
|
+
path = metadata_root / "e1m_modules" / "v2n" / "core-ownership.yaml"
|
|
2540
|
+
if not path.is_file():
|
|
2541
|
+
return []
|
|
2542
|
+
out = []
|
|
2543
|
+
for inst, e in sorted((_load_yaml(path).get("assignable") or {}).items()):
|
|
2544
|
+
if "m33" not in e:
|
|
2545
|
+
continue
|
|
2546
|
+
rows = instance_pfc(soc_spec, e)
|
|
2547
|
+
for r, pfc in rows:
|
|
2548
|
+
if pfc is None:
|
|
2549
|
+
raise ZephyrBoardEmitError(
|
|
2550
|
+
f"core-ownership.yaml assignable.{inst} has an m33: block but the SoC "
|
|
2551
|
+
f"linux_dt.{e.get('soc_instance')}.pinmux has no function code for "
|
|
2552
|
+
f"{r['peripheral']}")
|
|
2553
|
+
out.append((inst, e, rows))
|
|
2554
|
+
return out
|
|
2555
|
+
|
|
2556
|
+
|
|
2557
|
+
def _v2n_assignable_pinctrl(assignable: list) -> str:
|
|
2558
|
+
"""`&pinctrl { ... };` groups for the assignable instances (empty when
|
|
2559
|
+
none). Pins are the rows' own pfc_* triples -- no literals here."""
|
|
2560
|
+
out = ""
|
|
2561
|
+
for inst, e, rows in assignable:
|
|
2562
|
+
pc = e["m33"]["pinctrl"]
|
|
2563
|
+
pins = "".join(
|
|
2564
|
+
("\t\t\tpinmux = " if i == 0 else "\t\t\t\t ")
|
|
2565
|
+
+ f"<RZV_PINMUX({port}, {pin}, {func})>{';' if i == len(rows) - 1 else ','} "
|
|
2566
|
+
f"/* {r['peripheral']} {r['pad']} */\n"
|
|
2567
|
+
for i, (r, (port, pin, func)) in enumerate(rows))
|
|
2568
|
+
out += (
|
|
2569
|
+
"\n&pinctrl {\n"
|
|
2570
|
+
f"\t/* {inst}: pad group for the CM33 when a project assigns it (board.yaml\n"
|
|
2571
|
+
"\t * `ownership:`); unreferenced while the node stays disabled. */\n"
|
|
2572
|
+
f"\t{pc['group_label']}: {pc['node']} {{\n"
|
|
2573
|
+
f"\t\t{pc['child_node']} {{\n"
|
|
2574
|
+
f"{pins}"
|
|
2575
|
+
"\t\t};\n\t};\n};\n")
|
|
2576
|
+
return out
|
|
2577
|
+
|
|
2578
|
+
|
|
2579
|
+
def _v2n_assignable_dts(assignable: list) -> list[str]:
|
|
2580
|
+
"""Board-dts lines declaring each assignable node `disabled` with its
|
|
2581
|
+
pinctrl group; the owning project's overlay flips it to `okay`."""
|
|
2582
|
+
lines: list[str] = []
|
|
2583
|
+
for inst, e, _rows in assignable:
|
|
2584
|
+
m = e["m33"]
|
|
2585
|
+
lines += [
|
|
2586
|
+
"/*",
|
|
2587
|
+
f" * {inst} (assignable, metadata/e1m_modules/v2n/core-ownership.yaml): disabled on",
|
|
2588
|
+
" * the board; a project that assigns it to m33 enables it in its own overlay.",
|
|
2589
|
+
" */",
|
|
2590
|
+
f"&{m['dt_label']} {{",
|
|
2591
|
+
f"\tpinctrl-0 = <&{m['pinctrl']['group_label']}>;",
|
|
2592
|
+
'\tpinctrl-names = "default";',
|
|
2593
|
+
'\tstatus = "disabled";',
|
|
2594
|
+
"};",
|
|
2595
|
+
"",
|
|
2596
|
+
]
|
|
2597
|
+
return lines
|
|
2598
|
+
|
|
2599
|
+
|
|
2600
|
+
def _v2n_pinctrl_dtsi(links: dict[str, Any], assignable: list = ()) -> str:
|
|
2601
|
+
"""`<board>-pinctrl.dtsi` for a V2N/V2M `m33_sm` board.
|
|
2602
|
+
|
|
2603
|
+
Prose (the GD32 SPI block comment, the console/BRD_I2C one-liners)
|
|
2604
|
+
lives here as template text, matching `_aen_pinctrl_dtsi()`'s pattern;
|
|
2605
|
+
the pad numbers, PFC (port, pin, func) triples, DT node/group/child
|
|
2606
|
+
names, and the GD32 I2C address all come from
|
|
2607
|
+
`metadata/e1m_modules/v2n/supervisor-links.yaml`.
|
|
2608
|
+
"""
|
|
2609
|
+
console = links["console"]
|
|
2610
|
+
gd32_spi = links["gd32_spi"]
|
|
2611
|
+
brd_i2c = links["brd_i2c"]
|
|
2612
|
+
|
|
2613
|
+
txd0 = _pin_by_peripheral(console["pins"], "UART0_TXD0")
|
|
2614
|
+
rxd0 = _pin_by_peripheral(console["pins"], "UART0_RXD0")
|
|
2615
|
+
mosi = _pin_by_peripheral(gd32_spi["pins"], "GD32_SPI.MOSI")
|
|
2616
|
+
miso = _pin_by_peripheral(gd32_spi["pins"], "GD32_SPI.MISO")
|
|
2617
|
+
sclk = _pin_by_peripheral(gd32_spi["pins"], "GD32_SPI.SCLK")
|
|
2618
|
+
sda = _pin_by_peripheral(brd_i2c["pins"], "RIIC8_SDA8")
|
|
2619
|
+
scl = _pin_by_peripheral(brd_i2c["pins"], "RIIC8_SCL8")
|
|
2620
|
+
cs0 = gd32_spi["gpio_chip_select"]
|
|
2621
|
+
|
|
2622
|
+
# PFC alternate-function mnemonics + the console's own TXD0 reference,
|
|
2623
|
+
# derived from metadata (gd32_spi.dt_label's channel digit +
|
|
2624
|
+
# silicon_peripheral) rather than hardcoded, so a pfc_func or channel
|
|
2625
|
+
# change can't silently desync this prose from the RZV_PINMUX(...)
|
|
2626
|
+
# triples it documents.
|
|
2627
|
+
spi_channel = _v2n_sci_channel(gd32_spi["dt_label"])
|
|
2628
|
+
mosi_mnem = f"TXD{spi_channel}_{_v2n_spi_pfc_mnemonic(mosi, spi_channel)}"
|
|
2629
|
+
miso_mnem = f"RXD{spi_channel}_{_v2n_spi_pfc_mnemonic(miso, spi_channel)}"
|
|
2630
|
+
sclk_mnem = _v2n_spi_pfc_mnemonic(sclk, spi_channel)
|
|
2631
|
+
console_mnem = txd0["silicon_peripheral"].split("_", 1)[1]
|
|
2632
|
+
|
|
2633
|
+
return (
|
|
2634
|
+
_COPYRIGHT_C +
|
|
2635
|
+
" */\n"
|
|
2636
|
+
"\n"
|
|
2637
|
+
"#include <zephyr/dt-bindings/gpio/gpio.h>\n"
|
|
2638
|
+
"#include <zephyr/dt-bindings/pinctrl/renesas/pinctrl-rzv2n.h>\n"
|
|
2639
|
+
"\n"
|
|
2640
|
+
"&pinctrl {\n"
|
|
2641
|
+
f"\t/* {console['peripheral']} console: {txd0['silicon_pad']} TXD / "
|
|
2642
|
+
f"{rxd0['silicon_pad']} RXD */\n"
|
|
2643
|
+
f"\t{console['pinctrl_group_label']}: {console['pinctrl_node']} {{\n"
|
|
2644
|
+
f"\t\t{console['pinctrl_child_node']} {{\n"
|
|
2645
|
+
f"\t\t\tpinmux = <{_rzv_pinmux(txd0)}>, /* TXD */\n"
|
|
2646
|
+
f"\t\t\t\t <{_rzv_pinmux(rxd0)}>; /* RXD */\n"
|
|
2647
|
+
"\t\t\t/* RXD resets Hi-Z with no pull: a floating RXD0 raises sci0 eri.\n"
|
|
2648
|
+
"\t\t\t * Renesas' own RSCI UART pin groups always carry bias-pull-up\n"
|
|
2649
|
+
"\t\t\t * (audit CM33-01/IO-01/CM33-M3). Whole group; a pull-up on TXD is\n"
|
|
2650
|
+
"\t\t\t * harmless. */\n"
|
|
2651
|
+
"\t\t\tbias-pull-up;\n"
|
|
2652
|
+
"\t\t};\n"
|
|
2653
|
+
"\t};\n"
|
|
2654
|
+
"\n"
|
|
2655
|
+
"\t/*\n"
|
|
2656
|
+
f"\t * GD32G553 SPI fast path on SCI channel {spi_channel} (clock-synchronous Simple-SPI).\n"
|
|
2657
|
+
f"\t * Per the RZ/V2N PFC (Table 1.2-3): {mosi['silicon_pad']} = {mosi_mnem} (func{mosi['pfc_func']}), "
|
|
2658
|
+
f"{miso['silicon_pad']} =\n"
|
|
2659
|
+
f"\t * {miso_mnem} (func{miso['pfc_func']}), {sclk['silicon_pad']} = {sclk_mnem} (func{sclk['pfc_func']}). Calibrated against the known-good\n"
|
|
2660
|
+
f"\t * console pin {txd0['silicon_pad']} = {console_mnem} (func{txd0['pfc_func']}) and the P52 TXD1/SCK0/DE0/CTS0N column run.\n"
|
|
2661
|
+
"\t *\n"
|
|
2662
|
+
f"\t * {cs0['silicon_pad']} (SS{spi_channel}) is deliberately NOT muxed here: master Simple-SPI has no\n"
|
|
2663
|
+
f"\t * hardware slave-select, so {cs0['silicon_pad']} is driven as a GPIO chip-select "
|
|
2664
|
+
f"({cs0['gpio_node']} pin {cs0['gpio_pin']};\n"
|
|
2665
|
+
"\t * see cs-gpios in the board dts). Leaving it out keeps it in GPIO mode.\n"
|
|
2666
|
+
"\t */\n"
|
|
2667
|
+
f"\t{gd32_spi['pinctrl_group_label']}: {gd32_spi['pinctrl_node']} {{\n"
|
|
2668
|
+
f"\t\t{gd32_spi['pinctrl_child_node']} {{\n"
|
|
2669
|
+
f"\t\t\tpinmux = <{_rzv_pinmux(mosi)}>, /* {mosi_mnem.split('_', 1)[1]} {mosi['silicon_pad']} */\n"
|
|
2670
|
+
f"\t\t\t\t <{_rzv_pinmux(miso)}>, /* {miso_mnem.split('_', 1)[1]} {miso['silicon_pad']} */\n"
|
|
2671
|
+
f"\t\t\t\t <{_rzv_pinmux(sclk)}>; /* {sclk_mnem} {sclk['silicon_pad']} */\n"
|
|
2672
|
+
"\t\t};\n"
|
|
2673
|
+
"\t};\n"
|
|
2674
|
+
"\n"
|
|
2675
|
+
f"\t/* BRD_I2C ({brd_i2c['peripheral']}): on-module GD32G553 I2C slave @ "
|
|
2676
|
+
f"0x{brd_i2c['peer_address_7bit']:02x} */\n"
|
|
2677
|
+
f"\t{brd_i2c['pinctrl_group_label']}: {brd_i2c['pinctrl_node']} {{\n"
|
|
2678
|
+
f"\t\t{brd_i2c['pinctrl_child_node']} {{\n"
|
|
2679
|
+
f"\t\t\tpinmux = <{_rzv_pinmux(sda)}>, /* SDA {sda['silicon_pad']} */\n"
|
|
2680
|
+
f"\t\t\t\t <{_rzv_pinmux(scl)}>; /* SCL {scl['silicon_pad']} */\n"
|
|
2681
|
+
"\t\t};\n"
|
|
2682
|
+
"\t};\n"
|
|
2683
|
+
"};\n"
|
|
2684
|
+
) + _v2n_assignable_pinctrl(assignable)
|
|
2685
|
+
|
|
2686
|
+
|
|
2687
|
+
def _v2n_defconfig(links: dict[str, Any], soc_spec: dict[str, Any]) -> str:
|
|
2688
|
+
"""`<board>_defconfig` for a V2N/V2M `m33_sm` board.
|
|
2689
|
+
|
|
2690
|
+
`CONFIG_XIP=n` has no supervisor-link source -- it's a documented V2N/
|
|
2691
|
+
V2M family constant (the CM33 image always runs from RAM on this
|
|
2692
|
+
family), kept as a literal here rather than invented metadata. The
|
|
2693
|
+
console-disable prose and the CONFIG_* symbol names are template text
|
|
2694
|
+
(matching `_aen_defconfig()`'s pattern); which block gets emitted is
|
|
2695
|
+
still gated on the metadata's own `status:` per link, so a
|
|
2696
|
+
supervisor-links.yaml edit that (incorrectly) re-enables the console or
|
|
2697
|
+
disables the GD32 bridge fails loudly here instead of silently drifting
|
|
2698
|
+
the committed `_defconfig`.
|
|
2699
|
+
"""
|
|
2700
|
+
rc_a55, _, rc_size = _ram_console(soc_spec)
|
|
2701
|
+
win_base = _openamp_carveout(soc_spec)["cm33_ns_base"]
|
|
2702
|
+
console = links["console"]
|
|
2703
|
+
gd32_spi = links["gd32_spi"]
|
|
2704
|
+
brd_i2c = links["brd_i2c"]
|
|
2705
|
+
if console.get("status") != "disabled":
|
|
2706
|
+
raise ZephyrBoardEmitError(
|
|
2707
|
+
"supervisor-links.yaml console link must stay status: disabled "
|
|
2708
|
+
"for this _defconfig template -- its floating-RX fault (see "
|
|
2709
|
+
"the console pins' evidence: strings) is why the CM33 serial "
|
|
2710
|
+
"console is off; re-enabling it needs a matching _defconfig "
|
|
2711
|
+
"rewrite, not just a metadata flip")
|
|
2712
|
+
if gd32_spi.get("status") != "enabled":
|
|
2713
|
+
raise ZephyrBoardEmitError(
|
|
2714
|
+
"supervisor-links.yaml gd32_spi link must stay status: enabled "
|
|
2715
|
+
"for this _defconfig template to turn on CONFIG_SPI")
|
|
2716
|
+
if brd_i2c.get("status") != "disabled":
|
|
2717
|
+
raise ZephyrBoardEmitError(
|
|
2718
|
+
"supervisor-links.yaml brd_i2c link must stay status: disabled "
|
|
2719
|
+
"-- RIIC8/BRD_I2C is Cortex-A55/Linux-exclusive, so this "
|
|
2720
|
+
"_defconfig template does NOT turn on CONFIG_I2C for it; "
|
|
2721
|
+
"re-enabling it needs a matching _defconfig rewrite, not just "
|
|
2722
|
+
"a metadata flip")
|
|
2723
|
+
return (
|
|
2724
|
+
_COPYRIGHT_HASH +
|
|
2725
|
+
"\n"
|
|
2726
|
+
"CONFIG_XIP=n\n"
|
|
2727
|
+
"\n"
|
|
2728
|
+
"# CM33 serial console (sci0 = EVK \"Pmod USB-UART\") DISABLED on this board:\n"
|
|
2729
|
+
"# the only console is the A55's; sci0 is not wired as a CM33 console here, and\n"
|
|
2730
|
+
"# opening it faults on the floating RX (sci0 eri -> Zephyr fatal -> hang before\n"
|
|
2731
|
+
"# main()). Re-enable UART_CONSOLE (and sci0 in the dts) only with a Pmod\n"
|
|
2732
|
+
"# attached AND once Linux cannot gate the rsci_0_* clocks (audit CM33-05).\n"
|
|
2733
|
+
"CONFIG_SERIAL=y\n"
|
|
2734
|
+
"CONFIG_UART_CONSOLE=n\n"
|
|
2735
|
+
"\n"
|
|
2736
|
+
"# Enabling sci0 also needs CONFIG_UART_INTERRUPT_DRIVEN=y: the FSP always\n"
|
|
2737
|
+
"# enables the rxi/eri IRQs, and the Zephyr driver only IRQ_CONNECTs them\n"
|
|
2738
|
+
"# under it -- otherwise an unhandled NVIC line goes fatal. Not set here:\n"
|
|
2739
|
+
"# with sci0 disabled it resolves to n and Kconfig warns.\n"
|
|
2740
|
+
"\n"
|
|
2741
|
+
"# RAM console: the CM33 has no UART and no SWD yet, so printk lands in a\n"
|
|
2742
|
+
f"# fixed buffer (zephyr,ram-console node in the dts, inside the {win_base:#x}\n"
|
|
2743
|
+
"# window Linux reserves) that the A55 reads post-mortem via /dev/mem at\n"
|
|
2744
|
+
f"# {rc_a55:#x}. CONFIG_CONSOLE=y only pulls the RAM console in; with\n"
|
|
2745
|
+
"# UART_CONSOLE=n nothing touches sci0. Size must equal the dts node's reg.\n"
|
|
2746
|
+
"CONFIG_CONSOLE=y\n"
|
|
2747
|
+
"CONFIG_RAM_CONSOLE=y\n"
|
|
2748
|
+
f"CONFIG_RAM_CONSOLE_BUFFER_SIZE={rc_size}\n"
|
|
2749
|
+
"# With CONFIG_LOG=y, printk is routed into the logging core by default\n"
|
|
2750
|
+
"# (LOG_PRINTK), and no CM33 log backend reaches this buffer, so it would\n"
|
|
2751
|
+
"# stay empty. Keep printk direct to the RAM console.\n"
|
|
2752
|
+
"CONFIG_LOG_PRINTK=n\n"
|
|
2753
|
+
"\n"
|
|
2754
|
+
"# On-module GD32G553 supervisor bridge transport. SPI only: RIIC8/\n"
|
|
2755
|
+
"# BRD_I2C is Cortex-A55/Linux-exclusive, so no CONFIG_I2C here.\n"
|
|
2756
|
+
"CONFIG_SPI=y\n"
|
|
2757
|
+
)
|
|
2758
|
+
|
|
2759
|
+
|
|
2760
|
+
def _v2n_part_display(order_code: str) -> tuple[str, str]:
|
|
2761
|
+
"""Split an RZ/V2N `silicon_variant` order code into the board `.dts`
|
|
2762
|
+
header's part display (`"R9A09G056N44GBG"` -> `"R9A09G056-N44"`, the
|
|
2763
|
+
package suffix dropped and a hyphen inserted before the variant code)
|
|
2764
|
+
and the upstream Zephyr SoC dtsi include path both n44 and n48gbg
|
|
2765
|
+
variants share (`"arm/renesas/rz/rzv/r9a09g056.dtsi"`). Raises rather
|
|
2766
|
+
than guessing if a future RZ/V2N part doesn't fit this
|
|
2767
|
+
base+variant+package shape -- see the no-inventing-values rule.
|
|
2768
|
+
"""
|
|
2769
|
+
m = re.match(r"^(R9A09G056)(N[0-9]+)([A-Z]+)$", order_code)
|
|
2770
|
+
if not m:
|
|
2771
|
+
raise ZephyrBoardEmitError(
|
|
2772
|
+
f"silicon_variant order_code {order_code!r} doesn't match the "
|
|
2773
|
+
"RZ/V2N R9A09G056<variant><pkg> shape the board .dts header "
|
|
2774
|
+
"and #include path expect")
|
|
2775
|
+
base, variant_code, _pkg = m.groups()
|
|
2776
|
+
return f"{base}-{variant_code}", f"arm/renesas/rz/rzv/{base.lower()}.dtsi"
|
|
2777
|
+
|
|
2778
|
+
|
|
2779
|
+
_V2N_OPENAMP_TAIL: tuple[str, ...] = (
|
|
2780
|
+
'',
|
|
2781
|
+
'/*',
|
|
2782
|
+
' * No &canfd node here (alp-sdk#1146): CAN-FD cannot be enabled on this',
|
|
2783
|
+
' * core at the pinned Zephyr v4.4.1 / hal_renesas 06282060fa, and what is',
|
|
2784
|
+
' * missing is vendor data this tree does not carry -- not lines nobody has',
|
|
2785
|
+
' * got round to writing. Upstream DOES ship the driver',
|
|
2786
|
+
' * (drivers/can/can_renesas_rz_canfd.c) and both bindings',
|
|
2787
|
+
' * (dts/bindings/can/renesas,rz-canfd.yaml + renesas,rz-canfd-global.yaml),',
|
|
2788
|
+
' * and src/backends/can/zephyr_drv.c already binds any DT_ALIAS(alp_canN),',
|
|
2789
|
+
' * so this is an enablement gap and not a missing backend. Four things',
|
|
2790
|
+
' * block the enablement:',
|
|
2791
|
+
' *',
|
|
2792
|
+
' * 1. No FSP CAN-FD module for RZ/V. CONFIG_CAN_RENESAS_RZ_CANFD',
|
|
2793
|
+
' * selects USE_RZ_FSP_CANFD (Zephyr drivers/can/Kconfig.renesas_rz),',
|
|
2794
|
+
' * which compiles fsp/src/${SOC_SERIES_PREFIX}/r_canfd/r_canfd.c',
|
|
2795
|
+
' * (hal_renesas drivers/rz/CMakeLists.txt). hal_renesas carries that',
|
|
2796
|
+
' * module for RZ/G only: fsp/src/rzg/r_canfd/ and',
|
|
2797
|
+
' * fsp/inc/instances/rzg/r_canfd.h exist, the rzv counterparts do',
|
|
2798
|
+
' * not. Setting the symbol under SOC_SERIES_RZV2N is a CMake',
|
|
2799
|
+
" * configure error on a missing source file, and the driver's",
|
|
2800
|
+
' * #include "r_canfd.h" does not resolve on the rzv include path.',
|
|
2801
|
+
' *',
|
|
2802
|
+
' * 2. No CAN-FD node in the SoC devicetree. arm/renesas/rz/rzv/',
|
|
2803
|
+
' * r9a09g056.dtsi declares none -- no label to reference, no reg and',
|
|
2804
|
+
' * no interrupts to inherit. The only RZ SoC dtsi that carries them',
|
|
2805
|
+
" * is RZ/G3S's r9a08g045.dtsi (canfd-global@400c0000, global",
|
|
2806
|
+
' * interrupts 373 and 374, per-channel 375..380). DO NOT COPY THOSE',
|
|
2807
|
+
' * NUMBERS: different SoC, different vector map, different base. On',
|
|
2808
|
+
' * THIS part the CAN-FD register base is 0x42440000 -- hal_renesas',
|
|
2809
|
+
' * .../Include/R9A09G056N/iodefines/canfd_iodefine.h, R_CANFD_BASE.',
|
|
2810
|
+
" * The `(@ 0x400B0000)` in that same file's R_CANFD struct",
|
|
2811
|
+
" * doc-comment is a template annotation, not this SoC's base; it is",
|
|
2812
|
+
' * the second trap in one header.',
|
|
2813
|
+
' *',
|
|
2814
|
+
" * 3. No static NVIC vector exists to write. RZ/V2N's CM33 gives CAN-FD",
|
|
2815
|
+
" * no dedicated vector at all: bsp_irq_id.h's IRQn_Type enum has zero",
|
|
2816
|
+
' * CANFD entries, and all 20 CAN-FD sources live in the separate',
|
|
2817
|
+
' * IRQSELn_Type enum (354..373 -- per-channel err/rec/trx, plus',
|
|
2818
|
+
' * CANFD_INTRCANGERR_IRQSELn 366 and CANFD_INTRCANGRECC_IRQSELn 367',
|
|
2819
|
+
' * for the global controller). Reaching the NVIC means routing an',
|
|
2820
|
+
' * IRQSEL source onto one of the selectable slots',
|
|
2821
|
+
' * SEL0_IRQn..SEL126_IRQn (NVIC 353..479) at run time, through',
|
|
2822
|
+
" * R_BSP_SelectIrqSet() (bsp/mcu/all/bsp_mcu_api.h). Upstream's dtsi",
|
|
2823
|
+
' * hands those slots out itself (adc0 = 403 = SEL50, gpt0 = 406..408',
|
|
2824
|
+
' * = SEL53..55, on up to 453) and both CAN-FD bindings require',
|
|
2825
|
+
' * `interrupts`, so choosing slots here means inventing an allocation',
|
|
2826
|
+
' * that collides with whatever upstream picks when it adds the nodes.',
|
|
2827
|
+
' *',
|
|
2828
|
+
' * 4. No PFC function index for the pads. The module bonds out two of',
|
|
2829
|
+
" * the SoC's six controllers -- CANFD2 (CTX2 = P84, CRX2 = P85) and",
|
|
2830
|
+
' * CANFD3 (CTX3 = P86, CRX3 = P87), per',
|
|
2831
|
+
' * metadata/e1m_modules/v2n/renesas-peripheral-map.tsv -- but',
|
|
2832
|
+
' * metadata/pinmux/v2n.yaml carries pad <-> signal only, with no',
|
|
2833
|
+
' * alternate-function number. RZV_PINMUX() needs one, and it comes',
|
|
2834
|
+
" * from the RZ/V2N hardware manual's PFC table (the same source",
|
|
2835
|
+
' * sci7_spi_pins above cites), not from this tree.',
|
|
2836
|
+
' *',
|
|
2837
|
+
' * What IS already in place, so a future port does not re-derive it: the',
|
|
2838
|
+
' * clock path. RZ_CLOCK_CANFD(ch) exists',
|
|
2839
|
+
' * (dt-bindings/clock/renesas_rzv_clock.h) and',
|
|
2840
|
+
' * clock_control_renesas_rz_cpg.c already maps RZ_IP_CANFD onto',
|
|
2841
|
+
' * R_BSP_MODULE_START(FSP_IP_CANFD, ch); the rzv2n bsp_feature.h declares',
|
|
2842
|
+
' * 6 channels, CAN-FD support and 96 RX message buffers for this part.',
|
|
2843
|
+
' *',
|
|
2844
|
+
" * Carrier note: the TCAN1044 transceivers' standby line is NOT a Renesas",
|
|
2845
|
+
' * pad. CAN_STBY belongs to the GD32 IO-MCU on PB13',
|
|
2846
|
+
' * (metadata/pinmux/v2n.yaml, metadata/e1m_modules/v2n/gd32-io-mcu-map.tsv),',
|
|
2847
|
+
' * so taking the transceivers out of standby is a bridge GPIO call, not a',
|
|
2848
|
+
' * pinctrl entry.',
|
|
2849
|
+
' *',
|
|
2850
|
+
' * Until those clear, <alp/can.h> on this core returns ALP_ERR_NOT_READY',
|
|
2851
|
+
' * from src/backends/can/zephyr_drv.c -- the alp-canN aliases resolve to',
|
|
2852
|
+
' * nothing.',
|
|
2853
|
+
' */',
|
|
2854
|
+
'',
|
|
2855
|
+
'/*',
|
|
2856
|
+
' * OpenAMP / RPMsg carve-out for the M33-SM <-> A55-Linux link (alp-sdk #683,',
|
|
2857
|
+
' * Path B Phase 1). Addresses are the Renesas RZ/V Multi-OS Package memory',
|
|
2858
|
+
" * map, NOT the paper `ipc:` carve-out in this project's board.yaml. The two",
|
|
2859
|
+
' * are different regions by construction, not by omission: `resolve_carve_outs()`',
|
|
2860
|
+
" * (scripts/alp_orchestrate/carveout.py) allocates board.yaml's `ipc:` entry",
|
|
2861
|
+
' * from `ocram_low` (512 KiB, non-cacheable -- the only region it can reach',
|
|
2862
|
+
' * from both `a55_cluster` and `m33_sm`; metadata/socs/renesas/rzv2n/n44.json)',
|
|
2863
|
+
' * purely because the resolver needs *some* region and that is the only',
|
|
2864
|
+
' * candidate -- nothing in this firmware reads or writes the address it',
|
|
2865
|
+
' * resolves to. The real transport uses the hand-authored DDR window below',
|
|
2866
|
+
" * instead, sized from this responder's OpenAMP resource table (512 rpmsg",
|
|
2867
|
+
" * buffers x 512 B x 2 vrings -- m33_sm/src/resource_table.h's",
|
|
2868
|
+
' * RSC_TABLE_NUM_RPMSG_BUFF). That sizing happens to consume the whole of',
|
|
2869
|
+
' * `ocram_low` when expressed as `carve_out_kb` in board.yaml (both examples',
|
|
2870
|
+
' * on this SoM request 512, i.e. all 512 KiB of `ocram_low`) -- harmless',
|
|
2871
|
+
' * today because nothing consumes the resolved address, but it means a',
|
|
2872
|
+
' * second `ipc:` entry on this SoM has no ocram_low headroom left to land',
|
|
2873
|
+
' * in. Whether the fix is teaching the resolver about this DDR window, or',
|
|
2874
|
+
' * shrinking the paper `carve_out_kb` values to leave ocram_low headroom, is',
|
|
2875
|
+
' * an open sizing question for whoever picks it up next -- it is not settled',
|
|
2876
|
+
' * by #683, which is about bench-validating the real `/dev/rpmsg` transport,',
|
|
2877
|
+
' * not this address reconciliation.',
|
|
2878
|
+
' *',
|
|
2879
|
+
' * CORRECTED (alp-sdk #683, address root-cause fix): this region used to be',
|
|
2880
|
+
' * quoted at the RZ/V2L CM33-NS offset (0x62f00000, i.e. A55 - 0x20000000),',
|
|
2881
|
+
' * which is the V2N xSPI-NOR secure window on THIS SoC -- writes there never',
|
|
2882
|
+
' * reach DRAM. The authoritative V2N map (Renesas FSP',
|
|
2883
|
+
' * drivers/rz/fsp/src/rzv/bsp/mcu/rzv2n/bsp_slave_address.h, confirmed by',
|
|
2884
|
+
" * this board's upstream ddr node) is CM33-secure 0x80000000 / CM33-NS",
|
|
2885
|
+
' * 0x90000000 / A55 0x40000000, a 256 MiB window, i.e. A55 phys = CM33-NS -',
|
|
2886
|
+
' * 0x50000000. Of that window only A55 0x48000000..0x4FFFFFFF is real,',
|
|
2887
|
+
' * populated NS RAM (0x40000000..0x47FFFFFF reads back 0xFF) -- the base',
|
|
2888
|
+
' * below is chosen so the whole carve-out (0x4F700000..0x4FFFFFFF) sits',
|
|
2889
|
+
' * entirely inside that populated range, right up against its top. The',
|
|
2890
|
+
' * rsctbl/vring/shm regions below are all quoted in the CM33 view, matching',
|
|
2891
|
+
" * upstream's openamp_linux_zephyr sample; see resource_table.h for the",
|
|
2892
|
+
' * CM33->A55 translation macro this same fix corrected.',
|
|
2893
|
+
' *',
|
|
2894
|
+
' * mbox1 is hand-authored: unlike the RZ/V2L SoC dtsi (r9a07g054.dtsi, which',
|
|
2895
|
+
' * ships mbox1/mbox3/mbox4/mbox5), the upstream RZ/V2N SoC dtsi',
|
|
2896
|
+
' * (r9a09g056.dtsi) has no MHU node at all yet -- added here at board level',
|
|
2897
|
+
' * per the "board .dts stays hand-authored for the v2n family"',
|
|
2898
|
+
' * (scripts/gen_zephyr_board.py) rather than editing the vendored SoC dtsi.',
|
|
2899
|
+
' *',
|
|
2900
|
+
' * RESOLVED (alp-sdk #683 Phase 2): RZ/V2N\'s MHU hardware is the newer "MHU-B"',
|
|
2901
|
+
' * variant (hal_renesas drivers/rz/fsp/src/rzv/bsp/mcu/rzv2n/bsp_mhu_b.h) with',
|
|
2902
|
+
' * only channels {5, 11, 17, 23} valid (BSP_FEATURE_MHU_B_NS_VALID_CHANNEL_MASK)',
|
|
2903
|
+
' * and a non-linear per-channel register/IRQ pairing -- NOT the plain linear MHU',
|
|
2904
|
+
" * that Zephyr's mbox_renesas_rz_mhu.c + FSP r_mhu_ns.c implement (that pair does",
|
|
2905
|
+
' * not compile for rzv2n). This node now uses the vendored MHU-B port instead',
|
|
2906
|
+
' * (compatible `renesas,rz-mhu-b-mbox`, driver',
|
|
2907
|
+
' * zephyr/drivers/mbox/mbox_renesas_rz_mhu_b.c, FSP module',
|
|
2908
|
+
" * zephyr/drivers/mbox/r_mhu_b_ns/r_mhu_b_ns.c -- see that file's header for the",
|
|
2909
|
+
' * register-semantic notes and the "FLAG FOR REVIEW" send_type caveat, and',
|
|
2910
|
+
" * mbox_renesas_rz_mhu_b.c's header for the bounded #697 verification status:",
|
|
2911
|
+
' * init/attach and A55->M33 receive are silicon-proven, mbox_send() M33->A55',
|
|
2912
|
+
' * is unexercised and does not reach the A55 on this topology).',
|
|
2913
|
+
" * `channel = <5>` is the lowest MHU-B-valid channel. Per bsp_mhu_b.h's",
|
|
2914
|
+
' * R_BSP_MHU_B_NS_REG_PAIR_BODY, channel 5 sends via R_MHU_NS36 (base',
|
|
2915
|
+
' * 0x50480480) and receives via R_MHU_NS8 (base 0x50480100) -- `reg` below',
|
|
2916
|
+
' * documents the send register only (the driver resolves both from the',
|
|
2917
|
+
' * pair-body table by channel number, not from this property; the binding',
|
|
2918
|
+
' * schema requires `reg` regardless). `interrupts = <293 2>` is',
|
|
2919
|
+
' * MHU_MSG5_NS_IRQn (bsp_irq_id.h), which r_mhu_b_ns.c matches against',
|
|
2920
|
+
" * bsp_mhu_b.h's R_BSP_MHU_B_NS_SEND_TYPE_RSP_BODY to derive send_type = RSP",
|
|
2921
|
+
' * for this instance.',
|
|
2922
|
+
' */',
|
|
2923
|
+
'/ {',
|
|
2924
|
+
'\treserved-memory {',
|
|
2925
|
+
'\t\t#address-cells = <1>;',
|
|
2926
|
+
'\t\t#size-cells = <1>;',
|
|
2927
|
+
'\t\tranges;',
|
|
2928
|
+
'',
|
|
2929
|
+
'\t\t/* Whole OpenAMP region as one reservation to save MPU entries',
|
|
2930
|
+
"\t\t * (matches the vendor sample's rationale). */",
|
|
2931
|
+
'\t\topenamp_shm: memory@@carveout.hex@ {',
|
|
2932
|
+
'\t\t\tcompatible = "zephyr,memory-region";',
|
|
2933
|
+
'\t\t\treg = <@carveout.addr@ @carveout.size@>;',
|
|
2934
|
+
'\t\t\tzephyr,memory-region = "openamp_memory";',
|
|
2935
|
+
'\t\t\tzephyr,memory-attr = <DT_MEM_ARM(ATTR_MPU_IO)>;',
|
|
2936
|
+
'\t\t};',
|
|
2937
|
+
'\t};',
|
|
2938
|
+
'',
|
|
2939
|
+
'\t/* CM33-NS view minus A55 view of the OpenAMP reservation (from metadata) */',
|
|
2940
|
+
'\tzephyr,user {',
|
|
2941
|
+
'\t\talp,cm33-ns-to-a55-offset = <@carveout.offset@>;',
|
|
2942
|
+
'\t};',
|
|
2943
|
+
'',
|
|
2944
|
+
'\tchosen {',
|
|
2945
|
+
'\t\t/* The A55 master (DRIVER role) allocates every rpmsg buffer from',
|
|
2946
|
+
'\t\t * vring_shm1 (@vring-shm1.a55@ A55 / @vring-shm1.addr@ CM33-NS) -- the',
|
|
2947
|
+
'\t\t * "mst-alloc = vring-shm1" contract in the backend REFERENCE. The',
|
|
2948
|
+
"\t\t * M33's descriptor-translation window MUST cover that pool, so it",
|
|
2949
|
+
'\t\t * points at vring_shm1, not vring_shm0 (whose window ends exactly at',
|
|
2950
|
+
'\t\t * @vring-shm0.end@, leaving every buffer outside it). Silicon-root-caused',
|
|
2951
|
+
'\t\t * alongside the vring-DA fix (#683/#697 bench, 2026-07-11). */',
|
|
2952
|
+
'\t\tzephyr,ipc_shm = &vring_shm1;',
|
|
2953
|
+
'\t\tzephyr,ipc = &mbox_consumer;',
|
|
2954
|
+
'\t};',
|
|
2955
|
+
'',
|
|
2956
|
+
'\trsctbl: memory@@rsctbl.hex@ {',
|
|
2957
|
+
'\t\tcompatible = "mmio-sram";',
|
|
2958
|
+
'\t\treg = <@rsctbl.addr@ @rsctbl.size@>;',
|
|
2959
|
+
'\t};',
|
|
2960
|
+
'',
|
|
2961
|
+
"\t/* Widened from the RZ/V2L layout's 8-byte mhu1_shm (@ +0x1008) to a",
|
|
2962
|
+
'\t * full 4 KiB region immediately after rsctbl, so it lines up with the',
|
|
2963
|
+
"\t * A55/kernel-overlay side's @mhu-shm.a55hex@.mhu-shm node 1:1 (alp-sdk #683",
|
|
2964
|
+
'\t * address fix). */',
|
|
2965
|
+
'\tmhu1_shm: memory@@mhu-shm.hex@ {',
|
|
2966
|
+
'\t\tcompatible = "mmio-sram";',
|
|
2967
|
+
'\t\treg = <@mhu-shm.addr@ @mhu-shm.size@>;',
|
|
2968
|
+
'\t};',
|
|
2969
|
+
'',
|
|
2970
|
+
'\tvring_ctrl0: memory@@vring-ctl0.hex@ {',
|
|
2971
|
+
'\t\tcompatible = "mmio-sram";',
|
|
2972
|
+
'\t\treg = <@vring-ctl0.addr@ @vring-ctl0.size@>;',
|
|
2973
|
+
'\t};',
|
|
2974
|
+
'',
|
|
2975
|
+
'\tvring_ctrl1: memory@@vring-ctl1.hex@ {',
|
|
2976
|
+
'\t\tcompatible = "mmio-sram";',
|
|
2977
|
+
'\t\treg = <@vring-ctl1.addr@ @vring-ctl1.size@>;',
|
|
2978
|
+
'\t};',
|
|
2979
|
+
'',
|
|
2980
|
+
'\tvring_shm0: memory@@vring-shm0.hex@ {',
|
|
2981
|
+
'\t\tcompatible = "mmio-sram";',
|
|
2982
|
+
'\t\treg = <@vring-shm0.addr@ @vring-shm0.size@>;',
|
|
2983
|
+
'\t};',
|
|
2984
|
+
'',
|
|
2985
|
+
'\t/* The rpmsg buffer pool: the A55 master allocates from here, so this is',
|
|
2986
|
+
"\t * the M33's zephyr,ipc_shm window (see the chosen node above).",
|
|
2987
|
+
'\t * vring_shm0 is kept reserved so the region layout still matches the A55',
|
|
2988
|
+
"\t * kernel overlay's 6 nodes 1:1. */",
|
|
2989
|
+
'\tvring_shm1: memory@@vring-shm1.hex@ {',
|
|
2990
|
+
'\t\tcompatible = "mmio-sram";',
|
|
2991
|
+
'\t\treg = <@vring-shm1.addr@ @vring-shm1.size@>;',
|
|
2992
|
+
'\t};',
|
|
2993
|
+
'',
|
|
2994
|
+
'\tmbox_consumer: mbox-consumer {',
|
|
2995
|
+
'\t\tcompatible = "vnd,mbox-consumer";',
|
|
2996
|
+
'\t\tmboxes = <&mbox1 1>, <&mbox1 0>;',
|
|
2997
|
+
'\t\tmbox-names = "tx", "rx";',
|
|
2998
|
+
'\t};',
|
|
2999
|
+
'',
|
|
3000
|
+
'\tsoc {',
|
|
3001
|
+
'\t\t/* CA55_0 <-> CM33 -- see the MHU-B note above. */',
|
|
3002
|
+
'\t\tmbox1: mhu@50480480 {',
|
|
3003
|
+
'\t\t\tcompatible = "renesas,rz-mhu-b-mbox";',
|
|
3004
|
+
'\t\t\tchannel = <5>;',
|
|
3005
|
+
'\t\t\treg = <0x50480480 0x20>; /* R_MHU_NS36 (send reg) -- see the MHU-B note above */',
|
|
3006
|
+
'\t\t\ttx-mask = <0x00000002>; /* Channel 1 is for TX */',
|
|
3007
|
+
'\t\t\trx-mask = <0x00000001>; /* Channel 0 is for RX */',
|
|
3008
|
+
'\t\t\tchannels-count = <2>;',
|
|
3009
|
+
'\t\t\tinterrupts = <293 2>; /* MHU_MSG5_NS_IRQn, bsp_irq_id.h -- verified against the vendor header */',
|
|
3010
|
+
'\t\t\tinterrupt-names = "mhuns";',
|
|
3011
|
+
'\t\t\t#mbox-cells = <1>;',
|
|
3012
|
+
'\t\t\tshared-memory = <&mhu1_shm>;',
|
|
3013
|
+
'\t\t\tstatus = "okay";',
|
|
3014
|
+
'\t\t};',
|
|
3015
|
+
'\t};',
|
|
3016
|
+
'};',
|
|
3017
|
+
)
|
|
3018
|
+
|
|
3019
|
+
|
|
3020
|
+
def _openamp_carveout(soc_spec: dict[str, Any]) -> dict[str, Any]:
|
|
3021
|
+
"""The SoC spec's `openamp_carveout` block, refused with a coded error
|
|
3022
|
+
naming the first missing field -- never a bare `KeyError` out of `tan
|
|
3023
|
+
generate --target zephyr-board` against an alp-sdk that predates the
|
|
3024
|
+
block (alp-sdk#2685, the floor this change raises tan to)."""
|
|
3025
|
+
where = f"SoC spec {soc_spec.get('ref')} `openamp_carveout`"
|
|
3026
|
+
c = soc_spec.get("openamp_carveout")
|
|
3027
|
+
if not isinstance(c, dict) or not c:
|
|
3028
|
+
raise ZephyrBoardEmitError(
|
|
3029
|
+
f"{where} is missing -- the V2N/V2M board emits the OpenAMP window "
|
|
3030
|
+
"and the CM33 RAM console from it (needs alp-sdk b04bb0f7a or newer)")
|
|
3031
|
+
|
|
3032
|
+
def need(block: dict[str, Any], name: str, path: str) -> Any:
|
|
3033
|
+
if not isinstance(block, dict) or block.get(name) is None:
|
|
3034
|
+
raise ZephyrBoardEmitError(
|
|
3035
|
+
f"{where} has no `{path}{name}` field")
|
|
3036
|
+
return block[name]
|
|
3037
|
+
|
|
3038
|
+
for f in ("cm33_ns_base", "a55_base", "size"):
|
|
3039
|
+
need(c, f, "")
|
|
3040
|
+
regions = need(c, "regions", "")
|
|
3041
|
+
if not isinstance(regions, dict):
|
|
3042
|
+
raise ZephyrBoardEmitError(f"{where} `regions` is not a mapping")
|
|
3043
|
+
for name, r in regions.items():
|
|
3044
|
+
for f in ("offset", "size"):
|
|
3045
|
+
need(r, f, f"regions.{name}.")
|
|
3046
|
+
rc = need(c, "ram_console", "")
|
|
3047
|
+
for f in ("offset", "size"):
|
|
3048
|
+
need(rc, f, "ram_console.")
|
|
3049
|
+
return c
|
|
3050
|
+
|
|
3051
|
+
|
|
3052
|
+
def _openamp_subst(tail: tuple[str, ...], soc_spec: dict[str, Any]) -> list[str]:
|
|
3053
|
+
"""Fill the OpenAMP window tokens from the SoC's `openamp_carveout`
|
|
3054
|
+
(metadata/socs/**.json; the one declaration the Linux DT, the backend
|
|
3055
|
+
header and scripts/check_amp_window.py also read). Tokens:
|
|
3056
|
+
`@carveout.{addr,hex,size}@` for the whole reservation and, per name in
|
|
3057
|
+
`regions`, `@<name>.{addr,hex,size,end,a55,a55hex}@` -- addr/hex/end in
|
|
3058
|
+
the CM33-NS view, a55/a55hex in the A55 view."""
|
|
3059
|
+
c = _openamp_carveout(soc_spec)
|
|
3060
|
+
base, a55 = c["cm33_ns_base"], c["a55_base"]
|
|
3061
|
+
tok = {
|
|
3062
|
+
"@carveout.addr@": f"{base:#x}",
|
|
3063
|
+
"@carveout.hex@": f"{base:x}",
|
|
3064
|
+
"@carveout.size@": f"{c['size']:#x}",
|
|
3065
|
+
"@carveout.offset@": f"{base - a55:#x}",
|
|
3066
|
+
}
|
|
3067
|
+
for name, r in c["regions"].items():
|
|
3068
|
+
off, size = r["offset"], r["size"]
|
|
3069
|
+
tok.update({
|
|
3070
|
+
f"@{name}.addr@": f"{base + off:#x}",
|
|
3071
|
+
f"@{name}.hex@": f"{base + off:x}",
|
|
3072
|
+
f"@{name}.size@": f"{size:#x}",
|
|
3073
|
+
f"@{name}.end@": f"{base + off + size:#x}",
|
|
3074
|
+
f"@{name}.a55@": f"{a55 + off:#x}",
|
|
3075
|
+
f"@{name}.a55hex@": f"{a55 + off:x}",
|
|
3076
|
+
})
|
|
3077
|
+
out = []
|
|
3078
|
+
for line in tail:
|
|
3079
|
+
for k, v in tok.items():
|
|
3080
|
+
line = line.replace(k, v)
|
|
3081
|
+
out.append(line)
|
|
3082
|
+
return out
|
|
3083
|
+
|
|
3084
|
+
|
|
3085
|
+
def _ram_console(soc_spec: dict[str, Any]) -> tuple[int, int, int]:
|
|
3086
|
+
"""(A55 address, CM33-NS address, size) of the CM33 RAM console, from the
|
|
3087
|
+
SoC's `openamp_carveout.ram_console` (the one declaration; the dts node,
|
|
3088
|
+
CONFIG_RAM_CONSOLE_BUFFER_SIZE and scripts/gen_amp_window.py all read it)."""
|
|
3089
|
+
c = _openamp_carveout(soc_spec)
|
|
3090
|
+
off = c["ram_console"]["offset"]
|
|
3091
|
+
return c["a55_base"] + off, c["cm33_ns_base"] + off, c["ram_console"]["size"]
|
|
3092
|
+
|
|
3093
|
+
|
|
3094
|
+
def _v2n_dts(
|
|
3095
|
+
sku: str, dir_name: str, soc_spec: dict[str, Any], variant: dict[str, Any],
|
|
3096
|
+
sku_preset: dict[str, Any], links: dict[str, Any],
|
|
3097
|
+
assignable: list = (),
|
|
3098
|
+
) -> str:
|
|
3099
|
+
"""Board `.dts` for a V2N/V2M `m33_sm` board (#655 slice 2).
|
|
3100
|
+
|
|
3101
|
+
Reuses the upstream RZ/V2N SoC devicetree; adds the on-module GD32G553
|
|
3102
|
+
supervisor links from `supervisor-links.yaml` (the same source
|
|
3103
|
+
`_v2n_pinctrl_dtsi()` reads). Only SoMs whose
|
|
3104
|
+
`topology.m33_sm.openamp_ipc` preset field is true also get the
|
|
3105
|
+
OpenAMP/MHU-B reserved-memory block and the CAN-FD-unavailable
|
|
3106
|
+
analysis (alp-sdk #683/#1146) -- E1M-V2M101 does not carry either
|
|
3107
|
+
block in the currently-committed tree, so this generator reproduces
|
|
3108
|
+
that gap rather than "fixing" it; the byte-identity pin
|
|
3109
|
+
(`tests/scripts/test_gen_zephyr_board.py`) is against what is
|
|
3110
|
+
committed today, not against what a more "complete" V2M101 board
|
|
3111
|
+
would look like.
|
|
3112
|
+
"""
|
|
3113
|
+
slug = sku[len("E1M-"):].lower()
|
|
3114
|
+
compatible = f"alp,e1m-{slug}-m33-sm"
|
|
3115
|
+
model = f"Alp {sku} Cortex-M33 system manager"
|
|
3116
|
+
|
|
3117
|
+
part_display, soc_dtsi_include = _v2n_part_display(variant["order_code"])
|
|
3118
|
+
family = soc_spec.get("family")
|
|
3119
|
+
if not family or _is_tbd(family):
|
|
3120
|
+
raise ZephyrBoardEmitError(f"SoC spec {soc_spec.get('ref')} declares no usable `family`")
|
|
3121
|
+
family_display = f"{family} {part_display}"
|
|
3122
|
+
if (sku_preset.get("on_module") or {}).get("npu") == "deepx_dxm1":
|
|
3123
|
+
family_display += " + DEEPX DX-M1"
|
|
3124
|
+
|
|
3125
|
+
console = links["console"]
|
|
3126
|
+
gd32_spi = links["gd32_spi"]
|
|
3127
|
+
brd_i2c = links["brd_i2c"]
|
|
3128
|
+
wdt = _find_core(soc_spec, "m33_sm").get("watchdog") or {}
|
|
3129
|
+
if not wdt:
|
|
3130
|
+
raise ZephyrBoardEmitError(
|
|
3131
|
+
f"SoC spec {soc_spec.get('ref')} core m33_sm declares no `watchdog` "
|
|
3132
|
+
"block (base/size/counting_clock_hz) -- the V2N/V2M board emits the "
|
|
3133
|
+
"CM33 watchdog node and the alp-wdt0 alias from it")
|
|
3134
|
+
wdt_base = wdt["base"]
|
|
3135
|
+
txd0 = _pin_by_peripheral(console["pins"], "UART0_TXD0")
|
|
3136
|
+
rxd0 = _pin_by_peripheral(console["pins"], "UART0_RXD0")
|
|
3137
|
+
mosi = _pin_by_peripheral(gd32_spi["pins"], "GD32_SPI.MOSI")
|
|
3138
|
+
miso = _pin_by_peripheral(gd32_spi["pins"], "GD32_SPI.MISO")
|
|
3139
|
+
sclk = _pin_by_peripheral(gd32_spi["pins"], "GD32_SPI.SCLK")
|
|
3140
|
+
sda = _pin_by_peripheral(brd_i2c["pins"], "RIIC8_SDA8")
|
|
3141
|
+
scl = _pin_by_peripheral(brd_i2c["pins"], "RIIC8_SCL8")
|
|
3142
|
+
cs0 = gd32_spi["gpio_chip_select"]
|
|
3143
|
+
pads = links.get("gd32_pads") # optional: GD32 SWD + ATTN pads (alp,gd32-pads)
|
|
3144
|
+
peer_addr = brd_i2c["peer_address_7bit"]
|
|
3145
|
+
ch = _v2n_sci_channel(gd32_spi["dt_label"]) # "7"
|
|
3146
|
+
|
|
3147
|
+
has_openamp = bool(
|
|
3148
|
+
(sku_preset.get("topology") or {}).get("m33_sm", {}).get("openamp_ipc"))
|
|
3149
|
+
rc_a55, rc_addr, rc_size = _ram_console(soc_spec)
|
|
3150
|
+
carveout = _openamp_carveout(soc_spec)
|
|
3151
|
+
win_base, win_size = carveout["cm33_ns_base"], carveout["size"]
|
|
3152
|
+
|
|
3153
|
+
lines: list[str] = [
|
|
3154
|
+
"/*",
|
|
3155
|
+
" * Copyright (c) 2026 Alp Lab AB",
|
|
3156
|
+
" * SPDX-License-Identifier: Apache-2.0",
|
|
3157
|
+
" *",
|
|
3158
|
+
f" * {sku} ({family_display}) Cortex-M33 system-manager.",
|
|
3159
|
+
" *",
|
|
3160
|
+
" * Reuses the upstream RZ/V2N SoC devicetree + the EVK SCI_UART0 console and",
|
|
3161
|
+
" * adds the on-module GD32G553 supervisor links:",
|
|
3162
|
+
f" * - SCI{ch} Simple-SPI (alias {gd32_spi['alias']}): {mosi['silicon_pad']} MOSI{ch} / "
|
|
3163
|
+
f"{miso['silicon_pad']} MISO{ch} / {sclk['silicon_pad']} SCK{ch} +",
|
|
3164
|
+
f" * {cs0['silicon_pad']} GPIO chip-select (master SCI-SPI has no hardware slave-select)",
|
|
3165
|
+
f" * - RIIC8 / BRD_I2C: {sda['silicon_pad']} SDA / {scl['silicon_pad']} SCL "
|
|
3166
|
+
f"(GD32 slave @ 0x{peer_addr:02x}) -- Cortex-A55/Linux-exclusive,",
|
|
3167
|
+
" * NOT wired on this board: no alias, &i2c8 stays disabled below.",
|
|
3168
|
+
" *",
|
|
3169
|
+
" * The on-module silicon is the n44 variant; Zephyr only models the n48gbg",
|
|
3170
|
+
" * SoC, which is devicetree-identical for the M33 + SPI/I2C peripherals",
|
|
3171
|
+
" * (the n44/n48 delta is GPU/ISP/crypto fusing only).",
|
|
3172
|
+
" */",
|
|
3173
|
+
"",
|
|
3174
|
+
"/dts-v1/;",
|
|
3175
|
+
"",
|
|
3176
|
+
"#include <zephyr/dt-bindings/i2c/i2c.h>",
|
|
3177
|
+
"#include <zephyr/dt-bindings/gpio/gpio.h>",
|
|
3178
|
+
]
|
|
3179
|
+
if has_openamp:
|
|
3180
|
+
lines.append("#include <zephyr/dt-bindings/memory-attr/memory-attr-arm.h>")
|
|
3181
|
+
lines += [
|
|
3182
|
+
f"#include <{soc_dtsi_include}>",
|
|
3183
|
+
f'#include "{dir_name}-pinctrl.dtsi"',
|
|
3184
|
+
"",
|
|
3185
|
+
"/ {",
|
|
3186
|
+
f'\tmodel = "{model}";',
|
|
3187
|
+
f'\tcompatible = "{compatible}";',
|
|
3188
|
+
"",
|
|
3189
|
+
"\tchosen {",
|
|
3190
|
+
"\t\tzephyr,sram = &sram;",
|
|
3191
|
+
"\t\tzephyr,ram-console = &ram_console;",
|
|
3192
|
+
"\t\t/*",
|
|
3193
|
+
"\t\t * No CM33 serial console on this board. The only console is the",
|
|
3194
|
+
"\t\t * A55's (Linux, on the shared debug UART); sci0 here is the EVK",
|
|
3195
|
+
'\t\t * "Pmod USB-UART", which is NOT wired as a CM33 console on the SoM.',
|
|
3196
|
+
"\t\t * Bringing it up enables RX on a floating RXD, whose receive-error",
|
|
3197
|
+
"\t\t * interrupt (sci0 eri = NVIC 114) escalates to a Zephyr fatal",
|
|
3198
|
+
"\t\t * (arch_system_halt) and hangs the CM33 before main() ever runs.",
|
|
3199
|
+
"\t\t * Leave the UART console unset (sci0 is disabled below); only the",
|
|
3200
|
+
"\t\t * RAM console is chosen. Re-add zephyr,console/zephyr,shell-uart and",
|
|
3201
|
+
"\t\t * re-enable sci0 only when a Pmod USB-UART is attached.",
|
|
3202
|
+
"\t\t */",
|
|
3203
|
+
"\t};",
|
|
3204
|
+
"",
|
|
3205
|
+
"\taliases {",
|
|
3206
|
+
f"\t\t{gd32_spi['alias']} = &gd32_spi;",
|
|
3207
|
+
"\t\talp-wdt0 = &wdt0;",
|
|
3208
|
+
"\t};",
|
|
3209
|
+
"",
|
|
3210
|
+
"\tsram: memory@8003000 {",
|
|
3211
|
+
'\t\tcompatible = "mmio-sram";',
|
|
3212
|
+
"\t\treg = <0x08003000 0xfbfff>;",
|
|
3213
|
+
"\t};",
|
|
3214
|
+
"",
|
|
3215
|
+
"\t/*",
|
|
3216
|
+
f"\t * RAM console buffer (audit CM33-M2): {rc_size // 1024} KiB at CM33-NS {rc_addr:#x} (A55",
|
|
3217
|
+
f"\t * view {rc_a55:#x}), from the SoC's openamp_carveout.ram_console: inside the",
|
|
3218
|
+
f"\t * {win_size // 1048576} MiB {win_base:#x} window Linux reserves no-map and clear of every",
|
|
3219
|
+
"\t * region listed there (rsctbl with the liveness beacon in its top 16",
|
|
3220
|
+
"\t * bytes, mhu-shm, the vring control and buffer areas).",
|
|
3221
|
+
"\t */",
|
|
3222
|
+
f"\tram_console: memory@{rc_addr:x} {{",
|
|
3223
|
+
'\t\tcompatible = "zephyr,memory-region";',
|
|
3224
|
+
'\t\tzephyr,memory-region = "RAM_CONSOLE";',
|
|
3225
|
+
f"\t\treg = <{rc_addr:#x} {rc_size:#x}>;",
|
|
3226
|
+
"\t};",
|
|
3227
|
+
"",
|
|
3228
|
+
"\t/*",
|
|
3229
|
+
"\t * alp pin map for alp_spi/alp_gpio id resolution. alp_spi_open(cs_pin_id=N)",
|
|
3230
|
+
"\t * resolves its chip-select gpio_dt_spec from gpios[N] of this node (see the",
|
|
3231
|
+
"\t * SPI backend's alp_z_gpio_resolve()). Index 0 = the GD32 SPI chip-select",
|
|
3232
|
+
f"\t * on {cs0['silicon_pad']}, matching the example's cs_pin_id = 0.",
|
|
3233
|
+
]
|
|
3234
|
+
lines += [
|
|
3235
|
+
"\t */",
|
|
3236
|
+
"\talp_pins: alp-pins {",
|
|
3237
|
+
'\t\tcompatible = "alp,pin-array";',
|
|
3238
|
+
f"\t\tgpios = <&{cs0['gpio_node']} {cs0['gpio_pin']} GPIO_ACTIVE_LOW>;",
|
|
3239
|
+
"\t};",
|
|
3240
|
+
]
|
|
3241
|
+
if pads:
|
|
3242
|
+
|
|
3243
|
+
def pol(pad: dict[str, Any]) -> str:
|
|
3244
|
+
return "GPIO_ACTIVE_LOW" if pad["active_low"] else "GPIO_ACTIVE_HIGH"
|
|
3245
|
+
|
|
3246
|
+
def spec(role: str) -> str:
|
|
3247
|
+
pad = pads[role]
|
|
3248
|
+
return f"<&{pad['gpio_node']} {pad['gpio_pin']} {pol(pad)}>"
|
|
3249
|
+
|
|
3250
|
+
lines += [
|
|
3251
|
+
"",
|
|
3252
|
+
"\t/*",
|
|
3253
|
+
"\t * The GD32 control pads outside the SPI link (protocol v0.15, section 3.17).",
|
|
3254
|
+
"\t * A DEDICATED node, deliberately not entries of alp_pins above (whose index 0",
|
|
3255
|
+
"\t * is the GD32 SPI chip-select): resolved only through the INTERNAL pad opener",
|
|
3256
|
+
"\t * (the portable alp_gpio_open() refuses GD32G553_PAD_ID_*). All four are plain GPIO, inputs out of",
|
|
3257
|
+
"\t * reset. swclk and attn are the SAME pad (P71 = GD32 PA14): the host drives it",
|
|
3258
|
+
"\t * only inside an SWD session, with GD32_NRST asserted first, and otherwise uses",
|
|
3259
|
+
"\t * it as the bridge ATTN input with a rising-edge IRQ. nrst is open-drain on the",
|
|
3260
|
+
"\t * board; the Renesas GPIO driver has no open-drain mode, so the SWD driver",
|
|
3261
|
+
"\t * emulates it (low = assert, input = release) and never drives the pad high.",
|
|
3262
|
+
"\t */",
|
|
3263
|
+
"\tgd32_pads: gd32-pads {",
|
|
3264
|
+
'\t\tcompatible = "alp,gd32-pads";',
|
|
3265
|
+
f"\t\tswdio-gpios = {spec('swdio')};",
|
|
3266
|
+
f"\t\tswclk-gpios = {spec('swclk')};",
|
|
3267
|
+
f"\t\tnrst-gpios = {spec('nrst')};",
|
|
3268
|
+
f"\t\tattn-gpios = {spec('attn')};",
|
|
3269
|
+
"\t};",
|
|
3270
|
+
]
|
|
3271
|
+
lines += [
|
|
3272
|
+
"};",
|
|
3273
|
+
"",
|
|
3274
|
+
"/*",
|
|
3275
|
+
f' * {console["dt_label"]} = EVK "Pmod USB-UART" console. DISABLED on this board: it is not wired',
|
|
3276
|
+
" * as a CM33 console here, and opening it (RX enabled on a floating RXD) faults",
|
|
3277
|
+
" * the CM33 in its receive-error ISR (sci0 eri / NVIC 114) -> Zephyr fatal ->",
|
|
3278
|
+
" * arch_system_halt, before the app runs. Keep pinctrl for easy re-enable.",
|
|
3279
|
+
" */",
|
|
3280
|
+
f"&{console['dt_label']} {{",
|
|
3281
|
+
f"\tpinctrl-0 = <&{console['pinctrl_group_label']}>;",
|
|
3282
|
+
'\tpinctrl-names = "default";',
|
|
3283
|
+
'\tstatus = "disabled";',
|
|
3284
|
+
"",
|
|
3285
|
+
"\tuart0: uart {",
|
|
3286
|
+
"\t\tcurrent-speed = <115200>;",
|
|
3287
|
+
'\t\tstatus = "disabled";',
|
|
3288
|
+
"\t};",
|
|
3289
|
+
"};",
|
|
3290
|
+
"",
|
|
3291
|
+
"/*",
|
|
3292
|
+
f" * GD32 supervisor SPI fast path -- SCI channel {ch} in clock-synchronous",
|
|
3293
|
+
' * "Simple SPI" mode (sci7@42802800, compatible renesas,rz-sci-b). The board',
|
|
3294
|
+
f" * wires the GD32 to {mosi['silicon_pad']}/{miso['silicon_pad']}/{sclk['silicon_pad']}/{cs0['silicon_pad']}, "
|
|
3295
|
+
f"which per the RZ/V2N PFC (Table 1.2-3) are",
|
|
3296
|
+
f" * MOSI{ch}/MISO{ch}/SCK{ch}/SS{ch} = SCI channel {ch} -- NOT the dedicated SPI_B IP. The sci{ch}",
|
|
3297
|
+
" * parent (from the SoC dtsi) already supplies reg, channel = <7> and the fixed",
|
|
3298
|
+
" * CM33 vectors (eri=156, rxi=157, txi=158, tei=159); here we enable it, point it",
|
|
3299
|
+
" * at the SCI7 SPI pins, and attach the SPI child.",
|
|
3300
|
+
" *",
|
|
3301
|
+
" * Master Simple-SPI has NO hardware slave-select (the FSP sets CCR0.SSE only in",
|
|
3302
|
+
f" * slave mode), so the GD32 chip-select on {cs0['silicon_pad']} is driven as a GPIO via cs-gpios",
|
|
3303
|
+
f" * ({cs0['gpio_node']} pin {cs0['gpio_pin']}) -- the GD32's PA8 CS-EXTI needs per-transaction framing.",
|
|
3304
|
+
" */",
|
|
3305
|
+
f"&{gd32_spi['dt_label']} {{",
|
|
3306
|
+
f"\tpinctrl-0 = <&{gd32_spi['pinctrl_group_label']}>;",
|
|
3307
|
+
'\tpinctrl-names = "default";',
|
|
3308
|
+
'\tstatus = "okay";',
|
|
3309
|
+
"",
|
|
3310
|
+
"\tgd32_spi: spi {",
|
|
3311
|
+
'\t\tcompatible = "renesas,rz-sci-b-spi";',
|
|
3312
|
+
"\t\t#address-cells = <1>;",
|
|
3313
|
+
"\t\t#size-cells = <0>;",
|
|
3314
|
+
f"\t\tcs-gpios = <&{cs0['gpio_node']} {cs0['gpio_pin']} GPIO_ACTIVE_LOW>;",
|
|
3315
|
+
'\t\tstatus = "okay";',
|
|
3316
|
+
"\t};",
|
|
3317
|
+
"};",
|
|
3318
|
+
"",
|
|
3319
|
+
f"&{cs0['gpio_node']} {{",
|
|
3320
|
+
'\tstatus = "okay";',
|
|
3321
|
+
"};",
|
|
3322
|
+
"",
|
|
3323
|
+
]
|
|
3324
|
+
if pads and pads["attn"].get("tint_slot") is not None and pads["swdio"]["gpio_node"] == cs0["gpio_node"]:
|
|
3325
|
+
raise SystemExit("gd32_pads.attn.tint_slot set but the pads block is skipped (swdio shares CS0's gpio node)")
|
|
3326
|
+
if pads and pads["swdio"]["gpio_node"] != cs0["gpio_node"]:
|
|
3327
|
+
tint = pads["attn"].get("tint_slot")
|
|
3328
|
+
if tint is not None:
|
|
3329
|
+
if pads["attn"]["gpio_node"] != pads["swdio"]["gpio_node"]:
|
|
3330
|
+
raise SystemExit("gd32_pads.attn.tint_slot needs attn on the swdio gpio node")
|
|
3331
|
+
lines += [
|
|
3332
|
+
"/*",
|
|
3333
|
+
" * ATTN (P71) interrupt: the RZ GPIO driver routes a pin to a TINT slot through",
|
|
3334
|
+
" * the port's `irqs`; without it alp_gpio_irq_enable() has no route and the",
|
|
3335
|
+
" * supervisor stays on the staging-gap fallback. The slot is a shared ICU",
|
|
3336
|
+
" * resource, claimed here for the CM33 (supervisor-links.yaml gd32_pads.attn).",
|
|
3337
|
+
" */",
|
|
3338
|
+
f"&tint{tint} {{",
|
|
3339
|
+
'\tstatus = "okay";',
|
|
3340
|
+
"};",
|
|
3341
|
+
"",
|
|
3342
|
+
]
|
|
3343
|
+
lines += [f"&{pads['swdio']['gpio_node']} {{", '\tstatus = "okay";']
|
|
3344
|
+
if tint is not None:
|
|
3345
|
+
lines.append(f"\tirqs = <&tint{tint} {pads['attn']['gpio_pin']}>;")
|
|
3346
|
+
lines += ["};", ""]
|
|
3347
|
+
lines += [
|
|
3348
|
+
"/*",
|
|
3349
|
+
f" * {brd_i2c['peripheral']} / BRD_I2C is Cortex-A55/Linux-exclusive",
|
|
3350
|
+
" * (metadata/e1m_modules/v2n/core-ownership.yaml) -- the CM33 must never",
|
|
3351
|
+
" * master it, so this node stays disabled and carries no alias (no",
|
|
3352
|
+
" * alp_i2c_open() bus_id resolves to it on this board). The pinctrl group",
|
|
3353
|
+
f" * ({brd_i2c['pinctrl_group_label']} in {dir_name}-pinctrl.dtsi) is kept for",
|
|
3354
|
+
" * reference only -- an unreferenced group claims no pin.",
|
|
3355
|
+
" */",
|
|
3356
|
+
f"&{brd_i2c['dt_label']} {{",
|
|
3357
|
+
'\tstatus = "disabled";',
|
|
3358
|
+
"};",
|
|
3359
|
+
"",
|
|
3360
|
+
] + _v2n_assignable_dts(assignable) + [
|
|
3361
|
+
"/*",
|
|
3362
|
+
f" * wdt0 = the Cortex-M33's own watchdog (base {wdt_base} and counting clock",
|
|
3363
|
+
" * from the SoC spec's m33_sm `watchdog` block; hal_renesas R9A09G056N",
|
|
3364
|
+
" * wdt_iodefine.h R_WDT0_BASE). The upstream SoC devicetree",
|
|
3365
|
+
" * (arm/renesas/rz/rzv/r9a09g056.dtsi) declares no watchdog, and upstream's",
|
|
3366
|
+
" * `renesas,rz-wdt` driver cannot bind RZ/V2N, so the node uses the alp-sdk",
|
|
3367
|
+
" * driver zephyr/drivers/watchdog/wdt_renesas_rzv.c (compatible",
|
|
3368
|
+
" * `renesas,rzv-wdt`; alp-sdk#2660). DISABLED by default: an expiry resets the",
|
|
3369
|
+
" * WHOLE SoM, not just the M33, so a product opts in with `&wdt0 { status =",
|
|
3370
|
+
' * "okay"; };`. <alp/wdt.h> then reaches it through alias alp-wdt0. Enabling it',
|
|
3371
|
+
" * also needs the CPG module clocks held for the CM33 (renesas,cm33-owned-clocks,",
|
|
3372
|
+
" * docs/bench/rzv-wdt0-cm33.md). clock-freq is the WDT0 counting clock",
|
|
3373
|
+
" * (WDT_0_clk_loco, 24 MHz Main OSC; RZ/V2N hardware manual Table 4.4-2).",
|
|
3374
|
+
" */",
|
|
3375
|
+
"&{/soc} {",
|
|
3376
|
+
f"\twdt0: watchdog@{wdt_base[2:]} {{",
|
|
3377
|
+
'\t\tcompatible = "renesas,rzv-wdt";',
|
|
3378
|
+
f"\t\treg = <{wdt_base} {wdt['size']}>;",
|
|
3379
|
+
f"\t\tclock-freq = <{wdt['counting_clock_hz']}>;",
|
|
3380
|
+
'\t\tstatus = "disabled";',
|
|
3381
|
+
"\t};",
|
|
3382
|
+
"};",
|
|
3383
|
+
]
|
|
3384
|
+
if has_openamp:
|
|
3385
|
+
lines += _openamp_subst(_V2N_OPENAMP_TAIL, soc_spec)
|
|
3386
|
+
lines.append("")
|
|
3387
|
+
return "\n".join(lines)
|
|
3388
|
+
|
|
3389
|
+
|
|
3390
|
+
# ---------------------------------------------------------------------
|
|
3391
|
+
# Top-level entry point
|
|
3392
|
+
# ---------------------------------------------------------------------
|
|
3393
|
+
|
|
3394
|
+
|
|
3395
|
+
def emit_zephyr_board(
|
|
3396
|
+
sku: str, core_id: str, metadata_root: Path,
|
|
3397
|
+
) -> dict[str, str]:
|
|
3398
|
+
"""Return {relative_path: content} for the requested SoM+core's board tree.
|
|
3399
|
+
|
|
3400
|
+
*relative_path* is rooted at the board directory (e.g.
|
|
3401
|
+
``"alp_e1m_aen801_m55_hp/board.yml"``) so the caller can write the
|
|
3402
|
+
result straight under a `--board-root`-style output directory.
|
|
3403
|
+
"""
|
|
3404
|
+
sku_preset = _resolve_sku(sku, metadata_root)
|
|
3405
|
+
soc_spec = _load_soc_spec(sku_preset, metadata_root)
|
|
3406
|
+
variant = _resolve_variant(sku_preset, soc_spec)
|
|
3407
|
+
core = _find_core(soc_spec, core_id)
|
|
3408
|
+
dir_name, cluster, variant_tag, basename = _board_naming(
|
|
3409
|
+
sku_preset, core_id, variant, core)
|
|
3410
|
+
topo = _topology_entry(sku_preset, core_id)
|
|
3411
|
+
full_name = topo.get("zephyr_full_name")
|
|
3412
|
+
if not full_name:
|
|
3413
|
+
raise ZephyrBoardEmitError(
|
|
3414
|
+
f"topology.{core_id}.zephyr_full_name is required for "
|
|
3415
|
+
f"--emit zephyr-board (SoM {sku!r}); add it to "
|
|
3416
|
+
f"metadata/e1m_modules/{sku}.yaml")
|
|
3417
|
+
twister_name = topo.get("zephyr_twister_name") or full_name
|
|
3418
|
+
family = _sku_family_slug(sku)
|
|
3419
|
+
|
|
3420
|
+
files: dict[str, str] = {
|
|
3421
|
+
f"{dir_name}/board.yml": _board_yml(dir_name, full_name, variant_tag, family),
|
|
3422
|
+
f"{dir_name}/Kconfig.{dir_name}": _kconfig_board(dir_name, variant_tag, cluster),
|
|
3423
|
+
f"{dir_name}/{basename}.yaml": _twister_yaml(
|
|
3424
|
+
dir_name, variant_tag, cluster, twister_name, family,
|
|
3425
|
+
sku_preset, core_id, variant),
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
# relpath -> the extra generation-authority file its banner must name.
|
|
3429
|
+
banner_extra_source: dict[str, str] = {}
|
|
3430
|
+
if family == "aen":
|
|
3431
|
+
rx_row, tx_row = _aen_console_pinmux_rows(metadata_root)
|
|
3432
|
+
role = core_id.split("_")[-1]
|
|
3433
|
+
uart_node = _uart_node_label(rx_row)
|
|
3434
|
+
memory_map = sku_preset.get("memory_map")
|
|
3435
|
+
_aen_require_disjoint_slot0(sku, sku_preset, memory_map)
|
|
3436
|
+
# This role's own disjoint slot0 base (#1069), falling back to the
|
|
3437
|
+
# stock symmetric-layout address (the App MRAM base + the mcuboot
|
|
3438
|
+
# partition, same for every single-M55 AEN SKU and for AEN801
|
|
3439
|
+
# boards with no memory_map override) when this SoM declares no
|
|
3440
|
+
# per-role `<role>_slot0` region -- see _aen_role_slot0_map /
|
|
3441
|
+
# _aen_flash_partitions.
|
|
3442
|
+
slot0_region = _aen_role_slot0_map(memory_map, role)
|
|
3443
|
+
slot0_base = (slot0_region["base"] if slot0_region
|
|
3444
|
+
else _AEN_MRAM_BASE + _AEN_MCUBOOT_KIB * 1024)
|
|
3445
|
+
# The on-module (non-edge) pad + device source: BRD_I2C's P7_0/P7_1 and
|
|
3446
|
+
# the RV-3028 alarm on P15_0 reach no E1M edge pin, so metadata/pinmux/
|
|
3447
|
+
# aen.yaml (an edge-pad projection) has no row for them -- see
|
|
3448
|
+
# _load_aen_on_module_links().
|
|
3449
|
+
on_module_links = _load_aen_on_module_links(metadata_root)
|
|
3450
|
+
aen_pinctrl_relpath = f"{dir_name}/{dir_name}-pinctrl.dtsi"
|
|
3451
|
+
aen_dts_relpath = f"{dir_name}/{basename}.dts"
|
|
3452
|
+
files[aen_pinctrl_relpath] = _aen_pinctrl_dtsi(
|
|
3453
|
+
role, sku, rx_row, tx_row, _aen_family_display(soc_spec),
|
|
3454
|
+
on_module_links, _aen_part(soc_spec))
|
|
3455
|
+
files[f"{dir_name}/{basename}_defconfig"] = _aen_defconfig(
|
|
3456
|
+
uart_node, rx_row, tx_row, slot0_base)
|
|
3457
|
+
files[f"{dir_name}/Kconfig.defconfig"] = _aen_kconfig_defconfig(
|
|
3458
|
+
dir_name, role, _aen_part(soc_spec))
|
|
3459
|
+
files[aen_dts_relpath] = _aen_dts(
|
|
3460
|
+
sku, sku_preset, core_id, soc_spec, variant, dir_name, basename,
|
|
3461
|
+
rx_row, tx_row, metadata_root, on_module_links,
|
|
3462
|
+
_aen_ethos_u(soc_spec), memory_map,
|
|
3463
|
+
_load_aen_power_domains(metadata_root),
|
|
3464
|
+
_load_aen_wired_lpgpio_pads(metadata_root))
|
|
3465
|
+
banner_extra_source.update(dict.fromkeys(
|
|
3466
|
+
(aen_pinctrl_relpath, aen_dts_relpath),
|
|
3467
|
+
"metadata/e1m_modules/aen/on-module-links.yaml"))
|
|
3468
|
+
elif family in _v2n_declared_families(metadata_root):
|
|
3469
|
+
# `Kconfig.defconfig` stays hand-authored in this slice (it does
|
|
3470
|
+
# not exist in the V2N/V2M board directories); the board `.dts`
|
|
3471
|
+
# does not -- #655 slice 2 closed that gap (alp-sdk e4f2f60e4).
|
|
3472
|
+
# Which families this branch covers is read from
|
|
3473
|
+
# metadata/e1m_modules/v2n/supervisor-links.yaml's own
|
|
3474
|
+
# `families:` list, not a local constant -- dropping `v2n-m1`
|
|
3475
|
+
# there stops V2M emission without a code change.
|
|
3476
|
+
supervisor_links = _load_supervisor_links(metadata_root)
|
|
3477
|
+
v2n_pinctrl_relpath = f"{dir_name}/{dir_name}-pinctrl.dtsi"
|
|
3478
|
+
v2n_defconfig_relpath = f"{dir_name}/{basename}_defconfig"
|
|
3479
|
+
v2n_dts_relpath = f"{dir_name}/{basename}.dts"
|
|
3480
|
+
assignable = _v2n_assignable_m33(metadata_root, soc_spec)
|
|
3481
|
+
files[v2n_pinctrl_relpath] = _v2n_pinctrl_dtsi(supervisor_links, assignable)
|
|
3482
|
+
files[v2n_defconfig_relpath] = _v2n_defconfig(supervisor_links, soc_spec)
|
|
3483
|
+
files[v2n_dts_relpath] = _v2n_dts(
|
|
3484
|
+
sku, dir_name, soc_spec, variant, sku_preset, supervisor_links,
|
|
3485
|
+
assignable)
|
|
3486
|
+
# None of the three files carries a single pad, PFC triple or I2C
|
|
3487
|
+
# address from sku_rel/soc_json_rel below -- every one of those
|
|
3488
|
+
# facts comes from supervisor-links.yaml, so its path is threaded
|
|
3489
|
+
# into all three files' banners too (below), not just the
|
|
3490
|
+
# family-agnostic ones.
|
|
3491
|
+
banner_extra_source.update(dict.fromkeys(
|
|
3492
|
+
(v2n_pinctrl_relpath, v2n_defconfig_relpath, v2n_dts_relpath),
|
|
3493
|
+
"metadata/e1m_modules/v2n/supervisor-links.yaml"))
|
|
3494
|
+
|
|
3495
|
+
# `_load_soc_spec()` above already raised ZephyrBoardEmitError if
|
|
3496
|
+
# `sku_preset["silicon"]` didn't resolve, so `soc_path` can't be None
|
|
3497
|
+
# here -- but that's a dependency on call order, not a guarantee this
|
|
3498
|
+
# function itself enforces, so don't drop the None-guard idiom.
|
|
3499
|
+
soc_path = resolve_soc_path(sku_preset["silicon"], metadata_root)
|
|
3500
|
+
if soc_path is None:
|
|
3501
|
+
raise ZephyrBoardEmitError(
|
|
3502
|
+
f"silicon ref {sku_preset['silicon']!r} is not a triple-colon string")
|
|
3503
|
+
soc_json_rel = f"metadata/{soc_path.relative_to(metadata_root).as_posix()}"
|
|
3504
|
+
for relpath in list(files):
|
|
3505
|
+
style = "c" if relpath.endswith((".dts", "-pinctrl.dtsi")) else "hash"
|
|
3506
|
+
extra_source = banner_extra_source.get(relpath)
|
|
3507
|
+
files[relpath] = _with_generated_banner(
|
|
3508
|
+
files[relpath], style, sku, soc_json_rel, extra_source)
|
|
3509
|
+
|
|
3510
|
+
return files
|