minor cleanup
[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 #include "tda93xx.h"
10
11 enum {
12         IR_CMD_ONOFF    = 0x0c,
13         IR_CMD_MUTE             = 0x0d,
14         IR_CMD_VOLUP    = 0x10,
15         IR_CMD_VOLDN    = 0x11,
16         IR_CMD_PROGUP   = 0x20,
17         IR_CMD_PROGDN   = 0x21,
18         IR_CMD_OK               = 0x25,
19         IR_CMD_YELLOW   = 0x32,
20         IR_CMD_BLUE             = 0x34,
21         IR_CMD_GREEN    = 0x36,
22         IR_CMD_RED              = 0x37,
23         IR_CMD_TVAV             = 0x38,
24         IR_CMD_MENU             = 0x3b
25 };
26
27 enum {
28         STAT_ON                         = 1,
29         STAT_SYNC                       = 2,
30         STAT_60HZ                       = 4,
31         STAT_COLOR_PAL          = 0x10,
32         STAT_COLOR_NTSC         = 0x20,
33         STAT_COLOR_SECAM        = 0x30,
34
35         STAT_EN                         = 0x40,
36         STAT_BUSHOLD            = 0x80
37 };
38 #define STAT_COLOR_MASK         0x30
39
40 enum {
41         PEND_IR         = 1,
42         PEND_EN         = 2,
43         PEND_VOL        = 4
44 };
45
46 #define PD_IR   2
47 #define PD_ENSW 3
48
49 /* serial input buffer */
50 static char input[64];
51 static unsigned char inp_cidx;
52
53 static unsigned char data[16];
54
55 static uint16_t dump_addr;
56 static int dump_count;
57 static int dbgmode;
58
59 static volatile unsigned char pending;
60 static volatile unsigned char enable, prev_enable;
61 static unsigned char stat, prev_stat;
62 static volatile uint16_t ir_input;
63
64 static unsigned char volume, mute;
65 static unsigned char grab_timeout;
66
67 static void updhold(void);
68 static unsigned char read_status(void);
69 static void proc_cmd(char *input);
70 static void printstat(void);
71 static void printdump(void);
72
73
74 int main(void)
75 {
76         unsigned char pend;
77         uint16_t timer, prev_time;
78
79         /* tri-state everything and disable pullups */
80         DDRB = 1;       /* B0: activity LED */
81         DDRC = 0;       /* I2C pins as inputs */
82         DDRD = 0;
83         PORTB = 0;
84         PORTC = 0;
85         PORTD = 1 << PD_ENSW;
86
87         /* setup external interrupts */
88         EICRA = 0x6;    /* INT0 falling edge, INT1 both */
89         EIMSK = 3;              /* enable INT0 and INT1 */
90
91         /* we'll use the timer 0 compare interrupt for sampling the IR input pulses
92          * we need to sample every 889us or 1124.86 Hz
93          * interrupt frequency = F_CPU / (prescaler * (1 - OCRnx))
94          * 14745600 / (64.0 * (1.0 + 204)) -> 1123.9 Hz
95          */
96         TCCR0A = 2;             /* clear on compare match */
97         TCCR0B = 3;             /* clock select: ioclock / 64 <- prescaler */
98         OCR0A = 192;    /* count top (tweaked until it works) */
99
100         /* setup timer 1 as a freerunning 16bit counter */
101         TCCR1A = 0;
102         TCCR1B = 5;     /* prescaler: clk / 1024: 14.4khz */
103
104         timer = prev_time = 0;
105
106         /* read initial enable switch state */
107         enable = (~PIND >> PD_ENSW) & 1;
108         volume = 31;
109         mute = 0;
110
111         if(enable) grab_timeout = 10;
112
113         i2c_init();
114
115         init_serial(38400);
116         sei();
117
118         printf("TV i2c hack\n");
119
120         for(;;) {
121                 timer = TCNT1;
122                 i2c_check_async();
123
124                 cli();
125                 pend = pending;
126                 pending = 0;
127                 sei();
128                 if((pend & PEND_IR) && !(ir_input & 0x8000)) {
129                         if(dbgmode) {
130                                 printf("IR addr: %02x  cmd: %02x  (%04x)\n", (ir_input >> 6) & 0x1f, ir_input & 0x3f, ir_input);
131                         }
132
133                         if(((ir_input >> 6) & 0x1f) == 0) {     /* TV remote sends IR address 0 */
134                                 switch(ir_input & 0x3f) {
135                                 case IR_CMD_GREEN:
136                                         enable = 1;
137                                         pend |= PEND_EN;
138                                         break;
139
140                                 case IR_CMD_RED:
141                                         enable = 0;
142                                         pend |= PEND_EN;
143                                         break;
144
145                                 case IR_CMD_VOLUP:
146                                         if(volume < 0x3f) {
147                                                 volume++;
148                                                 pend |= PEND_VOL;
149                                         }
150                                         break;
151
152                                 case IR_CMD_VOLDN:
153                                         if(volume > 0) {
154                                                 volume--;
155                                                 pend |= PEND_VOL;
156                                         }
157                                         break;
158
159                                 case IR_CMD_MUTE:
160                                         mute ^= 1;
161                                         pend |= PEND_VOL;
162                                         break;
163                                 }
164                         }
165                 }
166                 if((pend & PEND_EN) && enable != prev_enable) {
167                         printf("enable: %d\n", enable);
168                         prev_enable = enable;
169
170                         if(!enable && i2c_isheld()) {
171                                 i2c_release();
172                                 printf("rel1\n");
173                         }
174                 }
175
176                 if(have_input()) {
177                         int c = getchar();
178                         putchar(c);
179
180                         if(c == '\r' || c == '\n') {
181                                 putchar('\n');
182                                 input[inp_cidx] = 0;
183                                 proc_cmd(input);
184                                 inp_cidx = 0;
185                         } else if(inp_cidx < sizeof input - 1) {
186                                 input[inp_cidx++] = c;
187                         }
188                 }
189
190                 if(timer - prev_time >= 14400) {        /* 1 sec */
191                         prev_time = timer;
192
193                         prev_stat = stat;
194                         stat = read_status();
195
196                         if(stat != prev_stat) {
197                                 printf("[%s|%s|%dHz|", stat & STAT_ON ? " on" : "off",
198                                                 stat & STAT_SYNC ? "lock" : "    ", stat & STAT_60HZ ? 60 : 50);
199
200                                 switch(stat & STAT_COLOR_MASK) {
201                                 case STAT_COLOR_PAL:
202                                         printf(" PAL ]");
203                                         break;
204                                 case STAT_COLOR_NTSC:
205                                         printf(" NTSC]");
206                                         break;
207                                 case STAT_COLOR_SECAM:
208                                         printf("SECAM]");
209                                         break;
210                                 default:
211                                         printf("     ]");
212                                 }
213
214                                 printf(" [%s|%s]\n", stat & STAT_EN ? "en" : "  ",
215                                                 stat & STAT_BUSHOLD ? "hold" : "    ");
216                         }
217                         /*printf(" %02x %02x %02x\n", data[0], data[1], data[2]);*/
218
219
220                         if(enable) {
221                                 static unsigned char rel_timeout;
222                                 if(i2c_isheld()) {
223                                         if(!(stat & STAT_ON) || (stat & STAT_COLOR_MASK)) {
224                                                 /* if we turn the TV off, or if we don't have RGB input
225                                                  * release the i2c bus (after a few seconds to make sure it's not a glitch)
226                                                  */
227                                                 if(++rel_timeout > 3) {
228                                                         i2c_release();
229                                                         printf("rel2\n");
230                                                         rel_timeout = 0;
231                                                         grab_timeout = 10;
232                                                 }
233                                         } else {
234                                                 rel_timeout = 0;
235                                                 /* handle any pending volume changes */
236                                                 /*
237                                                 if(pend & PEND_VOL) {
238                                                         updhold();
239                                                 }
240                                                 */
241                                         }
242
243                                 } else {
244                                         /* we're not currently holding the bus */
245                                         if((stat & STAT_ON) && !(stat & STAT_COLOR_MASK)) {
246                                                 /* if the TV is on and we're in RGB mode, grab the bus
247                                                  * (after a few seconds to allow the tv to initialize)
248                                                  */
249                                                 if(grab_timeout > 0) {
250                                                         grab_timeout--;
251                                                 } else {
252                                                         updhold();
253                                                         grab_timeout = 0;
254                                                 }
255                                         }
256                                 }
257                         }
258                 }
259         }
260         return 0;
261 }
262
263 static void updhold(void)
264 {
265         data[0] = mute ? 0 : volume;
266         data[1] = 0x1f;
267         /*
268         i2c_write(JADDR, JSUB_VOLUME, data, 1);
269         i2c_wait();
270         */
271         i2c_write(JADDR, JSUB_SATURATION, 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(JADDR, JSUB_STAT0, 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(JADDR, JSUB_AVSWITCH, 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(JADDR, JSUB_CTRL0, data, 1);
338                 i2c_wait();
339                 i2c_write(JADDR, JSUB_CTRL1, 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(JADDR, JSUB_VOLUME, 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(JADDR, JSUB_SATURATION, 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(JADDR, JSUB_STAT0, 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 }