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