1# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton,
2# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
3#
4# SPDX-License-Identifier: GPL-2.0-or-later
5
6import os
7import struct
8import time
9
10from .util import FatalError
11
12# Used for resetting into bootloader on Unix-like systems
13if os.name != "nt":
14    import fcntl
15    import termios
16
17    # Constants used for terminal status lines reading/setting.
18    # Taken from pySerial's backend for IO:
19    # https://github.com/pyserial/pyserial/blob/master/serial/serialposix.py
20    TIOCMSET = getattr(termios, "TIOCMSET", 0x5418)
21    TIOCMGET = getattr(termios, "TIOCMGET", 0x5415)
22    TIOCM_DTR = getattr(termios, "TIOCM_DTR", 0x002)
23    TIOCM_RTS = getattr(termios, "TIOCM_RTS", 0x004)
24
25DEFAULT_RESET_DELAY = 0.05  # default time to wait before releasing boot pin after reset
26
27
28class ResetStrategy(object):
29    def __init__(self, port, reset_delay=DEFAULT_RESET_DELAY):
30        self.port = port
31        self.reset_delay = reset_delay
32
33    def __call__():
34        pass
35
36    def _setDTR(self, state):
37        self.port.setDTR(state)
38
39    def _setRTS(self, state):
40        self.port.setRTS(state)
41        # Work-around for adapters on Windows using the usbser.sys driver:
42        # generate a dummy change to DTR so that the set-control-line-state
43        # request is sent with the updated RTS state and the same DTR state
44        self.port.setDTR(self.port.dtr)
45
46    def _setDTRandRTS(self, dtr=False, rts=False):
47        status = struct.unpack(
48            "I", fcntl.ioctl(self.port.fileno(), TIOCMGET, struct.pack("I", 0))
49        )[0]
50        if dtr:
51            status |= TIOCM_DTR
52        else:
53            status &= ~TIOCM_DTR
54        if rts:
55            status |= TIOCM_RTS
56        else:
57            status &= ~TIOCM_RTS
58        fcntl.ioctl(self.port.fileno(), TIOCMSET, struct.pack("I", status))
59
60
61class ClassicReset(ResetStrategy):
62    """
63    Classic reset sequence, sets DTR and RTS lines sequentially.
64    """
65
66    def __call__(self):
67        self._setDTR(False)  # IO0=HIGH
68        self._setRTS(True)  # EN=LOW, chip in reset
69        time.sleep(0.1)
70        self._setDTR(True)  # IO0=LOW
71        self._setRTS(False)  # EN=HIGH, chip out of reset
72        time.sleep(self.reset_delay)
73        self._setDTR(False)  # IO0=HIGH, done
74
75
76class UnixTightReset(ResetStrategy):
77    """
78    UNIX-only reset sequence with custom implementation,
79    which allows setting DTR and RTS lines at the same time.
80    """
81
82    def __call__(self):
83        self._setDTRandRTS(False, False)
84        self._setDTRandRTS(True, True)
85        self._setDTRandRTS(False, True)  # IO0=HIGH & EN=LOW, chip in reset
86        time.sleep(0.1)
87        self._setDTRandRTS(True, False)  # IO0=LOW & EN=HIGH, chip out of reset
88        time.sleep(self.reset_delay)
89        self._setDTRandRTS(False, False)  # IO0=HIGH, done
90        self._setDTR(False)  # Needed in some environments to ensure IO0=HIGH
91
92
93class USBJTAGSerialReset(ResetStrategy):
94    """
95    Custom reset sequence, which is required when the device
96    is connecting via its USB-JTAG-Serial peripheral.
97    """
98
99    def __call__(self):
100        self._setRTS(False)
101        self._setDTR(False)  # Idle
102        time.sleep(0.1)
103        self._setDTR(True)  # Set IO0
104        self._setRTS(False)
105        time.sleep(0.1)
106        self._setRTS(True)  # Reset. Calls inverted to go through (1,1) instead of (0,0)
107        self._setDTR(False)
108        self._setRTS(True)  # RTS set as Windows only propagates DTR on RTS setting
109        time.sleep(0.1)
110        self._setDTR(False)
111        self._setRTS(False)  # Chip out of reset
112
113
114class HardReset(ResetStrategy):
115    """
116    Reset sequence for hard resetting the chip.
117    Can be used to reset out of the bootloader or to restart a running app.
118    """
119
120    def __init__(self, port, uses_usb_otg=False):
121        super().__init__(port)
122        self.uses_usb_otg = uses_usb_otg
123
124    def __call__(self):
125        self._setRTS(True)  # EN->LOW
126        if self.uses_usb_otg:
127            # Give the chip some time to come out of reset,
128            # to be able to handle further DTR/RTS transitions
129            time.sleep(0.2)
130            self._setRTS(False)
131            time.sleep(0.2)
132        else:
133            time.sleep(0.1)
134            self._setRTS(False)
135
136
137class CustomReset(ResetStrategy):
138    """
139    Custom reset strategy defined with a string.
140
141    CustomReset object is created as "rst = CustomReset(port, seq_str)"
142    and can be later executed simply with "rst()"
143
144    The seq_str input string consists of individual commands divided by "|".
145    Commands (e.g. R0) are defined by a code (R) and an argument (0).
146
147    The commands are:
148    D: setDTR - 1=True / 0=False
149    R: setRTS - 1=True / 0=False
150    U: setDTRandRTS (Unix-only) - 0,0 / 0,1 / 1,0 / or 1,1
151    W: Wait (time delay) - positive float number
152
153    e.g.
154    "D0|R1|W0.1|D1|R0|W0.05|D0" represents the ClassicReset strategy
155    "U1,1|U0,1|W0.1|U1,0|W0.05|U0,0" represents the UnixTightReset strategy
156    """
157
158    format_dict = {
159        "D": "self.port.setDTR({})",
160        "R": "self.port.setRTS({})",
161        "W": "time.sleep({})",
162        "U": "self._setDTRandRTS({})",
163    }
164
165    def __call__(self):
166        exec(self.constructed_strategy)
167
168    def __init__(self, port, seq_str):
169        super().__init__(port)
170        self.constructed_strategy = self._parse_string_to_seq(seq_str)
171
172    def _parse_string_to_seq(self, seq_str):
173        try:
174            cmds = seq_str.split("|")
175            fn_calls_list = [self.format_dict[cmd[0]].format(cmd[1:]) for cmd in cmds]
176        except Exception as e:
177            raise FatalError(f'Invalid "custom_reset_sequence" option format: {e}')
178        return "\n".join(fn_calls_list)
179