1"""
2Utility to autogenerate pinctrl definitions.
3
4Usage::
5    python3 gd32pinctrl.py [-i /path/to/configs] [-o /path/to/include]
6
7Copyright (c) 2021 Teslabs Engineering S.L.
8SPDX-License-Identifier: Apache 2.0
9"""
10
11import argparse
12from collections import OrderedDict
13from pathlib import Path
14import re
15
16import yaml
17
18
19REPO_ROOT = Path(__file__).absolute().parents[1]
20"""Repository root."""
21
22AFIO_REMAP_SUFFIXES = {
23    0: ("NORMP",),
24    2: ("NORMP", "RMP"),
25    3: ("NORMP", "PRMP", "FRMP"),
26    4: ("NORMP", "PRMP1", "PRMP2", "FRMP"),
27}
28"""AFIO remap suffixes (# remap options <> suffix)."""
29
30AFIO_MODE_NAMES = {
31    "analog": "ANALOG",
32    "inp": "GPIO_IN",
33    "out": "ALTERNATE",
34}
35"""AFIO mode names."""
36
37HEADER = """/*
38 * Autogenerated file
39 *
40 * SPDX-License-Identifier: Apache 2.0
41 */
42"""
43"""Header for the generated files."""
44
45
46def get_header_fname(series, variant):
47    """Get header file name.
48
49    Args:
50        series: Series.
51        variant: Variant information.
52
53    Returns:
54        Header file name.
55    """
56
57    pincode = variant["pincode"].lower()
58    memories = f"({'-'.join((str(m).lower() for m in variant['memories']))})"
59    return f"{series}{pincode}{memories}xx-pinctrl.h"
60
61
62def get_port_pin(pin_name):
63    """Obtain port and pin number from a pin name
64
65    Args:
66        pin_name: Pin name, e.g. PA0
67
68    Returns:
69        Port and pin, e.g. A, 0.
70    """
71
72    m = re.match(r"P([A-Z])(\d+)", pin_name)
73    if not m:
74        raise ValueError(f"Unexpected pin name: {pin_name}")
75
76    return m.group(1), m.group(2)
77
78
79def generate_afio_header(outdir, variant, series, pin_cfgs):
80    """Generate AFIO header with pin configurations.
81
82    Args:
83        outdir: Output base directory.
84        variant: Variant information.
85        series: Series.
86        pin_cfgs: Pin configurations.
87    """
88
89    pin_cfgs = OrderedDict(sorted(pin_cfgs.items(), key=lambda kv: kv[0]))
90
91    with open(outdir / get_header_fname(series, variant), "w") as f:
92        f.write(HEADER)
93        f.write(f"\n#include \"{series}xx-afio.h\"\n")
94        for signal, cfg in pin_cfgs.items():
95            f.write(f"\n/* {signal} */\n")
96            for port, pin, mode, name_suffix, remap in cfg:
97                define = f"#define {signal}_P{port}{pin}{name_suffix}"
98                define_val = f"GD32_PINMUX_AFIO('{port}', {pin}, {mode}, {remap})"
99                f.write(f"{define} \\\n\t{define_val}\n")
100
101
102def generate_af_header(outdir, variant, series, pin_cfgs):
103    """Generate AF header with pin configurations.
104
105    Args:
106        outdir: Output base directory.
107        variant: Variant information.
108        series: Series.
109        pin_cfgs: Pin configurations.
110    """
111
112    pin_cfgs = OrderedDict(sorted(pin_cfgs.items(), key=lambda kv: kv[0]))
113
114    with open(outdir / get_header_fname(series, variant), "w") as f:
115        f.write(HEADER)
116        f.write("\n#include \"gd32-af.h\"\n")
117        for signal, cfg in pin_cfgs.items():
118            f.write(f"\n/* {signal} */\n")
119            for port, pin, mode in cfg:
120                define = f"#define {signal}_P{port}{pin}"
121                define_val = f"GD32_PINMUX_AF('{port}', {pin}, {mode})"
122                f.write(f"{define} \\\n\t{define_val}\n")
123
124
125def build_afio_pin_cfgs(variant, signal_configs, pins, remaps):
126    """Build AFIO pin configurations.
127
128    Args:
129        variant: Variant information.
130        signal_configs: Signal configurations.
131        pins: Pins description.
132        remaps: Remaps description.
133
134    Returns:
135        Dictionary with pins configuration.
136    """
137
138    pin_cfgs = {"ANALOG": []}
139
140    pincode = variant["pincode"]
141    memories = variant["memories"]
142
143    for signal, signal_cfg in signal_configs.items():
144        # check if signal is excluded from current pincode
145        if pincode in signal_cfg.get("exclude-pincodes", []):
146            continue
147
148        # check if signal is excluded from current list of memories
149        if set(memories).intersection(signal_cfg.get("exclude-memories", [])):
150            continue
151
152        signal_pins = {}
153
154        # collect all afs
155        for pin, pin_cfg in pins.items():
156            if pincode not in pin_cfg["pincodes"]:
157                continue
158            if signal in pin_cfg["afs"]:
159                signal_pins[pin] = [0]
160
161        # collect all remaps
162        remap_options = 0
163        signal_remaps = remaps.get(signal)
164        if signal_remaps:
165            for pin in signal_remaps["pins"]:
166                if not pin:
167                    continue
168
169                if pincode in pins[pin]["pincodes"]:
170                    if pin not in signal_pins:
171                        signal_pins[pin] = []
172
173                    if remap_options not in signal_pins[pin]:
174                        signal_pins[pin].append(remap_options)
175
176                remap_options += 1
177
178        for pin, remap_values in signal_pins.items():
179            for remap_value in remap_values:
180                for mode in signal_cfg["modes"]:
181                    port, pin_number = get_port_pin(pin)
182                    remap = AFIO_REMAP_SUFFIXES[remap_options][remap_value]
183
184                    name_suffix = ""
185                    if len(signal_cfg["modes"]) > 1:
186                        name_suffix = f"_{mode.upper()}"
187
188                    if remap_options > 0:
189                        name_suffix += f"_{remap}"
190                        remap = signal.split("_")[0] + f"_{remap}"
191
192                    if signal not in pin_cfgs:
193                        pin_cfgs[signal] = []
194
195                    pin_cfgs[signal].append(
196                        (
197                            port,
198                            pin_number,
199                            AFIO_MODE_NAMES[mode],
200                            name_suffix,
201                            remap,
202                        )
203                    )
204
205    # add analog entries (used for low power mode)
206    for pin, pin_cfg in pins.items():
207        if pincode not in pin_cfg["pincodes"]:
208            continue
209
210        port, pin_number = get_port_pin(pin)
211        pin_cfgs["ANALOG"].append((port, pin_number, "ANALOG", "", "NORMP"))
212
213    return pin_cfgs
214
215
216def build_af_pin_cfgs(variant, signal_configs, pins):
217    """Build AF pin configurations.
218
219    Args:
220        variant: Variant information.
221        signal_configs: Signals description.
222        pins: Pins description.
223
224    Returns:
225        Dictionary with pins configuration.
226    """
227
228    pin_cfgs = {"ANALOG": []}
229
230    pincode = variant["pincode"]
231    memories = variant["memories"]
232
233    for pin, pin_cfg in pins.items():
234        if pincode not in pin_cfg["pincodes"]:
235            continue
236
237        port, pin_number = get_port_pin(pin)
238
239        # add analog entry (used for low power mode)
240        pin_cfgs["ANALOG"].append((port, pin_number, "ANALOG"))
241
242        for signal, mode in pin_cfg["afs"].items():
243            signal_config = signal_configs.get(signal)
244            # check if signal is excluded from current pincode.
245            if signal_config and pincode in signal_config.get(
246                "exclude-pincodes", []
247            ):
248                continue
249            # check if signal is excluded from current list of memories
250            if signal_config and set(memories).intersection(
251                signal_config.get("exclude-memories", [])
252            ):
253                continue
254
255            if signal not in pin_cfgs:
256                pin_cfgs[signal] = []
257
258            if mode != "ANALOG":
259                mode = f"AF{mode}"
260
261            pin_cfgs[signal].append((port, pin_number, mode))
262
263    return pin_cfgs
264
265
266def main(indir, outdir) -> None:
267    """Entry point.
268
269    Args:
270        indir: Directory with pin configuration files.
271        outdir: Output directory
272    """
273
274    if outdir.exists():
275        for entry in outdir.glob("gd32*-pinctrl.h"):
276            entry.unlink()
277    else:
278        outdir.mkdir()
279
280    for entry in indir.iterdir():
281        if not entry.is_file() or entry.suffix not in (".yml", ".yaml"):
282            continue
283
284        config = yaml.load(open(entry), Loader=yaml.Loader)
285
286        model = config["model"]
287        series = config["series"]
288        variants = config["variants"]
289        signal_configs = config.get("signal-configs", {})
290        pins = config["pins"]
291
292        if model == "afio":
293            remaps = config["remaps"]
294            for variant in variants:
295                pin_cfgs = build_afio_pin_cfgs(variant, signal_configs, pins, remaps)
296                generate_afio_header(outdir, variant, series, pin_cfgs)
297        elif model == "af":
298            for variant in variants:
299                pin_cfgs = build_af_pin_cfgs(variant, signal_configs, pins)
300                generate_af_header(outdir, variant, series, pin_cfgs)
301        else:
302            raise ValueError(f"Unexpected model: {model}")
303
304
305if __name__ == "__main__":
306    parser = argparse.ArgumentParser()
307    parser.add_argument(
308        "-i",
309        "--indir",
310        type=Path,
311        default=REPO_ROOT / "pinconfigs",
312        help="Directory with pin configuration files",
313    )
314    parser.add_argument(
315        "-o",
316        "--outdir",
317        type=Path,
318        default=REPO_ROOT / "include" / "dt-bindings" / "pinctrl",
319        help="Output directory",
320    )
321    args = parser.parse_args()
322
323    main(args.indir, args.outdir)
324