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