/*  VT1500 Device Driver for Linux, Version 1.0.5
 *  Copyright (C) 1995  M. Gutschke
 *
 *  At the time of writing my e-mail address is:
 *	Internet: gutschk@uni-muenster.de
 *  My snail mail address is:
 *	Markus Gutschke
 *	Schlage 5a
 *	48268 Greven-Gimbte
 *	Germany
 *  If you like this software, I would appreciate if you sent me a postcard
 *  from your hometown. Under the terms of the GNU general public license
 *  you are free to include this program into (commercial) software
 *  distributions (e.g. putting it onto CD-ROM); nonetheless, I would really
 *  appreciate if you dropped me a short note (sending me a sample copy of
 *  your distribution would be even more appreciated!)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#undef REALLY_SLOW_IO
#define SLOW_PORT 0x80

#include <asm/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "rc.h"
#include "tvcontrol.h"
#include "terminal.h"

static struct {
  int  opened;
  int  port;
  int  radio;
  int  TVVGA;
  int  channel;
  int  frq;
  int  volume;
  int  bass;
  int  treble;
  int  mute;
  int  TVVCR;
  int  PALNTSC;
  int  hue;
  int  brightness;
  int  saturation;
  int  contrast;
  int  red;
  int  green;
  int  blue;
} TVStatus = {0,0x200,0,0,-1,591,0,0,0,1,1,1,128,0,0,0,0,0,0};

#define NUM_CHANNEL 104

static struct {
  const char name[4];
  const int  frq;
  int  prgNum;
  int  PALNTSC;
  int  adj;
  char longName[20];
} channelNames[NUM_CHANNEL+1] = {
  {  "2", 480,-1,-1,0,""},{  "3", 550,-1,-1,0,""},{  "4", 620,-1,-1,0,""},
  {"S01", 690,-1,-1,0,""},{"S02", 760,-1,-1,0,""},{"S03", 830,-1,-1,0,""},
  { "S1",1050,-1,-1,0,""},{ "S2",1120,-1,-1,0,""},{ "S3",1190,-1,-1,0,""},
  { "S4",1260,-1,-1,0,""},{ "S5",1330,-1,-1,0,""},{ "S6",1400,-1,-1,0,""},
  { "S7",1470,-1,-1,0,""},{ "S8",1540,-1,-1,0,""},{ "S9",1610,-1,-1,0,""},
  {"S10",1680,-1,-1,0,""},{  "5",1750,-1,-1,0,""},{  "6",1820,-1,-1,0,""},
  { "E7",1890,-1,-1,0,""},{  "8",1960,-1,-1,0,""},{  "9",2030,-1,-1,0,""},
  { "10",2100,-1,-1,0,""},{ "11",2170,-1,-1,0,""},{ "12",2240,-1,-1,0,""},
  {"S11",2310,-1,-1,0,""},{"S12",2380,-1,-1,0,""},{"S13",2450,-1,-1,0,""},
  {"S14",2520,-1,-1,0,""},{"S15",2590,-1,-1,0,""},{"S16",2660,-1,-1,0,""},
  {"S17",2730,-1,-1,0,""},{"S18",2800,-1,-1,0,""},{"S19",2870,-1,-1,0,""},
  {"S20",2940,-1,-1,0,""},{"S21",3030,-1,-1,0,""},{"S22",3110,-1,-1,0,""},
  {"S23",3190,-1,-1,0,""},{"S24",3270,-1,-1,0,""},{"S25",3350,-1,-1,0,""},
  {"S26",3430,-1,-1,0,""},{"S27",3510,-1,-1,0,""},{"S28",3590,-1,-1,0,""},
  {"S29",3670,-1,-1,0,""},{"S30",3750,-1,-1,0,""},{"S31",3830,-1,-1,0,""},
  {"S32",3910,-1,-1,0,""},{"S33",3990,-1,-1,0,""},{"S34",4070,-1,-1,0,""},
  {"S35",4150,-1,-1,0,""},{"S36",4230,-1,-1,0,""},{"S37",4310,-1,-1,0,""},
  {"S38",4390,-1,-1,0,""},{"S39",4470,-1,-1,0,""},{"S40",4550,-1,-1,0,""},
  {"S41",4630,-1,-1,0,""},{ "21",4710,-1,-1,0,""},{ "22",4790,-1,-1,0,""},
  { "23",4870,-1,-1,0,""},{ "24",4950,-1,-1,0,""},{ "25",5030,-1,-1,0,""},
  { "26",5110,-1,-1,0,""},{ "27",5190,-1,-1,0,""},{ "28",5270,-1,-1,0,""},
  { "29",5350,-1,-1,0,""},{ "30",5430,-1,-1,0,""},{ "31",5510,-1,-1,0,""},
  { "32",5590,-1,-1,0,""},{ "33",5670,-1,-1,0,""},{ "34",5750,-1,-1,0,""},
  { "35",5830,-1,-1,0,""},{ "36",5910,-1,-1,0,""},{ "37",5990,-1,-1,0,""},
  { "38",6070,-1,-1,0,""},{ "39",6150,-1,-1,0,""},{ "40",6230,-1,-1,0,""},
  { "41",6310,-1,-1,0,""},{ "42",6390,-1,-1,0,""},{ "43",6470,-1,-1,0,""},
  { "44",6550,-1,-1,0,""},{ "45",6630,-1,-1,0,""},{ "46",6710,-1,-1,0,""},
  { "47",6790,-1,-1,0,""},{ "48",6870,-1,-1,0,""},{ "49",6950,-1,-1,0,""},
  { "50",7030,-1,-1,0,""},{ "51",7110,-1,-1,0,""},{ "52",7190,-1,-1,0,""},
  { "53",7270,-1,-1,0,""},{ "54",7350,-1,-1,0,""},{ "55",7430,-1,-1,0,""},
  { "56",7510,-1,-1,0,""},{ "57",7590,-1,-1,0,""},{ "58",7670,-1,-1,0,""},
  { "59",7750,-1,-1,0,""},{ "60",7830,-1,-1,0,""},{ "61",7910,-1,-1,0,""},
  { "62",7990,-1,-1,0,""},{ "63",8070,-1,-1,0,""},{ "64",8150,-1,-1,0,""},
  { "65",8230,-1,-1,0,""},{ "66",8310,-1,-1,0,""},{ "67",8390,-1,-1,0,""},
  { "68",8470,-1,-1,0,""},{ "69",8550,-1,-1,0,""},{   "", -1,-1,-1,0,""}};

/*
 * The VT1500 card maps one eight bit port at either 0x200 or 0x300; the port
 * allows bitmapped access to an I2C bus:
 *
 *         write:        read:
 *  Bit 0: I2C clock     unknown
 *  Bit 1: I2C data      status
 *  Bit 2: TV/VGA        unknown
 *  Bit 3: unknown       unknown
 *  Bit 4: --            --
 *  Bit 5: unknown       unknown
 *  Bit 6: --            --
 *  Bit 7: --            --
 *
 * I2C encoding is a popular standard for transmitting data on a two-wire bus;
 * data is transmitted in units of eight bits, followed by a sync bit
 * (always high when performing write-accesses);
 * whenever a new (multi-byte) command starts a `command separator' is send:
 *
 * port:  0 131 0     0 232 010 010 232 232 010 232 010 232 0
 *           _          ___         _______     ___     ___
 * data:  __| |__ ... _|   |_______|       |___|   |___|   |_ ...
 *          ___          _   _   _   _   _   _   _   _   _  
 * clock: _|   |_ ... __| |_| |_| |_| |_| |_| |_| |_| |_| |__ ...
 *
 * bit:      CS          7   6   5   4   3   2   1   0   S
 *
 * value:    --          1   0   0   1   1   0   1   0   -  => 0x9A
 *
 * In order to detect, whether a VT1500 card is accessable on a given port,
 * an invalid bus state (both clock and data are simultaneously set to high)
 * is repeatedly output and the status bit is checked; if an error condition
 * is detected, both clock and data are set to low, in order to reset the bus.
 * With some broken cards this does not seem to work, as the status bit seems
 * to be not connected; in this case the command line option `--force' has to
 * be used.
 *
 * The I2C bus is specified for clock speeds of up to 10MHz. If this speed is
 * exceeded (possibly even below that speed), transmitted data will be
 * corrupted. Therefore, pausing I/O commands (inb_p/outb_p) have been used
 * and the status flag is constantly being monitored.
 *
 * The first command byte specifies the chip that you want to address, the
 * second byte usually specifies a register number and all following bytes
 * are parameters.
 *
 * On the VT1500 Rev 2.1 board, I could locate these chips (I do not know,
 * what the other chips do and whether they can be addressed by the I2C bus)
 * (part of the documentation has been copied from a document that is part
 * of the vid_src package (c) 05-05-95 by B. Schwall; this package can be
 * obtained from ftp://sunsite.unc.edu/pub/Linux/apps/video/vdi_src.tgz):
 *
 * Please, tell me, if I got anything wrong (or if you have more information).
 *
 * Address Chip    Registers
 *
 * 0x82 ?? TDA8425 sound control
 *                 0x00: volume     (range: 0xDA..0xF9)
 *                 0x01: volume     (range: 0xDA..0xF9)
 *                 0x02: bass       (bit 0..3: data, bit 4..7: have to be high)
 *                 0x03: treble     (bit 0..3: data, bit 4..7: have to be high)
 *                 0x04: reserved
 *                 0x05: reserved
 *                 0x06: reserved
 *                 0x07: reserved
 *
 * 0x88    TDA4680 color control
 *                 0x00: brightness (bits: 0..5)
 *                 0x01: saturation (bits: 0..5)
 *                 0x02: contrast   (bits: 0..5)
 *                 0x03: unknown    (0x22)
 *                 0x04: red        (bits: 0..5)
 *                 0x05: green      (bits: 0..5)
 *                 0x06: blue       (bits: 0..5)
 *                 0x07: unknown    (0x20)
 *                 0x08: unknown    (0x20)
 *                 0x09: unknown    (0x20)
 *                 0x0A: unknown    (0x30)
 *                 0x0B: unknown    (0x00)
 *                 0x0C: unknown    (0xCD)
 *                 0x0D: unknown    (0x10)
 *                 0x0E: reserved
 *                 0x0F: reserved
 *
 * 0x8A    SAA9051 multi-standard decoder
 *                 0x00: increment delay
 *                 0x01: HSY start time
 *                 0x02: HSY stop time
 *                 0x03: HC start time
 *                 0x04: HC stop time
 *                 0x05: HS start time (after PHI1)
 *                 0x06: hor.peaking: by  pf  bp2  bp1 cor2 cor1 ap2   ap1
 *                 0x07: hue control
 *                 0x08: control1:    hpll fs  vtr co  alt  ypn  ccfr1 ccfr0
 *                 0x09: control2:    vnl  oey oec -   cl   afcc ss1   ss0
 *                 0x0a: control3:    syc  ct  yc  ss3 ss2  ydl2 ydl1  ydl0
 *                 0x0b: secam delay compensation
 *                 0x0c: reserved
 *                 0x0d: reserved
 *                 0x0e: reserved
 *                 0x0f: reserved
 *
 * 0xBA            on screen display
 *                 unknown
 *
 * 0xC2            tuner frequency control
 *                 frequency resolution is 1/16 MHz (62,5kHz)
 *                 frequency is set by transmitting four bytes
 *                  1,2: (frequency+39MHz)*16/10MHz
 *                    3: 0x8E
 *                    4: range: [ 48Mhz..174MHz] -> 0xA0
 *                              (174MHz..457MHz) -> 0x90
 *                              [457MHz..855MHz] -> 0xA0
 *
 * Unidentified chips:
 *   TDA8708
 *   MN4780
 *   SAA9060
 *   SAA9057
 *   PCA8510
 *   TDA4566
 *   TDA7053
 *
 * What seems to be different to Rev. 2.0 boards, is the tuner frequency
 * control.
 *
 */

static int I2Cdata = 0,I2Cclock = 0,I2CisStarted;
int forcePort = 0;

static int chkPort(void)
{
  /* check, if a VT1500 device is connected */
  int i;

  if (!forcePort) {
    if (inb_p(TVStatus.port) & 0x2) {
      outb_p(0,TVStatus.port);
      for (i = 50; inb_p(TVStatus.port) & 0x2;
	   outb_p(3,TVStatus.port),
	   outb_p(0,TVStatus.port))
	if (!i--) return(0); }
    outb_p(3,TVStatus.port);
    for (i = 50; !(inb_p(TVStatus.port) & 0x2);
	 outb_p(0,TVStatus.port),
	 outb_p(3,TVStatus.port))
      if (!i--) return(0);
    outb_p(0,TVStatus.port);
    for (i = 50; inb_p(TVStatus.port) & 0x2;
	 outb_p(3,TVStatus.port),
	 outb_p(0,TVStatus.port))
      if (!i--) return(0); }
  return(1);
}

static __inline__ void out_data_clock(int data,int clock)
{
  /* output I2C encoded data/clock pair */
  int i;

  I2Cdata  = data;
  I2Cclock = clock;
  for (i = 10; i--; ) {
    outb_p((data           ? 0x02 : 0x00) |
	   (clock          ? 0x01 : 0x00) |
	   (TVStatus.TVVGA ? 0x04 : 0x00) |
	   0x0, /* ??? */
	   TVStatus.port);
    if ((int)(inb_p(TVStatus.port) & 2) != data)
      break; }
/*fprintf(stderr,"outb [0x%03x] 0x%02x\n",
	  TVStatus.port,
	  (data           ? 0x02 : 0x00) |
	  (clock          ? 0x01 : 0x00) |
	  (TVStatus.TVVGA ? 0x04 : 0x00)); */
  return;
}

static __inline__ void I2Cstop(void)
{
  /* send stop command */
  I2CisStarted = 0;
  if (I2Cclock) out_data_clock(I2Cdata,0);
  if (I2Cdata)  out_data_clock(0,0);
  out_data_clock(0,1);
  out_data_clock(1,1);
  return;
}

static __inline__ void I2Cstart(void)
{
  /* send start command */
  if (I2CisStarted) I2Cstop();
  I2CisStarted = 1;
  if (!I2Cclock) out_data_clock(I2Cdata,1);
  if (!I2Cdata)  out_data_clock(1,1);
  out_data_clock(0,1);
  out_data_clock(0,0);
  return;
}

static __inline__ void out_bit(int val)
{
  /* send I2C encoded bit */

  if (I2Cclock)       out_data_clock(I2Cdata,0);
  if (I2Cdata != val) out_data_clock(val,0);
  out_data_clock(val,1);
  out_data_clock(val,0);
  return;
}

static __inline__ int in_ack_bit(void)
{
  int rc,i;

  if (I2Cclock) out_data_clock(I2Cdata,0);
  if (!I2Cdata) out_data_clock(1,0);
  out_data_clock(1,1);
  for (i = 20; i--; out_data_clock(1,1))
    if (!(rc = !!(inb_p(TVStatus.port) & 2)))
      break;
  out_data_clock(1,1);
  out_data_clock(1,0);
  return(rc);
}

static __inline__ int in_bit(void)
{
  int rc;

  if (I2Cclock) out_data_clock(I2Cdata,0);
  if (!I2Cdata) out_data_clock(1,0);
  out_data_clock(1,1);
  rc = !!(inb_p(TVStatus.port) & 2);
  out_data_clock(1,0);
  out_data_clock(0,0);
  return(rc);
}

static __inline__ int I2Cwrite(int val)
{
  /* send I2C encoded byte */
  int mask;

  for (mask = 0x100; mask >>= 1; )
    out_bit(!!(val & mask)); /* data bits       */
  return(!in_ack_bit());     /* acknowledge bit */
}

static __inline__ int I2Cread()
{
  /* read I2C encoded byte */
  int mask,rc = 0;

  for (mask = 0x100; mask >>= 1; )
    if (in_bit()) rc |= mask;/* data bits       */
  out_bit(0);                /* acknowledge bit */
  return(rc);
}

void sndCmd(unsigned short cmd)
{
  static int addr = 0,i;

  if (!TVStatus.opened)
    return;
  for (i = 10; i-- && inb_p(TVStatus.port) & 2; out_data_clock(0,0));
  if (cmd & 0x8000) {
    I2Cstart();
    addr = cmd & 0xFF; }
  if (!I2Cwrite(cmd))
 /* fprintf(stderr,"\rWrite to addr 0x%02x (0x%02x) failed\n",addr,cmd&0xFF)*/;
  return;
}

void initializeTuner(void)
{
  static void closeTuner(void);
  static int initialized = 0;
  static unsigned short initData[] = {
    /* control2
     * VNL:       ACTIVE
     * D,BL,HS,VS:ACTIVE
     * UV0..3:    ACTIVE
     * CL:        CO controls color
     * AFCC:      LOW spikes
     * source:    0,4,8,12
     */

    /* TDA8708 sound control                                                 */
    0x8082,0x0004,0x0000,/* reserved                                         */
    0x8082,0x0005,0x0000,/* reserved                                         */
    0x8082,0x0006,0x0000,/* reserved                                         */
    0x8082,0x0007,0x0000,/* reserved                                         */

    /* SAA9051 multi-standard decoder                                        */
    0x808A,0x0000,0x0064,/* increment delay                                  */
    0x808A,0x0001,0x0035,/* HSY start time                                   */
    0x808A,0x0002,0x000A,/* HSY stop  time                                   */
    0x808A,0x0003,0x00F8,/* HC  start time                                   */
    0x808A,0x0004,0x00CD,/* HC  stop  time                                   */
    0x808A,0x0005,0x00FE,/* HS  start time                                   */
    0x808A,0x0009,0x00E0,/* control2:    vnl  oey oec -   cl   afcc ss1   ss0*/
    0x808A,0x000B,0x0000,/* secam delay compensation                         */
    0x808A,0x000C,0x0000,/* reserved                                         */
    0x808A,0x000D,0x0000,/* reserved                                         */
    0x808A,0x000E,0x0000,/* reserved                                         */
    0x808A,0x000F,0x0000,/* reserved                                         */

    /* TDA4680 color control                                                 */
    0x8088,0x0003,0x0022,/* unknown                                          */
    0x8088,0x0007,0x0020,/* unknown                                          */
    0x8088,0x0008,0x0020,/* unknown                                          */
    0x8088,0x0009,0x0020,/* unknown                                          */
    0x8088,0x000A,0x0030,/* unknown                                          */
    0x8088,0x000B,0x0000,/* unknown                                          */
    0x8088,0x000C,0x00CD,/* unknown                                          */
    0x8088,0x000D,0x0010,/* unknown                                          */
    0x8088,0x000E,0x0000,/* reserved                                         */
    0x8088,0x000F,0x0000,/* reserved                                         */

    /* On screen display                                                     */
    0x80BA,0x0079,0x0042,/* clear screen                                     */
    0xFFFF};
  unsigned short *ptr = initData;

  if (!initialized) {
    if (ioperm(SLOW_PORT,1,1)) {
      fprintf(stderr,"Cannot gain access to port 0x%x: %s\n",
	      SLOW_PORT,strerror(errno));
      exit(1); }
    if (RCSettings.port)
      TVStatus.port = abs(RCSettings.port);
    if (ioperm(TVStatus.port,1,1)) {
      fprintf(stderr,"Cannot gain access to port 0x%x: %s\n",
	      TVStatus.port,strerror(errno));
      if (ioperm(SLOW_PORT,1,0)) {
	fprintf(stderr,"Cannot release port 0x%x: %s\n",
		SLOW_PORT,strerror(errno)); }
      exit(1); }
    TVStatus.opened = 1;
    if (!chkPort()) {
      if (ioperm(TVStatus.port,1,0)) {
	fprintf(stderr,"Cannot release port 0x%x: %s\n",
		TVStatus.port,strerror(errno));
	if (ioperm(SLOW_PORT,1,0)) {
	  fprintf(stderr,"Cannot release port 0x%x: %s\n",
		  SLOW_PORT,strerror(errno)); }
	exit(1); }
      if (RCSettings.port) {
	fprintf(stderr,"No TV receiver card (VT1500) detected at port "
                       "address 0x%X\n",
		abs(RCSettings.port));
	if (ioperm(SLOW_PORT,1,0)) {
	  fprintf(stderr,"Cannot release port 0x%x: %s\n",
		  SLOW_PORT,strerror(errno)); }
	exit(1); }
      TVStatus.port = 0x300;
      if (ioperm(TVStatus.port,1,1)) {
	fprintf(stderr,"Cannot gain access to port 0x%x: %s\n",
		TVStatus.port,strerror(errno));
	if (ioperm(SLOW_PORT,1,0)) {
	  fprintf(stderr,"Cannot release port 0x%x: %s\n",
		  SLOW_PORT,strerror(errno)); }
	exit(1); }
      if (!chkPort()) {
	fprintf(stderr,"Cannot locate TV receiver card (VT1500) at port "
                       "address 0x200 or 0x300\n");
	if (ioperm(TVStatus.port,1,0))
	  fprintf(stderr,"Cannot release port 0x%x: %s\n",
		  TVStatus.port,strerror(errno));
	if (ioperm(SLOW_PORT,1,0)) {
	  fprintf(stderr,"Cannot release port 0x%x: %s\n",
		  SLOW_PORT,strerror(errno)); }
	exit(1); } }
    atexit(closeTuner);
    initialized = 1; }
  while (*ptr != 0xFFFF)
    sndCmd(*ptr++);
  setFrq(5910);
  setMute(RCSettings.mute);
  setVolume(RCSettings.volume);
  setBass(RCSettings.bass);
  setTreble(RCSettings.treble);
  if (RCSettings.PALNTSC) setPAL(); else setNTSC();
  setHue(RCSettings.hue);
  setBrightness(RCSettings.brightness);
  setSaturation(RCSettings.saturation);
  setContrast(RCSettings.contrast);
  setRed(RCSettings.red);
  setGreen(RCSettings.green);
  setBlue(RCSettings.blue);
  setChannel(RCSettings.channel);
  setVGA(!RCSettings.TVVGA);
  return;
}

static void closeTuner(void)
{
  setMute(1);
  setVGA(1);
  TVStatus.opened = 0;
  if (ioperm(TVStatus.port,1,0)) {
    fprintf(stderr,"Cannot release port 0x%x: %s\n",
	    TVStatus.port,strerror(errno)); }
  if (ioperm(SLOW_PORT,1,0)) {
    fprintf(stderr,"Cannot release port 0x%x: %s\n",
	    SLOW_PORT,strerror(errno)); }
  return;
}

void setRadio(int flag)
{
  TVStatus.radio = flag;
  setVGA(0);
  return;
}

void setFrq(int frq)
{
  /* this command is supported only for revision 2.1 (and higher (?))   */
  /* I have yet to find out what to do for revision 2.0 boards; the     */
  /* supplied DOS driver does not output correct I2C commands, so it    */
  /* will be hard to find out, what is actually happening...            */
  TVStatus.frq = frq;
  frq = (frq*16+5)/10+0x0270;
  sndCmd(0x80C2);
  sndCmd((frq >> 8)&0xFF);
  sndCmd(frq);
  sndCmd(0x8E);
  sndCmd(frq >= 0x1F00 /* 457MHz */ ? 0x30 :
	 frq >  0x0D50 /* 174MHz */ ? 0x90 :
                                      0xA0);
  return;
}

int setChannel(int channel)
{
  int index,frq,adj,prgNum,PALNTSC;

  switch (channel) {
 retry:
  case CHSTART: {
    int i = -1;
    for (index = NUM_CHANNEL; index--; )
      if (channelNames[index].prgNum != -1 && (i == -1 ||
	   channelNames[index].prgNum < channelNames[i].prgNum))
	i = index;
    TVStatus.channel = i; }
    /* fall thru */
  setchannel:
  case CHPREV:
  case CHNEXT:
    index = TVStatus.channel;
    if (index < 0 && (channel == CHPREV || channel == CHNEXT)) {
      channel = CHSTART;
      goto retry; }
    if (channelInfo(&index,0,channel == CHPREV ? -1 : channel == CHNEXT,
		    NULL,NULL,&frq,&adj,&prgNum,&PALNTSC)) {
      TVStatus.channel = -1;
      setMute(1);
      setVGA(1);
      return(-1); }
    setFrq(frq+adj);
    switch (PALNTSC) {
    case -1: setVGA(1); break;
    case 0:  setNTSC(); break;
    case 1:  setPAL();  break;
    default: break; }
    TVStatus.channel = index;
    break;
  default:
    for (index = NUM_CHANNEL; index--; )
      if (channelNames[index].prgNum == channel) {
	TVStatus.channel = index;
	goto setchannel; }
    break; }
  return(TVStatus.channel);
}

void setVolume(int volume)
{
  TVStatus.volume = volume &= 0x1F;
  volume += 0xDA;
  sndCmd(0x8082); sndCmd(0x0000); sndCmd(volume);
  sndCmd(0x8082); sndCmd(0x0001); sndCmd(volume);
  return;
}

void setBass(int bass)
{
  TVStatus.bass = bass &= 0x0F;
  sndCmd(0x8082); sndCmd(0x0002); sndCmd(0xF0|bass);
  return;
}

void setTreble(int treble)
{
  TVStatus.treble = treble &= 0x0F;
  sndCmd(0x8082); sndCmd(0x0003); sndCmd(0xF0|treble);
  return;
}

void setMute(int flag)
{
  if (flag < 0)
    flag = 0;
  else if (TVStatus.channel < 0 && TVStatus.TVVCR)
    flag = 1;
  TVStatus.mute = flag;
  sndCmd(0x8082); sndCmd(0x0008);
  sndCmd(flag ? TVStatus.TVVCR && RCSettings.externalMute ? 0xDE : 0xFF :
         TVStatus.TVVCR ? 0xDF : 0xDE);
  return;
}

void setVGA(int flag)
{
  if (flag == -1) {
    /* force TV mode */
    TVStatus.TVVGA = 1;
    /*
     * sync.ctrl: HSY and HC pulses enabled
     * CVBS:      ACTIVE
     * FBAS inp:  CVBS
     * source:    0
     * lum.delay: +2
     */
    sndCmd(0x808A); sndCmd(0x000A); sndCmd(0x0042); }
  else {
    if (TVStatus.channel < 0 && TVStatus.TVVCR) {
      setMute(1);
      flag = 1; }
    TVStatus.TVVGA = TVStatus.radio ||
                     (TVStatus.channel >= 0 &&
		      channelNames[TVStatus.channel].PALNTSC < 0) ? 0 : !flag;
    /*
     * sync.ctrl: HSY and HC pulses enabled
     * CVBS:      ACTIVE
     * FBAS inp:  CVBS
     * source:    0/4
     * lum.delay: +2
     */
    sndCmd(0x808A); sndCmd(0x000A); sndCmd(TVStatus.TVVCR ? 0x0042 : 0x004A); }
  return;
}

void setTV(void)
{
  TVStatus.TVVCR = 1;
  setVGA(!TVStatus.TVVGA);
  sndCmd(0x8082); sndCmd(0x0008);
  sndCmd(TVStatus.mute ? RCSettings.externalMute ? 0x00DE : 0x00FF : 0x00DF);
  return;
}

void setVCR(void)
{
  TVStatus.TVVCR = 0;
  setVGA(!TVStatus.TVVGA);
  sndCmd(0x8082); sndCmd(0x0008);
  sndCmd(TVStatus.mute ? 0x00FF : 0x00DE);
  return;
}

void setPAL(void)
{
  TVStatus.PALNTSC = 1;
  /*
   * bypass:    OFF
   * prefilter: ON
   * bandpass:  2.9MHz
   * coring:    OFF
   * app:       ???
   */
  sndCmd(0x808A); sndCmd(0x0006); sndCmd(0x0032);
  /*
   * HPLL:      CLOSED
   * FS:        50Hz, 625 lines
   * VTR:       VTR mode
   * color:     ON
   * alt.mode:  PAL
   * col.carr.: 4.43MHz
   * ccfr:      4.43MHz PAL
   */
  sndCmd(0x808A); sndCmd(0x0008); sndCmd(0x0038);
  return;
}

void setNTSC(void)
{
  TVStatus.PALNTSC = 0;
  /*
   * bypass:    OFF
   * prefilter: ON
   * bandpass:  2.6MHz
   * coring:    OFF
   * app:       ???
   */
  sndCmd(0x808A); sndCmd(0x0006); sndCmd(0x0022);
  /*
   * HPLL:      CLOSED
   * FS:        60Hz, 525 lines
   * VTR:       VTR mode
   * color:     ON
   * alt.mode:  NTSC
   * col.carr.: 3.58MHz
   * ccfr:      3.58MHz NTSC-M
   */
  sndCmd(0x808A); sndCmd(0x0008); sndCmd(0x0077);
  return;
}

void setHue(int hue)
{
  TVStatus.hue = hue &= 0xFF;
  sndCmd(0x808A); sndCmd(0x0007); sndCmd((hue-128) & 0xFF);
  return;
}

void setBrightness(int brightness)
{
  TVStatus.brightness = brightness &= 0x3F;
  sndCmd(0x8088); sndCmd(0x0000); sndCmd(brightness);
  return;
}

void setSaturation(int saturation)
{
  TVStatus.saturation = saturation &= 0x3F;
  sndCmd(0x8088); sndCmd(0x0001); sndCmd(saturation);
  return;
}

void setContrast(int contrast)
{
  TVStatus.contrast = contrast &= 0x3F;
  sndCmd(0x8088); sndCmd(0x0002); sndCmd(contrast);
  return;
}

void setRed(int red)
{
  TVStatus.red = red &= 0x3F;
  sndCmd(0x8088); sndCmd(0x0004); sndCmd(red);
  return;
}

void setGreen(int green)
{
  TVStatus.green = green &= 0x3F;
  sndCmd(0x8088); sndCmd(0x0005); sndCmd(green);
  return;
}

void setBlue(int blue)
{
  TVStatus.blue = blue &= 0x3F;
  sndCmd(0x8088); sndCmd(0x0006); sndCmd(blue);
  return;
}

const char *channelName(int frq,int *index)
{
  int l = 0,m,h = NUM_CHANNEL-1;

  if (frq > channelNames[0].frq) {
    if (frq >= channelNames[NUM_CHANNEL-1].frq)
      l = NUM_CHANNEL-1;
    else {
      while (l+1 < h) {
	m = (l+h)/2;
	if (frq <= channelNames[m].frq) h = m; else l = m; }
      if (frq >= (channelNames[l+1].frq+channelNames[l].frq)/2)
	l++; } }
  if (index)
    *index = l;
  return(channelNames[l].name);
}

int isVisible(void)
{
  return(TVStatus.opened && TVStatus.TVVGA);
}

int isRadio(void)
{
  return(TVStatus.radio);
}

int getTVVCR(void)
{
  return(TVStatus.TVVCR);
}

int getPALNTSC(void)
{
  return(TVStatus.PALNTSC);
}

int getChannel(void)
{
  return(TVStatus.channel);
}

int getFrq(void)
{
  return(TVStatus.frq);
}

int getMute(void)
{
  return(TVStatus.mute);
}

int getBass(void)
{
  return(TVStatus.bass);
}

int getTreble(void)
{
  return(TVStatus.treble);
}

int getVolume(void)
{
  return(TVStatus.volume);
}

int getBrightness(void)
{
  return(TVStatus.brightness);
}

int getSaturation(void)
{
  return(TVStatus.saturation);
}

int getContrast(void)
{
  return(TVStatus.contrast);
}

int getHue(void)
{
  return(TVStatus.hue);
}

int getRed(void)
{
  return(TVStatus.red);
}

int getGreen(void)
{
  return(TVStatus.green);
}

int getBlue(void)
{
  return(TVStatus.blue);
}

int channelInfo(int *index,int chPrg,int inc,
		const char **name,const char **longName,
		int *frq,int *adj,
		int *prgNum,int *PALNTSC)
{
  if (inc == 0) {
    if (!chPrg)
      *index = TVStatus.channel; }
  else {
    if (inc < 0) inc = -1; else inc = 1;
    if (chPrg) {
      if ((*index <= (inc > 0 ? -2 : 0)) ||
	  (*index >= (inc > 0 ? NUM_CHANNEL-1 : NUM_CHANNEL+1)))
	return(-1);
      *index += inc; }
    else {
      int i,j = -1;
      if (*index < 0 || *index >= NUM_CHANNEL)
	return(-1);
      for (i = NUM_CHANNEL; i--;)
	if (channelNames[i].prgNum != -1 &&
	    inc*channelNames[i].prgNum > inc*channelNames[*index].prgNum &&
	    (j < 0 || inc*channelNames[i].prgNum < inc*channelNames[j].prgNum))
	  j = i;
      if (j < 0)
	for (i = NUM_CHANNEL; i--;)
	  if (channelNames[i].prgNum != -1 && (j < 0 ||
	      inc*channelNames[i].prgNum < inc*channelNames[j].prgNum))
	    j = i;
      if (j < 0)
	return(-1);
      *index = j; } }
  if (name) *name = channelNames[*index].name;
  if (longName) *longName = channelNames[*index].longName;
  if (frq) *frq = channelNames[*index].frq;
  if (adj) *adj = channelNames[*index].adj;
  if (prgNum) *prgNum = channelNames[*index].prgNum;
  if (PALNTSC) *PALNTSC = channelNames[*index].PALNTSC;
  return(0);
}

void setChannelInfo(int index,char *longName,int adj,int prgNum,int PALNTSC)
{
  if (index < 0 || index >= NUM_CHANNEL)
    return;
  strcpy(channelNames[index].longName,longName);
  channelNames[index].adj = adj;
  channelNames[index].prgNum = prgNum;
  channelNames[index].PALNTSC = PALNTSC;
  if (index == TVStatus.channel ||
      TVStatus.channel < 0)
    setChannel(channelNames[index].prgNum);
  return;
}
