572 lines
19 KiB
Python
572 lines
19 KiB
Python
|
#!/usr/bin/env python3
|
||
|
############################################################################
|
||
|
# tools/stm32_pinmap_tool.py
|
||
|
#
|
||
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||
|
# contributor license agreements. See the NOTICE file distributed with
|
||
|
# this work for additional information regarding copyright ownership. The
|
||
|
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
||
|
# "License"); you may not use this file except in compliance with the
|
||
|
# License. You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||
|
# License for the specific language governing permissions and limitations
|
||
|
# under the License.
|
||
|
#
|
||
|
############################################################################
|
||
|
|
||
|
# for python2.7 compatibility
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import argparse
|
||
|
import os
|
||
|
import re
|
||
|
import sys
|
||
|
from argparse import RawTextHelpFormatter
|
||
|
from glob import glob
|
||
|
|
||
|
suffix = "_0"
|
||
|
remaps_re = re.compile(r"(.*REMAP.*)=y")
|
||
|
ip_block_re = re.compile(r"CONFIG_STM32[A-Z0-9]*_([A-Z0-9]+[0-9]*)=")
|
||
|
stm32f1_re = re.compile(r"stm32f10[0-9][a-z]*_pinmap")
|
||
|
speed_re = re.compile(r"(GPIO_(?:SPEED|MODE)_[zA-Z0-9]+)")
|
||
|
port_re = re.compile(r"GPIO_PORT([A-Z])\|")
|
||
|
pin_re = re.compile(r"GPIO_PIN(\d+)")
|
||
|
define_re = re.compile(r"#\s*define\s+(GPIO.*)\s+(GPIO.*?)\s+")
|
||
|
|
||
|
|
||
|
class GPIODef:
|
||
|
def __init__(self, original_name, name, description):
|
||
|
self.original_name = original_name
|
||
|
self.name = name
|
||
|
self.block = name.split("_")[1]
|
||
|
self.speed = None
|
||
|
s = speed_re.search(description)
|
||
|
if s:
|
||
|
self.speed = s.group(1)
|
||
|
s = port_re.search(description)
|
||
|
if s:
|
||
|
self.port = s.group(1)
|
||
|
s = pin_re.search(description)
|
||
|
if s:
|
||
|
self.pin = s.group(1)
|
||
|
|
||
|
def __str__(self):
|
||
|
fmt = "#define {0: <20} {1} /* P{2} */"
|
||
|
if self.speed:
|
||
|
if "MODE" in self.speed:
|
||
|
if "MHz" in self.speed:
|
||
|
# F1 has mode, MHz is output, we must adjust the speed
|
||
|
fmt = "#define {0: <20} GPIO_ADJUST_MODE({1}, {3}) /* P{2} */ "
|
||
|
else:
|
||
|
# All others had a OSPEDD reg so we just set it
|
||
|
fmt = "#define {0: <20} ({1} | {3}) /* P{2} */ "
|
||
|
|
||
|
return fmt.format(
|
||
|
self.original_name,
|
||
|
self.name,
|
||
|
self.port + self.pin,
|
||
|
self.speed,
|
||
|
)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return f"<GPIODef block:{self.block} \
|
||
|
original_name:{self.original_name} \
|
||
|
name:{self.name} port:{self.port} \
|
||
|
pin:{self.pin} speed:{self.speed}>"
|
||
|
|
||
|
|
||
|
# Detect python version
|
||
|
if sys.version_info[0] < 3:
|
||
|
runningPython3 = False
|
||
|
else:
|
||
|
runningPython3 = True
|
||
|
|
||
|
|
||
|
def parse_args():
|
||
|
# Parse commandline arguments
|
||
|
parser = argparse.ArgumentParser(
|
||
|
formatter_class=RawTextHelpFormatter,
|
||
|
description="""stm32_pinmap_tool.py
|
||
|
|
||
|
This tool is used to migrate legacy stm32 pinmap files that
|
||
|
had included pin speed (slew rate control) in pinmap pin definitions
|
||
|
|
||
|
These speeds should have never been part of the arch defines as these
|
||
|
are layout and board dependent. Therefore, the complete definition
|
||
|
should be a composition of the pinmap defines and speed, and defined in
|
||
|
board.h
|
||
|
|
||
|
Furthermore, pinmaps did not suffix pins that had only one ALT
|
||
|
appearance on a GPIO. Therefore there was no way to change the speed
|
||
|
or any other pins attribute i.e. Pullup Pulldown, Push pull. Open Drain etc.
|
||
|
|
||
|
The tool has a conversion mode and a report mode.
|
||
|
|
||
|
Conversion mode tool use:
|
||
|
|
||
|
Run the tool to do the conversion:
|
||
|
i.e tools/stm32_pinmap_tool.py
|
||
|
--pinmap arch/arm/src/stm32h7/hardware/stm32h7x3xx_pinmap.h
|
||
|
--legacy > arch/arm/src/stm32h7/hardware/stm32h7x3xx_pinmap-new.h
|
||
|
|
||
|
-- pinmap - the file to convert
|
||
|
--legacy will make a copy of the pinmap. Properly updating the file with
|
||
|
xxxx/xxxxxxx_legacy to the title block,
|
||
|
and adding _LEGACY to the #ifdef, #define and endif comment of the inclusion guard.
|
||
|
|
||
|
Conversion mode follow up edits:
|
||
|
1. diff and verify the original pinmap and the pinmap-new.h are as expected.
|
||
|
delete original pinmap
|
||
|
rename pinmap-new.h to the original pinmap name.
|
||
|
2. Edit the top level pinmap (i.e. arch/arm/src/stm32x/stm32x_pinmap.h) file and
|
||
|
add a CONFIG_STM32xx_USE_LEGACY_PINMAP section
|
||
|
that includes the legacy pinmap files.
|
||
|
|
||
|
For example
|
||
|
if defined(CONFIG_STM32H7_USE_LEGACY_PINMAP)
|
||
|
if defined(CONFIG_STM32H7_STM32H7X3XX)
|
||
|
include "hardware/stm32h7x3xx_pinmap_legacy.h"
|
||
|
elif defined(CONFIG_STM32H7_STM32H7B3XX)
|
||
|
include "hardware/stm32h7x3xx_pinmap_legacy.h"
|
||
|
elif defined(CONFIG_STM32H7_STM32H7X7XX)
|
||
|
include "hardware/stm32h7x3xx_pinmap_legacy.h"
|
||
|
else
|
||
|
error "Unsupported STM32 H7 Pin map"
|
||
|
endif
|
||
|
else
|
||
|
if defined(CONFIG_STM32H7_STM32H7X3XX)
|
||
|
include "hardware/stm32h7x3xx_pinmap.h"
|
||
|
elif defined(CONFIG_STM32H7_STM32H7B3XX)
|
||
|
include "hardware/stm32h7x3xx_pinmap.h"
|
||
|
elif defined(CONFIG_STM32H7_STM32H7X7XX)
|
||
|
include "hardware/stm32h7x3xx_pinmap.h"
|
||
|
else
|
||
|
error "Unsupported STM32 H7 Pin map"
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
3. Add a STM32Hx_USE_LEGACY_PINMAP to the Kconfig defaulted to y
|
||
|
|
||
|
For example
|
||
|
|
||
|
config STM32H7_USE_LEGACY_PINMAP
|
||
|
bool "Use the legacy pinmap with GPIO_SPEED_xxx included."
|
||
|
default y
|
||
|
---help---
|
||
|
In the past, pinmap files included GPIO_SPEED_xxxMhz. These speed
|
||
|
settings should have come from the board.h as it describes the wiring
|
||
|
of the SoC to the board. The speed is really slew rate control and
|
||
|
therefore is related to the layout and can only be properly set
|
||
|
in board.h.
|
||
|
|
||
|
STM32H7_USE_LEGACY_PINMAP is provided, to allow lazy migration to
|
||
|
using pinmaps without speeds. The work required to do this can be aided
|
||
|
by running tools/stm32_pinmap_tool.py. The tools will take a board.h
|
||
|
file and a legacy pinmap and output the required changes that one needs
|
||
|
to make to a board.h file.
|
||
|
|
||
|
Eventually, STM32H7_USE_LEGACY_PINMAP will be deprecated and the legacy
|
||
|
pinmaps removed from NuttX. Any new boards added should set
|
||
|
STM32H7_USE_LEGACY_PINMAP=n and fully define the pins in board.h
|
||
|
4. Add a warning to the xxx_gpio.c file
|
||
|
|
||
|
For example
|
||
|
|
||
|
#if defined(CONFIG_STM32_USE_LEGACY_PINMAP)
|
||
|
# pragma message "CONFIG_STM32_USE_LEGACY_PINMAP will be deprecated migrate board.h see tools/stm32_pinmap_tool.py"
|
||
|
#endif
|
||
|
|
||
|
Report mode tool use:
|
||
|
|
||
|
Run the tool to aid in migrating a board.h
|
||
|
|
||
|
tools/stm32_pinmap_tool.py --pinmap arch/arm/src/stm32h7/hardware/stm32h7x3xx_pinmap_legacy.h
|
||
|
--report <fullpath>/include/board.h
|
||
|
|
||
|
it will output 2 sections that should be used to update the board.h.
|
||
|
board.h defines that need to have speeds added.
|
||
|
board.h defines that will need to be added:
|
||
|
""",
|
||
|
)
|
||
|
|
||
|
parser.add_argument(
|
||
|
"--pinmap",
|
||
|
action="store",
|
||
|
help="""pin map file to convert (changes are printed on stdout) or
|
||
|
Legacy file pin map file named <filename>_legacy.<ext> to report board.h changes""",
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
"--report",
|
||
|
default=False,
|
||
|
action="store",
|
||
|
help="Generate change set for a board",
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
"--legacy",
|
||
|
default=False,
|
||
|
action="store_true",
|
||
|
help="If one does not exist, create a copy of the original pin map named <filename>_legacy.<ext>",
|
||
|
)
|
||
|
args = parser.parse_args()
|
||
|
return args
|
||
|
|
||
|
|
||
|
def create_legacy(source):
|
||
|
legacy = source.replace(".h", "_legacy.h")
|
||
|
sourceshort = source[source.find("arch") :]
|
||
|
legacyshort = legacy[legacy.find("arch") :]
|
||
|
srctag = "__" + sourceshort.upper().replace("/", "_")
|
||
|
destag = "__" + legacyshort.upper().replace("/", "_").replace(".", "_")
|
||
|
if not os.path.isfile(legacy):
|
||
|
fout = open(legacy, "w")
|
||
|
fin = open(source, "r")
|
||
|
|
||
|
for line in fin:
|
||
|
out = re.sub(sourceshort, legacyshort, line)
|
||
|
out = re.sub(srctag, destag, out)
|
||
|
fout.write(out)
|
||
|
fout.close()
|
||
|
fin.close()
|
||
|
|
||
|
|
||
|
def read_defconfigs(boardfile_path):
|
||
|
configs_lines = []
|
||
|
defconfigs_files = []
|
||
|
|
||
|
for dir, _, _ in os.walk(boardfile_path[: boardfile_path.find("include/board.h")]):
|
||
|
defconfigs_files.extend(glob(os.path.join(dir, "defconfig")))
|
||
|
|
||
|
for file in defconfigs_files:
|
||
|
defconfigfile = open(file, "r")
|
||
|
configs_lines.extend(defconfigfile.readlines())
|
||
|
defconfigfile.close()
|
||
|
return configs_lines
|
||
|
|
||
|
|
||
|
def build_ip_remap_list(boardfile_path):
|
||
|
ip_blocks = []
|
||
|
ip_remaps = []
|
||
|
configs_lines = read_defconfigs(boardfile_path)
|
||
|
configs_lines = sorted(set(configs_lines))
|
||
|
|
||
|
for line in configs_lines:
|
||
|
s = ip_block_re.search(line)
|
||
|
if s:
|
||
|
ip_blocks.extend([s.group(1)])
|
||
|
else:
|
||
|
s = remaps_re.search(line)
|
||
|
if s:
|
||
|
ip_remaps.extend([s.group(1)])
|
||
|
return [ip_blocks, ip_remaps]
|
||
|
|
||
|
|
||
|
def read_board_h(boardfile_path):
|
||
|
boardfile = open(boardfile_path, "r")
|
||
|
lines = boardfile.readlines()
|
||
|
boardfile.close()
|
||
|
return lines
|
||
|
|
||
|
|
||
|
def formated_print(lines):
|
||
|
maxlen = 0
|
||
|
for line in lines:
|
||
|
linelen = line.find("/*")
|
||
|
if linelen > maxlen:
|
||
|
maxlen = linelen
|
||
|
|
||
|
for line in lines:
|
||
|
linelen = line.find("/*")
|
||
|
if linelen > 1 and linelen < maxlen:
|
||
|
nl = line[:linelen] + " " * (maxlen - linelen) + line[linelen:]
|
||
|
line = nl
|
||
|
print(line)
|
||
|
|
||
|
|
||
|
def report(boardfile_path, boards_ip_blocks, changelog, changelog_like):
|
||
|
output = [
|
||
|
"",
|
||
|
]
|
||
|
output.extend(
|
||
|
[
|
||
|
"""
|
||
|
There were 3 issues with the Legacy pinmaps.
|
||
|
|
||
|
1. The legacy version of the pin defines included speed settings. (These are
|
||
|
in reality, slew rates).
|
||
|
|
||
|
2. Legacy pinmaps erroneously added speeds on pins that are only used
|
||
|
as an inputs (i.e UART4_RX). These speeds can be removed from the board.h
|
||
|
defines.
|
||
|
|
||
|
3. Also the legacy version of the pin defines did not have a suffix on all
|
||
|
pins and therefore all pins could not have the attributes set or changed
|
||
|
by board.h
|
||
|
|
||
|
The new pinmaps correct these issues:
|
||
|
|
||
|
Pin that had an explicit (GPIO_SPEED|MODE)_xxxMHz are removed or set to
|
||
|
the lowest speed.
|
||
|
|
||
|
If the pin had only one choice previously (un-suffixed) the pin name now
|
||
|
contains _0 as the suffix.
|
||
|
|
||
|
N.B. The correct speed setting for a given pin is very dependent on the
|
||
|
layout of the circuit board and load presented to the SoC on that pin.
|
||
|
|
||
|
The speeds listed below are from the Legacy pinmaps and are provided ONLY
|
||
|
to insure these changes do not break existing systems that are relying on
|
||
|
the legacy speed settings.
|
||
|
|
||
|
It highly recommended that the speed setting for each pin be verified for
|
||
|
overshoot and undershoot on real hardware and adjusted in the board,h
|
||
|
appropriately.
|
||
|
|
||
|
|
||
|
board.h defines that need to have speeds added.
|
||
|
|
||
|
"""
|
||
|
]
|
||
|
)
|
||
|
|
||
|
boards_blocks = []
|
||
|
Lines = read_board_h(boardfile_path)
|
||
|
for line in Lines:
|
||
|
s = define_re.search(line)
|
||
|
if s:
|
||
|
# #define GPIO_SD_CK GPIO_SD_CK_1 /* PD6 FC_PD6_SD_CK */
|
||
|
define = s.group(1)
|
||
|
original_name = s.group(2)
|
||
|
change = changelog.get(original_name)
|
||
|
if change:
|
||
|
pindef = GPIODef(define, original_name, line)
|
||
|
if pindef.block not in boards_blocks:
|
||
|
boards_blocks.append(pindef.block)
|
||
|
output.extend([f"\n/* {pindef.block} */\n"])
|
||
|
output.extend([str(changelog[original_name])])
|
||
|
if len(boards_blocks) == 0:
|
||
|
output.extend(
|
||
|
[
|
||
|
"""
|
||
|
No pins are defined in board.h to change speeds on (most likely an stm32f1")
|
||
|
We will define all the pins used next...
|
||
|
"""
|
||
|
]
|
||
|
)
|
||
|
|
||
|
formated_print(output)
|
||
|
output = []
|
||
|
|
||
|
output.extend(
|
||
|
[
|
||
|
"""
|
||
|
|
||
|
Pin that had only one choice previously (un-suffixed) pins will need to be
|
||
|
defined in board.h to map the un-suffixed) pin name used in the drives to
|
||
|
the _0 suffixed ones.
|
||
|
|
||
|
Pins that did not have an explicit (GPIO_SPEED|MODE)_xxxMHz specified are
|
||
|
listed with the pin name containing the new suffix.
|
||
|
|
||
|
|
||
|
board.h defines that may need to be added if the pins are used on the board:
|
||
|
|
||
|
|
||
|
"""
|
||
|
]
|
||
|
)
|
||
|
|
||
|
for block in boards_ip_blocks:
|
||
|
change = changelog_like.get(block)
|
||
|
if change:
|
||
|
block_title = f"\n/* {block} */\n"
|
||
|
for gpio in change:
|
||
|
if re.search(r"_\d+$", gpio.original_name) is None:
|
||
|
if block_title:
|
||
|
output.extend([block_title])
|
||
|
block_title = None
|
||
|
output.extend([str(gpio)])
|
||
|
|
||
|
formated_print(output)
|
||
|
|
||
|
|
||
|
def formatcols(list, cols):
|
||
|
lines = ("\t".join(list[i : i + cols]) for i in range(0, len(list), cols))
|
||
|
return "\n".join(lines)
|
||
|
|
||
|
|
||
|
def parse_conditional(lines, conditions):
|
||
|
defines = []
|
||
|
|
||
|
def_remap_re = re.compile(r"\s*defined\s*\((.*REMAP.*)\)")
|
||
|
def_else_re = re.compile(r"#\s*else")
|
||
|
def_endif_re = re.compile(r"#\s*endif")
|
||
|
|
||
|
active_define = None
|
||
|
output = True
|
||
|
once = False
|
||
|
|
||
|
for line in lines:
|
||
|
# process #[el]if define(...REMAP)
|
||
|
s = def_remap_re.search(line)
|
||
|
if s:
|
||
|
once = True
|
||
|
define = s.group(1)
|
||
|
if define in conditions:
|
||
|
active_define = define
|
||
|
output = True
|
||
|
else:
|
||
|
output = False
|
||
|
else:
|
||
|
# process #endif
|
||
|
s = def_endif_re.search(line)
|
||
|
if s:
|
||
|
active_define = None
|
||
|
output = True
|
||
|
else:
|
||
|
# process #elese
|
||
|
s = def_else_re.search(line)
|
||
|
if s:
|
||
|
once = True
|
||
|
# the if or elif was taken do not output the else
|
||
|
if active_define:
|
||
|
output = False
|
||
|
else:
|
||
|
output = output ^ True
|
||
|
|
||
|
if once or output:
|
||
|
once = False
|
||
|
defines.extend([line])
|
||
|
return defines
|
||
|
|
||
|
|
||
|
def formmatter(args):
|
||
|
# if pinmap passed is a legacy pinmap. Just generate a report
|
||
|
report_only = args.report is not False
|
||
|
|
||
|
speed_not_mode = stm32f1_re.search(args.pinmap) is None
|
||
|
|
||
|
if not report_only and args.legacy is True:
|
||
|
create_legacy(args.pinmap)
|
||
|
|
||
|
pinfile = open(args.pinmap, "r")
|
||
|
Lines = pinfile.readlines()
|
||
|
|
||
|
if report_only:
|
||
|
boards_ip_blocks, remaps = build_ip_remap_list(args.report)
|
||
|
print(
|
||
|
f"\n\nBoard enabled Blocks:\n\n{formatcols(sorted(boards_ip_blocks), 8)}\n\n"
|
||
|
)
|
||
|
if (
|
||
|
"ADC1" in boards_ip_blocks
|
||
|
or "ADC2" in boards_ip_blocks
|
||
|
or "ADC3" in boards_ip_blocks
|
||
|
):
|
||
|
boards_ip_blocks.extend(["ADC12"])
|
||
|
boards_ip_blocks.extend(["ADC123"])
|
||
|
boards_ip_blocks = sorted(boards_ip_blocks)
|
||
|
# Filter out ifdefed by remap conditionals (F1)
|
||
|
if len(remaps) > 0:
|
||
|
Lines = parse_conditional(Lines, remaps)
|
||
|
|
||
|
Pass = False
|
||
|
inComment = False
|
||
|
|
||
|
changelog = {}
|
||
|
changelog_like = {}
|
||
|
pass_list = [r"#\s*if", r"#\s*else", r"#\s*end", r"#\s*include", r"#\s*undef"]
|
||
|
pass_list_re = re.compile("|".join(pass_list))
|
||
|
|
||
|
for line in Lines:
|
||
|
if len(line.strip()) == 0:
|
||
|
Pass = True
|
||
|
if pass_list_re.search(line):
|
||
|
Pass = True
|
||
|
if "#define" in line and "GPIO" not in line:
|
||
|
Pass = True
|
||
|
if "defined(" in line:
|
||
|
Pass = True
|
||
|
if "/*" in line:
|
||
|
inComment = True
|
||
|
Pass = True
|
||
|
if "*/" in line:
|
||
|
inComment = False
|
||
|
Pass = True
|
||
|
if Pass or inComment:
|
||
|
Pass = False
|
||
|
if not report_only:
|
||
|
print(line.rstrip(), end="")
|
||
|
else:
|
||
|
changed = False
|
||
|
# split the line on spaces
|
||
|
pieces = line.split()
|
||
|
# deal with white space in the # define for nested defines
|
||
|
sel = 0
|
||
|
# Does it have white space then use next set?
|
||
|
if pieces[0] == "#":
|
||
|
sel = 1
|
||
|
original_name = pieces[sel + 1]
|
||
|
gpiocgf = pieces[sel + 2]
|
||
|
new_name = original_name
|
||
|
if re.search(r"_\d+$", original_name) is None:
|
||
|
# Add suffix
|
||
|
pad = ""
|
||
|
sel = line.find(original_name) + len(original_name)
|
||
|
if line[sel + len(suffix)] == "(":
|
||
|
pad = " "
|
||
|
if line[sel + len(suffix)] == "G":
|
||
|
pad = " ("
|
||
|
nl = line[:sel] + suffix + pad + line[sel + len(suffix) :]
|
||
|
new_name = original_name + suffix
|
||
|
changed = True
|
||
|
else:
|
||
|
nl = line
|
||
|
# Remove the speed or chege the Mode
|
||
|
if speed_not_mode:
|
||
|
ol = re.sub(r"\s*GPIO_SPEED_[zA-Z0-9]+\s*\|", "", nl)
|
||
|
else:
|
||
|
ol = re.sub(
|
||
|
r"(\s*)GPIO_MODE_[0-9]+MHz(\s*\|)", r"\g<1>GPIO_MODE_2MHz\g<2>", nl
|
||
|
)
|
||
|
|
||
|
changed = changed or ol != nl
|
||
|
if not report_only:
|
||
|
print(ol.strip(), end="")
|
||
|
if args.report and changed:
|
||
|
changelog[original_name] = pindef = GPIODef(
|
||
|
original_name, new_name, gpiocgf
|
||
|
)
|
||
|
|
||
|
# create changes by block if enabled
|
||
|
if pindef.block in boards_ip_blocks:
|
||
|
# Is block in already?
|
||
|
if pindef.block in changelog_like:
|
||
|
# do not duplicate it
|
||
|
if pindef not in changelog_like[pindef.block]:
|
||
|
changelog_like[pindef.block].append(pindef)
|
||
|
else:
|
||
|
changelog_like[pindef.block] = [pindef]
|
||
|
|
||
|
if not report_only:
|
||
|
print("")
|
||
|
if args.report:
|
||
|
report(args.report, boards_ip_blocks, changelog, changelog_like)
|
||
|
|
||
|
|
||
|
def main():
|
||
|
# Python2 is EOL
|
||
|
if not runningPython3:
|
||
|
raise RuntimeError(
|
||
|
"Python 2 is not supported. Please try again using Python 3."
|
||
|
)
|
||
|
args = parse_args()
|
||
|
formmatter(args)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|