#include <util/delay.h>
#include "serial.h"
+#undef USE_PCINT
+
enum {
I2C_IDLE,
- I2C_MASTER_SEND,
- I2C_MASTER_RECV
+ I2C_MASTER_WRITE,
+ I2C_MASTER_READ
+};
+
+enum {
+ ST_INVALID = 0,
+ ST_UNKNOWN = 0xf8,
+ ST_START = 0x08,
+ ST_REP_START = 0x10,
+ ST_SLA_W_ACK = 0x18,
+ ST_SLA_W_NACK = 0x20,
+ ST_WR_ACK = 0x28,
+ ST_WR_NACK = 0x30,
+ ST_ARBLOST = 0x38,
+ ST_SLA_R_ACK = 0x40,
+ ST_SLA_R_NACK = 0x48,
+ ST_RD_ACK = 0x50,
+ ST_RD_NACK = 0x58,
+ ST_MATCH_W = 0x60,
+ ST_ARBLOST_MATCH = 0x68,
+ ST_GENMATCH = 0x70,
+ ST_ARBLOST_GENMATCH = 0x78,
+ ST_SLAVE_RD_ACK = 0x80,
+ ST_SLAVE_RD_NACK = 0x88,
+ ST_SLAVE_GENRD_ACK = 0x90,
+ ST_SLAVE_GENRD_NACK = 0x98,
+ ST_SLAVE_STARTSTOP = 0xa0,
+ ST_SLAVE_SLA_WR_ACK = 0xa8,
+ ST_SLAVE_ARBLOST_WR_ACK = 0xb0,
+ ST_SLAVE_WR_ACK = 0xb8,
+ ST_SLAVE_WR_NACK = 0xc0,
+ ST_SLAVE_LAST_WR_ACK = 0xc8
+};
+
+enum {
+ EV_DEBUG,
+ EV_START,
+ EV_STOP,
+ EV_SLA_W,
+ EV_SLA_R,
+ EV_DATA
+};
+
+static const char *evname[] = {
+ "DBG",
+ "STA",
+ "STO",
+ "A+W",
+ "A+R",
+ "DAT"
};
#define TWINT_BIT (1 << TWINT)
+#define TWEA_BIT (1 << TWEA)
#define TWSTA_BIT (1 << TWSTA)
#define TWSTO_BIT (1 << TWSTO)
#define TWEN_BIT (1 << TWEN)
#define I2C_START() (TWCR = TWINT_BIT | TWSTA_BIT | TWEN_BIT | TWIE_BIT)
#define I2C_STOP() (TWCR = TWINT_BIT | TWSTO_BIT | TWEN_BIT | TWIE_BIT)
#define I2C_WRITE() (TWCR = TWINT_BIT | TWEN_BIT | TWIE_BIT)
+#define I2C_READ_ACK() (TWCR = TWINT_BIT | TWEA_BIT | TWEN_BIT | TWIE_BIT)
+#define I2C_READ_NACK() (TWCR = TWINT_BIT | TWEN_BIT | TWIE_BIT)
unsigned char i2c_addr, i2c_subaddr;
volatile unsigned char i2c_mode;
uint16_t evq[EVQ_SIZE];
volatile int evq_wr, evq_rd;
-#define LOGNUM(x) \
+#define LOGNUM(x) EVLOG(EV_DEBUG, (x))
+
+#define EVLOG(ev, data) \
do { \
- evq[evq_wr] = x; \
- evq_wr = (evq_wr + 1) & (EVQ_SIZE - 1); \
- if(evq_wr == evq_rd) { \
- evq_rd = (evq_rd + 1) & (EVQ_SIZE - 1); \
+ if(ev != EV_DEBUG || dbgmode) { \
+ evq[evq_wr] = ((uint16_t)(ev) << 8) | (data); \
+ evq_wr = (evq_wr + 1) & (EVQ_SIZE - 1); \
+ if(evq_wr == evq_rd) { \
+ evq_rd = (evq_rd + 1) & (EVQ_SIZE - 1); \
+ } \
} \
} while(0)
static char input[64];
static unsigned char inp_cidx;
-static unsigned char data[8];
+static unsigned char data[16];
+
+static void (*async_func)(void);
+
+static uint16_t dump_addr;
+static int dump_count;
+static int dbgmode;
+static int snooping;
static void proc_cmd(char *input);
-void i2c_write(unsigned char addr, unsigned char subaddr, int ndata, unsigned char *data);
+static void printstat(void);
+static void printdump(void);
+
+void i2c_write(unsigned char addr, unsigned char subaddr, unsigned char *data, int ndata);
+void i2c_read(unsigned char addr, unsigned char subaddr, unsigned char *buf, int size);
+void i2c_wait(void);
+void i2c_async(void (*donecb)(void));
+void i2c_check_async(void);
+void i2c_hold(void);
+void i2c_release(void);
+void i2c_snoop(int onoff);
+#ifndef USE_PCINT
+void snoop(void);
+#endif
int main(void)
{
+ uint16_t val;
+
/* tri-state everything and disable pullups */
DDRB = 1; /* B0: activity LED */
- DDRC = 0x20; /* SCL output so we can hold the clock low when disabling i2c */
+ DDRC = 0; /* I2C pins as inputs */
DDRD = 0;
PORTB = 0;
PORTC = 0;
TWBR = 10; /* 10 with 1x prescaler should make it about 100khz */
TWSR = 0; /* prescaler 1x */
- TWAR = 0x8a; /* let's monitor for commands going to the jungle */
+ TWAR = 0;
TWAMR = 0;
- TWCR = (1 << TWEN) | (1 << TWIE); /* enable i2c, don't ack, enable intr */
+ TWCR = 0; /* I2C disabled by default */
i2c_mode = I2C_IDLE;
+ i2c_snoop(0); /* disable snooping by default */
init_serial(38400);
sei();
printf("Starting i2c hack\n");
for(;;) {
- //uint16_t val = 0xffff;
+#ifndef USE_PCINT
+ if(snooping) {
+ snoop();
+ if(have_input()) {
+ i2c_snoop(0);
+ }
+ continue;
+ }
+#endif
+
+ i2c_check_async();
if(have_input()) {
int c = getchar();
}
/* read from queue and send over the serial port */
- /*
+ val = 0xffff;
cli();
if(evq_wr != evq_rd) {
val = evq[evq_rd];
sei();
if(val != 0xffff) {
- printf("s(%u): %x\n", (unsigned int)(val >> 8), (unsigned int)(val & 0xff));
+ unsigned char ev = val >> 8;
+ if(ev > EV_DATA) {
+ printf("%02x: %02x\n", ev, val & 0xff);
+ } else {
+ printf("%s: %02x\n", evname[ev], val & 0xff);
+ }
}
- */
- /*
- _delay_us(100);
- data[0] = 0x3f;
- i2c_write(0x8a, 0x1c, 1, data);
- */
}
return 0;
}
static void proc_cmd(char *input)
{
- if(strcmp(input, "rgb") == 0) {
- printf("OK sending RGB switch command\n");
+ char *endp;
+
+ if(strcmp(input, "dbg") == 0) {
+ printf("OK dbg\n");
+ dbgmode = 1;
- /* i2c addr 8b: jungle
- * subaddr 2b: Control 1 reg
- * bit 1: YUV
+ } else if(strcmp(input, "nodbg") == 0) {
+ printf("OK nodbg\n");
+ dbgmode = 0;
+
+ } else if(strcmp(input, "start") == 0) {
+ printf("OK snooping\n");
+ i2c_snoop(1);
+
+ } else if(strcmp(input, "stop") == 0) {
+ i2c_snoop(0);
+ printf("OK stopped snooping\n");
+
+ } else if(strcmp(input, "av") == 0) {
+ printf("OK AV\n");
+
+ /* AV switch (22): 0 0 SVO CMB1 CMB0 INA INB 0 */
+ data[0] = 0x22;
+ i2c_write(0x8a, 0x22, data, 1);
+ i2c_async(i2c_hold);
+
+ } else if(strcmp(input, "rgb") == 0) {
+ printf("OK RGB\n");
+
+ /* Control 0 (2a): 0 IE2 RBL AKB CL3 CL2 CL1 CL0
+ * Control 1 (2b): 0 0 0 0 0 0 YUV HBL
*/
- data[0] = 2;
- i2c_write(0x8a, 0x2b, 1, data);
+ data[0] = 0x70;
+ data[1] = 0;
+ i2c_write(0x8a, 0x2a, data, 1);
+ while(i2c_mode);
+ i2c_write(0x8a, 0x2b, data + 1, 1);
} else if(memcmp(input, "vol ", 4) == 0) {
int vol = atoi(input + 4);
if(vol < 1 || vol > 63) {
- printf("ERR invalid vol (%s)\n", input + 4);
+ printf("ERR vol (%s)\n", input + 4);
} else {
data[0] = vol;
- i2c_write(0x8a, 0x1f, 1, data);
+ i2c_write(0x8a, 0x1f, data, 1);
printf("OK volume: %d\n", vol);
}
- } else if(strcmp(input, "zoom") == 0) {
- data[0] = 0x3f;
- i2c_write(0x8a, 0x10, 1, data);
- printf("OK zoom in\n");
-
- } else if(strcmp(input, "unzoom") == 0) {
- data[0] = 0x20;
- i2c_write(0x8a, 0x10, 1, data);
- printf("OK unzoom\n");
-
} else if(memcmp(input, "sat", 3) == 0) {
- printf("OK saturate\n");
+ printf("OK sat\n");
data[0] = atoi(input + 3);
if(data[0] <= 0 || data[0] > 0x3f) {
data[0] = 0x1f;
}
- i2c_write(0x8a, 0x1c, 1, data);
- while(i2c_mode);
- TWCR = 0;
+ i2c_write(0x8a, 0x1c, data, 1);
+ i2c_async(i2c_hold); /* hold I2C when done */
+
+ } else if(strcmp(input, "rel") == 0) {
+ printf("OK release\n");
+ i2c_release();
+
+ } else if(strcmp(input, "status") == 0) {
+ i2c_read(0x8a, 0, data, 3);
+ i2c_async(printstat);
+
+ } else if(memcmp(input, "rd ", 3) == 0) {
+ dump_addr = strtol(input + 3, &endp, 16);
+
+ if(endp > input + 3 && dump_addr >= 0 && dump_addr < 2048) {
+ dump_count = 1;
+ i2c_read(0xa0 | ((dump_addr >> 7) & 0xe), dump_addr & 0xff, data, 1);
+ i2c_async(printdump);
+ } else {
+ printf("ERR address: %s\n", input + 3);
+ }
+
+ } else if(memcmp(input, "dump ", 5) == 0) {
+ dump_addr = strtol(input + 5, &endp, 16);
+
+ if(endp > input + 5 && dump_addr >= 0 && dump_addr < 2048) {
+ dump_count = 1;
+ dump_addr &= 0xff0;
+ i2c_read(0xa0 | ((dump_addr >> 7) & 0xe), dump_addr & 0xff, data, 16);
+ i2c_async(printdump);
+ } else {
+ printf("ERR address: %s\n", input + 5);
+ }
} else if(strcmp(input, "abort") == 0) {
if(i2c_mode != I2C_IDLE) {
i2c_mode = I2C_IDLE;
I2C_STOP();
- printf("OK aborting i2c op\n");
+ printf("OK\n");
} else {
printf("ERR i2c is idle\n");
}
} else {
- printf("ERR invalid command (%s)\n", input);
+ printf("ERR command (%s)\n", input);
}
}
-void i2c_write(unsigned char addr, unsigned char subaddr, int ndata, unsigned char *data)
+static void printstat(void)
+{
+ printf("OK status: %02x %02x %02x\n", (unsigned int)data[0],
+ (unsigned int)data[1], (unsigned int)data[2]);
+}
+
+static void printdump(void)
+{
+ int i;
+ while(dump_count > 0) {
+ printf("OK %03x:", dump_addr);
+ for(i=0; i<16; i++) {
+ if(i == 8) putchar(' ');
+
+ if(--dump_count >= 0) {
+ printf(" %02x", data[i]);
+ } else {
+ printf(" ");
+ }
+ dump_addr += 16;
+ }
+ putchar('\n');
+ }
+}
+
+void i2c_write(unsigned char addr, unsigned char subaddr, unsigned char *data, int ndata)
{
i2c_addr = addr & 0xfe;
i2c_subaddr = subaddr;
- i2c_mode = I2C_MASTER_SEND;
+ i2c_mode = I2C_MASTER_WRITE;
i2c_ndata = ndata;
i2c_data = data;
i2c_seq = 0;
I2C_START();
}
+void i2c_read(unsigned char addr, unsigned char subaddr, unsigned char *buf, int size)
+{
+ i2c_addr = addr & 0xfe;
+ i2c_subaddr = subaddr;
+ i2c_mode = I2C_MASTER_READ;
+ i2c_ndata = size;
+ i2c_data = buf;
+ i2c_seq = 0;
+ I2C_START();
+}
+
void i2c_handle_send(void)
{
unsigned char state = TWSR & 0xf8;
LOGNUM(state);
switch(state) {
- case 0x10:
+ case ST_REP_START: /* 0x10 */
/* repeated start, same as start, but also increment i2c_seq */
i2c_seq++;
- case 0x8:
+ case ST_START: /* 0x8 */
/* start initiated, write the slave address */
//printf("DBG i2c SLA: %x\n", (unsigned int)i2c_addr);
TWDR = i2c_addr;
I2C_WRITE();
break;
- case 0x18:
+ case ST_SLA_W_ACK: /* 0x18 */
/* slave addr sent and ACKed, send subaddr or data */
if(i2c_seq == 0) {
/* this is the first packet, send subaddr */
i2c_seq++;
- LOGNUM(0x100 | i2c_subaddr);
TWDR = i2c_subaddr;
I2C_WRITE();
} else {
if(i2c_ndata--) {
- LOGNUM(0x200 | *i2c_data);
TWDR = *i2c_data++;
I2C_WRITE();
} else {
}
break;
- case 0x20:
+ case ST_SLA_W_NACK: /* 0x20 */
/* slave addr sent but not ACKed, abort */
i2c_mode = I2C_IDLE;
I2C_STOP();
printf("i2c: NACK after SLA+W\n");
break;
- case 0x28:
+ case ST_WR_ACK: /* 0x28 */
/* data (or subaddr) sent and ACKed, send more data (or restart) if available */
#if 0
if(i2c_seq == 0) {
#endif
/* data was sent, send more data or stop */
if(i2c_ndata--) {
- LOGNUM(0x200 | *i2c_data);
TWDR = *i2c_data++;
I2C_WRITE();
} else {
// }
break;
- case 0x30:
+ case ST_WR_NACK: /* 0x30 */
/* data (or subaddr) sent but not ACKed */
if(i2c_seq == 0) {
/* NACK after subaddr, abort */
} else {
/* NACK after data */
if(i2c_ndata) {
- printf("i2c: NACK with %d data packets pending\n", i2c_ndata);
+ printf("i2c: NACK with %d pending\n", i2c_ndata);
}
}
i2c_mode = I2C_IDLE;
I2C_STOP();
break;
- case 0x38:
+ case ST_ARBLOST: /* 0x38 */
/* arbitration lost */
- printf("i2c: arbitration lost\n");
+ printf("i2c: arb lost\n");
I2C_START();
break;
- case 0:
+ case ST_INVALID: /* 0 */
printf("i2c: invalid start/stop\n");
i2c_mode = I2C_IDLE;
I2C_STOP();
void i2c_handle_recv(void)
{
- printf("i2c: recv unimplemented\n");
+ unsigned char state = TWSR & 0xf8;
+
+ LOGNUM(state);
+
+ switch(state) {
+ case ST_START: /* 0x8 */
+ TWDR = i2c_addr; /* start a *write* (we need to send the subaddress before reading) */
+ I2C_WRITE();
+ break;
+
+ case ST_REP_START: /* 0x10 */
+ /* repeated start, now we can issue the actual read */
+ TWDR = i2c_addr | 1;
+ I2C_WRITE();
+ break;
+
+ case ST_SLA_W_ACK: /* 0x18 */
+ /* SLA+W means we just started the write part, need to send the subaddress */
+ TWDR = i2c_subaddr;
+ I2C_WRITE();
+ break;
+
+ case ST_SLA_W_NACK:
+ /* slave addr sent but not ACKed, abort */
+ i2c_mode = I2C_IDLE;
+ I2C_STOP();
+ printf("i2c: NACK after SLA+W (R)\n");
+ break;
+
+ case ST_WR_ACK: /* 0x28 */
+ /* the subaddress write was ACKed, rep-start to issue the read */
+ I2C_START();
+ break;
+
+ case ST_WR_NACK:
+ /* the subaddress write was not ACKed, abort */
+ i2c_mode = I2C_IDLE;
+ I2C_STOP();
+ printf("i2c: NACK after subaddr (R)\n");
+ break;
+
+ case ST_SLA_R_ACK: /* 0x40 */
+ /* SLA+R was ACKed send ACK to start receiving */
+ I2C_READ_ACK();
+ break;
+
+ case ST_RD_ACK: /* 0x50 */
+ /* ... or last read was ACKed, again read next and ACK for more */
+ *i2c_data++ = TWDR;
+ if(--i2c_ndata > 0) {
+ I2C_READ_ACK();
+ } else {
+ I2C_READ_NACK();
+ }
+ break;
+
+ case ST_SLA_R_NACK: /* 0x48 */
+ /* SLA+R was sent but ACK was not received, abort */
+ i2c_mode = I2C_IDLE;
+ I2C_STOP();
+ printf("i2c: NACK after SLA+R\n");
+ break;
+
+ case ST_RD_NACK: /* 0x58 */
+ /* read without ACK, we get this after we send a NACK, or this is the last byte (?) */
+ if(i2c_ndata > 0) {
+ *i2c_data++ = TWDR;
+ i2c_ndata--;
+ }
+ i2c_mode = I2C_IDLE;
+ I2C_STOP();
+ break;
+
+ case ST_ARBLOST: /* 0x38 */
+ /* arbitration lost */
+ printf("i2c: arb lost\n");
+ I2C_START();
+ break;
+
+ case ST_INVALID: /* 0 */
+ printf("i2c: invalid start/stop\n");
+ i2c_mode = I2C_IDLE;
+ I2C_STOP();
+ break;
+
+ default:
+ printf("i2c: unexpected state (R): 0x%x\n", state);
+ i2c_mode = I2C_IDLE;
+ I2C_STOP();
+ }
}
ISR(TWI_vect)
{
- uint16_t ev;
-
switch(i2c_mode) {
- case I2C_MASTER_SEND:
+ case I2C_MASTER_WRITE:
i2c_handle_send();
break;
- case I2C_MASTER_RECV:
+ case I2C_MASTER_READ:
i2c_handle_recv();
break;
+ }
+}
- default:
- /* log traffic on the bus */
- ev = (uint16_t)(TWSR & 0xf8) << 8;
- switch(TWSR & 0xf8) {
- case 0x80: /* own SLA+W received, ack */
- case 0x88: /* own SLA+W recv, no-ack */
- case 0x90: /* general recv, ack */
- case 0x98: /* general recv, no-ack */
- ev |= TWDR;
- break;
-
- default:
- break;
- }
+void i2c_wait(void)
+{
+ while(i2c_mode);
+}
+
+void i2c_async(void (*donecb)(void))
+{
+ async_func = donecb;
+}
+
+void i2c_check_async(void)
+{
+ if(async_func && i2c_mode == I2C_IDLE) {
+ async_func();
+ async_func = 0;
+ }
+}
+
+void i2c_hold(void)
+{
+ TWCR = 0; /* make sure the AVR i2c hardware is disabled */
+ DDRC = 0x20; /* ... and drive SCL pin low */
+ PORTC = 0;
+}
+
+void i2c_release(void)
+{
+ DDRC = 0;
+}
+
+#define PC_SCL 5
+#define PC_SDA 4
+#define PC_SCL_BIT (1 << PC_SCL)
+#define PC_SDA_BIT (1 << PC_SDA)
- /* append data to the event queue */
- TWCR = TWINT_BIT | TWEN_BIT | TWIE_BIT;
+static unsigned char pcprev, state;
+static unsigned char value;
+static int nbits;
- evq[evq_wr] = ev;
- evq_wr = (evq_wr + 1) & (EVQ_SIZE - 1);
- if(evq_wr == evq_rd) {
+/* use PCINT12,13 to snoop i2c traffic */
+void i2c_snoop(int onoff)
+{
+ snooping = onoff;
+
+ if(!onoff) {
+#ifdef USE_PCINT
+ /* to stop snooping just disable the interrupt */
+ cli();
+ PCMSK1 = 0;
+ PCICR &= ~(1 << PCIE1);
+ sei();
+#endif
+ return;
+ }
+
+ TWCR = 0; /* make sure the i2c hw is disabled */
+ DDRC = 0;
+ PORTC = 0;
+
+#ifdef USE_PCINT
+ cli();
+ PCICR |= 1 << PCIE1; /* enable the pin-change interrupt */
+ PCMSK1 = (1 << PCINT12) | (1 << PCINT13);
+#endif
+
+ pcprev = PINC;
+ state = 0;
+ value = 0;
+ nbits = 0;
+#ifdef USE_PCINT
+ sei();
+#endif
+}
+
+#ifdef USE_PCINT
+ISR(PCINT1_vect)
+#else
+void snoop(void)
+#endif
+{
+ unsigned char pinc, delta;
+
+ pinc = PINC;
+ delta = pinc ^ pcprev;
+ pcprev = pinc;
+
+ if(delta & PC_SDA_BIT) {
+ if(pinc & PC_SCL_BIT) {
+ /* clock is high, so this is either a start or a stop */
+ if(pinc & PC_SDA_BIT) {
+ EVLOG(EV_STOP, 0);
+ state = 0;
+ } else {
+ EVLOG(EV_START, 0);
+ state = EV_START;
+ value = 0;
+ nbits = 0;
+ }
+ return;
+ }
+ }
+
+ if(!state) {
+ uint16_t val, ev;
+ if(evq_wr != evq_rd) {
+ val = evq[evq_rd];
evq_rd = (evq_rd + 1) & (EVQ_SIZE - 1);
+ ev = val >> 8;
+ if(ev <= EV_STOP) {
+ printf("%s\n", evname[ev]);
+ } else {
+ printf("%s: %02x\n", evname[ev], val & 0xff);
+ }
}
+ return;
+ }
- PORTB = 1;
+ if(delta & PC_SCL_BIT) {
+ if(pinc & PC_SCL_BIT) {
+ /* clock is going high, shift SDA */
+ value = (value << 1) | ((pinc >> PC_SDA) & 1);
+ if(++nbits >= 8) {
+ switch(state) {
+ case EV_START:
+ state = (value & 1) ? EV_SLA_R : EV_SLA_W;
+ EVLOG(state, value & 0xfe);
+ break;
+ case EV_SLA_W:
+ case EV_SLA_R:
+ state = EV_DATA;
+ EVLOG(state, value);
+ case EV_DATA:
+ //EVLOG(state, value);
+ default:
+ break;
+ }
+ nbits = 0;
+ value = 0;
+ }
+ }
}
+
+ PORTB ^= 1;
}