minor cleanup
[tv_i2c_hack] / src / main.c
index 2584292..49ffacc 100644 (file)
@@ -6,29 +6,45 @@
 #include <util/delay.h>
 #include "serial.h"
 #include "i2c.h"
-
-/* TODO
- * IR decoding, connected to PCINT0
- * enable/disable with switch on PCINT1
- * some of the buttons multiplexed on ADC0
- */
-
-#define EVQ_SIZE       64
-uint16_t evq[EVQ_SIZE];
-volatile int evq_wr, evq_rd;
-
-#define LOGNUM(x)      EVLOG(EV_DEBUG, (x))
-
-#define EVLOG(ev, data)        \
-       do { \
-               if(ev != EV_DEBUG || dbgmode) { \
-                       evq[evq_wr] = ((uint16_t)(ev) << 8) | (data); \
-                       evq_wr = (evq_wr + 1) & (EVQ_SIZE - 1); \
-                       if(evq_wr == evq_rd) { \
-                               evq_rd = (evq_rd + 1) & (EVQ_SIZE - 1); \
-                       } \
-               } \
-       } while(0)
+#include "tda93xx.h"
+
+enum {
+       IR_CMD_ONOFF    = 0x0c,
+       IR_CMD_MUTE             = 0x0d,
+       IR_CMD_VOLUP    = 0x10,
+       IR_CMD_VOLDN    = 0x11,
+       IR_CMD_PROGUP   = 0x20,
+       IR_CMD_PROGDN   = 0x21,
+       IR_CMD_OK               = 0x25,
+       IR_CMD_YELLOW   = 0x32,
+       IR_CMD_BLUE             = 0x34,
+       IR_CMD_GREEN    = 0x36,
+       IR_CMD_RED              = 0x37,
+       IR_CMD_TVAV             = 0x38,
+       IR_CMD_MENU             = 0x3b
+};
+
+enum {
+       STAT_ON                         = 1,
+       STAT_SYNC                       = 2,
+       STAT_60HZ                       = 4,
+       STAT_COLOR_PAL          = 0x10,
+       STAT_COLOR_NTSC         = 0x20,
+       STAT_COLOR_SECAM        = 0x30,
+
+       STAT_EN                         = 0x40,
+       STAT_BUSHOLD            = 0x80
+};
+#define STAT_COLOR_MASK                0x30
+
+enum {
+       PEND_IR         = 1,
+       PEND_EN         = 2,
+       PEND_VOL        = 4
+};
+
+#define PD_IR  2
+#define PD_ENSW        3
 
 /* serial input buffer */
 static char input[64];
@@ -40,6 +56,16 @@ static uint16_t dump_addr;
 static int dump_count;
 static int dbgmode;
 
+static volatile unsigned char pending;
+static volatile unsigned char enable, prev_enable;
+static unsigned char stat, prev_stat;
+static volatile uint16_t ir_input;
+
+static unsigned char volume, mute;
+static unsigned char grab_timeout;
+
+static void updhold(void);
+static unsigned char read_status(void);
 static void proc_cmd(char *input);
 static void printstat(void);
 static void printdump(void);
@@ -47,7 +73,8 @@ static void printdump(void);
 
 int main(void)
 {
-       uint16_t val;
+       unsigned char pend;
+       uint16_t timer, prev_time;
 
        /* tri-state everything and disable pullups */
        DDRB = 1;       /* B0: activity LED */
@@ -55,7 +82,33 @@ int main(void)
        DDRD = 0;
        PORTB = 0;
        PORTC = 0;
-       PORTD = 0;
+       PORTD = 1 << PD_ENSW;
+
+       /* setup external interrupts */
+       EICRA = 0x6;    /* INT0 falling edge, INT1 both */
+       EIMSK = 3;              /* enable INT0 and INT1 */
+
+       /* we'll use the timer 0 compare interrupt for sampling the IR input pulses
+        * we need to sample every 889us or 1124.86 Hz
+        * interrupt frequency = F_CPU / (prescaler * (1 - OCRnx))
+        * 14745600 / (64.0 * (1.0 + 204)) -> 1123.9 Hz
+        */
+       TCCR0A = 2;             /* clear on compare match */
+       TCCR0B = 3;             /* clock select: ioclock / 64 <- prescaler */
+       OCR0A = 192;    /* count top (tweaked until it works) */
+
+       /* setup timer 1 as a freerunning 16bit counter */
+       TCCR1A = 0;
+       TCCR1B = 5;     /* prescaler: clk / 1024: 14.4khz */
+
+       timer = prev_time = 0;
+
+       /* read initial enable switch state */
+       enable = (~PIND >> PD_ENSW) & 1;
+       volume = 31;
+       mute = 0;
+
+       if(enable) grab_timeout = 10;
 
        i2c_init();
 
@@ -65,8 +118,61 @@ int main(void)
        printf("TV i2c hack\n");
 
        for(;;) {
+               timer = TCNT1;
                i2c_check_async();
 
+               cli();
+               pend = pending;
+               pending = 0;
+               sei();
+               if((pend & PEND_IR) && !(ir_input & 0x8000)) {
+                       if(dbgmode) {
+                               printf("IR addr: %02x  cmd: %02x  (%04x)\n", (ir_input >> 6) & 0x1f, ir_input & 0x3f, ir_input);
+                       }
+
+                       if(((ir_input >> 6) & 0x1f) == 0) {     /* TV remote sends IR address 0 */
+                               switch(ir_input & 0x3f) {
+                               case IR_CMD_GREEN:
+                                       enable = 1;
+                                       pend |= PEND_EN;
+                                       break;
+
+                               case IR_CMD_RED:
+                                       enable = 0;
+                                       pend |= PEND_EN;
+                                       break;
+
+                               case IR_CMD_VOLUP:
+                                       if(volume < 0x3f) {
+                                               volume++;
+                                               pend |= PEND_VOL;
+                                       }
+                                       break;
+
+                               case IR_CMD_VOLDN:
+                                       if(volume > 0) {
+                                               volume--;
+                                               pend |= PEND_VOL;
+                                       }
+                                       break;
+
+                               case IR_CMD_MUTE:
+                                       mute ^= 1;
+                                       pend |= PEND_VOL;
+                                       break;
+                               }
+                       }
+               }
+               if((pend & PEND_EN) && enable != prev_enable) {
+                       printf("enable: %d\n", enable);
+                       prev_enable = enable;
+
+                       if(!enable && i2c_isheld()) {
+                               i2c_release();
+                               printf("rel1\n");
+                       }
+               }
+
                if(have_input()) {
                        int c = getchar();
                        putchar(c);
@@ -81,21 +187,123 @@ int main(void)
                        }
                }
 
-               /* read from queue and send over the serial port */
-               val = 0xffff;
-               cli();
-               if(evq_wr != evq_rd) {
-                       val = evq[evq_rd];
-                       evq_rd = (evq_rd + 1) & (EVQ_SIZE - 1);
+               if(timer - prev_time >= 14400) {        /* 1 sec */
+                       prev_time = timer;
+
+                       prev_stat = stat;
+                       stat = read_status();
+
+                       if(stat != prev_stat) {
+                               printf("[%s|%s|%dHz|", stat & STAT_ON ? " on" : "off",
+                                               stat & STAT_SYNC ? "lock" : "    ", stat & STAT_60HZ ? 60 : 50);
+
+                               switch(stat & STAT_COLOR_MASK) {
+                               case STAT_COLOR_PAL:
+                                       printf(" PAL ]");
+                                       break;
+                               case STAT_COLOR_NTSC:
+                                       printf(" NTSC]");
+                                       break;
+                               case STAT_COLOR_SECAM:
+                                       printf("SECAM]");
+                                       break;
+                               default:
+                                       printf("     ]");
+                               }
+
+                               printf(" [%s|%s]\n", stat & STAT_EN ? "en" : "  ",
+                                               stat & STAT_BUSHOLD ? "hold" : "    ");
+                       }
+                       /*printf(" %02x %02x %02x\n", data[0], data[1], data[2]);*/
+
+
+                       if(enable) {
+                               static unsigned char rel_timeout;
+                               if(i2c_isheld()) {
+                                       if(!(stat & STAT_ON) || (stat & STAT_COLOR_MASK)) {
+                                               /* if we turn the TV off, or if we don't have RGB input
+                                                * release the i2c bus (after a few seconds to make sure it's not a glitch)
+                                                */
+                                               if(++rel_timeout > 3) {
+                                                       i2c_release();
+                                                       printf("rel2\n");
+                                                       rel_timeout = 0;
+                                                       grab_timeout = 10;
+                                               }
+                                       } else {
+                                               rel_timeout = 0;
+                                               /* handle any pending volume changes */
+                                               /*
+                                               if(pend & PEND_VOL) {
+                                                       updhold();
+                                               }
+                                               */
+                                       }
+
+                               } else {
+                                       /* we're not currently holding the bus */
+                                       if((stat & STAT_ON) && !(stat & STAT_COLOR_MASK)) {
+                                               /* if the TV is on and we're in RGB mode, grab the bus
+                                                * (after a few seconds to allow the tv to initialize)
+                                                */
+                                               if(grab_timeout > 0) {
+                                                       grab_timeout--;
+                                               } else {
+                                                       updhold();
+                                                       grab_timeout = 0;
+                                               }
+                                       }
+                               }
+                       }
                }
-               sei();
+       }
+       return 0;
+}
+
+static void updhold(void)
+{
+       data[0] = mute ? 0 : volume;
+       data[1] = 0x1f;
+       /*
+       i2c_write(JADDR, JSUB_VOLUME, data, 1);
+       i2c_wait();
+       */
+       i2c_write(JADDR, JSUB_SATURATION, data + 1, 1);
+       i2c_wait();
+       i2c_hold();
+}
 
-               if(val != 0xffff) {
-                       unsigned char ev = val >> 8;
-                       printf("%02x: %02x\n", ev, val & 0xff);
+static unsigned char read_status(void)
+{
+       unsigned char s, col;
+
+       s = i2c_isheld() ? STAT_BUSHOLD : 0;
+       if(enable) s |= STAT_EN;
+
+       i2c_read(JADDR, JSUB_STAT0, data, 3);
+       if(s & STAT_BUSHOLD) {
+               i2c_wait();
+               i2c_hold();
+       } else {
+               i2c_wait();
+       }
+
+       if(data[2] & 0x80) s |= STAT_ON;
+       if(data[0] & 0x10) s |= STAT_SYNC;
+       if(data[1] & 0x20) s |= STAT_60HZ;
+       col = data[0] & 0x0f;
+
+       if(col != 0) {
+               if(col == 10) {
+                       s |= STAT_COLOR_SECAM;
+               } else if(col & 1) {
+                       s |= STAT_COLOR_NTSC;
+               } else {
+                       s |= STAT_COLOR_PAL;
                }
        }
-       return 0;
+
+       return s;
 }
 
 static void proc_cmd(char *input)
@@ -115,7 +323,7 @@ static void proc_cmd(char *input)
 
                /* AV switch (22): 0 0 SVO CMB1 CMB0 INA INB 0 */
                data[0] = 0x22;
-               i2c_write(0x8a, 0x22, data, 1);
+               i2c_write(JADDR, JSUB_AVSWITCH, data, 1);
                i2c_async(i2c_hold);
 
        } else if(strcmp(input, "rgb") == 0) {
@@ -126,9 +334,9 @@ static void proc_cmd(char *input)
                 */
                data[0] = 0x70;
                data[1] = 0;
-               i2c_write(0x8a, 0x2a, data, 1);
+               i2c_write(JADDR, JSUB_CTRL0, data, 1);
                i2c_wait();
-               i2c_write(0x8a, 0x2b, data + 1, 1);
+               i2c_write(JADDR, JSUB_CTRL1, data + 1, 1);
 
        } else if(memcmp(input, "vol ", 4) == 0) {
                int vol = atoi(input + 4);
@@ -136,7 +344,7 @@ static void proc_cmd(char *input)
                        printf("ERR vol (%s)\n", input + 4);
                } else {
                        data[0] = vol;
-                       i2c_write(0x8a, 0x1f, data, 1);
+                       i2c_write(JADDR, JSUB_VOLUME, data, 1);
                        printf("OK volume: %d\n", vol);
                }
 
@@ -146,7 +354,7 @@ static void proc_cmd(char *input)
                if(data[0] <= 0 || data[0] > 0x3f) {
                        data[0] = 0x1f;
                }
-               i2c_write(0x8a, 0x1c, data, 1);
+               i2c_write(JADDR, JSUB_SATURATION, data, 1);
                i2c_async(i2c_hold);    /* hold I2C when done */
 
        } else if(strcmp(input, "rel") == 0) {
@@ -154,7 +362,7 @@ static void proc_cmd(char *input)
                i2c_release();
 
        } else if(strcmp(input, "status") == 0) {
-               i2c_read(0x8a, 0, data, 3);
+               i2c_read(JADDR, JSUB_STAT0, data, 3);
                i2c_async(printstat);
 
        } else if(memcmp(input, "rd ", 3) == 0) {
@@ -213,3 +421,54 @@ static void printdump(void)
                putchar('\n');
        }
 }
+
+static unsigned char nsamples;
+static uint32_t samples;
+
+ISR(INT0_vect)
+{
+       /* ignore interrupts while a previous input is pending */
+       if(pending & 1) return;
+
+       /* IR going low */
+       ir_input = 0x8000;
+       nsamples = 1;   /* we're starting in the middle of the first bit which should be 01 (=1) */
+       samples = 0;
+       EIMSK &= 0xfe;  /* disable further interrupts while decoding IR code */
+
+       TCNT0 = 96;     /* reset the counter to half the range so it'll trigger in the middle of the current pulse */
+       TIFR0 |= 1 << OCF0A;    /* clear pending interrupts */
+       TIMSK0 = 1 << OCIE0A;   /* enable output compare interrupt */
+}
+
+ISR(TIMER0_COMPA_vect)
+{
+       static unsigned char err;
+
+       samples = (samples << 1) | (~(PIND >> PD_IR) & 1);
+       if((++nsamples & 1) == 0) {
+               if((samples & 3) == 0 || (samples & 3) == 3) {
+                       /* 00 or 11 are invalid sequences, we lost sync */
+                       err = 1;
+               }
+
+               /* 01->1, 10->0 */
+               ir_input = (ir_input << 1) | (samples & 1);
+       }
+
+       if(nsamples >= 28) {
+               if(err || !(ir_input & 0x2000)) {
+                       ir_input |= 0x8000;
+               }
+               pending |= 1;
+               err = 0;
+               TIMSK0 &= ~(1 << OCIE0A);       /* disable the sampling interrupt */
+               EIMSK |= 1;             /* re-enable the edge-detect interrupt for the next input */
+       }
+}
+
+ISR(INT1_vect)
+{
+       enable = (~PIND >> PD_ENSW) & 1;
+       pending |= 2;
+}