X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=tv_i2c_hack;a=blobdiff_plain;f=main.c;h=fa0be2f79e51077a8cb1f3a4a22668183acff1e0;hp=aa593dad668612fb2be5fbfcab62b7661f820e21;hb=efb5b9c4333f6034595b30d7d549fab2b120d9c9;hpb=fc0fb4f8d54c52ed6db4ca2deb8bf6d02d4800ff diff --git a/main.c b/main.c index aa593da..fa0be2f 100644 --- a/main.c +++ b/main.c @@ -6,13 +6,64 @@ #include #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) @@ -21,6 +72,8 @@ enum { #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; @@ -32,12 +85,16 @@ int i2c_retry_delay; 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) @@ -45,16 +102,38 @@ volatile int evq_wr, evq_rd; 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; @@ -62,11 +141,12 @@ int main(void) 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(); @@ -74,7 +154,17 @@ int main(void) 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(); @@ -91,7 +181,7 @@ int main(void) } /* read from queue and send over the serial port */ - /* + val = 0xffff; cli(); if(evq_wr != evq_rd) { val = evq[evq_rd]; @@ -100,85 +190,168 @@ int main(void) 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; @@ -187,27 +360,25 @@ void i2c_handle_send(void) 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 { @@ -218,14 +389,14 @@ void i2c_handle_send(void) } 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) { @@ -235,7 +406,6 @@ void i2c_handle_send(void) #endif /* data was sent, send more data or stop */ if(i2c_ndata--) { - LOGNUM(0x200 | *i2c_data); TWDR = *i2c_data++; I2C_WRITE(); } else { @@ -245,7 +415,7 @@ void i2c_handle_send(void) // } break; - case 0x30: + case ST_WR_NACK: /* 0x30 */ /* data (or subaddr) sent but not ACKed */ if(i2c_seq == 0) { /* NACK after subaddr, abort */ @@ -253,20 +423,20 @@ void i2c_handle_send(void) } 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(); @@ -281,46 +451,252 @@ void i2c_handle_send(void) 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; }