1#!/usr/bin/env python3
2#
3# Copyright (c) 2024 GARDENA GmbH
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""Generate crossbar register values from devicetree pinctrl nodes.
8
9The SiM3U1xx SoCs do support pinmuxing, but with many limimitations. It's also non-optional, so we
10can't just not support it and rely on default values.
11
12The hardware doesn't allow us to properly implement the pinmux API, but we still want to use the
13standard pinctrl nodes inside the devicetree to define which peripheral should be muxed to which
14pins. To accomplish that, this script parses these nodes and generates code that applies all
15`default` nodes of all enabled devices within soc.c.
16
17There's two crossbars:
18- crossbar 0: Controls portbanks 0 and 1
19- crossbar 1: Controls portbanks 2 and 3
20
21Each crossbar has two configuration-values which reside in different registers:
22- config: A bitmask which tells the crossbar which peripherals should be muxed. Some peripherals
23          have multiple bits to prevent having to mux unused pins (like UART flow control).
24- enable: A bit which enables or disables the whole crossbar. When disabled, all pins of the
25          crossbar are disconnected.
26
27And each portbank has this related config:
28- pbskipen: A bitmask where value `1` means, that the pin will not be muxed.
29            The index of the bit refers to the pin number.
30
31A crossbar has a list of signals that it tries to mux to pins that it controls. That list has a
32fixed order and signals that belong to the same peripheral are next to each other.
33The crossbar hardware simply iterates the signal list and assigns each signal to the lowest
34available pin. That has a few implications:
35
36- Pins of a peripheral are always consecutive within all non-skipped pins.
37- Peripherals that appear first in the signal list will always use lower pin-numbers than later ones
38- There's no way to change the muxing without side effects. Writing to pbskipen while the crossbar
39  is enabled might temporarily cause unwanted muxing. Disabling the crossbar will disconnect all
40  peripherals for a short time.
41
42The last point is the reason why we don't implement Zephyrs runtime pinmuxing API. Applying the
43pinmuxing one by one as drivers get initialized would require disconnecting pins quite often.
44"""
45
46import argparse
47import enum
48import os
49import pickle
50import sys
51
52sys.path.insert(
53    0, os.path.join(os.environ["ZEPHYR_BASE"], "scripts/dts/python-devicetree/src")
54)
55
56
57class Signal(enum.Enum):
58    USART0_TX = 0
59    USART0_RX = 1
60    USART0_RTS = 2
61    USART0_CTS = 3
62    USART0_UCLK = 4
63
64    USART1_TX = 9
65    USART1_RX = 10
66    USART1_RTS = 11
67    USART1_CTS = 12
68    USART1_UCLK = 13
69
70    SPI2_SCK = 50
71    SPI2_MISO = 51
72    SPI2_MOSI = 52
73    SPI2_NSS = 53
74
75
76class PinMode(enum.Enum):
77    ANALOG = 0
78    DIGITAL_INPUT = 1
79    PUSH_PULL_OUTPUT = 2
80
81
82class Pinmux:
83    def __init__(self, value, props):
84        self.pin = (value & 0x7, (value >> 3) & 0xFF)
85        self.signal = Signal((value >> 22) & 0x7F)
86
87        output_low = props["output-low"].val
88        output_high = props["output-high"].val
89        output_enable = props["output-enable"].val or output_low or output_high
90
91        input_enable = props["input-enable"].val
92
93        if output_enable and input_enable:
94            raise Exception("can't enable both output and input")
95        if output_low and output_high:
96            raise Exception("can't define output as both low and high")
97
98        if input_enable:
99            self.pinmode = PinMode.DIGITAL_INPUT
100        elif output_enable:
101            self.pinmode = PinMode.PUSH_PULL_OUTPUT
102
103            if output_low:
104                self.output_value = True
105            elif output_high:
106                self.output_value = False
107            else:
108                self.output_value = None
109        else:
110            self.pinmode = None
111
112    def __repr__(self):
113        return self.__dict__.__repr__()
114
115
116def parse_args():
117    parser = argparse.ArgumentParser(allow_abbrev=False)
118    parser.add_argument(
119        "edt_pickle", help="path to read the pickled edtlib.EDT object from"
120    )
121    parser.add_argument("out", help="path to write the header file")
122
123    return parser.parse_args()
124
125
126class CrossbarBit:
127    def __init__(self, bit, signals, pin_first, pin_last):
128        assert pin_first <= pin_last
129
130        self.bit = bit
131        self.signals = signals
132        self.pin_first = pin_first
133        self.pin_last = pin_last
134
135    def __repr__(self):
136        return self.__dict__.__repr__()
137
138
139class Crossbar:
140    def __init__(self, number, bits, portbanks):
141        self.number = number
142        self.value = 0
143        self.bits = bits
144        self.first_configurable = (0, 0)
145        self.portbanks = portbanks
146
147    def enable_bit(self, bit, pins):
148        if len(pins) != len(bit.signals):
149            raise Exception(
150                f"pins({pins}) and signals({bit.signals}) must be the same length"
151            )
152
153        for pin in pins:
154            if pin < self.first_configurable:
155                raise Exception(
156                    "can't enable crossbar pin anymore", pin, self.first_configurable
157                )
158
159            self.portbanks[pin[0]].unskip(pin[1])
160
161            self.first_configurable = (pin[0], pin[1] + 1)
162
163        self.value |= 1 << bit.bit
164
165    def mux(self, muxs):
166        for bit in self.bits:
167            # collect the signals that are enabled by this bit
168            signal_muxs = {}
169            for mux in muxs:
170                if self.number == 0 and mux.pin[0] != 0 and mux.pin[0] != 1:
171                    continue
172                if self.number == 1 and mux.pin[0] != 2 and mux.pin[0] != 3:
173                    continue
174
175                if mux.signal not in bit.signals:
176                    continue
177                if mux.signal in signal_muxs:
178                    raise Exception("duplicate signal", mux)
179                if mux.pin < bit.pin_first or mux.pin > bit.pin_last:
180                    raise Exception("can't mux signal to pin", mux, bit)
181
182                signal_muxs[mux.signal] = mux
183
184            # this bit is disabled
185            if len(signal_muxs.keys()) == 0:
186                continue
187            # we have to enable all the signals
188            if len(signal_muxs.keys()) != len(bit.signals):
189                raise Exception("missing signals for bit", bit, signal_muxs)
190
191            # build pin list for this bit in the required order
192            pins = []
193            for _index, signal in enumerate(bit.signals):
194                mux = signal_muxs[signal]
195                pins.append(mux.pin)
196
197            self.enable_bit(bit, pins)
198
199            for mux in signal_muxs.values():
200                self.portbanks[mux.pin[0]].apply_mux(mux.pin[1], mux)
201
202    def __repr__(self):
203        return self.__dict__.__repr__()
204
205
206class Portbank:
207    def __init__(self):
208        self.pins_high = 0
209        self.pins_low = 0
210        self.pins_push_pull_output = 0
211        self.pins_digital_input = 0
212        self.pins_analog = 0
213
214        # skip all pins by default
215        self.skip_enable = 0xFFFF
216
217    def unskip(self, pin):
218        self.skip_enable &= ~(1 << pin)
219
220    def apply_mux(self, pin, mux):
221        if mux.pinmode == PinMode.ANALOG:
222            self.pins_analog |= 1 << pin
223        elif mux.pinmode == PinMode.DIGITAL_INPUT:
224            self.pins_digital_input |= 1 << pin
225        elif mux.pinmode == PinMode.PUSH_PULL_OUTPUT:
226            self.pins_push_pull_output |= 1 << pin
227
228            if mux.output_value:
229                self.pins_high |= 1 << pin
230            elif not mux.output_value:
231                self.pins_low |= 1 << pin
232            elif mux.output_value is None:
233                pass
234            else:
235                raise Exception("unsupported output value", mux.output_value)
236        elif mux.pinmode is None:
237            pass
238        else:
239            raise Exception("unsupported pinmode", mux.pinmode)
240
241    def __repr__(self):
242        return self.__dict__.__repr__()
243
244
245def main():
246    args = parse_args()
247
248    with open(args.edt_pickle, "rb") as f:
249        edt = pickle.load(f)
250
251    pinmux_table = []
252    for node in edt.nodes:
253        if node.status != "okay":
254            continue
255        if not node.pinctrls:
256            continue
257
258        pinctrl = None
259        for p in node.pinctrls:
260            if p.name != "default":
261                continue
262
263            if pinctrl is not None:
264                raise Exception("multiple default nodes", node)
265            pinctrl = p
266
267        if pinctrl is None:
268            raise Exception("no default node", node)
269
270        for conf_node in pinctrl.conf_nodes:
271            for child in conf_node.children.values():
272                for pin in child.props["pinmux"].val:
273                    pinmux_table.append(Pinmux(pin, child.props))
274
275    crossbar0_bits = [
276        CrossbarBit(0, [Signal.USART0_TX, Signal.USART0_RX], (0, 0), (1, 15)),
277        CrossbarBit(1, [Signal.USART0_RTS, Signal.USART0_CTS], (0, 0), (1, 15)),
278        CrossbarBit(2, [Signal.USART0_UCLK], (0, 0), (1, 15)),
279        CrossbarBit(5, [Signal.USART1_TX, Signal.USART1_RX], (0, 0), (1, 15)),
280        CrossbarBit(6, [Signal.USART1_RTS, Signal.USART1_CTS], (0, 0), (1, 15)),
281        CrossbarBit(7, [Signal.USART1_UCLK], (0, 0), (1, 15)),
282        CrossbarBit(
283            32 + 3,
284            [Signal.SPI2_SCK, Signal.SPI2_MISO, Signal.SPI2_MOSI],
285            (0, 0),
286            (1, 15),
287        ),
288        CrossbarBit(32 + 4, [Signal.SPI2_NSS], (0, 0), (1, 15)),
289    ]
290    crossbar1_bits = [
291        CrossbarBit(
292            7, [Signal.SPI2_SCK, Signal.SPI2_MISO, Signal.SPI2_MOSI], (2, 6), (3, 11)
293        ),
294        CrossbarBit(8, [Signal.SPI2_NSS], (2, 6), (3, 11)),
295    ]
296
297    portbanks = [Portbank(), Portbank(), Portbank(), Portbank()]
298    crossbars = [
299        Crossbar(0, crossbar0_bits, portbanks),
300        Crossbar(1, crossbar1_bits, portbanks),
301    ]
302
303    for crossbar in crossbars:
304        crossbar.mux(pinmux_table)
305
306    with open(args.out, "w", encoding="utf-8") as f:
307        for index, crossbar in enumerate(crossbars):
308            print(f"#define CROSSBAR_{index}_CONFIG 0x{crossbar.value:08X}ULL", file=f)
309
310        for index, portbank in enumerate(portbanks):
311            print(
312                f"#define PORTBANK_{index}_SKIPEN_VALUE 0x{portbank.skip_enable:04X}",
313                file=f,
314            )
315
316            print(
317                f"#define PORTBANK_{index}_PINS_HIGH 0x{portbank.pins_high:04X}",
318                file=f,
319            )
320            print(
321                f"#define PORTBANK_{index}_PINS_LOW 0x{portbank.pins_low:04X}",
322                file=f,
323            )
324
325            print(
326                f"#define PORTBANK_{index}_PINS_DIGITAL_INPUT 0x{portbank.pins_digital_input:04X}",
327                file=f,
328            )
329            print(
330                f"#define PORTBANK_{index}_PINS_PUSH_PULL_OUTPUT 0x{portbank.pins_push_pull_output:04X}",
331                file=f,
332            )
333            print(
334                f"#define PORTBANK_{index}_PINS_ANALOG 0x{portbank.pins_analog:04X}",
335                file=f,
336            )
337
338
339if __name__ == "__main__":
340    main()
341