Fix BCM4500 I2C address and add hardware diagnostic commands

Correct BCM4500 I2C address from 0x10 (8-bit wire) to 0x08 (7-bit)
since fx2lib shifts internally. Add i2c_combined_read() with repeated
START for proper BCM4500 register access. Add I2C bus scan (0xB4),
raw read (0xB5), and indirect protocol diagnostic (0xB6) commands.
Single-transaction indirect reads/writes for BCM4500 register protocol.

Verified on hardware: BCM4500 ACKs at 0x08, BOOT_8PSK returns config
0x03. Register reads still return zeros — BCM4500 needs DSP firmware
loaded via LOAD_BCM4500 (0x88) before registers become functional.
This commit is contained in:
Ryan Malloy 2026-02-12 07:17:47 -07:00
parent 5710584267
commit 890a38bfa0

View File

@ -18,8 +18,8 @@
#define SYNCDELAY SYNCDELAY4 #define SYNCDELAY SYNCDELAY4
/* BCM4500 I2C address (7-bit) */ /* BCM4500 I2C address (7-bit); 8-bit wire address is 0x10/0x11 */
#define BCM4500_ADDR 0x10 #define BCM4500_ADDR 0x08
/* BCM4500 indirect register protocol registers */ /* BCM4500 indirect register protocol registers */
#define BCM_REG_PAGE 0xA6 #define BCM_REG_PAGE 0xA6
@ -81,6 +81,68 @@ static __xdata BYTE i2c_rd[8];
/* ---------- BCM4500 I2C helpers ---------- */ /* ---------- BCM4500 I2C helpers ---------- */
/*
* Combined I2C write-read with repeated START (no STOP between
* write and read phases). Many I2C devices including the BCM4500
* require this pattern instead of separate write+stop/read+stop.
*
* Sequence: START addr+W reg RESTART addr+R data STOP
*/
static BOOL i2c_combined_read(BYTE addr, BYTE reg, BYTE len, BYTE *buf) {
BYTE i;
BYTE tmp;
/* START + write address */
I2CS |= bmSTART;
I2DAT = addr << 1;
while (!(I2CS & bmDONE))
;
if (!(I2CS & bmACK))
goto fail;
/* Write register address */
I2DAT = reg;
while (!(I2CS & bmDONE))
;
if (!(I2CS & bmACK))
goto fail;
/* REPEATED START + read address */
I2CS |= bmSTART;
I2DAT = (addr << 1) | 1;
while (!(I2CS & bmDONE))
;
if (!(I2CS & bmACK))
goto fail;
/* For single byte, set LASTRD before dummy read */
if (len == 1)
I2CS |= bmLASTRD;
/* Dummy read to trigger first clock burst */
tmp = I2DAT;
for (i = 0; i < len; i++) {
while (!(I2CS & bmDONE))
;
if (i == len - 2)
I2CS |= bmLASTRD;
if (i == len - 1)
I2CS |= bmSTOP;
buf[i] = I2DAT;
}
while (I2CS & bmSTOP)
;
return TRUE;
fail:
I2CS |= bmSTOP;
while (I2CS & bmSTOP)
;
return FALSE;
}
/* /*
* Write one byte to a BCM4500 direct I2C register (subaddr). * Write one byte to a BCM4500 direct I2C register (subaddr).
* This writes to the I2C register directly, not through the * This writes to the I2C register directly, not through the
@ -92,49 +154,42 @@ static BOOL bcm_direct_write(BYTE reg, BYTE val) {
} }
/* /*
* Read one byte from a BCM4500 direct I2C register. * Read one byte from a BCM4500 direct I2C register using
* combined write-read with repeated START.
*/ */
static BOOL bcm_direct_read(BYTE reg, BYTE *val) { static BOOL bcm_direct_read(BYTE reg, BYTE *val) {
BYTE r = reg; return i2c_combined_read(BCM4500_ADDR, reg, 1, val);
if (!i2c_write(BCM4500_ADDR, 1, &r, 0, NULL))
return FALSE;
if (!i2c_read(BCM4500_ADDR, 1, val))
return FALSE;
return TRUE;
} }
/* /*
* Write a value to a BCM4500 indirect register. * Write a value to a BCM4500 indirect register.
* Protocol: * Single multi-byte I2C write to 0xA6 with auto-increment:
* 1. Write page/register to 0xA6 * [0xA6] = page, [0xA7] = data, [0xA8] = 0x03 (write cmd)
* 2. Write data to 0xA7
* 3. Write 0x03 to 0xA8 (execute write)
*/ */
static BOOL bcm_indirect_write(BYTE reg, BYTE val) { static BOOL bcm_indirect_write(BYTE reg, BYTE val) {
if (!bcm_direct_write(BCM_REG_PAGE, reg)) BYTE start_reg = BCM_REG_PAGE;
return FALSE; i2c_rd[0] = reg;
if (!bcm_direct_write(BCM_REG_DATA, val)) i2c_rd[1] = val;
return FALSE; i2c_rd[2] = BCM_CMD_WRITE;
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE)) return i2c_write(BCM4500_ADDR, 1, &start_reg, 3, i2c_rd);
return FALSE;
return TRUE;
} }
/* /*
* Read a value from a BCM4500 indirect register. * Read a value from a BCM4500 indirect register.
* Protocol: * Protocol: single multi-byte I2C write to 0xA6 with auto-increment:
* 1. Write page/register to 0xA6 * [0xA6] = page, [0xA7] = 0x00, [0xA8] = 0x01 (read cmd)
* 2. Write 0x01 to 0xA8 (execute read) * Then read the result from 0xA7.
* 3. Read data from 0xA7
*/ */
static BOOL bcm_indirect_read(BYTE reg, BYTE *val) { static BOOL bcm_indirect_read(BYTE reg, BYTE *val) {
if (!bcm_direct_write(BCM_REG_PAGE, reg)) BYTE start_reg = BCM_REG_PAGE;
/* page, placeholder data, read command — written to A6,A7,A8 in one shot */
i2c_rd[0] = reg;
i2c_rd[1] = 0x00;
i2c_rd[2] = BCM_CMD_READ;
if (!i2c_write(BCM4500_ADDR, 1, &start_reg, 3, i2c_rd))
return FALSE; return FALSE;
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_READ)) delay(1);
return FALSE; return i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, val);
if (!bcm_direct_read(BCM_REG_DATA, val))
return FALSE;
return TRUE;
} }
/* /*
@ -735,6 +790,113 @@ BOOL handle_vendorcommand(BYTE cmd) {
do_blind_scan(); do_blind_scan();
return TRUE; return TRUE;
/* 0xB4: I2C_BUS_SCAN -- probe all 7-bit addresses, return 16-byte bitmap */
case 0xB4: {
BYTE a, byte_idx, bit;
/* 128 addresses / 8 = 16 bytes bitmap */
for (byte_idx = 0; byte_idx < 16; byte_idx++)
EP0BUF[byte_idx] = 0;
for (a = 1; a < 0x78; a++) {
/* Try START + address + write, see if ACK comes back */
I2CS |= bmSTART;
I2DAT = a << 1; /* write direction */
while (!(I2CS & bmDONE))
;
if (I2CS & bmACK) {
/* Device responded at this address */
byte_idx = a >> 3;
bit = a & 0x07;
EP0BUF[byte_idx] |= (1 << bit);
}
I2CS |= bmSTOP;
while (I2CS & bmSTOP)
;
}
EP0BCH = 0;
EP0BCL = 16;
return TRUE;
}
/* 0xB5: I2C_RAW_READ -- read N bytes from any I2C address
* wValue = 7-bit I2C address, wIndex = register, wLength = bytes to read
* Uses combined write-read with repeated START */
case 0xB5: {
BYTE i2c_addr_b5 = (BYTE)wval;
BYTE i2c_reg_b5 = (BYTE)SETUP_INDEX();
BYTE i2c_len_b5 = (BYTE)SETUP_LENGTH();
BYTE ok;
if (i2c_len_b5 > 64) i2c_len_b5 = 64;
ok = i2c_combined_read(i2c_addr_b5, i2c_reg_b5, i2c_len_b5, EP0BUF);
if (!ok) {
BYTE fi;
for (fi = 0; fi < i2c_len_b5; fi++)
EP0BUF[fi] = 0xFF;
}
EP0BCH = 0;
EP0BCL = i2c_len_b5;
return TRUE;
}
/* 0xB6: I2C_DIAG -- step-by-step indirect register read diagnostic
* wValue = page/register to read
* Returns 8 bytes: [write_A6_ok, readback_A6, write_A8_ok, readback_A8,
* readback_A7, direct_read_A6, direct_read_A7, direct_read_A8] */
case 0xB6: {
BYTE target_reg = (BYTE)wval;
BYTE diag[8];
BYTE rb;
/* Step 1: Write target register to page select (0xA6) */
diag[0] = bcm_direct_write(BCM_REG_PAGE, target_reg) ? 0x01 : 0x00;
/* Step 2: Read back 0xA6 to verify write */
rb = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_PAGE, 1, &rb);
diag[1] = rb;
/* Step 3: Write read command (0x01) to 0xA8 */
diag[2] = bcm_direct_write(BCM_REG_CMD, BCM_CMD_READ) ? 0x01 : 0x00;
/* Step 4: Read back 0xA8 to check command status */
rb = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_CMD, 1, &rb);
diag[3] = rb;
/* Step 5: Small delay for command execution */
delay(2);
/* Step 6: Read 0xA7 (data register) — this is the result */
rb = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &rb);
diag[4] = rb;
/* Step 7: Read back all three control regs for final state */
rb = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_PAGE, 1, &rb);
diag[5] = rb;
rb = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &rb);
diag[6] = rb;
rb = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_CMD, 1, &rb);
diag[7] = rb;
EP0BUF[0] = diag[0];
EP0BUF[1] = diag[1];
EP0BUF[2] = diag[2];
EP0BUF[3] = diag[3];
EP0BUF[4] = diag[4];
EP0BUF[5] = diag[5];
EP0BUF[6] = diag[6];
EP0BUF[7] = diag[7];
EP0BCH = 0;
EP0BCL = 8;
return TRUE;
}
default: default:
return FALSE; return FALSE;
} }