2195 lines
44 KiB
C
2195 lines
44 KiB
C
/*
|
|
* 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, ®s[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;
|
|
}
|