--- /dev/null
+#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();
+ }
+}