1#!/usr/bin/env python3
2#
3# Copyright 2023-2025 NXP
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""
8Wrapper around support libraries for i.MX RT, LPC, and Kinetis SOCs.
9Allows user to generate pin control board files from MCUXpresso config
10tools (MEX) files
11"""
12# Standard python libraries
13import argparse
14import tempfile
15import zipfile
16import pathlib
17import sys
18import datetime
19import re
20
21# SOC configuration data support libraries
22from imx import imx_cfg_utils
23from kinetis import kinetis_cfg_utils
24from lpc import lpc_cfg_utils
25
26
27HELPSTR = """
28Processes NXP signal configuration files
29
30Given a processor data pack, generates the SOC level pinctrl DTSI defintions
31required for Zephyr. This tool is intended to be used with the configuration
32data downloaded from NXP's MCUXpresso SDK builder.
33"""
34
35
36def parse_args():
37    """
38    Parses arguments, and returns object with parsed arguments
39    as properties
40    """
41    parser = argparse.ArgumentParser(description=HELPSTR,
42            formatter_class=argparse.RawDescriptionHelpFormatter)
43    parser.add_argument('data_pack', metavar = 'DATA_PACK',
44                        type=str,
45                        help='Path to downloaded data package zip')
46    parser.add_argument('--copyright', action='store_true',
47                        help='Enable default NXP copyright')
48    parser.add_argument('--soc-output', metavar = 'SOC_OUT', type=str,
49                        help='Output directory for soc level dtsi files')
50    parser.add_argument('--controller', metavar = 'CTRL', type=str,
51                        help=("SOC pin controller type."
52                        "Currently supports: [IOMUX, IOCON, PORT]"))
53
54    return parser.parse_args()
55
56
57def processor_to_controller(processor_name):
58    """
59    Returns pin controller type for processor, or None if
60    processor is unknown
61    """
62    # Select family of pin controller based on SOC type
63    if "IMXRT1" in processor_name:
64        # Use IMX config tools
65        return 'IOMUX'
66    if "IMXRT7" in processor_name:
67        # LPC config tools
68        return 'IOCON'
69    if "IMXRT6" in processor_name:
70        # LPC config tools
71        return 'IOCON'
72    if "IMXRT5" in processor_name:
73        # LPC config tools
74        return 'IOCON'
75    if "LPC55" in processor_name:
76        # LPC config tools
77        return 'IOCON'
78    if "MK" in processor_name:
79        # Kinetis config tools
80        return 'PORT'
81    if "MCX" in processor_name:
82        # Kinetis config tools
83        return 'PORT'
84    # Unknown processor family
85    return "UNKNOWN"
86
87def get_pack_version(pack_dir):
88    """
89    Gets datapack version
90    @param pack_dir: root directory data pack is in
91    """
92    # Check version of the config tools archive
93    npi_data = pathlib.Path(pack_dir) / 'npidata.mf'
94    data_version = 0.0
95    with open(npi_data, 'r', encoding='UTF8') as stream:
96        line = stream.readline()
97        while line != '':
98            match = re.search(r'data_version=([\d\.]+)', line)
99            if match:
100                data_version = float(match.group(1))
101                break
102            line = stream.readline()
103    return data_version
104
105def main():
106    """
107    Main entry point. Will process data pack, and generate board pin control
108    headers
109    """
110    args = parse_args()
111    if not args.soc_output:
112        args.soc_output = "."
113    elif not pathlib.Path(args.soc_output).is_dir():
114        print('SOC output path must be a directory')
115        sys.exit(255)
116
117    # Extract the Data pack to a temporary directory
118    temp_dir = tempfile.TemporaryDirectory()
119    zipfile.ZipFile(args.data_pack).extractall(temp_dir.name)
120
121    data_version = get_pack_version(temp_dir.name)
122    print(f"Found data pack version {data_version}")
123    if round(data_version) != 16:
124        print("Warning: This tool is only verified for data pack version 16, "
125            "other versions may not work")
126
127    # Attempt to locate the signal XML files we will generate from
128    proc_root = pathlib.Path(temp_dir.name) / 'processors'
129    search_pattern = "*/ksdk2_0/*/signal_configuration.xml"
130    # Pathlib glob returns an iteration, so use sum to count the length
131    package_count = sum(1 for _ in proc_root.glob(search_pattern))
132    if package_count == 0:
133        search_pattern = "*/i_mx_2_0/*/signal_configuration.xml"
134        package_count = sum(1 for _ in proc_root.glob(search_pattern))
135        if package_count == 0:
136            print("No signal configuration files were found in this data pack")
137            sys.exit(255)
138    if args.copyright:
139        # Add default copyright
140        nxp_copyright = (f"Copyright {datetime.datetime.today().year}, NXP\n"
141        f" * SPDX-License-Identifier: Apache-2.0")
142    else:
143        nxp_copyright = ""
144    for signal_xml in proc_root.glob(search_pattern):
145        package_root = signal_xml.parent
146        package_name = package_root.name
147        if args.controller is None:
148            # Determine pin controller type using SOC package name
149            args.controller = processor_to_controller(package_name)
150        # If controller is still unknown, error out
151        if args.controller == "UNKNOWN":
152            print(f"Error: package {package_name} is not currently supported by this tool")
153            print("You can try specifying your pin controller family manually, "
154                "but this is unsupported!")
155            sys.exit(255)
156
157        # Select correct config tool script for the signal file
158        out_dir = args.soc_output.rstrip('/')
159        if args.controller == 'IOMUX':
160            cfg_util = imx_cfg_utils.NXPSdkUtil(str(package_root),
161                                                copyright_header=nxp_copyright)
162            out_path = f"{out_dir}/{cfg_util.get_part_num().lower()}-pinctrl.dtsi"
163        elif args.controller == 'IOCON':
164            cfg_util = lpc_cfg_utils.NXPSdkUtil(str(package_root),
165                                                copyright_header=nxp_copyright)
166            out_path = f"{out_dir}/{cfg_util.get_part_num().upper()}-pinctrl.h"
167        elif args.controller == 'PORT':
168            cfg_util = kinetis_cfg_utils.NXPSdkUtil(str(package_root),
169                                                    copyright_header=nxp_copyright)
170            out_path = f"{out_dir}/{cfg_util.get_part_num().upper()}-pinctrl.h"
171        else:
172            print("Error: unknown controller type")
173            sys.exit(255)
174
175        cfg_util.write_pinctrl_defs(out_path)
176        print(f"Wrote pinctrl headers to {out_path}")
177
178
179if __name__ == "__main__":
180    main()
181