-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathcard_manager.c
More file actions
421 lines (358 loc) · 11.1 KB
/
Copy pathcard_manager.c
File metadata and controls
421 lines (358 loc) · 11.1 KB
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
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
/*
* Card Manager - I2C board detection and unified initialisation
*
* Detection protocol:
* On a floating or weakly-pulled bus the I²C core can sample SDA low
* during the ACK slot and report ACK for many addresses even with
* nothing connected. Therefore we NEVER rely on a simple ACK-based
* scan for detection decisions.
*
* Instead, detection works by content verification:
* 1. Attempt i2c_write_read() to the primary LPC address (0x40).
* 2. Read 3 bytes from register 0x70 (SOFT_ID / BOARD_ID / HARD_REV).
* 3. board_id must be one of the known values (0x01, 0x02, 0x31)
* AND must not be 0x00 or 0xFF (floating bus artefacts).
* 4. Only if that passes: call the matching driver init().
*
* A separate informational bus scan is still performed for the web UI
* (GET /api/cards), but its results are NEVER used for detection.
*/
#include "card_manager.h"
#include <zephyr/kernel.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/logging/log.h>
#include <string.h>
#include "../drivers/fpga_hal/fpga_hal.h"
#include "card_settings.h"
#ifdef CONFIG_MI_CARD
#include "../drivers/mi_card/mi_card.h"
#endif
#ifdef CONFIG_LO_CARD
#include "../drivers/lo_card/lo_card.h"
#endif
#ifdef CONFIG_IO_CARD
#include "../drivers/io_card/io_card.h"
#endif
LOG_MODULE_REGISTER(card_manager, LOG_LEVEL_INF);
/* LPC register map shared by all card types */
#define LPC_PRIMARY_ADDR 0x40
#define LPC_REG_SOFT_ID 0x70 /* 3 bytes: soft_id, board_id, hard_rev */
/* Board IDs */
#define BOARD_ID_MI 0x01
#define BOARD_ID_LO 0x02
#define BOARD_ID_IO_IN 0x31 /* IO-card input LPC (primary detection point) */
/* I2C scan range */
#define I2C_SCAN_FIRST 0x08
#define I2C_SCAN_LAST 0x77
/* -----------------------------------------------------------------------
* Module state
* --------------------------------------------------------------------- */
static const struct device *s_i2c_dev;
static struct card_info s_cards[CARD_MAX_SLOTS];
static struct card_i2c_scan_result s_scan;
static struct k_mutex s_lock;
static bool s_outputs_allowed; /* PTP lock seen */
/* -----------------------------------------------------------------------
* Helpers
* --------------------------------------------------------------------- */
static int lpc_read_ident(uint8_t addr, struct card_ident *out)
{
uint8_t reg = LPC_REG_SOFT_ID;
uint8_t buf[3];
int ret = i2c_write_read(s_i2c_dev, addr, ®, 1, buf, 3);
if (ret < 0) {
return ret;
}
out->lpc_addr = addr;
out->soft_id = buf[0];
out->board_id = buf[1];
out->hard_rev = buf[2];
return 0;
}
/*
* Informational bus scan for the web UI.
*
* IMPORTANT: results are NEVER used for detection decisions.
*
* When SDA floats (no device, weak pull-up, or bus not driven) the I²C
* core can sample 0 during the ACK slot and report a false ACK. This
* causes large runs of false-positive addresses (typically 0x08–0x1F or
* similar) even with nothing connected.
*
* We therefore perform the scan only to populate the informational list
* shown in the web UI. We additionally verify each hit with a 1-byte
* register read to weed out the worst false positives — but we still
* cannot guarantee accuracy. Detection itself uses content verification
* (see detect_and_init_slot0).
*/
static void do_scan(void)
{
s_scan.count = 0;
/*
* Only probe the specific addresses we actually expect hardware at.
* This avoids the false-ACK storm and keeps scan time short.
*/
static const uint8_t known_addrs[] = {
/* Input LPCs */
0x40, 0x41,
/* Unknown device seen at 0x42 on some boards */
0x42,
/* Output LPC (behind MUX, may not respond directly) */
0x43,
/* ADC chips (CS5368): AD1=0 → 0x4C/0x4D, AD1=1 → 0x4E/0x4F */
0x4C, 0x4D, 0x4E, 0x4F,
/* DAC chips */
0x18, 0x19,
/* MUX variants: PCA9540B/TCA9543A (0x70-0x73), PCA9542 (0x70) */
0x70, 0x71, 0x72, 0x73,
/* Si5351A, SSD1306 (same shared bus as the cards) */
0x60, 0x3C,
/* DSP (AD1941) */
0x14,
};
for (size_t i = 0; i < ARRAY_SIZE(known_addrs); i++) {
uint8_t addr = known_addrs[i];
uint8_t dummy;
if (i2c_read(s_i2c_dev, &dummy, 1, addr) == 0) {
if (s_scan.count < CARD_MGR_MAX_I2C_DEVICES) {
s_scan.addr[s_scan.count++] = addr;
}
LOG_DBG("I2C scan: device at 0x%02x", addr);
}
}
LOG_INF("I2C scan complete: %d device(s) found (probed known addresses only)",
s_scan.count);
}
/* -----------------------------------------------------------------------
* Detection & init for slot 0
* --------------------------------------------------------------------- */
/*
* Valid board_id values. 0x00 and 0xFF are floating-bus artefacts and
* must be rejected even if the i2c_write_read() call returned 0.
*/
static bool board_id_is_known(uint8_t id)
{
return id == BOARD_ID_MI || id == BOARD_ID_LO || id == BOARD_ID_IO_IN;
}
static int detect_and_init_slot0(void)
{
struct card_info *card = &s_cards[CARD_SLOT_MAIN];
card->present = false;
card->initialized = false;
card->type = CARD_TYPE_NONE;
memset(&card->ident, 0, sizeof(card->ident));
/*
* Detection by content verification — NOT by ACK scan.
*
* Attempt to read the identity registers from the primary LPC address.
* We read twice and require consistent results to guard against
* single-bit glitches on a floating bus.
*/
struct card_ident ident_a, ident_b;
if (lpc_read_ident(LPC_PRIMARY_ADDR, &ident_a) < 0) {
LOG_INF("No card detected (no response at 0x%02x)", LPC_PRIMARY_ADDR);
return 0;
}
/* Reject bus-float values */
if (!board_id_is_known(ident_a.board_id)) {
LOG_INF("No card detected (board_id=0x%02x not recognised - "
"floating bus?)", ident_a.board_id);
return 0;
}
/* Second read — must match first */
if (lpc_read_ident(LPC_PRIMARY_ADDR, &ident_b) < 0 ||
ident_b.board_id != ident_a.board_id) {
LOG_WRN("Board ID unstable (0x%02x vs 0x%02x) - bus noise, "
"skipping init", ident_a.board_id, ident_b.board_id);
return 0;
}
card->ident = ident_a;
card->present = true;
LOG_INF("LPC at 0x%02x: soft_id=0x%02x board_id=0x%02x rev=0x%02x",
ident_a.lpc_addr, ident_a.soft_id, ident_a.board_id, ident_a.hard_rev);
int ret = 0;
switch (ident_a.board_id) {
#ifdef CONFIG_MI_CARD
case BOARD_ID_MI:
card->type = CARD_TYPE_MI;
LOG_INF("Detected: MI card (8-ch ADC preamp)");
ret = mi_card_init(s_i2c_dev);
break;
#endif
#ifdef CONFIG_LO_CARD
case BOARD_ID_LO:
card->type = CARD_TYPE_LO;
LOG_INF("Detected: LO card (8-ch DAC output)");
ret = lo_card_init(s_i2c_dev);
break;
#endif
#ifdef CONFIG_IO_CARD
case BOARD_ID_IO_IN:
card->type = CARD_TYPE_IO;
LOG_INF("Detected: IO card (16-ch ADC + 8-ch DAC)");
ret = io_card_init(s_i2c_dev);
break;
#endif
default:
LOG_WRN("Unknown board_id=0x%02x at 0x40 - not initialised",
ident_a.board_id);
return 0;
}
if (ret < 0) {
LOG_ERR("Card init failed: %d", ret);
card->initialized = false;
} else {
card->initialized = true;
LOG_INF("Card initialised: %s", card_type_name(card->type));
/* Restore persisted gain/48V/mute settings. For cards whose
* converters are still gated on the PTP lock (LO) this is a
* no-op here; activate_slot0_locked() re-applies. */
card_settings_apply();
}
return ret;
}
/* Bring the outputs of the detected card up. Caller holds s_lock. */
static int activate_slot0_locked(void)
{
const struct card_info *card = &s_cards[CARD_SLOT_MAIN];
int ret = 0;
if (!card->present) {
return 0;
}
switch (card->type) {
#ifdef CONFIG_LO_CARD
case CARD_TYPE_LO:
ret = lo_card_activate();
if (ret == 0) {
/* Converters are up now — restore the persisted
* clip/mute settings before opening the relays. */
card_settings_apply();
ret = lo_card_enable_outputs(true);
}
break;
#endif
#ifdef CONFIG_IO_CARD
case CARD_TYPE_IO:
ret = io_card_enable_outputs(true);
break;
#endif
default:
/* MI card is input-only — nothing to unmute */
break;
}
return ret;
}
/* -----------------------------------------------------------------------
* Public API
* --------------------------------------------------------------------- */
int card_manager_early_mute(const struct device *i2c_dev)
{
struct card_ident ident;
if (i2c_dev == NULL || !device_is_ready(i2c_dev)) {
return -ENODEV;
}
/* Pre-init boot path: single-threaded, s_lock not initialised yet. */
s_i2c_dev = i2c_dev;
if (lpc_read_ident(LPC_PRIMARY_ADDR, &ident) < 0 ||
!board_id_is_known(ident.board_id)) {
LOG_INF("Early mute: no card LPC answering (yet)");
return 0;
}
switch (ident.board_id) {
#ifdef CONFIG_LO_CARD
case BOARD_ID_LO:
lo_card_early_mute(i2c_dev);
break;
#endif
default:
/* MI is input-only; the IO card's output path is brought up
* muted by io_card_init() itself. */
break;
}
return 0;
}
int card_manager_init(const struct device *i2c_dev)
{
if (i2c_dev == NULL || !device_is_ready(i2c_dev)) {
LOG_ERR("I2C device not ready");
return -ENODEV;
}
k_mutex_init(&s_lock);
s_i2c_dev = i2c_dev;
/* On boards without a shared display/card reset line, nRST has been
* held LOW since early boot (set in main()) — the cards' MCLK is
* derived by the FPGA, so main() only calls this once the FPGA is
* configured and answering (the CS5368 ADCs need a stable MCLK
* before nRST is released or their I2C won't come up). On shared-
* nRST boards the line is already high; this is a no-op there. */
LOG_INF("Card manager: releasing ADDA nRST...");
fpga_hal_set_adda_nrst(true);
k_msleep(200); /* wait for all card ICs to come out of reset before I2C scan */
LOG_INF("Card manager: ADDA nRST released");
return card_manager_rescan();
}
int card_manager_rescan(void)
{
if (s_i2c_dev == NULL) {
return -ENODEV;
}
k_mutex_lock(&s_lock, K_FOREVER);
/*
* Step 1: informational scan of known addresses (for the web UI).
* Results are NOT used for detection — see do_scan() comment.
*/
LOG_INF("Card manager: probing known I2C addresses...");
do_scan();
/*
* Step 2: content-based detection.
* Reads and validates LPC identity registers directly.
*/
int ret = detect_and_init_slot0();
/*
* Step 3: if the outputs were already activated (PTP lock seen),
* restore the active state — this is the runtime-recovery path
* (nRST pulse / card re-seated).
*/
if (ret == 0 && s_outputs_allowed) {
ret = activate_slot0_locked();
}
k_mutex_unlock(&s_lock);
return ret;
}
int card_manager_activate_outputs(void)
{
int ret;
if (s_i2c_dev == NULL) {
return -ENODEV;
}
k_mutex_lock(&s_lock, K_FOREVER);
s_outputs_allowed = true;
ret = activate_slot0_locked();
k_mutex_unlock(&s_lock);
return ret;
}
const struct card_info *card_manager_get_info(int slot)
{
if (slot < 0 || slot >= CARD_MAX_SLOTS) {
return NULL;
}
return &s_cards[slot];
}
void card_manager_get_scan_result(struct card_i2c_scan_result *out)
{
if (out == NULL) {
return;
}
k_mutex_lock(&s_lock, K_FOREVER);
*out = s_scan;
k_mutex_unlock(&s_lock);
}
const char *card_type_name(card_type_t type)
{
switch (type) {
case CARD_TYPE_MI: return "MI (8-ch ADC preamp)";
case CARD_TYPE_LO: return "LO (8-ch DAC output)";
case CARD_TYPE_IO: return "IO (16-ch ADC + 8-ch DAC)";
default: return "none";
}
}