Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
  * 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 "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\n");
                }
                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\n");
                }
        }

        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;
}