code cleanup, dropped the sniffer
[tv_i2c_hack] / src / i2c.c
diff --git a/src/i2c.c b/src/i2c.c
new file mode 100644 (file)
index 0000000..c7af41d
--- /dev/null
+++ b/src/i2c.c
@@ -0,0 +1,344 @@
+#include <stdio.h>
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#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();
+       }
+}