1""" 2Implements a module to parse iMX.RT MEX configuration files to extract pin 3configuration groups, and transform them into pinctrl groups suitable for use 4in Zephyr. 5""" 6 7 8import collections 9import xml.etree.ElementTree as ET 10import re 11import os 12import pathlib 13import logging 14import __main__ 15 16# MEX file has a default namespace, map it here 17NAMESPACES = {'mex' : 'http://mcuxpresso.nxp.com/XSD/mex_configuration_14'} 18 19class Peripheral: 20 """ 21 Internal class used to represent a peripheral 22 """ 23 def __init__(self, peripheral_xml, register_xml = None): 24 """ 25 Initializes peripheral object using peripheral XML structure 26 @param peripheral_xml: peripheral XML object - parsed from registers.xml 27 @param register: path to XML file with register description for this peripheral 28 """ 29 self._name = peripheral_xml.attrib['name'] 30 self._fullname = peripheral_xml.attrib['full_name'] 31 # Peripheral address space in bytes 32 self._size = int(peripheral_xml.attrib['size']) 33 self._base_addr = int(peripheral_xml.find('base_address').attrib['addr'], 0) 34 # Do not load registers now, just record the filename. This 35 # Lazy loading speeds up the script, since we only need registers from 36 # some peripherals 37 if register_xml is not None: 38 self._registers = {} 39 self._register_file = register_xml 40 else: 41 self._registers = None 42 self._register_file = None 43 44 def _load_registers(self): 45 """ 46 Loads registers from register XML file 47 """ 48 if (self._register_file is not None) and (self._registers == {}): 49 # Parse registers 50 try: 51 if not pathlib.Path(self._register_file).exists(): 52 raise RuntimeError(f"Register file {self._register_file} does not exist") 53 register_xml = ET.parse(self._register_file) 54 for register_def in register_xml.findall('register'): 55 reg = Register(register_def) 56 self._registers[reg.get_name()] = reg 57 # Parse register template definitions to locate remaining 58 # Register definitions 59 reg_templates = {} 60 for template in register_xml.findall('reg_template'): 61 reg_templates[template.get('rid')] = template 62 for reg_instance in register_xml.findall('reg_instance'): 63 reg = TemplatedRegister(reg_templates[reg_instance.get('rid')], reg_instance) 64 self._registers[reg.get_name()] = reg 65 66 except ET.ParseError: 67 raise RuntimeError(f"Register file {self._register_file} is not valid XML") 68 elif self._register_file is None: 69 raise RuntimeError("Cannot load registers, no xml file path provided") 70 71 def __repr__(self): 72 """ 73 Generate string representation of the object 74 """ 75 if self._registers: 76 return ("Peripheral(%s, 0x%X, %d Regs)" % 77 (self._name, self._base_addr, len(self._registers))) 78 return ("Peripheral(%s, 0x%X)" % 79 (self._name, self._base_addr)) 80 81 def get_name(self): 82 """ 83 Gets peripheral name 84 """ 85 return self._name 86 87 def get_size(self): 88 """ 89 Gets size of peripheral address region in bytes 90 """ 91 return self._size 92 93 def get_base(self): 94 """ 95 Gets base address of peripheral 96 """ 97 return self._base_addr 98 99 def get_register(self, reg_name): 100 """ 101 Get register object within peripheral by name 102 @param reg_name: name of register to get 103 """ 104 self._load_registers() 105 return self._registers[reg_name] 106 107 def get_reg_addr(self, reg_name): 108 """ 109 Gets full address of register in peripheral 110 @param reg_name: name of register to calculate address for 111 """ 112 self._load_registers() 113 return self._base_addr + self._registers[reg_name].get_offset() 114 115 116class Register: 117 """ 118 Internal class used to represent a register in a peripheral 119 """ 120 def __init__(self, register_xml): 121 """ 122 Constructs a register object from provided register xml data 123 """ 124 self._name = register_xml.attrib['name'] 125 self._offset = int(register_xml.attrib['offset'], 0) 126 # Build mapping of register field values to descriptions 127 self._bit_field_map = {} 128 for bit_field in register_xml.findall('bit_field'): 129 bit_field_map = {} 130 for bit_field_value in bit_field.findall('bit_field_value'): 131 # Some iMX8 fields have a ?, remove that 132 bit_field_str = bit_field_value.attrib['value'].strip('?') 133 field_val = int(bit_field_str, 0) 134 bit_field_map[field_val] = bit_field_value.attrib 135 # Save bit field mapping 136 self._bit_field_map[bit_field.attrib['name']] = bit_field_map 137 138 def __repr__(self): 139 """ 140 Generate string representation of the object 141 """ 142 return "Register(%s, 0x%X)" % (self._name, self._offset) 143 144 145 def get_name(self): 146 """ 147 Get the name of the register 148 """ 149 return self._name 150 151 def get_offset(self): 152 """ 153 Get the offset of this register from the base 154 """ 155 return self._offset 156 157 def get_bit_field_value_description(self, bit_field, value): 158 """ 159 Get human-readable description of the value a bit field in the register 160 represents 161 @param bit_field: name of register bit field 162 @param value: value assigned to bit field 163 @return description of effect that value has on register 164 """ 165 return self._bit_field_map[bit_field][value]['description'] 166 167 def get_bit_fields(self): 168 """ 169 Get list of all bit fields present in register 170 """ 171 return self._bit_field_map.keys() 172 173 174class TemplatedRegister(Register): 175 """ 176 Subclass of standard register, that implements support for templated 177 register definitions in a manner compatible with the standard register 178 class instance. 179 """ 180 def __init__(self, template_xml, instance_xml): 181 """ 182 Constructs a register instance based off the register template XML 183 and register instance XML 184 """ 185 self._values = instance_xml.get('vals').split(' ') 186 self._name = self._sub_template(template_xml.attrib['name']) 187 self._offset = int(self._sub_template(template_xml.attrib['offset']), 0) 188 # Build mapping of register field values to descriptions 189 self._bit_field_map = {} 190 for bit_field in template_xml.findall('bit_field'): 191 bit_field_map = {} 192 for bit_field_value in bit_field.findall('bit_field_value'): 193 # Some iMX8 fields have a ?, remove that 194 bit_field_str = bit_field_value.attrib['value'].strip('?') 195 field_val = int(bit_field_str, 0) 196 bit_field_map[field_val] = bit_field_value.attrib 197 # Save bit field mapping 198 self._bit_field_map[bit_field.attrib['name']] = bit_field_map 199 200 def _sub_template(self, string): 201 """ 202 Uses string substitution to replace references to template parameter 203 in string with value in a value array. For instance, 204 SW_PAD_CTL_PAD_GPIO_EMC_{1} would become SW_PAD_CTL_PAD_GPIO_EMC_15 205 if values[1] == 15 206 """ 207 for i in range(len(self._values)): 208 string = re.sub(r'\{' + re.escape(str(i)) + r'\}', 209 self._values[i], string) 210 return string 211 212 213 214class SignalPin: 215 """ 216 Internal class representing a signal on the SOC 217 """ 218 def __init__(self, pin, peripheral_map, imx_rt): 219 """ 220 Initializes a SignalPin object 221 @param pin: pin XML object from signal_configuration.xml 222 @param peripheral_map mapping of peripheral names to peripheral objects 223 @param imx_rt: is this signal configuration for an IMX RT part 224 """ 225 self._name = pin.attrib['name'] 226 self._properties = self._get_pin_properties(pin.find('functional_properties')) 227 self._iomuxc_options = {} 228 229 cfg_addr = 0x0 230 pad_name = self._name 231 for prop in pin.findall('functional_properties/functional_property'): 232 cfg_assign_xml = prop.find('state/configuration/assign') 233 if cfg_assign_xml is None: 234 # Not a configurable register. Skip. 235 return 236 match = re.match(r'init_([\w_]+)', cfg_assign_xml.attrib['configuration_step']) 237 periph_name = match.group(1) 238 # See if this property will have the pad configuration address 239 prop_id = prop.attrib.get('id') 240 if (prop_id != 'software_input_on') and (prop_id != 'SION'): 241 match = re.match(re.escape(periph_name) + r'_(\w+)', cfg_assign_xml.attrib['register']) 242 reg_name = match.group(1) 243 match = re.match(r'SW_PAD_CTL_PAD_(\w+)', reg_name) 244 pad_name = match.group(1) 245 cfg_reg = peripheral_map[periph_name].get_register(reg_name) 246 cfg_addr = peripheral_map[periph_name].get_base() + cfg_reg.get_offset() 247 # We have found the pad configuration address. Break. 248 break 249 for connections in pin.findall('connections'): 250 name_part = connections.attrib.get('name_part') 251 connection = connections.find('connection') 252 signal = connection.find('peripheral_signal_ref').attrib['signal'] 253 if imx_rt: 254 name = f"{periph_name}_{pad_name}_{name_part}" 255 else: 256 name = f"{periph_name}_{pad_name}_{signal.upper()}_{name_part}" 257 # Determine the configuration register type. This is needed for 258 # iMX RT11xx series devices 259 cfg_fields = cfg_reg.get_bit_fields() 260 if 'PDRV' in cfg_fields: 261 pin_type = 'pdrv' 262 elif 'ODE_LPSR' in cfg_fields: 263 pin_type = 'lpsr' 264 elif 'ODE_SNVS' in cfg_fields: 265 pin_type = 'snvs' 266 elif 'PUE' in cfg_fields: 267 pin_type = 'pue' 268 else: 269 pin_type = 'unknown' 270 iomux_opt = IOMUXOption(connection, peripheral_map, cfg_addr, name, pin_type) 271 peripheral = connection.find('peripheral_signal_ref').attrib['peripheral'] 272 channel = connection.find('peripheral_signal_ref').attrib.get('channel') 273 if channel is not None: 274 mux_name = f"{peripheral}_{signal}, {channel}" 275 else: 276 mux_name = f"{peripheral}_{signal}" 277 self._iomuxc_options[mux_name] = iomux_opt 278 279 def __repr__(self): 280 """ 281 String representation of object 282 """ 283 return "SignalPin(%s)" % (self._name) 284 285 def __hash__(self): 286 """ 287 Override hash method to return pin name as hash 288 """ 289 return hash(self._name) 290 291 def __eq__(self, obj): 292 """ 293 Like the hash method, we override the eq method to return true if two 294 objects have the same pin name 295 """ 296 return isinstance(obj, SignalPin) and self._name == obj._name 297 298 def __lt__(self, obj): 299 """ 300 Compare objects based on name 301 """ 302 if not isinstance(obj, SignalPin): 303 return True 304 return self._name < obj._name 305 306 def get_name(self): 307 """ 308 Get name of pin 309 """ 310 return self._name 311 312 def get_iomux_connection(self, signal): 313 """ 314 Gets an IOMUXOption object for the relevant signal name 315 @param signal: Signal name on pin to get mux option for 316 """ 317 if signal in self._iomuxc_options: 318 return self._iomuxc_options[signal] 319 return None 320 321 def get_mux_options(self): 322 """ 323 Gets all unique settings for IOMUX on the specific pin 324 """ 325 diff = len(self._iomuxc_options.values()) - len(set(self._iomuxc_options.values())) 326 if diff: 327 logging.warning("Warning: %d mux options dropped", diff) 328 return set(self._iomuxc_options.values()) 329 330 def get_pin_properties(self): 331 """ 332 Gets array of pin property names 333 """ 334 return self._properties.keys() 335 336 def get_pin_property_default(self, prop): 337 """ 338 Gets name of default pin property 339 @param prop: name of pin property 340 """ 341 return self._properties[prop]['default'] 342 343 def get_pin_defaults(self): 344 """ 345 Gets mapping of all pin property names to default value names 346 """ 347 pin_defaults = {} 348 for prop in self.get_pin_properties(): 349 pin_default = self.get_pin_property_default(prop) 350 pin_defaults[prop] = pin_default 351 return pin_defaults 352 353 def get_pin_property_value(self, prop, selection): 354 """ 355 Gets bit value for pin property 356 @param prop: name of pin property 357 @param selection: name of option selected for property 358 """ 359 return self._properties[prop][selection] 360 361 def _get_pin_properties(self, props): 362 """ 363 Builds dictionary with all pin properties 364 @param props: pin function_properties XML object in signal_configuration.xml 365 """ 366 prop_mapping = {} 367 for prop in props.findall('functional_property'): 368 if len(prop.findall('state/configuration/assign')) == 0: 369 # Not configurable property. Skip 370 continue 371 prop_id = prop.attrib['id'] 372 prop_mapping[prop_id] = {} 373 prop_mapping[prop_id]['default'] = prop.attrib['default'] 374 for state in prop.findall('state'): 375 reg_assign = state.find('configuration/assign') 376 bit_value = int(reg_assign.attrib['bit_field_value'], 0) 377 prop_mapping[prop_id][state.attrib['id']] = bit_value 378 return prop_mapping 379 380 381# named tuple for GPIO port/pin 382GPIO = collections.namedtuple('GPIO', 'port pin') 383 384class IOMUXOption: 385 """ 386 Internal class representing an IOMUXC option 387 """ 388 def __init__(self, connection, peripheral_map, cfg_reg, name, pin_type): 389 """ 390 Initializes an IOMUXC option object 391 @param connection: connection XML object from signal_configuration.xml 392 @param peripheral_map: mapping of peripheral names to peripheral objects 393 @param cfg_reg: configuration register for this IOMUXC option 394 @param name: allows caller to override iomuxc name, if it is known 395 @param pin_type: sets pin type value for config register (for RT11xx) 396 """ 397 self._mux = 0 398 self._mux_val = 0 399 self._daisy = 0 400 self._daisy_val = 0 401 self._cfg_reg = cfg_reg 402 self._has_extended_config = False 403 self._has_gpr = False 404 self._extended_config = [] 405 self._name = name 406 self._type = pin_type 407 # Check if this connection controls a GPIO 408 peripheral = connection.find('peripheral_signal_ref').attrib.get('peripheral') 409 channel = connection.find('peripheral_signal_ref').attrib.get('channel') 410 if 'GPIO' in peripheral and channel is not None: 411 match = re.search(r'GPIO(\d+)', peripheral) 412 gpio_port = match.group(1) 413 self._is_gpio = True 414 self._gpio = GPIO(int(gpio_port), int(channel)) 415 else: 416 self._is_gpio = False 417 self._gpio = (0, 0) 418 # Get connection register names 419 for assignment in connection.findall('configuration/assign'): 420 match = re.match(r'init_([\w_]+)', assignment.attrib['configuration_step']) 421 periph_name = match.group(1) 422 match = re.match(re.escape(periph_name) + r'_(\w+)', assignment.attrib['register']) 423 reg_name = match.group(1) 424 full_name = f"{periph_name}_{reg_name}" 425 periph = peripheral_map[periph_name] 426 addr = periph.get_reg_addr(reg_name) 427 value = int(assignment.attrib['bit_field_value'], 0) 428 if assignment.attrib.get('bit_field') == 'DAISY': 429 self._daisy = addr 430 self._daisy_val = value 431 elif assignment.attrib.get('bit_field') == 'MUX_MODE': 432 self._mux = addr 433 self._mux_val = value 434 elif periph_name == 'IOMUXC_GPR': 435 # GPR register can be used as a secondary pinmux selection, 436 # record this setting 437 self._has_gpr = True 438 self._gpr_reg = addr 439 gpr_mask = int(assignment.attrib.get('bit_field_mask'), 0) 440 # Calculate gpr bit shift 441 self._gpr_shift = ((gpr_mask) & -(gpr_mask)).bit_length() - 1 442 self._gpr_val = int(assignment.attrib.get('bit_field_value'), 0) 443 else: 444 # Add register name and bit field value to extra configuration 445 self._has_extended_config = True 446 config = {full_name : assignment.attrib['bit_field_value']} 447 self._extended_config.append(config) 448 449 def __repr__(self): 450 """ 451 String representation of object 452 """ 453 if self._has_extended_config: 454 return "IOMUXOpt(%s, 0x%X = %d, ExtCfg)" % (self._name, self._mux, self._mux_val) 455 elif self._has_gpr: 456 return "IOMUXOpt(%s, 0x%X = %d, GPR)" % (self._name, self._mux, self._mux_val) 457 return "IOMUXOpt(%s, 0x%X = %d)" % (self._name, self._mux, self._mux_val) 458 459 def __hash__(self): 460 """ 461 Override hash method to return the same hash if iomuxc name is the same. 462 This means an object with extended configuration has the same hash as 463 one without it if they have the same MUX, DAISY, and CFG registers. 464 This behavior is desirable because it allows a set of iomuxc registers 465 to be generated with unique iomuxc names 466 """ 467 return hash(self._name) 468 469 def __eq__(self, obj): 470 """ 471 Like the hash method, we override the eq method to return true if two 472 objects have the same iomuxc name 473 """ 474 return isinstance(obj, IOMUXOption) and self._name == obj._name 475 476 def __lt__(self, obj): 477 """ 478 Compare objects based on name 479 """ 480 if not isinstance(obj, IOMUXOption): 481 return True 482 return self._name < obj._name 483 484 def get_name(self): 485 """ 486 Get IOMUXC name 487 """ 488 return self._name 489 490 def get_mux_reg(self): 491 """ 492 Get the mux reg for this iomux option 493 """ 494 return self._mux 495 496 def is_gpio(self): 497 """ 498 return True if this iomux option is for a GPIO 499 """ 500 return self._is_gpio 501 502 def gpio(self): 503 """ 504 Get iomux gpio port and pin as a tuple of (port,pin) 505 only valid if is_gpio is True 506 """ 507 return self._gpio 508 509 def get_mux_val(self): 510 """ 511 Get the mux value for this iomux option 512 """ 513 return self._mux_val 514 515 def get_daisy_reg(self): 516 """ 517 Get the daisy reg for this iomux option 518 """ 519 return self._daisy 520 521 def get_daisy_val(self): 522 """ 523 Get the daisy value for this iomux option 524 """ 525 return self._daisy_val 526 527 def get_cfg_reg(self): 528 """ 529 Get the configuration reg for this iomux option 530 """ 531 return self._cfg_reg 532 533 def has_gpr(self): 534 """ 535 Return true if iomux option has associated GPR configuration requirement 536 """ 537 return self._has_gpr 538 539 def gpr_reg(self): 540 """ 541 If has_gpr() is true, return GPR register address 542 """ 543 return self._gpr_reg 544 545 def gpr_shift(self): 546 """ 547 If has_gpr() is true, return shift on GPR register value 548 """ 549 return self._gpr_shift 550 551 def gpr_val(self): 552 """ 553 If has_gpr() is true, return GPR register value 554 """ 555 return self._gpr_val 556 557 def has_extended_config(self): 558 """ 559 Return true if the iomux option requires extended register configuration 560 """ 561 return self._has_extended_config 562 563 def get_extended_config(self): 564 """ 565 Get any extended configuration for this option 566 """ 567 return self._extended_config 568 569 def get_cfg_type(self): 570 """ 571 Get the configuration type for this option. Currently only relevant 572 for RT11xx SOCs. 573 """ 574 return self._type 575 576class PinGroup: 577 """ 578 Internal class representing pin group 579 """ 580 def __init__(self, function, signal_map, rt): 581 """ 582 Creates a pin group 583 @param function: function xml structure from MEX configuration file 584 @param signal_map: signal mapping, maps signal names to signal file 585 """ 586 self._name = function.attrib.get('name') 587 description = function.find('mex:description', NAMESPACES) 588 pins = function.find('mex:pins', NAMESPACES) 589 if description is not None and description.text is not None: 590 # Replace <br> html tag with newline 591 self._description = description.text.replace("<br/>", "\n") 592 else: 593 self._description = "" 594 # Build dictionary mapping pin properties to pins. This allows us to 595 # group pins based on shared configuration 596 self._pin_groups = collections.defaultdict(lambda: []) 597 for pin in pins: 598 signal_name = pin.attrib.get('pin_signal') 599 if signal_name not in signal_map: 600 logging.warning("Warning: Signal name %s not present in mapping", signal_name) 601 # No way to find mux option 602 continue 603 signal = signal_map[signal_name] 604 mux_name = f"{pin.attrib.get('peripheral')}_{pin.attrib.get('signal')}" 605 iomuxc_conn = signal.get_iomux_connection(mux_name) 606 if iomuxc_conn is None: 607 logging.warning("Warning: Signal name %s has no mux", signal_name) 608 # determine functional properties for each pin 609 defaults = signal.get_pin_defaults() 610 # Get pin overrides 611 features = pin.find('mex:pin_features', NAMESPACES) 612 pin_overrides = {} 613 if features is not None: 614 for feature in pin.find('mex:pin_features', NAMESPACES): 615 pin_overrides[feature.attrib.get('name')] = feature.attrib.get('value') 616 if rt: 617 pin_props = self._props_to_dts(pin_overrides, defaults) 618 else: 619 pin_props = self._imx_props_to_dts(pin_overrides, defaults) 620 self._pin_groups[pin_props].append(iomuxc_conn) 621 622 def __repr__(self): 623 """ 624 Get string representation of the object 625 """ 626 return "PinGroup(%s)" % (self._name) 627 628 def __eq__(self, obj): 629 """ 630 return true if two objects have the same pin group name 631 """ 632 return isinstance(obj, PinGroup) and self._name == obj._name 633 634 def __lt__(self, obj): 635 """ 636 Compare objects based on name 637 """ 638 if not isinstance(obj, PinGroup): 639 return True 640 return self._name < obj._name 641 642 def get_pin_props(self): 643 """ 644 Get all unique pin properties 645 """ 646 return self._pin_groups.keys() 647 648 def get_pins(self, props): 649 """ 650 Get all pins with a provided set of properties 651 @param props: property set 652 """ 653 return self._pin_groups[props] 654 655 def get_description(self): 656 """ 657 Get description of the pin group, if present. If no description present, 658 description will be "" 659 """ 660 return self._description 661 662 def get_name(self): 663 """ 664 Get pin group name 665 """ 666 return self._name 667 668 def _imx_props_to_dts(self, props, defaults): 669 """ 670 Remap dictionary of property names from NXP defined values to 671 Zephyr ones. Valid for iMX8 series parts. 672 """ 673 zephyr_props = [] 674 prop_mapping = { 675 # DSE field name mappings 676 'HIZ': 'disabled', 677 'OHM_255': '255-ohm', 678 'OHM_105': '105-ohm', 679 'OHM_75': '75-ohm', 680 'OHM_85': '85-ohm', 681 'OHM_65': '65-ohm', 682 'OHM_45': '45-ohm', 683 'OHM_40': '40-ohm', 684 # SRE field name mappings 685 'SLOW': 'slow', 686 'MEDIUM': 'medium', 687 'FAST': 'fast', 688 'MAX': 'max', 689 # FSEL field name mappings 690 'Slow': 'slow', 691 'Fast': 'fast', 692 # Alternative FSEL field name mappings for IMX8MM 693 'SLOW0': 'slow', 694 'SLOW1': 'slow', 695 'FAST0': 'fast', 696 'FAST1': 'fast', 697 # Alternative DSE field name mappings for IMX8MP 698 'X1': 'x1', 699 'X2': 'x2', 700 'X4': 'x4', 701 'X6': 'x6', 702 # Alternative DSE field name mappings for IMX8MM 703 'X1_0': '255-ohm', 704 'X1_1': '255-ohm', 705 'X4_0': '105-ohm', 706 'X4_1': '105-ohm', 707 'X2_0': '85-ohm', 708 'X2_1': '85-ohm', 709 'X6_0': '40-ohm', 710 'X6_1': '40-ohm', 711 } 712 # Lambda to convert property names to zephyr formatted strings 713 sanitize = lambda x: "\"" + prop_mapping[x] + "\"" if (x in prop_mapping) else "" 714 # Lambda to get property value or fallback to default 715 # Note if property is not in either dict we fall back to empty string 716 prop_val = lambda x: props[x] if x in props else (defaults[x] if x in defaults else "") 717 # For each property, append the provided override or the default names 718 if prop_val('SION') == 'ENABLED': 719 zephyr_props.append('input-enable') 720 if prop_val('LVTTL') == 'Enabled': 721 zephyr_props.append('nxp,lvttl') 722 if prop_val('HYS') == 'Enable' or prop_val('HYS') == 'Schmitt': 723 zephyr_props.append('input-schmitt-enable') 724 if prop_val('PE') == 'Enabled': 725 # If PE is present, pull down resistor will be available. 726 if prop_val('PUE') == 'Weak_Pull_Up': 727 zephyr_props.append('bias-pull-up') 728 elif prop_val('PUE') == 'Weak_Pull_Down': 729 zephyr_props.append('bias-pull-down') 730 else: 731 # Without PE bit, only pull up resistor is available 732 if prop_val('PUE') == 'Enabled': 733 zephyr_props.append('bias-pull-up') 734 if prop_val('ODE') == 'Enabled' or prop_val('ODE') == 'Open_Drain_Enable': 735 zephyr_props.append('drive-open-drain') 736 if 'SRE' in defaults: 737 zephyr_props.append(f"slew-rate = {sanitize(prop_val('SRE'))}") 738 if 'FSEL' in defaults: 739 zephyr_props.append(f"slew-rate = {sanitize(prop_val('FSEL'))}") 740 if 'DSE' in defaults: 741 zephyr_props.append(f"drive-strength = {sanitize(prop_val('DSE'))}") 742 743 return tuple(zephyr_props) 744 745 746 def _props_to_dts(self, props, defaults): 747 """ 748 Remap dictionary of property names from NXP defined values to 749 Zephyr ones. Valid for iMX.RT 1xxx series parts. 750 @param props: Dictionary of NXP property names and values 751 @param defaults: Dictionary of NXP property names and default pin values 752 @return array of strings suitable for writing to DTS 753 """ 754 zephyr_props = [] 755 prop_mapping = { 756 'MHZ_50': '50-mhz', 757 'MHZ_100_01': '100-mhz', 758 # On some iMX RT10xx SOCs, 150 MHz is mapped to this value. However, 759 # this is not consistent for all iMX RT10xx supported by 760 # config tools. Therefore, we just force both MHZ_100_01 and 761 # MHZ_100 to 100-mhz. 762 'MHZ_100': '100-mhz', 763 'MHZ_200': '200-mhz', 764 'R0': 'r0', 765 'R0_2': 'r0-2', 766 'R0_3': 'r0-3', 767 'R0_4': 'r0-4', 768 'R0_5': 'r0-5', 769 'R0_6': 'r0-6', 770 'R0_7': 'r0-7', 771 'Pull_Down_100K_Ohm': '100k', 772 'Pull_Up_47K_Ohm': '47k', 773 'Pull_Up_100K_Ohm': '100k', 774 'Pull_Up_22K_Ohm': '22k', 775 'Fast': 'fast', 776 'Slow': 'slow', 777 'Normal': 'normal', 778 'High': 'high' 779 } 780 # Lambda to convert property names to zephyr formatted strings 781 sanitize = lambda x: "\"" + prop_mapping[x] + "\"" if (x in prop_mapping) else "" 782 # Lambda to get property value or fallback to default 783 # Note if property is not in either dict we fall back to empty string 784 prop_val = lambda x: props[x] if x in props else (defaults[x] if x in defaults else "") 785 # Check pin defaults and overrides to see if the pin will have a pull or keeper 786 if 'pull_keeper_enable' in defaults: 787 pull_keeper = prop_val('pull_keeper_enable') == 'Enable' 788 else: 789 # RT11xx series has no PKE field, pull/keeper is always enabled 790 pull_keeper = True 791 # config utils maps slow slew rate and fast slew rate incorrectly 792 # (slow slew rate should set 0b1 to SRE field). Switch mapping here. 793 prop_mapping['Slow'] = 'fast' 794 prop_mapping['Fast'] = 'slow' 795 if pull_keeper: 796 # Determine if pull or keeper is selected 797 keeper = prop_val('pull_keeper_select') == 'Keeper' 798 else: 799 zephyr_props.append('bias-disable') 800 keeper = False 801 # For each property, append the provided override or the default names 802 if prop_val('drive_strength') != '': 803 zephyr_props.append(f"drive-strength = {sanitize(prop_val('drive_strength'))}") 804 if prop_val('hysteresis_enable') == 'Enable': 805 zephyr_props.append('input-schmitt-enable') 806 if prop_val('open_drain') == 'Enable': 807 zephyr_props.append('drive-open-drain') 808 if pull_keeper and not keeper: 809 if 'pull_keeper_enable' in defaults: 810 if prop_val('pull_up_down_config') == 'Pull_Down_100K_Ohm': 811 # Pull down the pin 812 zephyr_props.append('bias-pull-down') 813 zephyr_props.append("bias-pull-down-value = " 814 f"{sanitize(prop_val('pull_up_down_config'))}") 815 else: 816 # Pull up the pin 817 zephyr_props.append('bias-pull-up') 818 zephyr_props.append("bias-pull-up-value = " 819 f"{sanitize(prop_val('pull_up_down_config'))}") 820 elif 'pull_up_down_config' in defaults: 821 # RT11xx series has no pullup/pulldown value selection, 822 # only pull up/ pull down 823 if prop_val('pull_up_down_config') == 'Pull_Down': 824 zephyr_props.append('bias-pull-down') 825 else: 826 zephyr_props.append('bias-pull-up') 827 else: 828 # RT1xx series has a second pin register layout, which 829 # uses a different property value for pull up and pull down 830 if prop_val('pull_down_pull_up_config') == 'Pull_Down': 831 zephyr_props.append('bias-pull-down') 832 elif prop_val('pull_down_pull_up_config') == 'Pull_Up': 833 zephyr_props.append('bias-pull-up') 834 else: 835 zephyr_props.append('bias-disable') 836 if 'slew_rate' in defaults: 837 zephyr_props.append(f"slew-rate = {sanitize(prop_val('slew_rate'))}") 838 if 'speed' in defaults: 839 zephyr_props.append(f"nxp,speed = {sanitize(prop_val('speed'))}") 840 if prop_val('software_input_on') == 'Enable': 841 zephyr_props.append('input-enable') 842 return tuple(zephyr_props) 843 844class NXPSdkUtil: 845 """ 846 Class for iMX.RT configuration file parser for Zephyr 847 """ 848 def __init__(self, cfg_root, copyright_header = "", log_level = logging.DEBUG): 849 """ 850 Initialize SDK utilities. 851 Providing a signal_configuration.xml file as well as an iomuxc.h file will enable 852 the class to parse MEX files and generate output DTS 853 @param cfg_root processor configuration folder root 854 @param copyright_header: copyright string to add to any generated file header 855 """ 856 # Validate configuration path 857 cfg_path = pathlib.Path(cfg_root) 858 self._logger = logging.getLogger('') 859 self._logger.setLevel(log_level) 860 if not cfg_path.is_dir(): 861 raise RuntimeError("Provided configuration path must be directory") 862 # Find all required register and signal defintions 863 signal_path = cfg_path / 'signal_configuration.xml' 864 register_path = cfg_path / 'registers/registers.xml' 865 register_dir = cfg_path / 'registers' 866 if not (signal_path.exists() and register_path.exists() 867 and register_dir.is_dir()): 868 raise RuntimeError("Required processor configuration files not present") 869 try: 870 # Load the register xml defintion 871 register_xml = ET.parse(str(register_path)) 872 # Load the peripheral defintions 873 self._peripheral_map = self._load_peripheral_map(register_xml, register_dir) 874 except ET.ParseError: 875 raise RuntimeError(f"Malformed XML tree in {register_xml}") 876 try: 877 # Try to parse the signal XML file 878 signal_file = str(signal_path) 879 signal_xml = ET.parse(signal_file) 880 # Set SOC name and SKU 881 self._soc_sku = signal_xml.find('part_information/part_number').attrib['id'] 882 self._soc = re.match(r'MIMXR?T?[0-9]+(M\w\d)*', self._soc_sku).group(0) 883 reference = signal_xml.find('reference') 884 if reference is not None: 885 # Signal configuration is stored in reference file, open that 886 file_name = reference.get('file') 887 file_path = cfg_path.parent / file_name 888 if not file_path.exists(): 889 raise RuntimeError("Signal configuration file references " 890 "unknown signal configuration file path", file_path) 891 # Load and parse this signal configuration file 892 signal_xml = ET.parse(str(file_path)) 893 # Load the signal file defintion 894 self._signal_map = self._load_signal_map(signal_xml) 895 except ET.ParseError: 896 logging.error("Malformed XML tree %s", signal_file) 897 self._signal_map = None 898 except IOError: 899 logging.error("File %s could not be opened", signal_file) 900 self._signal_map = None 901 self._copyright = copyright_header 902 903 def get_soc(self): 904 """ 905 Get SOC this class is initialized for 906 """ 907 return self._soc 908 909 def get_part_num(self): 910 """ 911 Get part number this class is initialized for 912 """ 913 return self._soc_sku 914 915 def write_gpio_mux(self, outputfile): 916 """ 917 Write pinctrl defintions for GPIO mux. These defintions map GPIO port 918 and pin combinations to iomuxc options. Note that these defintions are 919 not indended to be used directly, and will likely need to be hand edited. 920 @param outputfile file to write gpio dtsi file to 921 """ 922 # Layered dictionary of gpio mux options. The root keys 923 # are the port names, and those port names map to 924 # dictionaries of pin->iomux option mappings 925 gpio_map = collections.defaultdict(lambda: {}) 926 # regex to get pin number from gpio mux option 927 pin_re = re.compile(r'gpio\d+_io(\d+)|\d+_gpiomux_io(\d\d)|_mux\d_io(\d\d)') 928 with open(outputfile, "w", encoding='utf8') as gpio_dsti: 929 # Write header 930 gpio_dsti.write(f"/*\n" 931 f" * File created by {os.path.basename(__main__.__file__)}\n" 932 " * not intended for direct usage. Hand edit these DTS\n" 933 " * nodes as needed to integrate them into Zephyr.\n" 934 " */\n\n") 935 for pin in sorted(self._signal_map.values()): 936 for iomux_opt in sorted(pin.get_mux_options()): 937 if iomux_opt.is_gpio(): 938 gpio = iomux_opt.gpio() 939 if 'CM7' in iomux_opt.get_name(): 940 gpio_map[f"{gpio.port}_CM7"][gpio.pin] = iomux_opt 941 else: 942 gpio_map[str(gpio.port)][gpio.pin] = iomux_opt 943 # Now write SOC level GPIO pinmux definitions. These are required 944 # so that gpio driver is capable of selecting pinmux options when 945 # a gpio pin is configured. 946 gpio_dsti.write("/*\n" 947 " * GPIO pinmux options. These options define the pinmux settings\n" 948 " * for GPIO ports on the package, so that the GPIO driver can\n" 949 " * select GPIO mux options during GPIO configuration.\n" 950 " */\n\n") 951 for port in sorted(gpio_map): 952 dts_node = (f"&gpio{port}{{\n" 953 "\tpinmux = ") 954 gpio_gaps = [] 955 last_pin_num = -1 956 for pin in sorted(gpio_map[port]): 957 iomux_opt = gpio_map[port][pin] 958 opt_name = iomux_opt.get_name().lower() 959 opt_match = pin_re.search(opt_name) 960 if opt_match is None: 961 logging.warning("Unmatched gpio pin num %s", opt_name) 962 pin = 0 963 else: 964 # Several different pinmux patterns exist, so choose 965 # the group with a valid string in it 966 if opt_match.group(1): 967 pin = int(opt_match.group(1)) 968 elif opt_match.group(2): 969 pin = int(opt_match.group(2)) 970 elif opt_match.group(3): 971 pin = int(opt_match.group(3)) 972 973 if (pin - last_pin_num) != 1: 974 # gap in gpio pin number present. Account for this. 975 gpio_gaps.append((last_pin_num + 1, 976 ((pin - last_pin_num) - 1))) 977 dts_node += f"<&{opt_name}>,\n\t\t" 978 last_pin_num = pin 979 dts_node = re.sub(r',\n\t\t$', ";\n", dts_node) 980 if len(gpio_gaps) != 0: 981 dts_node += "\tgpio-reserved-ranges = " 982 for pair in gpio_gaps: 983 dts_node += f"<{pair[0]} {pair[1]}>, " 984 dts_node = re.sub(r', $', ";\n", dts_node) 985 # end group 986 dts_node += "};\n\n" 987 988 gpio_dsti.write(dts_node) 989 gpio_dsti.close() 990 991 992 993 def write_pinctrl_defs(self, outputfile): 994 """ 995 Writes a pinctrl dtsi file that defines all pinmux options. The board 996 level pin groups will include the pinctrl definitions here, and define 997 the properties to be set on each pin. 998 @param outputfile file to write pinctrl dtsi file to 999 """ 1000 with open(outputfile, "w", encoding='utf8') as soc_dtsi: 1001 # Start by writing header 1002 header = (f"/*\n" 1003 f" * {self._copyright}\n" 1004 f" *\n" 1005 f" * Note: File generated by {os.path.basename(__main__.__file__)}\n" 1006 f" * from configuration data for {self._soc_sku}\n" 1007 " */\n\n") 1008 soc_dtsi.write(header) 1009 # Write documentation block 1010 soc_dtsi.write("/*\n" 1011 " * SOC level pinctrl defintions\n" 1012 " * These definitions define SOC level defaults for each pin,\n" 1013 " * and select the pinmux for the pin. Pinmux entries are a tuple of:\n" 1014 " * <mux_register mux_mode input_register input_daisy config_register>\n" 1015 " * the mux_register and input_daisy reside in the IOMUXC peripheral, and\n" 1016 " * the pinctrl driver will write the mux_mode and input_daisy values into\n" 1017 " * each register, respectively. The config_register is used to configure\n" 1018 " * the pin based on the devicetree properties set\n" 1019 " */\n\n") 1020 # RT11xx has multiple types of pin registers, with a variety 1021 # of register layouts. Define types here. 1022 soc_rt11xx = re.match(r'MIMXRT11\d+', self._soc) is not None 1023 soc_dtsi.write("&iomuxc {\n") 1024 for pin in sorted(self._signal_map.values()): 1025 for iomux_opt in sorted(pin.get_mux_options()): 1026 # Get iomuxc constant values 1027 iomuxc_name = iomux_opt.get_name() 1028 register = iomux_opt.get_mux_reg() 1029 mode = iomux_opt.get_mux_val() 1030 input_reg = iomux_opt.get_daisy_reg() 1031 input_daisy = iomux_opt.get_daisy_val() 1032 config_reg = iomux_opt.get_cfg_reg() 1033 # build DTS node 1034 dts_node = f"\t/omit-if-no-ref/ {iomuxc_name.lower()}: {iomuxc_name} {{\n" 1035 dts_node += (f"\t\tpinmux = <0x{register:x} {mode:d} 0x{input_reg:x} " 1036 f"{input_daisy:d} 0x{config_reg:x}>;\n") 1037 if soc_rt11xx: 1038 # RT11xx pins can have multiple register layouts, so we need to 1039 # record the type of pin here 1040 reg_type = f"pin-{iomux_opt.get_cfg_type()}" 1041 dts_node += f"\t\t{reg_type};\n" 1042 if iomux_opt.has_gpr(): 1043 gpr_reg = iomux_opt.gpr_reg() 1044 gpr_shift = iomux_opt.gpr_shift() 1045 gpr_val = iomux_opt.gpr_val() 1046 # Add GPR configuration 1047 dts_node += f"\t\tgpr = <0x{gpr_reg:x} 0x{gpr_shift:x} 0x{gpr_val:x}>;\n" 1048 dts_node += "\t};\n" 1049 # Write iomuxc dts node to file 1050 soc_dtsi.write(dts_node) 1051 soc_dtsi.write("};\n\n") 1052 1053 def write_pinctrl_groups(self, mexfile, outputfile): 1054 """ 1055 Write pinctrl groups to disk as a parsed DTS file. Intended for use 1056 with the output of @ref write_pinctrl_header 1057 """ 1058 if self._signal_map is None: 1059 logging.error("Cannot write pinctrl groups without a signal map") 1060 return 1061 # Parse the mex file 1062 pin_groups = self._parse_mex_cfg(mexfile) 1063 # Start by writing header 1064 header = (f"/*\n" 1065 f" * {self._copyright}\n" 1066 f" *\n" 1067 f" * Note: File generated by {os.path.basename(__main__.__file__)}\n" 1068 f" * from {os.path.basename(mexfile)}\n" 1069 " */\n\n") 1070 with open(outputfile, "w", encoding="utf8") as dts_file: 1071 dts_file.write(header) 1072 if 'RT' in self.get_part_num(): 1073 dts_file.write("#include <nxp/nxp_imx/rt/" 1074 f"{self.get_part_num().lower()}-pinctrl.dtsi>\n\n") 1075 else: 1076 dts_file.write("#include <nxp/nxp_imx/" 1077 f"{self.get_part_num().lower()}-pinctrl.dtsi>\n\n") 1078 dts_file.write("&pinctrl {\n") 1079 for pin_group in pin_groups.values(): 1080 pin_props = pin_group.get_pin_props() 1081 description = pin_group.get_description() 1082 # if a description is present, write it 1083 if description != "": 1084 description_lines = description.split("\n") 1085 if len(description_lines) == 1: 1086 dts_file.write(f"\t/* {description} */\n") 1087 else: 1088 dts_file.write("\t/*\n") 1089 for line in description_lines: 1090 dts_file.write(f"\t * {line}\n") 1091 dts_file.write("\t */\n") 1092 # Write pin group name 1093 name = pin_group.get_name().lower() 1094 dts_file.write(f"\t{name}: {name} {{\n") 1095 idx = 0 1096 for pin_prop in sorted(pin_props): 1097 group_str = f"\t\tgroup{idx} {{\n" 1098 # Write all pin names 1099 group_str += f"\t\t\tpinmux = " 1100 for pin in pin_group.get_pins(pin_prop): 1101 group_str += f"<&{pin.get_name().lower()}>,\n\t\t\t\t" 1102 # Strip out last 3 tabs and close pin name list 1103 group_str = re.sub(r',\n\t\t\t\t$', ';\n', group_str) 1104 idx += 1 1105 # Write all pin props 1106 for prop in pin_prop: 1107 group_str += f"\t\t\t{prop};\n" 1108 group_str += "\t\t};\n" 1109 dts_file.write(group_str) 1110 # Write closing brace of pin group 1111 dts_file.write("\t};\n\n") 1112 # Write closing brace of pinctrl node 1113 dts_file.write("};\n\n") 1114 1115 """ 1116 Private class methods 1117 """ 1118 def _load_peripheral_map(self, reg_xml, reg_dir): 1119 """ 1120 Generates a mapping of peripheral names to peripheral objects 1121 @param reg_xml: XML tree for register file 1122 @param reg_dir: directory where register defintion files are stored 1123 @return dict mapping peripheral names to base addresses 1124 """ 1125 periph_map = {} 1126 for peripheral in reg_xml.findall('peripherals/peripheral'): 1127 periph_path = reg_dir / peripheral.attrib.get('link') 1128 if periph_path.exists(): 1129 try: 1130 # Build register map for this peripheral 1131 periph_map[peripheral.attrib['name']] = Peripheral(peripheral, 1132 str(periph_path)) 1133 except ET.ParseError: 1134 logging.error("Malformed XML tree in %s, skipping...", periph_path) 1135 periph_map[peripheral.attrib['name']] = Peripheral(peripheral) 1136 1137 return periph_map 1138 1139 def _generate_pin_overrides(self, pin): 1140 """ 1141 Create pinctrl dict using the SOC pad and pin XML definition 1142 populates any selected pin features for the pinctrl dict 1143 @param signal_pin signal pin object for this connection 1144 @param pin: SOC pin XML structure 1145 @return dictionary with pinctrl feature settings 1146 """ 1147 overrides = {} 1148 features = pin.find('mex:pin_features', NAMESPACES) 1149 if features is not None: 1150 for feature in features: 1151 # Get bit field value of feature 1152 name = feature.attrib['name'] 1153 value = feature.attrib['value'] 1154 overrides[name] = value 1155 return overrides 1156 1157 def _parse_mex_cfg(self, mexfile): 1158 """ 1159 Parses mex configuration into pin groups. 1160 @param mexfile: mex configuration file to parse 1161 @return parsed pin groups 1162 """ 1163 pin_groups = {} 1164 try: 1165 mex_xml = ET.parse(mexfile) 1166 is_rt = 'RT' in get_processor_name(mexfile) 1167 for function in mex_xml.findall( 1168 'mex:tools/mex:pins/mex:functions_list/mex:function', NAMESPACES): 1169 group = PinGroup(function, self._signal_map, is_rt) 1170 pin_groups[group.get_name()] = group 1171 return pin_groups 1172 except ET.ParseError: 1173 logging.error("Error: Could not parse mex file %s", mexfile) 1174 return None 1175 1176 def _load_signal_map(self, xml): 1177 """ 1178 Generates a dictionary with pin names as keys, mapping to 1179 dictionary with peripheral signal refs. The values in this dictionary 1180 are IOMUXC options with their default values assigned 1181 @param xml signal xml object 1182 """ 1183 # Open signal XML file 1184 signal_root = xml.getroot() 1185 # Get the pins element 1186 pads = signal_root.find('pins') 1187 # Now, build a mapping in memory between the name of each pin, and 1188 # the peripheral signal refs 1189 iomuxc_options = {} 1190 imx_rt = 'RT' in self._soc 1191 for pad in pads: 1192 pad_name = pad.attrib['name'] 1193 # Verify signal pad is configurable 1194 if len(pad.findall('functional_properties/functional_property')) != 0: 1195 iomuxc_options[pad_name] = SignalPin(pad, self._peripheral_map, imx_rt) 1196 return iomuxc_options 1197 1198 1199""" 1200Convenience methods, exposed here because they may be required before 1201the files required to instantiate an NXPSDKUtil class have been located 1202""" 1203 1204def get_board_name(mexfile): 1205 """ 1206 Extracts board name from a mex file 1207 @param mexfile: mex file to parse for board name 1208 """ 1209 try: 1210 config_tree = ET.parse(mexfile) 1211 if config_tree.getroot().find('mex:common/mex:board', NAMESPACES) is None: 1212 return get_processor_name(mexfile) + '-board' 1213 return config_tree.getroot().find('mex:common/mex:board', 1214 NAMESPACES).text 1215 except ET.ParseError: 1216 logging.error("Malformed XML tree %s", mexfile) 1217 return None 1218 except IOError: 1219 logging.error("File %s could not be opened", mexfile) 1220 return None 1221 1222def get_processor_name(mexfile): 1223 """ 1224 Extracts processor name from a mex file 1225 @param mexfile: mex file to parse for processor name 1226 """ 1227 try: 1228 config_tree = ET.parse(mexfile) 1229 processor = config_tree.getroot().find('mex:common/mex:processor', 1230 NAMESPACES) 1231 if processor is None: 1232 raise RuntimeError("Cannot locate processor name in MEX file.") 1233 return processor.text 1234 except ET.ParseError: 1235 logging.error("Malformed XML tree %s", mexfile) 1236 return None 1237 except IOError: 1238 logging.error("File %s could not be opened", mexfile) 1239 return None 1240 1241def get_package_name(mexfile): 1242 """ 1243 Extracts package name from a mex file 1244 @param mexfile: mex file to parse for package name 1245 """ 1246 try: 1247 config_tree = ET.parse(mexfile) 1248 package = config_tree.getroot().find('mex:common/mex:package', 1249 NAMESPACES) 1250 if package is None: 1251 raise RuntimeError("Cannot locate package name in MEX file. " 1252 "Are you using v11 of the MCUXpresso configuration tools?") 1253 return package.text 1254 except ET.ParseError: 1255 logging.error("Malformed XML tree %s", mexfile) 1256 return None 1257 except IOError: 1258 logging.error("File %s could not be opened", mexfile) 1259 return None 1260