#!/usr/bin/env python3 """ This script generates the imxrt-iomuxc pad API from an i.MX RT SVD file. Consider using the patched SVD files provided by the imxrt-ral project. Usage: python3 iomuxc.py path/to/imxrt.svd | rustfmt > path/to/output.rs """ import re import sys import xml.etree.ElementTree as ET from collections import defaultdict NEW_DOCSTRING = r""" /// Take all pads from this group /// /// # Safety /// /// You may safely call this once to acquire all of the pads. /// Subsequent calls may return pads that are mutably aliased /// elsewhere. Consider calling new() at the start of your program.""" ERASE_DOCSTRING = r""" /// Erase all of the pads /// /// The return type is an array, where the index indicates the /// pad offset from the start of the group. For example, AD_B0_03 /// would be referenced as erased_pads\[3\]. /// /// See `ErasedPads` for more information.""" class GpioImpl: """A pad's GPIO implementation.""" __slots__ = [ "alt", "module", "offset", ] def __init__(self, alt, module, offset): self.alt = alt self.module = module self.offset = offset def extract_pads(iomuxc): base_address = int(iomuxc.find("./baseAddress").text, 16) # Collect MUX and PAD absolute register addresses. pads = defaultdict(dict) for register in iomuxc.findall("./registers/register"): name = register.find("./name").text if "SW_MUX_CTL_PAD_" in name: name = name.replace("SW_MUX_CTL_PAD_", "") offset = int(register.find("./addressOffset").text, 16) pads[name]["MUX"] = base_address + offset # Figure out the associated GPIO from the ALTs. mux_field = register.find("./fields/field[name='MUX_MODE']") if mux_field is None: continue for alt in mux_field.findall("./enumeratedValues/enumeratedValue"): desc = alt.find("./description").text # This is the path taken on nearly all i.MX RT variants... if gpio_match := re.search(r"GPIO\d{1,2}_IO\d{2}", desc): gpio_text = gpio_match.group(0) [gpio_module, gpio_offset] = re.findall(r"\d+", gpio_text) gpio_module = int(gpio_module) gpio_offset = int(gpio_offset) # ...except for the 1010, which references "GPIO1_IOxx" # as "GPIOMUX_IOxx". elif gpio_match := re.search(r"GPIOMUX_IO\d{2}", desc): gpio_text = gpio_match.group(0) [gpio_offset] = re.findall(r"\d+", gpio_text) gpio_module = 1 gpio_offset = int(gpio_offset) # But wait! The 1176 SVD has a third form, "GPIO_MUXx_IOyz", # which is mixed with the first form... elif gpio_match := re.search(r"GPIO_MUX\d_IO\d{2}", desc): gpio_text = gpio_match.group(0) [gpio_module, gpio_offset] = re.findall(r"\d+", gpio_text) gpio_module = int(gpio_module) gpio_offset = int(gpio_offset) else: # There's no (expected) GPIO alt. This path is handled # later during code generation. continue alt_value = int(alt.find("./value").text, 16) gpio_impls = pads[name].get("GPIO", []) gpio_impls.append(GpioImpl(alt_value, gpio_module, gpio_offset)) pads[name]["GPIO"] = gpio_impls elif "SW_PAD_CTL_PAD_" in name: name = name.replace("SW_PAD_CTL_PAD_", "") offset = int(register.find("./addressOffset").text, 16) pads[name]["PAD"] = base_address + offset # Sanity check. for name, registers in pads.items(): assert "PAD" in registers and "MUX" in registers return pads def iomuxc(path): tree = ET.parse(path) root = tree.getroot() iomuxc = root.find("./peripherals/peripheral[name='IOMUXC']") iomuxc_lpsr = root.find("./peripherals/peripheral[name='IOMUXC_LPSR']") pads = extract_pads(iomuxc) if iomuxc_lpsr is not None: pads |= extract_pads(iomuxc_lpsr) # Create pad groups. groups = defaultdict(list) for name in pads.keys(): group = name[: -len("_01")] groups[group].append(name) # Generate Rust modules print("// Generated by iomuxc.py") print("#![allow(non_camel_case_types)]") print() for group, pad_names in groups.items(): print(f"/// Pads with the prefix {group}.") print(f"pub mod {group.lower()} {{") print() # Pad type aliases, constants. for pad_name in pad_names: registers = pads[pad_name] mux_addr = hex(registers["MUX"]) pad_addr = hex(registers["PAD"]) mux_reg_name = f"{pad_name}_MUX_ADDR" pad_reg_name = f"{pad_name}_PAD_ADDR" print(f"const {mux_reg_name}: u32 = {mux_addr};") print(f"const {pad_reg_name}: u32 = {pad_addr};") print(f"pub type {pad_name} = crate::Pad<{mux_reg_name}, {pad_reg_name}>;") # impl gpio::Pin for gpio_impl in registers.get("GPIO", []): print() print(f"impl crate::gpio::Pin<{gpio_impl.module}> for {pad_name} {{") print(f"const ALT: u32 = {gpio_impl.alt};") print(f"const OFFSET: u32 = {gpio_impl.offset};") print("}") if registers.get("GPIO") is None: print(f"// {pad_name} does not have any GPIO alternates.") print() # Pads struct print(f"/// All pads with prefix {group}.") print("pub struct Pads {") for pad_name in pad_names: pad_number = pad_name[-len("01") :] print(f"pub p{pad_number}: {pad_name},") print("}") # ErasedPads print(f"/// Erased pads with prefix {group}.") print("///") print("/// Use [`Pads::erase()`] to get an `ErasedPads` instance.") print(f"pub type ErasedPads = [crate::ErasedPad; {len(pad_names)}];") # Pads impl print("impl Pads {") print(NEW_DOCSTRING) print("#[inline] pub const unsafe fn new() -> Self { Self {") for pad_name in pad_names: pad_number = pad_name[-len("01") :] print(f"p{pad_number}: {pad_name}::new(),") print("} }") print(ERASE_DOCSTRING) print("#[inline] pub const fn erase(self) -> ErasedPads { [") for pad_name in sorted(pad_names): pad_number = pad_name[-len("01") :] print(f"self.p{pad_number}.erase(),") print("] }") print("}") print("}") print() # Generate top-level Pads struct print("/// All of the pads.") print("pub struct Pads {") for group in groups.keys(): print(f"pub {group.lower()}: {group.lower()}::Pads,") print("}") print() print("impl Pads {") print(NEW_DOCSTRING) print("#[inline] pub const unsafe fn new() -> Self { Self {") for group in groups.keys(): print(f"{group.lower()}: {group.lower()}::Pads::new(),") print("} }") print(ERASE_DOCSTRING) print("#[inline] pub const fn erase(self) -> ErasedPads { ErasedPads {") for group in groups.keys(): print(f"{group.lower()}: self.{group.lower()}.erase(),") print("} }") print("}") print() # Generate top-level ErasedPads struct print("/// All erased pads.") print("pub struct ErasedPads {") for group in groups.keys(): print(f"pub {group.lower()}: {group.lower()}::ErasedPads,") print("}") if __name__ == "__main__": iomuxc(sys.argv[1])