#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];
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);
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 */
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();
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);
}
}
- /* 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)
/* 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) {
*/
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);
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);
}
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) {
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) {
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;
+}