| #!/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() |