1"""
2BICR Generation Tool
3--------------------
4
5This tool is used to generate a BICR (Board Information Configuration Register)
6file from a JSON file that contains the BICR configuration. It can also be used
7to do the reverse operation, i.e., to extract the BICR configuration from a BICR
8hex file.
9
10::
11
12         JSON     ┌────────────┐     JSON
13           │      │            │       ▲
14           └─────►│            ├───────┘
15bicrgen.py16           ┌─────►│            ├───────┐
17           │      │            │       ▼
18          HEX     └────────────┘      HEX
19
20Usage::
21
22    python genbicr.py \
23        --input <input_file.{hex,json}> \
24        [--svd <svd_file>] \
25        [--output <output_file.{hex,json}>] \
26        [--list]
27
28Copyright (c) 2024 Nordic Semiconductor ASA
29SPDX-License-Identifier: Apache-2.0
30"""
31
32import argparse
33import json
34import struct
35import sys
36import xml.etree.ElementTree as ET
37from dataclasses import dataclass
38from enum import Enum
39from pathlib import Path
40from pprint import pprint
41
42from intelhex import IntelHex
43
44
45class Register:
46    def __init__(self, regs: ET.Element, name: str, data: bytes | bytearray | None = None) -> None:
47        cluster_name, reg_name = name.split(".")
48
49        cluster = regs.find(f".//registers/cluster[name='{cluster_name}']")
50        self._offset = int(cluster.find("addressOffset").text, 0)
51
52        self._reg = cluster.find(f".//register[name='{reg_name}']")
53        self._offset += int(self._reg.find("addressOffset").text, 0)
54        self._size = int(self._reg.find("size").text, 0) // 8
55
56        self._data = data
57
58    @property
59    def offset(self) -> int:
60        return self._offset
61
62    @property
63    def size(self) -> int:
64        return self._size
65
66    def _msk_pos(self, name: str) -> tuple[int, int]:
67        field = self._reg.find(f".//fields/field[name='{name}']")
68        field_lsb = int(field.find("lsb").text, 0)
69        field_msb = int(field.find("msb").text, 0)
70
71        mask = (0xFFFFFFFF - (1 << field_lsb) + 1) & (0xFFFFFFFF >> (31 - field_msb))
72
73        return mask, field_lsb
74
75    def _enums(self, field: str) -> list[ET.Element]:
76        return self._reg.findall(
77            f".//fields/field[name='{field}']/enumeratedValues/enumeratedValue"
78        )
79
80    def __getitem__(self, field: str) -> int:
81        if not self._data:
82            raise TypeError("Empty register")
83
84        msk, pos = self._msk_pos(field)
85        raw = struct.unpack("<I", self._data[self._offset : self._offset + 4])[0]
86        return (raw & msk) >> pos
87
88    def __setitem__(self, field: str, value: int) -> None:
89        if not isinstance(self._data, bytearray):
90            raise TypeError("Register is read-only")
91
92        msk, pos = self._msk_pos(field)
93        raw = raw = struct.unpack("<I", self._data[self._offset : self._offset + 4])[0]
94        raw &= ~msk
95        raw |= (value << pos) & msk
96        self._data[self._offset : self._offset + 4] = struct.pack("<I", raw)
97
98    def enum_get(self, field: str) -> str:
99        value = self[field]
100        for enum in self._enums(field):
101            if value == int(enum.find("value").text, 0):
102                return enum.find("name").text
103
104        raise ValueError(f"Invalid enum value for {field}: {value}")
105
106    def enum_set(self, field: str, value: str) -> None:
107        for enum in self._enums(field):
108            if value == enum.find("name").text:
109                self[field] = int(enum.find("value").text, 0)
110                return
111
112        raise ValueError(f"Invalid enum value for {field}: {value}")
113
114
115class PowerSupplyScheme(Enum):
116    UNCONFIGURED = "Unconfigured"
117    VDD_VDDH_1V8 = "VDD_VDDH_1V8"
118    VDDH_2V1_5V5 = "VDDH_2V1_5V5"
119
120
121@dataclass
122class PowerConfig:
123    scheme: PowerSupplyScheme
124
125    @classmethod
126    def from_raw(cls: "PowerConfig", bicr_spec: ET.Element, data: bytes) -> "PowerConfig":
127        power_config = Register(bicr_spec, "POWER.CONFIG", data)
128
129        if (
130            power_config.enum_get("VDDAO5V0") == "Shorted"
131            and power_config.enum_get("VDDAO1V8") == "External"
132        ):
133            scheme = PowerSupplyScheme.VDD_VDDH_1V8
134        elif (
135            power_config.enum_get("VDDAO5V0") == "External"
136            and power_config.enum_get("VDDAO1V8") == "Internal"
137        ):
138            scheme = PowerSupplyScheme.VDDH_2V1_5V5
139        else:
140            scheme = PowerSupplyScheme.UNCONFIGURED
141
142        return cls(scheme=scheme)
143
144    @classmethod
145    def from_json(cls: "PowerConfig", data: dict) -> "PowerConfig":
146        power = data["power"]
147
148        return cls(scheme=PowerSupplyScheme[power["scheme"]])
149
150    def to_raw(self, bicr_spec: ET.Element, buf: bytearray):
151        power_config = Register(bicr_spec, "POWER.CONFIG", buf)
152
153        if self.scheme == PowerSupplyScheme.VDD_VDDH_1V8:
154            power_config.enum_set("VDDAO5V0", "Shorted")
155            power_config.enum_set("VDDAO1V8", "External")
156            power_config.enum_set("VDD1V0", "Internal")
157            power_config.enum_set("VDDRF1V0", "Shorted")
158            power_config.enum_set("VDDAO0V8", "Internal")
159            power_config.enum_set("VDDVS0V8", "Internal")
160            power_config.enum_set("INDUCTOR", "Present")
161        elif self.scheme == PowerSupplyScheme.VDDH_2V1_5V5:
162            power_config.enum_set("VDDAO5V0", "External")
163            power_config.enum_set("VDDAO1V8", "Internal")
164            power_config.enum_set("VDD1V0", "Internal")
165            power_config.enum_set("VDDRF1V0", "Shorted")
166            power_config.enum_set("VDDAO0V8", "Internal")
167            power_config.enum_set("VDDVS0V8", "Internal")
168            power_config.enum_set("INDUCTOR", "Present")
169        else:
170            power_config.enum_set("VDDAO5V0", "Unconfigured")
171            power_config.enum_set("VDDAO1V8", "Unconfigured")
172            power_config.enum_set("VDD1V0", "Unconfigured")
173            power_config.enum_set("VDDRF1V0", "Unconfigured")
174            power_config.enum_set("VDDAO0V8", "Unconfigured")
175            power_config.enum_set("VDDVS0V8", "Unconfigured")
176            power_config.enum_set("INDUCTOR", "Unconfigured")
177
178    def to_json(self, buf: dict):
179        buf["power"] = {"scheme": self.scheme.name}
180
181
182class IoPortPower(Enum):
183    DISCONNECTED = "Disconnected"
184    SHORTED = "Shorted"
185    EXTERNAL_1V8 = "External1V8"
186
187
188class IoPortPowerExtended(Enum):
189    DISCONNECTED = "Disconnected"
190    SHORTED = "Shorted"
191    EXTERNAL_1V8 = "External1V8"
192    EXTERNAL_FULL = "ExternalFull"
193
194
195@dataclass
196class IoPortPowerConfig:
197    p1_supply: IoPortPower
198    p2_supply: IoPortPower
199    p6_supply: IoPortPower
200    p7_supply: IoPortPower
201    p9_supply: IoPortPowerExtended
202
203    @classmethod
204    def from_raw(
205        cls: "IoPortPowerConfig", bicr_spec: ET.Element, data: bytes
206    ) -> "IoPortPowerConfig":
207        ioport_power0 = Register(bicr_spec, "IOPORT.POWER0", data)
208        ioport_power1 = Register(bicr_spec, "IOPORT.POWER1", data)
209
210        return cls(
211            p1_supply=IoPortPower(ioport_power0.enum_get("P1")),
212            p2_supply=IoPortPower(ioport_power0.enum_get("P2")),
213            p6_supply=IoPortPower(ioport_power0.enum_get("P6")),
214            p7_supply=IoPortPower(ioport_power0.enum_get("P7")),
215            p9_supply=IoPortPowerExtended(ioport_power1.enum_get("P9")),
216        )
217
218    @classmethod
219    def from_json(cls: "IoPortPowerConfig", data: dict) -> "IoPortPowerConfig":
220        ioport_power = data["ioPortPower"]
221
222        return cls(
223            p1_supply=IoPortPower[ioport_power["p1Supply"]],
224            p2_supply=IoPortPower[ioport_power["p2Supply"]],
225            p6_supply=IoPortPower[ioport_power["p6Supply"]],
226            p7_supply=IoPortPower[ioport_power["p7Supply"]],
227            p9_supply=IoPortPowerExtended[ioport_power["p9Supply"]],
228        )
229
230    def to_raw(self, bicr_spec: ET.Element, buf: bytearray):
231        ioport_power0 = Register(bicr_spec, "IOPORT.POWER0", buf)
232        ioport_power1 = Register(bicr_spec, "IOPORT.POWER1", buf)
233
234        ioport_power0.enum_set("P1", self.p1_supply.value)
235        ioport_power0.enum_set("P2", self.p2_supply.value)
236        ioport_power0.enum_set("P6", self.p6_supply.value)
237        ioport_power0.enum_set("P7", self.p7_supply.value)
238        ioport_power1.enum_set("P9", self.p9_supply.value)
239
240    def to_json(self, buf: dict):
241        buf["ioPortPower"] = {
242            "p1Supply": self.p1_supply.name,
243            "p2Supply": self.p2_supply.name,
244            "p6Supply": self.p6_supply.name,
245            "p7Supply": self.p7_supply.name,
246            "p9Supply": self.p9_supply.name,
247        }
248
249
250@dataclass
251class IoPortImpedanceConfig:
252    p6_impedance_ohms: int
253    p7_impedance_ohms: int
254
255    @classmethod
256    def from_raw(
257        cls: "IoPortImpedanceConfig", bicr_spec: ET.Element, data: bytes
258    ) -> "IoPortImpedanceConfig":
259        drivectl0 = Register(bicr_spec, "IOPORT.DRIVECTRL0", data)
260
261        return cls(
262            p6_impedance_ohms=int(drivectl0.enum_get("P6")[4:]),
263            p7_impedance_ohms=int(drivectl0.enum_get("P7")[4:]),
264        )
265
266    @classmethod
267    def from_json(cls: "IoPortImpedanceConfig", data: dict) -> "IoPortImpedanceConfig":
268        ioport_impedance = data["ioPortImpedance"]
269
270        return cls(
271            p6_impedance_ohms=ioport_impedance["p6ImpedanceOhms"],
272            p7_impedance_ohms=ioport_impedance["p7ImpedanceOhms"],
273        )
274
275    def to_raw(self, bicr_spec: ET.Element, buf: bytearray):
276        drivectl0 = Register(bicr_spec, "IOPORT.DRIVECTRL0", buf)
277
278        drivectl0.enum_set("P6", f"Ohms{self.p6_impedance_ohms}")
279        drivectl0.enum_set("P7", f"Ohms{self.p7_impedance_ohms}")
280
281    def to_json(self, buf: dict):
282        buf["ioPortImpedance"] = {
283            "p6ImpedanceOhms": self.p6_impedance_ohms,
284            "p7ImpedanceOhms": self.p7_impedance_ohms,
285        }
286
287
288class LFXOMode(Enum):
289    CRYSTAL = "Crystal"
290    EXT_SINE = "ExtSine"
291    EXT_SQUARE = "ExtSquare"
292
293
294@dataclass
295class LFXOConfig:
296    accuracy_ppm: int
297    mode: LFXOMode
298    builtin_load_capacitors: bool
299    builtin_load_capacitance_pf: int | None
300    startup_time_ms: int
301
302    @classmethod
303    def from_raw(cls: "LFXOConfig", bicr_spec: ET.Element, data: bytes) -> "LFXOConfig":
304        lfosc_lfxoconfig = Register(bicr_spec, "LFOSC.LFXOCONFIG", data)
305
306        try:
307            loadcap = lfosc_lfxoconfig.enum_get("LOADCAP")
308        except ValueError:
309            builtin_load_capacitors = True
310            builtin_load_capacitance_pf = lfosc_lfxoconfig["LOADCAP"]
311        else:
312            if loadcap == "Unconfigured":
313                raise ValueError("Invalid LFXO load capacitors configuration")
314
315            builtin_load_capacitors = False
316            builtin_load_capacitance_pf = None
317
318        startup_time_ms = 0
319        try:
320            lfosc_lfxoconfig.enum_get("TIME")
321        except ValueError:
322            startup_time_ms = lfosc_lfxoconfig["TIME"]
323        else:
324            raise ValueError("Invalid LFXO startup time (not configured)")
325
326        return cls(
327            accuracy_ppm=int(lfosc_lfxoconfig.enum_get("ACCURACY")[:3]),
328            mode=LFXOMode(lfosc_lfxoconfig.enum_get("MODE")),
329            builtin_load_capacitors=builtin_load_capacitors,
330            builtin_load_capacitance_pf=builtin_load_capacitance_pf,
331            startup_time_ms=startup_time_ms,
332        )
333
334    @classmethod
335    def from_json(cls: "LFXOConfig", data: dict) -> "LFXOConfig":
336        lfxo = data["lfosc"]["lfxo"]
337
338        builtin_load_capacitors = lfxo["builtInLoadCapacitors"]
339        if builtin_load_capacitors:
340            builtin_load_capacitance_pf = lfxo["builtInLoadCapacitancePf"]
341        else:
342            builtin_load_capacitance_pf = None
343
344        return cls(
345            accuracy_ppm=lfxo["accuracyPPM"],
346            mode=LFXOMode[lfxo["mode"]],
347            builtin_load_capacitors=builtin_load_capacitors,
348            builtin_load_capacitance_pf=builtin_load_capacitance_pf,
349            startup_time_ms=lfxo["startupTimeMs"],
350        )
351
352    def to_raw(self, bicr_spec: ET.Element, buf: bytearray):
353        lfosc_lfxoconfig = Register(bicr_spec, "LFOSC.LFXOCONFIG", buf)
354
355        lfosc_lfxoconfig.enum_set("ACCURACY", f"{self.accuracy_ppm}ppm")
356        lfosc_lfxoconfig.enum_set("MODE", self.mode.value)
357        lfosc_lfxoconfig["TIME"] = self.startup_time_ms
358
359        if self.builtin_load_capacitors:
360            lfosc_lfxoconfig["LOADCAP"] = self.builtin_load_capacitance_pf
361        else:
362            lfosc_lfxoconfig.enum_set("LOADCAP", "External")
363
364    def to_json(self, buf: dict):
365        lfosc = buf["lfosc"]
366        lfosc["lfxo"] = {
367            "accuracyPPM": self.accuracy_ppm,
368            "mode": self.mode.name,
369            "builtInLoadCapacitors": self.builtin_load_capacitors,
370            "startupTimeMs": self.startup_time_ms,
371        }
372
373        if self.builtin_load_capacitors:
374            lfosc["lfxo"]["builtInLoadCapacitancePf"] = self.builtin_load_capacitance_pf
375
376
377@dataclass
378class LFRCCalibrationConfig:
379    calibration_enabled: bool
380    temp_meas_interval_seconds: float | None
381    temp_delta_calibration_trigger_celsius: float | None
382    max_meas_interval_between_calibrations: int | None
383
384    @classmethod
385    def from_raw(
386        cls: "LFRCCalibrationConfig", bicr_spec: ET.Element, data: bytes
387    ) -> "LFRCCalibrationConfig":
388        lfosc_lfrcautocalconfig = Register(bicr_spec, "LFOSC.LFRCAUTOCALCONFIG", data)
389
390        calibration_enabled = lfosc_lfrcautocalconfig.enum_get("ENABLE") == "Enabled"
391        if calibration_enabled:
392            return cls(
393                calibration_enabled=calibration_enabled,
394                temp_meas_interval_seconds=lfosc_lfrcautocalconfig["TEMPINTERVAL"],
395                temp_delta_calibration_trigger_celsius=lfosc_lfrcautocalconfig["TEMPDELTA"],
396                max_meas_interval_between_calibrations=lfosc_lfrcautocalconfig["INTERVALMAXNO"],
397            )
398        else:
399            return cls(
400                calibration_enabled=calibration_enabled,
401                temp_meas_interval_seconds=None,
402                temp_delta_calibration_trigger_celsius=None,
403                max_meas_interval_between_calibrations=None,
404            )
405
406    @classmethod
407    def from_json(cls: "LFRCCalibrationConfig", data: dict) -> "LFRCCalibrationConfig":
408        lfrccal = data["lfosc"]["lfrccal"]
409
410        calibration_enabled = lfrccal["calibrationEnabled"]
411        if calibration_enabled:
412            temp_meas_interval_seconds = lfrccal["tempMeasIntervalSeconds"]
413            temp_delta_calibration_trigger_celsius = lfrccal["tempDeltaCalibrationTriggerCelsius"]
414            max_meas_interval_between_calibrations = lfrccal["maxMeasIntervalBetweenCalibrations"]
415        else:
416            temp_meas_interval_seconds = None
417            temp_delta_calibration_trigger_celsius = None
418            max_meas_interval_between_calibrations = None
419
420        return cls(
421            calibration_enabled=calibration_enabled,
422            temp_meas_interval_seconds=temp_meas_interval_seconds,
423            temp_delta_calibration_trigger_celsius=temp_delta_calibration_trigger_celsius,
424            max_meas_interval_between_calibrations=max_meas_interval_between_calibrations,
425        )
426
427    def to_raw(self, bicr_spec: ET.Element, buf: bytearray):
428        lfosc_lfrcautocalconfig = Register(bicr_spec, "LFOSC.LFRCAUTOCALCONFIG", buf)
429
430        lfosc_lfrcautocalconfig.enum_set(
431            "ENABLE", "Enabled" if self.calibration_enabled else "Disabled"
432        )
433        if self.calibration_enabled:
434            lfosc_lfrcautocalconfig["TEMPINTERVAL"] = self.temp_meas_interval_seconds
435            lfosc_lfrcautocalconfig["TEMPDELTA"] = self.temp_delta_calibration_trigger_celsius
436            lfosc_lfrcautocalconfig["INTERVALMAXNO"] = self.max_meas_interval_between_calibrations
437
438    def to_json(self, buf: dict):
439        lfosc = buf["lfosc"]
440        lfosc["lfrccal"] = {
441            "calibrationEnabled": self.calibration_enabled,
442        }
443
444        if self.calibration_enabled:
445            lfosc["lfrccal"]["tempMeasIntervalSeconds"] = self.temp_meas_interval_seconds
446            lfosc["lfrccal"]["tempDeltaCalibrationTriggerCelsius"] = (
447                self.temp_delta_calibration_trigger_celsius
448            )
449            lfosc["lfrccal"]["maxMeasIntervalBetweenCalibrations"] = (
450                self.max_meas_interval_between_calibrations
451            )
452
453
454class LFOSCSource(Enum):
455    LFXO = "LFXO"
456    LFRC = "LFRC"
457
458
459@dataclass
460class LFOSCConfig:
461    source: LFOSCSource
462    lfxo: LFXOConfig | None
463    lfrccal: LFRCCalibrationConfig | None
464
465    @classmethod
466    def from_raw(cls: "LFOSCConfig", bicr_spec: ET.Element, data: bytes) -> "LFOSCConfig":
467        lfosc_lfxoconfig = Register(bicr_spec, "LFOSC.LFXOCONFIG", data)
468
469        mode = lfosc_lfxoconfig.enum_get("MODE")
470        if mode == "Disabled":
471            source = LFOSCSource.LFRC
472            lfxo = None
473            lfrccal = LFRCCalibrationConfig.from_raw(bicr_spec, data)
474        elif mode == "Unconfigured":
475            raise ValueError("Invalid LFOSC configuration")
476        else:
477            source = LFOSCSource.LFXO
478            lfxo = LFXOConfig.from_raw(bicr_spec, data)
479            lfrccal = None
480
481        return cls(
482            source=source,
483            lfxo=lfxo,
484            lfrccal=lfrccal,
485        )
486
487    @classmethod
488    def from_json(cls: "LFOSCConfig", data: dict) -> "LFOSCConfig":
489        lfosc = data["lfosc"]
490
491        source = LFOSCSource[lfosc["source"]]
492        if source == LFOSCSource.LFXO:
493            source = source
494            lfxo = LFXOConfig.from_json(data)
495            lfrccal = None
496        else:
497            source = source
498            lfxo = None
499            lfrccal = LFRCCalibrationConfig.from_json(data)
500
501        return cls(
502            source=source,
503            lfxo=lfxo,
504            lfrccal=lfrccal,
505        )
506
507    def to_raw(self, bicr_spec: ET.Element, buf: bytearray):
508        lfosc_lfxoconfig = Register(bicr_spec, "LFOSC.LFXOCONFIG", buf)
509
510        if self.source == LFOSCSource.LFRC:
511            lfosc_lfxoconfig.enum_set("MODE", "Disabled")
512            self.lfrccal.to_raw(bicr_spec, buf)
513        elif self.source == LFOSCSource.LFXO:
514            self.lfxo.to_raw(bicr_spec, buf)
515
516    def to_json(self, buf: dict):
517        buf["lfosc"] = {
518            "source": self.source.name,
519        }
520
521        if self.source == LFOSCSource.LFXO:
522            self.lfxo.to_json(buf)
523        else:
524            self.lfrccal.to_json(buf)
525
526
527class HFXOMode(Enum):
528    CRYSTAL = "Crystal"
529    EXT_SQUARE = "ExtSquare"
530
531
532@dataclass
533class HFXOConfig:
534    mode: HFXOMode
535    builtin_load_capacitors: bool
536    builtin_load_capacitance_pf: float | None
537    startup_time_us: int
538
539    @classmethod
540    def from_raw(cls: "HFXOConfig", bicr_spec: ET.Element, data: bytes) -> "HFXOConfig":
541        hfxo_config = Register(bicr_spec, "HFXO.CONFIG", data)
542        hfxo_startuptime = Register(bicr_spec, "HFXO.STARTUPTIME", data)
543
544        mode = HFXOMode(hfxo_config.enum_get("MODE"))
545
546        try:
547            loadcap = hfxo_config.enum_get("LOADCAP")
548        except ValueError:
549            builtin_load_capacitors = True
550            builtin_load_capacitance_pf = hfxo_config["LOADCAP"] * 0.25
551        else:
552            if loadcap == "Unconfigured":
553                raise ValueError("Invalid HFXO load capacitors configuration")
554
555            builtin_load_capacitors = False
556            builtin_load_capacitance_pf = None
557
558        startup_time_us = 0
559        try:
560            hfxo_startuptime.enum_get("TIME")
561        except ValueError:
562            startup_time_us = hfxo_startuptime["TIME"]
563        else:
564            raise ValueError("Invalid LFXO startup time (not configured)")
565
566        return cls(
567            mode=mode,
568            builtin_load_capacitors=builtin_load_capacitors,
569            builtin_load_capacitance_pf=builtin_load_capacitance_pf,
570            startup_time_us=startup_time_us,
571        )
572
573    @classmethod
574    def from_json(cls: "HFXOConfig", data: dict) -> "HFXOConfig":
575        hfxo = data["hfxo"]
576
577        builtin_load_capacitors = hfxo["builtInLoadCapacitors"]
578        if builtin_load_capacitors:
579            builtin_load_capacitance_pf = hfxo["builtInLoadCapacitancePf"]
580        else:
581            builtin_load_capacitance_pf = None
582
583        return cls(
584            mode=HFXOMode[hfxo["mode"]],
585            builtin_load_capacitors=builtin_load_capacitors,
586            builtin_load_capacitance_pf=builtin_load_capacitance_pf,
587            startup_time_us=hfxo["startupTimeUs"],
588        )
589
590    def to_raw(self, bicr_spec: ET.Element, buf: bytearray):
591        hfxo_config = Register(bicr_spec, "HFXO.CONFIG", buf)
592        hfxo_startuptime = Register(bicr_spec, "HFXO.STARTUPTIME", buf)
593
594        hfxo_config.enum_set("MODE", self.mode.value)
595        hfxo_startuptime["TIME"] = self.startup_time_us
596
597        if self.builtin_load_capacitors:
598            hfxo_config["LOADCAP"] = int(self.builtin_load_capacitance_pf / 0.25)
599        else:
600            hfxo_config.enum_set("LOADCAP", "External")
601
602    def to_json(self, buf: dict):
603        buf["hfxo"] = {
604            "mode": self.mode.name,
605            "builtInLoadCapacitors": self.builtin_load_capacitors,
606            "startupTimeUs": self.startup_time_us,
607        }
608
609        if self.builtin_load_capacitors:
610            buf["hfxo"]["builtInLoadCapacitancePf"] = self.builtin_load_capacitance_pf
611
612
613@dataclass
614class BICR:
615    power: PowerConfig
616    ioport_power: IoPortPowerConfig
617    ioport_impedance: IoPortImpedanceConfig
618    lfosc: LFOSCConfig
619    hfxo: HFXOConfig
620
621    @classmethod
622    def from_raw(cls: "BICR", bicr_spec: ET.Element, data: bytes) -> "BICR":
623        return cls(
624            power=PowerConfig.from_raw(bicr_spec, data),
625            ioport_power=IoPortPowerConfig.from_raw(bicr_spec, data),
626            ioport_impedance=IoPortImpedanceConfig.from_raw(bicr_spec, data),
627            lfosc=LFOSCConfig.from_raw(bicr_spec, data),
628            hfxo=HFXOConfig.from_raw(bicr_spec, data),
629        )
630
631    @classmethod
632    def from_json(cls: "BICR", data: dict) -> "BICR":
633        return cls(
634            power=PowerConfig.from_json(data),
635            ioport_power=IoPortPowerConfig.from_json(data),
636            ioport_impedance=IoPortImpedanceConfig.from_json(data),
637            lfosc=LFOSCConfig.from_json(data),
638            hfxo=HFXOConfig.from_json(data),
639        )
640
641    def to_raw(self, bicr_spec: ET.Element, buf: bytearray):
642        self.power.to_raw(bicr_spec, buf)
643        self.ioport_power.to_raw(bicr_spec, buf)
644        self.ioport_impedance.to_raw(bicr_spec, buf)
645        self.lfosc.to_raw(bicr_spec, buf)
646        self.hfxo.to_raw(bicr_spec, buf)
647
648    def to_json(self, buf: dict):
649        self.power.to_json(buf)
650        self.ioport_power.to_json(buf)
651        self.ioport_impedance.to_json(buf)
652        self.lfosc.to_json(buf)
653        self.hfxo.to_json(buf)
654
655
656if __name__ == "__main__":
657    parser = argparse.ArgumentParser(allow_abbrev=False)
658    parser.add_argument("-i", "--input", type=Path, required=True, help="Input file")
659    parser.add_argument("-s", "--svd", type=Path, help="SVD file")
660    group = parser.add_mutually_exclusive_group(required=True)
661    group.add_argument("-o", "--output", type=Path, help="Output file")
662    group.add_argument("-l", "--list", action="store_true", help="List BICR options")
663    args = parser.parse_args()
664
665    if args.input.suffix == ".hex" or (args.output and args.output.suffix == ".hex"):
666        if not args.svd:
667            sys.exit("SVD file is required for hex files")
668
669        bicr_spec = ET.parse(args.svd).getroot().find(".//peripheral[name='BICR_NS']")
670
671    if args.input.suffix == ".hex":
672        ih = IntelHex()
673        ih.loadhex(args.input)
674        bicr = BICR.from_raw(bicr_spec, ih.tobinstr())
675    elif args.input.suffix == ".json":
676        with open(args.input) as f:
677            data = json.load(f)
678        bicr = BICR.from_json(data)
679    else:
680        sys.exit("Unsupported input file format")
681
682    if args.output:
683        if args.output.suffix == ".hex":
684            bicr_address = int(bicr_spec.find("baseAddress").text, 0)
685            last_reg = Register(bicr_spec, "TAMPC.ACTIVESHIELD")
686            bicr_size = last_reg.offset + last_reg.size
687
688            buf = bytearray([0xFF] * bicr_size)
689            bicr.to_raw(bicr_spec, buf)
690
691            ih = IntelHex()
692            ih.frombytes(buf, offset=bicr_address)
693            ih.tofile(args.output, format="hex")
694        elif args.output.suffix == ".json":
695            buf = dict()
696            bicr.to_json(buf)
697
698            with open(args.output, "w") as f:
699                json.dump(buf, f, indent=4)
700        else:
701            sys.exit("Unsupported output file format")
702    elif args.list:
703        pprint(bicr)
704