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("&lt;br/&gt;", "\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