Skip to content

Commit 8a03b08

Browse files
jgangemideadprogram
authored andcommitted
feat: add ESP32-C3 USB-JTAG PostConnect with RTC_CNTL disable
- detect USB-JTAG/Serial via ROM .bss (revision-dependent address) - read efuse to determine chip revision for correct UARTDEV address - disable RTC WDT and enable SWD auto-feed when USB-JTAG active
1 parent 736aa48 commit 8a03b08

File tree

2 files changed

+326
-0
lines changed

2 files changed

+326
-0
lines changed

pkg/espflasher/target_esp32c3.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
package espflasher
22

3+
import "fmt"
4+
5+
// ESP32-C3 register addresses for USB interface detection and watchdog control.
6+
// Reference: esptool/targets/esp32c3.py
7+
const (
8+
// UARTDEV_BUF_NO address depends on chip revision (read from efuse).
9+
// Revision < 1 (ECO0-ECO3): base 0x3FCDF064
10+
// Revision >= 1 (ECO4+): base 0x3FCDF060
11+
// The actual register is 24 bytes (+0x18) from the base address.
12+
esp32c3UARTDevBufNoRev0 uint32 = 0x3FCDF07C // 0x3FCDF064 + 24
13+
esp32c3UARTDevBufNoRev101 uint32 = 0x3FCDF078 // 0x3FCDF060 + 24
14+
esp32c3UARTDevBufNoUSBJTAGSerial uint32 = 3 // USB-JTAG/Serial active
15+
16+
// Efuse register for chip version detection.
17+
// Major chip version is in bits 24:22; minor in bits 21:20.
18+
esp32c3EfuseRdMacSpiSys1 uint32 = 0x60008844
19+
20+
// RTC_CNTL watchdog registers (different offsets from S3).
21+
esp32c3RTCCntlWDTConfig0 uint32 = 0x60008090
22+
esp32c3RTCCntlWDTWProtect uint32 = 0x600080A8
23+
esp32c3RTCCntlWDTWKey uint32 = 0x50D83AA1
24+
25+
// Super Watchdog (SWD) registers.
26+
esp32c3RTCCntlSWDConf uint32 = 0x600080AC
27+
esp32c3RTCCntlSWDAutoFeedEn uint32 = 1 << 31
28+
esp32c3RTCCntlSWDWProtect uint32 = 0x600080B0
29+
esp32c3RTCCntlSWDWKey uint32 = 0x8F1D312A
30+
)
31+
332
// ESP32-C3 target definition.
433
// Reference: https://github.com/espressif/esptool/blob/master/esptool/targets/esp32c3.py
534

@@ -39,4 +68,93 @@ var defESP32C3 = &chipDef{
3968
},
4069

4170
FlashSizes: defaultFlashSizes(),
71+
72+
PostConnect: esp32c3PostConnect,
73+
}
74+
75+
// esp32c3ChipRevision reads the chip revision from efuse.
76+
// Returns major*100 + minor (e.g., 101 = major 1, minor 0, sub 1).
77+
// Chip revision is encoded in EFUSE_RD_MAC_SPI_SYS_1_REG:
78+
// - Major version: bits 24:22
79+
// - Minor version: bits 21:20
80+
func esp32c3ChipRevision(f *Flasher) (uint32, error) {
81+
val, err := f.ReadRegister(esp32c3EfuseRdMacSpiSys1)
82+
if err != nil {
83+
return 0, err
84+
}
85+
major := (val >> 22) & 0x7
86+
minor := (val >> 20) & 0x3
87+
return major*100 + minor, nil
88+
}
89+
90+
// esp32c3UARTDevAddr returns the correct UARTDEV_BUF_NO address based on chip revision.
91+
func esp32c3UARTDevAddr(f *Flasher) uint32 {
92+
rev, err := esp32c3ChipRevision(f)
93+
if err != nil {
94+
// Default to older revision if efuse read fails
95+
return esp32c3UARTDevBufNoRev0
96+
}
97+
if rev >= 101 {
98+
return esp32c3UARTDevBufNoRev101
99+
}
100+
return esp32c3UARTDevBufNoRev0
101+
}
102+
103+
// esp32c3PostConnect detects the USB interface type and disables watchdogs
104+
// when connected via USB-JTAG/Serial. Without this, the RTC WDT fires
105+
// during flash and resets the chip mid-operation.
106+
// Reference: esptool/targets/esp32c3.py _post_connect()
107+
func esp32c3PostConnect(f *Flasher) error {
108+
addr := esp32c3UARTDevAddr(f)
109+
val, err := f.ReadRegister(addr)
110+
if err != nil {
111+
// In secure download mode, the register may be unreadable.
112+
// Default to non-USB behavior (safe fallback).
113+
return nil
114+
}
115+
116+
if val == esp32c3UARTDevBufNoUSBJTAGSerial {
117+
f.usesUSB = true
118+
f.logf("USB-JTAG/Serial interface detected, disabling watchdogs")
119+
if err := disableWatchdogsESP32C3(f); err != nil {
120+
return err
121+
}
122+
}
123+
124+
return nil
125+
}
126+
127+
// disableWatchdogsESP32C3 disables the RTC WDT and enables SWD auto-feed.
128+
// This prevents the watchdog from resetting the chip during flash operations
129+
// when connected via USB-JTAG/Serial.
130+
func disableWatchdogsESP32C3(f *Flasher) error {
131+
// Unlock and disable RTC WDT
132+
if err := f.WriteRegister(esp32c3RTCCntlWDTWProtect, esp32c3RTCCntlWDTWKey); err != nil {
133+
return fmt.Errorf("unlock RTC WDT: %w", err)
134+
}
135+
if err := f.WriteRegister(esp32c3RTCCntlWDTConfig0, 0); err != nil {
136+
return fmt.Errorf("disable RTC WDT: %w", err)
137+
}
138+
if err := f.WriteRegister(esp32c3RTCCntlWDTWProtect, 0); err != nil {
139+
return fmt.Errorf("lock RTC WDT: %w", err)
140+
}
141+
142+
// Unlock SWD and enable auto-feed
143+
if err := f.WriteRegister(esp32c3RTCCntlSWDWProtect, esp32c3RTCCntlSWDWKey); err != nil {
144+
return fmt.Errorf("unlock SWD: %w", err)
145+
}
146+
147+
swdConf, err := f.ReadRegister(esp32c3RTCCntlSWDConf)
148+
if err != nil {
149+
return fmt.Errorf("read SWD conf: %w", err)
150+
}
151+
swdConf |= esp32c3RTCCntlSWDAutoFeedEn
152+
if err := f.WriteRegister(esp32c3RTCCntlSWDConf, swdConf); err != nil {
153+
return fmt.Errorf("enable SWD auto-feed: %w", err)
154+
}
155+
if err := f.WriteRegister(esp32c3RTCCntlSWDWProtect, 0); err != nil {
156+
return fmt.Errorf("lock SWD: %w", err)
157+
}
158+
159+
return nil
42160
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package espflasher
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestESP32C3ChipRevisionRev0(t *testing.T) {
12+
// Efuse value with major=0, minor=0
13+
mc := &mockConnection{
14+
readRegFunc: func(addr uint32) (uint32, error) {
15+
if addr == esp32c3EfuseRdMacSpiSys1 {
16+
return 0x00000000, nil
17+
}
18+
return 0, nil
19+
},
20+
}
21+
f := &Flasher{
22+
conn: mc,
23+
opts: &FlasherOptions{},
24+
}
25+
26+
rev, err := esp32c3ChipRevision(f)
27+
require.NoError(t, err)
28+
assert.Equal(t, uint32(0), rev, "revision should be 0 for major=0, minor=0")
29+
}
30+
31+
func TestESP32C3ChipRevisionRev101(t *testing.T) {
32+
// Efuse value with major=1, minor=1
33+
// major=1: bits 24:22 = (1 << 22) = 0x00400000
34+
// minor=1: bits 21:20 = (1 << 20) = 0x00100000
35+
// Combined: 0x00500000
36+
mc := &mockConnection{
37+
readRegFunc: func(addr uint32) (uint32, error) {
38+
if addr == esp32c3EfuseRdMacSpiSys1 {
39+
// major=1: (1 << 22) = 0x00400000
40+
// minor=1: (1 << 20) = 0x00100000
41+
// combined: 0x00500000
42+
return 0x00500000, nil
43+
}
44+
return 0, nil
45+
},
46+
}
47+
f := &Flasher{
48+
conn: mc,
49+
opts: &FlasherOptions{},
50+
}
51+
52+
rev, err := esp32c3ChipRevision(f)
53+
require.NoError(t, err)
54+
assert.Equal(t, uint32(101), rev, "revision should be 101 for major=1, minor=1")
55+
}
56+
57+
func TestESP32C3UARTDevAddrRev0(t *testing.T) {
58+
// Return major=0, minor=0 from efuse
59+
mc := &mockConnection{
60+
readRegFunc: func(addr uint32) (uint32, error) {
61+
return 0x00000000, nil
62+
},
63+
}
64+
f := &Flasher{
65+
conn: mc,
66+
opts: &FlasherOptions{},
67+
}
68+
69+
addr := esp32c3UARTDevAddr(f)
70+
assert.Equal(t, esp32c3UARTDevBufNoRev0, addr, "should use Rev0 address")
71+
}
72+
73+
func TestESP32C3UARTDevAddrRev101(t *testing.T) {
74+
// Return major=1, minor=1 from efuse (revision 101)
75+
mc := &mockConnection{
76+
readRegFunc: func(addr uint32) (uint32, error) {
77+
if addr == esp32c3EfuseRdMacSpiSys1 {
78+
// major=1: (1 << 22) = 0x00400000
79+
// minor=1: (1 << 20) = 0x00100000
80+
// combined: 0x00500000
81+
return 0x00500000, nil
82+
}
83+
return 0, nil
84+
},
85+
}
86+
f := &Flasher{
87+
conn: mc,
88+
opts: &FlasherOptions{},
89+
}
90+
91+
addr := esp32c3UARTDevAddr(f)
92+
assert.Equal(t, esp32c3UARTDevBufNoRev101, addr, "should use Rev101 address")
93+
}
94+
95+
func TestESP32C3UARTDevAddrReadError(t *testing.T) {
96+
// Efuse read fails, should default to Rev0
97+
mc := &mockConnection{
98+
readRegFunc: func(addr uint32) (uint32, error) {
99+
return 0, errors.New("efuse read error")
100+
},
101+
}
102+
f := &Flasher{
103+
conn: mc,
104+
opts: &FlasherOptions{},
105+
}
106+
107+
addr := esp32c3UARTDevAddr(f)
108+
assert.Equal(t, esp32c3UARTDevBufNoRev0, addr, "should default to Rev0 on read error")
109+
}
110+
111+
func TestESP32C3PostConnectUSBJTAGRev0(t *testing.T) {
112+
writeCount := 0
113+
readCount := 0
114+
115+
mc := &mockConnection{
116+
readRegFunc: func(addr uint32) (uint32, error) {
117+
readCount++
118+
if addr == esp32c3EfuseRdMacSpiSys1 {
119+
return 0x00000000, nil // Major=0, Minor=0 -> Rev0
120+
}
121+
if addr == esp32c3UARTDevBufNoRev0 {
122+
return esp32c3UARTDevBufNoUSBJTAGSerial, nil
123+
}
124+
// Return 0 for SWD conf read
125+
return 0, nil
126+
},
127+
writeRegFunc: func(addr, value, mask, delayUS uint32) error {
128+
writeCount++
129+
return nil
130+
},
131+
}
132+
f := &Flasher{
133+
conn: mc,
134+
opts: &FlasherOptions{},
135+
}
136+
137+
err := esp32c3PostConnect(f)
138+
require.NoError(t, err)
139+
assert.True(t, f.usesUSB, "usesUSB should be true for USB-JTAG/Serial")
140+
assert.Greater(t, writeCount, 0, "should have written registers to disable watchdog")
141+
}
142+
143+
func TestESP32C3PostConnectUSBJTAGRev101(t *testing.T) {
144+
writeCount := 0
145+
146+
mc := &mockConnection{
147+
readRegFunc: func(addr uint32) (uint32, error) {
148+
if addr == esp32c3EfuseRdMacSpiSys1 {
149+
// major=1, minor=1: (1 << 22) | (1 << 20) = 0x00500000
150+
return 0x00500000, nil // Major=1, Minor=1 -> Rev101
151+
}
152+
if addr == esp32c3UARTDevBufNoRev101 {
153+
return esp32c3UARTDevBufNoUSBJTAGSerial, nil
154+
}
155+
// Return 0 for SWD conf read
156+
return 0, nil
157+
},
158+
writeRegFunc: func(addr, value, mask, delayUS uint32) error {
159+
writeCount++
160+
return nil
161+
},
162+
}
163+
f := &Flasher{
164+
conn: mc,
165+
opts: &FlasherOptions{},
166+
}
167+
168+
err := esp32c3PostConnect(f)
169+
require.NoError(t, err)
170+
assert.True(t, f.usesUSB, "usesUSB should be true for USB-JTAG/Serial")
171+
assert.Greater(t, writeCount, 0, "should have written registers to disable watchdog")
172+
}
173+
174+
func TestESP32C3PostConnectUART(t *testing.T) {
175+
mc := &mockConnection{
176+
readRegFunc: func(addr uint32) (uint32, error) {
177+
if addr == esp32c3EfuseRdMacSpiSys1 {
178+
return 0x00000000, nil
179+
}
180+
return 0, nil // Not USB, return UART value
181+
},
182+
}
183+
f := &Flasher{
184+
conn: mc,
185+
opts: &FlasherOptions{},
186+
}
187+
188+
err := esp32c3PostConnect(f)
189+
require.NoError(t, err)
190+
assert.False(t, f.usesUSB, "usesUSB should be false for UART")
191+
}
192+
193+
func TestESP32C3PostConnectSecureMode(t *testing.T) {
194+
// Simulate read error (secure download mode)
195+
mc := &mockConnection{
196+
readRegFunc: func(addr uint32) (uint32, error) {
197+
return 0, errors.New("register not readable") // Simulate unreadable register
198+
},
199+
}
200+
f := &Flasher{
201+
conn: mc,
202+
opts: &FlasherOptions{},
203+
}
204+
205+
err := esp32c3PostConnect(f)
206+
require.NoError(t, err, "should gracefully handle read error")
207+
assert.False(t, f.usesUSB, "should default to non-USB on read error")
208+
}

0 commit comments

Comments
 (0)