diff --git a/soc/nordic/nrf54h/CMakeLists.txt b/soc/nordic/nrf54h/CMakeLists.txt index 10c59b04583..fb17418cb45 100644 --- a/soc/nordic/nrf54h/CMakeLists.txt +++ b/soc/nordic/nrf54h/CMakeLists.txt @@ -18,4 +18,5 @@ zephyr_library_sources_ifdef(CONFIG_SOC_NRF54H20_NO_MRAM_LATENCY mram.c) # for the image correctly zephyr_linker_sources(SECTIONS SORT_KEY zzz_place_align_at_end align.ld) +add_subdirectory(bicr) add_subdirectory(gpd) diff --git a/soc/nordic/nrf54h/Kconfig b/soc/nordic/nrf54h/Kconfig index 3e46ce83077..f5af731a2f6 100644 --- a/soc/nordic/nrf54h/Kconfig +++ b/soc/nordic/nrf54h/Kconfig @@ -64,6 +64,7 @@ config SOC_NRF54H20_CPUPPR config SOC_NRF54H20_CPUFLPR depends on RISCV_CORE_NORDIC_VPR +rsource "bicr/Kconfig" rsource "gpd/Kconfig" config SOC_NRF54H20_NO_MRAM_LATENCY diff --git a/soc/nordic/nrf54h/bicr/CMakeLists.txt b/soc/nordic/nrf54h/bicr/CMakeLists.txt new file mode 100644 index 00000000000..a93e36abbd2 --- /dev/null +++ b/soc/nordic/nrf54h/bicr/CMakeLists.txt @@ -0,0 +1,21 @@ +if(CONFIG_SOC_NRF54H20_GENERATE_BICR) + set(bicr_json_file ${BOARD_DIR}/bicr.json) + set(bicr_hex_file ${PROJECT_BINARY_DIR}/bicr.hex) + set(svd_file ${ZEPHYR_HAL_NORDIC_MODULE_DIR}/nrfx/mdk/nrf54h20_application.svd) + + if(EXISTS ${bicr_json_file}) + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${bicr_json_file}) + + execute_process( + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_LIST_DIR}/bicrgen.py + --svd ${svd_file} + --input ${bicr_json_file} + --output ${bicr_hex_file} + WORKING_DIRECTORY ${BOARD_DIR} + COMMAND_ERROR_IS_FATAL ANY + ) + message(STATUS "Generated BICR hex file: ${bicr_hex_file}") + endif() +endif() diff --git a/soc/nordic/nrf54h/bicr/Kconfig b/soc/nordic/nrf54h/bicr/Kconfig new file mode 100644 index 00000000000..a1ba7d2400c --- /dev/null +++ b/soc/nordic/nrf54h/bicr/Kconfig @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Nordic Semiconductor +# SPDX-License-Identifier: Apache-2.0 + +config SOC_NRF54H20_GENERATE_BICR + bool "Generate nRF54H20 BICR file" + depends on SOC_NRF54H20_CPUAPP + default y + help + This option generates a BICR file for the board being used. Board + directory must contain a "bicr.json" file for this option to work. diff --git a/soc/nordic/nrf54h/bicr/bicr-schema.json b/soc/nordic/nrf54h/bicr/bicr-schema.json new file mode 100644 index 00000000000..28eb208af3e --- /dev/null +++ b/soc/nordic/nrf54h/bicr/bicr-schema.json @@ -0,0 +1,422 @@ +{ + "title": "nRF54H20 BICR Configuration", + "type": "object", + "properties": { + "power": { + "type": "object", + "title": "Power supply configuration", + "properties": { + "scheme": { + "type": "string", + "title": "Power supply scheme", + "enumNames": [ + "Unconfigured (system will not boot)", + "VDDH supplied with 2.1-5.5 V and VDD regulated by the chip (inductor present)", + "Both VDD and VDDH supplied with 1.8 V (inductor present)" + ], + "enum": [ + "UNCONFIGURED", + "VDD_VDDH_1V8", + "VDDH_2V1_5V5" + ], + "default": "UNCONFIGURED" + } + }, + "required": [ + "scheme" + ] + }, + "ioPortPower": { + "type": "object", + "title": "IO port power configuration", + "properties": { + "p1Supply": { + "type": "string", + "title": "P1 power supply (VDDIO_P1)", + "enumNames": [ + "Not supplied (P1 not used)", + "VDDIO_P1 connected to an external 1.8 V supply or to VDD_EXT", + "VDDIO_P1 shorted to VDD" + ], + "enum": [ + "DISCONNECTED", + "EXTERNAL_1V8", + "SHORTED" + ], + "default": "DISCONNECTED" + }, + "p2Supply": { + "type": "string", + "title": "P2 power supply (VDDIO_P2)", + "enumNames": [ + "Not supplied (P2 not used)", + "VDDIO_P2 connected to an external 1.8 V supply or to VDD_EXT", + "VDDIO_P2 shorted to VDD" + ], + "enum": [ + "DISCONNECTED", + "EXTERNAL_1V8", + "SHORTED" + ], + "default": "DISCONNECTED" + }, + "p6Supply": { + "type": "string", + "title": "P6 power supply (VDDIO_P6)", + "enumNames": [ + "Not supplied (P6 not used)", + "VDDIO_P6 connected to an external 1.8 V supply or to VDD_EXT", + "VDDIO_P6 shorted to VDD" + ], + "enum": [ + "DISCONNECTED", + "EXTERNAL_1V8", + "SHORTED" + ], + "default": "DISCONNECTED" + }, + "p7Supply": { + "type": "string", + "title": "P7 power supply (VDDIO_P7)", + "enumNames": [ + "Not supplied (P7 not used)", + "VDDIO_P7 connected to an external 1.8 V supply or to VDD_EXT", + "VDDIO_P7 shorted to VDD" + ], + "enum": [ + "DISCONNECTED", + "EXTERNAL_1V8", + "SHORTED" + ], + "default": "DISCONNECTED" + }, + "p9Supply": { + "type": "string", + "title": "P9 power supply (VDDIO_P9)", + "enumNames": [ + "Not supplied (P9 not used)", + "VDDIO_P9 connected to an external 1.8 V supply or to VDD_EXT", + "VDDIO_P9 connected to an external supply (above 1.8 V, up to 3 V)", + "VDDIO_P9 shorted to VDD" + ], + "enum": [ + "DISCONNECTED", + "EXTERNAL_1V8", + "EXTERNAL_FULL", + "SHORTED" + ], + "default": "EXTERNAL_FULL" + } + }, + "required": [ + "p1Supply", + "p2Supply", + "p6Supply", + "p7Supply", + "p9Supply" + ] + }, + "ioPortImpedance": { + "type": "object", + "title": "IO port impedance configuration", + "properties": { + "p6ImpedanceOhms": { + "type": "number", + "title": "P6 impedance", + "enum": [ + 33, + 40, + 50, + 66, + 100 + ], + "default": 50 + }, + "p7ImpedanceOhms": { + "type": "number", + "title": "P7 impedance", + "enum": [ + 33, + 40, + 50, + 66, + 100 + ], + "default": 50 + } + }, + "required": [ + "p6ImpedanceOhms", + "p7ImpedanceOhms" + ] + }, + "lfosc": { + "type": "object", + "title": "Low Frequency Oscillator (LFOSC) configuration", + "properties": { + "source": { + "type": "string", + "title": "Source", + "enumNames": [ + "Low Frequency Crystal Oscillator (LFXO)", + "Low Frequency RC Oscillator (LFRC)" + ], + "enum": [ + "LFXO", + "LFRC" + ], + "default": "LFXO" + } + }, + "required": [ + "source" + ], + "dependencies": { + "source": { + "oneOf": [ + { + "properties": { + "source": { + "const": "LFXO" + }, + "lfxo": { + "type": "object", + "title": "Low Frequency Crystal Oscillator (LFXO) configuration", + "properties": { + "mode": { + "type": "string", + "title": "Mode", + "enumNames": [ + "Crystal", + "External sine signal", + "External square signal" + ], + "enum": [ + "CRYSTAL", + "ETX_SINE", + "EXT_SQUARE" + ], + "default": "CRYSTAL" + }, + "accuracyPPM": { + "type": "number", + "title": "Accuracy", + "enum": [ + 20, + 30, + 50, + 75, + 100, + 150, + 250, + 500 + ], + "default": 20 + }, + "startupTimeMs": { + "type": "number", + "title": "Startup time", + "minimum": 0, + "maximum": 4094, + "multipleOf": 1, + "default": 600 + } + }, + "required": [ + "mode", + "accuracyPPM", + "startupTimeMs" + ], + "dependencies": { + "mode": { + "oneOf": [ + { + "properties": { + "mode": { + "const": "CRYSTAL" + }, + "builtInLoadCapacitors": { + "type": "boolean", + "title": "Use built-in load capacitors", + "default": true + } + }, + "required": [ + "builtInLoadCapacitors" + ], + "dependencies": { + "builtInLoadCapacitors": { + "oneOf": [ + { + "properties": { + "builtInLoadCapacitors": { + "const": true + }, + "builtInLoadCapacitancePf": { + "type": "integer", + "title": "Built-in load capacitance", + "minimum": 1, + "maximum": 25, + "multipleOf": 1, + "default": 15 + } + }, + "required": [ + "builtInLoadCapacitancePf" + ] + } + ] + } + } + } + ] + } + } + } + } + }, + { + "properties": { + "source": { + "const": "LFRC" + }, + "lfrccal": { + "type": "object", + "title": "Low Frequency RC (LFRC) autocalibration configuration", + "properties": { + "calibrationEnabled": { + "type": "boolean", + "title": "Enable autocalibration", + "default": true + } + }, + "required": [ + "calibrationEnabled" + ], + "dependencies": { + "calibrationEnabled": { + "oneOf": [ + { + "properties": { + "calibrationEnabled": { + "const": true + }, + "tempMeasIntervalSeconds": { + "type": "number", + "title": "Temperature measurement interval", + "minimum": 0.25, + "maximum": 31.75, + "multipleOf": 0.25, + "default": 4 + }, + "tempDeltaCalibrationTriggerCelsius": { + "type": "number", + "title": "Temperature delta that should trigger calibration", + "minimum": 0.25, + "maximum": 31.75, + "multipleOf": 0.25, + "default": 0.5 + }, + "maxMeasIntervalBetweenCalibrations": { + "type": "number", + "title": "Maximum number of measurement intervals between calibrations", + "minimum": 0, + "maximum": 31, + "multipleOf": 1, + "default": 2 + } + }, + "required": [ + "tempMeasIntervalSeconds", + "tempDeltaCalibrationTriggerCelsius", + "maxMeasIntervalBetweenCalibrations" + ] + } + ] + } + } + } + } + } + ] + } + } + }, + "hfxo": { + "type": "object", + "title": "High Frequency Cristal Oscillator (HFXO) configuration", + "properties": { + "mode": { + "type": "string", + "title": "Mode", + "enumNames": [ + "Crystal", + "External square signal" + ], + "enum": [ + "CRYSTAL", + "EXT_SQUARE" + ], + "default": "CRYSTAL" + }, + "startupTimeUs": { + "type": "number", + "title": "Startup time", + "minimum": 0, + "maximum": 4294967294, + "multipleOf": 1, + "default": 850 + } + }, + "required": [ + "mode", + "startupTimeUs" + ], + "dependencies": { + "mode": { + "oneOf": [ + { + "properties": { + "mode": { + "const": "CRYSTAL" + }, + "builtInLoadCapacitors": { + "type": "boolean", + "title": "Use built-in load capacitors", + "default": true + } + }, + "required": [ + "builtInLoadCapacitors" + ], + "dependencies": { + "builtInLoadCapacitors": { + "oneOf": [ + { + "properties": { + "builtInLoadCapacitors": { + "const": true + }, + "builtInLoadCapacitancePf": { + "type": "number", + "title": "Built-in load capacitance", + "minimum": 0.25, + "maximum": 25.75, + "multipleOf": 0.25, + "default": 14 + } + }, + "required": [ + "builtInLoadCapacitancePf" + ] + } + ] + } + } + } + ] + } + } + } + } +} diff --git a/soc/nordic/nrf54h/bicr/bicrgen.py b/soc/nordic/nrf54h/bicr/bicrgen.py new file mode 100644 index 00000000000..b28fdc571a8 --- /dev/null +++ b/soc/nordic/nrf54h/bicr/bicrgen.py @@ -0,0 +1,703 @@ +""" +BICR Generation Tool +-------------------- + +This tool is used to generate a BICR (Board Information Configuration Register) +file from a JSON file that contains the BICR configuration. It can also be used +to do the reverse operation, i.e., to extract the BICR configuration from a BICR +hex file. + +:: + + JSON ┌────────────┐ JSON + │ │ │ ▲ + └─────►│ ├───────┘ + │ bicrgen.py │ + ┌─────►│ ├───────┐ + │ │ │ ▼ + HEX └────────────┘ HEX + +Usage:: + + python genbicr.py \ + --input \ + [--svd ] \ + [--output ] \ + [--list] + +Copyright (c) 2024 Nordic Semiconductor ASA +SPDX-License-Identifier: Apache-2.0 +""" + +import argparse +import json +import struct +import sys +import xml.etree.ElementTree as ET +from dataclasses import dataclass +from enum import Enum +from pathlib import Path +from pprint import pprint + +from intelhex import IntelHex + + +class Register: + def __init__(self, regs: ET.Element, name: str, data: bytes | bytearray | None = None) -> None: + cluster_name, reg_name = name.split(".") + + cluster = regs.find(f".//registers/cluster[name='{cluster_name}']") + self._offset = int(cluster.find("addressOffset").text, 0) + + self._reg = cluster.find(f".//register[name='{reg_name}']") + self._offset += int(self._reg.find("addressOffset").text, 0) + self._size = int(self._reg.find("size").text, 0) // 8 + + self._data = data + + @property + def offset(self) -> int: + return self._offset + + @property + def size(self) -> int: + return self._size + + def _msk_pos(self, name: str) -> tuple[int, int]: + field = self._reg.find(f".//fields/field[name='{name}']") + field_lsb = int(field.find("lsb").text, 0) + field_msb = int(field.find("msb").text, 0) + + mask = (0xFFFFFFFF - (1 << field_lsb) + 1) & (0xFFFFFFFF >> (31 - field_msb)) + + return mask, field_lsb + + def _enums(self, field: str) -> list[ET.Element]: + return self._reg.findall( + f".//fields/field[name='{field}']/enumeratedValues/enumeratedValue" + ) + + def __getitem__(self, field: str) -> int: + if not self._data: + raise TypeError("Empty register") + + msk, pos = self._msk_pos(field) + raw = struct.unpack("> pos + + def __setitem__(self, field: str, value: int) -> None: + if not isinstance(self._data, bytearray): + raise TypeError("Register is read-only") + + msk, pos = self._msk_pos(field) + raw = raw = struct.unpack(" str: + value = self[field] + for enum in self._enums(field): + if value == int(enum.find("value").text, 0): + return enum.find("name").text + + raise ValueError(f"Invalid enum value for {field}: {value}") + + def enum_set(self, field: str, value: str) -> None: + for enum in self._enums(field): + if value == enum.find("name").text: + self[field] = int(enum.find("value").text, 0) + return + + raise ValueError(f"Invalid enum value for {field}: {value}") + + +class PowerSupplyScheme(Enum): + UNCONFIGURED = "Unconfigured" + VDD_VDDH_1V8 = "VDD_VDDH_1V8" + VDDH_2V1_5V5 = "VDDH_2V1_5V5" + + +@dataclass +class PowerConfig: + scheme: PowerSupplyScheme + + @classmethod + def from_raw(cls: "PowerConfig", bicr_spec: ET.Element, data: bytes) -> "PowerConfig": + power_config = Register(bicr_spec, "POWER.CONFIG", data) + + if ( + power_config.enum_get("VDDAO5V0") == "Shorted" + and power_config.enum_get("VDDAO1V8") == "External" + ): + scheme = PowerSupplyScheme.VDD_VDDH_1V8 + elif ( + power_config.enum_get("VDDAO5V0") == "External" + and power_config.enum_get("VDDAO1V8") == "Internal" + ): + scheme = PowerSupplyScheme.VDDH_2V1_5V5 + else: + scheme = PowerSupplyScheme.UNCONFIGURED + + return cls(scheme=scheme) + + @classmethod + def from_json(cls: "PowerConfig", data: dict) -> "PowerConfig": + power = data["power"] + + return cls(scheme=PowerSupplyScheme[power["scheme"]]) + + def to_raw(self, bicr_spec: ET.Element, buf: bytearray): + power_config = Register(bicr_spec, "POWER.CONFIG", buf) + + if self.scheme == PowerSupplyScheme.VDD_VDDH_1V8: + power_config.enum_set("VDDAO5V0", "Shorted") + power_config.enum_set("VDDAO1V8", "External") + power_config.enum_set("VDD1V0", "Internal") + power_config.enum_set("VDDRF1V0", "Shorted") + power_config.enum_set("VDDAO0V8", "Internal") + power_config.enum_set("VDDVS0V8", "Internal") + power_config.enum_set("INDUCTOR", "Present") + elif self.scheme == PowerSupplyScheme.VDDH_2V1_5V5: + power_config.enum_set("VDDAO5V0", "External") + power_config.enum_set("VDDAO1V8", "Internal") + power_config.enum_set("VDD1V0", "Internal") + power_config.enum_set("VDDRF1V0", "Shorted") + power_config.enum_set("VDDAO0V8", "Internal") + power_config.enum_set("VDDVS0V8", "Internal") + power_config.enum_set("INDUCTOR", "Present") + else: + power_config.enum_set("VDDAO5V0", "Unconfigured") + power_config.enum_set("VDDAO1V8", "Unconfigured") + power_config.enum_set("VDD1V0", "Unconfigured") + power_config.enum_set("VDDRF1V0", "Unconfigured") + power_config.enum_set("VDDAO0V8", "Unconfigured") + power_config.enum_set("VDDVS0V8", "Unconfigured") + power_config.enum_set("INDUCTOR", "Unconfigured") + + def to_json(self, buf: dict): + buf["power"] = {"scheme": self.scheme.name} + + +class IoPortPower(Enum): + DISCONNECTED = "Disconnected" + SHORTED = "Shorted" + EXTERNAL_1V8 = "External1V8" + + +class IoPortPowerExtended(Enum): + DISCONNECTED = "Disconnected" + SHORTED = "Shorted" + EXTERNAL_1V8 = "External1V8" + EXTERNAL_FULL = "ExternalFull" + + +@dataclass +class IoPortPowerConfig: + p1_supply: IoPortPower + p2_supply: IoPortPower + p6_supply: IoPortPower + p7_supply: IoPortPower + p9_supply: IoPortPowerExtended + + @classmethod + def from_raw( + cls: "IoPortPowerConfig", bicr_spec: ET.Element, data: bytes + ) -> "IoPortPowerConfig": + ioport_power0 = Register(bicr_spec, "IOPORT.POWER0", data) + ioport_power1 = Register(bicr_spec, "IOPORT.POWER1", data) + + return cls( + p1_supply=IoPortPower(ioport_power0.enum_get("P1")), + p2_supply=IoPortPower(ioport_power0.enum_get("P2")), + p6_supply=IoPortPower(ioport_power0.enum_get("P6")), + p7_supply=IoPortPower(ioport_power0.enum_get("P7")), + p9_supply=IoPortPowerExtended(ioport_power1.enum_get("P9")), + ) + + @classmethod + def from_json(cls: "IoPortPowerConfig", data: dict) -> "IoPortPowerConfig": + ioport_power = data["ioPortPower"] + + return cls( + p1_supply=IoPortPower[ioport_power["p1Supply"]], + p2_supply=IoPortPower[ioport_power["p2Supply"]], + p6_supply=IoPortPower[ioport_power["p6Supply"]], + p7_supply=IoPortPower[ioport_power["p7Supply"]], + p9_supply=IoPortPowerExtended[ioport_power["p9Supply"]], + ) + + def to_raw(self, bicr_spec: ET.Element, buf: bytearray): + ioport_power0 = Register(bicr_spec, "IOPORT.POWER0", buf) + ioport_power1 = Register(bicr_spec, "IOPORT.POWER1", buf) + + ioport_power0.enum_set("P1", self.p1_supply.value) + ioport_power0.enum_set("P2", self.p2_supply.value) + ioport_power0.enum_set("P6", self.p6_supply.value) + ioport_power0.enum_set("P7", self.p7_supply.value) + ioport_power1.enum_set("P9", self.p9_supply.value) + + def to_json(self, buf: dict): + buf["ioPortPower"] = { + "p1Supply": self.p1_supply.name, + "p2Supply": self.p2_supply.name, + "p6Supply": self.p6_supply.name, + "p7Supply": self.p7_supply.name, + "p9Supply": self.p9_supply.name, + } + + +@dataclass +class IoPortImpedanceConfig: + p6_impedance_ohms: int + p7_impedance_ohms: int + + @classmethod + def from_raw( + cls: "IoPortImpedanceConfig", bicr_spec: ET.Element, data: bytes + ) -> "IoPortImpedanceConfig": + drivectl0 = Register(bicr_spec, "IOPORT.DRIVECTRL0", data) + + return cls( + p6_impedance_ohms=int(drivectl0.enum_get("P6")[4:]), + p7_impedance_ohms=int(drivectl0.enum_get("P7")[4:]), + ) + + @classmethod + def from_json(cls: "IoPortImpedanceConfig", data: dict) -> "IoPortImpedanceConfig": + ioport_impedance = data["ioPortImpedance"] + + return cls( + p6_impedance_ohms=ioport_impedance["p6ImpedanceOhms"], + p7_impedance_ohms=ioport_impedance["p7ImpedanceOhms"], + ) + + def to_raw(self, bicr_spec: ET.Element, buf: bytearray): + drivectl0 = Register(bicr_spec, "IOPORT.DRIVECTRL0", buf) + + drivectl0.enum_set("P6", f"Ohms{self.p6_impedance_ohms}") + drivectl0.enum_set("P7", f"Ohms{self.p7_impedance_ohms}") + + def to_json(self, buf: dict): + buf["ioPortImpedance"] = { + "p6ImpedanceOhms": self.p6_impedance_ohms, + "p7ImpedanceOhms": self.p7_impedance_ohms, + } + + +class LFXOMode(Enum): + CRYSTAL = "Crystal" + EXT_SINE = "ExtSine" + EXT_SQUARE = "ExtSquare" + + +@dataclass +class LFXOConfig: + accuracy_ppm: int + mode: LFXOMode + builtin_load_capacitors: bool + builtin_load_capacitance_pf: int | None + startup_time_ms: int + + @classmethod + def from_raw(cls: "LFXOConfig", bicr_spec: ET.Element, data: bytes) -> "LFXOConfig": + lfosc_lfxoconfig = Register(bicr_spec, "LFOSC.LFXOCONFIG", data) + + try: + loadcap = lfosc_lfxoconfig.enum_get("LOADCAP") + except ValueError: + builtin_load_capacitors = True + builtin_load_capacitance_pf = lfosc_lfxoconfig["LOADCAP"] + else: + if loadcap == "Unconfigured": + raise ValueError("Invalid LFXO load capacitors configuration") + + builtin_load_capacitors = False + builtin_load_capacitance_pf = None + + startup_time_ms = 0 + try: + lfosc_lfxoconfig.enum_get("TIME") + except ValueError: + startup_time_ms = lfosc_lfxoconfig["TIME"] + else: + raise ValueError("Invalid LFXO startup time (not configured)") + + return cls( + accuracy_ppm=int(lfosc_lfxoconfig.enum_get("ACCURACY")[:3]), + mode=LFXOMode(lfosc_lfxoconfig.enum_get("MODE")), + builtin_load_capacitors=builtin_load_capacitors, + builtin_load_capacitance_pf=builtin_load_capacitance_pf, + startup_time_ms=startup_time_ms, + ) + + @classmethod + def from_json(cls: "LFXOConfig", data: dict) -> "LFXOConfig": + lfxo = data["lfosc"]["lfxo"] + + builtin_load_capacitors = lfxo["builtInLoadCapacitors"] + if builtin_load_capacitors: + builtin_load_capacitance_pf = lfxo["builtInLoadCapacitancePf"] + else: + builtin_load_capacitance_pf = None + + return cls( + accuracy_ppm=lfxo["accuracyPPM"], + mode=LFXOMode[lfxo["mode"]], + builtin_load_capacitors=builtin_load_capacitors, + builtin_load_capacitance_pf=builtin_load_capacitance_pf, + startup_time_ms=lfxo["startupTimeMs"], + ) + + def to_raw(self, bicr_spec: ET.Element, buf: bytearray): + lfosc_lfxoconfig = Register(bicr_spec, "LFOSC.LFXOCONFIG", buf) + + lfosc_lfxoconfig.enum_set("ACCURACY", f"{self.accuracy_ppm}ppm") + lfosc_lfxoconfig.enum_set("MODE", self.mode.value) + lfosc_lfxoconfig["TIME"] = self.startup_time_ms + + if self.builtin_load_capacitors: + lfosc_lfxoconfig["LOADCAP"] = self.builtin_load_capacitance_pf + else: + lfosc_lfxoconfig.enum_set("LOADCAP", "External") + + def to_json(self, buf: dict): + lfosc = buf["lfosc"] + lfosc["lfxo"] = { + "accuracyPPM": self.accuracy_ppm, + "mode": self.mode.name, + "builtInLoadCapacitors": self.builtin_load_capacitors, + "startupTimeMs": self.startup_time_ms, + } + + if self.builtin_load_capacitors: + lfosc["lfxo"]["builtInLoadCapacitancePf"] = self.builtin_load_capacitance_pf + + +@dataclass +class LFRCCalibrationConfig: + calibration_enabled: bool + temp_meas_interval_seconds: float | None + temp_delta_calibration_trigger_celsius: float | None + max_meas_interval_between_calibrations: int | None + + @classmethod + def from_raw( + cls: "LFRCCalibrationConfig", bicr_spec: ET.Element, data: bytes + ) -> "LFRCCalibrationConfig": + lfosc_lfrcautocalconfig = Register(bicr_spec, "LFOSC.LFRCAUTOCALCONFIG", data) + + calibration_enabled = lfosc_lfrcautocalconfig.enum_get("ENABLE") == "Enabled" + if calibration_enabled: + return cls( + calibration_enabled=calibration_enabled, + temp_meas_interval_seconds=lfosc_lfrcautocalconfig["TEMPINTERVAL"], + temp_delta_calibration_trigger_celsius=lfosc_lfrcautocalconfig["TEMPDELTA"], + max_meas_interval_between_calibrations=lfosc_lfrcautocalconfig["INTERVALMAXNO"], + ) + else: + return cls( + calibration_enabled=calibration_enabled, + temp_meas_interval_seconds=None, + temp_delta_calibration_trigger_celsius=None, + max_meas_interval_between_calibrations=None, + ) + + @classmethod + def from_json(cls: "LFRCCalibrationConfig", data: dict) -> "LFRCCalibrationConfig": + lfrccal = data["lfosc"]["lfrccal"] + + calibration_enabled = lfrccal["calibrationEnabled"] + if calibration_enabled: + temp_meas_interval_seconds = lfrccal["tempMeasIntervalSeconds"] + temp_delta_calibration_trigger_celsius = lfrccal["tempDeltaCalibrationTriggerCelsius"] + max_meas_interval_between_calibrations = lfrccal["maxMeasIntervalBetweenCalibrations"] + else: + temp_meas_interval_seconds = None + temp_delta_calibration_trigger_celsius = None + max_meas_interval_between_calibrations = None + + return cls( + calibration_enabled=calibration_enabled, + temp_meas_interval_seconds=temp_meas_interval_seconds, + temp_delta_calibration_trigger_celsius=temp_delta_calibration_trigger_celsius, + max_meas_interval_between_calibrations=max_meas_interval_between_calibrations, + ) + + def to_raw(self, bicr_spec: ET.Element, buf: bytearray): + lfosc_lfrcautocalconfig = Register(bicr_spec, "LFOSC.LFRCAUTOCALCONFIG", buf) + + lfosc_lfrcautocalconfig.enum_set( + "ENABLE", "Enabled" if self.calibration_enabled else "Disabled" + ) + if self.calibration_enabled: + lfosc_lfrcautocalconfig["TEMPINTERVAL"] = self.temp_meas_interval_seconds + lfosc_lfrcautocalconfig["TEMPDELTA"] = self.temp_delta_calibration_trigger_celsius + lfosc_lfrcautocalconfig["INTERVALMAXNO"] = self.max_meas_interval_between_calibrations + + def to_json(self, buf: dict): + lfosc = buf["lfosc"] + lfosc["lfrccal"] = { + "calibrationEnabled": self.calibration_enabled, + } + + if self.calibration_enabled: + lfosc["lfrccal"]["tempMeasIntervalSeconds"] = self.temp_meas_interval_seconds + lfosc["lfrccal"]["tempDeltaCalibrationTriggerCelsius"] = ( + self.temp_delta_calibration_trigger_celsius + ) + lfosc["lfrccal"]["maxMeasIntervalBetweenCalibrations"] = ( + self.max_meas_interval_between_calibrations + ) + + +class LFOSCSource(Enum): + LFXO = "LFXO" + LFRC = "LFRC" + + +@dataclass +class LFOSCConfig: + source: LFOSCSource + lfxo: LFXOConfig | None + lfrccal: LFRCCalibrationConfig | None + + @classmethod + def from_raw(cls: "LFOSCConfig", bicr_spec: ET.Element, data: bytes) -> "LFOSCConfig": + lfosc_lfxoconfig = Register(bicr_spec, "LFOSC.LFXOCONFIG", data) + + mode = lfosc_lfxoconfig.enum_get("MODE") + if mode == "Disabled": + source = LFOSCSource.LFRC + lfxo = None + lfrccal = LFRCCalibrationConfig.from_raw(bicr_spec, data) + elif mode == "Unconfigured": + raise ValueError("Invalid LFOSC configuration") + else: + source = LFOSCSource.LFXO + lfxo = LFXOConfig.from_raw(bicr_spec, data) + lfrccal = None + + return cls( + source=source, + lfxo=lfxo, + lfrccal=lfrccal, + ) + + @classmethod + def from_json(cls: "LFOSCConfig", data: dict) -> "LFOSCConfig": + lfosc = data["lfosc"] + + source = LFOSCSource[lfosc["source"]] + if source == LFOSCSource.LFXO: + source = source + lfxo = LFXOConfig.from_json(data) + lfrccal = None + else: + source = source + lfxo = None + lfrccal = LFRCCalibrationConfig.from_json(data) + + return cls( + source=source, + lfxo=lfxo, + lfrccal=lfrccal, + ) + + def to_raw(self, bicr_spec: ET.Element, buf: bytearray): + lfosc_lfxoconfig = Register(bicr_spec, "LFOSC.LFXOCONFIG", buf) + + if self.source == LFOSCSource.LFRC: + lfosc_lfxoconfig.enum_set("MODE", "Disabled") + self.lfrccal.to_raw(bicr_spec, buf) + elif self.source == LFOSCSource.LFXO: + self.lfxo.to_raw(bicr_spec, buf) + + def to_json(self, buf: dict): + buf["lfosc"] = { + "source": self.source.name, + } + + if self.source == LFOSCSource.LFXO: + self.lfxo.to_json(buf) + else: + self.lfrccal.to_json(buf) + + +class HFXOMode(Enum): + CRYSTAL = "Crystal" + EXT_SQUARE = "ExtSquare" + + +@dataclass +class HFXOConfig: + mode: HFXOMode + builtin_load_capacitors: bool + builtin_load_capacitance_pf: float | None + startup_time_us: int + + @classmethod + def from_raw(cls: "HFXOConfig", bicr_spec: ET.Element, data: bytes) -> "HFXOConfig": + hfxo_config = Register(bicr_spec, "HFXO.CONFIG", data) + hfxo_startuptime = Register(bicr_spec, "HFXO.STARTUPTIME", data) + + mode = HFXOMode(hfxo_config.enum_get("MODE")) + + try: + loadcap = hfxo_config.enum_get("LOADCAP") + except ValueError: + builtin_load_capacitors = True + builtin_load_capacitance_pf = hfxo_config["LOADCAP"] * 0.25 + else: + if loadcap == "Unconfigured": + raise ValueError("Invalid HFXO load capacitors configuration") + + builtin_load_capacitors = False + builtin_load_capacitance_pf = None + + startup_time_us = 0 + try: + hfxo_startuptime.enum_get("TIME") + except ValueError: + startup_time_us = hfxo_startuptime["TIME"] + else: + raise ValueError("Invalid LFXO startup time (not configured)") + + return cls( + mode=mode, + builtin_load_capacitors=builtin_load_capacitors, + builtin_load_capacitance_pf=builtin_load_capacitance_pf, + startup_time_us=startup_time_us, + ) + + @classmethod + def from_json(cls: "HFXOConfig", data: dict) -> "HFXOConfig": + hfxo = data["hfxo"] + + builtin_load_capacitors = hfxo["builtInLoadCapacitors"] + if builtin_load_capacitors: + builtin_load_capacitance_pf = hfxo["builtInLoadCapacitancePf"] + else: + builtin_load_capacitance_pf = None + + return cls( + mode=HFXOMode[hfxo["mode"]], + builtin_load_capacitors=builtin_load_capacitors, + builtin_load_capacitance_pf=builtin_load_capacitance_pf, + startup_time_us=hfxo["startupTimeUs"], + ) + + def to_raw(self, bicr_spec: ET.Element, buf: bytearray): + hfxo_config = Register(bicr_spec, "HFXO.CONFIG", buf) + hfxo_startuptime = Register(bicr_spec, "HFXO.STARTUPTIME", buf) + + hfxo_config.enum_set("MODE", self.mode.value) + hfxo_startuptime["TIME"] = self.startup_time_us + + if self.builtin_load_capacitors: + hfxo_config["LOADCAP"] = int(self.builtin_load_capacitance_pf / 0.25) + else: + hfxo_config.enum_set("LOADCAP", "External") + + def to_json(self, buf: dict): + buf["hfxo"] = { + "mode": self.mode.name, + "builtInLoadCapacitors": self.builtin_load_capacitors, + "startupTimeUs": self.startup_time_us, + } + + if self.builtin_load_capacitors: + buf["hfxo"]["builtInLoadCapacitancePf"] = self.builtin_load_capacitance_pf + + +@dataclass +class BICR: + power: PowerConfig + ioport_power: IoPortPowerConfig + ioport_impedance: IoPortImpedanceConfig + lfosc: LFOSCConfig + hfxo: HFXOConfig + + @classmethod + def from_raw(cls: "BICR", bicr_spec: ET.Element, data: bytes) -> "BICR": + return cls( + power=PowerConfig.from_raw(bicr_spec, data), + ioport_power=IoPortPowerConfig.from_raw(bicr_spec, data), + ioport_impedance=IoPortImpedanceConfig.from_raw(bicr_spec, data), + lfosc=LFOSCConfig.from_raw(bicr_spec, data), + hfxo=HFXOConfig.from_raw(bicr_spec, data), + ) + + @classmethod + def from_json(cls: "BICR", data: dict) -> "BICR": + return cls( + power=PowerConfig.from_json(data), + ioport_power=IoPortPowerConfig.from_json(data), + ioport_impedance=IoPortImpedanceConfig.from_json(data), + lfosc=LFOSCConfig.from_json(data), + hfxo=HFXOConfig.from_json(data), + ) + + def to_raw(self, bicr_spec: ET.Element, buf: bytearray): + self.power.to_raw(bicr_spec, buf) + self.ioport_power.to_raw(bicr_spec, buf) + self.ioport_impedance.to_raw(bicr_spec, buf) + self.lfosc.to_raw(bicr_spec, buf) + self.hfxo.to_raw(bicr_spec, buf) + + def to_json(self, buf: dict): + self.power.to_json(buf) + self.ioport_power.to_json(buf) + self.ioport_impedance.to_json(buf) + self.lfosc.to_json(buf) + self.hfxo.to_json(buf) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument("-i", "--input", type=Path, required=True, help="Input file") + parser.add_argument("-s", "--svd", type=Path, help="SVD file") + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("-o", "--output", type=Path, help="Output file") + group.add_argument("-l", "--list", action="store_true", help="List BICR options") + args = parser.parse_args() + + if args.input.suffix == ".hex" or (args.output and args.output.suffix == ".hex"): + if not args.svd: + sys.exit("SVD file is required for hex files") + + bicr_spec = ET.parse(args.svd).getroot().find(".//peripheral[name='BICR_NS']") + + if args.input.suffix == ".hex": + ih = IntelHex() + ih.loadhex(args.input) + bicr = BICR.from_raw(bicr_spec, ih.tobinstr()) + elif args.input.suffix == ".json": + with open(args.input) as f: + data = json.load(f) + bicr = BICR.from_json(data) + else: + sys.exit("Unsupported input file format") + + if args.output: + if args.output.suffix == ".hex": + bicr_address = int(bicr_spec.find("baseAddress").text, 0) + last_reg = Register(bicr_spec, "TAMPC.ACTIVESHIELD") + bicr_size = last_reg.offset + last_reg.size + + buf = bytearray([0xFF] * bicr_size) + bicr.to_raw(bicr_spec, buf) + + ih = IntelHex() + ih.frombytes(buf, offset=bicr_address) + ih.tofile(args.output, format="hex") + elif args.output.suffix == ".json": + buf = dict() + bicr.to_json(buf) + + with open(args.output, "w") as f: + json.dump(buf, f, indent=4) + else: + sys.exit("Unsupported output file format") + elif args.list: + pprint(bicr)