1#!/usr/bin/env python 2 3""" 4Copyright (c) 2024 Silicon Laboratories Inc. 5 6SPDX-License-Identifier: Apache-2.0 7""" 8 9import argparse 10import cmsis_svd 11import datetime 12import lxml 13import re 14import shutil 15import tempfile 16import urllib.request 17import zipfile 18 19from pathlib import Path 20 21import cmsis_svd.parser 22 23PIN_TOOL_URL = "https://github.com/SiliconLabs/simplicity_sdk/releases/download/v2024.6.2/pintool.zip" 24CMSIS_PACK_URL = "https://www.silabs.com/documents/public/cmsis-packs/SiliconLabs.GeckoPlatform_FAMILY_DFP.2024.6.0.pack" 25 26# Families to parse to produce generic pinout header 27FAMILIES = { 28 "xg21": ["efr32mg21", "efr32bg21", "mgm21", "bgm21"], 29 "xg22": ["efr32mg22", "efr32bg22", "efr32fg22", "mgm22", "bgm22", "efm32pg22"], 30 "xg23": ["efr32fg23", "efr32sg23", "efr32zg23", "zgm23", "efm32pg23"], # "fgm23", 31 "xg24": ["efr32mg24", "efr32bg24", "mgm24", "bgm24"], 32 "xg25": ["efr32fg25"], 33 "xg26": ["efr32mg26", "efr32bg26"], 34 "xg27": ["efr32mg27", "efr32bg27"], 35 "xg28": ["efr32fg28", "efr32sg28", "efr32zg28", "efm32pg28"], 36 "xg29": ["efr32bg29"], 37} 38ABUSES = { 39 "xg21": "platform/Device/SiliconLabs/EFR32MG21/Include/efr32mg21_gpio.h", 40 "xg22": "platform/Device/SiliconLabs/EFR32BG22/Include/efr32bg22_gpio.h", 41 "xg23": "platform/Device/SiliconLabs/EFR32FG23/Include/efr32fg23_gpio.h", 42 "xg24": "platform/Device/SiliconLabs/EFR32MG24/Include/efr32mg24_gpio.h", 43 "xg25": "platform/Device/SiliconLabs/EFR32FG25/Include/efr32fg25_gpio.h", 44 "xg26": "platform/Device/SiliconLabs/EFR32MG26/Include/efr32mg26_gpio.h", 45 "xg27": "platform/Device/SiliconLabs/EFR32BG27/Include/efr32bg27_gpio.h", 46 "xg28": "platform/Device/SiliconLabs/EFR32FG28/Include/efr32fg28_gpio.h", 47 "xg29": "platform/Device/SiliconLabs/EFR32BG29/Include/efr32bg29_gpio.h", 48} 49 50# Certain peripherals have different names in SVD and Pin Tool data; rename the SVD peripheral 51PERIPHERAL_ALIAS = { 52 "FRC": "PTI", 53 "LETIMER": "LETIMER0", 54 "SYXO0": "HFXO0", 55} 56 57# Certain signals have different names in SVD and Pin Tool data; rename the SVD signal 58SIGNAL_ALIAS = { 59 "CCC0": "CDTI0", 60 "CCC1": "CDTI1", 61 "CCC2": "CDTI2", 62 "CCC3": "CDTI3", 63 "USART1::SCLK": "CLK", 64} 65 66# Certain signals have different names in SVD and Pin Tool data; rename the Pin Tool signal 67PT_SIGNAL_ALIAS = { 68 "ACMPOUT": "DIGOUT", 69 "COLOUT0": "COL_OUT_0", 70 "COLOUT1": "COL_OUT_1", 71 "COLOUT2": "COL_OUT_2", 72 "COLOUT3": "COL_OUT_3", 73 "COLOUT4": "COL_OUT_4", 74 "COLOUT5": "COL_OUT_5", 75 "COLOUT6": "COL_OUT_6", 76 "COLOUT7": "COL_OUT_7", 77 "ROWSENSE0": "ROW_SENSE_0", 78 "ROWSENSE1": "ROW_SENSE_1", 79 "ROWSENSE2": "ROW_SENSE_2", 80 "ROWSENSE3": "ROW_SENSE_3", 81 "ROWSENSE4": "ROW_SENSE_4", 82 "ROWSENSE5": "ROW_SENSE_5", 83 "ANTROLLOVER": "ANT_ROLL_OVER", 84 "ANTRR0": "ANT_RR0", 85 "ANTRR1": "ANT_RR1", 86 "ANTRR2": "ANT_RR2", 87 "ANTRR3": "ANT_RR3", 88 "ANTRR4": "ANT_RR4", 89 "ANTRR5": "ANT_RR5", 90 "ANTSWEN": "ANT_SW_EN", 91 "ANTSWUS": "ANT_SW_US", 92 "ANTTRIG": "ANT_TRIG", 93 "ANTTRIGSTOP": "ANT_TRIG_STOP", 94 "BUFOUTREQINASYNC": "BUFOUT_REQ_IN_ASYNC", 95 "USBVBUSSENSE": "USB_VBUS_SENSE", 96} 97 98# Expected offset of DBGROUTEPEN register across all of Series 2. 99# Used as base address of pinctrl device tree node. 100PINCTRL_GPIO_OFFSET = 1088 101 102 103class Peripheral: 104 def __init__(self, name, offset): 105 self.name = name 106 self.offset = offset 107 self.signals = [] 108 109 def max_signal_len(self): 110 return max(len(s.name) for s in self.signals) 111 112 def set_signal_enable(self, name, bit): 113 for signal in self.signals: 114 if signal.name == name: 115 break 116 else: 117 signal = Signal(name, self) 118 self.signals.append(signal) 119 120 signal.have_enable = True 121 signal.enable = bit 122 123 def set_signal_route(self, name, offset): 124 for signal in self.signals: 125 if signal.name == name: 126 break 127 else: 128 signal = Signal(name, self) 129 self.signals.append(signal) 130 131 signal.route = offset - self.offset 132 133 134class Signal: 135 def __init__(self, name, peripheral): 136 self.peripheral = peripheral 137 self.name = name 138 self.route = None 139 self.have_enable = False 140 self.enable = 0 141 self.pinout = {} 142 143 def display_name(self): 144 return f"{self.peripheral.name}_{self.name}" 145 146 147def download_pin_tool_data(path: Path) -> None: 148 """ 149 Download Pin Tool zip file from SiSDK release artifact 150 """ 151 dst = path / "pin_tool" 152 if dst.exists(): 153 print("Skipping download of Pin Tool data, already exists") 154 return 155 print("Downloading Pin Tool data") 156 with urllib.request.urlopen(PIN_TOOL_URL) as response: 157 with tempfile.NamedTemporaryFile() as tmp_file: 158 shutil.copyfileobj(response, tmp_file) 159 160 with zipfile.ZipFile(tmp_file, 'r') as zip: 161 zip.extractall(dst) 162 163 164def download_cmsis_pack(path: Path, family: str) -> None: 165 """ 166 Download CMSIS Pack containing SVD files for a given family 167 """ 168 dst = path / "pack" / family 169 if dst.exists(): 170 print(f"Skipping download of CMSIS Pack for {family}, already exists") 171 return 172 print(f"Downloading CMSIS Pack for {family}") 173 with urllib.request.urlopen(CMSIS_PACK_URL.replace("FAMILY", family.upper())) as response: 174 with tempfile.NamedTemporaryFile() as tmp_file: 175 shutil.copyfileobj(response, tmp_file) 176 177 with zipfile.ZipFile(tmp_file, 'r') as zip: 178 zip.extractall(dst) 179 180 181def parse_svd(peripherals, path: Path, family: str) -> None: 182 for svd_path in (path / "pack" / family / "SVD" / family.upper()).glob("*.svd"): 183 print(f"Parsing SVD for {svd_path.stem}") 184 parser = cmsis_svd.parser.SVDParser.for_xml_file(svd_path) 185 gpio: cmsis_svd.parser.SVDPeripheral = next(filter(lambda p: p.name == "GPIO_NS", parser.get_device().peripherals)) 186 for reg in gpio.registers: 187 if reg.name == "DBGROUTEPEN": 188 assert PINCTRL_GPIO_OFFSET == reg.address_offset 189 190 reg_offset_word = (reg.address_offset - PINCTRL_GPIO_OFFSET) // 4 191 192 if reg.name.endswith("_ROUTEEN"): 193 peripheral = reg.name[:-8] 194 peripheral = PERIPHERAL_ALIAS.get(peripheral, peripheral) 195 if peripheral not in peripherals: 196 peripherals[peripheral] = Peripheral(peripheral, reg_offset_word) 197 198 for field in reg.fields: 199 if field.name.endswith("PEN"): 200 signal = field.name[:-3] 201 signal = SIGNAL_ALIAS.get(signal, signal) 202 signal = SIGNAL_ALIAS.get(f"{peripheral}::{signal}", signal) 203 peripherals[peripheral].set_signal_enable(signal, field.bit_offset) 204 205 if reg.name.endswith("ROUTE"): 206 peripheral, signal = reg.name.split("_", 1) 207 peripheral = PERIPHERAL_ALIAS.get(peripheral, peripheral) 208 signal = signal[:-5] 209 signal = SIGNAL_ALIAS.get(signal, signal) 210 signal = SIGNAL_ALIAS.get(f"{peripheral}::{signal}", signal) 211 212 if peripheral not in peripherals: 213 peripherals[peripheral] = Peripheral(peripheral, reg_offset_word) 214 215 peripherals[peripheral].set_signal_route(signal, reg_offset_word) 216 217 218def parse_pin_tool(peripherals, path: Path, family: str): 219 for pin_tool in (path / "pin_tool" / "platform" / "hwconf_data" / "pin_tool" / family).glob("*/PORTIO.portio"): 220 print(f"Parsing Pin Tool for {pin_tool.parent.stem}") 221 with open(pin_tool, 'r') as f: 222 tree = lxml.etree.parse(f) 223 224 for peripheral in peripherals.values(): 225 for signal in peripheral.signals: 226 pt_signal = PT_SIGNAL_ALIAS.get(signal.name, signal.name) 227 228 if peripheral.name == "PRS0": 229 pt_peripheral = f"PRS.{signal.name}" 230 pt_signal_prefix = "PRS" 231 else: 232 pt_peripheral = peripheral.name 233 pt_signal_prefix = peripheral.name 234 235 for node in tree.getroot().xpath(f'portIo/pinRoutes/module[@name="{pt_peripheral}"]/selector[@name="{pt_signal_prefix}_{pt_signal}"]'): 236 for loc in node.xpath(f'route[@name="{pt_signal}"]/location'): 237 port = int(loc.attrib["portBankIndex"]) 238 pin = int(loc.attrib["pinIndex"]) 239 if port not in signal.pinout: 240 signal.pinout[port] = set() 241 signal.pinout[port].add(pin) 242 243 break 244 else: 245 print(f"WARN: No Pin Tool match for {signal.display_name()} for {pin_tool.parent.stem}") 246 247 248def write_header(path: Path, family, peripherals: dict, abuses: list) -> None: 249 """ 250 Write DT binding header containing DBUS routing data for pinctrl use 251 """ 252 lines = [ 253 "/*", 254 f" * Copyright (c) {datetime.date.today().year} Silicon Laboratories Inc.", 255 " * SPDX-License-Identifier: Apache-2.0", 256 " *", 257 f" * Pin Control for Silicon Labs {family.upper()} devices", 258 " *", 259 f" * This file was generated by the script {Path(__file__).name} in the hal_silabs module.", 260 " * Do not manually edit.", 261 " */", 262 "", 263 f"#ifndef ZEPHYR_DT_BINDINGS_PINCTRL_SILABS_{family.upper()}_PINCTRL_H_", 264 f"#define ZEPHYR_DT_BINDINGS_PINCTRL_SILABS_{family.upper()}_PINCTRL_H_", 265 "", 266 "#include <dt-bindings/pinctrl/silabs-pinctrl-dbus.h>", 267 "", 268 ] 269 270 # Emit generic peripheral macros 271 for peripheral in peripherals.values(): 272 have_content = False 273 for signal in peripheral.signals: 274 if signal.route is not None: 275 pad = peripheral.max_signal_len() - len(signal.name) + 1 276 lines.append(f"#define SILABS_DBUS_{signal.display_name()}(port, pin){' ' * pad}" 277 f"SILABS_DBUS(port, pin, {peripheral.offset}, {int(signal.have_enable)}, " 278 f"{signal.enable}, {signal.route})") 279 have_content = True 280 else: 281 print(f"WARN: No route register for {signal.display_name()}") 282 if have_content: 283 lines.append("") 284 285 # Emit pin-specific macros using peripheral macros 286 for peripheral in peripherals.values(): 287 have_content = False 288 for signal in peripheral.signals: 289 for port, pins in signal.pinout.items(): 290 for pin in sorted(pins): 291 pad = peripheral.max_signal_len() - len(signal.name) + 1 292 lines.append(f"#define {signal.display_name()}_P{chr(65 + port)}{pin}{' ' * pad}" 293 f"SILABS_DBUS_{signal.display_name()}(0x{port:x}, 0x{pin:x})") 294 have_content = True 295 if have_content: 296 lines.append("") 297 298 # Emit analog buses 299 max_len = 0 300 for abus in abuses: 301 curr_len = len(abus["bus_name"]) + len(abus["peripheral"]) 302 if curr_len > max_len: 303 max_len = curr_len 304 for abus in abuses: 305 curr_len = len(abus["bus_name"]) + len(abus["peripheral"]) 306 lines.append(f"#define ABUS_{abus["bus_name"]}_{abus["peripheral"]}{' ' * (max_len - curr_len + 1)}" 307 f"SILABS_ABUS(0x{abus["base_offset"]:x}, 0x{abus["parity"]:x}, 0x{abus["value"]:x})") 308 lines.append("") 309 310 lines.append(f"#endif /* ZEPHYR_DT_BINDINGS_PINCTRL_SILABS_{family.upper()}_PINCTRL_H_ */") 311 lines.append("") 312 path.mkdir(parents=True, exist_ok=True) 313 (path / f"{family}-pinctrl.h").write_text("\n".join(lines)) 314 315def parse_abus(file: Path) -> list: 316 offset_map = { 317 "EVEN0": 0, 318 "EVEN1": 1, 319 "ODD0": 2, 320 "ODD1": 3, 321 } 322 peripheral_map = { 323 "ADC0": "IADC0", 324 } 325 abuses = [] 326 with file.open() as f: 327 for line in f: 328 if m := re.match(r"#define _GPIO_([A-Z])[A-Z]?BUSALLOC_([A-Z]+(EVEN\d|ODD\d))_([^\s]+)\s+0x(.+)UL", line): 329 if m.group(4) not in ["DEFAULT", "TRISTATE", "MASK"]: 330 abuses.append({ 331 "base_offset": ord(m.group(1)) - 65, 332 "bus_name": m.group(2), 333 "parity": offset_map[m.group(3)], 334 "peripheral": peripheral_map.get(m.group(4), m.group(4)), 335 "value": int(m.group(5), base=16), 336 }) 337 338 return abuses 339 340if __name__ == "__main__": 341 parser = argparse.ArgumentParser(description="Generate headers for Pinctrl for Series 2 devices. " 342 "The headers are used from DeviceTree, and represent every " 343 "allowed pin selection for every digital bus signal as a DT " 344 "compatible macro.") 345 parser.add_argument("--workdir", "-w", default=Path(__file__).parent.absolute() / "cache", 346 type=Path, help="Working directory to store downloaded Pin Tool and " 347 "CMSIS-Pack artifacts.") 348 parser.add_argument("--sdk", "-s", default=Path(__file__).parent.parent.absolute() / "simplicity_sdk", 349 type=Path, help="SDK directory.") 350 parser.add_argument("--out", "-o", default=(Path(__file__).parent.absolute() / "out"), type=Path, 351 help="Output directory for generated bindings. Defaults to the directory " 352 "./out relative to the script. Set to $ZEPHYR_BASE/include/zephyr/" 353 "dt-bindings/pinctrl/silabs/ to directly generate output into the expected " 354 "location within the Zephyr main tree.") 355 parser.add_argument("--family", "-f", default="xg24", choices=FAMILIES.keys(), 356 help="Device family to generate pinctrl bindings for. Defaults to xg24 if " 357 "not set.") 358 args = parser.parse_args() 359 360 download_pin_tool_data(args.workdir) 361 362 peripherals = {} 363 364 for family in FAMILIES[args.family]: 365 download_cmsis_pack(args.workdir, family) 366 # Find DBUS register offsets for all peripheral signals from SVD 367 parse_svd(peripherals, args.workdir, family) 368 # Add available pins for all peripheral signals from Pin Tool data 369 parse_pin_tool(peripherals, args.workdir, family) 370 371 abuses = parse_abus(args.sdk / ABUSES[args.family]) 372 373 write_header(args.out, args.family, peripherals, abuses) 374