1#!/usr/bin/env python3
2#
3# Copyright (c) 2022,2024 NXP
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""
8Implements a configuration file parser for LPC and RT6xx/5xx MCUs,
9which can generate pinctrl definitions for Zephyr
10"""
11
12import xml.etree.ElementTree as ET
13import re
14import collections
15import logging
16import os
17import pathlib
18import __main__
19
20NAMESPACES = {'mex': 'http://mcuxpresso.nxp.com/XSD/mex_configuration_14'}
21
22class MUXOption:
23    """
24    Internal class representing a mux option on the SOC
25    """
26    def __init__(self, connection, imx_rt = ''):
27        """
28        Initializes a mux option
29        @param connection XML connection option from signal_configuration.xml
30        """
31        self._name = connection.attrib.get('name_part')
32        logging.debug("\t\t %s", self._name)
33        if self._name is None:
34            self._name = ''
35            return
36        # Get MUX settings
37        self._offset = -1
38        # Get default instance index
39        self._index = 0
40        for periph in connection.iter('peripheral_signal_ref'):
41            self._periph = periph.attrib.get('peripheral')
42            self._signal = periph.attrib.get('signal')
43            self._channel = periph.attrib.get('channel')
44        self._mux_overrides = {}
45        if imx_rt == 'MIMXRT7XX':
46            # RT 7xx series has different function register and instance number
47            func_name = 'FSEL'
48            pio_regex = re.compile(r'IOPCTL(\d+)_PIO(\d+)_(\d+)')
49        elif imx_rt == 'MIMXRT5/6XX':
50            # RT 6xx/5xx series has different function register
51            func_name = 'FSEL'
52            pio_regex = re.compile(r'IOPCTL_PIO(\d+)_(\d+)')
53        else:
54            func_name = 'FUNC'
55            pio_regex = re.compile(r'IOCON_PIO(\d)_*(\d+)')
56        for assign in connection.iter('assign'):
57            reg = assign.attrib.get('register')
58            field = assign.attrib.get('bit_field')
59            val = assign.attrib.get('bit_field_value')
60            logging.debug('\t\t\t [ASSIGN] %s %s', reg, val)
61            # Only process PIO register FUNC setting
62            match = pio_regex.match(reg)
63            if match and (field == func_name):
64                if self._channel:
65                    # For mux options with channels, format pin name as:
66                    # {Peripheral}_{Signal}{Channel}_{Pin}
67                    self._name = f"{self._periph}_{self._signal}{self._channel}"
68                # Append name of pin
69                if imx_rt == 'MIMXRT7XX':
70                    self._name += f"_PIO{match.group(2)}_{match.group(3)}"
71                    self._index = int(match.group(1))
72                    port = int(match.group(2))
73                    pin = int(match.group(3))
74                    if port < 4:
75                        self._offset = (port * 32) + pin
76                    elif port < 8:
77                        self._offset = ((port - 4) * 32) + pin
78                    else:
79                        self._offset = ((port - 8) * 32) + pin
80                else:
81                    self._name += f"_PIO{match.group(1)}_{match.group(2)}"
82                    port = int(match.group(1))
83                    pin = int(match.group(2))
84                    self._offset = (port * 32) + pin
85                self._mux = int(val, 16)
86            elif match and field == 'MODE':
87                # MUX overrides pullup/pulldown mode
88                if val == '0':
89                    self._mux_overrides['mode'] = 'inactive'
90                elif val == '1':
91                    self._mux_overrides['mode'] = 'pullDown'
92                elif val == '2':
93                    self._mux_overrides['mode'] = 'pullUp'
94                elif val == '3':
95                    self._mux_overrides['mode'] = 'repeater'
96            elif match and field == 'ASW' and not imx_rt:
97                # MUX override analog switch setting
98                if val == '0x1':
99                    self._mux_overrides['asw'] = 'enabled'
100                    self._mux_overrides['digimode'] = 'disabled'
101            elif match and field == 'ASW0' and not imx_rt:
102                # LPC553x has two ASW bits
103                if val == '0x1':
104                    self._mux_overrides['asw0'] = 'enabled'
105                    self._mux_overrides['digimode'] = 'disabled'
106            elif match and field == 'ASW1' and not imx_rt:
107                # LPC553x has two ASW bits
108                if val == '0x1':
109                    self._mux_overrides['asw1'] = 'enabled'
110                    self._mux_overrides['digimode'] = 'disabled'
111            elif match and field == 'AMENA' and imx_rt:
112                # MUX override analog switch setting
113                if val == '0x1':
114                    self._mux_overrides['amena'] = 'enabled'
115
116        if self._name == 'PMIC_I2C_SCL' and imx_rt == "MIMXRT5/6XX":
117            # RT600/500 have special pmic I2C pins
118            self._offset = 0x100
119            self._mux = 0
120        elif self._name == 'PMIC_I2C_SDA' and imx_rt == "MIMXRT5/6XX":
121            self._offset = 0x101
122            self._mux = 0
123        elif self._name == 'PMIC_I2C_SCL' and imx_rt == "MIMXRT7XX":
124            self._index = 1
125            self._offset = 0x96
126            self._mux = 0
127        elif self._name == 'PMIC_I2C_SDA' and imx_rt == "MIMXRT7XX":
128            self._index = 1
129            self._offset = 0x97
130            self._mux = 0
131        if re.match(r'^\d', self._name):
132            # If string starts with a digit, it will not be a valid C name
133            self._name = f"PIN_{self._name}"
134        if self._offset == -1:
135            # Not a valid port mapping. Clear name
136            self._name = ''
137
138    def __repr__(self):
139        """
140        String representation of object
141        """
142        return "MUXOption(%s)" % (self._name)
143
144    def get_name(self):
145        """
146        Get mux option name
147        """
148        return self._name
149
150    def get_mux_name(self):
151        """
152        Get name of the mux option, without pin name
153        """
154        if self._channel:
155            return f"{self._periph}_{self._signal}, {self._channel}"
156        return f"{self._periph}_{self._signal}"
157
158    def get_mux_overrides(self):
159        """
160        Some MUX options define specific pin property overrides. Get them here
161        if they exist
162        """
163        return self._mux_overrides
164
165    def get_port(self):
166        """
167        Get mux port
168        """
169        return self._port
170
171    def get_signal(self):
172        """
173        Get mux signal name
174        """
175        return self._signal
176
177    def get_offset(self):
178        """
179        Get mux register offset
180        """
181        return self._offset
182
183    def get_pin(self):
184        """
185        Get mux pin
186        """
187        return self._pin
188
189    def get_index(self):
190        """
191        Get mux instance index
192        """
193        return self._index
194
195    def get_mux(self):
196        """
197        Get mux register write value
198        """
199        return self._mux
200
201    def get_periph(self):
202        """
203        Get peripheral name
204        """
205        return self._periph
206
207    def get_channel(self):
208        """
209        Get channel number
210        """
211        return self._channel
212
213    def __hash__(self):
214        """
215        Override hash method to return pin name as hash
216        """
217        return hash(self._name)
218
219    def __eq__(self, obj):
220        """
221        Like the hash method, we override the eq method to return true if two
222        objects have the same pin name
223        """
224        return isinstance(obj, MUXOption) and self._name == obj._name
225
226    def __lt__(self, obj):
227        """
228        Compare objects based on name
229        """
230        if not isinstance(obj, MUXOption):
231            return True
232        return self._name < obj._name
233
234
235class SignalPin:
236    """
237    Internal class representing a signal on the SOC
238    """
239    def __init__(self, pin, imx_rt = ''):
240        """
241        Initializes a SignalPin object
242        @param pin: pin XML object from signal_configuration.xml
243        """
244        # lpc pin names are formatted as PIOx_y
245        pin_regex = re.search(r'PIO(\d+)_(\d+)', pin.attrib['name'])
246        if (imx_rt and (pin.attrib['name'] == 'PMIC_I2C_SCL' or
247            pin.attrib['name'] == 'PMIC_I2C_SDA')):
248            # iMX RT has special pins without a mux setting
249            self._name = pin.attrib['name']
250            self._port = 0
251            self._pin = 0
252        elif pin_regex is None:
253            logging.debug('Could not match pin name %s', pin.attrib['name'])
254            self._name = ''
255            return
256        else:
257            self._name = pin.attrib['name']
258            self._port = int(pin_regex.group(1))
259            self._pin = int(pin_regex.group(2))
260        self._properties = self._get_pin_properties(pin.find('functional_properties'))
261        self._mux_options = {}
262        for connections in pin.findall('connections'):
263            mux_opt = MUXOption(connections, imx_rt = imx_rt)
264            # Only append mux options with a valid name
265            if mux_opt.get_name() != '':
266                self._mux_options[mux_opt.get_mux_name()] = mux_opt
267
268    def __repr__(self):
269        """
270        String representation of object
271        """
272        return "SignalPin(%s)" % (self._name)
273
274    def __hash__(self):
275        """
276        Override hash method to return pin name as hash
277        """
278        return hash(self._name)
279
280    def __eq__(self, obj):
281        """
282        Like the hash method, we override the eq method to return true if two
283        objects have the same pin and port
284        """
285        return isinstance(obj, SignalPin) and self._name == obj._name
286
287    def __lt__(self, obj):
288        """
289        Compare objects based on port and pin
290        """
291        if not isinstance(obj, SignalPin):
292            return True
293        if self._port == obj._port:
294            return self._pin < obj._pin
295        return self._port < obj._port
296
297    def get_name(self):
298        """
299        Get name of pin
300        """
301        return self._name
302
303    def get_port(self):
304        """
305        Get PORT this signal is defined for
306        """
307        return self._port
308
309    def get_pin(self):
310        """
311        Get pin this signal is defined for
312        """
313        return self._pin
314
315    def get_mux_connection(self, signal):
316        """
317        Gets an MUXOption object for the relevant signal name
318        @param signal: Signal name on pin to get mux option for
319        """
320        if signal in self._mux_options:
321            return self._mux_options[signal]
322        return None
323
324    def get_mux_options(self):
325        """
326        Gets all unique settings for IOMUX on the specific pin
327        """
328        return set(self._mux_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            prop_id = prop.attrib['id']
369            if not 'default' in prop.attrib:
370                # No default property. Skip
371                continue
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                if reg_assign:
377                    bit_value = int(reg_assign.attrib['bit_field_value'], 0)
378                else:
379                    # Assume writing zero to register will select default
380                    bit_value = 0
381                prop_mapping[prop_id][state.attrib['id']] = bit_value
382        return prop_mapping
383
384class PinGroup:
385    """
386    Internal class representing pin group
387    """
388    def __init__(self, function, signal_map, imx_rt = ''):
389        """
390        Creates a pin group
391        @param function: function xml structure from MEX configuration file
392        @param signal_map: Signal mapping, maps signal names to signal pins
393        """
394        self._name = function.attrib.get('name')
395        pins = function.find('mex:pins', NAMESPACES)
396        description = function.find('mex:description', NAMESPACES)
397        if description is not None and description.text is not None:
398            # Replace <br> html tag with newline
399            self._description = description.text.replace("&lt;br/&gt;", "\n")
400        else:
401            self._description = ""
402        # Build dictionary mapping pin properties to pins. This allows us to
403        # group pins based on shared configuration
404        self._pin_groups = collections.defaultdict(lambda: [])
405        for pin in pins:
406            # find signal defintion for this pin
407            signal_name = pin.attrib.get('pin_signal')
408            if not signal_name in signal_map:
409                logging.warning('Signal name %s not present in mapping', signal_name)
410                # No way to find mux option
411                continue
412            # Get mux option for this signal
413            signal = signal_map[signal_name]
414            mux_option = f"{pin.attrib.get('peripheral')}_{pin.attrib.get('signal')}"
415            mux = signal.get_mux_connection(mux_option)
416            if mux is None:
417                logging.warning('Signal name %s has no mux', mux_option)
418                # Do not add pinmux option to group
419                continue
420            # Get pin defaults for this pin
421            defaults = signal.get_pin_defaults()
422            # Get pin overrides
423            features = pin.find('mex:pin_features', NAMESPACES)
424            pin_overrides = {}
425            if features is not None:
426                for feature in pin.find('mex:pin_features', NAMESPACES):
427                    pin_overrides[feature.attrib.get('name')] = feature.attrib.get('value')
428            # Get pin mux option overrides
429            for (override, value) in mux.get_mux_overrides().items():
430                pin_overrides[override] = value
431            if imx_rt:
432                pin_props = self._imx_rt_props_to_dts(pin_overrides, defaults)
433            else:
434                pin_props = self._lpc_props_to_dts(pin_overrides, defaults)
435            self._pin_groups[pin_props].append(mux)
436
437    def __repr__(self):
438        """
439        Get string representation of the object
440        """
441        return "PinGroup(%s)" % (self._name)
442
443    def __eq__(self, obj):
444        """
445        return true if two objects have the same pin group name
446        """
447        return isinstance(obj, PinGroup) and self._name == obj._name
448
449    def __lt__(self, obj):
450        """
451        Compare objects based on name
452        """
453        if not isinstance(obj, PinGroup):
454            return True
455        return self._name < obj._name
456
457    def get_pin_props(self):
458        """
459        Get all unique pin properties
460        """
461        return self._pin_groups.keys()
462
463    def get_pins(self, props):
464        """
465        Get all pins with a provided set of properties
466        @param props: property set
467        """
468        return self._pin_groups[props]
469
470    def get_description(self):
471        """
472        Get description of the pin group, if present. If no description present,
473        description will be ""
474        """
475        return self._description
476
477    def get_name(self):
478        """
479        Get pin group name
480        """
481        return self._name
482
483    def _imx_rt_props_to_dts(self, props, defaults):
484        """
485        Remap dictionary of property names from NXP defined values to
486        Zephyr ones (applies to RT600/RT500 properties)
487        @param props: Dictionary of NXP property names and values
488        @param defaults: Dictionary of NXP property names and default pin values
489        @return array of strings suitable for writing to DTS
490        """
491        zephyr_props = []
492        prop_mapping = {
493            # Slew rate property mappings
494            'normal': 'normal',
495            'slow': 'slow',
496            # Drive strength property mappings
497            'normal': 'normal',
498            'full': 'high'
499        }
500        # Lambda to convert property names to zephyr formatted strings
501        sanitize = lambda x: "\"" + prop_mapping[x] + "\"" if (x in prop_mapping) else ""
502        # Lambda to get property value or fallback on default
503        prop_val = lambda x: props[x] if x in props else defaults[x]
504        # For each property, append the provided override or the default
505        # Check pull settings
506        pull_enable = prop_val('pupdena') == 'enabled'
507        if pull_enable:
508            if prop_val('pupdsel') == 'pullDown':
509                zephyr_props.append('bias-pull-down')
510            else:
511                zephyr_props.append('bias-pull-up')
512        # input buffer
513        if prop_val('ibena') == 'enabled':
514            zephyr_props.append('input-enable')
515        # Slew rate settings
516        zephyr_props.append(f"slew-rate = {sanitize(prop_val('slew_rate'))}")
517        # Drive strength
518        zephyr_props.append(f"drive-strength = {sanitize(prop_val('drive'))}")
519        # analog switch
520        if prop_val('amena') == 'enabled':
521            zephyr_props.append('nxp,analog-mode')
522        # open drain
523        if prop_val('odena') == 'enabled':
524            zephyr_props.append('drive-open-drain')
525        # Pin invert settings
526        if prop_val('iiena') == 'enabled':
527            zephyr_props.append('nxp,invert')
528
529        return tuple(zephyr_props)
530
531    def _lpc_props_to_dts(self, props, defaults):
532        """
533        Remap dictionary of property names from NXP defined values to
534        Zephyr ones (applies to LPC properties only)
535        @param props: Dictionary of NXP property names and values
536        @param defaults: Dictionary of NXP property names and default pin values
537        @return array of strings suitable for writing to DTS
538        """
539        zephyr_props = []
540        prop_mapping = {
541            # Slew rate property mappings
542            'standard': 'standard',
543            'fast': 'fast',
544            # power source property mappings
545            'signal3v3': '3v3',
546            'signal1v8': '1v8',
547            # i2cfilter property mappings
548            'nonhighspeedmode': 'slow',
549            'highspeedmode': 'fast'
550        }
551        # Lambda to convert property names to zephyr formatted strings
552        sanitize = lambda x: "\"" + prop_mapping[x] + "\"" if (x in prop_mapping) else ""
553        # Lambda to get property value or fallback on default
554        prop_val = lambda x: props[x] if x in props else defaults[x] if x in defaults else ""
555        # For each property, append the provided override or the default
556        # Check pull settings
557        if prop_val('mode') == 'pullUp':
558            zephyr_props.append('bias-pull-up')
559        elif prop_val('mode') == 'pullDown':
560            zephyr_props.append('bias-pull-down')
561        elif prop_val('mode') == 'repeater':
562            # Repeater latches the pin to the last input, to keep it from floating
563            zephyr_props.append('drive-push-pull')
564        # Slew rate settings
565        if 'slew_rate' in defaults:
566            zephyr_props.append(f"slew-rate = {sanitize(prop_val('slew_rate'))}")
567        # Pin invert settings
568        if prop_val('invert') == 'enabled':
569            zephyr_props.append('nxp,invert')
570        # open drain settings
571        if prop_val('open_drain') == 'enabled':
572            zephyr_props.append('drive-open-drain')
573        if 'asw' in defaults:
574            # analog switch setting (ASW bit for type A pins)
575            if prop_val('asw') == 'enabled' and prop_val('digimode') == 'disabled':
576                # Note we only respect the ASW setting if digimode is false,
577                # This condition can only occur when a mux specific override sets
578                # DIGIMODE=0, ASW=1.
579                zephyr_props.append('nxp,analog-mode')
580        if prop_val('asw0') == 'enabled' and prop_val('digimode') == 'disabled':
581            # analog switch setting 0 (LPC553x has two ASW bits)
582            zephyr_props.append('nxp,analog-mode')
583        if prop_val('asw1') == 'enabled' and prop_val('digimode') == 'disabled':
584            # analog switch setting 0 (LPC553x has two ASW bits)
585            zephyr_props.append('nxp,analog-alt-mode')
586        if 'ssel' in defaults:
587            # i2c power source select (SSEL bit for type I pins)
588            zephyr_props.append(f"power-source = {sanitize(prop_val('ssel'))}")
589            # i2c filter (FILTEROFF bit for type I pins)
590            # Note that when filter_off == 'enabled', the filter is actually on
591            if prop_val('filter_off') == 'enabled':
592                # Check i2c filter speed bit (I2CFILTER bit for type I pins)
593                zephyr_props.append(f"nxp,i2c-filter = {sanitize(prop_val('i2cfilter'))}")
594            # i2c pullup (ECS bit for type I pins)
595            if prop_val('ecs') == 'enabled':
596                zephyr_props.append('nxp,i2c-pullup')
597            # i2c mode (EGP bit for type I pins)
598            if prop_val('egp') == 'i2c':
599                zephyr_props.append('nxp,i2c-mode')
600
601        return tuple(zephyr_props)
602
603
604class NXPSdkUtil:
605    """
606    Class for lpc configuration file parser
607    """
608    def __init__(self, cfg_root, copyright_header = "", log_level = logging.ERROR):
609        """
610        Initialize SDK utilities.
611        Providing a signal file will enable this class to parse MEX files,
612        and generate output DTS
613        @param cfg_root processor configuration folder root
614        @param copyright_header: copyright string to add to any generated file header
615        @param log_level: log level for SDK utility
616        """
617        # Load the signal XML data
618
619        self._logger = logging.getLogger('')
620        self._logger.setLevel(log_level)
621        self._parse_signal_xml(pathlib.Path(cfg_root)/'signal_configuration.xml')
622        self._copyright = copyright_header
623        logging.info("Loaded %d configurable pin defs", len(self._pins))
624
625    def _parse_signal_xml(self, signal_fn):
626        """
627        Parses signal XML configuration file. Builds a list of pins, which can
628        be used to generate soc level DTSI file.
629        @param signal_fn: signal_configuration.xml file to parse
630        """
631        self._pins = {}
632        try:
633            signal_tree = ET.parse(signal_fn)
634        except ET.ParseError:
635            logging.error("Could not parse provided signal file: %s", signal_fn)
636            return
637
638        signal_root = signal_tree.getroot()
639
640        self._part_num = signal_root.find("./part_information/part_number").get('id')
641        if 'MIMXRT7' in self._part_num:
642            # IMX RT600/500 series part. Different register layout and pin names
643            self._imx_rt = 'MIMXRT7XX'
644        elif 'MIMXRT' in self._part_num:
645            # IMX RT600/500 series part. Different register layout and pin names
646            self._imx_rt = 'MIMXRT5/6XX'
647        else:
648            self._imx_rt = ''
649
650        logging.info("Loaded XML for %s", self._part_num)
651
652        pins_node = signal_root.find("pins")
653        for pin in pins_node:
654            signal = SignalPin(pin, self._imx_rt)
655            # Only add valid signal pins to list
656            if signal.get_name() != '':
657                self._pins[signal.get_name()] = signal
658
659    def get_part_num(self):
660        """
661        Return the part number this class is instantiated for
662        """
663        return self._part_num
664
665    def write_pinctrl_defs(self, outputfile):
666        """
667        Writes all pin mux options into pinctrl header file. Board level pin
668        groups can include this pinctrl header file to access pin control
669        defintions.
670        @param outputfile: file to write output pinctrl defs to
671        """
672        file_header = ("/*\n"
673            f" * NOTE: File generated by {os.path.basename(__main__.__file__)}\n"
674            f" * from {self._part_num}/signal_configuration.xml\n"
675            " *\n"
676            f" * {self._copyright}\n"
677            " */\n"
678            "\n")
679
680        if self._imx_rt:
681            # Notes on the below macro:
682            # Due to IOPCTL instance number is nonunique, index variable is
683            # introduced to represent the label of IOPCTL instance. We use
684            # 4 bits to store index value.
685            # We store the pin and port values as an offset, because some pins
686            # do not follow a consistent offset. We use 12 bits to store this
687            # offset.
688            # Mux values range from 0-15, so we give 4 bits
689            # shift the offset to the MSBs of the mux value, so they
690            # don't conflict with pin configuration settings
691            # Store the mux value at the offset it will actually be written to the
692            # configuration register
693            mux_macro = ("#define IOPCTL_MUX(index, offset, mux)\t\t\\\n"
694                "\t((((index) & 0xF) << 16) |\t\t\\\n"
695                "\t(((offset) & 0xFFF) << 20) |\t\t\\\n"
696                "\t(((mux) & 0xF) << 0))\n\n")
697        else:
698            # Notes on the below macro:
699            # We store the pin and port values as an offset, because some pins
700            # do not follow a consistent offset. We use 12 bits to store this
701            # offset.
702            # Mux values range from 0-15, so we give 4 bits
703            # type values range from 0-2, so we give 3 bits
704            # shift the offset to the MSBs of the mux value, so they
705            # don't conflict with pin configuration settings
706            # Store the mux value at the offset it will actually be written to the
707            # configuration register
708            mux_macro = ("#define IOCON_MUX(offset, type, mux)\t\t\\\n"
709                    "\t(((offset & 0xFFF) << 20) |\t\t\\\n"
710                    "\t(((type) & 0x3) << 18) |\t\t\\\n"
711                    "\t(((mux) & 0xF) << 0))\n\n"
712                    "#define IOCON_TYPE_D 0x0\n"
713                    "#define IOCON_TYPE_I 0x1\n"
714                    "#define IOCON_TYPE_A 0x2\n\n")
715        with open(outputfile, "w", encoding="utf8") as file:
716            file.write(file_header)
717            # ifdef guard
718            file.write(f"#ifndef _ZEPHYR_DTS_BINDING_{self._part_num.upper()}_\n")
719            file.write(f"#define _ZEPHYR_DTS_BINDING_{self._part_num.upper()}_\n\n")
720            # Write macro to make port name
721            file.write(mux_macro)
722            # Write pins
723            for pin in sorted(self._pins.values()):
724                if not self._imx_rt:
725                    # LPC IOCON has analog and I2C type pins, iMX RT does not
726                    if 'asw' in pin.get_pin_defaults():
727                        pin_type = 'IOCON_TYPE_A' # Analog pin type
728                    elif 'asw0' in pin.get_pin_defaults():
729                        pin_type = 'IOCON_TYPE_A' # LPC553x has ASW0 and ASW1 bits
730                    elif 'ssel' in pin.get_pin_defaults():
731                        pin_type = 'IOCON_TYPE_I'
732                    else:
733                        pin_type = 'IOCON_TYPE_D'
734                sig_port = pin.get_port()
735                sig_pin = pin.get_pin()
736                for mux in sorted(pin.get_mux_options()):
737                    index = mux.get_index()
738                    offset = mux.get_offset()
739                    label = mux.get_name()
740                    mux = mux.get_mux()
741                    if self._imx_rt:
742                        file.write(f"#define {label} IOPCTL_MUX({index}, {offset}, {mux}) "
743                            f"/* PIO{sig_port}_{sig_pin} */\n")
744                    else:
745                        file.write(f"#define {label} IOCON_MUX({offset}, {pin_type}, {mux}) "
746                            f"/* PIO{sig_port}_{sig_pin} */\n")
747
748            file.write("\n#endif\n")
749
750    def _parse_mex_cfg(self, mexfile):
751        """
752        Parses mex configuration into pin groups.
753        @param mexfile: mex configuration file to parse
754        @return parsed pin groups
755        """
756        pin_groups = {}
757        try:
758            mex_xml = ET.parse(mexfile)
759            for function in mex_xml.findall(
760                'mex:tools/mex:pins/mex:functions_list/mex:function', NAMESPACES):
761                group = PinGroup(function, self._pins, self._imx_rt)
762                pin_groups[group.get_name()] = group
763            return pin_groups
764        except ET.ParseError:
765            logging.error("Could not parse mex file %s", mex_xml)
766            return None
767
768    def write_pinctrl_groups(self, mexfile, outputfile):
769        """
770        Write pinctrl groups to disk as a parsed DTS file. Intended for use
771        with the output of @ref write_pinctrl_defs
772        @param mexfile: mex file to parse
773        @param outputfile: DTS pinctrl file to write pin groups to
774        """
775
776        file_header = ("/*\n"
777            f" * NOTE: File generated by {os.path.basename(__main__.__file__)}\n"
778            f" * from {os.path.basename(mexfile)}\n"
779            " *\n"
780            f" * {self._copyright}\n"
781            " */\n"
782            "\n")
783        pin_groups = self._parse_mex_cfg(mexfile)
784        with open(outputfile, "w", encoding="utf8") as file:
785            file.write(file_header)
786            if self._imx_rt:
787                file.write(f"\n#include <nxp/nxp_imx/rt/{get_package_name(mexfile)}-pinctrl.h>\n\n")
788            else:
789                file.write(f"\n#include <nxp/lpc/{get_package_name(mexfile)}-pinctrl.h>\n\n")
790            file.write("&pinctrl {\n")
791            # Write pin groups back out to disk
792            for group in pin_groups.values():
793                pin_props = group.get_pin_props()
794                description = group.get_description()
795                # if a description is present, write it
796                if description != "":
797                    description_lines = description.split("\n")
798                    if len(description_lines) == 1:
799                        file.write(f"\t/* {description} */\n")
800                    else:
801                        file.write("\t/*\n")
802                        for line in description_lines:
803                            file.write(f"\t * {line}\n")
804                        file.write("\t */\n")
805                logging.info("Writing pin group %s to disk", group.get_name())
806                file.write(f"\t{group.get_name().lower()}: {group.get_name().lower()} {{\n")
807                idx = 0
808                for pin_prop in sorted(pin_props):
809                    group_str = f"\t\tgroup{idx} {{\n"
810                    # Write all pin names
811                    group_str += "\t\t\tpinmux = "
812                    for pin in group.get_pins(pin_prop):
813                        group_str += f"<{pin.get_name()}>,\n\t\t\t\t"
814                    # Strip out last 3 tabs and close pin name list
815                    group_str = re.sub(r',\n\t\t\t\t$', ';\n', group_str)
816                    idx += 1
817                    # Write all pin props
818                    if pin_prop is None:
819                        logging.error("No pin properties present")
820                    for prop in pin_prop:
821                        group_str += f"\t\t\t{prop};\n"
822                    group_str += "\t\t};\n"
823                    file.write(group_str)
824                file.write("\t};\n\n")
825            file.write("};\n")
826
827"""
828Utility functions used to get details about board/processor from MEX file
829"""
830
831def get_board_name(mexfile):
832    """
833    Extracts board name from a mex file
834    @param mexfile: mex file to parse for board name
835    """
836    try:
837        config_tree = ET.parse(mexfile)
838        if config_tree.getroot().find('mex:common/mex:board', NAMESPACES) is None:
839            return get_processor_name(mexfile) + '-board'
840        return config_tree.getroot().find('mex:common/mex:board',
841            NAMESPACES).text
842    except ET.ParseError:
843        print(f"Malformed XML tree {mexfile}")
844        return None
845    except IOError:
846        print(f"File {mexfile} could not be opened")
847        return None
848
849def get_processor_name(mexfile):
850    """
851    Extracts processor name from a mex file
852    @param mexfile: mex file to parse for processor name
853    """
854    try:
855        config_tree = ET.parse(mexfile)
856        processor = config_tree.getroot().find('mex:common/mex:processor',
857            NAMESPACES)
858        if processor is None:
859            raise RuntimeError("Cannot locate processor name in MEX file. "
860                "Are you using v12 of the MCUXpresso configuration tools?")
861        return processor.text
862    except ET.ParseError:
863        print(f"Malformed XML tree {mexfile}")
864        return None
865    except IOError:
866        print(f"File {mexfile} could not be opened")
867        return None
868
869def get_package_name(mexfile):
870    """
871    Extracts package name from a mex file
872    @param mexfile: mex file to parse for package name
873    """
874    try:
875        config_tree = ET.parse(mexfile)
876        return config_tree.getroot().find('mex:common/mex:package',
877            NAMESPACES).text
878    except ET.ParseError:
879        print(f"Malformed XML tree {mexfile}")
880        return None
881    except IOError:
882        print(f"File {mexfile} could not be opened")
883        return None
884