blob: ed8d3b0f03f65b4a881eb5a3e674bf232a681ad6 [file] [log] [blame]
/*
bttv - Bt848 frame grabber driver
Copyright (C) 1996,97,98 Ralph Metzler <rjkm@thp.uni-koeln.de>
& Marcus Metzler <mocm@thp.uni-koeln.de>
(c) 1999-2002 Gerd Knorr <kraxel@bytesex.org>
some v4l2 code lines are taken from Justin's bttv2 driver which is
(c) 2000 Justin Schoeman <justin@suntiger.ee.up.ac.za>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/init.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/kdev_t.h>
#include <asm/io.h>
#include "bttvp.h"
int bttv_num; /* number of Bt848s in use */
struct bttv bttvs[BTTV_MAX];
unsigned int bttv_debug = 0;
unsigned int bttv_verbose = 1;
unsigned int bttv_gpio = 0;
/* config variables */
#if defined(__sparc__) || defined(__powerpc__) || defined(__hppa__)
static unsigned int bigendian=1;
#else
static unsigned int bigendian=0;
#endif
static unsigned int radio[4];
static unsigned int irq_debug = 0;
static unsigned int gbuffers = 8;
static unsigned int gbufsize = 0x208000;
static int video_nr = -1;
static int radio_nr = -1;
static int vbi_nr = -1;
static unsigned int fdsr = 0;
static unsigned int gpint = 1;
/* options */
static unsigned int combfilter = 0;
static unsigned int lumafilter = 0;
static unsigned int automute = 1;
static unsigned int chroma_agc = 0;
static unsigned int adc_crush = 1;
/* API features (turn on/off stuff for testing) */
static unsigned int sloppy = 0;
static unsigned int v4l2 = 1;
/* insmod args */
MODULE_PARM(radio,"1-" __stringify(BTTV_MAX) "i");
MODULE_PARM_DESC(radio,"The TV card supports radio, default is 0 (no)");
MODULE_PARM(bigendian,"i");
MODULE_PARM_DESC(bigendian,"byte order of the framebuffer, default is native endian");
MODULE_PARM(bttv_verbose,"i");
MODULE_PARM_DESC(bttv_verbose,"verbose startup messages, default is 1 (yes)");
MODULE_PARM(bttv_gpio,"i");
MODULE_PARM_DESC(bttv_gpio,"log gpio changes, default is 0 (no)");
MODULE_PARM(bttv_debug,"i");
MODULE_PARM_DESC(bttv_debug,"debug messages, default is 0 (no)");
MODULE_PARM(irq_debug,"i");
MODULE_PARM_DESC(irq_debug,"irq handler debug messages, default is 0 (no)");
MODULE_PARM(gbuffers,"i");
MODULE_PARM_DESC(gbuffers,"number of capture buffers. range 2-32, default 8");
MODULE_PARM(gbufsize,"i");
MODULE_PARM_DESC(gbufsize,"size of the capture buffers, default is 0x208000");
MODULE_PARM(video_nr,"i");
MODULE_PARM(radio_nr,"i");
MODULE_PARM(vbi_nr,"i");
MODULE_PARM(fdsr,"i");
MODULE_PARM(gpint,"i");
MODULE_PARM(combfilter,"i");
MODULE_PARM(lumafilter,"i");
MODULE_PARM(automute,"i");
MODULE_PARM_DESC(automute,"mute audio on bad/missing video signal, default is 1 (yes)");
MODULE_PARM(chroma_agc,"i");
MODULE_PARM_DESC(chroma_agc,"enables the AGC of chroma signal, default is 0 (no)");
MODULE_PARM(adc_crush,"i");
MODULE_PARM_DESC(adc_crush,"enables the luminance ADC crush, default is 1 (yes)");
MODULE_PARM(sloppy,"i");
MODULE_PARM(v4l2,"i");
MODULE_DESCRIPTION("bttv - v4l/v4l2 driver module for bt848/878 based cards");
MODULE_AUTHOR("Ralph Metzler & Marcus Metzler & Gerd Knorr");
MODULE_LICENSE("GPL");
/* kernel args */
#ifndef MODULE
static int __init p_radio(char *str) { return bttv_parse(str,BTTV_MAX,radio); }
__setup("bttv.radio=", p_radio);
#endif
/* ----------------------------------------------------------------------- */
/* static data */
/* special timing tables from conexant... */
static u8 SRAM_Table[][60] =
{
/* PAL digital input over GPIO[7:0] */
{
45, // 45 bytes following
0x36,0x11,0x01,0x00,0x90,0x02,0x05,0x10,0x04,0x16,
0x12,0x05,0x11,0x00,0x04,0x12,0xC0,0x00,0x31,0x00,
0x06,0x51,0x08,0x03,0x89,0x08,0x07,0xC0,0x44,0x00,
0x81,0x01,0x01,0xA9,0x0D,0x02,0x02,0x50,0x03,0x37,
0x37,0x00,0xAF,0x21,0x00
},
/* NTSC digital input over GPIO[7:0] */
{
51, // 51 bytes following
0x0C,0xC0,0x00,0x00,0x90,0x02,0x03,0x10,0x03,0x06,
0x10,0x04,0x12,0x12,0x05,0x02,0x13,0x04,0x19,0x00,
0x04,0x39,0x00,0x06,0x59,0x08,0x03,0x83,0x08,0x07,
0x03,0x50,0x00,0xC0,0x40,0x00,0x86,0x01,0x01,0xA6,
0x0D,0x02,0x03,0x11,0x01,0x05,0x37,0x00,0xAC,0x21,
0x00,
},
// TGB_NTSC392 // quartzsight
// This table has been modified to be used for Fusion Rev D
{
0x2A, // size of table = 42
0x06, 0x08, 0x04, 0x0a, 0xc0, 0x00, 0x18, 0x08, 0x03, 0x24,
0x08, 0x07, 0x02, 0x90, 0x02, 0x08, 0x10, 0x04, 0x0c, 0x10,
0x05, 0x2c, 0x11, 0x04, 0x55, 0x48, 0x00, 0x05, 0x50, 0x00,
0xbf, 0x0c, 0x02, 0x2f, 0x3d, 0x00, 0x2f, 0x3f, 0x00, 0xc3,
0x20, 0x00
}
};
const struct bttv_tvnorm bttv_tvnorms[] = {
/* PAL-BDGHI */
/* max. active video is actually 922, but 924 is divisible by 4 and 3! */
/* actually, max active PAL with HSCALE=0 is 948, NTSC is 768 - nil */
{
.v4l2_id = V4L2_STD_PAL,
.name = "PAL",
.Fsc = 35468950,
.swidth = 924,
.sheight = 576,
.totalwidth = 1135,
.adelay = 0x7f,
.bdelay = 0x72,
.iform = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1),
.scaledtwidth = 1135,
.hdelayx1 = 186,
.hactivex1 = 924,
.vdelay = 0x20,
.vbipack = 255,
.sram = 0,
},{
.v4l2_id = V4L2_STD_NTSC_M,
.name = "NTSC",
.Fsc = 28636363,
.swidth = 768,
.sheight = 480,
.totalwidth = 910,
.adelay = 0x68,
.bdelay = 0x5d,
.iform = (BT848_IFORM_NTSC|BT848_IFORM_XT0),
.scaledtwidth = 910,
.hdelayx1 = 128,
.hactivex1 = 910,
.vdelay = 0x1a,
.vbipack = 144,
.sram = 1,
},{
.v4l2_id = V4L2_STD_SECAM,
.name = "SECAM",
.Fsc = 35468950,
.swidth = 924,
.sheight = 576,
.totalwidth = 1135,
.adelay = 0x7f,
.bdelay = 0xb0,
.iform = (BT848_IFORM_SECAM|BT848_IFORM_XT1),
.scaledtwidth = 1135,
.hdelayx1 = 186,
.hactivex1 = 922,
.vdelay = 0x20,
.vbipack = 255,
.sram = 0, /* like PAL, correct? */
},{
.v4l2_id = V4L2_STD_PAL_Nc,
.name = "PAL-Nc",
.Fsc = 28636363,
.swidth = 640,
.sheight = 576,
.totalwidth = 910,
.adelay = 0x68,
.bdelay = 0x5d,
.iform = (BT848_IFORM_PAL_NC|BT848_IFORM_XT0),
.scaledtwidth = 780,
.hdelayx1 = 130,
.hactivex1 = 734,
.vdelay = 0x1a,
.vbipack = 144,
.sram = -1,
},{
.v4l2_id = V4L2_STD_PAL_M,
.name = "PAL-M",
.Fsc = 28636363,
.swidth = 640,
.sheight = 480,
.totalwidth = 910,
.adelay = 0x68,
.bdelay = 0x5d,
.iform = (BT848_IFORM_PAL_M|BT848_IFORM_XT0),
.scaledtwidth = 780,
.hdelayx1 = 135,
.hactivex1 = 754,
.vdelay = 0x1a,
.vbipack = 144,
.sram = -1,
},{
.v4l2_id = V4L2_STD_PAL_N,
.name = "PAL-N",
.Fsc = 35468950,
.swidth = 768,
.sheight = 576,
.totalwidth = 1135,
.adelay = 0x7f,
.bdelay = 0x72,
.iform = (BT848_IFORM_PAL_N|BT848_IFORM_XT1),
.scaledtwidth = 944,
.hdelayx1 = 186,
.hactivex1 = 922,
.vdelay = 0x20,
.vbipack = 144,
.sram = -1,
},{
.v4l2_id = V4L2_STD_NTSC_M_JP,
.name = "NTSC-JP",
.Fsc = 28636363,
.swidth = 640,
.sheight = 480,
.totalwidth = 910,
.adelay = 0x68,
.bdelay = 0x5d,
.iform = (BT848_IFORM_NTSC_J|BT848_IFORM_XT0),
.scaledtwidth = 780,
.hdelayx1 = 135,
.hactivex1 = 754,
.vdelay = 0x16,
.vbipack = 144,
.sram = -1,
}
};
const int BTTV_TVNORMS = (sizeof(bttv_tvnorms)/sizeof(struct bttv_tvnorm));
/* ----------------------------------------------------------------------- */
/* bttv format list
packed pixel formats must come first */
const struct bttv_format bttv_formats[] = {
{
.name = "8 bpp, gray",
.palette = VIDEO_PALETTE_GREY,
.fourcc = V4L2_PIX_FMT_GREY,
.btformat = BT848_COLOR_FMT_Y8,
.depth = 8,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "8 bpp, dithered color",
.palette = VIDEO_PALETTE_HI240,
.fourcc = V4L2_PIX_FMT_HI240,
.btformat = BT848_COLOR_FMT_RGB8,
.depth = 8,
.flags = FORMAT_FLAGS_PACKED | FORMAT_FLAGS_DITHER,
},{
.name = "15 bpp RGB, le",
.palette = VIDEO_PALETTE_RGB555,
.fourcc = V4L2_PIX_FMT_RGB555,
.btformat = BT848_COLOR_FMT_RGB15,
.depth = 16,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "15 bpp RGB, be",
.palette = -1,
.fourcc = V4L2_PIX_FMT_RGB555X,
.btformat = BT848_COLOR_FMT_RGB15,
.btswap = 0x03, /* byteswap */
.depth = 16,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "16 bpp RGB, le",
.palette = VIDEO_PALETTE_RGB565,
.fourcc = V4L2_PIX_FMT_RGB565,
.btformat = BT848_COLOR_FMT_RGB16,
.depth = 16,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "16 bpp RGB, be",
.palette = -1,
.fourcc = V4L2_PIX_FMT_RGB565X,
.btformat = BT848_COLOR_FMT_RGB16,
.btswap = 0x03, /* byteswap */
.depth = 16,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "24 bpp RGB, le",
.palette = VIDEO_PALETTE_RGB24,
.fourcc = V4L2_PIX_FMT_BGR24,
.btformat = BT848_COLOR_FMT_RGB24,
.depth = 24,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "32 bpp RGB, le",
.palette = VIDEO_PALETTE_RGB32,
.fourcc = V4L2_PIX_FMT_BGR32,
.btformat = BT848_COLOR_FMT_RGB32,
.depth = 32,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "32 bpp RGB, be",
.palette = -1,
.fourcc = V4L2_PIX_FMT_RGB32,
.btformat = BT848_COLOR_FMT_RGB32,
.btswap = 0x0f, /* byte+word swap */
.depth = 32,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "4:2:2, packed, YUYV",
.palette = VIDEO_PALETTE_YUV422,
.fourcc = V4L2_PIX_FMT_YUYV,
.btformat = BT848_COLOR_FMT_YUY2,
.depth = 16,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "4:2:2, packed, YUYV",
.palette = VIDEO_PALETTE_YUYV,
.fourcc = V4L2_PIX_FMT_YUYV,
.btformat = BT848_COLOR_FMT_YUY2,
.depth = 16,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "4:2:2, packed, UYVY",
.palette = VIDEO_PALETTE_UYVY,
.fourcc = V4L2_PIX_FMT_UYVY,
.btformat = BT848_COLOR_FMT_YUY2,
.btswap = 0x03, /* byteswap */
.depth = 16,
.flags = FORMAT_FLAGS_PACKED,
},{
.name = "4:2:2, planar, Y-Cb-Cr",
.palette = VIDEO_PALETTE_YUV422P,
.fourcc = V4L2_PIX_FMT_YUV422P,
.btformat = BT848_COLOR_FMT_YCrCb422,
.depth = 16,
.flags = FORMAT_FLAGS_PLANAR,
.hshift = 1,
.vshift = 0,
},{
.name = "4:2:0, planar, Y-Cb-Cr",
.palette = VIDEO_PALETTE_YUV420P,
.fourcc = V4L2_PIX_FMT_YUV420,
.btformat = BT848_COLOR_FMT_YCrCb422,
.depth = 12,
.flags = FORMAT_FLAGS_PLANAR,
.hshift = 1,
.vshift = 1,
},{
.name = "4:2:0, planar, Y-Cr-Cb",
.palette = -1,
.fourcc = V4L2_PIX_FMT_YVU420,
.btformat = BT848_COLOR_FMT_YCrCb422,
.depth = 12,
.flags = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb,
.hshift = 1,
.vshift = 1,
},{
.name = "4:1:1, planar, Y-Cb-Cr",
.palette = VIDEO_PALETTE_YUV411P,
.fourcc = V4L2_PIX_FMT_YUV411P,
.btformat = BT848_COLOR_FMT_YCrCb411,
.depth = 12,
.flags = FORMAT_FLAGS_PLANAR,
.hshift = 2,
.vshift = 0,
},{
.name = "4:1:0, planar, Y-Cb-Cr",
.palette = VIDEO_PALETTE_YUV410P,
.fourcc = V4L2_PIX_FMT_YUV410,
.btformat = BT848_COLOR_FMT_YCrCb411,
.depth = 9,
.flags = FORMAT_FLAGS_PLANAR,
.hshift = 2,
.vshift = 2,
},{
.name = "4:1:0, planar, Y-Cr-Cb",
.palette = -1,
.fourcc = V4L2_PIX_FMT_YVU410,
.btformat = BT848_COLOR_FMT_YCrCb411,
.depth = 9,
.flags = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb,
.hshift = 2,
.vshift = 2,
},{
.name = "raw scanlines",
.palette = VIDEO_PALETTE_RAW,
.fourcc = -1,
.btformat = BT848_COLOR_FMT_RAW,
.depth = 8,
.flags = FORMAT_FLAGS_RAW,
}
};
const int BTTV_FORMATS = (sizeof(bttv_formats)/sizeof(struct bttv_format));
/* ----------------------------------------------------------------------- */
#define V4L2_CID_PRIVATE_CHROMA_AGC (V4L2_CID_PRIVATE_BASE + 0)
#define V4L2_CID_PRIVATE_COMBFILTER (V4L2_CID_PRIVATE_BASE + 1)
#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_PRIVATE_BASE + 2)
#define V4L2_CID_PRIVATE_LUMAFILTER (V4L2_CID_PRIVATE_BASE + 3)
#define V4L2_CID_PRIVATE_AGC_CRUSH (V4L2_CID_PRIVATE_BASE + 4)
#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 5)
static const struct v4l2_queryctrl no_ctl = {
.name = "42",
.flags = V4L2_CTRL_FLAG_DISABLED,
};
static const struct v4l2_queryctrl bttv_ctls[] = {
/* --- video --- */
{
.id = V4L2_CID_BRIGHTNESS,
.name = "Brightness",
.minimum = 0,
.maximum = 65535,
.step = 256,
.default_value = 32768,
.type = V4L2_CTRL_TYPE_INTEGER,
},{
.id = V4L2_CID_CONTRAST,
.name = "Contrast",
.minimum = 0,
.maximum = 65535,
.step = 128,
.default_value = 32768,
.type = V4L2_CTRL_TYPE_INTEGER,
},{
.id = V4L2_CID_SATURATION,
.name = "Saturation",
.minimum = 0,
.maximum = 65535,
.step = 128,
.default_value = 32768,
.type = V4L2_CTRL_TYPE_INTEGER,
},{
.id = V4L2_CID_HUE,
.name = "Hue",
.minimum = 0,
.maximum = 65535,
.step = 256,
.default_value = 32768,
.type = V4L2_CTRL_TYPE_INTEGER,
},
/* --- audio --- */
{
.id = V4L2_CID_AUDIO_MUTE,
.name = "Mute",
.minimum = 0,
.maximum = 1,
.type = V4L2_CTRL_TYPE_BOOLEAN,
},{
.id = V4L2_CID_AUDIO_VOLUME,
.name = "Volume",
.minimum = 0,
.maximum = 65535,
.step = 65535/100,
.default_value = 65535,
.type = V4L2_CTRL_TYPE_INTEGER,
},{
.id = V4L2_CID_AUDIO_BALANCE,
.name = "Balance",
.minimum = 0,
.maximum = 65535,
.step = 65535/100,
.default_value = 32768,
.type = V4L2_CTRL_TYPE_INTEGER,
},{
.id = V4L2_CID_AUDIO_BASS,
.name = "Bass",
.minimum = 0,
.maximum = 65535,
.step = 65535/100,
.default_value = 32768,
.type = V4L2_CTRL_TYPE_INTEGER,
},{
.id = V4L2_CID_AUDIO_TREBLE,
.name = "Treble",
.minimum = 0,
.maximum = 65535,
.step = 65535/100,
.default_value = 32768,
.type = V4L2_CTRL_TYPE_INTEGER,
},
/* --- private --- */
{
.id = V4L2_CID_PRIVATE_CHROMA_AGC,
.name = "chroma agc",
.minimum = 0,
.maximum = 1,
.type = V4L2_CTRL_TYPE_BOOLEAN,
},{
.id = V4L2_CID_PRIVATE_COMBFILTER,
.name = "combfilter",
.minimum = 0,
.maximum = 1,
.type = V4L2_CTRL_TYPE_BOOLEAN,
},{
.id = V4L2_CID_PRIVATE_AUTOMUTE,
.name = "automute",
.minimum = 0,
.maximum = 1,
.type = V4L2_CTRL_TYPE_BOOLEAN,
},{
.id = V4L2_CID_PRIVATE_LUMAFILTER,
.name = "luma decimation filter",
.minimum = 0,
.maximum = 1,
.type = V4L2_CTRL_TYPE_BOOLEAN,
},{
.id = V4L2_CID_PRIVATE_AGC_CRUSH,
.name = "agc crush",
.minimum = 0,
.maximum = 1,
.type = V4L2_CTRL_TYPE_BOOLEAN,
}
};
const int BTTV_CTLS = (sizeof(bttv_ctls)/sizeof(struct v4l2_queryctrl));
/* ----------------------------------------------------------------------- */
/* resource management */
static
int check_alloc_btres(struct bttv *btv, struct bttv_fh *fh, int bit)
{
if (fh->resources & bit)
/* have it already allocated */
return 1;
/* is it free? */
down(&btv->reslock);
if (btv->resources & bit) {
/* no, someone else uses it */
up(&btv->reslock);
return 0;
}
/* it's free, grab it */
fh->resources |= bit;
btv->resources |= bit;
up(&btv->reslock);
return 1;
}
static
int check_btres(struct bttv_fh *fh, int bit)
{
return (fh->resources & bit);
}
static
int locked_btres(struct bttv *btv, int bit)
{
return (btv->resources & bit);
}
static
void free_btres(struct bttv *btv, struct bttv_fh *fh, int bits)
{
#if 1 /* DEBUG */
if ((fh->resources & bits) != bits) {
/* trying to free ressources not allocated by us ... */
printk("bttv: BUG! (btres)\n");
}
#endif
down(&btv->reslock);
fh->resources &= ~bits;
btv->resources &= ~bits;
up(&btv->reslock);
}
/* ----------------------------------------------------------------------- */
/* If Bt848a or Bt849, use PLL for PAL/SECAM and crystal for NTSC */
/* Frequency = (F_input / PLL_X) * PLL_I.PLL_F/PLL_C
PLL_X = Reference pre-divider (0=1, 1=2)
PLL_C = Post divider (0=6, 1=4)
PLL_I = Integer input
PLL_F = Fractional input
F_input = 28.636363 MHz:
PAL (CLKx2 = 35.46895 MHz): PLL_X = 1, PLL_I = 0x0E, PLL_F = 0xDCF9, PLL_C = 0
*/
static void set_pll_freq(struct bttv *btv, unsigned int fin, unsigned int fout)
{
unsigned char fl, fh, fi;
/* prevent overflows */
fin/=4;
fout/=4;
fout*=12;
fi=fout/fin;
fout=(fout%fin)*256;
fh=fout/fin;
fout=(fout%fin)*256;
fl=fout/fin;
btwrite(fl, BT848_PLL_F_LO);
btwrite(fh, BT848_PLL_F_HI);
btwrite(fi|BT848_PLL_X, BT848_PLL_XCI);
}
static void set_pll(struct bttv *btv)
{
int i;
if (!btv->pll.pll_crystal)
return;
if (btv->pll.pll_ofreq == btv->pll.pll_current) {
dprintk("bttv%d: PLL: no change required\n",btv->nr);
return;
}
if (btv->pll.pll_ifreq == btv->pll.pll_ofreq) {
/* no PLL needed */
if (btv->pll.pll_current == 0)
return;
vprintk(KERN_INFO "bttv%d: PLL can sleep, using XTAL (%d).\n",
btv->nr,btv->pll.pll_ifreq);
btwrite(0x00,BT848_TGCTRL);
btwrite(0x00,BT848_PLL_XCI);
btv->pll.pll_current = 0;
return;
}
vprintk(KERN_INFO "bttv%d: PLL: %d => %d ",btv->nr,
btv->pll.pll_ifreq, btv->pll.pll_ofreq);
set_pll_freq(btv, btv->pll.pll_ifreq, btv->pll.pll_ofreq);
for (i=0; i<10; i++) {
/* Let other people run while the PLL stabilizes */
vprintk(".");
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(HZ/10);
if (btread(BT848_DSTATUS) & BT848_DSTATUS_PLOCK) {
btwrite(0,BT848_DSTATUS);
} else {
btwrite(0x08,BT848_TGCTRL);
btv->pll.pll_current = btv->pll.pll_ofreq;
vprintk(" ok\n");
return;
}
}
btv->pll.pll_current = -1;
vprintk("failed\n");
return;
}
/* used to switch between the bt848's analog/digital video capture modes */
void bt848A_set_timing(struct bttv *btv)
{
int i, len;
int table_idx = bttv_tvnorms[btv->tvnorm].sram;
int fsc = bttv_tvnorms[btv->tvnorm].Fsc;
if (bttv_tvcards[btv->type].muxsel[btv->input] < 0) {
dprintk("bttv%d: load digital timing table (table_idx=%d)\n",
btv->nr,table_idx);
/* timing change...reset timing generator address */
btwrite(0x00, BT848_TGCTRL);
btwrite(0x02, BT848_TGCTRL);
btwrite(0x00, BT848_TGCTRL);
len=SRAM_Table[table_idx][0];
for(i = 1; i <= len; i++)
btwrite(SRAM_Table[table_idx][i],BT848_TGLB);
btv->pll.pll_ofreq = 27000000;
set_pll(btv);
btwrite(0x11, BT848_TGCTRL);
btwrite(0x41, BT848_DVSIF);
} else {
btv->pll.pll_ofreq = fsc;
set_pll(btv);
btwrite(0x0, BT848_DVSIF);
}
}
/* ----------------------------------------------------------------------- */
static void bt848_bright(struct bttv *btv, int bright)
{
int value;
btv->bright = bright;
/* We want -128 to 127 we get 0-65535 */
value = (bright >> 8) - 128;
btwrite(value & 0xff, BT848_BRIGHT);
}
static void bt848_hue(struct bttv *btv, int hue)
{
int value;
btv->hue = hue;
/* -128 to 127 */
value = (hue >> 8) - 128;
btwrite(value & 0xff, BT848_HUE);
}
static void bt848_contrast(struct bttv *btv, int cont)
{
int value,hibit;
btv->contrast = cont;
/* 0-511 */
value = (cont >> 7);
hibit = (value >> 6) & 4;
btwrite(value & 0xff, BT848_CONTRAST_LO);
btaor(hibit, ~4, BT848_E_CONTROL);
btaor(hibit, ~4, BT848_O_CONTROL);
}
static void bt848_sat(struct bttv *btv, int color)
{
int val_u,val_v,hibits;
btv->saturation = color;
/* 0-511 for the color */
val_u = color >> 7;
val_v = ((color>>7)*180L)/254;
hibits = (val_u >> 7) & 2;
hibits |= (val_v >> 8) & 1;
btwrite(val_u & 0xff, BT848_SAT_U_LO);
btwrite(val_v & 0xff, BT848_SAT_V_LO);
btaor(hibits, ~3, BT848_E_CONTROL);
btaor(hibits, ~3, BT848_O_CONTROL);
}
/* ----------------------------------------------------------------------- */
static int
video_mux(struct bttv *btv, unsigned int input)
{
int mux,mask2;
if (input >= bttv_tvcards[btv->type].video_inputs)
return -EINVAL;
/* needed by RemoteVideo MX */
mask2 = bttv_tvcards[btv->type].gpiomask2;
if (mask2)
btaor(mask2,~mask2,BT848_GPIO_OUT_EN);
#if 0
/* This seems to get rid of some synchronization problems */
btand(~(3<<5), BT848_IFORM);
schedule_timeout(HZ/10);
#endif
if (input==bttv_tvcards[btv->type].svhs) {
btor(BT848_CONTROL_COMP, BT848_E_CONTROL);
btor(BT848_CONTROL_COMP, BT848_O_CONTROL);
} else {
btand(~BT848_CONTROL_COMP, BT848_E_CONTROL);
btand(~BT848_CONTROL_COMP, BT848_O_CONTROL);
}
mux = bttv_tvcards[btv->type].muxsel[input] & 3;
btaor(mux<<5, ~(3<<5), BT848_IFORM);
dprintk(KERN_DEBUG "bttv%d: video mux: input=%d mux=%d\n",
btv->nr,input,mux);
/* card specific hook */
if(bttv_tvcards[btv->type].muxsel_hook)
bttv_tvcards[btv->type].muxsel_hook (btv, input);
return 0;
}
static char *audio_modes[] = {
"audio: tuner", "audio: radio", "audio: extern",
"audio: intern", "audio: off"
};
static int
audio_mux(struct bttv *btv, int mode)
{
int val,mux,i2c_mux,signal;
btaor(bttv_tvcards[btv->type].gpiomask,
~bttv_tvcards[btv->type].gpiomask,BT848_GPIO_OUT_EN);
signal = btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC;
switch (mode) {
case AUDIO_MUTE:
btv->audio |= AUDIO_MUTE;
break;
case AUDIO_UNMUTE:
btv->audio &= ~AUDIO_MUTE;
break;
case AUDIO_TUNER:
case AUDIO_RADIO:
case AUDIO_EXTERN:
case AUDIO_INTERN:
btv->audio &= AUDIO_MUTE;
btv->audio |= mode;
}
i2c_mux = mux = (btv->audio & AUDIO_MUTE) ? AUDIO_OFF : btv->audio;
if (btv->opt_automute && !signal && !btv->radio_user)
mux = AUDIO_OFF;
#if 0
printk("bttv%d: amux: mode=%d audio=%d signal=%s mux=%d/%d irq=%s\n",
btv->nr, mode, btv->audio, signal ? "yes" : "no",
mux, i2c_mux, in_interrupt() ? "yes" : "no");
#endif
val = bttv_tvcards[btv->type].audiomux[mux];
btaor(val,~bttv_tvcards[btv->type].gpiomask, BT848_GPIO_DATA);
if (bttv_gpio)
bttv_gpio_tracking(btv,audio_modes[mux]);
if (!in_interrupt())
bttv_call_i2c_clients(btv,AUDC_SET_INPUT,&(i2c_mux));
return 0;
}
static void
i2c_vidiocschan(struct bttv *btv)
{
struct video_channel c;
memset(&c,0,sizeof(c));
c.norm = btv->tvnorm;
c.channel = btv->input;
bttv_call_i2c_clients(btv,VIDIOCSCHAN,&c);
if (btv->type == BTTV_VOODOOTV_FM)
bttv_tda9880_setnorm(btv,c.norm);
}
static int
set_tvnorm(struct bttv *btv, unsigned int norm)
{
const struct bttv_tvnorm *tvnorm;
if (norm < 0 || norm >= BTTV_TVNORMS)
return -EINVAL;
btv->tvnorm = norm;
tvnorm = &bttv_tvnorms[norm];
btwrite(tvnorm->adelay, BT848_ADELAY);
btwrite(tvnorm->bdelay, BT848_BDELAY);
btaor(tvnorm->iform,~(BT848_IFORM_NORM|BT848_IFORM_XTBOTH),
BT848_IFORM);
btwrite(tvnorm->vbipack, BT848_VBI_PACK_SIZE);
btwrite(1, BT848_VBI_PACK_DEL);
bt848A_set_timing(btv);
switch (btv->type) {
case BTTV_VOODOOTV_FM:
bttv_tda9880_setnorm(btv,norm);
break;
#if 0
case BTTV_OSPREY540:
osprey_540_set_norm(btv,norm);
break;
#endif
}
return 0;
}
static void
set_input(struct bttv *btv, unsigned int input)
{
btv->input = input;
video_mux(btv,input);
audio_mux(btv,(input == bttv_tvcards[btv->type].tuner ?
AUDIO_TUNER : AUDIO_EXTERN));
set_tvnorm(btv,btv->tvnorm);
}
static void init_bt848(struct bttv *btv)
{
int val;
btwrite(0, BT848_SRESET);
btwrite(0x00, BT848_CAP_CTL);
btwrite(BT848_COLOR_CTL_GAMMA, BT848_COLOR_CTL);
btwrite(BT848_IFORM_XTAUTO | BT848_IFORM_AUTO, BT848_IFORM);
/* set planar and packed mode trigger points and */
/* set rising edge of inverted GPINTR pin as irq trigger */
btwrite(BT848_GPIO_DMA_CTL_PKTP_32|
BT848_GPIO_DMA_CTL_PLTP1_16|
BT848_GPIO_DMA_CTL_PLTP23_16|
BT848_GPIO_DMA_CTL_GPINTC|
BT848_GPIO_DMA_CTL_GPINTI,
BT848_GPIO_DMA_CTL);
val = btv->opt_chroma_agc ? BT848_SCLOOP_CAGC : 0;
btwrite(val, BT848_E_SCLOOP);
btwrite(val, BT848_O_SCLOOP);
btwrite(0x20, BT848_E_VSCALE_HI);
btwrite(0x20, BT848_O_VSCALE_HI);
btwrite(BT848_ADC_RESERVED | (btv->opt_adc_crush ? BT848_ADC_CRUSH : 0),
BT848_ADC);
if (btv->opt_lumafilter) {
btwrite(0, BT848_E_CONTROL);
btwrite(0, BT848_O_CONTROL);
} else {
btwrite(BT848_CONTROL_LDEC, BT848_E_CONTROL);
btwrite(BT848_CONTROL_LDEC, BT848_O_CONTROL);
}
}
extern void bttv_reinit_bt848(struct bttv *btv)
{
unsigned long flags;
if (bttv_verbose)
printk(KERN_INFO "bttv%d: reset, reinitialize\n",btv->nr);
spin_lock_irqsave(&btv->s_lock,flags);
btv->errors=0;
bttv_set_dma(btv,0,0);
spin_unlock_irqrestore(&btv->s_lock,flags);
init_bt848(btv);
btv->pll.pll_current = -1;
set_input(btv,btv->input);
}
static int get_control(struct bttv *btv, struct v4l2_control *c)
{
struct video_audio va;
int i;
for (i = 0; i < BTTV_CTLS; i++)
if (bttv_ctls[i].id == c->id)
break;
if (i == BTTV_CTLS)
return -EINVAL;
if (i >= 4 && i <= 8) {
memset(&va,0,sizeof(va));
bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va);
if (btv->audio_hook)
btv->audio_hook(btv,&va,0);
}
switch (c->id) {
case V4L2_CID_BRIGHTNESS:
c->value = btv->bright;
break;
case V4L2_CID_HUE:
c->value = btv->hue;
break;
case V4L2_CID_CONTRAST:
c->value = btv->contrast;
break;
case V4L2_CID_SATURATION:
c->value = btv->saturation;
break;
case V4L2_CID_AUDIO_MUTE:
c->value = (VIDEO_AUDIO_MUTE == va.flags) ? 1 : 0;
break;
case V4L2_CID_AUDIO_VOLUME:
c->value = va.volume;
break;
case V4L2_CID_AUDIO_BALANCE:
c->value = va.balance;
break;
case V4L2_CID_AUDIO_BASS:
c->value = va.bass;
break;
case V4L2_CID_AUDIO_TREBLE:
c->value = va.treble;
break;
case V4L2_CID_PRIVATE_CHROMA_AGC:
c->value = btv->opt_chroma_agc;
break;
case V4L2_CID_PRIVATE_COMBFILTER:
c->value = btv->opt_combfilter;
break;
case V4L2_CID_PRIVATE_LUMAFILTER:
c->value = btv->opt_lumafilter;
break;
case V4L2_CID_PRIVATE_AUTOMUTE:
c->value = btv->opt_automute;
break;
case V4L2_CID_PRIVATE_AGC_CRUSH:
c->value = btv->opt_adc_crush;
break;
default:
return -EINVAL;
}
return 0;
}
static int set_control(struct bttv *btv, struct v4l2_control *c)
{
struct video_audio va;
int i,val;
for (i = 0; i < BTTV_CTLS; i++)
if (bttv_ctls[i].id == c->id)
break;
if (i == BTTV_CTLS)
return -EINVAL;
if (i >= 4 && i <= 8) {
memset(&va,0,sizeof(va));
bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va);
if (btv->audio_hook)
btv->audio_hook(btv,&va,0);
}
switch (c->id) {
case V4L2_CID_BRIGHTNESS:
bt848_bright(btv,c->value);
break;
case V4L2_CID_HUE:
bt848_hue(btv,c->value);
break;
case V4L2_CID_CONTRAST:
bt848_contrast(btv,c->value);
break;
case V4L2_CID_SATURATION:
bt848_sat(btv,c->value);
break;
case V4L2_CID_AUDIO_MUTE:
if (c->value) {
va.flags |= VIDEO_AUDIO_MUTE;
audio_mux(btv, AUDIO_MUTE);
} else {
va.flags &= ~VIDEO_AUDIO_MUTE;
audio_mux(btv, AUDIO_UNMUTE);
}
break;
case V4L2_CID_AUDIO_VOLUME:
va.volume = c->value;
break;
case V4L2_CID_AUDIO_BALANCE:
va.balance = c->value;
break;
case V4L2_CID_AUDIO_BASS:
va.bass = c->value;
break;
case V4L2_CID_AUDIO_TREBLE:
va.treble = c->value;
break;
case V4L2_CID_PRIVATE_CHROMA_AGC:
btv->opt_chroma_agc = c->value;
val = btv->opt_chroma_agc ? BT848_SCLOOP_CAGC : 0;
btwrite(val, BT848_E_SCLOOP);
btwrite(val, BT848_O_SCLOOP);
break;
case V4L2_CID_PRIVATE_COMBFILTER:
btv->opt_combfilter = c->value;
break;
case V4L2_CID_PRIVATE_LUMAFILTER:
btv->opt_lumafilter = c->value;
if (btv->opt_lumafilter) {
btand(~BT848_CONTROL_LDEC, BT848_E_CONTROL);
btand(~BT848_CONTROL_LDEC, BT848_O_CONTROL);
} else {
btor(BT848_CONTROL_LDEC, BT848_E_CONTROL);
btor(BT848_CONTROL_LDEC, BT848_O_CONTROL);
}
break;
case V4L2_CID_PRIVATE_AUTOMUTE:
btv->opt_automute = c->value;
break;
case V4L2_CID_PRIVATE_AGC_CRUSH:
btv->opt_adc_crush = c->value;
btwrite(BT848_ADC_RESERVED | (btv->opt_adc_crush ? BT848_ADC_CRUSH : 0),
BT848_ADC);
break;
default:
return -EINVAL;
}
if (i >= 4 && i <= 8) {
bttv_call_i2c_clients(btv, VIDIOCSAUDIO, &va);
if (btv->audio_hook)
btv->audio_hook(btv,&va,1);
}
return 0;
}
/* ----------------------------------------------------------------------- */
void bttv_gpio_tracking(struct bttv *btv, char *comment)
{
unsigned int outbits, data;
outbits = btread(BT848_GPIO_OUT_EN);
data = btread(BT848_GPIO_DATA);
printk(KERN_DEBUG "bttv%d: gpio: en=%08x, out=%08x in=%08x [%s]\n",
btv->nr,outbits,data & outbits, data & ~outbits, comment);
}
void bttv_field_count(struct bttv *btv)
{
int need_count = 0;
if (btv->users)
need_count++;
if (need_count) {
/* start field counter */
btor(BT848_INT_VSYNC,BT848_INT_MASK);
} else {
/* stop field counter */
btand(~BT848_INT_VSYNC,BT848_INT_MASK);
btv->field_count = 0;
}
}
static const struct bttv_format*
format_by_palette(int palette)
{
int i;
for (i = 0; i < BTTV_FORMATS; i++) {
if (-1 == bttv_formats[i].palette)
continue;
if (bttv_formats[i].palette == palette)
return bttv_formats+i;
}
return NULL;
}
static const struct bttv_format*
format_by_fourcc(int fourcc)
{
int i;
for (i = 0; i < BTTV_FORMATS; i++) {
if (-1 == bttv_formats[i].fourcc)
continue;
if (bttv_formats[i].fourcc == fourcc)
return bttv_formats+i;
}
return NULL;
}
/* ----------------------------------------------------------------------- */
/* misc helpers */
static int
bttv_switch_overlay(struct bttv *btv, struct bttv_fh *fh,
struct bttv_buffer *new)
{
struct bttv_buffer *old;
unsigned long flags;
int retval = 0;
if (new)
new->vb.state = STATE_DONE;
spin_lock_irqsave(&btv->s_lock,flags);
old = btv->screen;
btv->screen = new;
bttv_set_dma(btv, 0x03, 1);
spin_unlock_irqrestore(&btv->s_lock,flags);
if (NULL == new)
free_btres(btv,fh,RESOURCE_OVERLAY);
if (NULL != old)
bttv_dma_free(btv, old);
return retval;
}
/* ----------------------------------------------------------------------- */
/* video4linux (1) interface */
static int bttv_prepare_buffer(struct bttv *btv, struct bttv_buffer *buf,
const struct bttv_format *fmt,
int width, int height,
enum v4l2_field field)
{
int redo_dma_risc = 0;
int rc;
/* check settings */
if (NULL == fmt)
return -EINVAL;
if (fmt->btformat == BT848_COLOR_FMT_RAW) {
width = RAW_BPL;
height = RAW_LINES*2;
if (width*height > buf->vb.bsize)
return -EINVAL;
buf->vb.size = buf->vb.bsize;
} else {
if (width < 48 ||
height < 32 ||
width > bttv_tvnorms[btv->tvnorm].swidth ||
height > bttv_tvnorms[btv->tvnorm].sheight)
return -EINVAL;
buf->vb.size = (width * height * fmt->depth) >> 3;
if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size)
return -EINVAL;
}
/* alloc + fill struct bttv_buffer (if changed) */
if (buf->vb.width != width || buf->vb.height != height ||
buf->vb.field != field ||
buf->tvnorm != btv->tvnorm || buf->fmt != fmt) {
buf->vb.width = width;
buf->vb.height = height;
buf->vb.field = field;
buf->tvnorm = btv->tvnorm;
buf->fmt = fmt;
redo_dma_risc = 1;
}
#if 0
if (STATE_NEEDS_INIT == buf->vb.state) {
if (redo_dma_risc)
bttv_dma_free(btv,buf);
}
#endif
/* alloc risc memory */
if (STATE_NEEDS_INIT == buf->vb.state) {
redo_dma_risc = 1;
if (0 != (rc = videobuf_iolock(btv->dev,&buf->vb)))
goto fail;
}
if (redo_dma_risc)
if (0 != (rc = bttv_buffer_risc(btv,buf)))
goto fail;
buf->vb.state = STATE_PREPARED;
return 0;
fail:
bttv_dma_free(btv,buf);
return rc;
}
static int
buffer_setup(struct file *file, int *count, int *size)
{
struct bttv_fh *fh = file->private_data;
*size = fh->fmt->depth*fh->width*fh->height >> 3;
if (0 == *count)
*count = gbuffers;
while (*size * *count > gbuffers * gbufsize)
(*count)--;
return 0;
}
static int
buffer_prepare(struct file *file, struct videobuf_buffer *vb,
enum v4l2_field field)
{
struct bttv_buffer *buf = (struct bttv_buffer*)vb;
struct bttv_fh *fh = file->private_data;
return bttv_prepare_buffer(fh->btv, buf, fh->fmt,
fh->width, fh->height, field);
}
static void
buffer_queue(struct file *file, struct videobuf_buffer *vb)
{
struct bttv_buffer *buf = (struct bttv_buffer*)vb;
struct bttv_fh *fh = file->private_data;
buf->vb.state = STATE_QUEUED;
list_add_tail(&buf->vb.queue,&fh->btv->capture);
bttv_set_dma(fh->btv, 0x03, 1);
}
static void buffer_release(struct file *file, struct videobuf_buffer *vb)
{
struct bttv_buffer *buf = (struct bttv_buffer*)vb;
struct bttv_fh *fh = file->private_data;
bttv_dma_free(fh->btv,buf);
}
static struct videobuf_queue_ops bttv_video_qops = {
.buf_setup = buffer_setup,
.buf_prepare = buffer_prepare,
.buf_queue = buffer_queue,
.buf_release = buffer_release,
};
static const char *v4l1_ioctls[] = {
"?", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT",
"CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ",
"SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT",
"GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO",
"SMICROCODE", "GVBIFMT", "SVBIFMT" };
#define V4L1_IOCTLS (sizeof(v4l1_ioctls)/sizeof(char*))
int bttv_common_ioctls(struct bttv *btv, unsigned int cmd, void *arg)
{
switch (cmd) {
case BTTV_VERSION:
return BTTV_VERSION_CODE;
/* *** v4l1 *** ************************************************ */
case VIDIOCGFREQ:
{
unsigned long *freq = arg;
*freq = btv->freq;
return 0;
}
case VIDIOCSFREQ:
{
unsigned long *freq = arg;
down(&btv->lock);
btv->freq=*freq;
bttv_call_i2c_clients(btv,VIDIOCSFREQ,freq);
if (btv->has_matchbox && btv->radio_user)
tea5757_set_freq(btv,*freq);
up(&btv->lock);
return 0;
}
case VIDIOCGTUNER:
{
struct video_tuner *v = arg;
if (-1 == bttv_tvcards[btv->type].tuner)
return -EINVAL;
if (v->tuner) /* Only tuner 0 */
return -EINVAL;
strcpy(v->name, "Television");
v->rangelow = 0;
v->rangehigh = 0x7FFFFFFF;
v->flags = VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM;
v->mode = btv->tvnorm;
v->signal = (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC) ? 0xFFFF : 0;
bttv_call_i2c_clients(btv,cmd,v);
return 0;
}
case VIDIOCSTUNER:
{
struct video_tuner *v = arg;
if (v->tuner) /* Only tuner 0 */
return -EINVAL;
if (v->mode >= BTTV_TVNORMS)
return -EINVAL;
down(&btv->lock);
set_tvnorm(btv,v->mode);
bttv_call_i2c_clients(btv,cmd,v);
up(&btv->lock);
return 0;
}
case VIDIOCGCHAN:
{
struct video_channel *v = arg;
if (v->channel >= bttv_tvcards[btv->type].video_inputs)
return -EINVAL;
v->tuners=0;
v->flags = VIDEO_VC_AUDIO;
v->type = VIDEO_TYPE_CAMERA;
v->norm = btv->tvnorm;
if(v->channel == bttv_tvcards[btv->type].tuner) {
strcpy(v->name,"Television");
v->flags|=VIDEO_VC_TUNER;
v->type=VIDEO_TYPE_TV;
v->tuners=1;
} else if (v->channel == bttv_tvcards[btv->type].svhs) {
strcpy(v->name,"S-Video");
} else {
sprintf(v->name,"Composite%d",v->channel);
}
return 0;
}
case VIDIOCSCHAN:
{
struct video_channel *v = arg;
if (v->channel < 0 ||
v->channel >= bttv_tvcards[btv->type].video_inputs)
return -EINVAL;
if (v->norm >= BTTV_TVNORMS)
return -EINVAL;
down(&btv->lock);
if (v->channel == btv->input &&
v->norm == btv->tvnorm) {
/* nothing to do */
up(&btv->lock);
return 0;
}
btv->tvnorm = v->norm;
set_input(btv,v->channel);
up(&btv->lock);
return 0;
}
case VIDIOCGAUDIO:
{
struct video_audio *v = arg;
memset(v,0,sizeof(*v));
strcpy(v->name,"Television");
v->flags |= VIDEO_AUDIO_MUTABLE;
v->mode = VIDEO_SOUND_MONO;
down(&btv->lock);
bttv_call_i2c_clients(btv,cmd,v);
/* card specific hooks */
if (btv->audio_hook)
btv->audio_hook(btv,v,0);
up(&btv->lock);
return 0;
}
case VIDIOCSAUDIO:
{
struct video_audio *v = arg;
if(v->audio < 0 ||
v->audio >= bttv_tvcards[btv->type].audio_inputs)
return -EINVAL;
down(&btv->lock);
audio_mux(btv, (v->flags&VIDEO_AUDIO_MUTE) ? AUDIO_MUTE : AUDIO_UNMUTE);
bttv_call_i2c_clients(btv,cmd,v);
/* card specific hooks */
if (btv->audio_hook)
btv->audio_hook(btv,v,1);
up(&btv->lock);
return 0;
}
/* *** v4l2 *** ************************************************ */
case VIDIOC_ENUMSTD:
{
struct v4l2_standard *e = arg;
if (e->index < 0 || e->index >= BTTV_TVNORMS)
return -EINVAL;
v4l2_video_std_construct(e, bttv_tvnorms[e->index].v4l2_id,
bttv_tvnorms[e->index].name);
return 0;
}
case VIDIOC_G_STD:
{
v4l2_std_id *id = arg;
*id = bttv_tvnorms[btv->tvnorm].v4l2_id;
return 0;
}
case VIDIOC_S_STD:
{
v4l2_std_id *id = arg;
int i;
for (i = 0; i < BTTV_TVNORMS; i++)
if (*id & bttv_tvnorms[i].v4l2_id)
break;
if (i == BTTV_TVNORMS)
return -EINVAL;
down(&btv->lock);
set_tvnorm(btv,i);
i2c_vidiocschan(btv);
up(&btv->lock);
return 0;
}
case VIDIOC_QUERYSTD:
{
v4l2_std_id *id = arg;
if (btread(BT848_DSTATUS) & BT848_DSTATUS_NUML)
*id = V4L2_STD_625_50;
else
*id = V4L2_STD_525_60;
return 0;
}
case VIDIOC_ENUMINPUT:
{
struct v4l2_input *i = arg;
int n;
n = i->index;
if (n >= bttv_tvcards[btv->type].video_inputs)
return -EINVAL;
memset(i,0,sizeof(*i));
i->index = n;
i->type = V4L2_INPUT_TYPE_CAMERA;
i->audioset = 1;
if (i->index == bttv_tvcards[btv->type].tuner) {
sprintf(i->name, "Television");
i->type = V4L2_INPUT_TYPE_TUNER;
i->tuner = 0;
} else if (i->index==bttv_tvcards[btv->type].svhs) {
sprintf(i->name, "S-Video");
} else {
sprintf(i->name,"Composite%d",i->index);
}
if (i->index == btv->input) {
__u32 dstatus = btread(BT848_DSTATUS);
if (0 == (dstatus & BT848_DSTATUS_PRES))
i->status |= V4L2_IN_ST_NO_SIGNAL;
if (0 == (dstatus & BT848_DSTATUS_HLOC))
i->status |= V4L2_IN_ST_NO_H_LOCK;
}
for (n = 0; n < BTTV_TVNORMS; n++)
i->std |= bttv_tvnorms[n].v4l2_id;
return 0;
}
case VIDIOC_G_INPUT:
{
int *i = arg;
*i = btv->input;
return 0;
}
case VIDIOC_S_INPUT:
{
int *i = arg;
if (*i < 0 || *i > bttv_tvcards[btv->type].video_inputs)
return -EINVAL;
down(&btv->lock);
set_input(btv,*i);
i2c_vidiocschan(btv);
up(&btv->lock);
return 0;
}
case VIDIOC_G_TUNER:
{
struct v4l2_tuner *t = arg;
if (-1 == bttv_tvcards[btv->type].tuner)
return -EINVAL;
if (0 != t->index)
return -EINVAL;
down(&btv->lock);
memset(t,0,sizeof(*t));
strcpy(t->name, "Television");
t->type = V4L2_TUNER_ANALOG_TV;
t->capability = V4L2_TUNER_CAP_NORM;
t->rangehigh = 0xffffffffUL;
t->rxsubchans = V4L2_TUNER_SUB_MONO;
if (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC)
t->signal = 0xffff;
{
/* Hmmm ... */
struct video_audio va;
memset(&va, 0, sizeof(struct video_audio));
bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va);
if (btv->audio_hook)
btv->audio_hook(btv,&va,0);
if(va.mode & VIDEO_SOUND_STEREO)
t->rxsubchans |= V4L2_TUNER_SUB_STEREO;
if(va.mode & VIDEO_SOUND_LANG1)
t->rxsubchans |= V4L2_TUNER_SUB_LANG1;
if(va.mode & VIDEO_SOUND_LANG2)
t->rxsubchans |= V4L2_TUNER_SUB_LANG2;
}
/* FIXME: fill capability+audmode */
up(&btv->lock);
return 0;
}
case VIDIOC_S_TUNER:
{
struct v4l2_tuner *t = arg;
if (-1 == bttv_tvcards[btv->type].tuner)
return -EINVAL;
if (0 != t->index)
return -EINVAL;
down(&btv->lock);
{
struct video_audio va;
memset(&va, 0, sizeof(struct video_audio));
if (t->audmode == V4L2_TUNER_MODE_MONO)
va.mode = VIDEO_SOUND_MONO;
else if (t->audmode == V4L2_TUNER_MODE_STEREO)
va.mode = VIDEO_SOUND_STEREO;
else if (t->audmode == V4L2_TUNER_MODE_LANG1)
va.mode = VIDEO_SOUND_LANG1;
else if (t->audmode == V4L2_TUNER_MODE_LANG2)
va.mode = VIDEO_SOUND_LANG2;
bttv_call_i2c_clients(btv, VIDIOCSAUDIO, &va);
if (btv->audio_hook)
btv->audio_hook(btv,&va,1);
}
up(&btv->lock);
return 0;
}
case VIDIOC_G_FREQUENCY:
{
struct v4l2_frequency *f = arg;
memset(f,0,sizeof(*f));
f->type = V4L2_TUNER_ANALOG_TV;
f->frequency = btv->freq;
return 0;
}
case VIDIOC_S_FREQUENCY:
{
struct v4l2_frequency *f = arg;
if (unlikely(f->tuner != 0))
return -EINVAL;
if (unlikely(f->type != V4L2_TUNER_ANALOG_TV))
return -EINVAL;
down(&btv->lock);
btv->freq = f->frequency;
bttv_call_i2c_clients(btv,VIDIOCSFREQ,&btv->freq);
if (btv->has_matchbox && btv->radio_user)
tea5757_set_freq(btv,btv->freq);
up(&btv->lock);
return 0;
}
default:
return -ENOIOCTLCMD;
}
return 0;
}
static int verify_window(const struct bttv_tvnorm *tvn,
struct v4l2_window *win, int fixup)
{
enum v4l2_field field;
int maxw, maxh;
if (win->w.width < 48 || win->w.height < 32)
return -EINVAL;
if (win->clipcount > 2048)
return -EINVAL;
field = win->field;
maxw = tvn->swidth;
maxh = tvn->sheight;
if (V4L2_FIELD_ANY == field) {
field = (win->w.height > maxh/2)
? V4L2_FIELD_INTERLACED
: V4L2_FIELD_TOP;
}
switch (field) {
case V4L2_FIELD_TOP:
case V4L2_FIELD_BOTTOM:
maxh = maxh / 2;
break;
case V4L2_FIELD_INTERLACED:
break;
default:
return -EINVAL;
}
if (!fixup && (win->w.width > maxw || win->w.height > maxh))
return -EINVAL;
if (1 /* depth < 4bpp */) {
/* adjust and align writes */
int left = (win->w.left + 3) & ~3;
int width = win->w.width & ~3;
while (left + width > win->w.left + win->w.width)
width -= 4;
win->w.left = left;
win->w.width = width;
}
if (win->w.width > maxw)
win->w.width = maxw;
if (win->w.height > maxh)
win->w.height = maxh;
win->field = field;
return 0;
}
static int setup_window(struct bttv_fh *fh, struct bttv *btv,
struct v4l2_window *win, int fixup)
{
struct v4l2_clip *clips = NULL;
int n,size,retval = 0;
retval = verify_window(&bttv_tvnorms[btv->tvnorm],win,fixup);
if (0 != retval)
return retval;
/* copy clips -- luckily v4l1 + v4l2 are binary
compatible here ...*/
n = win->clipcount;
size = sizeof(struct video_clip)*(n+4);
clips = kmalloc(size,GFP_KERNEL);
if (NULL == clips)
return -ENOMEM;
if (n > 0) {
if (copy_from_user(clips,win->clips,
sizeof(struct v4l2_clip)*win->clipcount)) {
kfree(clips);
return -EFAULT;
}
}
/* clip against screen */
if (NULL != btv->fbuf.base)
n = bttv_screen_clips(btv->fbuf.width, btv->fbuf.width,
&win->w, clips, n);
bttv_sort_clips(clips,n);
down(&fh->cap.lock);
if (fh->ov.clips)
kfree(fh->ov.clips);
fh->ov.clips = clips;
fh->ov.nclips = n;
fh->ov.w = win->w;
fh->ov.field = win->field;
btv->init.ov.w.width = win->w.width;
btv->init.ov.w.height = win->w.height;
/* update overlay if needed */
retval = 0;
if (check_btres(fh, RESOURCE_OVERLAY)) {
struct bttv_buffer *new;
new = videobuf_alloc(sizeof(*new));
bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new);
retval = bttv_switch_overlay(btv,fh,new);
}
up(&fh->cap.lock);
return retval;
}
/* ----------------------------------------------------------------------- */
static struct videobuf_queue* bttv_queue(struct bttv_fh *fh)
{
struct videobuf_queue* q = NULL;
switch (fh->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
q = &fh->cap;
break;
case V4L2_BUF_TYPE_VBI_CAPTURE:
q = &fh->vbi;
break;
default:
BUG();
}
return q;
}
static int bttv_resource(struct bttv_fh *fh)
{
int res = 0;
switch (fh->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
res = RESOURCE_VIDEO;
break;
case V4L2_BUF_TYPE_VBI_CAPTURE:
res = RESOURCE_VBI;
break;
default:
BUG();
}
return res;
}
static int bttv_switch_type(struct bttv_fh *fh, enum v4l2_buf_type type)
{
struct videobuf_queue *q = bttv_queue(fh);
int res = bttv_resource(fh);
if (check_btres(fh,res))
return -EBUSY;
if (videobuf_queue_is_busy(q))
return -EBUSY;
fh->type = type;
return 0;
}
static int bttv_g_fmt(struct bttv_fh *fh, struct v4l2_format *f)
{
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
memset(&f->fmt.pix,0,sizeof(struct v4l2_pix_format));
f->fmt.pix.width = fh->width;
f->fmt.pix.height = fh->height;
f->fmt.pix.field = fh->cap.field;
f->fmt.pix.pixelformat = fh->fmt->fourcc;
f->fmt.pix.sizeimage =
(fh->width * fh->height * fh->fmt->depth)/8;
return 0;
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
memset(&f->fmt.win,0,sizeof(struct v4l2_window));
f->fmt.win.w = fh->ov.w;
f->fmt.win.field = fh->ov.field;
return 0;
case V4L2_BUF_TYPE_VBI_CAPTURE:
bttv_vbi_fmt(fh,f);
return 0;
default:
return -EINVAL;
}
}
static int bttv_try_fmt(struct bttv_fh *fh, struct bttv *btv,
struct v4l2_format *f)
{
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
{
const struct bttv_format *fmt;
enum v4l2_field field;
int maxw,maxh;
fmt = format_by_fourcc(f->fmt.pix.pixelformat);
if (NULL == fmt)
return -EINVAL;
if (0 != f->fmt.pix.bytesperline)
/* FIXME -- not implemented yet */
return -EINVAL;
/* fixup format */
maxw = bttv_tvnorms[btv->tvnorm].swidth;
maxh = bttv_tvnorms[btv->tvnorm].sheight;
field = f->fmt.pix.field;
if (V4L2_FIELD_ANY == field)
field = (f->fmt.pix.height > maxh/2)
? V4L2_FIELD_INTERLACED
: V4L2_FIELD_BOTTOM;
if (V4L2_FIELD_SEQ_BT == field)
field = V4L2_FIELD_SEQ_TB;
switch (field) {
case V4L2_FIELD_TOP:
case V4L2_FIELD_BOTTOM:
maxh = maxh/2;
break;
case V4L2_FIELD_INTERLACED:
break;
case V4L2_FIELD_SEQ_TB:
if (fmt->flags & FORMAT_FLAGS_PLANAR)
return -EINVAL;
default:
return -EINVAL;
}
/* update data for the application */
f->fmt.pix.field = field;
if (f->fmt.pix.width < 48)
f->fmt.pix.width = 48;
if (f->fmt.pix.height < 32)
f->fmt.pix.height = 32;
if (f->fmt.pix.width > maxw)
f->fmt.pix.width = maxw;
if (f->fmt.pix.height > maxh)
f->fmt.pix.height = maxh;
f->fmt.pix.sizeimage =
(fh->width * fh->height * fmt->depth)/8;
return 0;
}
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
return verify_window(&bttv_tvnorms[btv->tvnorm],
&f->fmt.win, 1);
#if 0
case V4L2_BUF_TYPE_VBI_CAPTURE:
retval = bttv_switch_type(fh,f->type);
if (0 != retval)
return retval;
if (fh->vbi.reading || fh->vbi.streaming)
return -EBUSY;
bttv_vbi_setlines(fh,btv,f->fmt.vbi.count[0]);
bttv_vbi_fmt(fh,f);
return 0;
#endif
default:
return -EINVAL;
}
}
static int bttv_s_fmt(struct bttv_fh *fh, struct bttv *btv,
struct v4l2_format *f)
{
int retval;
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
{
const struct bttv_format *fmt;
retval = bttv_switch_type(fh,f->type);
if (0 != retval)
return retval;
retval = bttv_try_fmt(fh,btv,f);
if (0 != retval)
return retval;
fmt = format_by_fourcc(f->fmt.pix.pixelformat);
/* update our state informations */
down(&fh->cap.lock);
fh->fmt = fmt;
fh->cap.field = f->fmt.pix.field;
fh->width = f->fmt.pix.width;
fh->height = f->fmt.pix.height;
btv->init.fmt = fmt;
btv->init.width = f->fmt.pix.width;
btv->init.height = f->fmt.pix.height;
up(&fh->cap.lock);
return 0;
}
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
return setup_window(fh, btv, &f->fmt.win, 1);
case V4L2_BUF_TYPE_VBI_CAPTURE:
retval = bttv_switch_type(fh,f->type);
if (0 != retval)
return retval;
if (fh->vbi.reading || fh->vbi.streaming)
return -EBUSY;
bttv_vbi_setlines(fh,btv,f->fmt.vbi.count[0]);
bttv_vbi_fmt(fh,f);
return 0;
default:
return -EINVAL;
}
}
static int bttv_do_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, void *arg)
{
struct bttv_fh *fh = file->private_data;
struct bttv *btv = fh->btv;
unsigned long flags;
int retval;
if (bttv_debug > 1) {
switch (_IOC_TYPE(cmd)) {
case 'v':
printk("bttv%d: ioctl 0x%x (v4l1, VIDIOC%s)\n",
btv->nr, cmd, (_IOC_NR(cmd) < V4L1_IOCTLS) ?
v4l1_ioctls[_IOC_NR(cmd)] : "???");
break;
case 'V':
printk("bttv%d: ioctl 0x%x (v4l2, %s)\n",
btv->nr, cmd, v4l2_ioctl_names[_IOC_NR(cmd)]);
break;
default:
printk("bttv%d: ioctl 0x%x (???)\n",
btv->nr, cmd);
}
}
if (btv->errors)
bttv_reinit_bt848(btv);
switch (cmd) {
/* *** v4l1 *** ************************************************ */
case VIDIOCGCAP:
{
struct video_capability *cap = arg;
memset(cap,0,sizeof(*cap));
strcpy(cap->name,btv->video_dev.name);
if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) {
/* vbi */
cap->type = VID_TYPE_TUNER|VID_TYPE_TELETEXT;
} else {
/* others */
cap->type = VID_TYPE_CAPTURE|
VID_TYPE_TUNER|
VID_TYPE_OVERLAY|
VID_TYPE_CLIPPING|
VID_TYPE_SCALES;
cap->channels = bttv_tvcards[btv->type].video_inputs;
cap->audios = bttv_tvcards[btv->type].audio_inputs;
cap->maxwidth = bttv_tvnorms[btv->tvnorm].swidth;
cap->maxheight = bttv_tvnorms[btv->tvnorm].sheight;
cap->minwidth = 48;
cap->minheight = 32;
}
return 0;
}
case VIDIOCGPICT:
{
struct video_picture *pic = arg;
memset(pic,0,sizeof(*pic));
pic->brightness = btv->bright;
pic->contrast = btv->contrast;
pic->hue = btv->hue;
pic->colour = btv->saturation;
if (fh->fmt) {
pic->depth = fh->fmt->depth;
pic->palette = fh->fmt->palette;
}
return 0;
}
case VIDIOCSPICT:
{
struct video_picture *pic = arg;
const struct bttv_format *fmt;
fmt = format_by_palette(pic->palette);
if (NULL == fmt)
return -EINVAL;
down(&fh->cap.lock);
retval = -EINVAL;
if (fmt->depth != pic->depth && !sloppy)
goto fh_unlock_and_return;
fh->ovfmt = fmt;
fh->fmt = fmt;
btv->init.ovfmt = fmt;
btv->init.fmt = fmt;
if (bigendian) {
/* dirty hack time: swap bytes for overlay if the
display adaptor is big endian (insmod option) */
if (fmt->palette == VIDEO_PALETTE_RGB555 ||
fmt->palette == VIDEO_PALETTE_RGB565 ||
fmt->palette == VIDEO_PALETTE_RGB32) {
fh->ovfmt = fmt+1;
}
}
bt848_bright(btv,pic->brightness);
bt848_contrast(btv,pic->contrast);
bt848_hue(btv,pic->hue);
bt848_sat(btv,pic->colour);
up(&fh->cap.lock);
return 0;
}
case VIDIOCGWIN:
{
struct video_window *win = arg;
memset(win,0,sizeof(*win));
win->x = fh->ov.w.left;
win->y = fh->ov.w.top;
win->width = fh->ov.w.width;
win->height = fh->ov.w.height;
return 0;
}
case VIDIOCSWIN:
{
struct video_window *win = arg;
struct v4l2_window w2;
w2.field = V4L2_FIELD_ANY;
w2.w.left = win->x;
w2.w.top = win->y;
w2.w.width = win->width;
w2.w.height = win->height;
w2.clipcount = win->clipcount;
w2.clips = (struct v4l2_clip*)win->clips;
retval = setup_window(fh, btv, &w2, 0);
if (0 == retval) {
/* on v4l1 this ioctl affects the read() size too */
fh->width = fh->ov.w.width;
fh->height = fh->ov.w.height;
btv->init.width = fh->ov.w.width;
btv->init.height = fh->ov.w.height;
}
return retval;
}
case VIDIOCGFBUF:
{
struct video_buffer *fbuf = arg;
*fbuf = btv->fbuf;
return 0;
}
case VIDIOCSFBUF:
{
struct video_buffer *fbuf = arg;
const struct bttv_format *fmt;
unsigned long end;
if(!capable(CAP_SYS_ADMIN) &&
!capable(CAP_SYS_RAWIO))
return -EPERM;
end = (unsigned long)fbuf->base +
fbuf->height * fbuf->bytesperline;
down(&fh->cap.lock);
retval = -EINVAL;
if (sloppy) {
/* also set the default palette -- for backward
compatibility with older versions */
switch (fbuf->depth) {
case 8:
fmt = format_by_palette(VIDEO_PALETTE_HI240);
break;
case 16:
fmt = format_by_palette(VIDEO_PALETTE_RGB565);
break;
case 24:
fmt = format_by_palette(VIDEO_PALETTE_RGB24);
break;
case 32:
fmt = format_by_palette(VIDEO_PALETTE_RGB32);
break;
case 15:
fbuf->depth = 16;
fmt = format_by_palette(VIDEO_PALETTE_RGB555);
break;
default:
fmt = NULL;
break;
}
if (NULL == fmt)
goto fh_unlock_and_return;
fh->ovfmt = fmt;
fh->fmt = fmt;
btv->init.ovfmt = fmt;
btv->init.fmt = fmt;
} else {
if (15 == fbuf->depth)
fbuf->depth = 16;
if (fbuf->depth != 8 && fbuf->depth != 16 &&
fbuf->depth != 24 && fbuf->depth != 32)
goto fh_unlock_and_return;
}
btv->fbuf = *fbuf;
up(&fh->cap.lock);
return 0;
}
case VIDIOCCAPTURE:
case VIDIOC_OVERLAY:
{
struct bttv_buffer *new;
int *on = arg;
if (*on) {
/* verify args */
if (NULL == btv->fbuf.base)
return -EINVAL;
if (fh->ov.w.width <48 ||
fh->ov.w.height<32 ||
fh->ov.w.width >bttv_tvnorms[btv->tvnorm].swidth ||
fh->ov.w.height>bttv_tvnorms[btv->tvnorm].sheight||
NULL == fh->ovfmt)
return -EINVAL;
}
if (!check_alloc_btres(btv,fh,RESOURCE_OVERLAY))
return -EBUSY;
down(&fh->cap.lock);
if (*on) {
fh->ov.tvnorm = btv->tvnorm;
new = videobuf_alloc(sizeof(*new));
bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new);
} else {
new = NULL;
}
/* switch over */
retval = bttv_switch_overlay(btv,fh,new);
up(&fh->cap.lock);
return retval;
}
case VIDIOCGMBUF:
{
struct video_mbuf *mbuf = arg;
int i;
down(&fh->cap.lock);
retval = videobuf_mmap_setup(file,&fh->cap,gbuffers,gbufsize);
if (retval < 0)
goto fh_unlock_and_return;
memset(mbuf,0,sizeof(*mbuf));
mbuf->frames = gbuffers;
mbuf->size = gbuffers * gbufsize;
for (i = 0; i < gbuffers; i++)
mbuf->offsets[i] = i * gbufsize;
up(&fh->cap.lock);
return 0;
}
case VIDIOCMCAPTURE:
{
struct video_mmap *vm = arg;
struct bttv_buffer *buf;
enum v4l2_field field;
if (vm->frame >= VIDEO_MAX_FRAME)
return -EINVAL;
down(&fh->cap.lock);
retval = -EINVAL;
buf = (struct bttv_buffer *)fh->cap.bufs[vm->frame];
if (NULL == buf)
goto fh_unlock_and_return;
if (0 == buf->vb.baddr)
goto fh_unlock_and_return;
if (buf->vb.state == STATE_QUEUED ||
buf->vb.state == STATE_ACTIVE)
goto fh_unlock_and_return;
field = (vm->height > bttv_tvnorms[btv->tvnorm].sheight/2)
? V4L2_FIELD_INTERLACED
: V4L2_FIELD_BOTTOM;
retval = bttv_prepare_buffer(btv,buf,
format_by_palette(vm->format),
vm->width,vm->height,field);
if (0 != retval)
goto fh_unlock_and_return;
spin_lock_irqsave(&btv->s_lock,flags);
buffer_queue(file,&buf->vb);
spin_unlock_irqrestore(&btv->s_lock,flags);
up(&fh->cap.lock);
return 0;
}
case VIDIOCSYNC:
{
int *frame = arg;
struct bttv_buffer *buf;
if (*frame >= VIDEO_MAX_FRAME)
return -EINVAL;
down(&fh->cap.lock);
retval = -EINVAL;
buf = (struct bttv_buffer *)fh->cap.bufs[*frame];
if (NULL == buf)
goto fh_unlock_and_return;
retval = videobuf_waiton(&buf->vb,0,1);
if (0 != retval)
goto fh_unlock_and_return;
switch (buf->vb.state) {
case STATE_ERROR:
retval = -EIO;
/* fall through */
case STATE_DONE:
videobuf_dma_pci_sync(btv->dev,&buf->vb.dma);
bttv_dma_free(btv,buf);
break;
default:
retval = -EINVAL;
break;
}
up(&fh->cap.lock);
return retval;
}
case BTTV_VERSION:
case VIDIOCGFREQ:
case VIDIOCSFREQ:
case VIDIOCGTUNER:
case VIDIOCSTUNER:
case VIDIOCGCHAN:
case VIDIOCSCHAN:
case VIDIOCGAUDIO:
case VIDIOCSAUDIO:
return bttv_common_ioctls(btv,cmd,arg);
/* *** v4l2 *** ************************************************ */
case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = arg;
if (0 == v4l2)
return -EINVAL;
strcpy(cap->driver,"bttv");
strncpy(cap->card,btv->video_dev.name,sizeof(cap->card));
sprintf(cap->bus_info,"PCI:%s",btv->dev->slot_name);
cap->version = BTTV_VERSION_CODE;
cap->capabilities =
V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_VIDEO_OVERLAY |
V4L2_CAP_VBI_CAPTURE |
V4L2_CAP_TUNER |
V4L2_CAP_READWRITE |
V4L2_CAP_STREAMING;
return 0;
}
case VIDIOC_ENUM_FMT:
{
struct v4l2_fmtdesc *f = arg;
enum v4l2_buf_type type;
int i, index;
type = f->type;
if (V4L2_BUF_TYPE_VBI_CAPTURE == type) {
/* vbi */
index = f->index;
if (0 != index)
return -EINVAL;
memset(f,0,sizeof(*f));
f->index = index;
f->type = type;
f->pixelformat = V4L2_PIX_FMT_GREY;
strcpy(f->description,"vbi data");
return 0;
}
/* video capture + overlay */
index = -1;
for (i = 0; i < BTTV_FORMATS; i++) {
if (bttv_formats[i].fourcc != -1)
index++;
if (index == f->index)
break;
}
if (BTTV_FORMATS == i)
return -EINVAL;
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
break;
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
if (!(bttv_formats[i].flags & FORMAT_FLAGS_PACKED))
return -EINVAL;
break;
default:
return -EINVAL;
}
memset(f,0,sizeof(*f));
f->index = index;
f->type = type;
f->pixelformat = bttv_formats[i].fourcc;
strncpy(f->description,bttv_formats[i].name,31);
return 0;
}
case VIDIOC_TRY_FMT:
{
struct v4l2_format *f = arg;
return bttv_try_fmt(fh,btv,f);
}
case VIDIOC_G_FMT:
{
struct v4l2_format *f = arg;
return bttv_g_fmt(fh,f);
}
case VIDIOC_S_FMT:
{
struct v4l2_format *f = arg;
return bttv_s_fmt(fh,btv,f);
}
case VIDIOC_G_FBUF:
{
struct v4l2_framebuffer *fb = arg;
memset(fb,0,sizeof(*fb));
fb->base = btv->fbuf.base;
fb->fmt.width = btv->fbuf.width;
fb->fmt.height = btv->fbuf.height;
fb->fmt.bytesperline = btv->fbuf.bytesperline;
fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING;
if (fh->ovfmt)
fb->fmt.pixelformat = fh->ovfmt->fourcc;
return 0;
}
case VIDIOC_S_FBUF:
{
struct v4l2_framebuffer *fb = arg;
const struct bttv_format *fmt;
unsigned long end;
if(!capable(CAP_SYS_ADMIN) &&
!capable(CAP_SYS_RAWIO))
return -EPERM;
/* check args */
end = (unsigned long)fb->base +
fb->fmt.height * fb->fmt.bytesperline;
fmt = format_by_fourcc(fb->fmt.pixelformat);
if (NULL == fmt)
return -EINVAL;
if (0 == (fmt->flags & FORMAT_FLAGS_PACKED))
return -EINVAL;
down(&fh->cap.lock);
retval = -EINVAL;
if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) {
if (fb->fmt.width > bttv_tvnorms[btv->tvnorm].swidth)
goto fh_unlock_and_return;
if (fb->fmt.height > bttv_tvnorms[btv->tvnorm].sheight)
goto fh_unlock_and_return;
}
/* ok, accept it */
btv->fbuf.base = fb->base;
btv->fbuf.width = fb->fmt.width;
btv->fbuf.height = fb->fmt.height;
btv->fbuf.depth = fmt->depth;
if (0 != fb->fmt.bytesperline)
btv->fbuf.bytesperline = fb->fmt.bytesperline;
else
btv->fbuf.bytesperline = btv->fbuf.width*fmt->depth/8;
retval = 0;
fh->ovfmt = fmt;
btv->init.ovfmt = fmt;
if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) {
fh->ov.w.left = 0;
fh->ov.w.top = 0;
fh->ov.w.width = fb->fmt.width;
fh->ov.w.height = fb->fmt.height;
btv->init.ov.w.width = fb->fmt.width;
btv->init.ov.w.height = fb->fmt.height;
if (fh->ov.clips)
kfree(fh->ov.clips);
fh->ov.clips = NULL;
fh->ov.nclips = 0;
if (check_btres(fh, RESOURCE_OVERLAY)) {
struct bttv_buffer *new;
new = videobuf_alloc(sizeof(*new));
bttv_overlay_risc(btv,&fh->ov,fh->ovfmt,new);
retval = bttv_switch_overlay(btv,fh,new);
}
}
up(&fh->cap.lock);
return retval;
}
case VIDIOC_REQBUFS:
return videobuf_reqbufs(file,bttv_queue(fh),arg);
case VIDIOC_QUERYBUF:
return videobuf_querybuf(bttv_queue(fh),arg);
case VIDIOC_QBUF:
return videobuf_qbuf(file,bttv_queue(fh),arg);
case VIDIOC_DQBUF:
return videobuf_dqbuf(file,bttv_queue(fh),arg);
case VIDIOC_STREAMON:
{
int res = bttv_resource(fh);
if (!check_alloc_btres(btv,fh,res))
return -EBUSY;
return videobuf_streamon(file,bttv_queue(fh));
}
case VIDIOC_STREAMOFF:
{
int res = bttv_resource(fh);
retval = videobuf_streamoff(file,bttv_queue(fh));
if (retval < 0)
return retval;
free_btres(btv,fh,res);
return 0;
}
case VIDIOC_QUERYCTRL:
{
struct v4l2_queryctrl *c = arg;
int i;
if ((c->id < V4L2_CID_BASE ||
c->id >= V4L2_CID_LASTP1) &&
(c->id < V4L2_CID_PRIVATE_BASE ||
c->id >= V4L2_CID_PRIVATE_LASTP1))
return -EINVAL;
for (i = 0; i < BTTV_CTLS; i++)
if (bttv_ctls[i].id == c->id)
break;
if (i == BTTV_CTLS) {
*c = no_ctl;
return 0;
}
*c = bttv_ctls[i];
if (i >= 4 && i <= 8) {
struct video_audio va;
memset(&va,0,sizeof(va));
bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va);
if (btv->audio_hook)
btv->audio_hook(btv,&va,0);
switch (bttv_ctls[i].id) {
case V4L2_CID_AUDIO_VOLUME:
if (!(va.flags & VIDEO_AUDIO_VOLUME))
*c = no_ctl;
break;
case V4L2_CID_AUDIO_BALANCE:
if (!(va.flags & VIDEO_AUDIO_BALANCE))
*c = no_ctl;
break;
case V4L2_CID_AUDIO_BASS:
if (!(va.flags & VIDEO_AUDIO_BASS))
*c = no_ctl;
break;
case V4L2_CID_AUDIO_TREBLE:
if (!(va.flags & VIDEO_AUDIO_TREBLE))
*c = no_ctl;
break;
}
}
return 0;
}
case VIDIOC_G_CTRL:
return get_control(btv,arg);
case VIDIOC_S_CTRL:
return set_control(btv,arg);
case VIDIOC_G_PARM:
{
struct v4l2_streamparm *parm = arg;
struct v4l2_standard s;
if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
memset(parm,0,sizeof(*parm));
v4l2_video_std_construct(&s, bttv_tvnorms[btv->tvnorm].v4l2_id,
bttv_tvnorms[btv->tvnorm].name);
parm->parm.capture.timeperframe = s.frameperiod;
return 0;
}
case VIDIOC_ENUMSTD:
case VIDIOC_G_STD:
case VIDIOC_S_STD:
case VIDIOC_ENUMINPUT:
case VIDIOC_G_INPUT:
case VIDIOC_S_INPUT:
case VIDIOC_G_TUNER:
case VIDIOC_S_TUNER:
case VIDIOC_G_FREQUENCY:
case VIDIOC_S_FREQUENCY:
return bttv_common_ioctls(btv,cmd,arg);
default:
return -ENOIOCTLCMD;
}
return 0;
fh_unlock_and_return:
up(&fh->cap.lock);
return retval;
}
static int bttv_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct bttv_fh *fh = file->private_data;
switch (cmd) {
case BTTV_VBISIZE:
bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE);
return fh->lines * 2 * 2048;
default:
return video_usercopy(inode, file, cmd, arg, bttv_do_ioctl);
}
}
static ssize_t bttv_read(struct file *file, char *data,
size_t count, loff_t *ppos)
{
struct bttv_fh *fh = file->private_data;
int retval = 0;
if (fh->btv->errors)
bttv_reinit_bt848(fh->btv);
dprintk("read count=%d type=%s\n",
(int)count,v4l2_type_names[fh->type]);
switch (fh->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
if (locked_btres(fh->btv,RESOURCE_VIDEO))
return -EBUSY;
retval = videobuf_read_one(file, &fh->cap, data, count, ppos);
break;
case V4L2_BUF_TYPE_VBI_CAPTURE:
if (!check_alloc_btres(fh->btv,fh,RESOURCE_VBI))
return -EBUSY;
retval = videobuf_read_stream(file, &fh->vbi, data, count, ppos, 1);
break;
default:
BUG();
}
return retval;
}
static unsigned int bttv_poll(struct file *file, poll_table *wait)
{
struct bttv_fh *fh = file->private_data;
struct bttv_buffer *buf;
if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) {
if (!check_alloc_btres(fh->btv,fh,RESOURCE_VBI))
return -EBUSY;
return videobuf_poll_stream(file, &fh->vbi, wait);
}
if (check_btres(fh,RESOURCE_VIDEO)) {
/* streaming capture */
if (list_empty(&fh->cap.stream))
return POLLERR;
buf = list_entry(fh->cap.stream.next,struct bttv_buffer,vb.stream);
} else {
/* read() capture */
down(&fh->cap.lock);
if (NULL == fh->cap.read_buf) {
/* need to capture a new frame */
if (locked_btres(fh->btv,RESOURCE_VIDEO)) {
up(&fh->cap.lock);
return POLLERR;
}
fh->cap.read_buf = videobuf_alloc(fh->cap.msize);
if (NULL == fh->cap.read_buf) {
up(&fh->cap.lock);
return POLLERR;
}
if (0 != fh->cap.ops->buf_prepare(file,fh->cap.read_buf,fh->cap.field)) {
up(&fh->cap.lock);
return POLLERR;
}
fh->cap.ops->buf_queue(file,fh->cap.read_buf);
fh->cap.read_off = 0;
}
up(&fh->cap.lock);
buf = (struct bttv_buffer*)fh->cap.read_buf;
}
poll_wait(file, &buf->vb.done, wait);
if (buf->vb.state == STATE_DONE ||
buf->vb.state == STATE_ERROR)
return POLLIN|POLLRDNORM;
return 0;
}
static int bttv_open(struct inode *inode, struct file *file)
{
unsigned int minor = minor(inode->i_rdev);
struct bttv *btv = NULL;
struct bttv_fh *fh;
enum v4l2_buf_type type = 0;
int i;
dprintk(KERN_DEBUG "bttv: open minor=%d\n",minor);
for (i = 0; i < bttv_num; i++) {
if (bttvs[i].video_dev.minor == minor) {
btv = &bttvs[i];
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
break;
}
if (bttvs[i].vbi_dev.minor == minor) {
btv = &bttvs[i];
type = V4L2_BUF_TYPE_VBI_CAPTURE;
break;
}
}
if (NULL == btv)
return -ENODEV;
dprintk(KERN_DEBUG "bttv%d: open called (type=%s)\n",
btv->nr,v4l2_type_names[type]);
/* allocate per filehandle data */
fh = kmalloc(sizeof(*fh),GFP_KERNEL);
if (NULL == fh)
return -ENOMEM;
file->private_data = fh;
*fh = btv->init;
fh->type = type;
videobuf_queue_init(&fh->cap, &bttv_video_qops,
btv->dev, &btv->s_lock,
V4L2_BUF_TYPE_VIDEO_CAPTURE,
V4L2_FIELD_INTERLACED,
sizeof(struct bttv_buffer));
videobuf_queue_init(&fh->vbi, &bttv_vbi_qops,
btv->dev, &btv->s_lock,
V4L2_BUF_TYPE_VBI_CAPTURE,
V4L2_FIELD_SEQ_TB,
sizeof(struct bttv_buffer));
i2c_vidiocschan(btv);
btv->users++;
bttv_field_count(btv);
return 0;
}
static int bttv_release(struct inode *inode, struct file *file)
{
struct bttv_fh *fh = file->private_data;
struct bttv *btv = fh->btv;
/* turn off overlay */
if (check_btres(fh, RESOURCE_OVERLAY))
bttv_switch_overlay(btv,fh,NULL);
/* stop video capture */
if (check_btres(fh, RESOURCE_VIDEO)) {
videobuf_streamoff(file,&fh->cap);
free_btres(btv,fh,RESOURCE_VIDEO);
}
if (fh->cap.read_buf) {
buffer_release(file,fh->cap.read_buf);
kfree(fh->cap.read_buf);
}
/* stop vbi capture */
if (check_btres(fh, RESOURCE_VBI)) {
if (fh->vbi.streaming)
videobuf_streamoff(file,&fh->vbi);
if (fh->vbi.reading)
videobuf_read_stop(file,&fh->vbi);
free_btres(btv,fh,RESOURCE_VBI);
}
file->private_data = NULL;
kfree(fh);
btv->users--;
bttv_field_count(btv);
return 0;
}
static int
bttv_mmap(struct file *file, struct vm_area_struct *vma)
{
struct bttv_fh *fh = file->private_data;
dprintk("bttv%d: mmap type=%s 0x%lx+%ld\n",
fh->btv->nr, v4l2_type_names[fh->type],
vma->vm_start, vma->vm_end - vma->vm_start);
return videobuf_mmap_mapper(vma,bttv_queue(fh));
}
static struct file_operations bttv_fops =
{
.owner = THIS_MODULE,
.open = bttv_open,
.release = bttv_release,
.ioctl = bttv_ioctl,
.llseek = no_llseek,
.read = bttv_read,
.mmap = bttv_mmap,
.poll = bttv_poll,
};
static struct video_device bttv_video_template =
{
.name = "UNSET",
type: VID_TYPE_CAPTURE|VID_TYPE_TUNER|VID_TYPE_OVERLAY|
VID_TYPE_CLIPPING|VID_TYPE_SCALES,
.hardware = VID_HARDWARE_BT848,
.fops = &bttv_fops,
.minor = -1,
};
struct video_device bttv_vbi_template =
{
.name = "bt848/878 vbi",
.type = VID_TYPE_TUNER|VID_TYPE_TELETEXT,
.hardware = VID_HARDWARE_BT848,
.fops = &bttv_fops,
.minor = -1,
};
/* ----------------------------------------------------------------------- */
/* radio interface */
static int radio_open(struct inode *inode, struct file *file)
{
unsigned int minor = minor(inode->i_rdev);
struct bttv *btv = NULL;
unsigned long v = 400*16;
int i;
dprintk("bttv: open minor=%d\n",minor);
for (i = 0; i < bttv_num; i++) {
if (bttvs[i].radio_dev.minor == minor) {
btv = &bttvs[i];
break;
}
}
if (NULL == btv)
return -ENODEV;
dprintk("bttv%d: open called (radio)\n",btv->nr);
down(&btv->lock);
if (btv->radio_user) {
up(&btv->lock);
return -EBUSY;
}
btv->radio_user++;
file->private_data = btv;
i2c_vidiocschan(btv);
bttv_call_i2c_clients(btv,VIDIOCSFREQ,&v);
bttv_call_i2c_clients(btv,AUDC_SET_RADIO,&btv->tuner_type);
audio_mux(btv,AUDIO_RADIO);
up(&btv->lock);
return 0;
}
static int radio_release(struct inode *inode, struct file *file)
{
struct bttv *btv = file->private_data;
btv->radio_user--;
return 0;
}
static int radio_do_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, void *arg)
{
struct bttv *btv = file->private_data;
switch (cmd) {
case VIDIOCGCAP:
{
struct video_capability *cap = arg;
memset(cap,0,sizeof(*cap));
strcpy(cap->name,btv->radio_dev.name);
cap->type = VID_TYPE_TUNER;
cap->channels = 1;
cap->audios = 1;
return 0;
}
case VIDIOCGTUNER:
{
struct video_tuner *v = arg;
if(v->tuner)
return -EINVAL;
memset(v,0,sizeof(*v));
strcpy(v->name, "Radio");
/* japan: 76.0 MHz - 89.9 MHz
western europe: 87.5 MHz - 108.0 MHz
russia: 65.0 MHz - 108.0 MHz */
v->rangelow=(int)(65*16);
v->rangehigh=(int)(108*16);
bttv_call_i2c_clients(btv,cmd,v);
return 0;
}
case VIDIOCSTUNER:
/* nothing to do */
return 0;
case BTTV_VERSION:
case VIDIOCGFREQ:
case VIDIOCSFREQ:
case VIDIOCGAUDIO:
case VIDIOCSAUDIO:
return bttv_common_ioctls(btv,cmd,arg);
default:
return -ENOIOCTLCMD;
}
return 0;
}
static int radio_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
return video_usercopy(inode, file, cmd, arg, radio_do_ioctl);
}
static struct file_operations radio_fops =
{
.owner = THIS_MODULE,
.open = radio_open,
.release = radio_release,
.ioctl = radio_ioctl,
.llseek = no_llseek,
};
static struct video_device radio_template =
{
.name = "bt848/878 radio",
.type = VID_TYPE_TUNER,
.hardware = VID_HARDWARE_BT848,
.fops = &radio_fops,
.minor = -1,
};
/* ----------------------------------------------------------------------- */
/* irq handler */
static char *irq_name[] = { "FMTCHG", "VSYNC", "HSYNC", "OFLOW", "HLOCK",
"VPRES", "6", "7", "I2CDONE", "GPINT", "10",
"RISCI", "FBUS", "FTRGT", "FDSR", "PPERR",
"RIPERR", "PABORT", "OCERR", "SCERR" };
static void bttv_print_irqbits(u32 print, u32 mark)
{
int i;
printk("bits:");
for (i = 0; i < (sizeof(irq_name)/sizeof(char*)); i++) {
if (print & (1 << i))
printk(" %s",irq_name[i]);
if (mark & (1 << i))
printk("*");
}
}
static void bttv_print_riscaddr(struct bttv *btv)
{
printk(" main: %08Lx\n",
(u64)btv->main.dma);
printk(" vbi : o=%08Lx e=%08Lx\n",
btv->vcurr ? (u64)btv->vcurr->top.dma : 0,
btv->vcurr ? (u64)btv->vcurr->bottom.dma : 0);
printk(" cap : o=%08Lx e=%08Lx\n",
btv->top ? (u64)btv->top->top.dma : 0,
btv->bottom ? (u64)btv->bottom->bottom.dma : 0);
printk(" scr : o=%08Lx e=%08Lx\n",
btv->screen ? (u64)btv->screen->top.dma : 0,
btv->screen ? (u64)btv->screen->bottom.dma : 0);
}
static void bttv_irq_timeout(unsigned long data)
{
struct bttv *btv = (struct bttv *)data;
struct bttv_buffer *o_bottom,*o_top,*o_vcurr;
struct bttv_buffer *capture;
if (bttv_verbose) {
printk(KERN_INFO "bttv%d: timeout: risc=%08x, ",
btv->nr,btread(BT848_RISC_COUNT));
bttv_print_irqbits(btread(BT848_INT_STAT),0);
printk("\n");
}
spin_lock(&btv->s_lock);
o_top = btv->top;
o_bottom = btv->bottom;
o_vcurr = btv->vcurr;
btv->top = NULL;
btv->bottom = NULL;
btv->vcurr = NULL;
/* deactivate stuff */
bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0);
bttv_risc_hook(btv, RISC_SLOT_E_FIELD, NULL, 0);
bttv_risc_hook(btv, RISC_SLOT_O_VBI, NULL, 0);
bttv_risc_hook(btv, RISC_SLOT_E_VBI, NULL, 0);
bttv_set_dma(btv, 0, 0);
/* wake up + free */
if (o_top == o_bottom) {
if (NULL != o_top) {
o_top->vb.state = STATE_ERROR;
wake_up(&o_top->vb.done);
}
} else {
if (NULL != o_top) {
o_top->vb.state = STATE_ERROR;
wake_up(&o_top->vb.done);
}
if (NULL != o_bottom) {
o_bottom->vb.state = STATE_ERROR;
wake_up(&o_bottom->vb.done);
}
}
if (NULL != o_vcurr) {
o_vcurr->vb.state = STATE_ERROR;
wake_up(&o_vcurr->vb.done);
}
/* cancel all outstanding capture / vbi requests */
while (!list_empty(&btv->capture)) {
capture = list_entry(btv->capture.next, struct bttv_buffer, vb.queue);
list_del(&capture->vb.queue);
capture->vb.state = STATE_ERROR;
wake_up(&capture->vb.done);
}
while (!list_empty(&btv->vcapture)) {
capture = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue);
list_del(&capture->vb.queue);
capture->vb.state = STATE_ERROR;
wake_up(&capture->vb.done);
}
btv->errors++;
spin_unlock(&btv->s_lock);
}
static void
bttv_irq_switch_fields(struct bttv *btv)
{
struct bttv_buffer *o_bottom,*o_top,*o_vcurr;
struct bttv_buffer *capture;
dma_addr_t rc;
int irqflags = 0;
struct timeval ts;
spin_lock(&btv->s_lock);
o_top = btv->top;
o_bottom = btv->bottom;
o_vcurr = btv->vcurr;
btv->top = NULL;
btv->bottom = NULL;
btv->vcurr = NULL;
/* vbi request ? */
if (!list_empty(&btv->vcapture)) {
irqflags = 1;
btv->vcurr = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue);
list_del(&btv->vcurr->vb.queue);
}
/* capture request ? */
if (!list_empty(&btv->capture)) {
irqflags = 1;
capture = list_entry(btv->capture.next, struct bttv_buffer, vb.queue);
list_del(&capture->vb.queue);
if (V4L2_FIELD_HAS_TOP(capture->vb.field))
btv->top = capture;
if (V4L2_FIELD_HAS_BOTTOM(capture->vb.field))
btv->bottom = capture;
/* capture request for other field ? */
if (!V4L2_FIELD_HAS_BOTH(capture->vb.field) &&
!list_empty(&btv->capture)) {
capture = list_entry(btv->capture.next, struct bttv_buffer, vb.queue);
if (!V4L2_FIELD_HAS_BOTH(capture->vb.field)) {
if (NULL == btv->top &&
V4L2_FIELD_TOP == capture->vb.field) {
btv->top = capture;
list_del(&capture->vb.queue);
}
if (NULL == btv->bottom &&
V4L2_FIELD_BOTTOM == capture->vb.field) {
btv->bottom = capture;
list_del(&capture->vb.queue);
}
}
}
}
/* screen overlay ? */
if (NULL != btv->screen) {
if (V4L2_FIELD_HAS_BOTH(btv->screen->vb.field)) {
if (NULL == btv->top && NULL == btv->bottom) {
btv->top = btv->screen;
btv->bottom = btv->screen;
}
} else {
if (V4L2_FIELD_TOP == btv->screen->vb.field &&
NULL == btv->top) {
btv->top = btv->screen;
}
if (V4L2_FIELD_BOTTOM == btv->screen->vb.field &&
NULL == btv->bottom) {
btv->bottom = btv->screen;
}
}
}
if (irq_debug)
printk(KERN_DEBUG "bttv%d: irq top=0x%08x bottom=0x%08x"
" vbi=0x%08x/0x%08x\n", btv->nr,
btv->top ? btv->top->top.dma : 0,
btv->bottom ? btv->bottom->bottom.dma : 0,
btv->vcurr ? btv->vcurr->top.dma : 0,
btv->vcurr ? btv->vcurr->bottom.dma : 0);
rc = btread(BT848_RISC_COUNT);
if (rc < btv->main.dma || rc > btv->main.dma + 0x100)
printk("bttv%d: Huh? IRQ latency? main=0x%x rc=0x%x\n",
btv->nr,btv->main.dma,rc);
/* activate new fields */
bttv_buffer_activate(btv,btv->top,btv->bottom);
if (btv->vcurr) {
btv->vcurr->vb.state = STATE_ACTIVE;
bttv_risc_hook(btv, RISC_SLOT_O_VBI, &btv->vcurr->top, 0);
bttv_risc_hook(btv, RISC_SLOT_E_VBI, &btv->vcurr->bottom, 0);
} else {
bttv_risc_hook(btv, RISC_SLOT_O_VBI, NULL, 0);
bttv_risc_hook(btv, RISC_SLOT_E_VBI, NULL, 0);
}
bttv_set_dma(btv, 0, irqflags);
/* wake up + free */
do_gettimeofday(&ts);
if (o_top == o_bottom) {
if (NULL != o_top && btv->top != o_top) {
o_top->vb.ts = ts;
o_top->vb.field_count = btv->field_count;
o_top->vb.state = STATE_DONE;
wake_up(&o_top->vb.done);
}
} else {
if (NULL != o_top && btv->top != o_top) {
o_top->vb.ts = ts;
o_top->vb.field_count = btv->field_count;
o_top->vb.state = STATE_DONE;
wake_up(&o_top->vb.done);
}
if (NULL != o_bottom && btv->bottom != o_bottom) {
o_bottom->vb.ts = ts;
o_bottom->vb.field_count = btv->field_count;
o_bottom->vb.state = STATE_DONE;
wake_up(&o_bottom->vb.done);
}
}
if (NULL != o_vcurr) {
o_vcurr->vb.ts = ts;
o_vcurr->vb.field_count = btv->field_count;
o_vcurr->vb.state = STATE_DONE;
wake_up(&o_vcurr->vb.done);
}
spin_unlock(&btv->s_lock);
}
static void bttv_irq(int irq, void *dev_id, struct pt_regs * regs)
{
u32 stat,astat;
u32 dstat;
int count;
struct bttv *btv;
btv=(struct bttv *)dev_id;
count=0;
while (1)
{
/* get/clear interrupt status bits */
stat=btread(BT848_INT_STAT);
astat=stat&btread(BT848_INT_MASK);
if (!astat)
return;
btwrite(stat,BT848_INT_STAT);
/* get device status bits */
dstat=btread(BT848_DSTATUS);
if (irq_debug) {
printk(KERN_DEBUG "bttv%d: irq loop=%d fc=%d "
"riscs=%x, riscc=%08x, ",
btv->nr, count, btv->field_count,
stat>>28, btread(BT848_RISC_COUNT));
bttv_print_irqbits(stat,astat);
if (stat & BT848_INT_HLOCK)
printk(" HLOC => %s", (dstat & BT848_DSTATUS_HLOC)
? "yes" : "no");
if (stat & BT848_INT_VPRES)
printk(" PRES => %s", (dstat & BT848_DSTATUS_PRES)
? "yes" : "no");
if (stat & BT848_INT_FMTCHG)
printk(" NUML => %s", (dstat & BT848_DSTATUS_NUML)
? "625" : "525");
printk("\n");
}
if (astat&BT848_INT_VSYNC)
btv->field_count++;
if (astat & BT848_INT_GPINT)
wake_up(&btv->gpioq);
if ((astat & BT848_INT_RISCI) && (stat & (1<<28)))
bttv_irq_switch_fields(btv);
if ((astat & BT848_INT_HLOCK) && btv->opt_automute)
audio_mux(btv, -1);
if (astat & (BT848_INT_SCERR|BT848_INT_OCERR)) {
printk(KERN_INFO "bttv%d: %s%s @ %08x,",btv->nr,
(astat & BT848_INT_SCERR) ? "SCERR" : "",
(astat & BT848_INT_OCERR) ? "OCERR" : "",
btread(BT848_RISC_COUNT));
bttv_print_irqbits(stat,astat);
printk("\n");
if (bttv_debug)
bttv_print_riscaddr(btv);
}
if (fdsr && astat & BT848_INT_FDSR) {
printk(KERN_INFO "bttv%d: FDSR @ %08x\n",
btv->nr,btread(BT848_RISC_COUNT));
if (bttv_debug)
bttv_print_riscaddr(btv);
}
count++;
if (count > 20) {
btwrite(0, BT848_INT_MASK);
printk(KERN_ERR
"bttv%d: IRQ lockup, cleared int mask\n", btv->nr);
}
}
}
/* ----------------------------------------------------------------------- */
/* initialitation */
/* register video4linux devices */
static int __devinit bttv_register_video(struct bttv *btv)
{
if(video_register_device(&btv->video_dev,VFL_TYPE_GRABBER,video_nr)<0)
return -1;
printk(KERN_INFO "bttv%d: registered device video%d\n",
btv->nr,btv->video_dev.minor & 0x1f);
if(video_register_device(&btv->vbi_dev,VFL_TYPE_VBI,vbi_nr)<0) {
video_unregister_device(&btv->video_dev);
return -1;
}
printk(KERN_INFO "bttv%d: registered device vbi%d\n",
btv->nr,btv->vbi_dev.minor & 0x1f);
if (!btv->has_radio)
return 0;
if (video_register_device(&btv->radio_dev, VFL_TYPE_RADIO,radio_nr)<0) {
video_unregister_device(&btv->vbi_dev);
video_unregister_device(&btv->video_dev);
return -1;
}
printk(KERN_INFO "bttv%d: registered device radio%d\n",
btv->nr,btv->radio_dev.minor & 0x1f);
return 0;
}
/* on OpenFirmware machines (PowerMac at least), PCI memory cycle */
/* response on cards with no firmware is not enabled by OF */
static void pci_set_command(struct pci_dev *dev)
{
#if defined(__powerpc__)
unsigned int cmd;
pci_read_config_dword(dev, PCI_COMMAND, &cmd);
cmd = (cmd | PCI_COMMAND_MEMORY );
pci_write_config_dword(dev, PCI_COMMAND, cmd);
#endif
}
static int __devinit bttv_probe(struct pci_dev *dev,
const struct pci_device_id *pci_id)
{
int result;
unsigned char lat;
struct bttv *btv;
if (bttv_num == BTTV_MAX)
return -ENOMEM;
printk(KERN_INFO "bttv: Bt8xx card found (%d).\n", bttv_num);
btv=&bttvs[bttv_num];
memset(btv,0,sizeof(*btv));
btv->nr = bttv_num;
sprintf(btv->name,"bttv%d",btv->nr);
/* initialize structs / fill in defaults */
init_MUTEX(&btv->lock);
init_MUTEX(&btv->reslock);
btv->s_lock = SPIN_LOCK_UNLOCKED;
init_waitqueue_head(&btv->gpioq);
INIT_LIST_HEAD(&btv->capture);
INIT_LIST_HEAD(&btv->vcapture);
init_timer(&btv->timeout);
btv->timeout.function = bttv_irq_timeout;
btv->timeout.data = (unsigned long)btv;
btv->i2c_rc = -1;
btv->tuner_type = -1;
memcpy(&btv->video_dev, &bttv_video_template, sizeof(bttv_video_template));
memcpy(&btv->radio_dev, &radio_template, sizeof(radio_template));
memcpy(&btv->vbi_dev, &bttv_vbi_template, sizeof(bttv_vbi_template));
btv->video_dev.minor = -1;
btv->video_dev.priv = btv;
btv->radio_dev.minor = -1;
btv->radio_dev.priv = btv;
btv->vbi_dev.minor = -1;
btv->vbi_dev.priv = btv;
btv->has_radio=radio[btv->nr];
/* pci stuff (init, get irq/mmip, ... */
btv->dev = dev;
btv->id = dev->device;
if (pci_enable_device(dev)) {
printk(KERN_WARNING "bttv%d: Can't enable device.\n",
btv->nr);
return -EIO;
}
if (pci_set_dma_mask(dev, 0xffffffff)) {
printk(KERN_WARNING "bttv%d: No suitable DMA available.\n",
btv->nr);
return -EIO;
}
if (!request_mem_region(pci_resource_start(dev,0),
pci_resource_len(dev,0),
btv->name)) {
return -EBUSY;
}
pci_set_master(dev);
pci_set_command(dev);
pci_set_drvdata(dev,btv);
if (!pci_dma_supported(dev,0xffffffff)) {
printk("bttv: Oops: no 32bit PCI DMA ???\n");
result = -EIO;
goto fail1;
}
pci_read_config_byte(dev, PCI_CLASS_REVISION, &btv->revision);
pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat);
printk(KERN_INFO "bttv%d: Bt%d (rev %d) at %s, ",
bttv_num,btv->id, btv->revision, dev->slot_name);
printk("irq: %d, latency: %d, mmio: 0x%lx\n",
btv->dev->irq, lat, pci_resource_start(dev,0));
#ifdef __sparc__
/* why no ioremap for sparc? */
btv->bt848_mmio=(unsigned char *)pci_resource_start(dev,0);
#else
btv->bt848_mmio=ioremap(pci_resource_start(dev,0), 0x1000);
#endif
/* identify card */
bttv_idcard(btv);
/* disable irqs, register irq handler */
btwrite(0, BT848_INT_MASK);
result = request_irq(btv->dev->irq, bttv_irq,
SA_SHIRQ | SA_INTERRUPT,btv->name,(void *)btv);
if (result < 0) {
printk(KERN_ERR "bttv%d: can't get IRQ %d\n",
bttv_num,btv->dev->irq);
goto fail1;
}
if (0 != bttv_handle_chipset(btv)) {
result = -EIO;
goto fail2;
}
/* init options from insmod args */
btv->opt_combfilter = combfilter;
btv->opt_lumafilter = lumafilter;
btv->opt_automute = automute;
btv->opt_chroma_agc = chroma_agc;
btv->opt_adc_crush = adc_crush;
/* fill struct bttv with some useful defaults */
btv->init.btv = btv;
btv->init.ov.w.width = 320;
btv->init.ov.w.height = 240;
btv->init.fmt = format_by_palette(VIDEO_PALETTE_RGB24);
btv->init.width = 320;
btv->init.height = 240;
btv->init.lines = 16;
btv->input = 0;
/* initialize hardware */
if (bttv_gpio)
bttv_gpio_tracking(btv,"pre-init");
bttv_risc_init_main(btv);
init_bt848(btv);
/* gpio */
btwrite(0x00, BT848_GPIO_REG_INP);
btwrite(0x00, BT848_GPIO_OUT_EN);
if (bttv_gpio)
bttv_gpio_tracking(btv,"init");
/* interrupt */
btwrite(0xfffffUL, BT848_INT_STAT);
btwrite((btv->triton1) |
(gpint ? BT848_INT_GPINT : 0) |
BT848_INT_SCERR|
(fdsr ? BT848_INT_FDSR : 0) |
BT848_INT_RISCI|BT848_INT_OCERR|BT848_INT_VPRES|
BT848_INT_FMTCHG|BT848_INT_HLOCK,
BT848_INT_MASK);
/* needs to be done before i2c is registered */
bttv_init_card1(btv);
/* register i2c */
init_bttv_i2c(btv);
/* some card-specific stuff (needs working i2c) */
bttv_init_card2(btv);
/* register video4linux */
bttv_register_video(btv);
bt848_bright(btv,32768);
bt848_contrast(btv,32768);
bt848_hue(btv,32768);
bt848_sat(btv,32768);
audio_mux(btv,AUDIO_MUTE);
set_input(btv,0);
/* everything is fine */
bttv_num++;
return 0;
fail2:
free_irq(btv->dev->irq,btv);
fail1:
release_mem_region(pci_resource_start(btv->dev,0),
pci_resource_len(btv->dev,0));
pci_set_drvdata(dev,NULL);
return result;
}
static void __devexit bttv_remove(struct pci_dev *pci_dev)
{
struct bttv *btv = pci_get_drvdata(pci_dev);
if (bttv_verbose)
printk("bttv%d: unloading\n",btv->nr);
/* shutdown everything (DMA+IRQs) */
btand(~15, BT848_GPIO_DMA_CTL);
btwrite(0, BT848_INT_MASK);
btwrite(~0x0, BT848_INT_STAT);
btwrite(0x0, BT848_GPIO_OUT_EN);
if (bttv_gpio)
bttv_gpio_tracking(btv,"cleanup");
/* tell gpio modules we are leaving ... */
btv->shutdown=1;
wake_up(&btv->gpioq);
/* unregister i2c_bus */
if (0 == btv->i2c_rc)
i2c_bit_del_bus(&btv->i2c_adap);
/* unregister video4linux */
if (btv->video_dev.minor!=-1)
video_unregister_device(&btv->video_dev);
if (btv->radio_dev.minor!=-1)
video_unregister_device(&btv->radio_dev);
if (btv->vbi_dev.minor!=-1)
video_unregister_device(&btv->vbi_dev);
/* free allocated memory */
bttv_riscmem_free(btv->dev,&btv->main);
/* free ressources */
free_irq(btv->dev->irq,btv);
release_mem_region(pci_resource_start(btv->dev,0),
pci_resource_len(btv->dev,0));
pci_set_drvdata(pci_dev, NULL);
return;
}
static struct pci_device_id bttv_pci_tbl[] __devinitdata = {
{PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT848,
PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
{PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT849,
PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
{PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT878,
PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
{PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT879,
PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
{0,}
};
MODULE_DEVICE_TABLE(pci, bttv_pci_tbl);
static struct pci_driver bttv_pci_driver = {
.name = "bttv",
.id_table = bttv_pci_tbl,
.probe = bttv_probe,
.remove = __devexit_p(bttv_remove),
};
static int bttv_init_module(void)
{
bttv_num = 0;
printk(KERN_INFO "bttv: driver version %d.%d.%d loaded\n",
(BTTV_VERSION_CODE >> 16) & 0xff,
(BTTV_VERSION_CODE >> 8) & 0xff,
BTTV_VERSION_CODE & 0xff);
if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME)
gbuffers = 2;
if (gbufsize < 0 || gbufsize > BTTV_MAX_FBUF)
gbufsize = BTTV_MAX_FBUF;
gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK;
if (bttv_verbose)
printk(KERN_INFO "bttv: using %d buffers with %dk (%d pages) each for capture\n",
gbuffers, gbufsize >> 10, gbufsize >> PAGE_SHIFT);
bttv_check_chipset();
return pci_module_init(&bttv_pci_driver);
}
static void bttv_cleanup_module(void)
{
pci_unregister_driver(&bttv_pci_driver);
return;
}
module_init(bttv_init_module);
module_exit(bttv_cleanup_module);
/*
* Local variables:
* c-basic-offset: 8
* End:
*/