Skip to content

Commit 16a1b91

Browse files
Add initial script to parse signal XML file for iMX.RT parts
Add initial script to generate pinctrl groups from MEX file for iMX.RT parts. Signed-off-by: Daniel DeGrasse <[email protected]>
1 parent 7f4e1a4 commit 16a1b91

File tree

2 files changed

+396
-0
lines changed

2 files changed

+396
-0
lines changed

tools/rt_cfg_utils.py

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
"""
2+
Implements a module to parse iMX.RT MEX configuration files to extract pin
3+
configuration groups, and transform them into pinctrl groups suitable for use
4+
in Zephyr.
5+
"""
6+
7+
8+
import xml.etree.ElementTree as ET
9+
import collections
10+
import datetime
11+
import re
12+
import os
13+
import sys
14+
15+
# MEX file has a default namespace, map it here
16+
namespaces = {'mex' : 'http://mcuxpresso.nxp.com/XSD/mex_configuration_10'}
17+
18+
19+
class NXPSdkUtil:
20+
"""
21+
Class for iMX.RT configuration file parser for Zephyr
22+
"""
23+
def __init__(self, iomuxc_file, signal_file):
24+
"""
25+
Initialize SDK utilities.
26+
Providing a signal_configuration.xml file as well as an iomuxc.h file will enable
27+
the class to parse MEX files and generate output DTS
28+
"""
29+
# Load the iomuxc file
30+
try:
31+
iomux = open(iomuxc_file, 'r', encoding='utf8')
32+
# Load the iomuxc name mapping
33+
self._iomuxc_map = self._build_iomuxc_map(iomux)
34+
except IOError:
35+
print(f"File {iomuxc_file} could not be opened")
36+
self._iomcux_map = None
37+
# Try to parse the signal XML file
38+
try:
39+
signal_xml = ET.parse(signal_file)
40+
# Load the signal file defintion
41+
self._signal_map = self._load_signal_map(signal_xml)
42+
# Set SOC name and SKU
43+
self._soc_sku = signal_xml.find('part_information/part_number').attrib['id']
44+
self._soc = re.match("MIMXRT[0-9]+", self._soc_sku).group(0)
45+
except ET.ParseError:
46+
print(f"Malformed XML tree {signal_file}")
47+
self._signal_map = None
48+
except IOError:
49+
print(f"File {signal_file} could not be opened")
50+
self._signal_map = None
51+
52+
def get_soc(self):
53+
"""
54+
Get SOC this class is initialized for
55+
"""
56+
return self._soc
57+
58+
def get_sku(self):
59+
"""
60+
Get SOC SKU this class is initialized for
61+
"""
62+
return self._soc_sku
63+
64+
def write_pinctrl_header(self, outputfile):
65+
"""
66+
Writes a pinctrl header suitable for inclusion in a DTS file, which
67+
defines IOMUXC register constants for each pin configuration
68+
@param outputfile file to write pinctrl header to
69+
"""
70+
if self._iomuxc_map is None:
71+
print("Cannot write pinctrl header file without an iomuxc file")
72+
try:
73+
header_file = open(outputfile, "w", encoding='utf8')
74+
except IOError:
75+
print(f"Could not write to file {outputfile}")
76+
return
77+
# Start by writing header
78+
header = (f"/*\n"
79+
f" * Generated by {os.path.basename(__file__)} on {datetime.date.today()}\n"
80+
" */\n\n")
81+
header_file.write(header)
82+
for (iomuxc, entry) in self._iomuxc_map.items():
83+
# Write iomuxc entry
84+
register = entry[0]
85+
mode = entry[1]
86+
input_reg = entry[2]
87+
input_daisy = entry[3]
88+
config_reg = entry[4]
89+
entry = (f"#define {iomuxc} 0x{register:x} {mode:d} 0x{input_reg:x} "
90+
f"{input_daisy:d} 0x{config_reg:x}\n")
91+
header_file.write(entry)
92+
header_file.write("\n")
93+
header_file.close()
94+
95+
96+
def write_pinctrl_groups(self, mexfile, outputfile):
97+
"""
98+
Write pinctrl groups to disk as a parsed DTS file
99+
@param mexfile: mex file to parse for pin configuration
100+
@param outputfile- file to write dts to
101+
"""
102+
if self._signal_map is None:
103+
print("Cannot write pinctrl groups without a signal map")
104+
try:
105+
config_tree = ET.parse(mexfile)
106+
except ET.ParseError:
107+
print(f"Malformed XML tree {mexfile}")
108+
return
109+
except IOError:
110+
print(f"File {mexfile} could not be opened")
111+
return
112+
try:
113+
dts_file = open(outputfile, "w", encoding='utf8')
114+
except IOError:
115+
print(f"Could not write to file {outputfile}")
116+
return
117+
# Parse the mex file
118+
pin_groups = self._parse_mex_cfg(config_tree, self._signal_map)
119+
# Start by writing header
120+
header = ("/*\n"
121+
f" * Generated by {os.path.basename(__file__)} on {datetime.date.today()}\n"
122+
" */\n\n")
123+
dts_file.write(header)
124+
dts_file.write(f"#include <nxp/nxp_imx/rt/{self.get_soc().lower()}_iomuxc.h>\n\n")
125+
dts_file.write("&pinctrl {\n")
126+
# Sort pin groups alphabetically
127+
sorted_groups = collections.OrderedDict(sorted(pin_groups.items()))
128+
for pin_group in sorted_groups:
129+
dts_file.write(f"\t{pin_group}: {pin_group} {{\n")
130+
# Sort the pin groups by their IOMUXC mapping name
131+
sorted_pins = collections.OrderedDict(sorted(sorted_groups[pin_group].items()))
132+
for pin in sorted_pins:
133+
# Write the pinctrl node
134+
dts_file.write(f"\t\t{pin} {{\n")
135+
# Write the pinmux setting first
136+
dts_file.write(f"\t\t\tpinmux = <{sorted_pins[pin]['pinmux']}>;\n")
137+
# Remove the pinmux setting, and write the remaining settings in sorted order
138+
sorted_pins[pin].pop('pinmux')
139+
feature_dict = collections.OrderedDict(sorted(sorted_pins[pin].items()))
140+
for setting in feature_dict:
141+
dts_file.write(f"\t\t\tnxp,{setting} = <{feature_dict[setting]}>;\n")
142+
dts_file.write("\t\t};\n")
143+
dts_file.write("\t};\n")
144+
dts_file.write("};\n\n")
145+
# DTS file write is done, close it
146+
dts_file.close()
147+
148+
def get_board_name(self, mexfile):
149+
"""
150+
Extracts board name from a mex file
151+
"""
152+
try:
153+
config_tree = ET.parse(mexfile)
154+
except ET.ParseError:
155+
print(f"Malformed XML tree {mexfile}")
156+
return None
157+
except IOError:
158+
print(f"File {mexfile} could not be opened")
159+
return None
160+
return config_tree.getroot().find('mex:common/mex:board', namespaces).text
161+
162+
163+
"""
164+
Private class methods
165+
"""
166+
def _generate_pin_node(self, pad, pin):
167+
"""
168+
Create pinctrl dict using the SOC pad and pin XML definition
169+
populates any selected pin features for the pinctrl dict
170+
@param pad: SOC pad XML structure
171+
@param pin: SOC pin XML structure
172+
@return dictionary with pinctrl feature settings
173+
"""
174+
pin_node = {}
175+
features = pin.find('mex:pin_features', namespaces)
176+
if features is not None:
177+
for feature in features:
178+
# Get bit field value of feature
179+
for prop in pad.find('functional_properties'):
180+
if prop.attrib['id'] == feature.attrib['name']:
181+
# Extract bit field
182+
bit_field = None
183+
for state in prop:
184+
if state.attrib['id'] == feature.attrib['value']:
185+
bit_field = (state.find('configuration/assign')
186+
.attrib['bit_field_value'])
187+
break
188+
if bit_field is None:
189+
print((f"Error: invalid value {feature.attrib['value']} "
190+
f"for feature {feature.attrib['name']}"))
191+
# Exit here, do not finish parsing the MEX file
192+
sys.exit(-1)
193+
else:
194+
# Save bit field to pin node
195+
pin_node[feature.attrib['name']] = bit_field
196+
# Populate remaining possible configuration values with defaults for pin
197+
for prop in pad.find('functional_properties'):
198+
if not prop.attrib['id'] in pin_node and prop.find('state') is not None:
199+
for state in prop:
200+
if state.attrib['id'] == prop.attrib['default']:
201+
# Default state located, record bit field
202+
assign = state.find('configuration/assign')
203+
pin_node[prop.attrib['id']] = assign.attrib['bit_field_value']
204+
break
205+
return pin_node
206+
207+
def _find_periph_connection(self, pad, pin):
208+
"""
209+
Given the XML structure of an SOC pad, and pin selection,
210+
locates the correct mux option in the pad and generates
211+
a valid pinmux name
212+
@param pad: SOC pad XML structure
213+
@param pin: SOC pin XML structure
214+
@return pinmux name if connection is valid, None otherwise
215+
"""
216+
mapping_name = None
217+
for mux in pad.findall('connections'):
218+
for connection in mux:
219+
periph_sig_ref = connection.find('peripheral_signal_ref')
220+
if 'channel' in periph_sig_ref.attrib:
221+
sig_name = periph_sig_ref.attrib['signal']
222+
chan = periph_sig_ref.attrib['channel']
223+
signal = f"{sig_name}, {chan}"
224+
else:
225+
signal = periph_sig_ref.attrib['signal']
226+
if (signal == pin.attrib['signal'] and
227+
periph_sig_ref.attrib['peripheral'] == pin.attrib['peripheral']):
228+
# Found valid mapping. Set the IOMUXC name
229+
name_part = mux.attrib['name_part']
230+
# if the header name ends in DATAx,
231+
# it must be expanded to DATA0x (except for USDHC)
232+
if re.search(r'DATA\d$', name_part) and not re.search('USDHC', name_part):
233+
name_part = re.sub(r'DATA(\d)$', r'DATA0\g<1>', name_part)
234+
mapping_name = f"IOMUXC_{pad.attrib['name']}_{name_part}"
235+
break
236+
return mapping_name
237+
238+
def _parse_mex_cfg(self, mex_xml, pin_mapping):
239+
"""
240+
Parses mex config file into pinctrl groups
241+
@param mex_xml: xml to load MEX config from
242+
@param pin_mapping: mapping of SOC pads to their associated XML structures
243+
@return pinctrl groups dict
244+
"""
245+
# MEX file is an XML file, parse it
246+
config_root = mex_xml.getroot()
247+
248+
# Get the tools/pins/functions element to find functions defining pin groups
249+
pin_groups = config_root.find('mex:tools/mex:pins/mex:functions_list', namespaces)
250+
251+
pinctrl_groups = {}
252+
253+
# Parse each pin group
254+
for pin_group in pin_groups:
255+
# Create initial empty pinctrl group
256+
pinctrl_group = {}
257+
# Get pin group pins
258+
required_pins = pin_group.find('mex:pins', namespaces)
259+
for pin in required_pins:
260+
pin_node = {}
261+
# Locate the pin name in the pin mapping
262+
if not pin.attrib['pin_signal'] in pin_mapping:
263+
# Pin signal is not found in SOC pin mapping
264+
print((f"Error: pin mapping has invalid pin name for peripheral "
265+
f"{pin.attrib['peripheral']} in group {pin_group.attrib['name']}"))
266+
continue
267+
# Get XML descriping pad on SOC
268+
pad = pin_mapping[pin.attrib['pin_signal']]
269+
# Get the connection for the pin from the SOC mapping
270+
# all mapping names are defined in fsl_iomuxc.h
271+
mapping_name = self._find_periph_connection(pad, pin)
272+
if mapping_name is None:
273+
print((f"Error: mapping peripheral {pin.attrib['peripheral']} "
274+
"to pad {pad.attrib['name']} is not valid"))
275+
# Exit here, do not finish parsing the MEX file.
276+
sys.exit(-1)
277+
# determine functional properties for pin
278+
pin_node = self._generate_pin_node(pad, pin)
279+
pin_node['pinmux'] = mapping_name
280+
# Add pin node to pinctrl group
281+
pinctrl_group[mapping_name.lower()] = pin_node
282+
# Add finished group to all groups
283+
pinctrl_groups[pin_group.attrib['name']] = pinctrl_group
284+
# Return the finished pinctrl groups
285+
return pinctrl_groups
286+
287+
def _build_iomuxc_map(self, iomuxc):
288+
"""
289+
Build dictionary containing mapping of IOMUXC names to
290+
a tuple with their register values
291+
@param iomuxc iomuxc file to read from
292+
@return generated dictionary
293+
"""
294+
regexstr = (r'#define\s+(IOMUXC_\w+)\s+' # MUX name
295+
r'([0x]*[\dA-F]+)U*[\s,]+' # MUX register
296+
r'([0x]*[\dA-F]+)U*[\s,]+' # MUX mode
297+
r'([0x]*[\dA-F]+)U*[\s,]+' # MUX input_reg
298+
r'([0x]*[\dA-F]+)U*[\s,]+' # MUX input_daisy
299+
r'([0x]*[\dA-F]+)U*') # config register
300+
pattern = re.compile(regexstr)
301+
302+
iomuxc_map = {}
303+
line = iomuxc.readline()
304+
while line != "":
305+
# Parse line for pinmux info
306+
match = pattern.match(line)
307+
if match is None or len(match.group(0)) == 0:
308+
# Skip to next line
309+
line = iomuxc.readline()
310+
continue
311+
# Read iomux configuration values
312+
name = match.group(1)
313+
register = int(match.group(2), 0)
314+
mode = int(match.group(3), 0)
315+
input_reg = int(match.group(4), 0)
316+
input_daisy = int(match.group(5), 0)
317+
config_reg = int(match.group(6), 0)
318+
iomuxc_map[name] = (register, mode, input_reg, input_daisy, config_reg)
319+
line = iomuxc.readline()
320+
return iomuxc_map
321+
322+
def _load_signal_map(self, xml):
323+
"""
324+
Load signal XML file as a mapping of SOC pads to their XML structures
325+
into memory to enable faster parsing
326+
@param xml signal xml file
327+
@return mapping of SOC pads to XML structures
328+
"""
329+
# Open signal XML file
330+
signal_root = xml.getroot()
331+
# Get the pins element
332+
pins = signal_root.find('pins')
333+
# Now, build a mapping in memory between the name of each pin
334+
# and the XML elements representing that pin
335+
pin_map = {}
336+
for pin in pins:
337+
pin_map[pin.attrib['name']] = pin
338+
return pin_map

tools/rt_dts_gen.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""
2+
Wrapper around rt_cfg_utils.py to provide arguments to module
3+
"""
4+
import argparse
5+
6+
from rt_cfg_utils import NXPSdkUtil
7+
8+
9+
HELPSTR = """
10+
Processes NXP iMX.RT MEX configuration files
11+
12+
Given a mex file, generates the board level pinctrl groups required for
13+
Zephyr. Only supports iMX.RT parts.
14+
15+
Additional required files:
16+
- fsl_iomucx.h file defining pinmux options
17+
- signal_configuration.xml file for SOC
18+
19+
Where to find required files:
20+
- fsl_iomuxc.h file can be found in mcux/mcux-sdk/devices/{DEVICE_NAME}/drivers/
21+
- signal_configuration.xml file can be sourced from the NXP SDK builder, by
22+
downloading the config tools offline data for your processor. The file
23+
will be stored in processors/{SOC_NAME}/ksdk2_0/{SOC_NAME}/
24+
"""
25+
26+
27+
parser = argparse.ArgumentParser(description=HELPSTR,
28+
formatter_class=argparse.RawDescriptionHelpFormatter)
29+
parser.add_argument('mex_file', metavar = 'MEX',
30+
type=str, help='path to source MEX file')
31+
parser.add_argument('iomuxc_file', metavar = 'IOMUXC',
32+
type=str, help='Direct path to source iomuxc file')
33+
parser.add_argument('signal_file', metavar = 'SIGNAL_XML', type=str,
34+
help='Direct path to source signal_configuration.xml file')
35+
parser.add_argument('--dts_output', metavar = 'DTS_OUT', type=str,
36+
help='Output path for DTS file')
37+
parser.add_argument('--iomuxc_output', metavar = 'DTS_OUT', type=str,
38+
help='Output path for iomuxc header file')
39+
40+
args = parser.parse_args()
41+
42+
43+
util = NXPSdkUtil(args.iomuxc_file, args.signal_file)
44+
45+
# Set defaults for optional arguments
46+
if not args.dts_output:
47+
board = util.get_board_name(args.mex_file).lower().replace('-', '_')
48+
if board:
49+
args.dts_output = f"{board}-pinctrl.dtsi"
50+
else:
51+
args.dts_output = f"{util.get_soc().lower()}-pinctrl.dtsi"
52+
if not args.iomuxc_output:
53+
args.iomuxc_output = f"{util.get_soc().lower()}_iomuxc.h"
54+
print(f"Generating configuration for {util.get_board_name(args.mex_file)}")
55+
util.write_pinctrl_header(args.iomuxc_output)
56+
print(f"Wrote pinctrl header file to {args.iomuxc_output}")
57+
util.write_pinctrl_groups(args.mex_file, args.dts_output)
58+
print(f"Wrote pinctrl dts file to {args.dts_output}")

0 commit comments

Comments
 (0)