-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathdevice.go
370 lines (323 loc) · 9.36 KB
/
device.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
package cyw43439
import (
"context"
"errors"
"runtime"
"sync"
"time"
"log/slog"
"github.com/soypat/cyw43439/whd"
"golang.org/x/exp/constraints"
)
// opMode determines the enabled modes of operation as a bitfield.
// To select multiple modes use OR operation:
//
// mode := ModeWifi | ModeBluetooth
type opMode uint32
const (
modeInit opMode = 1 << iota
modeWifi
modeBluetooth
)
// CYW43439 internal link state enum.
type linkState uint8
const (
linkStateDown = iota
linkStateUpWaitForSSID
linkStateUp
linkStateFailed
linkStateAuthFailed
linkStateWaitForReconnect
)
type outputPin func(bool)
func DefaultBluetoothConfig() Config {
return Config{
Firmware: embassyFWbt,
CLM: embassyFWclm,
mode: modeInit | modeBluetooth,
}
}
func DefaultWifiBluetoothConfig() Config {
return Config{
Firmware: wifibtFW,
CLM: clmFW,
mode: modeInit | modeWifi | modeBluetooth,
}
}
func DefaultWifiConfig() Config {
return Config{
Firmware: wifiFW2,
CLM: clmFW,
mode: modeInit | modeWifi,
}
}
// type OutputPin func(bool)
type Device struct {
mu sync.Mutex
pwr outputPin
lastStatusGet time.Time
spi spibus
log logstate
mode opMode
btaddr uint32
b2hReadPtr uint32
h2bWritePtr uint32
backplaneWindow uint32
ioctlID uint16
sdpcmSeq uint8
sdpcmSeqMax uint8
mac [6]byte
eventmask eventMask
// uint32 buffers to ensure alignment of buffers.
rwBuf [2]uint32 // rwBuf used for read* and write* functions.
_sendIoctlBuf [2048 / 4]uint32 // _sendIoctlBuf used only in sendIoctl and tx.
_iovarBuf [2048 / 4]uint32 // _iovarBuf used in get_iovar*, set_iovar* and write_backplane calls.
_rxBuf [2048 / 4]uint32 // Used in check_status->rx calls and handle_irq.
// We define headers in the Device struct to alleviate stack growth. Also used along with _sendIoctlBuf
lastSDPCMHeader whd.SDPCMHeader
auxCDCHeader whd.CDCHeader
auxBDCHeader whd.BDCHeader
rcvEth func([]byte) error
rcvHCI func([]byte) error
logger *slog.Logger
_traceenabled bool
state linkState
}
type Config struct {
Firmware string
CLM string
Logger *slog.Logger
// mode selects the enabled operation modes of the CYW43439.
mode opMode
}
func (d *Device) Init(cfg Config) (err error) {
if cfg.mode&(modeBluetooth|modeWifi) == 0 {
return errors.New("no operation mode selected")
}
err = d.acquire(0)
defer d.release()
if err != nil {
return err
}
d.info("Init:start")
start := time.Now()
// Reference: https://github.com/embassy-rs/embassy/blob/6babd5752e439b234151104d8d20bae32e41d714/cyw43/src/runner.rs#L76
d.logger = cfg.Logger
d._traceenabled = d.logger != nil && d.logger.Handler().Enabled(context.Background(), levelTrace)
d.backplaneWindow = 0xaaaa_aaaa
err = d.initBus(cfg.mode)
if err != nil {
return errjoin(errors.New("failed to init bus"), err)
}
d.debug("Init:alp")
d.write8(FuncBackplane, whd.SDIO_CHIP_CLOCK_CSR, whd.SBSDIO_ALP_AVAIL_REQ)
// Check if we can set the bluetooth watermark during ALP.
if d.bt_mode_enabled() {
d.debug("Init:bt-watermark")
d.write8(FuncBackplane, whd.REG_BACKPLANE_FUNCTION2_WATERMARK, 0x10)
watermark, _ := d.read8(FuncBackplane, whd.REG_BACKPLANE_FUNCTION2_WATERMARK)
if watermark != 0x10 {
return errBTWatermark
}
}
for {
got, _ := d.read8(FuncBackplane, whd.SDIO_CHIP_CLOCK_CSR)
if got&whd.SBSDIO_ALP_AVAIL != 0 {
break // ALP available-> clock OK.
}
time.Sleep(time.Millisecond)
}
// Clear request for ALP.
d.write8(FuncBackplane, whd.SDIO_CHIP_CLOCK_CSR, 0)
chip_id, _ := d.bp_read16(0x1800_0000)
// Upload firmware.
err = d.core_disable(whd.CORE_WLAN_ARM)
if err != nil {
return err
}
err = d.core_disable(whd.CORE_SOCSRAM) // TODO:is this needed if we reset right after?
if err != nil {
return err
}
err = d.core_reset(whd.CORE_SOCSRAM, false)
if err != nil {
return err
}
// this is 4343x specific stuff: Disable remap for SRAM_3
d.bp_write32(whd.SOCSRAM_BASE_ADDRESS+0x10, 3)
d.bp_write32(whd.SOCSRAM_BASE_ADDRESS+0x44, 0)
d.debug("flashing firmware", slog.Uint64("chip_id", uint64(chip_id)), slog.Int("fwlen", len(cfg.Firmware)))
var ramAddr uint32 // Start at ATCM_RAM_BASE_ADDRESS = 0.
err = d.bp_writestring(ramAddr, cfg.Firmware)
if err != nil {
return err
}
// Load NVRAM
const chipRAMSize = 512 * 1024
nvramLen := alignup(uint32(len(nvram43439)), 4)
d.debug("flashing nvram")
err = d.bp_writestring(ramAddr+chipRAMSize-4-nvramLen, nvram43439)
if err != nil {
return err
}
nvramLenWords := nvramLen / 4
nvramLenMagic := ((^nvramLenWords) << 16) | nvramLenWords
d.bp_write32(ramAddr+chipRAMSize-4, nvramLenMagic)
// Start core.
d.debug("Init:start-core")
err = d.core_reset(whd.CORE_WLAN_ARM, false)
if err != nil {
return err
}
if !d.core_is_up(whd.CORE_WLAN_ARM) {
return errors.New("core not up after reset")
}
d.debug("core up")
// Wait until HT clock is available, takes about 29ms.
deadline := time.Now().Add(100 * time.Millisecond)
for {
got, _ := d.read8(FuncBackplane, whd.SDIO_CHIP_CLOCK_CSR)
if got&0x80 != 0 {
break
}
if time.Since(deadline) >= 0 {
return errors.New("timeout waiting for chip clock")
}
time.Sleep(time.Millisecond)
}
// "Set up the interrupt mask and enable interrupts"
d.debug("Init:intr-mask")
d.bp_write32(whd.SDIO_BASE_ADDRESS+whd.SDIO_INT_HOST_MASK, whd.I_HMB_SW_MASK)
if d.bt_mode_enabled() {
d.bp_write32(whd.SDIO_BASE_ADDRESS+whd.SDIO_INT_HOST_MASK, whd.I_HMB_FC_CHANGE)
}
d.write16(FuncBus, whd.SPI_INTERRUPT_ENABLE_REGISTER, whd.F2_PACKET_AVAILABLE)
// ""Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped.""
// "Sounds scary..."
// yea it does
const REG_BACKPLANE_FUNCTION2_WATERMARK = 0x10008
d.write8(FuncBackplane, REG_BACKPLANE_FUNCTION2_WATERMARK, whd.SPI_F2_WATERMARK)
// Wait for F2 to be ready
deadline = time.Now().Add(100 * time.Millisecond)
for !d.status().F2RxReady() {
if time.Since(deadline) >= 0 {
return errors.New("wifi startup timeout")
}
time.Sleep(time.Millisecond)
}
// Clear pulls.
d.write8(FuncBackplane, whd.SDIO_PULL_UP, 0)
d.read8(FuncBackplane, whd.SDIO_PULL_UP)
// Start HT clock.
d.write8(FuncBackplane, whd.SDIO_CHIP_CLOCK_CSR, whd.SBSDIO_HT_AVAIL_REQ)
deadline = time.Now().Add(64 * time.Millisecond)
for {
got, err := d.read8(FuncBackplane, whd.SDIO_CHIP_CLOCK_CSR)
if err != nil {
return err
}
if got&0x80 != 0 {
break
} else if time.Since(deadline) > 0 {
return errors.New("ht clock timeout")
}
time.Sleep(time.Millisecond)
}
err = d.log_init()
if err != nil {
return err
}
d.log_read()
d.debug("base init done")
if cfg.CLM == "" {
return nil
}
// Starting polling to simulate hw interrupts
// go d.irqPoll()
err = d.initControl(cfg.CLM)
if err != nil {
return err
}
err = d.set_power_management(pmPowerSave)
d.state = linkStateDown
d.info("Init:done", slog.Duration("took", time.Since(start)))
return err
}
func (d *Device) GPIOSet(wlGPIO uint8, value bool) (err error) {
d.info("GPIOSet", slog.Uint64("wlGPIO", uint64(wlGPIO)), slog.Bool("value", value))
if wlGPIO >= 3 {
return errors.New("gpio out of range")
}
val0 := uint32(1) << wlGPIO
val1 := b2u32(value) << wlGPIO
err = d.acquire(modeInit)
defer d.release()
if err != nil {
return err
}
return d.set_iovar2("gpioout", whd.IF_STA, val0, val1)
}
// status gets gSPI last bus status or reads it from the device if it's stale, for some definition of stale.
func (d *Device) status() Status {
// TODO(soypat): Are we sure we don't want to re-acquire status if it's been very long?
sinceStat := time.Since(d.lastStatusGet)
if sinceStat < 10*time.Microsecond {
runtime.Gosched() // Probably in hot loop.
} else {
d.lastStatusGet = time.Now()
got, _ := d.read32(FuncBus, whd.SPI_STATUS_REGISTER) // Explicitly get Status.
return Status(got)
}
return d.spi.Status()
}
// Reset power-cycles the CYW43439 by turning WLREGON off and on
// and waiting the suggested amount of time for SPI bus to initialize.
// To use Device again Init should be called after a Reset.
func (d *Device) Reset() {
d.acquire(0)
d.reset()
d.release()
}
func (d *Device) reset() {
d.pwr(false)
time.Sleep(20 * time.Millisecond)
d.pwr(true)
time.Sleep(250 * time.Millisecond) // Wait for bus to initialize.
d.mode = 0
d.backplaneWindow = 0
d.state = 0
d.ioctlID = 0
d.sdpcmSeq = 0
d.sdpcmSeqMax = 1
}
func (d *Device) getInterrupts() Interrupts {
irq, err := d.read16(FuncBus, whd.SPI_INTERRUPT_REGISTER)
if err != nil {
return 0
}
return Interrupts(irq)
}
func (d *Device) acquire(mode opMode) error {
d.mu.Lock()
if mode != 0 && d.mode == 0 {
return errors.New("device uninitialized")
} else if mode&d.mode != mode {
return errors.New("device mode uninitialized")
}
return nil
}
func (d *Device) release() {
d.mu.Unlock()
}
// alignup rounds `val` up to nearest multiple of `alignup`. `alignup` must be a power of 2.
func alignup[T constraints.Unsigned](val, align T) T {
return (val + align - 1) &^ (align - 1)
}
// align rounds `val` down to nearest multiple of `align`. `align` must be a power of 2.
func aligndown[T constraints.Unsigned](val, align T) T {
return val &^ (align - 1)
}
// isaligned checks if `val` is wholly divisible by `align`. `align` must be a power of 2.
func isaligned[T constraints.Unsigned](val, align T) bool {
return val&(align-1) == 0
}