a3f30c8992d06cae3f32393c40cb9e01eeabf38a
[tv_i2c_hack] / src / main.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <avr/io.h>
5 #include <avr/interrupt.h>
6 #include <util/delay.h>
7 #include "serial.h"
8 #include "i2c.h"
9
10 enum {
11         IR_CMD_ONOFF    = 0x0c,
12         IR_CMD_MUTE             = 0x0d,
13         IR_CMD_VOLUP    = 0x10,
14         IR_CMD_VOLDN    = 0x11,
15         IR_CMD_PROGUP   = 0x20,
16         IR_CMD_PROGDN   = 0x21,
17         IR_CMD_OK               = 0x25,
18         IR_CMD_YELLOW   = 0x32,
19         IR_CMD_BLUE             = 0x34,
20         IR_CMD_GREEN    = 0x36,
21         IR_CMD_RED              = 0x37,
22         IR_CMD_TVAV             = 0x38,
23         IR_CMD_MENU             = 0x3b
24 };
25
26 enum {
27         STAT_ON                         = 1,
28         STAT_SYNC                       = 2,
29         STAT_60HZ                       = 4,
30         STAT_COLOR_PAL          = 0x10,
31         STAT_COLOR_NTSC         = 0x20,
32         STAT_COLOR_SECAM        = 0x30,
33
34         STAT_EN                         = 0x40,
35         STAT_BUSHOLD            = 0x80
36 };
37 #define STAT_COLOR_MASK         0x30
38
39 enum {
40         PEND_IR         = 1,
41         PEND_EN         = 2,
42         PEND_VOL        = 4
43 };
44
45 /* TODO
46  * IR decoding, connected to PD2
47  * enable/disable with switch on PCINT1
48  * some of the buttons multiplexed on ADC0
49  */
50 #define PD_IR   2
51 #define PD_ENSW 3
52
53 /* serial input buffer */
54 static char input[64];
55 static unsigned char inp_cidx;
56
57 static unsigned char data[16];
58
59 static uint16_t dump_addr;
60 static int dump_count;
61 static int dbgmode;
62
63 static volatile unsigned char pending;
64 static volatile unsigned char enable, prev_enable;
65 static unsigned char stat, prev_stat;
66 static volatile uint16_t ir_input;
67
68 static unsigned char volume, mute;
69 static unsigned char grab_timeout;
70
71 static void updhold(void);
72 static unsigned char read_status(void);
73 static void proc_cmd(char *input);
74 static void printstat(void);
75 static void printdump(void);
76
77
78 int main(void)
79 {
80         unsigned char pend;
81         uint16_t timer, prev_time;
82
83         /* tri-state everything and disable pullups */
84         DDRB = 1;       /* B0: activity LED */
85         DDRC = 0;       /* I2C pins as inputs */
86         DDRD = 0;
87         PORTB = 0;
88         PORTC = 0;
89         PORTD = 1 << PD_ENSW;
90
91         /* setup external interrupts */
92         EICRA = 0x6;    /* INT0 falling edge, INT1 both */
93         EIMSK = 3;              /* enable INT0 and INT1 */
94
95         /* we'll use the timer 0 compare interrupt for sampling the IR input pulses
96          * we need to sample every 889us or 1124.86 Hz
97          * interrupt frequency = F_CPU / (prescaler * (1 - OCRnx))
98          * 14745600 / (64.0 * (1.0 + 204)) -> 1123.9 Hz
99          */
100         TCCR0A = 2;             /* clear on compare match */
101         TCCR0B = 3;             /* clock select: ioclock / 64 <- prescaler */
102         OCR0A = 192;    /* count top (tweaked until it works) */
103
104         /* setup timer 1 as a freerunning 16bit counter */
105         TCCR1A = 0;
106         TCCR1B = 5;     /* prescaler: clk / 1024: 14.4khz */
107
108         timer = prev_time = 0;
109
110         /* read initial enable switch state */
111         enable = (~PIND >> PD_ENSW) & 1;
112         volume = 31;
113         mute = 0;
114
115         if(enable) grab_timeout = 10;
116
117         i2c_init();
118
119         init_serial(38400);
120         sei();
121
122         printf("TV i2c hack\n");
123
124         for(;;) {
125                 timer = TCNT1;
126                 i2c_check_async();
127
128                 cli();
129                 pend = pending;
130                 pending = 0;
131                 sei();
132                 if((pend & PEND_IR) && !(ir_input & 0x8000)) {
133                         printf("IR addr: %02x  cmd: %02x  (%04x)\n", (ir_input >> 6) & 0x1f, ir_input & 0x3f, ir_input);
134
135                         if(((ir_input >> 6) & 0x1f) == 0) {     /* TV remote sends IR address 0 */
136                                 switch(ir_input & 0x3f) {
137                                 case IR_CMD_GREEN:
138                                         enable = 1;
139                                         pend |= PEND_EN;
140                                         break;
141
142                                 case IR_CMD_RED:
143                                         enable = 0;
144                                         pend |= PEND_EN;
145                                         break;
146
147                                 case IR_CMD_VOLUP:
148                                         if(volume < 0x3f) {
149                                                 volume++;
150                                                 pend |= PEND_VOL;
151                                         }
152                                         break;
153
154                                 case IR_CMD_VOLDN:
155                                         if(volume > 0) {
156                                                 volume--;
157                                                 pend |= PEND_VOL;
158                                         }
159                                         break;
160
161                                 case IR_CMD_MUTE:
162                                         mute ^= 1;
163                                         pend |= PEND_VOL;
164                                         break;
165                                 }
166                         }
167                 }
168                 if((pend & PEND_EN) && enable != prev_enable) {
169                         printf("enable: %d\n", enable);
170                         prev_enable = enable;
171
172                         if(!enable && i2c_isheld()) {
173                                 i2c_release();
174                                 printf("rel1\n");
175                         }
176                 }
177
178                 if(have_input()) {
179                         int c = getchar();
180                         putchar(c);
181
182                         if(c == '\r' || c == '\n') {
183                                 putchar('\n');
184                                 input[inp_cidx] = 0;
185                                 proc_cmd(input);
186                                 inp_cidx = 0;
187                         } else if(inp_cidx < sizeof input - 1) {
188                                 input[inp_cidx++] = c;
189                         }
190                 }
191
192                 if(timer - prev_time >= 14400) {        /* 1 sec */
193                         prev_time = timer;
194
195                         prev_stat = stat;
196                         stat = read_status();
197
198                         if(stat != prev_stat) {
199                                 printf("[%s|%s|%dHz|", stat & STAT_ON ? " on" : "off",
200                                                 stat & STAT_SYNC ? "lock" : "    ", stat & STAT_60HZ ? 60 : 50);
201
202                                 switch(stat & STAT_COLOR_MASK) {
203                                 case STAT_COLOR_PAL:
204                                         printf(" PAL ]");
205                                         break;
206                                 case STAT_COLOR_NTSC:
207                                         printf(" NTSC]");
208                                         break;
209                                 case STAT_COLOR_SECAM:
210                                         printf("SECAM]");
211                                         break;
212                                 default:
213                                         printf("     ]");
214                                 }
215
216                                 printf(" [%s|%s]\n", stat & STAT_EN ? "en" : "  ",
217                                                 stat & STAT_BUSHOLD ? "hold" : "    ");
218                         }
219                         /*printf(" %02x %02x %02x\n", data[0], data[1], data[2]);*/
220
221
222                         if(enable) {
223                                 static unsigned char rel_timeout;
224                                 if(i2c_isheld()) {
225                                         if(!(stat & STAT_ON) || (stat & STAT_COLOR_MASK)) {
226                                                 /* if we turn the TV off, or if we don't have RGB input
227                                                  * release the i2c bus (after a few seconds to make sure it's not a glitch)
228                                                  */
229                                                 if(++rel_timeout > 3) {
230                                                         i2c_release();
231                                                         printf("rel2\n");
232                                                         rel_timeout = 0;
233                                                         grab_timeout = 10;
234                                                 }
235                                         } else {
236                                                 rel_timeout = 0;
237                                                 /* handle any pending volume changes */
238                                                 /*
239                                                 if(pend & PEND_VOL) {
240                                                         updhold();
241                                                 }
242                                                 */
243                                         }
244
245                                 } else {
246                                         /* we're not currently holding the bus */
247                                         if((stat & STAT_ON) && !(stat & STAT_COLOR_MASK)) {
248                                                 /* if the TV is on and we're in RGB mode, grab the bus
249                                                  * (after a few seconds to allow the tv to initialize)
250                                                  */
251                                                 if(grab_timeout > 0) {
252                                                         grab_timeout--;
253                                                 } else {
254                                                         updhold();
255                                                         grab_timeout = 0;
256                                                 }
257                                         }
258                                 }
259                         }
260                 }
261         }
262         return 0;
263 }
264
265 static void updhold(void)
266 {
267         data[0] = mute ? 0 : volume;
268         data[1] = 0x1f;
269         i2c_write(0x8a, 0x1f, data, 1);
270         i2c_wait();
271         i2c_write(0x8a, 0x1c, data + 1, 1);
272         i2c_wait();
273         i2c_hold();
274 }
275
276 static unsigned char read_status(void)
277 {
278         unsigned char s, col;
279
280         s = i2c_isheld() ? STAT_BUSHOLD : 0;
281         if(enable) s |= STAT_EN;
282
283         i2c_read(0x8a, 0, data, 3);
284         if(s & STAT_BUSHOLD) {
285                 i2c_wait();
286                 i2c_hold();
287         } else {
288                 i2c_wait();
289         }
290
291         if(data[2] & 0x80) s |= STAT_ON;
292         if(data[0] & 0x10) s |= STAT_SYNC;
293         if(data[1] & 0x20) s |= STAT_60HZ;
294         col = data[0] & 0x0f;
295
296         if(col != 0) {
297                 if(col == 10) {
298                         s |= STAT_COLOR_SECAM;
299                 } else if(col & 1) {
300                         s |= STAT_COLOR_NTSC;
301                 } else {
302                         s |= STAT_COLOR_PAL;
303                 }
304         }
305
306         return s;
307 }
308
309 static void proc_cmd(char *input)
310 {
311         char *endp;
312
313         if(strcmp(input, "dbg") == 0) {
314                 printf("OK dbg\n");
315                 dbgmode = 1;
316
317         } else if(strcmp(input, "nodbg") == 0) {
318                 printf("OK nodbg\n");
319                 dbgmode = 0;
320
321         } else if(strcmp(input, "av") == 0) {
322                 printf("OK AV\n");
323
324                 /* AV switch (22): 0 0 SVO CMB1 CMB0 INA INB 0 */
325                 data[0] = 0x22;
326                 i2c_write(0x8a, 0x22, data, 1);
327                 i2c_async(i2c_hold);
328
329         } else if(strcmp(input, "rgb") == 0) {
330                 printf("OK RGB\n");
331
332                 /* Control 0 (2a): 0 IE2 RBL AKB  CL3 CL2 CL1 CL0
333                  * Control 1 (2b): 0  0   0   0    0   0  YUV HBL
334                  */
335                 data[0] = 0x70;
336                 data[1] = 0;
337                 i2c_write(0x8a, 0x2a, data, 1);
338                 i2c_wait();
339                 i2c_write(0x8a, 0x2b, data + 1, 1);
340
341         } else if(memcmp(input, "vol ", 4) == 0) {
342                 int vol = atoi(input + 4);
343                 if(vol < 1 || vol > 63) {
344                         printf("ERR vol (%s)\n", input + 4);
345                 } else {
346                         data[0] = vol;
347                         i2c_write(0x8a, 0x1f, data, 1);
348                         printf("OK volume: %d\n", vol);
349                 }
350
351         } else if(memcmp(input, "sat", 3) == 0) {
352                 printf("OK sat\n");
353                 data[0] = atoi(input + 3);
354                 if(data[0] <= 0 || data[0] > 0x3f) {
355                         data[0] = 0x1f;
356                 }
357                 i2c_write(0x8a, 0x1c, data, 1);
358                 i2c_async(i2c_hold);    /* hold I2C when done */
359
360         } else if(strcmp(input, "rel") == 0) {
361                 printf("OK release\n");
362                 i2c_release();
363
364         } else if(strcmp(input, "status") == 0) {
365                 i2c_read(0x8a, 0, data, 3);
366                 i2c_async(printstat);
367
368         } else if(memcmp(input, "rd ", 3) == 0) {
369                 dump_addr = strtol(input + 3, &endp, 16);
370
371                 if(endp > input + 3 && dump_addr >= 0 && dump_addr < 2048) {
372                         dump_count = 1;
373                         i2c_read(0xa0 | ((dump_addr >> 7) & 0xe), dump_addr & 0xff, data, 1);
374                         i2c_async(printdump);
375                 } else {
376                         printf("ERR address: %s\n", input + 3);
377                 }
378
379         } else if(memcmp(input, "dump ", 5) == 0) {
380                 dump_addr = strtol(input + 5, &endp, 16);
381
382                 if(endp > input + 5 && dump_addr >= 0 && dump_addr < 2048) {
383                         dump_count = 1;
384                         dump_addr &= 0xff0;
385                         i2c_read(0xa0 | ((dump_addr >> 7) & 0xe), dump_addr & 0xff, data, 16);
386                         i2c_async(printdump);
387                 } else {
388                         printf("ERR address: %s\n", input + 5);
389                 }
390
391         } else if(strcmp(input, "abort") == 0) {
392                 i2c_abort();
393                 printf("OK\n");
394
395         } else {
396                 printf("ERR command (%s)\n", input);
397         }
398 }
399
400 static void printstat(void)
401 {
402         printf("OK status: %02x %02x %02x\n", (unsigned int)data[0],
403                         (unsigned int)data[1], (unsigned int)data[2]);
404 }
405
406 static void printdump(void)
407 {
408         int i;
409         while(dump_count > 0) {
410                 printf("OK %03x:", dump_addr);
411                 for(i=0; i<16; i++) {
412                         if(i == 8) putchar(' ');
413
414                         if(--dump_count >= 0) {
415                                 printf(" %02x", data[i]);
416                         } else {
417                                 printf("   ");
418                         }
419                         dump_addr += 16;
420                 }
421                 putchar('\n');
422         }
423 }
424
425 static unsigned char nsamples;
426 static uint32_t samples;
427
428 ISR(INT0_vect)
429 {
430         /* ignore interrupts while a previous input is pending */
431         if(pending & 1) return;
432
433         /* IR going low */
434         ir_input = 0x8000;
435         nsamples = 1;   /* we're starting in the middle of the first bit which should be 01 (=1) */
436         samples = 0;
437         EIMSK &= 0xfe;  /* disable further interrupts while decoding IR code */
438
439         TCNT0 = 96;     /* reset the counter to half the range so it'll trigger in the middle of the current pulse */
440         TIFR0 |= 1 << OCF0A;    /* clear pending interrupts */
441         TIMSK0 = 1 << OCIE0A;   /* enable output compare interrupt */
442 }
443
444 ISR(TIMER0_COMPA_vect)
445 {
446         static unsigned char err;
447
448         samples = (samples << 1) | (~(PIND >> PD_IR) & 1);
449         if((++nsamples & 1) == 0) {
450                 if((samples & 3) == 0 || (samples & 3) == 3) {
451                         /* 00 or 11 are invalid sequences, we lost sync */
452                         err = 1;
453                 }
454
455                 /* 01->1, 10->0 */
456                 ir_input = (ir_input << 1) | (samples & 1);
457         }
458
459         if(nsamples >= 28) {
460                 if(err || !(ir_input & 0x2000)) {
461                         ir_input |= 0x8000;
462                 }
463                 pending |= 1;
464                 err = 0;
465                 TIMSK0 &= ~(1 << OCIE0A);       /* disable the sampling interrupt */
466                 EIMSK |= 1;             /* re-enable the edge-detect interrupt for the next input */
467         }
468 }
469
470 ISR(INT1_vect)
471 {
472         enable = (~PIND >> PD_ENSW) & 1;
473         pending |= 2;
474 }