minor cleanup
[tv_i2c_hack] / src / i2c.c
1 #include <stdio.h>
2 #include <avr/io.h>
3 #include <avr/interrupt.h>
4 #include "i2c.h"
5
6 #define PC_SDA  0x10
7 #define PC_SCL  0x20
8
9 enum {
10         I2C_IDLE,
11         I2C_MASTER_WRITE,
12         I2C_MASTER_READ,
13         I2C_HOLD        = 0xff
14 };
15
16 enum {
17         ST_INVALID                      = 0,
18         ST_UNKNOWN                      = 0xf8,
19         ST_START                        = 0x08,
20         ST_REP_START            = 0x10,
21         ST_SLA_W_ACK            = 0x18,
22         ST_SLA_W_NACK           = 0x20,
23         ST_WR_ACK                       = 0x28,
24         ST_WR_NACK                      = 0x30,
25         ST_ARBLOST                      = 0x38,
26         ST_SLA_R_ACK            = 0x40,
27         ST_SLA_R_NACK           = 0x48,
28         ST_RD_ACK                       = 0x50,
29         ST_RD_NACK                      = 0x58,
30         ST_MATCH_W                      = 0x60,
31         ST_ARBLOST_MATCH        = 0x68,
32         ST_GENMATCH                     = 0x70,
33         ST_ARBLOST_GENMATCH = 0x78,
34         ST_SLAVE_RD_ACK         = 0x80,
35         ST_SLAVE_RD_NACK        = 0x88,
36         ST_SLAVE_GENRD_ACK      = 0x90,
37         ST_SLAVE_GENRD_NACK     = 0x98,
38         ST_SLAVE_STARTSTOP      = 0xa0,
39         ST_SLAVE_SLA_WR_ACK     = 0xa8,
40         ST_SLAVE_ARBLOST_WR_ACK = 0xb0,
41         ST_SLAVE_WR_ACK         = 0xb8,
42         ST_SLAVE_WR_NACK        = 0xc0,
43         ST_SLAVE_LAST_WR_ACK = 0xc8
44 };
45
46 enum {
47         EV_DEBUG,
48         EV_START,
49         EV_STOP,
50         EV_SLA_W,
51         EV_SLA_R,
52         EV_DATA
53 };
54
55 #define TWINT_BIT       (1 << TWINT)
56 #define TWEA_BIT        (1 << TWEA)
57 #define TWSTA_BIT       (1 << TWSTA)
58 #define TWSTO_BIT       (1 << TWSTO)
59 #define TWEN_BIT        (1 << TWEN)
60 #define TWIE_BIT        (1 << TWIE)
61
62 #define I2C_START()     (TWCR = TWINT_BIT | TWSTA_BIT | TWEN_BIT | TWIE_BIT)
63 #define I2C_STOP()      (TWCR = TWINT_BIT | TWSTO_BIT | TWEN_BIT | TWIE_BIT)
64 #define I2C_WRITE()     (TWCR = TWINT_BIT | TWEN_BIT | TWIE_BIT)
65 #define I2C_READ_ACK()  (TWCR = TWINT_BIT | TWEA_BIT | TWEN_BIT | TWIE_BIT)
66 #define I2C_READ_NACK() (TWCR = TWINT_BIT | TWEN_BIT | TWIE_BIT)
67
68 static unsigned char i2c_addr, i2c_subaddr;
69 static volatile unsigned char i2c_mode;
70 static int i2c_seq, i2c_ndata;
71 static unsigned char *i2c_data;
72
73 static void (*async_func)(void);
74
75 void i2c_init(void)
76 {
77         TWBR = 10;              /* 10 with 1x prescaler should make it about 100khz */
78         TWSR = 0;               /* prescaler 1x */
79         TWAR = 0;
80         TWAMR = 0;
81         TWCR = 0;               /* I2C disabled by default */
82
83         i2c_mode = I2C_IDLE;
84 }
85
86
87 void i2c_write(unsigned char addr, unsigned char subaddr, unsigned char *data, int ndata)
88 {
89         i2c_addr = addr & 0xfe;
90         i2c_subaddr = subaddr;
91         i2c_mode = I2C_MASTER_WRITE;
92         i2c_ndata = ndata;
93         i2c_data = data;
94         i2c_seq = 0;
95         I2C_START();
96 }
97
98 void i2c_read(unsigned char addr, unsigned char subaddr, unsigned char *buf, int size)
99 {
100         i2c_addr = addr & 0xfe;
101         i2c_subaddr = subaddr;
102         i2c_mode = I2C_MASTER_READ;
103         i2c_ndata = size;
104         i2c_data = buf;
105         i2c_seq = 0;
106         I2C_START();
107 }
108
109 void i2c_handle_send(void)
110 {
111         unsigned char state = TWSR & 0xf8;
112
113         /*LOGNUM(state);*/
114
115         switch(state) {
116         case ST_REP_START:      /* 0x10 */
117                 /* repeated start, same as start, but also increment i2c_seq */
118                 i2c_seq++;
119         case ST_START:  /* 0x8 */
120                 /* start initiated, write the slave address */
121                 //printf("DBG i2c SLA: %x\n", (unsigned int)i2c_addr);
122                 TWDR = i2c_addr;
123                 I2C_WRITE();
124                 break;
125
126         case ST_SLA_W_ACK:      /* 0x18 */
127                 /* slave addr sent and ACKed, send subaddr or data */
128                 if(i2c_seq == 0) {
129                         /* this is the first packet, send subaddr */
130                         i2c_seq++;
131                         TWDR = i2c_subaddr;
132                         I2C_WRITE();
133                 } else {
134                         if(i2c_ndata--) {
135                                 TWDR = *i2c_data++;
136                                 I2C_WRITE();
137                         } else {
138                                 /* done sending, send stop */
139                                 i2c_mode = I2C_IDLE;
140                                 I2C_STOP();
141                         }
142                 }
143                 break;
144
145         case ST_SLA_W_NACK:     /* 0x20 */
146                 /* slave addr sent but not ACKed, abort */
147                 i2c_mode = I2C_IDLE;
148                 I2C_STOP();
149                 printf("i2c: NACK after SLA+W\n");
150                 break;
151
152         case ST_WR_ACK: /* 0x28 */
153                 /* data (or subaddr) sent and ACKed, send more data (or restart) if available */
154 #if 0
155                 if(i2c_seq == 0) {
156                         /* subaddr was sent, send repeated start */
157                         I2C_START();
158                 } else {
159 #endif
160                         /* data was sent, send more data or stop */
161                         if(i2c_ndata--) {
162                                 TWDR = *i2c_data++;
163                                 I2C_WRITE();
164                         } else {
165                                 i2c_mode = I2C_IDLE;
166                                 I2C_STOP();
167                         }
168 //              }
169                 break;
170
171         case ST_WR_NACK:        /* 0x30 */
172                 /* data (or subaddr) sent but not ACKed */
173                 if(i2c_seq == 0) {
174                         /* NACK after subaddr, abort */
175                         printf("i2c: NACK after subaddr\n");
176                 } else {
177                         /* NACK after data */
178                         if(i2c_ndata) {
179                                 printf("i2c: NACK with %d pending\n", i2c_ndata);
180                         }
181                 }
182                 i2c_mode = I2C_IDLE;
183                 I2C_STOP();
184                 break;
185
186         case ST_ARBLOST:        /* 0x38 */
187                 /* arbitration lost */
188                 printf("i2c: arb lost\n");
189                 I2C_START();
190                 break;
191
192         case ST_INVALID:        /* 0 */
193                 printf("i2c: invalid start/stop\n");
194                 i2c_mode = I2C_IDLE;
195                 I2C_STOP();
196                 break;
197
198         default:
199                 printf("i2c: unexpected state (W): 0x%x\n", state);
200                 i2c_mode = I2C_IDLE;
201                 I2C_STOP();
202         }
203 }
204
205 void i2c_handle_recv(void)
206 {
207         unsigned char state = TWSR & 0xf8;
208
209         /*LOGNUM(state);*/
210
211         switch(state) {
212         case ST_START:          /* 0x8 */
213                 TWDR = i2c_addr;        /* start a *write* (we need to send the subaddress before reading) */
214                 I2C_WRITE();
215                 break;
216
217         case ST_REP_START:      /* 0x10 */
218                 /* repeated start, now we can issue the actual read */
219                 TWDR = i2c_addr | 1;
220                 I2C_WRITE();
221                 break;
222
223         case ST_SLA_W_ACK:      /* 0x18 */
224                 /* SLA+W means we just started the write part, need to send the subaddress */
225                 TWDR = i2c_subaddr;
226                 I2C_WRITE();
227                 break;
228
229         case ST_SLA_W_NACK:
230                 /* slave addr sent but not ACKed, abort */
231                 i2c_mode = I2C_IDLE;
232                 I2C_STOP();
233                 printf("i2c: NACK after SLA+W (R)\n");
234                 break;
235
236         case ST_WR_ACK:         /* 0x28 */
237                 /* the subaddress write was ACKed, rep-start to issue the read */
238                 I2C_START();
239                 break;
240
241         case ST_WR_NACK:
242                 /* the subaddress write was not ACKed, abort */
243                 i2c_mode = I2C_IDLE;
244                 I2C_STOP();
245                 printf("i2c: NACK after subaddr (R)\n");
246                 break;
247
248         case ST_SLA_R_ACK:      /* 0x40 */
249                 /* SLA+R was ACKed send ACK to start receiving */
250                 I2C_READ_ACK();
251                 break;
252
253         case ST_RD_ACK:         /* 0x50 */
254                 /* ... or last read was ACKed, again read next and ACK for more */
255                 *i2c_data++ = TWDR;
256                 if(--i2c_ndata > 0) {
257                         I2C_READ_ACK();
258                 } else {
259                         I2C_READ_NACK();
260                 }
261                 break;
262
263         case ST_SLA_R_NACK:     /* 0x48 */
264                 /* SLA+R was sent but ACK was not received, abort */
265                 i2c_mode = I2C_IDLE;
266                 I2C_STOP();
267                 printf("i2c: NACK after SLA+R\n");
268                 break;
269
270         case ST_RD_NACK:        /* 0x58 */
271                 /* read without ACK, we get this after we send a NACK, or this is the last byte (?) */
272                 if(i2c_ndata > 0) {
273                         *i2c_data++ = TWDR;
274                         i2c_ndata--;
275                 }
276                 i2c_mode = I2C_IDLE;
277                 I2C_STOP();
278                 break;
279
280         case ST_ARBLOST:        /* 0x38 */
281                 /* arbitration lost */
282                 printf("i2c: arb lost\n");
283                 I2C_START();
284                 break;
285
286         case ST_INVALID:        /* 0 */
287                 printf("i2c: invalid start/stop\n");
288                 i2c_mode = I2C_IDLE;
289                 I2C_STOP();
290                 break;
291
292         default:
293                 printf("i2c: unexpected state (R): 0x%x\n", state);
294                 i2c_mode = I2C_IDLE;
295                 I2C_STOP();
296         }
297 }
298
299 ISR(TWI_vect)
300 {
301         switch(i2c_mode) {
302         case I2C_MASTER_WRITE:
303                 i2c_handle_send();
304                 break;
305
306         case I2C_MASTER_READ:
307                 i2c_handle_recv();
308                 break;
309         }
310 }
311
312 void i2c_wait(void)
313 {
314         while(i2c_mode);
315 }
316
317 void i2c_async(void (*donecb)(void))
318 {
319         async_func = donecb;
320 }
321
322 void i2c_check_async(void)
323 {
324         if(async_func && i2c_mode == I2C_IDLE) {
325                 async_func();
326                 async_func = 0;
327         }
328 }
329
330 void i2c_hold(void)
331 {
332         TWCR = 0;               /* make sure the AVR i2c hardware is disabled */
333         DDRC |= PC_SCL; /* ... and drive SCL pin low */
334         PORTC &= ~PC_SCL;
335
336         i2c_mode = I2C_HOLD;
337 }
338
339 void i2c_release(void)
340 {
341         DDRC &= ~PC_SCL;
342
343         i2c_mode = I2C_IDLE;
344 }
345
346 unsigned char i2c_isheld(void)
347 {
348         return i2c_mode == I2C_HOLD;
349 }
350
351 void i2c_abort(void)
352 {
353         if(i2c_mode != I2C_IDLE) {
354                 i2c_mode = I2C_IDLE;
355                 I2C_STOP();
356         }
357 }