X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=tv_i2c_hack;a=blobdiff_plain;f=src%2Fi2c.c;fp=src%2Fi2c.c;h=c7af41db615c0147b490491e4905559b9798fc53;hp=0000000000000000000000000000000000000000;hb=3b128b470c001555ba1c69ba8b0c96994182f8a1;hpb=efb5b9c4333f6034595b30d7d549fab2b120d9c9 diff --git a/src/i2c.c b/src/i2c.c new file mode 100644 index 0000000..c7af41d --- /dev/null +++ b/src/i2c.c @@ -0,0 +1,344 @@ +#include +#include +#include +#include "i2c.h" + +enum { + I2C_IDLE, + 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 +}; + +#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 TWIE_BIT (1 << TWIE) + +#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) + +static unsigned char i2c_addr, i2c_subaddr; +static volatile unsigned char i2c_mode; +static int i2c_seq, i2c_ndata; +static unsigned char *i2c_data; + +static void (*async_func)(void); + +void i2c_init(void) +{ + TWBR = 10; /* 10 with 1x prescaler should make it about 100khz */ + TWSR = 0; /* prescaler 1x */ + TWAR = 0; + TWAMR = 0; + TWCR = 0; /* I2C disabled by default */ + + i2c_mode = I2C_IDLE; +} + + +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_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 ST_REP_START: /* 0x10 */ + /* repeated start, same as start, but also increment i2c_seq */ + i2c_seq++; + 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 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++; + TWDR = i2c_subaddr; + I2C_WRITE(); + } else { + if(i2c_ndata--) { + TWDR = *i2c_data++; + I2C_WRITE(); + } else { + /* done sending, send stop */ + i2c_mode = I2C_IDLE; + I2C_STOP(); + } + } + break; + + 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 ST_WR_ACK: /* 0x28 */ + /* data (or subaddr) sent and ACKed, send more data (or restart) if available */ +#if 0 + if(i2c_seq == 0) { + /* subaddr was sent, send repeated start */ + I2C_START(); + } else { +#endif + /* data was sent, send more data or stop */ + if(i2c_ndata--) { + TWDR = *i2c_data++; + I2C_WRITE(); + } else { + i2c_mode = I2C_IDLE; + I2C_STOP(); + } +// } + break; + + case ST_WR_NACK: /* 0x30 */ + /* data (or subaddr) sent but not ACKed */ + if(i2c_seq == 0) { + /* NACK after subaddr, abort */ + printf("i2c: NACK after subaddr\n"); + } else { + /* NACK after data */ + if(i2c_ndata) { + printf("i2c: NACK with %d pending\n", 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 (W): 0x%x\n", state); + i2c_mode = I2C_IDLE; + I2C_STOP(); + } +} + +void i2c_handle_recv(void) +{ + 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) +{ + switch(i2c_mode) { + case I2C_MASTER_WRITE: + i2c_handle_send(); + break; + + case I2C_MASTER_READ: + i2c_handle_recv(); + 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; +} + +void i2c_abort(void) +{ + if(i2c_mode != I2C_IDLE) { + i2c_mode = I2C_IDLE; + I2C_STOP(); + } +}