Skip to content

Commit 06d59b1

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 ebf3d15 commit 06d59b1

File tree

3 files changed

+379
-0
lines changed

3 files changed

+379
-0
lines changed
6.85 KB
Binary file not shown.

tools/rt_cfg_utils.py

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

tools/rt_dts_gen.py

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

0 commit comments

Comments
 (0)