Last active
June 23, 2020 13:07
-
-
Save gmarull/8c00bbc1ac6db435e9ac1468c3d2b726 to your computer and use it in GitHub Desktop.
Utility to autogenerate Zephyr DT pinctrl files for all STM32 microcontrollers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Utility to autogenerate Zephyr DT pinctrl files for all STM32 microcontrollers. | |
Usage:: | |
python3 stm32_pinctrl_gen.py -p /path/to/STM32CubeMX -o /path/to/output_dir | |
Copyright (c) 2020 Teslabs Engineering S.L. | |
SPDX-License-Identifier: Apache-2.0 | |
""" | |
import argparse | |
import glob | |
import logging | |
import os | |
import re | |
import shutil | |
import xml.etree.ElementTree as ET | |
logger = logging.getLogger(__name__) | |
NS = "{http://mcd.rou.st.com/modules.php?name=mcu}" | |
"""MCU XML namespace.""" | |
PINCTRL_TEMPLATE = """ | |
/* | |
* WARNING: Autogenerated file using stm32_pinctrl_gen.py | |
* | |
* SPDX-License-Identifier: Apache-2.0 | |
*/ | |
#include <dt-bindings/pinctrl/stm32-pinctrl.h> | |
#include "../pinctrl_st_stm32.h" | |
/ {{ | |
\tsoc {{ | |
\t\tpinctrl: pin-controller@{pinctrl_addr:08X} {{ | |
{pinctrl_entries} | |
\t\t}}; | |
\t}}; | |
}}; | |
""" | |
"""pinctrl file template.""" | |
PINCTRL_GROUP_TEMPLATE = "\t\t\t/* {group} */\n" | |
"""pinctrl group template.""" | |
PINCTRL_ENTRY_TEMPLATE = "\t\t\tDT_STM32_{signal}({port}, {pin}, {periph}, AF{af})\n" | |
"""pinctrl entry template.""" | |
PINCTRL_MAPPINGS = [ | |
{ | |
"name": "UART_CTS", | |
"matches": [ | |
{"re": r"UART(\d+)_CTS", "periph": "uart"}, | |
{"re": r"USART(\d+)_CTS", "periph": "usart"}, | |
], | |
}, | |
{ | |
"name": "UART_RTS", | |
"matches": [ | |
{"re": r"UART(\d+)_RTS", "periph": "uart"}, | |
{"re": r"USART(\d+)_RTS", "periph": "usart"}, | |
], | |
}, | |
{ | |
"name": "UART_TX", | |
"matches": [ | |
{"re": r"UART(\d+)_TX", "periph": "uart"}, | |
{"re": r"USART(\d+)_TX", "periph": "usart"}, | |
], | |
}, | |
{ | |
"name": "UART_RX", | |
"matches": [ | |
{"re": r"UART(\d+)_RX", "periph": "uart"}, | |
{"re": r"USART(\d+)_RX", "periph": "usart"}, | |
], | |
}, | |
] | |
"""pinctrl mappings (according to ``pinctrl_st_stm32.h`` header).""" | |
PINCTRL_ADDR = { | |
"stm32f0": 0x48000000, | |
"stm32f1": 0x40010800, | |
"stm32f2": 0x40020000, | |
"stm32f3": 0x48000000, | |
"stm32f4": 0x40020000, | |
"stm32f7": 0x40020000, | |
"stm32g0": 0x50000000, | |
"stm32g4": 0x48000000, | |
"stm32h7": 0x58020000, | |
"stm32l0": 0x50000000, | |
"stm32l4": 0x48000000, | |
"stm32l5": 0x42020000, | |
"stm32mp1": 0x50002000, | |
"stm32wb": 0x48000000, | |
} | |
"""pinctrl peripheral address for each family.""" | |
def get_gpio_ip_afs(cube_path): | |
"""Obtain all GPIO IP alternate functions. | |
Example output:: | |
{ | |
"STM32L4P_gpio_v1_0": { | |
"PA2": { | |
"EVENTOUT": 15, | |
"LPUART1_TX": 8, | |
... | |
}, | |
... | |
}, | |
... | |
} | |
Args: | |
cube_path: Path to CubeMX package. | |
Returns: | |
Dictionary of alternate functions. | |
""" | |
ip_path = os.path.join(cube_path, "db", "mcu", "IP") | |
if not os.path.exists(ip_path): | |
raise FileNotFoundError(f"IP DB folder '{ip_path}' does not exist") | |
results = dict() | |
for gpio_file in glob.glob(os.path.join(ip_path, "GPIO-*_Modes.xml")): | |
m = re.search(r"GPIO-(.*)_Modes.xml", gpio_file) | |
gpio_ip = m.group(1) | |
gpio_ip_entries = dict() | |
results[gpio_ip] = gpio_ip_entries | |
gpio_tree = ET.parse(gpio_file) | |
gpio_root = gpio_tree.getroot() | |
for pin in gpio_root.findall(NS + "GPIO_Pin"): | |
pin_name = pin.get("Name") | |
pin_entries = dict() | |
gpio_ip_entries[pin_name] = pin_entries | |
for signal in pin.findall(NS + "PinSignal"): | |
signal_name = signal.get("Name") | |
param = signal.find(NS + "SpecificParameter") | |
if not param: | |
# NOTE: Only notify error if not F1 IP. F1 may use re-mapping | |
# which is not supported. | |
if "STM32F1" not in gpio_ip: | |
logger.error( | |
f"Missing signal {signal_name} parameters (ip: {gpio_ip})" | |
) | |
continue | |
value = param.find(NS + "PossibleValue") | |
if not param: | |
logger.error(f"Missing signal {signal_name} value (ip: {gpio_ip})") | |
continue | |
m = re.search(r"^GPIO_AF(\d+)_[A-Z0-9]+", value.text) | |
if not m: | |
# NOTE: Only notify error if not F1 IP. F1 may use re-mapping | |
# definitions which are not supported. | |
if "STM32F1" not in gpio_ip: | |
logger.error( | |
f"Unexpected AF format: {value.text} (ip: {gpio_ip})" | |
) | |
continue | |
af_n = int(m.group(1)) | |
pin_entries[signal_name] = af_n | |
return results | |
def get_mcu_signals(cube_path, gpio_ip_afs): | |
"""Obtain all MCU signals. | |
Example output:: | |
{ | |
"STM32WB": [ | |
{ | |
"name": "STM32WB30CEUx" | |
"pins: [ | |
{ | |
"name": "PA0", | |
"signals" : [ | |
{ | |
"name": "ADC1_IN5", | |
"af": 8 | |
}, | |
... | |
] | |
}, | |
... | |
] | |
}, | |
... | |
] | |
} | |
Args: | |
cube_path: Path to CubeMX. | |
gpio_ip_afs: GPIO IP alternate functions. | |
Returns: | |
Dictionary with all MCU signals. | |
""" | |
mcus_path = os.path.join(cube_path, "db", "mcu") | |
if not os.path.exists(mcus_path): | |
raise FileNotFoundError(f"MCU DB folder '{mcus_path}' does not exist") | |
results = dict() | |
for mcu_file in glob.glob(os.path.join(mcus_path, "STM32*.xml")): | |
mcu_tree = ET.parse(mcu_file) | |
mcu_root = mcu_tree.getroot() | |
# obtain family, reference and GPIO IP | |
family = mcu_root.get("Family") | |
ref = mcu_root.get("RefName") | |
gpio_ip_version = None | |
for ip in mcu_root.findall(NS + "IP"): | |
if ip.get("Name") == "GPIO": | |
gpio_ip_version = ip.get("Version") | |
break | |
if not gpio_ip_version: | |
logger.error(f"GPIO IP version not specified (mcu: {mcu_file})") | |
continue | |
if gpio_ip_version not in gpio_ip_afs: | |
logger.error(f"GPIO IP version {gpio_ip_version} not available") | |
continue | |
gpio_ip = gpio_ip_afs[gpio_ip_version] | |
# create reference entry on its family | |
if family not in results: | |
family_entries = list() | |
results[family] = family_entries | |
else: | |
family_entries = results[family] | |
pin_entries = list() | |
family_entries.append({"name": ref, "pins": pin_entries}) | |
# process all pins | |
for pin in mcu_root.findall(NS + "Pin"): | |
if pin.get("Type") != "I/O": | |
continue | |
m = re.search(r"^P[A-Z]\d+", pin.get("Name")) | |
if not m: | |
continue | |
pin_name = m.group(0) | |
if pin_name not in gpio_ip: | |
continue | |
pin_afs = gpio_ip[pin_name] | |
pin_signals = list() | |
pin_entries.append({"name": pin_name, "signals": pin_signals}) | |
# process all pin signals | |
for signal in pin.findall(NS + "Signal"): | |
if signal.get("Name") == "GPIO": | |
continue | |
signal_name = signal.get("Name") | |
if signal_name not in pin_afs: | |
continue | |
pin_af = pin_afs[signal_name] | |
pin_signals.append({"name": signal_name, "af": pin_af}) | |
return results | |
def main(cube_path, output): | |
"""Entry point. | |
Args: | |
cube_path: CubeMX path. | |
output: Output directory. | |
""" | |
gpio_ip_afs = get_gpio_ip_afs(cube_path) | |
mcu_signals = get_mcu_signals(cube_path, gpio_ip_afs) | |
if os.path.exists(output): | |
shutil.rmtree(output) | |
os.makedirs(output) | |
for family, refs in mcu_signals.items(): | |
# obtain family pinctrl address | |
pinctrl_addr = PINCTRL_ADDR.get(family.lower()) | |
if not pinctrl_addr: | |
continue | |
# create directory for each family | |
family_dir = os.path.join(output, family.lower()[5:]) | |
if not os.path.exists(family_dir): | |
os.makedirs(family_dir) | |
# process each reference | |
for ref in refs: | |
entries = dict() | |
# process each pin in the current reference | |
for pin in ref["pins"]: | |
# obtain pin port (A, B, ...) and number (0, 1, ...) | |
m = re.search(r"^P([A-Z])(\d+)$", pin["name"]) | |
if not m: | |
logger.error(f"Unexpected pin name format: {pin['name']}") | |
continue | |
pin_port = m.group(1).lower() | |
pin_number = int(m.group(2)) | |
# process each pin available signal (matched against mapping table) | |
for signal in pin["signals"]: | |
for mapping in PINCTRL_MAPPINGS: | |
for match in mapping["matches"]: | |
m = re.search(match["re"], signal["name"]) | |
if not m: | |
continue | |
periph_number = m.group(1) | |
if mapping["name"] not in entries: | |
entries[mapping["name"]] = list() | |
entries[mapping["name"]].append( | |
{ | |
"port": pin_port, | |
"pin": pin_number, | |
"periph": f"{match['periph']}{periph_number}", | |
"af": signal["af"], | |
} | |
) | |
if not entries: | |
continue | |
# format entries (grouped and sorted) | |
formatted_entries = "" | |
for name, signals in entries.items(): | |
formatted_entries += PINCTRL_GROUP_TEMPLATE.format(group=name) | |
sorted_signals = sorted( | |
signals, key=lambda entry: (entry["port"], entry["pin"]) | |
) | |
for signal in sorted_signals: | |
formatted_entries += PINCTRL_ENTRY_TEMPLATE.format( | |
signal=name, | |
port=signal["port"], | |
pin=signal["pin"], | |
periph=signal["periph"], | |
af=signal["af"], | |
) | |
formatted_entries += "\n" | |
# write pinctrl file | |
ref_file = os.path.join(family_dir, ref["name"].lower() + "-pinctrl.dtsi") | |
with open(ref_file, "w") as f: | |
f.write( | |
PINCTRL_TEMPLATE.format( | |
pinctrl_addr=pinctrl_addr, | |
pinctrl_entries=formatted_entries[:-1], | |
) | |
) | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-p", "--cube-path", required=True, help="CubeMX path") | |
parser.add_argument("-o", "--output", required=True, help="Output directory") | |
args = parser.parse_args() | |
main(args.cube_path, args.output) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment