1#!/usr/bin/env python3
2#
3# Copyright 2023, 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 "IMXRT6" in processor_name:
67        # LPC config tools
68        return 'IOCON'
69    if "IMXRT5" in processor_name:
70        # LPC config tools
71        return 'IOCON'
72    if "LPC55" in processor_name:
73        # LPC config tools
74        return 'IOCON'
75    if "MK" in processor_name:
76        # Kinetis config tools
77        return 'PORT'
78    # Unknown processor family
79    return "UNKNOWN"
80
81def get_pack_version(pack_dir):
82    """
83    Gets datapack version
84    @param pack_dir: root directory data pack is in
85    """
86    # Check version of the config tools archive
87    npi_data = pathlib.Path(pack_dir) / 'npidata.mf'
88    data_version = 0.0
89    with open(npi_data, 'r', encoding='UTF8') as stream:
90        line = stream.readline()
91        while line != '':
92            match = re.search(r'data_version=([\d\.]+)', line)
93            if match:
94                data_version = float(match.group(1))
95                break
96            line = stream.readline()
97    return data_version
98
99def main():
100    """
101    Main entry point. Will process data pack, and generate board pin control
102    headers
103    """
104    args = parse_args()
105    if not args.soc_output:
106        args.soc_output = "."
107    elif not pathlib.Path(args.soc_output).is_dir():
108        print('SOC output path must be a directory')
109        sys.exit(255)
110
111    # Extract the Data pack to a temporary directory
112    temp_dir = tempfile.TemporaryDirectory()
113    zipfile.ZipFile(args.data_pack).extractall(temp_dir.name)
114
115    data_version = get_pack_version(temp_dir.name)
116    print(f"Found data pack version {data_version}")
117    if round(data_version) != 14:
118        print("Warning: This tool is only verified for data pack version 13, "
119            "other versions may not work")
120
121    # Attempt to locate the signal XML files we will generate from
122    proc_root = pathlib.Path(temp_dir.name) / 'processors'
123    search_pattern = "*/ksdk2_0/*/signal_configuration.xml"
124    # Pathlib glob returns an iteration, so use sum to count the length
125    package_count = sum(1 for _ in proc_root.glob(search_pattern))
126    if package_count == 0:
127        print("No signal configuration files were found in this data pack")
128        sys.exit(255)
129    if args.copyright:
130        # Add default copyright
131        nxp_copyright = (f"Copyright {datetime.datetime.today().year}, NXP\n"
132        f" * SPDX-License-Identifier: Apache-2.0")
133    else:
134        nxp_copyright = ""
135    for signal_xml in proc_root.glob(search_pattern):
136        package_root = signal_xml.parent
137        package_name = package_root.name
138        if args.controller is None:
139            # Determine pin controller type using SOC package name
140            args.controller = processor_to_controller(package_name)
141        # If controller is still unknown, error out
142        if args.controller == "UNKNOWN":
143            print(f"Error: package {package_name} is not currently supported by this tool")
144            print("You can try specifying your pin controller family manually, "
145                "but this is unsupported!")
146            sys.exit(255)
147
148        # Select correct config tool script for the signal file
149        out_dir = args.soc_output.rstrip('/')
150        if args.controller == 'IOMUX':
151            cfg_util = imx_cfg_utils.NXPSdkUtil(str(package_root),
152                                                copyright_header=nxp_copyright)
153            out_path = f"{out_dir}/{cfg_util.get_part_num().lower()}-pinctrl.dtsi"
154        elif args.controller == 'IOCON':
155            cfg_util = lpc_cfg_utils.NXPSdkUtil(str(package_root),
156                                                copyright_header=nxp_copyright)
157            out_path = f"{out_dir}/{cfg_util.get_part_num().upper()}-pinctrl.h"
158        elif args.controller == 'PORT':
159            cfg_util = kinetis_cfg_utils.NXPSdkUtil(str(package_root),
160                                                    copyright_header=nxp_copyright)
161            out_path = f"{out_dir}/{cfg_util.get_part_num().upper()}-pinctrl.h"
162        else:
163            print("Error: unknown controller type")
164            sys.exit(255)
165
166        cfg_util.write_pinctrl_defs(out_path)
167        print(f"Wrote pinctrl headers to {out_path}")
168
169
170if __name__ == "__main__":
171    main()
172