/* bttv - Bt848 frame grabber driver Copyright (C) 1996,97,98 Ralph Metzler & Marcus Metzler (c) 1999-2002 Gerd Knorr some v4l2 code lines are taken from Justin's bttv2 driver which is (c) 2000 Justin Schoeman 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "bttvp.h" #include "rds.h" unsigned 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 */ #ifdef __BIG_ENDIAN static unsigned int bigendian=1; #else static unsigned int bigendian=0; #endif static unsigned int radio[BTTV_MAX]; 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 int debug_latency = 0; static unsigned int fdsr = 0; /* 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; static unsigned int whitecrush_upper = 0xCF; static unsigned int whitecrush_lower = 0x7F; static unsigned int vcr_hack = 0; static unsigned int irq_iswitch = 0; static unsigned int uv_ratio = 50; static unsigned int full_luma_range = 0; static unsigned int coring = 0; extern int no_overlay; /* API features (turn on/off stuff for testing) */ static unsigned int v4l2 = 1; /* insmod args */ module_param(bttv_verbose, int, 0644); module_param(bttv_gpio, int, 0644); module_param(bttv_debug, int, 0644); module_param(irq_debug, int, 0644); module_param(debug_latency, int, 0644); module_param(fdsr, int, 0444); module_param(video_nr, int, 0444); module_param(radio_nr, int, 0444); module_param(vbi_nr, int, 0444); module_param(gbuffers, int, 0444); module_param(gbufsize, int, 0444); module_param(v4l2, int, 0644); module_param(bigendian, int, 0644); module_param(irq_iswitch, int, 0644); module_param(combfilter, int, 0444); module_param(lumafilter, int, 0444); module_param(automute, int, 0444); module_param(chroma_agc, int, 0444); module_param(adc_crush, int, 0444); module_param(whitecrush_upper, int, 0444); module_param(whitecrush_lower, int, 0444); module_param(vcr_hack, int, 0444); module_param(uv_ratio, int, 0444); module_param(full_luma_range, int, 0444); module_param(coring, int, 0444); module_param_array(radio, int, NULL, 0444); MODULE_PARM_DESC(radio,"The TV card supports radio, default is 0 (no)"); MODULE_PARM_DESC(bigendian,"byte order of the framebuffer, default is native endian"); MODULE_PARM_DESC(bttv_verbose,"verbose startup messages, default is 1 (yes)"); MODULE_PARM_DESC(bttv_gpio,"log gpio changes, default is 0 (no)"); MODULE_PARM_DESC(bttv_debug,"debug messages, default is 0 (no)"); MODULE_PARM_DESC(irq_debug,"irq handler debug messages, default is 0 (no)"); MODULE_PARM_DESC(gbuffers,"number of capture buffers. range 2-32, default 8"); MODULE_PARM_DESC(gbufsize,"size of the capture buffers, default is 0x208000"); MODULE_PARM_DESC(automute,"mute audio on bad/missing video signal, default is 1 (yes)"); MODULE_PARM_DESC(chroma_agc,"enables the AGC of chroma signal, default is 0 (no)"); MODULE_PARM_DESC(adc_crush,"enables the luminance ADC crush, default is 1 (yes)"); MODULE_PARM_DESC(whitecrush_upper,"sets the white crush upper value, default is 207"); MODULE_PARM_DESC(whitecrush_lower,"sets the white crush lower value, default is 127"); MODULE_PARM_DESC(vcr_hack,"enables the VCR hack (improves synch on poor VCR tapes), default is 0 (no)"); MODULE_PARM_DESC(irq_iswitch,"switch inputs in irq handler"); MODULE_PARM_DESC(uv_ratio,"ratio between u and v gains, default is 50"); MODULE_PARM_DESC(full_luma_range,"use the full luma range, default is 0 (no)"); MODULE_PARM_DESC(coring,"set the luma coring level, default is 0 (no)"); MODULE_DESCRIPTION("bttv - v4l/v4l2 driver module for bt848/878 based cards"); MODULE_AUTHOR("Ralph Metzler & Marcus Metzler & Gerd Knorr"); MODULE_LICENSE("GPL"); /* ----------------------------------------------------------------------- */ /* sysfs */ static ssize_t show_card(struct class_device *cd, char *buf) { struct video_device *vfd = to_video_device(cd); struct bttv *btv = dev_get_drvdata(vfd->dev); return sprintf(buf, "%d\n", btv ? btv->c.type : UNSET); } static CLASS_DEVICE_ATTR(card, S_IRUGO, show_card, NULL); /* ----------------------------------------------------------------------- */ /* 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, },{ /* that one hopefully works with the strange timing * which video recorders produce when playing a NTSC * tape on a PAL TV ... */ .v4l2_id = V4L2_STD_PAL_60, .name = "PAL-60", .Fsc = 35468950, .swidth = 924, .sheight = 480, .totalwidth = 1135, .adelay = 0x7f, .bdelay = 0x72, .iform = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1), .scaledtwidth = 1135, .hdelayx1 = 186, .hactivex1 = 924, .vdelay = 0x1a, .vbipack = 255, .vtotal = 524, .sram = -1, } }; static const unsigned int BTTV_TVNORMS = ARRAY_SIZE(bttv_tvnorms); /* ----------------------------------------------------------------------- */ /* bttv format list packed pixel formats must come first */ static 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, } }; static const unsigned int BTTV_FORMATS = ARRAY_SIZE(bttv_formats); /* ----------------------------------------------------------------------- */ #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_VCR_HACK (V4L2_CID_PRIVATE_BASE + 5) #define V4L2_CID_PRIVATE_WHITECRUSH_UPPER (V4L2_CID_PRIVATE_BASE + 6) #define V4L2_CID_PRIVATE_WHITECRUSH_LOWER (V4L2_CID_PRIVATE_BASE + 7) #define V4L2_CID_PRIVATE_UV_RATIO (V4L2_CID_PRIVATE_BASE + 8) #define V4L2_CID_PRIVATE_FULL_LUMA_RANGE (V4L2_CID_PRIVATE_BASE + 9) #define V4L2_CID_PRIVATE_CORING (V4L2_CID_PRIVATE_BASE + 10) #define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 11) 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, },{ .id = V4L2_CID_PRIVATE_VCR_HACK, .name = "vcr hack", .minimum = 0, .maximum = 1, .type = V4L2_CTRL_TYPE_BOOLEAN, },{ .id = V4L2_CID_PRIVATE_WHITECRUSH_UPPER, .name = "whitecrush upper", .minimum = 0, .maximum = 255, .step = 1, .default_value = 0xCF, .type = V4L2_CTRL_TYPE_INTEGER, },{ .id = V4L2_CID_PRIVATE_WHITECRUSH_LOWER, .name = "whitecrush lower", .minimum = 0, .maximum = 255, .step = 1, .default_value = 0x7F, .type = V4L2_CTRL_TYPE_INTEGER, },{ .id = V4L2_CID_PRIVATE_UV_RATIO, .name = "uv ratio", .minimum = 0, .maximum = 100, .step = 1, .default_value = 50, .type = V4L2_CTRL_TYPE_INTEGER, },{ .id = V4L2_CID_PRIVATE_FULL_LUMA_RANGE, .name = "full luma range", .minimum = 0, .maximum = 1, .type = V4L2_CTRL_TYPE_BOOLEAN, },{ .id = V4L2_CID_PRIVATE_CORING, .name = "coring", .minimum = 0, .maximum = 3, .step = 1, .default_value = 0, .type = V4L2_CTRL_TYPE_INTEGER, } }; static const int BTTV_CTLS = ARRAY_SIZE(bttv_ctls); /* ----------------------------------------------------------------------- */ /* 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 ((fh->resources & bits) != bits) { /* trying to free ressources not allocated by us ... */ printk("bttv: BUG! (btres)\n"); } 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->c.nr); return; } if (btv->pll.pll_ifreq == btv->pll.pll_ofreq) { /* no PLL needed */ if (btv->pll.pll_current == 0) return; bttv_printk(KERN_INFO "bttv%d: PLL can sleep, using XTAL (%d).\n", btv->c.nr,btv->pll.pll_ifreq); btwrite(0x00,BT848_TGCTRL); btwrite(0x00,BT848_PLL_XCI); btv->pll.pll_current = 0; return; } bttv_printk(KERN_INFO "bttv%d: PLL: %d => %d ",btv->c.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 */ bttv_printk("."); msleep(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; bttv_printk(" ok\n"); return; } } btv->pll.pll_current = -1; bttv_printk("failed\n"); return; } /* used to switch between the bt848's analog/digital video capture modes */ static 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 (UNSET == bttv_tvcards[btv->c.type].muxsel[btv->input]) { dprintk("bttv%d: load digital timing table (table_idx=%d)\n", btv->c.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; // printk("bttv: set bright: %d\n",bright); // DEBUG 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 * btv->opt_uv_ratio) / 50) >> 7; val_v = (((color * (100 - btv->opt_uv_ratio) / 50) >>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->c.type].video_inputs) return -EINVAL; /* needed by RemoteVideo MX */ mask2 = bttv_tvcards[btv->c.type].gpiomask2; if (mask2) gpio_inout(mask2,mask2); if (input == btv->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->c.type].muxsel[input] & 3; btaor(mux<<5, ~(3<<5), BT848_IFORM); dprintk(KERN_DEBUG "bttv%d: video mux: input=%d mux=%d\n", btv->c.nr,input,mux); /* card specific hook */ if(bttv_tvcards[btv->c.type].muxsel_hook) bttv_tvcards[btv->c.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; gpio_inout(bttv_tvcards[btv->c.type].gpiomask, bttv_tvcards[btv->c.type].gpiomask); 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; val = bttv_tvcards[btv->c.type].audiomux[mux]; gpio_bits(bttv_tvcards[btv->c.type].gpiomask,val); 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->c.type == BTTV_BOARD_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->c.type) { case BTTV_BOARD_VOODOOTV_FM: bttv_tda9880_setnorm(btv,norm); break; } return 0; } static void set_input(struct bttv *btv, unsigned int input) { unsigned long flags; btv->input = input; if (irq_iswitch) { spin_lock_irqsave(&btv->s_lock,flags); if (btv->curr.frame_irq) { /* active capture -> delayed input switch */ btv->new_input = input; } else { video_mux(btv,input); } spin_unlock_irqrestore(&btv->s_lock,flags); } else { video_mux(btv,input); } audio_mux(btv,(input == bttv_tvcards[btv->c.type].tuner ? AUDIO_TUNER : AUDIO_EXTERN)); set_tvnorm(btv,btv->tvnorm); i2c_vidiocschan(btv); } static void init_irqreg(struct bttv *btv) { /* clear status */ btwrite(0xfffffUL, BT848_INT_STAT); if (bttv_tvcards[btv->c.type].no_video) { /* i2c only */ btwrite(BT848_INT_I2CDONE, BT848_INT_MASK); } else { /* full video */ btwrite((btv->triton1) | (btv->gpioirq ? 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_I2CDONE, BT848_INT_MASK); } } static void init_bt848(struct bttv *btv) { int val; if (bttv_tvcards[btv->c.type].no_video) { /* very basic init only */ init_irqreg(btv); return; } 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); btwrite(whitecrush_upper, BT848_WC_UP); btwrite(whitecrush_lower, BT848_WC_DOWN); 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); } bt848_bright(btv, btv->bright); bt848_hue(btv, btv->hue); bt848_contrast(btv, btv->contrast); bt848_sat(btv, btv->saturation); /* interrupt */ init_irqreg(btv); } static void bttv_reinit_bt848(struct bttv *btv) { unsigned long flags; if (bttv_verbose) printk(KERN_INFO "bttv%d: reset, reinitialize\n",btv->c.nr); spin_lock_irqsave(&btv->s_lock,flags); btv->errors=0; bttv_set_dma(btv,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; case V4L2_CID_PRIVATE_VCR_HACK: c->value = btv->opt_vcr_hack; break; case V4L2_CID_PRIVATE_WHITECRUSH_UPPER: c->value = btv->opt_whitecrush_upper; break; case V4L2_CID_PRIVATE_WHITECRUSH_LOWER: c->value = btv->opt_whitecrush_lower; break; case V4L2_CID_PRIVATE_UV_RATIO: c->value = btv->opt_uv_ratio; break; case V4L2_CID_PRIVATE_FULL_LUMA_RANGE: c->value = btv->opt_full_luma_range; break; case V4L2_CID_PRIVATE_CORING: c->value = btv->opt_coring; 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; case V4L2_CID_PRIVATE_VCR_HACK: btv->opt_vcr_hack = c->value; break; case V4L2_CID_PRIVATE_WHITECRUSH_UPPER: btv->opt_whitecrush_upper = c->value; btwrite(c->value, BT848_WC_UP); break; case V4L2_CID_PRIVATE_WHITECRUSH_LOWER: btv->opt_whitecrush_lower = c->value; btwrite(c->value, BT848_WC_DOWN); break; case V4L2_CID_PRIVATE_UV_RATIO: btv->opt_uv_ratio = c->value; bt848_sat(btv, btv->saturation); break; case V4L2_CID_PRIVATE_FULL_LUMA_RANGE: btv->opt_full_luma_range = c->value; btaor((c->value<<7), ~BT848_OFORM_RANGE, BT848_OFORM); break; case V4L2_CID_PRIVATE_CORING: btv->opt_coring = c->value; btaor((c->value<<5), ~BT848_OFORM_CORE32, BT848_OFORM); 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->c.nr,outbits,data & outbits, data & ~outbits, comment); } static 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) { unsigned 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) { unsigned 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; dprintk("switch_overlay: enter [new=%p]\n",new); if (new) new->vb.state = STATE_DONE; spin_lock_irqsave(&btv->s_lock,flags); old = btv->screen; btv->screen = new; btv->loop_irq |= 1; bttv_set_dma(btv, 0x03); spin_unlock_irqrestore(&btv->s_lock,flags); if (NULL == new) free_btres(btv,fh,RESOURCE_OVERLAY); if (NULL != old) { dprintk("switch_overlay: old=%p state is %d\n",old,old->vb.state); bttv_dma_free(btv, old); kfree(old); } dprintk("switch_overlay: done\n"); return retval; } /* ----------------------------------------------------------------------- */ /* video4linux (1) interface */ static int bttv_prepare_buffer(struct bttv *btv, struct bttv_buffer *buf, const struct bttv_format *fmt, unsigned int width, unsigned 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; } /* alloc risc memory */ if (STATE_NEEDS_INIT == buf->vb.state) { redo_dma_risc = 1; if (0 != (rc = videobuf_iolock(btv->c.pci,&buf->vb,&btv->fbuf))) 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 videobuf_queue *q, unsigned int *count, unsigned int *size) { struct bttv_fh *fh = q->priv_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 videobuf_queue *q, struct videobuf_buffer *vb, enum v4l2_field field) { struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); struct bttv_fh *fh = q->priv_data; return bttv_prepare_buffer(fh->btv, buf, fh->fmt, fh->width, fh->height, field); } static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) { struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); struct bttv_fh *fh = q->priv_data; struct bttv *btv = fh->btv; buf->vb.state = STATE_QUEUED; list_add_tail(&buf->vb.queue,&btv->capture); if (!btv->curr.frame_irq) { btv->loop_irq |= 1; bttv_set_dma(btv, 0x03); } } static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) { struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); struct bttv_fh *fh = q->priv_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 ARRAY_SIZE(v4l1_ioctls) static 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 (UNSET == bttv_tvcards[btv->c.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; unsigned int channel = v->channel; if (channel >= bttv_tvcards[btv->c.type].video_inputs) return -EINVAL; v->tuners=0; v->flags = VIDEO_VC_AUDIO; v->type = VIDEO_TYPE_CAMERA; v->norm = btv->tvnorm; if (channel == bttv_tvcards[btv->c.type].tuner) { strcpy(v->name,"Television"); v->flags|=VIDEO_VC_TUNER; v->type=VIDEO_TYPE_TV; v->tuners=1; } else if (channel == btv->svhs) { strcpy(v->name,"S-Video"); } else { sprintf(v->name,"Composite%d",channel); } return 0; } case VIDIOCSCHAN: { struct video_channel *v = arg; unsigned int channel = v->channel; if (channel >= bttv_tvcards[btv->c.type].video_inputs) return -EINVAL; if (v->norm >= BTTV_TVNORMS) return -EINVAL; down(&btv->lock); if (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; unsigned int audio = v->audio; if (audio >= bttv_tvcards[btv->c.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; unsigned int index = e->index; if (index >= BTTV_TVNORMS) return -EINVAL; v4l2_video_std_construct(e, bttv_tvnorms[e->index].v4l2_id, bttv_tvnorms[e->index].name); e->index = index; 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; unsigned 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; unsigned int n; n = i->index; if (n >= bttv_tvcards[btv->c.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->c.type].tuner) { sprintf(i->name, "Television"); i->type = V4L2_INPUT_TYPE_TUNER; i->tuner = 0; } else if (i->index == btv->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: { unsigned int *i = arg; if (*i > bttv_tvcards[btv->c.type].video_inputs) return -EINVAL; down(&btv->lock); set_input(btv,*i); up(&btv->lock); return 0; } case VIDIOC_G_TUNER: { struct v4l2_tuner *t = arg; if (UNSET == bttv_tvcards[btv->c.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->rangehigh = 0xffffffffUL; t->capability = V4L2_TUNER_CAP_NORM; 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->audmode = V4L2_TUNER_MODE_STEREO; t->rxsubchans |= V4L2_TUNER_SUB_STEREO; } if(va.mode & VIDEO_SOUND_LANG1) { t->audmode = V4L2_TUNER_MODE_LANG1; t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; } } /* FIXME: fill capability+audmode */ up(&btv->lock); return 0; } case VIDIOC_S_TUNER: { struct v4l2_tuner *t = arg; if (UNSET == bttv_tvcards[btv->c.type].tuner) return -EINVAL; if (0 != t->index) return -EINVAL; down(&btv->lock); { struct video_audio va; memset(&va, 0, sizeof(struct video_audio)); bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); 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; } case VIDIOC_LOG_STATUS: { bttv_call_i2c_clients(btv, VIDIOC_LOG_STATUS, NULL); 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 (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; if (NULL == fh->ovfmt) return -EINVAL; if (!(fh->ovfmt->flags & FORMAT_FLAGS_PACKED)) return -EINVAL; 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(*clips)*(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)*n)) { kfree(clips); return -EFAULT; } } /* clip against screen */ if (NULL != btv->fbuf.base) n = btcx_screen_clips(btv->fbuf.fmt.width, btv->fbuf.fmt.height, &win->w, clips, n); btcx_sort_clips(clips,n); /* 4-byte alignments */ switch (fh->ovfmt->depth) { case 8: case 24: btcx_align(&win->w, clips, n, 3); break; case 16: btcx_align(&win->w, clips, n, 1); break; case 32: /* no alignment fixups needed */ break; default: BUG(); } down(&fh->cap.lock); kfree(fh->ov.clips); fh->ov.clips = clips; fh->ov.nclips = n; fh->ov.w = win->w; fh->ov.field = win->field; fh->ov.setup_ok = 1; btv->init.ov.w.width = win->w.width; btv->init.ov.w.height = win->w.height; btv->init.ov.field = win->field; /* 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.bytesperline = (f->fmt.pix.width * fh->fmt->depth) >> 3; f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; 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_get_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; unsigned int maxw,maxh; fmt = format_by_fourcc(f->fmt.pix.pixelformat); if (NULL == fmt) 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: case V4L2_FIELD_ALTERNATE: maxh = maxh/2; break; case V4L2_FIELD_INTERLACED: break; case V4L2_FIELD_SEQ_TB: if (fmt->flags & FORMAT_FLAGS_PLANAR) return -EINVAL; break; 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.width &= ~0x03; f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; return 0; } case V4L2_BUF_TYPE_VIDEO_OVERLAY: return verify_window(&bttv_tvnorms[btv->tvnorm], &f->fmt.win, 1); case V4L2_BUF_TYPE_VBI_CAPTURE: bttv_vbi_try_fmt(fh,f); return 0; 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->cap.last = V4L2_FIELD_NONE; 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: if (no_overlay > 0) { printk ("V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n"); return -EINVAL; } 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 (locked_btres(fh->btv, RESOURCE_VBI)) return -EBUSY; bttv_vbi_try_fmt(fh,f); bttv_vbi_setlines(fh,btv,f->fmt.vbi.count[0]); bttv_vbi_get_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 = 0; if (bttv_debug > 1) { switch (_IOC_TYPE(cmd)) { case 'v': printk("bttv%d: ioctl 0x%x (v4l1, VIDIOC%s)\n", btv->c.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->c.nr, cmd, v4l2_ioctl_names[_IOC_NR(cmd)]); break; default: printk("bttv%d: ioctl 0x%x (???)\n", btv->c.nr, cmd); } } if (btv->errors) bttv_reinit_bt848(btv); switch (cmd) { case VIDIOCSFREQ: case VIDIOCSTUNER: case VIDIOCSCHAN: case VIDIOC_S_CTRL: case VIDIOC_S_STD: case VIDIOC_S_INPUT: case VIDIOC_S_TUNER: case VIDIOC_S_FREQUENCY: retval = v4l2_prio_check(&btv->prio,&fh->prio); if (0 != retval) return retval; }; 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_CLIPPING| VID_TYPE_SCALES; if (no_overlay <= 0) cap->type |= VID_TYPE_OVERLAY; cap->maxwidth = bttv_tvnorms[btv->tvnorm].swidth; cap->maxheight = bttv_tvnorms[btv->tvnorm].sheight; cap->minwidth = 48; cap->minheight = 32; } cap->channels = bttv_tvcards[btv->c.type].video_inputs; cap->audios = bttv_tvcards[btv->c.type].audio_inputs; 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); if (fmt->depth != pic->depth) { retval = -EINVAL; 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; if (no_overlay > 0) { printk ("VIDIOCSWIN: no_overlay\n"); return -EINVAL; } 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 __user *)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->base = btv->fbuf.base; fbuf->width = btv->fbuf.fmt.width; fbuf->height = btv->fbuf.fmt.height; fbuf->bytesperline = btv->fbuf.fmt.bytesperline; if (fh->ovfmt) fbuf->depth = fh->ovfmt->depth; 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; 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; btv->fbuf.base = fbuf->base; btv->fbuf.fmt.width = fbuf->width; btv->fbuf.fmt.height = fbuf->height; if (fbuf->bytesperline) btv->fbuf.fmt.bytesperline = fbuf->bytesperline; else btv->fbuf.fmt.bytesperline = btv->fbuf.fmt.width*fbuf->depth/8; 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.setup_ok) { dprintk("bttv%d: overlay: !setup_ok\n",btv->c.nr); 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; unsigned int i; down(&fh->cap.lock); retval = videobuf_mmap_setup(&fh->cap,gbuffers,gbufsize, V4L2_MEMORY_MMAP); 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(&fh->cap,&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->c.pci,&buf->vb.dma); bttv_dma_free(btv,buf); break; default: retval = -EINVAL; break; } up(&fh->cap.lock); return retval; } case VIDIOCGVBIFMT: { struct vbi_format *fmt = (void *) arg; struct v4l2_format fmt2; if (fh->type != V4L2_BUF_TYPE_VBI_CAPTURE) { retval = bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE); if (0 != retval) return retval; } bttv_vbi_get_fmt(fh, &fmt2); memset(fmt,0,sizeof(*fmt)); fmt->sampling_rate = fmt2.fmt.vbi.sampling_rate; fmt->samples_per_line = fmt2.fmt.vbi.samples_per_line; fmt->sample_format = VIDEO_PALETTE_RAW; fmt->start[0] = fmt2.fmt.vbi.start[0]; fmt->count[0] = fmt2.fmt.vbi.count[0]; fmt->start[1] = fmt2.fmt.vbi.start[1]; fmt->count[1] = fmt2.fmt.vbi.count[1]; if (fmt2.fmt.vbi.flags & VBI_UNSYNC) fmt->flags |= V4L2_VBI_UNSYNC; if (fmt2.fmt.vbi.flags & VBI_INTERLACED) fmt->flags |= V4L2_VBI_INTERLACED; return 0; } case VIDIOCSVBIFMT: { struct vbi_format *fmt = (void *) arg; struct v4l2_format fmt2; retval = bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE); if (0 != retval) return retval; bttv_vbi_get_fmt(fh, &fmt2); if (fmt->sampling_rate != fmt2.fmt.vbi.sampling_rate || fmt->samples_per_line != fmt2.fmt.vbi.samples_per_line || fmt->sample_format != VIDEO_PALETTE_RAW || fmt->start[0] != fmt2.fmt.vbi.start[0] || fmt->start[1] != fmt2.fmt.vbi.start[1] || fmt->count[0] != fmt->count[1] || fmt->count[0] < 1 || fmt->count[0] > 32 /* VBI_MAXLINES */) return -EINVAL; bttv_vbi_setlines(fh,btv,fmt->count[0]); return 0; } 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"); strlcpy(cap->card,btv->video_dev->name,sizeof(cap->card)); sprintf(cap->bus_info,"PCI:%s",pci_name(btv->c.pci)); cap->version = BTTV_VERSION_CODE; cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; if (no_overlay <= 0) cap->capabilities |= V4L2_CAP_VIDEO_OVERLAY; if (bttv_tvcards[btv->c.type].tuner != UNSET && bttv_tvcards[btv->c.type].tuner != TUNER_ABSENT) cap->capabilities |= V4L2_CAP_TUNER; return 0; } case VIDIOC_ENUM_FMT: { struct v4l2_fmtdesc *f = arg; enum v4l2_buf_type type; unsigned int i; int 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 ((unsigned int)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; strlcpy(f->description,bttv_formats[i].name,sizeof(f->description)); 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; *fb = btv->fbuf; 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; if(!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) return -EPERM; /* check args */ 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.fmt.width = fb->fmt.width; btv->fbuf.fmt.height = fb->fmt.height; if (0 != fb->fmt.bytesperline) btv->fbuf.fmt.bytesperline = fb->fmt.bytesperline; else btv->fbuf.fmt.bytesperline = btv->fbuf.fmt.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; 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(bttv_queue(fh),arg); case VIDIOC_QUERYBUF: return videobuf_querybuf(bttv_queue(fh),arg); case VIDIOC_QBUF: return videobuf_qbuf(bttv_queue(fh),arg); case VIDIOC_DQBUF: return videobuf_dqbuf(bttv_queue(fh),arg, file->f_flags & O_NONBLOCK); case VIDIOC_STREAMON: { int res = bttv_resource(fh); if (!check_alloc_btres(btv,fh,res)) return -EBUSY; return videobuf_streamon(bttv_queue(fh)); } case VIDIOC_STREAMOFF: { int res = bttv_resource(fh); retval = videobuf_streamoff(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_G_PRIORITY: { enum v4l2_priority *p = arg; *p = v4l2_prio_max(&btv->prio); return 0; } case VIDIOC_S_PRIORITY: { enum v4l2_priority *prio = arg; return v4l2_prio_change(&btv->prio, &fh->prio, *prio); } 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: case VIDIOC_LOG_STATUS: 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 __user *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("bttv%d: read count=%d type=%s\n", fh->btv->c.nr,(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(&fh->cap, data, count, ppos, file->f_flags & O_NONBLOCK); break; case V4L2_BUF_TYPE_VBI_CAPTURE: if (!check_alloc_btres(fh->btv,fh,RESOURCE_VBI)) return -EBUSY; retval = videobuf_read_stream(&fh->vbi, data, count, ppos, 1, file->f_flags & O_NONBLOCK); 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; enum v4l2_field field; if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { if (!check_alloc_btres(fh->btv,fh,RESOURCE_VBI)) return POLLERR; 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; } fh->cap.read_buf->memory = V4L2_MEMORY_USERPTR; field = videobuf_next_field(&fh->cap); if (0 != fh->cap.ops->buf_prepare(&fh->cap,fh->cap.read_buf,field)) { up(&fh->cap.lock); return POLLERR; } fh->cap.ops->buf_queue(&fh->cap,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) { int minor = iminor(inode); struct bttv *btv = NULL; struct bttv_fh *fh; enum v4l2_buf_type type = 0; unsigned int i; dprintk(KERN_DEBUG "bttv: open minor=%d\n",minor); for (i = 0; i < bttv_num; i++) { if (bttvs[i].video_dev && bttvs[i].video_dev->minor == minor) { btv = &bttvs[i]; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; break; } if (bttvs[i].vbi_dev && 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->c.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; fh->ov.setup_ok = 0; v4l2_prio_open(&btv->prio,&fh->prio); videobuf_queue_init(&fh->cap, &bttv_video_qops, btv->c.pci, &btv->s_lock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED, sizeof(struct bttv_buffer), fh); videobuf_queue_init(&fh->vbi, &bttv_vbi_qops, btv->c.pci, &btv->s_lock, V4L2_BUF_TYPE_VBI_CAPTURE, V4L2_FIELD_SEQ_TB, sizeof(struct bttv_buffer), fh); i2c_vidiocschan(btv); btv->users++; if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) bttv_vbi_setlines(fh,btv,16); 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(&fh->cap); free_btres(btv,fh,RESOURCE_VIDEO); } if (fh->cap.read_buf) { buffer_release(&fh->cap,fh->cap.read_buf); kfree(fh->cap.read_buf); } /* stop vbi capture */ if (check_btres(fh, RESOURCE_VBI)) { if (fh->vbi.streaming) videobuf_streamoff(&fh->vbi); if (fh->vbi.reading) videobuf_read_stop(&fh->vbi); free_btres(btv,fh,RESOURCE_VBI); } /* free stuff */ videobuf_mmap_free(&fh->cap); videobuf_mmap_free(&fh->vbi); v4l2_prio_close(&btv->prio,&fh->prio); 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->c.nr, v4l2_type_names[fh->type], vma->vm_start, vma->vm_end - vma->vm_start); return videobuf_mmap_mapper(bttv_queue(fh),vma); } 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_CLIPPING|VID_TYPE_SCALES, .hardware = VID_HARDWARE_BT848, .fops = &bttv_fops, .minor = -1, }; static 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) { int minor = iminor(inode); struct bttv *btv = NULL; unsigned 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->c.nr); down(&btv->lock); btv->radio_user++; file->private_data = btv; 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; struct rds_command cmd; btv->radio_user--; bttv_call_i2c_clients(btv, RDS_CMD_CLOSE, &cmd); 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"); 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 ssize_t radio_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { struct bttv *btv = file->private_data; struct rds_command cmd; cmd.block_count = count/3; cmd.buffer = data; cmd.instance = file; cmd.result = -ENODEV; bttv_call_i2c_clients(btv, RDS_CMD_READ, &cmd); return cmd.result; } static unsigned int radio_poll(struct file *file, poll_table *wait) { struct bttv *btv = file->private_data; struct rds_command cmd; cmd.instance = file; cmd.event_list = wait; cmd.result = -ENODEV; bttv_call_i2c_clients(btv, RDS_CMD_POLL, &cmd); return cmd.result; } static struct file_operations radio_fops = { .owner = THIS_MODULE, .open = radio_open, .read = radio_read, .release = radio_release, .ioctl = radio_ioctl, .llseek = no_llseek, .poll = radio_poll, }; static struct video_device radio_template = { .name = "bt848/878 radio", .type = VID_TYPE_TUNER, .hardware = VID_HARDWARE_BT848, .fops = &radio_fops, .minor = -1, }; /* ----------------------------------------------------------------------- */ /* some debug code */ static int bttv_risc_decode(u32 risc) { static char *instr[16] = { [ BT848_RISC_WRITE >> 28 ] = "write", [ BT848_RISC_SKIP >> 28 ] = "skip", [ BT848_RISC_WRITEC >> 28 ] = "writec", [ BT848_RISC_JUMP >> 28 ] = "jump", [ BT848_RISC_SYNC >> 28 ] = "sync", [ BT848_RISC_WRITE123 >> 28 ] = "write123", [ BT848_RISC_SKIP123 >> 28 ] = "skip123", [ BT848_RISC_WRITE1S23 >> 28 ] = "write1s23", }; static int incr[16] = { [ BT848_RISC_WRITE >> 28 ] = 2, [ BT848_RISC_JUMP >> 28 ] = 2, [ BT848_RISC_SYNC >> 28 ] = 2, [ BT848_RISC_WRITE123 >> 28 ] = 5, [ BT848_RISC_SKIP123 >> 28 ] = 2, [ BT848_RISC_WRITE1S23 >> 28 ] = 3, }; static char *bits[] = { "be0", "be1", "be2", "be3/resync", "set0", "set1", "set2", "set3", "clr0", "clr1", "clr2", "clr3", "irq", "res", "eol", "sol", }; int i; printk("0x%08x [ %s", risc, instr[risc >> 28] ? instr[risc >> 28] : "INVALID"); for (i = ARRAY_SIZE(bits)-1; i >= 0; i--) if (risc & (1 << (i + 12))) printk(" %s",bits[i]); printk(" count=%d ]\n", risc & 0xfff); return incr[risc >> 28] ? incr[risc >> 28] : 1; } static void bttv_risc_disasm(struct bttv *btv, struct btcx_riscmem *risc) { unsigned int i,j,n; printk("%s: risc disasm: %p [dma=0x%08lx]\n", btv->c.name, risc->cpu, (unsigned long)risc->dma); for (i = 0; i < (risc->size >> 2); i += n) { printk("%s: 0x%lx: ", btv->c.name, (unsigned long)(risc->dma + (i<<2))); n = bttv_risc_decode(risc->cpu[i]); for (j = 1; j < n; j++) printk("%s: 0x%lx: 0x%08x [ arg #%d ]\n", btv->c.name, (unsigned long)(risc->dma + ((i+j)<<2)), risc->cpu[i+j], j); if (0 == risc->cpu[i]) break; } } static void bttv_print_riscaddr(struct bttv *btv) { printk(" main: %08Lx\n", (unsigned long long)btv->main.dma); printk(" vbi : o=%08Lx e=%08Lx\n", btv->cvbi ? (unsigned long long)btv->cvbi->top.dma : 0, btv->cvbi ? (unsigned long long)btv->cvbi->bottom.dma : 0); printk(" cap : o=%08Lx e=%08Lx\n", btv->curr.top ? (unsigned long long)btv->curr.top->top.dma : 0, btv->curr.bottom ? (unsigned long long)btv->curr.bottom->bottom.dma : 0); printk(" scr : o=%08Lx e=%08Lx\n", btv->screen ? (unsigned long long)btv->screen->top.dma : 0, btv->screen ? (unsigned long long)btv->screen->bottom.dma : 0); bttv_risc_disasm(btv, &btv->main); } /* ----------------------------------------------------------------------- */ /* irq handler */ static char *irq_name[] = { "FMTCHG", // format change detected (525 vs. 625) "VSYNC", // vertical sync (new field) "HSYNC", // horizontal sync "OFLOW", // chroma/luma AGC overflow "HLOCK", // horizontal lock changed "VPRES", // video presence changed "6", "7", "I2CDONE", // hw irc operation finished "GPINT", // gpio port triggered irq "10", "RISCI", // risc instruction triggered irq "FBUS", // pixel data fifo dropped data (high pci bus latencies) "FTRGT", // pixel data fifo overrun "FDSR", // fifo data stream resyncronisation "PPERR", // parity error (data transfer) "RIPERR", // parity error (read risc instructions) "PABORT", // pci abort "OCERR", // risc instruction error "SCERR", // syncronisation error }; static void bttv_print_irqbits(u32 print, u32 mark) { unsigned int i; printk("bits:"); for (i = 0; i < ARRAY_SIZE(irq_name); i++) { if (print & (1 << i)) printk(" %s",irq_name[i]); if (mark & (1 << i)) printk("*"); } } static void bttv_irq_debug_low_latency(struct bttv *btv, u32 rc) { printk("bttv%d: irq: skipped frame [main=%lx,o_vbi=%lx,o_field=%lx,rc=%lx]\n", btv->c.nr, (unsigned long)btv->main.dma, (unsigned long)btv->main.cpu[RISC_SLOT_O_VBI+1], (unsigned long)btv->main.cpu[RISC_SLOT_O_FIELD+1], (unsigned long)rc); if (0 == (btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC)) { printk("bttv%d: Oh, there (temporarely?) is no input signal. " "Ok, then this is harmless, don't worry ;)\n", btv->c.nr); return; } printk("bttv%d: Uhm. Looks like we have unusual high IRQ latencies.\n", btv->c.nr); printk("bttv%d: Lets try to catch the culpit red-handed ...\n", btv->c.nr); dump_stack(); } static int bttv_irq_next_video(struct bttv *btv, struct bttv_buffer_set *set) { struct bttv_buffer *item; memset(set,0,sizeof(*set)); /* capture request ? */ if (!list_empty(&btv->capture)) { set->frame_irq = 1; item = list_entry(btv->capture.next, struct bttv_buffer, vb.queue); if (V4L2_FIELD_HAS_TOP(item->vb.field)) set->top = item; if (V4L2_FIELD_HAS_BOTTOM(item->vb.field)) set->bottom = item; /* capture request for other field ? */ if (!V4L2_FIELD_HAS_BOTH(item->vb.field) && (item->vb.queue.next != &btv->capture)) { item = list_entry(item->vb.queue.next, struct bttv_buffer, vb.queue); if (!V4L2_FIELD_HAS_BOTH(item->vb.field)) { if (NULL == set->top && V4L2_FIELD_TOP == item->vb.field) { set->top = item; } if (NULL == set->bottom && V4L2_FIELD_BOTTOM == item->vb.field) { set->bottom = item; } if (NULL != set->top && NULL != set->bottom) set->top_irq = 2; } } } /* screen overlay ? */ if (NULL != btv->screen) { if (V4L2_FIELD_HAS_BOTH(btv->screen->vb.field)) { if (NULL == set->top && NULL == set->bottom) { set->top = btv->screen; set->bottom = btv->screen; } } else { if (V4L2_FIELD_TOP == btv->screen->vb.field && NULL == set->top) { set->top = btv->screen; } if (V4L2_FIELD_BOTTOM == btv->screen->vb.field && NULL == set->bottom) { set->bottom = btv->screen; } } } dprintk("bttv%d: next set: top=%p bottom=%p [screen=%p,irq=%d,%d]\n", btv->c.nr,set->top, set->bottom, btv->screen,set->frame_irq,set->top_irq); return 0; } static void bttv_irq_wakeup_video(struct bttv *btv, struct bttv_buffer_set *wakeup, struct bttv_buffer_set *curr, unsigned int state) { struct timeval ts; do_gettimeofday(&ts); if (wakeup->top == wakeup->bottom) { if (NULL != wakeup->top && curr->top != wakeup->top) { if (irq_debug > 1) printk("bttv%d: wakeup: both=%p\n",btv->c.nr,wakeup->top); wakeup->top->vb.ts = ts; wakeup->top->vb.field_count = btv->field_count; wakeup->top->vb.state = state; wake_up(&wakeup->top->vb.done); } } else { if (NULL != wakeup->top && curr->top != wakeup->top) { if (irq_debug > 1) printk("bttv%d: wakeup: top=%p\n",btv->c.nr,wakeup->top); wakeup->top->vb.ts = ts; wakeup->top->vb.field_count = btv->field_count; wakeup->top->vb.state = state; wake_up(&wakeup->top->vb.done); } if (NULL != wakeup->bottom && curr->bottom != wakeup->bottom) { if (irq_debug > 1) printk("bttv%d: wakeup: bottom=%p\n",btv->c.nr,wakeup->bottom); wakeup->bottom->vb.ts = ts; wakeup->bottom->vb.field_count = btv->field_count; wakeup->bottom->vb.state = state; wake_up(&wakeup->bottom->vb.done); } } } static void bttv_irq_wakeup_vbi(struct bttv *btv, struct bttv_buffer *wakeup, unsigned int state) { struct timeval ts; if (NULL == wakeup) return; do_gettimeofday(&ts); wakeup->vb.ts = ts; wakeup->vb.field_count = btv->field_count; wakeup->vb.state = state; wake_up(&wakeup->vb.done); } static void bttv_irq_timeout(unsigned long data) { struct bttv *btv = (struct bttv *)data; struct bttv_buffer_set old,new; struct bttv_buffer *ovbi; struct bttv_buffer *item; unsigned long flags; if (bttv_verbose) { printk(KERN_INFO "bttv%d: timeout: drop=%d irq=%d/%d, risc=%08x, ", btv->c.nr, btv->framedrop, btv->irq_me, btv->irq_total, btread(BT848_RISC_COUNT)); bttv_print_irqbits(btread(BT848_INT_STAT),0); printk("\n"); } spin_lock_irqsave(&btv->s_lock,flags); /* deactivate stuff */ memset(&new,0,sizeof(new)); old = btv->curr; ovbi = btv->cvbi; btv->curr = new; btv->cvbi = NULL; btv->loop_irq = 0; bttv_buffer_activate_video(btv, &new); bttv_buffer_activate_vbi(btv, NULL); bttv_set_dma(btv, 0); /* wake up */ bttv_irq_wakeup_video(btv, &old, &new, STATE_ERROR); bttv_irq_wakeup_vbi(btv, ovbi, STATE_ERROR); /* cancel all outstanding capture / vbi requests */ while (!list_empty(&btv->capture)) { item = list_entry(btv->capture.next, struct bttv_buffer, vb.queue); list_del(&item->vb.queue); item->vb.state = STATE_ERROR; wake_up(&item->vb.done); } while (!list_empty(&btv->vcapture)) { item = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue); list_del(&item->vb.queue); item->vb.state = STATE_ERROR; wake_up(&item->vb.done); } btv->errors++; spin_unlock_irqrestore(&btv->s_lock,flags); } static void bttv_irq_wakeup_top(struct bttv *btv) { struct bttv_buffer *wakeup = btv->curr.top; if (NULL == wakeup) return; spin_lock(&btv->s_lock); btv->curr.top_irq = 0; btv->curr.top = NULL; bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); do_gettimeofday(&wakeup->vb.ts); wakeup->vb.field_count = btv->field_count; wakeup->vb.state = STATE_DONE; wake_up(&wakeup->vb.done); spin_unlock(&btv->s_lock); } static inline int is_active(struct btcx_riscmem *risc, u32 rc) { if (rc < risc->dma) return 0; if (rc > risc->dma + risc->size) return 0; return 1; } static void bttv_irq_switch_video(struct bttv *btv) { struct bttv_buffer_set new; struct bttv_buffer_set old; dma_addr_t rc; spin_lock(&btv->s_lock); /* new buffer set */ bttv_irq_next_video(btv, &new); rc = btread(BT848_RISC_COUNT); if ((btv->curr.top && is_active(&btv->curr.top->top, rc)) || (btv->curr.bottom && is_active(&btv->curr.bottom->bottom, rc))) { btv->framedrop++; if (debug_latency) bttv_irq_debug_low_latency(btv, rc); spin_unlock(&btv->s_lock); return; } /* switch over */ old = btv->curr; btv->curr = new; btv->loop_irq &= ~1; bttv_buffer_activate_video(btv, &new); bttv_set_dma(btv, 0); /* switch input */ if (UNSET != btv->new_input) { video_mux(btv,btv->new_input); btv->new_input = UNSET; } /* wake up finished buffers */ bttv_irq_wakeup_video(btv, &old, &new, STATE_DONE); spin_unlock(&btv->s_lock); } static void bttv_irq_switch_vbi(struct bttv *btv) { struct bttv_buffer *new = NULL; struct bttv_buffer *old; u32 rc; spin_lock(&btv->s_lock); if (!list_empty(&btv->vcapture)) new = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue); old = btv->cvbi; rc = btread(BT848_RISC_COUNT); if (NULL != old && (is_active(&old->top, rc) || is_active(&old->bottom, rc))) { btv->framedrop++; if (debug_latency) bttv_irq_debug_low_latency(btv, rc); spin_unlock(&btv->s_lock); return; } /* switch */ btv->cvbi = new; btv->loop_irq &= ~4; bttv_buffer_activate_vbi(btv, new); bttv_set_dma(btv, 0); bttv_irq_wakeup_vbi(btv, old, STATE_DONE); spin_unlock(&btv->s_lock); } static irqreturn_t bttv_irq(int irq, void *dev_id, struct pt_regs * regs) { u32 stat,astat; u32 dstat; int count; struct bttv *btv; int handled = 0; btv=(struct bttv *)dev_id; if (btv->any_irq) handled = bttv_any_irq(&btv->c); count=0; while (1) { /* get/clear interrupt status bits */ stat=btread(BT848_INT_STAT); astat=stat&btread(BT848_INT_MASK); if (!astat) break; handled = 1; 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->c.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); bttv_gpio_irq(&btv->c); } if (astat & BT848_INT_I2CDONE) { btv->i2c_done = stat; wake_up(&btv->i2c_queue); } if ((astat & BT848_INT_RISCI) && (stat & (4<<28))) bttv_irq_switch_vbi(btv); if ((astat & BT848_INT_RISCI) && (stat & (2<<28))) bttv_irq_wakeup_top(btv); if ((astat & BT848_INT_RISCI) && (stat & (1<<28))) bttv_irq_switch_video(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->c.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->c.nr,btread(BT848_RISC_COUNT)); if (bttv_debug) bttv_print_riscaddr(btv); } count++; if (count > 4) { if (count > 8 || !(astat & BT848_INT_GPINT)) { btwrite(0, BT848_INT_MASK); printk(KERN_ERR "bttv%d: IRQ lockup, cleared int mask [", btv->c.nr); } else { printk(KERN_ERR "bttv%d: IRQ lockup, clearing GPINT from int mask [", btv->c.nr); btwrite(btread(BT848_INT_MASK) & (-1 ^ BT848_INT_GPINT), BT848_INT_MASK); }; bttv_print_irqbits(stat,astat); printk("]\n"); } } btv->irq_total++; if (handled) btv->irq_me++; return IRQ_RETVAL(handled); } /* ----------------------------------------------------------------------- */ /* initialitation */ static struct video_device *vdev_init(struct bttv *btv, struct video_device *template, char *type) { struct video_device *vfd; vfd = video_device_alloc(); if (NULL == vfd) return NULL; *vfd = *template; vfd->minor = -1; vfd->dev = &btv->c.pci->dev; vfd->release = video_device_release; snprintf(vfd->name, sizeof(vfd->name), "BT%d%s %s (%s)", btv->id, (btv->id==848 && btv->revision==0x12) ? "A" : "", type, bttv_tvcards[btv->c.type].name); return vfd; } static void bttv_unregister_video(struct bttv *btv) { if (btv->video_dev) { if (-1 != btv->video_dev->minor) video_unregister_device(btv->video_dev); else video_device_release(btv->video_dev); btv->video_dev = NULL; } if (btv->vbi_dev) { if (-1 != btv->vbi_dev->minor) video_unregister_device(btv->vbi_dev); else video_device_release(btv->vbi_dev); btv->vbi_dev = NULL; } if (btv->radio_dev) { if (-1 != btv->radio_dev->minor) video_unregister_device(btv->radio_dev); else video_device_release(btv->radio_dev); btv->radio_dev = NULL; } } /* register video4linux devices */ static int __devinit bttv_register_video(struct bttv *btv) { if (no_overlay <= 0) { bttv_video_template.type |= VID_TYPE_OVERLAY; } else { printk("bttv: Overlay support disabled.\n"); } /* video */ btv->video_dev = vdev_init(btv, &bttv_video_template, "video"); if (NULL == btv->video_dev) goto err; if (video_register_device(btv->video_dev,VFL_TYPE_GRABBER,video_nr)<0) goto err; printk(KERN_INFO "bttv%d: registered device video%d\n", btv->c.nr,btv->video_dev->minor & 0x1f); video_device_create_file(btv->video_dev, &class_device_attr_card); /* vbi */ btv->vbi_dev = vdev_init(btv, &bttv_vbi_template, "vbi"); if (NULL == btv->vbi_dev) goto err; if (video_register_device(btv->vbi_dev,VFL_TYPE_VBI,vbi_nr)<0) goto err; printk(KERN_INFO "bttv%d: registered device vbi%d\n", btv->c.nr,btv->vbi_dev->minor & 0x1f); if (!btv->has_radio) return 0; /* radio */ btv->radio_dev = vdev_init(btv, &radio_template, "radio"); if (NULL == btv->radio_dev) goto err; if (video_register_device(btv->radio_dev, VFL_TYPE_RADIO,radio_nr)<0) goto err; printk(KERN_INFO "bttv%d: registered device radio%d\n", btv->c.nr,btv->radio_dev->minor & 0x1f); /* all done */ return 0; err: bttv_unregister_video(btv); return -1; } /* 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->c.nr = bttv_num; sprintf(btv->c.name,"bttv%d",btv->c.nr); /* initialize structs / fill in defaults */ init_MUTEX(&btv->lock); init_MUTEX(&btv->reslock); spin_lock_init(&btv->s_lock); spin_lock_init(&btv->gpio_lock); init_waitqueue_head(&btv->gpioq); init_waitqueue_head(&btv->i2c_queue); INIT_LIST_HEAD(&btv->c.subs); INIT_LIST_HEAD(&btv->capture); INIT_LIST_HEAD(&btv->vcapture); v4l2_prio_init(&btv->prio); init_timer(&btv->timeout); btv->timeout.function = bttv_irq_timeout; btv->timeout.data = (unsigned long)btv; btv->i2c_rc = -1; btv->tuner_type = UNSET; btv->pinnacle_id = UNSET; btv->new_input = UNSET; btv->has_radio=radio[btv->c.nr]; /* pci stuff (init, get irq/mmio, ... */ btv->c.pci = dev; btv->id = dev->device; if (pci_enable_device(dev)) { printk(KERN_WARNING "bttv%d: Can't enable device.\n", btv->c.nr); return -EIO; } if (pci_set_dma_mask(dev, DMA_32BIT_MASK)) { printk(KERN_WARNING "bttv%d: No suitable DMA available.\n", btv->c.nr); return -EIO; } if (!request_mem_region(pci_resource_start(dev,0), pci_resource_len(dev,0), btv->c.name)) { printk(KERN_WARNING "bttv%d: can't request iomem (0x%lx).\n", btv->c.nr, pci_resource_start(dev,0)); return -EBUSY; } pci_set_master(dev); pci_set_command(dev); pci_set_drvdata(dev,btv); 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, pci_name(dev)); printk("irq: %d, latency: %d, mmio: 0x%lx\n", btv->c.pci->irq, lat, pci_resource_start(dev,0)); schedule(); btv->bt848_mmio=ioremap(pci_resource_start(dev,0), 0x1000); if (NULL == ioremap(pci_resource_start(dev,0), 0x1000)) { printk("bttv%d: ioremap() failed\n", btv->c.nr); result = -EIO; goto fail1; } /* identify card */ bttv_idcard(btv); /* disable irqs, register irq handler */ btwrite(0, BT848_INT_MASK); result = request_irq(btv->c.pci->irq, bttv_irq, SA_SHIRQ | SA_INTERRUPT,btv->c.name,(void *)btv); if (result < 0) { printk(KERN_ERR "bttv%d: can't get IRQ %d\n", bttv_num,btv->c.pci->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; btv->opt_vcr_hack = vcr_hack; btv->opt_whitecrush_upper = whitecrush_upper; btv->opt_whitecrush_lower = whitecrush_lower; btv->opt_uv_ratio = uv_ratio; btv->opt_full_luma_range = full_luma_range; btv->opt_coring = coring; /* 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_verbose) bttv_gpio_tracking(btv,"init"); /* needs to be done before i2c is registered */ bttv_init_card1(btv); /* register i2c + gpio */ init_bttv_i2c(btv); /* some card-specific stuff (needs working i2c) */ bttv_init_card2(btv); init_irqreg(btv); /* register video4linux + input */ if (!bttv_tvcards[btv->c.type].no_video) { 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); } /* add subdevices */ if (btv->has_remote) bttv_sub_add_device(&btv->c, "remote"); if (bttv_tvcards[btv->c.type].has_dvb) bttv_sub_add_device(&btv->c, "dvb"); /* everything is fine */ bttv_num++; return 0; fail2: free_irq(btv->c.pci->irq,btv); fail1: if (btv->bt848_mmio) iounmap(btv->bt848_mmio); release_mem_region(pci_resource_start(btv->c.pci,0), pci_resource_len(btv->c.pci,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->c.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); bttv_sub_del_devices(&btv->c); /* unregister i2c_bus + input */ fini_bttv_i2c(btv); /* unregister video4linux */ bttv_unregister_video(btv); /* free allocated memory */ btcx_riscmem_free(btv->c.pci,&btv->main); /* free ressources */ free_irq(btv->c.pci->irq,btv); iounmap(btv->bt848_mmio); release_mem_region(pci_resource_start(btv->c.pci,0), pci_resource_len(btv->c.pci,0)); pci_set_drvdata(pci_dev, NULL); return; } static int bttv_suspend(struct pci_dev *pci_dev, pm_message_t state) { struct bttv *btv = pci_get_drvdata(pci_dev); struct bttv_buffer_set idle; unsigned long flags; dprintk("bttv%d: suspend %d\n", btv->c.nr, state.event); /* stop dma + irqs */ spin_lock_irqsave(&btv->s_lock,flags); memset(&idle, 0, sizeof(idle)); btv->state.video = btv->curr; btv->state.vbi = btv->cvbi; btv->state.loop_irq = btv->loop_irq; btv->curr = idle; btv->loop_irq = 0; bttv_buffer_activate_video(btv, &idle); bttv_buffer_activate_vbi(btv, NULL); bttv_set_dma(btv, 0); btwrite(0, BT848_INT_MASK); spin_unlock_irqrestore(&btv->s_lock,flags); /* save bt878 state */ btv->state.gpio_enable = btread(BT848_GPIO_OUT_EN); btv->state.gpio_data = gpio_read(); /* save pci state */ pci_save_state(pci_dev); if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) { pci_disable_device(pci_dev); btv->state.disabled = 1; } return 0; } static int bttv_resume(struct pci_dev *pci_dev) { struct bttv *btv = pci_get_drvdata(pci_dev); unsigned long flags; int err; dprintk("bttv%d: resume\n", btv->c.nr); /* restore pci state */ if (btv->state.disabled) { err=pci_enable_device(pci_dev); if (err) { printk(KERN_WARNING "bttv%d: Can't enable device.\n", btv->c.nr); return err; } btv->state.disabled = 0; } err=pci_set_power_state(pci_dev, PCI_D0); if (err) { pci_disable_device(pci_dev); printk(KERN_WARNING "bttv%d: Can't enable device.\n", btv->c.nr); btv->state.disabled = 1; return err; } pci_restore_state(pci_dev); /* restore bt878 state */ bttv_reinit_bt848(btv); gpio_inout(0xffffff, btv->state.gpio_enable); gpio_write(btv->state.gpio_data); /* restart dma */ spin_lock_irqsave(&btv->s_lock,flags); btv->curr = btv->state.video; btv->cvbi = btv->state.vbi; btv->loop_irq = btv->state.loop_irq; bttv_buffer_activate_video(btv, &btv->curr); bttv_buffer_activate_vbi(btv, btv->cvbi); bttv_set_dma(btv, 0); spin_unlock_irqrestore(&btv->s_lock,flags); return 0; } static struct pci_device_id bttv_pci_tbl[] = { {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), .suspend = bttv_suspend, .resume = bttv_resume, }; 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); #ifdef SNAPSHOT printk(KERN_INFO "bttv: snapshot date %04d-%02d-%02d\n", SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); #endif 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(); bus_register(&bttv_sub_bus_type); return pci_module_init(&bttv_pci_driver); } static void bttv_cleanup_module(void) { pci_unregister_driver(&bttv_pci_driver); bus_unregister(&bttv_sub_bus_type); return; } module_init(bttv_init_module); module_exit(bttv_cleanup_module); /* * Local variables: * c-basic-offset: 8 * End: */