shithub: riscv

ref: d4124d48f9c7e85fdaa50640035080174c94196e
dir: /sys/src/9/pc/devtv.c/

View raw version
/*
  * Driver for Bt848 TV tuner.
  *
  */
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"
#include	"io.h"
#include	"../port/pci.h"
#include	"hcwAMC.h"

#define max(a, b)	(((a) > (b))? (a): (b))

enum {
	Qdir = 0,
	Qsubdir,
	Qsubbase,
	Qvdata = Qsubbase,
	Qadata,
	Qctl,
	Qregs,

	Brooktree_vid = 0x109e,
	Brooktree_848_did = 0x0350,
	Brooktree_878_did = 0x036E,
	Intel_vid = 0x8086,
	Intel_82437_did = 0x122d,

	K = 1024,
	M = K * K,

	Ntvs = 4,

	Numring = 16,

	ntsc_rawpixels = 910,
	ntsc_sqpixels = 780,		/* Including blanking & inactive */
	ntsc_hactive = 640,
	ntsc_vactive = 480,
	ntsc_clkx1delay = 135,		/* Clock ticks. */
	ntsc_clkx1hactive = 754,
	ntsc_vdelay = 26,		/* # of scan lines. */
	ntsc_vscale = 0,

	i2c_nostop = 1 << 5,
	i2c_nos1b  = 1 << 4,
	i2c_timing = 7 << 4,
	i2c_bt848w3b = 1 << 2,
	i2c_bt848scl = 1 << 1,
	i2c_bt848sda = 1 << 0,
	i2c_scl = i2c_bt848scl,
	i2c_sda = i2c_bt848sda,

	i2c_miroproee = 0x80,		/* MIRO PRO EEPROM */
	i2c_tea6300 = 0x80,
	i2c_tda8425 = 0x82,
	i2c_tda9840 = 0x84,
	i2c_tda9850 = 0xb6,
	i2c_haupee = 0xa0,		/* Hauppage EEPROM */
	i2c_stbee = 0xae,		/* STB EEPROM */
	i2c_msp3400 = 0x80,

	i2c_timeout = 1000,
	i2c_delay = 10,

	Bt848_miropro = 0,
	Bt848_miro,
	Bt878_hauppauge,

	/* Bit fields. */
	iform_muxsel1 = 3 << 5,		/* 004 */
	iform_muxsel0 = 2 << 5,
	iform_xtselmask = 3 << 3,
	iform_xtauto = 3 << 3,
	iform_formatmask = 7 << 0,
	iform_ntsc = 1 << 0,

	control_ldec = 1 << 5,		/* 02C */
	contrast_100percent = 0xd8,	/* 030 */

	vscale_interlaced = 1 << 5,	/* 04C */

	adelay_ntsc = 104,		/* 060 */
	bdelay_ntsc = 93,		/* 064 */
	adc_crush = 1 << 0,		/* 068 */

	colorfmt_rgb16 = 2 << 4 | 2 << 0,	/* 0D4 */
	colorfmt_YCbCr422 = 8 << 4 | 8 << 0,
	colorfmt_YCbCr411 = 9 << 4 | 9 << 0,
	colorctl_gamma     = 1 << 4,	/* 0D8 */
	capctl_fullframe   = 1 << 4,	/* 0DC */
	capctl_captureodd  = 1 << 1,
	capctl_captureeven = 1 << 0,
	vbipacksize = 0x190,		/* 0E0 */

	intstat_riscstatshift = 28,	/* 100 */
	intstat_i2crack = 1 << 25,
	intstat_scerr = 1 << 19,
	intstat_ocerr = 1 << 18,
	intstat_pabort = 1 << 17,
	intstat_riperr = 1 << 16,
	intstat_pperr = 1 << 15,
	intstat_fdsr = 1 << 14,
	intstat_ftrgt = 1 << 13,
	intstat_fbus = 1 << 12,
	intstat_risci = 1 << 11,
	intstat_i2cdone = 1 << 8,
	intstat_vpress = 1 << 5,
	intstat_hlock = 1 << 4,
	intstat_vsync = 1 << 1,
	intstat_fmtchg = 1 << 0,
	intmask_etbf = 1 << 23,		/* 104 */

	gpiodmactl_apwrdn = 1 << 26,	/* 10C */
	gpiodmactl_daes2 = 1 << 13,
	gpiodmactl_daiomda = 1 << 6,
	gpiodmactl_pltp23_16 = 2 << 6,
	gpiodmactl_pltp23_0 = 0 << 6,
	gpiodmactl_pltp1_16 = 2 << 4,
	gpiodmactl_pltp1_0 = 0 << 4,
	gpiodmactl_acapenable = 1 << 4,
	gpiodmactl_pktp_32 = 3 << 2,
	gpiodmactl_pktp_0 = 0 << 2,
	gpiodmactl_riscenable = 1 << 1,
	gpiodmactl_fifoenable = 1 << 0,

	/* RISC instructions and parameters. */
	fifo_vre = 0x4,
	fifo_vro = 0xc,
	fifo_fm1 = 0x6,
	fifo_fm3 = 0xe,

	riscirq = 1 << 24,
	riscwrite = 1 << 28,
	riscwrite123 = 9 << 28,
	riscwrite1s23 = 11 << 28,
		riscwrite_sol = 1 << 27,
		riscwrite_eol = 1 << 26,
	riscskip = 0x2 << 28,
	riscjmp = 0x7 << 28,
	riscsync = 0x8 << 28,
		riscsync_resync = 1 << 15,
		riscsync_vre = fifo_vre << 0,
		riscsync_vro = fifo_vro << 0,
		riscsync_fm1 = fifo_fm1 << 0,
		riscsync_fm3 = fifo_fm3 << 0,
	risclabelshift_set = 16,
	risclabelshift_reset = 20,

	AudioTuner = 0,
	AudioRadio,
	AudioExtern,
	AudioIntern,
	AudioOff,
	AudioOn,

	asel_tv = 0,
	asel_radio,
	asel_mic,
	asel_smxc,

	Hwbase_ad = 448000,

	msp_dem = 0x10,
	msp_bbp = 0x12,

	/* Altera definitions. */
	gpio_altera_data = 1 << 0,
	gpio_altera_clock = 1 << 20,
	gpio_altera_nconfig = 1 << 23,

	Ial = 0x140001,
	Idma = 0x100002,

	Adsp = 0x7fd8,
	Adsp_verifysystem = 1,
	Adsp_querysupportplay,
	Adsp_setstyle,
	Adsp_setsrate,
	Adsp_setchannels,
	Adsp_setresolution,
	Adsp_setcrcoptions,
	Adsp_bufenqfor,
	Adsp_logbuffer,
	Adsp_startplay,
	Adsp_stopplay,
	Adsp_autostop,
	Adsp_startrecord,
	Adsp_stoprecord,
	Adsp_getlastprocessed,
	Adsp_pause,
	Adsp_resume,
	Adsp_setvolume,
	Adsp_querysupportrecord,
	Adsp_generalbufenq,
	Adsp_setdownmixtype,
	Adsp_setigain,
	Adsp_setlineout,
	Adsp_setlangmixtype,

	Kfir_gc = 0,
	Kfir_dsp_riscmc,
	Kfir_dsp_risccram,
	Kfir_dsp_unitmc,
	Kfir_bsm_mc,
	Kfir_mux_mc,

	Kfir_devid_gc = 7,
	Kfir_devid_dsp = 4,
	Kfir_devid_bsm = 5,
	Kfir_devid_mux = 8,

	Kfir_200 = 200,
	Kfir_dev_inst = Kfir_200,
	Kfir_201 = 201,
	Kfir_exec = Kfir_201,
	Kfir_202 = 202,
	Kfir_adr_eready = 254,

	Kfir_d_eready_encoding = 0,
	Kfir_d_eready_ready,
	Kfir_d_eready_test,
	Kfir_d_eready_stopdetect,
	Kfir_d_eready_seqend,

	VT_KFIR_OFF = 0,
	VT_KFIR_ON,

	VT_KFIR_LAYER_II = 1,
	VT_KFIR_STEREO = 1,

	Gpioinit = 0,
	Gpiooutput,
	Gpioinput,

	Srate_5512 = 0,
	Srate_11025 = 2,
	Srate_16000 = 3,
	Srate_22050 = 4,
	Srate_32000 = 5,
	Srate_44100 = 6,
	Srate_48000 = 7,

};

typedef struct Variant Variant;
struct Variant {
	ushort	vid;
	ushort	did;
	char	*name;
};

typedef struct Bt848 Bt848;
struct Bt848 {
	ulong	devstat;	/* 000 */
	ulong	iform;		/* 004 */
	ulong	tdec;		/* 008 */
	ulong	ecrop;		/* 00C */
	ulong	evdelaylo;	/* 010 */
	ulong	evactivelo;	/* 014 */
	ulong	ehdelaylo;	/* 018 */
	ulong	ehactivelo;	/* 01C */
	ulong	ehscalehi;	/* 020 */
	ulong	ehscalelo;	/* 024 */
	ulong	bright;		/* 028 */
	ulong	econtrol;	/* 02C */
	ulong	contrastlo;	/* 030 */
	ulong	satulo;		/* 034 */
	ulong	satvlo;		/* 038 */
	ulong	hue;		/* 03C */
	ulong	escloop;	/* 040 */
	ulong	pad0;		/* 044 */
	ulong	oform;		/* 048 */
	ulong	evscalehi;	/* 04C */
	ulong	evscalelo;	/* 050 */
	ulong	test;		/* 054 */
	ulong	pad1[2];	/* 058-05C */
	ulong	adelay;		/* 060 */
	ulong	bdelay;		/* 064 */
	ulong	adc;		/* 068 */
	ulong	evtc;		/* 06C */
	ulong	pad2[3];	/* 070-078 */
	ulong	sreset;		/* 07C */
	ulong	tglb;		/* 080 */
	ulong	tgctrl;		/* 084 */
	ulong	pad3;		/* 088 */
	ulong	ocrop;		/* 08C */
	ulong	ovdelaylo;	/* 090 */
	ulong	ovactivelo;	/* 094 */
	ulong	ohdelaylo;	/* 098 */
	ulong	ohactivelo;	/* 09C */
	ulong	ohscalehi;	/* 0A0 */
	ulong	ohscalelo;	/* 0A4 */
	ulong	pad4;		/* 0A8 */
	ulong	ocontrol;	/* 0AC */
	ulong	pad5[4];	/* 0B0-0BC */
	ulong	oscloop;	/* 0C0 */
	ulong	pad6[2];	/* 0C4-0C8 */
	ulong	ovscalehi;	/* 0CC */
	ulong	ovscalelo;	/* 0D0 */
	ulong	colorfmt;	/* 0D4 */
	ulong	colorctl;	/* 0D8 */
	ulong	capctl;		/* 0DC */
	ulong	vbipacksize;	/* 0E0 */
	ulong	vbipackdel;	/* 0E4 */
	ulong	fcap;		/* 0E8 */
	ulong	ovtc;		/* 0EC */
	ulong	pllflo;		/* 0F0 */
	ulong	pllfhi;		/* 0F4 */
	ulong	pllxci;		/* 0F8 */
	ulong	dvsif;		/* 0FC */
	ulong	intstat;	/* 100 */
	ulong	intmask;	/* 104 */
	ulong	pad7;		/* 108 */
	ulong	gpiodmactl;	/* 10C */
	ulong	i2c;		/* 110 */
	ulong	riscstrtadd;	/* 114 */
	ulong	gpioouten;	/* 118 */
	ulong	gpioreginp;	/* 11C */
	ulong	risccount;	/* 120 */
	ulong	pad8[55];	/* 124-1FC */
	ulong	gpiodata[64];	/* 200-2FC */
};

#define packetlen	i2c

typedef struct Tuner Tuner;
struct Tuner {
	char	*name;
  	ushort	freq_vhfh;	/* Start frequency */
	ushort	freq_uhf;
	uchar	VHF_L;
	uchar	VHF_H;
	uchar	UHF;
	uchar	cfg;
	ushort	offs;
};

typedef struct Frame Frame;
struct Frame {
	ulong	*fstart;
	ulong	*fjmp;
	uchar	*fbase;
};

typedef struct Tv Tv;
struct Tv {
	Lock;
	Rendez;
	Bt848	*bt848;
	Bt848	*bt878;		/* Really only audio control registers */
	Variant	*variant;
	Tuner	*tuner;
	Pcidev	*pci;
	uchar	i2ctuneraddr;
	uchar	i2ccmd;		/* I2C command */
	int	board;		/* What board is this? */
	ulong	cfmt;		/* Current color format. */
	int	channel;	/* Current channel */
	Ref	fref;		/* Copying images? */
	int	nframes;	/* Number of frames to capture. */
	Frame	*frames;	/* DMA program */
	int	lvframe;	/* Last video frame DMAed */
	uchar	*amux;		/* Audio multiplexer. */
	int	nablocks;	/* Number of audio blocks allocated */
	int	absize;		/* Audio block size */
	int	narblocks;	/* Number of audio blocks received */
	ulong	*arisc;		/* Audio risc bloc */
	uchar	*abuf;		/* Audio data buffers */
	char	ainfo[128];

	/* WinTV/PVR stuff. */
	int	msp;
	Lock	kfirlock;
	ulong	i2cstate;	/* Last i2c state. */
	int	gpiostate;	/* Current GPIO state */
	ulong	alterareg;	/* Last used altera register */
	ulong	alteraclock;	/* Used to clock the altera */
	int	asrate;		/* Audio sample rate */
	uchar	aleft, aright;	/* Left and right audio volume */
	ulong	kfirclock;
	Ref	aref;		/* Copying audio? */
};

enum {
	TemicPAL = 0,
	PhilipsPAL,
	PhilipsNTSC,
	PhilipsSECAM,
	Notuner,
	PhilipsPALI,
	TemicNTSC,
	TemicPALI,
	Temic4036,
	AlpsTSBH1,
	AlpsTSBE1,

	Freqmultiplier = 16,
};

static Tuner tuners[] = {
        {"Temic PAL", Freqmultiplier * 140.25, Freqmultiplier * 463.25,
		0x02, 0x04, 0x01, 0x8e, 623 },
	{"Philips PAL_I", Freqmultiplier * 140.25, Freqmultiplier * 463.25,
		0xa0, 0x90, 0x30, 0x8e, 623 },
	{"Philips NTSC",  Freqmultiplier * 157.25, Freqmultiplier * 451.25,
		0xA0, 0x90, 0x30, 0x8e, 732 },
	{"Philips SECAM", Freqmultiplier * 168.25, Freqmultiplier * 447.25,
		0xA7, 0x97, 0x37, 0x8e, 623 },
	{"NoTuner", 0, 0,
		0x00, 0x00, 0x00, 0x00, 0 },
	{"Philips PAL", Freqmultiplier * 168.25, Freqmultiplier * 447.25,
		0xA0, 0x90, 0x30, 0x8e, 623 },
	{"Temic NTSC", Freqmultiplier * 157.25, Freqmultiplier * 463.25,
		0x02, 0x04, 0x01, 0x8e, 732 },
	{"TEMIC PAL_I", Freqmultiplier * 170.00, Freqmultiplier * 450.00,
		0x02, 0x04, 0x01, 0x8e, 623 },
	{"Temic 4036 FY5 NTSC", Freqmultiplier * 157.25, Freqmultiplier * 463.25,
		0xa0, 0x90, 0x30, 0x8e, 732 },
	{"Alps TSBH1", Freqmultiplier * 137.25, Freqmultiplier * 385.25,
		0x01, 0x02, 0x08, 0x8e, 732 },
	{"Alps TSBE1", Freqmultiplier * 137.25, Freqmultiplier * 385.25,
		0x01, 0x02, 0x08, 0x8e, 732 },
};

static int hp_tuners[] = {
	Notuner,
	Notuner,
	Notuner,
	Notuner,
	Notuner,
	PhilipsNTSC,
	Notuner,
	Notuner,
	PhilipsPAL,
	PhilipsSECAM,
	PhilipsNTSC,
	PhilipsPALI,
	Notuner,
	Notuner,
	TemicPAL,
	TemicPALI,
	Notuner,
	PhilipsSECAM,
	PhilipsNTSC,
	PhilipsPALI,
	Notuner,
	PhilipsPAL,
	Notuner,
	PhilipsNTSC,
};

enum {
	CMvstart,
	CMastart,
	CMastop,
	CMvgastart,
	CMvstop,
	CMchannel,
	CMcolormode,
	CMvolume,
	CMmute,
};

static Cmdtab tvctlmsg[] = {
	CMvstart,	"vstart",	2,
	CMastart,	"astart",	5,
	CMastop,	"astop",	1,
	CMvgastart,	"vgastart",	3,
	CMvstop,	"vstop",	1,
	CMchannel,	"channel",	3,
	CMcolormode,	"colormode",	2,
	CMvolume,	"volume",	3,
	CMmute,		"mute",		1,
};

static Variant variant[] = {
	{ Brooktree_vid, Brooktree_848_did, "Brooktree 848 TV tuner", },
	{ Brooktree_vid, Brooktree_878_did, "Brooktree 878 TV tuner", },
};

static char *boards[] = {
	"MIRO PRO",
	"MIRO",
	"Hauppauge Bt878",
};

static ushort Adspfsample[] = {
	0x500, 0x700, 0x400, 0x600, 0x300, 0x200, 0x000, 0x100
};
static ushort Adspstereorates[] = {
	64, 96, 112, 128, 160, 192, 224, 256, 320, 384
};

static uchar miroamux[] = { 2, 0, 0, 0, 10, 0 };
static uchar hauppaugeamux[] = { 0, 1, 2, 3, 4, 0 };
static char *nicamstate[] = {
	"analog", "???", "digital", "bad digital receiption"
};


static Tv tvs[Ntvs];
static int ntvs;

static int i2cread(Tv *, uchar, uchar *);
static int i2cwrite(Tv *, uchar, uchar, uchar, int);
static void tvinterrupt(Ureg *, Tv *);
static void vgastart(Tv *, ulong, int);
static void vstart(Tv *, int, int, int, int);
static void astart(Tv *, char *, uint, uint, uint);
static void vstop(Tv *);
static void astop(Tv *);
static void colormode(Tv *, char *);
static void frequency(Tv *, int, int);
static int getbitspp(Tv *);
static char *getcolormode(ulong);
static int mspreset(Tv *);
static void i2cscan(Tv *);
static int kfirinitialize(Tv *);
static void msptune(Tv *);
static void mspvolume(Tv *, int, int, int);
static void gpioenable(Tv *, ulong, ulong);
static void gpiowrite(Tv *, ulong, ulong);

static void
tvinit(void)
{
	Pcidev *pci;
	ulong intmask;

	/* Test for a triton memory controller. */
	intmask = 0;
	if (pcimatch(nil, Intel_vid, Intel_82437_did))
		intmask = intmask_etbf;

	pci = nil;
	while ((pci = pcimatch(pci, 0, 0)) != nil) {
		int i, t;
		Tv *tv;
		Bt848 *bt848;
		ushort hscale, hdelay;
		uchar v;

		for (i = 0; i != nelem(variant); i++)
			if (pci->vid == variant[i].vid && pci->did == variant[i].did)
				break;
		if (i == nelem(variant))
			continue;

		if (ntvs >= Ntvs) {
			print("#V: Too many TV cards found\n");
			continue;
		}

		tv = &tvs[ntvs++];
		tv->variant = &variant[i];
		tv->pci = pci;
		tv->bt848 = (Bt848 *)vmap(pci->mem[0].bar & ~0x0F, 4 * K);
		if (tv->bt848 == nil)
			panic("#V: Cannot allocate memory for Bt848");
		bt848 = tv->bt848;

		/* i2c stuff. */
		if (pci->did >= 878)
			tv->i2ccmd = 0x83;
		else
			tv->i2ccmd = i2c_timing | i2c_bt848scl | i2c_bt848sda;

		t = 0;
		if (i2cread(tv, i2c_haupee, &v)) {
			uchar ee[256];
			Pcidev *pci878;
			Bt848 *bt878;

			tv->board = Bt878_hauppauge;
			if (!i2cwrite(tv, i2c_haupee, 0, 0, 0))
				panic("#V: Cannot write to Hauppauge EEPROM");
			for (i = 0; i != sizeof ee; i++)
				if (!i2cread(tv, i2c_haupee + 1, &ee[i]))
					panic("#V: Cannot read from Hauppauge EEPROM");

			if (ee[9] > sizeof hp_tuners / sizeof hp_tuners[0])
				panic("#V: Tuner out of range (max %d, this %d)",
					sizeof hp_tuners / sizeof hp_tuners[0], ee[9]);
			t = hp_tuners[ee[9]];

			/* Initialize the audio channel. */
			if ((pci878 = pcimatch(nil, Brooktree_vid, 0x878)) == nil)
				panic("#V: Unsupported Hauppage board");

			tv->bt878 = bt878 =
				(Bt848 *)vmap(pci878->mem[0].bar & ~0x0F, 4 * K);
			if (bt878 == nil)
				panic("#V: Cannot allocate memory for the Bt878");

			kfirinitialize(tv);
			// i2cscan(tv);
			mspreset(tv);

			bt878->gpiodmactl = 0;
			bt878->intstat = (ulong)-1;
			intrenable(pci878->intl, (void (*)(Ureg *, void *))tvinterrupt,
					tv, pci878->tbdf, "tv");

			tv->amux = hauppaugeamux;
		}
		else if (i2cread(tv, i2c_stbee, &v)) {
			USED(t);
			panic("#V: Cannot deal with STB cards");
		}
		else if (i2cread(tv, i2c_miroproee, &v)) {
			tv->board = Bt848_miropro;
			t = ((bt848->gpiodata[0] >> 10) - 1) & 7;
			tv->amux = miroamux;
		}
		else {
			tv->board = Bt848_miro;
			tv->amux = miroamux;
			t = ((bt848->gpiodata[0] >> 10) - 1) & 7;
		}

		if (t >= nelem(tuners))
			t = 4;
		tv->tuner = &tuners[t];
		tv->i2ctuneraddr =
			i2cread(tv, 0xc1, &v)? 0xc0:
			i2cread(tv, 0xc3, &v)? 0xc2:
			i2cread(tv, 0xc5, &v)? 0xc4:
			i2cread(tv, 0xc7, &v)? 0xc6: -1;

		bt848->capctl = capctl_fullframe;
		bt848->adelay = adelay_ntsc;
		bt848->bdelay = bdelay_ntsc;
		bt848->iform = iform_muxsel0|iform_xtauto|iform_ntsc;
		bt848->vbipacksize = vbipacksize & 0xff;
		bt848->vbipackdel = (vbipacksize >> 8) & 1;

		// setpll(bt848);

		tv->cfmt = bt848->colorfmt = colorfmt_rgb16;

		hscale = (ntsc_rawpixels * 4096) / ntsc_sqpixels - 4096;
		hdelay = (ntsc_clkx1delay * ntsc_hactive) / ntsc_clkx1hactive;

		bt848->ovtc = bt848->evtc = 0;
		bt848->ehscalehi = bt848->ohscalehi = (hscale >> 8) & 0xff;
		bt848->ehscalelo = bt848->ohscalelo = hscale & 0xff;
		bt848->evscalehi &= ~0x1f;
		bt848->ovscalehi &= ~0x1f;
		bt848->evscalehi |= vscale_interlaced | ((ntsc_vscale >> 8) & 0x1f);
		bt848->ovscalehi |= vscale_interlaced | (ntsc_vscale >> 8) & 0x1f;
		bt848->evscalelo = bt848->ovscalelo = ntsc_vscale & 0xff;
		bt848->ehactivelo = bt848->ohactivelo = ntsc_hactive & 0xff;
		bt848->ehdelaylo = bt848->ohdelaylo = hdelay & 0xff;
		bt848->evactivelo = bt848->ovactivelo = ntsc_vactive & 0xff;
		bt848->evdelaylo = bt848->ovdelaylo = ntsc_vdelay & 0xff;
		bt848->ecrop = bt848->ocrop =
			((ntsc_hactive >> 8) & 0x03) |
			((hdelay >> 6) & 0x0C) |
	        		((ntsc_vactive >> 4) & 0x30) |
			((ntsc_vdelay >> 2) & 0xC0);

		bt848->colorctl = colorctl_gamma;
		bt848->capctl = 0;
		bt848->gpiodmactl = gpiodmactl_pltp23_16 |
			gpiodmactl_pltp1_16 | gpiodmactl_pktp_32;
		bt848->gpioreginp = 0;
		bt848->contrastlo = contrast_100percent;
		bt848->bright = 16;
		bt848->adc = (2 << 6) | adc_crush;
		bt848->econtrol = bt848->ocontrol = control_ldec;
		bt848->escloop = bt848->oscloop = 0;
		bt848->intstat = (ulong)-1;
		bt848->intmask = intmask |
			intstat_vsync | intstat_scerr | intstat_risci | intstat_ocerr |
			intstat_vpress | intstat_fmtchg;


		if (tv->amux) {
			gpioenable(tv, ~0xfff, 0xfff);
			gpiowrite(tv, ~0xfff, tv->amux[AudioRadio]);
		}

		print("#V%ld: %s (rev %d) (%s/%s) intl %d\n",
			tv - tvs, tv->variant->name, pci->rid, boards[tv->board],
			tv->tuner->name, pci->intl);

		intrenable(pci->intl, (void (*)(Ureg *, void *))tvinterrupt,
			tv, pci->tbdf, "tv");
	}
}

static Chan*
tvattach(char *spec)
{
	return devattach('V', spec);
}

#define TYPE(q)		((int)((q).path & 0xff))
#define DEV(q)		((int)(((q).path >> 8) & 0xff))
#define QID(d, t)	((((d) & 0xff) << 8) | (t))

static int
tv1gen(Chan *c, int i, Dir *dp)
{
	Qid qid;

	switch (i) {
	case Qvdata:
		mkqid(&qid, QID(DEV(c->qid), Qvdata), 0, QTFILE);
		devdir(c, qid, "video", 0, eve, 0444, dp);
		return 1;
	case Qadata:
		mkqid(&qid, QID(DEV(c->qid), Qadata), 0, QTFILE);
		devdir(c, qid, "audio", 0, eve, 0444, dp);
		return 1;
	case Qctl:
		mkqid(&qid, QID(DEV(c->qid), Qctl), 0, QTFILE);
		devdir(c, qid, "ctl", 0, eve, 0444, dp);
		return 1;
	case Qregs:
		mkqid(&qid, QID(DEV(c->qid), Qregs), 0, QTFILE);
		devdir(c, qid, "regs", 0, eve, 0444, dp);
		return 1;
	}
	return -1;
}

static int
tvgen(Chan *c, char *, Dirtab *, int, int i, Dir *dp)
{
	Qid qid;
	int dev;

	dev = DEV(c->qid);
	switch (TYPE(c->qid)) {
	case Qdir:
		if (i == DEVDOTDOT) {
			mkqid(&qid, Qdir, 0, QTDIR);
			devdir(c, qid, "#V", 0, eve, 0555, dp);
			return 1;
		}

		if (i >= ntvs)
			return -1;

		mkqid(&qid, QID(i, Qsubdir), 0, QTDIR);
		snprint(up->genbuf, sizeof(up->genbuf), "tv%d", i);
		devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
		return 1;

	case Qsubdir:
		if (i == DEVDOTDOT) {
			mkqid(&qid, QID(dev, Qdir), 0, QTDIR);
			snprint(up->genbuf, sizeof(up->genbuf), "tv%d", dev);
			devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
			return 1;
		}

		return tv1gen(c, i + Qsubbase, dp);

	case Qvdata:
	case Qadata:
	case Qctl:
	case Qregs:
		return tv1gen(c, TYPE(c->qid), dp);

	default:
		return -1;
	}
}

static Walkqid *
tvwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, 0, 0, tvgen);
}

static int
tvstat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, 0, 0, tvgen);
}

static Chan*
tvopen(Chan *c, int omode)
{
	if (omode != OREAD &&
		TYPE(c->qid) != Qctl && TYPE(c->qid) != Qvdata)
		error(Eperm);

	switch (TYPE(c->qid)) {
	case Qdir:
		return devopen(c, omode, nil, 0, tvgen);
	case Qadata:
		if (tvs[DEV(c->qid)].bt878 == nil)
			error(Enonexist);
		break;
	}

	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;

	if (TYPE(c->qid) == Qadata)
		c->aux = nil;
	return c;
}

static void
tvclose(Chan *)
{
}

static int
audioblock(void *)
{
	return 1;
}

static long
tvread(Chan *c, void *a, long n, vlong offset)
{
	static char regs[10 * K];
	static int regslen;
	Tv *tv;
	char *e, *p;
	uchar *src;

	USED(offset);

	switch(TYPE(c->qid)) {
	case Qdir:
	case Qsubdir:
		return devdirread(c, a, n, 0, 0, tvgen);

	case Qvdata: {
		int bpf, nb;

		tv = &tvs[DEV(c->qid)];
		bpf = ntsc_hactive * ntsc_vactive * getbitspp(tv) / 8;

		if (offset >= bpf)
			return 0;

		nb = n;
		if (offset + nb > bpf)
			nb = bpf - offset;

		ilock(tv);
		if (tv->frames == nil || tv->lvframe >= tv->nframes ||
			tv->frames[tv->lvframe].fbase == nil) {
			iunlock(tv);
			return 0;
		}

		src = tv->frames[tv->lvframe].fbase;
		incref(&tv->fref);
		iunlock(tv);

		memmove(a, src + offset, nb);
		decref(&tv->fref);
		return nb;
	}

	case Qadata: {
		ulong uablock = (ulong)c->aux, bnum, tvablock;
		int boffs, nbytes;

		tv = &tvs[DEV(c->qid)];
		if (tv->bt878 == nil)
			error("#V: No audio device");
		if (tv->absize == 0)
			error("#V: audio not initialized");

		bnum = offset / tv->absize;
		boffs = offset % tv->absize;
		nbytes = tv->absize - boffs;

		incref(&tv->aref);
		for (;;) {
			tvablock = tv->narblocks;	/* Current tv block. */

			if (uablock == 0)
				uablock = tvablock - 1;

			if (tvablock >= uablock + bnum + tv->narblocks)
				uablock = tvablock - 1 - bnum;

			if (uablock + bnum == tvablock) {
				sleep(tv, audioblock, nil);
				continue;
			}
			break;
		}

		print("uablock %ld, bnum %ld, boffs %d, nbytes %d, tvablock %ld\n",
			uablock, bnum, boffs, nbytes, tvablock);
		src = tv->abuf + ((uablock + bnum) % tv->nablocks) * tv->absize;
		print("copying from %#p (abuf %#p), nbytes %d (block %ld.%ld)\n",
			src + boffs, tv->abuf, nbytes, uablock, bnum);

		memmove(a, src + boffs, nbytes);
		decref(&tv->aref);

		uablock += (boffs + nbytes) % tv->absize;
		c->aux = (void*)uablock;

		return nbytes;
	}

	case Qctl: {
		char str[128];

		tv = &tvs[DEV(c->qid)];
		snprint(str, sizeof str, "%dx%dx%d %s channel %d %s\n",
			ntsc_hactive, ntsc_vactive, getbitspp(tv),
			getcolormode(tv->cfmt), tv->channel, tv->ainfo);
		return readstr(offset, a, strlen(str) + 1, str);
	}

	case Qregs:
		if (offset == 0) {
			Bt848 *bt848;
			int i;

			tv = &tvs[DEV(c->qid)];
			bt848 = tv->bt848;

			e = regs + sizeof(regs);
			p = regs;
			for (i = 0; i < 0x300 >> 2; i++)
				p = seprint(p, e, "%.3X %.8ulX\n", i << 2,
					((ulong *)bt848)[i]);
			if (tv->bt878) {
				bt848 = tv->bt878;

				for (i = 0; i < 0x300 >> 2; i++)
					p = seprint(p, e, "%.3X %.8ulX\n",
						i << 2, ((ulong *)bt848)[i]);
			}

			regslen = p - regs;
		}

		if (offset >= regslen)
			return 0;
		if (offset + n > regslen)
			n = regslen - offset;

		return readstr(offset, a, n, &regs[offset]);

	default:
		n = 0;
		break;
	}
	return n;
}

static long
tvwrite(Chan *c, void *a, long n, vlong)
{
	Cmdbuf *cb;
	Cmdtab *ct;
	Tv *tv;

	tv = &tvs[DEV(c->qid)];
	switch(TYPE(c->qid)) {
	case Qctl:
		cb = parsecmd(a, n);
		if(waserror()){
			free(cb);
			nexterror();
		}
		ct = lookupcmd(cb, tvctlmsg, nelem(tvctlmsg));
		switch (ct->index) {
		case CMvstart:
			vstart(tv, (int)strtol(cb->f[1], (char **)nil, 0),
				ntsc_hactive, ntsc_vactive, ntsc_hactive);
			break;

		case CMastart:
			astart(tv, cb->f[1], (uint)strtol(cb->f[2], (char **)nil, 0),
				(uint)strtol(cb->f[3], (char **)nil, 0),
				(uint)strtol(cb->f[4], (char **)nil, 0));
			break;

		case CMastop:
			astop(tv);
			break;

		case CMvgastart:
			vgastart(tv, strtoul(cb->f[1], (char **)nil, 0),
				(int)strtoul(cb->f[2], (char **)nil, 0));
			break;

		case CMvstop:
			vstop(tv);
			break;

		case CMchannel:
			frequency(tv, (int)strtol(cb->f[1], (char **)nil, 0),
				(int)strtol(cb->f[2], (char **)nil, 0));
			break;

		case CMcolormode:
			colormode(tv, cb->f[1]);
			break;

		case CMvolume:
			if (!tv->msp)
				error("#V: No volume control");

			mspvolume(tv, 0, (int)strtol(cb->f[1], (char **)nil, 0),
				(int)strtol(cb->f[2], (char **)nil, 0));
			break;

		case CMmute:
			if (!tv->msp)
				error("#V: No volume control");

			mspvolume(tv, 1, 0, 0);
			break;
		}
		poperror();
		free(cb);
		break;

	default:
		error(Eio);
	}
	return n;
}

Dev tvdevtab = {
	'V',
	"tv",

	devreset,
	tvinit,
	devshutdown,
	tvattach,
	tvwalk,
	tvstat,
	tvopen,
	devcreate,
	tvclose,
	tvread,
	devbread,
	tvwrite,
	devbwrite,
	devremove,
	devwstat,
};

static void
tvinterrupt(Ureg *, Tv *tv)
{
	Bt848 *bt848 = tv->bt848, *bt878 = tv->bt878;

	for (;;) {
		ulong vstat, astat;
		uchar fnum;

		vstat = bt848->intstat;
		fnum = (vstat >> intstat_riscstatshift) & 0xf;
		vstat &= bt848->intmask;

		if (bt878)
			astat = bt878->intstat & bt878->intmask;
		else
			astat = 0;

		if (vstat == 0 && astat == 0)
			break;

		if (astat)
			print("vstat %.8luX, astat %.8luX\n", vstat, astat);

		bt848->intstat = vstat;
		if (bt878)
			bt878->intstat = astat;

		if ((vstat & intstat_fmtchg) == intstat_fmtchg) {
			iprint("int: fmtchg\n");
			vstat &= ~intstat_fmtchg;
		}

		if ((vstat & intstat_vpress) == intstat_vpress) {
//			iprint("int: vpress\n");
			vstat &= ~intstat_vpress;
		}

		if ((vstat & intstat_vsync) == intstat_vsync)
			vstat &= ~intstat_vsync;

		if ((vstat & intstat_scerr) == intstat_scerr) {
			iprint("int: scerr\n");
			bt848->gpiodmactl &=
				~(gpiodmactl_riscenable|gpiodmactl_fifoenable);
			bt848->gpiodmactl |= gpiodmactl_fifoenable;
			bt848->gpiodmactl |= gpiodmactl_riscenable;
			vstat &= ~intstat_scerr;
		}

		if ((vstat & intstat_risci) == intstat_risci) {
			tv->lvframe = fnum;
			vstat &= ~intstat_risci;
		}

		if ((vstat & intstat_ocerr) == intstat_ocerr) {
			iprint("int: ocerr\n");
			vstat &= ~intstat_ocerr;
		}

		if ((vstat & intstat_fbus) == intstat_fbus) {
			iprint("int: fbus\n");
			vstat &= ~intstat_fbus;
		}

		if (vstat)
			iprint("int: (v) ignored interrupts %.8ulX\n", vstat);

		if ((astat & intstat_risci) == intstat_risci) {
			tv->narblocks++;
			if ((tv->narblocks % 100) == 0)
				print("a");
			wakeup(tv);
			astat &= ~intstat_risci;
		}

		if ((astat & intstat_fdsr) == intstat_fdsr) {
			iprint("int: (a) fdsr\n");
			bt848->gpiodmactl &=
				~(gpiodmactl_acapenable |
					gpiodmactl_riscenable | gpiodmactl_fifoenable);
			astat &= ~intstat_fdsr;
		}

		if (astat)
			iprint("int: (a) ignored interrupts %.8ulX\n", astat);
	}
}

static int
i2cread(Tv *tv, uchar off, uchar *v)
{
	Bt848 *bt848 = tv->bt848;
	ulong intstat;
	int i;

	bt848->intstat	= intstat_i2cdone;
	bt848->i2c = (off << 24) | tv->i2ccmd;

	intstat = -1;
	for (i = 0; i != 1000; i++) {
		if ((intstat = bt848->intstat) & intstat_i2cdone)
			break;
		microdelay(1000);
	}

	if (i == 1000) {
		print("i2cread: timeout\n");
		return 0;
	}

	if ((intstat & intstat_i2crack) == 0)
		return 0;

	*v = bt848->i2c >> 8;
	return 1;
}

static int
i2cwrite(Tv *tv, uchar addr, uchar sub, uchar data, int both)
{
	Bt848 *bt848 = tv->bt848;
	ulong intstat, d;
	int i;

	bt848->intstat	= intstat_i2cdone;
	d = (addr << 24) | (sub << 16) | tv->i2ccmd;
	if (both)
		d |= (data << 8) | i2c_bt848w3b;
	bt848->i2c = d;

	intstat = 0;
	for (i = 0; i != 1000; i++) {
		if ((intstat = bt848->intstat) & intstat_i2cdone)
			break;
		microdelay(1000);
	}

	if (i == i2c_timeout) {
		print("i2cwrite: timeout\n");
		return 0;
	}

	if ((intstat & intstat_i2crack) == 0)
		return 0;

	return 1;
}

static ulong *
riscpacked(ulong pa, int fnum, int w, int h, int stride, ulong **lastjmp)
{
	ulong *p, *pbase;
	int i;

	pbase = p = (ulong *)malloc((h + 6) * 2 * sizeof(ulong));
	assert(p);

	assert(w <= 0x7FF);

	*p++ = riscsync | riscsync_resync | riscsync_vre;
	*p++ = 0;

	*p++ = riscsync | riscsync_fm1;
	*p++ = 0;

	for (i = 0; i != h / 2; i++) {
		*p++ = riscwrite | w | riscwrite_sol | riscwrite_eol;
		*p++ = pa + i * 2 * stride;
	}

	*p++ = riscsync | riscsync_resync | riscsync_vro;
	*p++ = 0;

	*p++ = riscsync | riscsync_fm1;
	*p++ = 0;

	for (i = 0; i != h / 2; i++) {
		*p++ = riscwrite | w | riscwrite_sol | riscwrite_eol;
		*p++ = pa + (i * 2 + 1) * stride;
	}

	/* reset status.  you really need two instructions ;-(. */
	*p++ = riscjmp | (0xf << risclabelshift_reset);
	*p++ = PADDR(p);
	*p++ = riscjmp | riscirq | (fnum << risclabelshift_set);
	*lastjmp = p;

	return pbase;
}

static ulong *
riscplanar411(ulong pa, int fnum, int w, int h, ulong **lastjmp)
{
	ulong *p, *pbase, Cw, Yw, Ch;
	uchar *Ybase, *Cbbase, *Crbase;
	int i, bitspp;

	bitspp = 6;
	assert(w * bitspp / 8 <= 0x7FF);
	pbase = p = (ulong *)malloc((h + 6) * 5 * sizeof(ulong));
	assert(p);

	Yw = w;
	Ybase = (uchar *)pa;
	Cw = w >> 1;
	Ch = h >> 1;
	Cbbase = Ybase + Yw * h;
	Crbase = Cbbase + Cw * Ch;

	*p++ = riscsync | riscsync_resync | riscsync_vre;
	*p++ = 0;

	*p++ = riscsync | riscsync_fm3;
	*p++ = 0;

	for (i = 0; i != h / 2; i++) {
		*p++ = riscwrite123 | Yw | riscwrite_sol | riscwrite_eol;
		*p++ = (Cw << 16) | Cw;
		*p++ = (ulong)(Ybase + i * 2 * Yw);
		*p++ = (ulong)(Cbbase + i * Cw);	/* Do not interlace */
		*p++ = (ulong)(Crbase + i * Cw);
	}

	*p++ = riscsync | riscsync_resync | riscsync_vro;
	*p++ = 0;

	*p++ = riscsync | riscsync_fm3;
	*p++ = 0;

	for (i = 0; i != h / 2; i++) {
		*p++ = riscwrite1s23 | Yw | riscwrite_sol | riscwrite_eol;
		*p++ = (Cw << 16) | Cw;
		*p++ = (ulong)(Ybase + (i * 2 + 1) * Yw);
	}

	/* reset status.  you really need two instructions ;-(. */
	*p++ = riscjmp | (0xf << risclabelshift_reset);
	*p++ = PADDR(p);
	*p++ = riscjmp | riscirq | (fnum << risclabelshift_set);
	*lastjmp = p;

	return pbase;
}

static ulong *
riscplanar422(ulong pa, int fnum, int w, int h, ulong **lastjmp)
{
	ulong *p, *pbase, Cw, Yw;
	uchar *Ybase, *Cbbase, *Crbase;
	int i, bpp;

	bpp = 2;
	assert(w * bpp <= 0x7FF);
	pbase = p = (ulong *)malloc((h + 6) * 5 * sizeof(ulong));
	assert(p);

	Yw = w;
	Ybase = (uchar *)pa;
	Cw = w >> 1;
	Cbbase = Ybase + Yw * h;
	Crbase = Cbbase + Cw * h;

	*p++ = riscsync | riscsync_resync | riscsync_vre;
	*p++ = 0;

	*p++ = riscsync | riscsync_fm3;
	*p++ = 0;

	for (i = 0; i != h / 2; i++) {
		*p++ = riscwrite123 | Yw | riscwrite_sol | riscwrite_eol;
		*p++ = (Cw << 16) | Cw;
		*p++ = (ulong)(Ybase + i * 2 * Yw);
		*p++ = (ulong)(Cbbase + i * 2 * Cw);
		*p++ = (ulong)(Crbase + i * 2 * Cw);
	}

	*p++ = riscsync | riscsync_resync | riscsync_vro;
	*p++ = 0;

	*p++ = riscsync | riscsync_fm3;
	*p++ = 0;

	for (i = 0; i != h / 2; i++) {
		*p++ = riscwrite123 | Yw | riscwrite_sol | riscwrite_eol;
		*p++ = (Cw << 16) | Cw;
		*p++ = (ulong)(Ybase + (i * 2 + 1) * Yw);
		*p++ = (ulong)(Cbbase + (i * 2 + 1) * Cw);
		*p++ = (ulong)(Crbase + (i * 2 + 1) * Cw);
	}

	/* reset status.  you really need two instructions ;-(. */
	*p++ = riscjmp | (0xf << risclabelshift_reset);
	*p++ = PADDR(p);
	*p++ = riscjmp | riscirq | (fnum << risclabelshift_set);
	*lastjmp = p;

	return pbase;
}

static ulong *
riscaudio(ulong pa, int nblocks, int bsize)
{
	ulong *p, *pbase;
	int i;

	pbase = p = (ulong *)malloc((nblocks + 3) * 2 * sizeof(ulong));
	assert(p);

	*p++ = riscsync|riscsync_fm1;
	*p++ = 0;

	for (i = 0; i != nblocks; i++) {
		*p++ = riscwrite | riscwrite_sol | riscwrite_eol | bsize | riscirq |
			((i & 0xf) << risclabelshift_set) |
			((~i & 0xf) << risclabelshift_reset);
		*p++ = pa + i * bsize;
	}

	*p++ = riscsync | riscsync_vro;
	*p++ = 0;
	*p++ = riscjmp;
	*p++ = PADDR(pbase);
	USED(p);

	return pbase;
}


static void
vactivate(Tv *tv, Frame *frames, int nframes)
{
	Bt848 *bt848 = tv->bt848;

	ilock(tv);
	if (tv->frames) {
		iunlock(tv);
		error(Einuse);
	}
	poperror();

	tv->frames = frames;
	tv->nframes = nframes;

	bt848->riscstrtadd = PADDR(tv->frames[0].fstart);
	bt848->capctl |= capctl_captureodd|capctl_captureeven;
	bt848->gpiodmactl |= gpiodmactl_fifoenable;
	bt848->gpiodmactl |= gpiodmactl_riscenable;

	iunlock(tv);
}

static void
vstart(Tv *tv, int nframes, int w, int h, int stride)
{
	Frame *frames;
	int bitspp, i, bpf;

	if (nframes >= 0x10)
		error(Ebadarg);

	bitspp = getbitspp(tv);
	bpf = w * h * bitspp / 8;

	/* Add one as a spare. */
	frames = (Frame *)malloc(nframes * sizeof(Frame));
	assert(frames);
	if (waserror()) {
		for (i = 0; i != nframes; i++)
			if (frames[i].fbase)
				free(frames[i].fbase);
		free(frames);
		nexterror();
	}
	memset(frames, 0, nframes * sizeof(Frame));

	for (i = 0; i != nframes; i++) {
		if ((frames[i].fbase = (uchar *)malloc(bpf)) == nil)
			error(Enomem);

		switch (tv->cfmt) {
		case colorfmt_YCbCr422:
			frames[i].fstart = riscplanar422(PADDR(frames[i].fbase),				i, w, h, &frames[i].fjmp);
			break;
		case colorfmt_YCbCr411:
			frames[i].fstart = riscplanar411(PADDR(frames[i].fbase),
				i, w, h, &frames[i].fjmp);
			break;
		case colorfmt_rgb16:
			frames[i].fstart = riscpacked(PADDR(frames[i].fbase), i,
				w * bitspp / 8, h, stride * bitspp / 8,
				&frames[i].fjmp);
			break;
		default:
			panic("vstart: Unsupport colorformat");
		}
	}

	for (i = 0; i != nframes; i++)
		*frames[i].fjmp = PADDR(i == nframes - 1? frames[0].fstart:
			frames[i + 1].fstart);

	vactivate(tv, frames, nframes);
}

static void
astart(Tv *tv, char *input, uint rate, uint nab, uint nasz)
{
	Bt848 *bt878 = tv->bt878;
	ulong *arisc;
	int selector;
	uchar *abuf;
	int s, d;

	if (bt878 == nil || tv->amux == nil)
		error("#V: Card does not support audio");

	selector = 0;
	if (!strcmp(input, "tv"))
		selector = asel_tv;
	else if (!strcmp(input, "radio"))
		selector = asel_radio;
	else if (!strcmp(input, "mic"))
		selector = asel_mic;
	else if (!strcmp(input, "smxc"))
		selector = asel_smxc;
	else
		error("#V: Invalid input");

	if (nasz > 0xfff)
		error("#V: Audio block size too big (max 0xfff)");

	abuf = (uchar *)malloc(nab * nasz * sizeof(uchar));
	assert(abuf);
	arisc = riscaudio(PADDR(abuf), nab, nasz);

	ilock(tv);
	if (tv->arisc) {
		iunlock(tv);
		free(abuf);
		free(arisc);
		error(Einuse);
	}

	tv->arisc = arisc;
	tv->abuf = abuf;
	tv->nablocks = nab;
	tv->absize = nasz;

	bt878->riscstrtadd = PADDR(tv->arisc);
	bt878->packetlen = (nab << 16) | nasz;
	bt878->intmask = intstat_scerr | intstat_ocerr | intstat_risci |
			intstat_pabort | intstat_riperr | intstat_pperr |
			intstat_fdsr | intstat_ftrgt | intstat_fbus;

	/* Assume analog, 16bpp */
	for (s = 0; s < 16; s++)
		if (rate << s > Hwbase_ad * 4 / 15)
			break;
	for (d = 15; d >= 4; d--)
		if (rate << s < Hwbase_ad * 4 / d)
			break;

	print("astart: sampleshift %d, decimation %d\n", s, d);

	tv->narblocks = 0;
	bt878->gpiodmactl = gpiodmactl_fifoenable |
		gpiodmactl_riscenable | gpiodmactl_acapenable |
		gpiodmactl_daes2 |		/* gpiodmactl_apwrdn | */
		gpiodmactl_daiomda | d << 8 | 9 << 28 | selector << 24;
	print("dmactl %.8ulX\n", bt878->gpiodmactl);
	iunlock(tv);
}

static void
astop(Tv *tv)
{
	Bt848 *bt878 = tv->bt878;

	ilock(tv);
	if (tv->aref.ref > 0) {
		iunlock(tv);
		error(Einuse);
	}

	if (tv->abuf) {
		bt878->gpiodmactl &= ~gpiodmactl_riscenable;
		bt878->gpiodmactl &= ~gpiodmactl_fifoenable;

		free(tv->abuf);
		tv->abuf = nil;
		free(tv->arisc);
		tv->arisc = nil;
	}
	iunlock(tv);
}

static void
vgastart(Tv *tv, ulong pa, int stride)
{
	Frame *frame;

	frame = (Frame *)malloc(sizeof(Frame));
	assert(frame);
	if (waserror()) {
		free(frame);
		nexterror();
	}

	frame->fbase = nil;
	frame->fstart = riscpacked(pa, 0, ntsc_hactive * getbitspp(tv) / 8,
		ntsc_vactive, stride * getbitspp(tv) / 8, &frame->fjmp);
	*frame->fjmp = PADDR(frame->fstart);

	vactivate(tv, frame, 1);
}

static void
vstop(Tv *tv)
{
	Bt848 *bt848 = tv->bt848;

	ilock(tv);
	if (tv->fref.ref > 0) {
		iunlock(tv);
		error(Einuse);
	}

	if (tv->frames) {
		int i;

		bt848->gpiodmactl &= ~gpiodmactl_riscenable;
		bt848->gpiodmactl &= ~gpiodmactl_fifoenable;
		bt848->capctl &= ~(capctl_captureodd|capctl_captureeven);

		for (i = 0; i != tv->nframes; i++)
			if (tv->frames[i].fbase)
				free(tv->frames[i].fbase);
		free(tv->frames);
		tv->frames = nil;
	}
	iunlock(tv);
}

static long hrcfreq[] = {		/* HRC CATV frequencies */
	    0,  7200,  5400,  6000,  6600,  7800,  8400, 17400,
	18000, 18600, 19200, 19800, 20400, 21000, 12000, 12600,
	13200, 13800, 14400, 15000, 15600, 16200, 16800, 21600,
	22200, 22800, 23400, 24000, 24600, 25200, 25800, 26400,
	27000, 27600, 28200, 28800, 29400, 30000, 30600, 31200,
	31800, 32400, 33000, 33600, 34200, 34800, 35400, 36000,
	36600, 37200, 37800, 38400, 39000, 39600, 40200, 40800,
	41400, 42000, 42600, 43200, 43800, 44400, 45000, 45600,
	46200, 46800, 47400, 48000, 48600, 49200, 49800, 50400,
	51000, 51600, 52200, 52800, 53400, 54000, 54600, 55200,
	55800, 56400, 57000, 57600, 58200, 58800, 59400, 60000,
	60600, 61200, 61800, 62400, 63000, 63600, 64200,  9000,
	 9600, 10200, 10800, 11400, 64800, 65400, 66000, 66600,
	67200, 67800, 68400, 69000, 69600, 70200, 70800, 71400,
	72000, 72600, 73200, 73800, 74400, 75000, 75600, 76200,
	76800, 77400, 78000, 78600, 79200, 79800,
};

static void
frequency(Tv *tv, int channel, int finetune)
{
	Tuner *tuner = tv->tuner;
	long freq;
	ushort div;
	uchar cfg;

	if (channel < 0 || channel > nelem(hrcfreq))
		error(Ebadarg);

	freq = (hrcfreq[channel] * Freqmultiplier) / 100;

	if (freq < tuner->freq_vhfh)
		cfg = tuner->VHF_L;
	else if (freq < tuner->freq_uhf)
		cfg = tuner->VHF_H;
	else
		cfg = tuner->UHF;

	div = (freq + tuner->offs + finetune) & 0x7fff;

	if (!i2cwrite(tv, tv->i2ctuneraddr, (div >> 8) & 0x7f, div, 1))
		error(Eio);

	if (!i2cwrite(tv, tv->i2ctuneraddr, tuner->cfg, cfg, 1))
		error(Eio);

	tv->channel = channel;
	if (tv->msp)
		msptune(tv);
}

static struct {
	char	*cmode;
	ulong	realmode;
	ulong	cbits;
} colormodes[] = {
	{ "RGB16",	colorfmt_rgb16,		colorfmt_rgb16, },
	{ "YCbCr422",	colorfmt_YCbCr422,	colorfmt_YCbCr422, },
	{ "YCbCr411",	colorfmt_YCbCr411,	colorfmt_YCbCr422, },
};

static void
colormode(Tv *tv, char *colormode)
{
	Bt848 *bt848 = tv->bt848;
	int i;

	for (i = 0; i != nelem(colormodes); i++)
		if (!strcmp(colormodes[i].cmode, colormode))
			break;

	if (i == nelem(colormodes))
		error(Ebadarg);

	tv->cfmt = colormodes[i].realmode;
	bt848->colorfmt = colormodes[i].cbits;
}

static int
getbitspp(Tv *tv)
{
	switch (tv->cfmt) {
	case colorfmt_rgb16:
	case colorfmt_YCbCr422:
		return 16;
	case colorfmt_YCbCr411:
		return 12;
	default:
		error("getbitspp: Unsupport color format\n");
	}
	return -1;
}

static char *
getcolormode(ulong cmode)
{
	switch (cmode) {
	case colorfmt_rgb16:
		return "RGB16";
	case colorfmt_YCbCr411:
		return "YCbCr411";
	case colorfmt_YCbCr422:
		return (cmode == colorfmt_YCbCr422)? "YCbCr422": "YCbCr411";
	default:
		error("getcolormode: Unsupport color format\n");
	}
	return nil;
}

static void
i2c_set(Tv *tv, int scl, int sda)
{
	Bt848 *bt848 = tv->bt848;
	ulong d;

	bt848->i2c = (scl << 1) | sda;
	d = bt848->i2c;
	USED(d);
	microdelay(i2c_delay);
}

static uchar
i2c_getsda(Tv *tv)
{
	Bt848 *bt848 = tv->bt848;

	return bt848->i2c & i2c_sda;
}

static void
i2c_start(Tv *tv)
{
	i2c_set(tv, 0, 1);
	i2c_set(tv, 1, 1);
	i2c_set(tv, 1, 0);
	i2c_set(tv, 0, 0);
}

static void
i2c_stop(Tv *tv)
{
	i2c_set(tv, 0, 0);
	i2c_set(tv, 1, 0);
	i2c_set(tv, 1, 1);
}

static void
i2c_bit(Tv *tv, int sda)
{
	i2c_set(tv, 0, sda);
	i2c_set(tv, 1, sda);
	i2c_set(tv, 0, sda);
}

static int
i2c_getack(Tv *tv)
{
	int ack;

	i2c_set(tv, 0, 1);
	i2c_set(tv, 1, 1);
	ack = i2c_getsda(tv);
	i2c_set(tv, 0, 1);
	return ack;
}

static int
i2c_wr8(Tv *tv, uchar d, int wait)
{
	int i, ack;

	i2c_set(tv, 0, 0);
	for (i = 0; i != 8; i++) {
		i2c_bit(tv, (d & 0x80)? 1: 0);
		d <<= 1;
	}
	if (wait)
		microdelay(wait);

	ack = i2c_getack(tv);
	return ack == 0;
}

static uchar
i2c_rd8(Tv *tv, int lastbyte)
{
	int i;
	uchar d;

	d = 0;
	i2c_set(tv, 0, 1);
	for (i = 0; i != 8; i++) {
		i2c_set(tv, 1, 1);
		d <<= 1;
		if (i2c_getsda(tv))
			d |= 1;
		i2c_set(tv, 0, 1);
	}

	i2c_bit(tv, lastbyte? 1: 0);
	return d;
}

static int
mspsend(Tv *tv, uchar *cmd, int ncmd)
{
	int i, j, delay;

	for (i = 0; i != 3; i++) {
		delay = 2000;

		i2c_start(tv);
		for (j = 0; j != ncmd; j++) {
			if (!i2c_wr8(tv, cmd[j], delay))
				break;
			delay = 0;
		}
		i2c_stop(tv);

		if (j == ncmd)
			return 1;

		microdelay(10000);
		print("mspsend: retrying\n");
	}

	return 0;
}

static int
mspwrite(Tv *tv, uchar sub, ushort reg, ushort v)
{
	uchar b[6];

	b[0] = i2c_msp3400;
	b[1] = sub;
	b[2] = reg >> 8;
	b[3] = reg;
	b[4] = v >> 8;
	b[5] = v;
	return mspsend(tv, b, sizeof b);
}

static int
mspread(Tv *tv, uchar sub, ushort reg, ushort *data)
{
	uchar b[4];
	int i;

	b[0] = i2c_msp3400;
	b[1] = sub;
	b[2] = reg >> 8;
	b[3] = reg;

	for (i = 0; i != 3; i++) {
		i2c_start(tv);
		if (!i2c_wr8(tv, b[0], 2000) ||
			!i2c_wr8(tv, b[1] | 1, 0) ||
			!i2c_wr8(tv, b[2], 0) ||
			!i2c_wr8(tv, b[3], 0)) {

			i2c_stop(tv);
			microdelay(10000);
			print("retrying\n");
			continue;
		}

		i2c_start(tv);

		if (!i2c_wr8(tv, b[0] | 1, 2000)) {
			i2c_stop(tv);
			continue;
		}

		*data  = i2c_rd8(tv, 0) << 8;
		*data |= i2c_rd8(tv, 1);
		i2c_stop(tv);
		return 1;
	}
	return 0;
}

static uchar mspt_reset[] = { i2c_msp3400, 0, 0x80, 0 };
static uchar mspt_on[] = { i2c_msp3400, 0, 0, 0 };

static int
mspreset(Tv *tv)
{
	ushort v, p;
	Bt848 *bt848 = tv->bt848;
	ulong b;

	b = 1 << 5;
	gpioenable(tv, ~b, b);
	gpiowrite(tv, ~b, 0);
	microdelay(2500);
	gpiowrite(tv, ~b, b);

	bt848->i2c = 0x80;

	microdelay(2000);
	mspsend(tv, mspt_reset, sizeof mspt_reset);

	microdelay(2000);
	if (!mspsend(tv, mspt_on, sizeof mspt_on)) {
		print("#V: Cannot find MSP34x5G on the I2C bus (on)\n");
		return 0;
	}
	microdelay(2000);

	if (!mspread(tv, msp_bbp, 0x001e, &v)) {
		print("#V: Cannot read MSP34xG5 chip version\n");
		return 0;
	}

	if (!mspread(tv, msp_bbp, 0x001f, &p)) {
		print("#V: Cannot read MSP34xG5 product code\n");
		return 0;
	}

	print("#V: MSP34%dg ROM %.d, %d.%d\n",
		(uchar)(p >> 8), (uchar)p, (uchar)(v >> 8), (uchar)v);

	tv->msp = 1;
	return 1;
}

static void
mspvolume(Tv *tv, int mute, int l, int r)
{
	short v, d;
	ushort b;

	if (mute) {
		v = 0;
		b = 0;
	}
	else {
		tv->aleft = l;
		tv->aright = r;
		d = v = max(l, r);
		if (d == 0)
			d++;
		b = ((r - l) * 0x7f) / d;
	}

	mspwrite(tv, msp_bbp, 0, v << 8);
	mspwrite(tv, msp_bbp, 7, v? 0x4000: 0);
	mspwrite(tv, msp_bbp, 1, b << 8);
}

static char *
mspaformat(int f)
{
	switch (f) {
	case 0:
		return "unknown";
	case 2:
	case 0x20:
	case 0x30:
		return "M-BTSC";
	case 3:
		return "B/G-FM";
	case 4:
	case 9:
	case 0xB:
		return "L-AM/NICAM D/Kn";
	case 8:
		return "B/G-NICAM";
	case 0xA:
		return "I";
	case 0x40:
		return "FM-Radio";
	}
	return "unknown format";
}


static void
msptune(Tv *tv)
{
	ushort d, s, nicam;
	int i;

	mspvolume(tv, 1, 0, 0);
	if (!mspwrite(tv, msp_dem, 0x0030, 0x2033))
		error("#V: Cannot set MODUS register");

	if (!mspwrite(tv, msp_bbp, 0x0008, 0x0320))
		error("#V: Cannot set loadspeaker input");

	if (!mspwrite(tv, msp_dem, 0x0040, 0x0001))
		error("#V: Cannot set I2S clock freq");
	if (!mspwrite(tv, msp_bbp, 0x000d, 0x1900))
		error("#V: Cannot set SCART prescale");
	if (!mspwrite(tv, msp_bbp, 0x000e, 0x2403))
		error("#V: Cannot set FM/AM prescale");
	if (!mspwrite(tv, msp_bbp, 0x0010, 0x5a00))
		error("#V: Cannot set NICAM prescale");
	if (!mspwrite(tv, msp_dem, 0x0020, 0x0001))
		error("#V: Cannot start auto detect");

	for (d = (ushort)-1, i = 0; i != 10; i++) {
		if (!mspread(tv, msp_dem, 0x007e, &d))
			error("#V: Cannot get autodetect info MSP34xG5");

		if (d == 0 || d < 0x800)
			break;
		delay(50);
	}

	if (!mspread(tv, msp_dem, 0x0200, &s))
		error("#V: Cannot get status info MSP34xG5");

	mspvolume(tv, 0, tv->aleft, tv->aright);

	nicam = ((s >> 4) & 2) | ((s >> 9) & 1);
	snprint(tv->ainfo, sizeof tv->ainfo, "%s %s %s",
		mspaformat(d), (s & (1 << 6))? "stereo": "mono",
		nicamstate[nicam]);
}

static void
i2cscan(Tv *tv)
{
	int i, ack;

	for (i = 0; i < 0x100; i += 2) {
		i2c_start(tv);
		ack = i2c_wr8(tv, i, 0);
		i2c_stop(tv);
		if (ack)
			print("i2c device @%.2uX\n", i);
	}

	for (i = 0xf0; i != 0xff; i++) {
		i2c_start(tv);
		ack = i2c_wr8(tv, i, 0);
		i2c_stop(tv);
		if (ack)
			print("i2c device may be at @%.2uX\n", i);
	}
}

static void
gpioenable(Tv *tv, ulong mask, ulong data)
{
	Bt848 *bt848 = tv->bt848;

	bt848->gpioouten = (bt848->gpioouten & mask) | data;
}

static void
gpiowrite(Tv *tv, ulong mask, ulong data)
{
	Bt848 *bt848 = tv->bt848;

	bt848->gpiodata[0] = (bt848->gpiodata[0] & mask) | data;
}

static void
alteraoutput(Tv *tv)
{
	if (tv->gpiostate == Gpiooutput)
		return;

	gpioenable(tv, ~0xffffff, 0x56ffff);
	microdelay(10);
	tv->gpiostate = Gpiooutput;
}

static void
alterainput(Tv *tv)
{
	if (tv->gpiostate == Gpioinput)
		return;

	gpioenable(tv, ~0xffffff, 0x570000);
	microdelay(10);
	tv->gpiostate = Gpioinput;
}

static void
alterareg(Tv *tv, ulong reg)
{
	if (tv->alterareg == reg)
		return;

	gpiowrite(tv, ~0x56ffff, (reg & 0x54ffff) | tv->alteraclock);
	microdelay(10);
	tv->alterareg = reg;
}

static void
alterawrite(Tv *tv, ulong reg, ushort data)
{
	alteraoutput(tv);
	alterareg(tv, reg);

	tv->alteraclock ^= 0x20000;
	gpiowrite(tv, ~0x56ffff, (reg & 0x540000) | data | tv->alteraclock);
	microdelay(10);
}

static void
alteraread(Tv *tv, int reg, ushort *data)
{
	Bt848 *bt848 = tv->bt848;

	if (tv->alterareg != reg) {
		alteraoutput(tv);
		alterareg(tv, reg);
	}
	else {
		gpioenable(tv, ~0xffffff, 0x560000);
		microdelay(10);
	}

	alterainput(tv);
	gpiowrite(tv, ~0x570000, (reg & 0x560000) | tv->alteraclock);
	microdelay(10);
	*data = (ushort)bt848->gpiodata[0];
	microdelay(10);
}

static void
kfirloadu(Tv *tv, uchar *u, int ulen)
{
	Bt848 *bt848 = tv->bt848;
	int i, j;

	ilock(&tv->kfirlock);
	bt848->gpioouten &= 0xff000000;
	bt848->gpioouten |= gpio_altera_data |
		gpio_altera_clock | gpio_altera_nconfig;
	bt848->gpiodata[0] &= 0xff000000;
	microdelay(10);
	bt848->gpiodata[0] |= gpio_altera_nconfig;
	microdelay(10);

	/* Download the microcode */
	for (i = 0; i != ulen; i++)
		for (j = 0; j != 8; j++) {
			bt848->gpiodata[0] &= ~(gpio_altera_clock|gpio_altera_data);
			if (u[i] & 1)
				bt848->gpiodata[0] |= gpio_altera_data;
			bt848->gpiodata[0] |= gpio_altera_clock;
			u[i] >>= 1;
		}
	bt848->gpiodata[0] &= ~gpio_altera_clock;
	microdelay(100);

	/* Initialize. */
	for (i = 0; i != 30; i++) {
		bt848->gpiodata[0] &= ~gpio_altera_clock;
		bt848->gpiodata[0] |= gpio_altera_clock;
	}
	bt848->gpiodata[0] &= ~(gpio_altera_clock|gpio_altera_data);
	iunlock(&tv->kfirlock);

	tv->gpiostate = Gpioinit;
}

static void
kfirreset(Tv *tv)
{
	alterawrite(tv, 0, 0);
	microdelay(10);
	alterawrite(tv, 0x40000, 0);
	microdelay(10);
	alterawrite(tv, 0x40006, 0x80);
	microdelay(10);
	alterawrite(tv, 8, 1);
	microdelay(10);
	alterawrite(tv, 0x40004, 2);
	microdelay(10);
	alterawrite(tv, 4, 3);
	microdelay(3);
}

static int
kfirinitialize(Tv *tv)
{
	/* Initialize parameters? */

	tv->gpiostate = Gpioinit;
	tv->alterareg = -1;
	tv->alteraclock = 0x20000;
	kfirloadu(tv, hcwAMC, sizeof hcwAMC);
	kfirreset(tv);
	return 1;
}