#!/usr/bin/env python3 ############################################################################ # tools/stm32_pinmap_tool.py # # SPDX-License-Identifier: Apache-2.0 # # 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"" # 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 /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 _legacy. 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 _legacy.", ) 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()