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