forked from luck/tmp_suning_uos_patched
befd6e645c
KBL is not needed on saa7134, so, let's remove it. However, we should take some care to avoid opening the module while initializing it. This issue exists with newer udev's that opens a device as soon as the driver is registered. So, a proper lock is needed on open. Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
2640 lines
67 KiB
C
2640 lines
67 KiB
C
/*
|
|
*
|
|
* device driver for philips saa7134 based TV cards
|
|
* video4linux video interface
|
|
*
|
|
* (c) 2001-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sort.h>
|
|
|
|
#include "saa7134-reg.h"
|
|
#include "saa7134.h"
|
|
#include <media/v4l2-common.h>
|
|
|
|
#ifdef CONFIG_VIDEO_V4L1_COMPAT
|
|
/* Include V4L1 specific functions. Should be removed soon */
|
|
#include <linux/videodev.h>
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
unsigned int video_debug;
|
|
static unsigned int gbuffers = 8;
|
|
static unsigned int noninterlaced; /* 0 */
|
|
static unsigned int gbufsize = 720*576*4;
|
|
static unsigned int gbufsize_max = 720*576*4;
|
|
static char secam[] = "--";
|
|
module_param(video_debug, int, 0644);
|
|
MODULE_PARM_DESC(video_debug,"enable debug messages [video]");
|
|
module_param(gbuffers, int, 0444);
|
|
MODULE_PARM_DESC(gbuffers,"number of capture buffers, range 2-32");
|
|
module_param(noninterlaced, int, 0644);
|
|
MODULE_PARM_DESC(noninterlaced,"capture non interlaced video");
|
|
module_param_string(secam, secam, sizeof(secam), 0644);
|
|
MODULE_PARM_DESC(secam, "force SECAM variant, either DK,L or Lc");
|
|
|
|
|
|
#define dprintk(fmt, arg...) if (video_debug&0x04) \
|
|
printk(KERN_DEBUG "%s/video: " fmt, dev->name , ## arg)
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Defines for Video Output Port Register at address 0x191 */
|
|
|
|
/* Bit 0: VIP code T bit polarity */
|
|
|
|
#define VP_T_CODE_P_NON_INVERTED 0x00
|
|
#define VP_T_CODE_P_INVERTED 0x01
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Defines for Video Output Port Register at address 0x195 */
|
|
|
|
/* Bit 2: Video output clock delay control */
|
|
|
|
#define VP_CLK_CTRL2_NOT_DELAYED 0x00
|
|
#define VP_CLK_CTRL2_DELAYED 0x04
|
|
|
|
/* Bit 1: Video output clock invert control */
|
|
|
|
#define VP_CLK_CTRL1_NON_INVERTED 0x00
|
|
#define VP_CLK_CTRL1_INVERTED 0x02
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Defines for Video Output Port Register at address 0x196 */
|
|
|
|
/* Bits 2 to 0: VSYNC pin video vertical sync type */
|
|
|
|
#define VP_VS_TYPE_MASK 0x07
|
|
|
|
#define VP_VS_TYPE_OFF 0x00
|
|
#define VP_VS_TYPE_V123 0x01
|
|
#define VP_VS_TYPE_V_ITU 0x02
|
|
#define VP_VS_TYPE_VGATE_L 0x03
|
|
#define VP_VS_TYPE_RESERVED1 0x04
|
|
#define VP_VS_TYPE_RESERVED2 0x05
|
|
#define VP_VS_TYPE_F_ITU 0x06
|
|
#define VP_VS_TYPE_SC_FID 0x07
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* data structs for video */
|
|
|
|
static int video_out[][9] = {
|
|
[CCIR656] = { 0x00, 0xb1, 0x00, 0xa1, 0x00, 0x04, 0x06, 0x00, 0x00 },
|
|
};
|
|
|
|
static struct saa7134_format formats[] = {
|
|
{
|
|
.name = "8 bpp gray",
|
|
.fourcc = V4L2_PIX_FMT_GREY,
|
|
.depth = 8,
|
|
.pm = 0x06,
|
|
},{
|
|
.name = "15 bpp RGB, le",
|
|
.fourcc = V4L2_PIX_FMT_RGB555,
|
|
.depth = 16,
|
|
.pm = 0x13 | 0x80,
|
|
},{
|
|
.name = "15 bpp RGB, be",
|
|
.fourcc = V4L2_PIX_FMT_RGB555X,
|
|
.depth = 16,
|
|
.pm = 0x13 | 0x80,
|
|
.bswap = 1,
|
|
},{
|
|
.name = "16 bpp RGB, le",
|
|
.fourcc = V4L2_PIX_FMT_RGB565,
|
|
.depth = 16,
|
|
.pm = 0x10 | 0x80,
|
|
},{
|
|
.name = "16 bpp RGB, be",
|
|
.fourcc = V4L2_PIX_FMT_RGB565X,
|
|
.depth = 16,
|
|
.pm = 0x10 | 0x80,
|
|
.bswap = 1,
|
|
},{
|
|
.name = "24 bpp RGB, le",
|
|
.fourcc = V4L2_PIX_FMT_BGR24,
|
|
.depth = 24,
|
|
.pm = 0x11,
|
|
},{
|
|
.name = "24 bpp RGB, be",
|
|
.fourcc = V4L2_PIX_FMT_RGB24,
|
|
.depth = 24,
|
|
.pm = 0x11,
|
|
.bswap = 1,
|
|
},{
|
|
.name = "32 bpp RGB, le",
|
|
.fourcc = V4L2_PIX_FMT_BGR32,
|
|
.depth = 32,
|
|
.pm = 0x12,
|
|
},{
|
|
.name = "32 bpp RGB, be",
|
|
.fourcc = V4L2_PIX_FMT_RGB32,
|
|
.depth = 32,
|
|
.pm = 0x12,
|
|
.bswap = 1,
|
|
.wswap = 1,
|
|
},{
|
|
.name = "4:2:2 packed, YUYV",
|
|
.fourcc = V4L2_PIX_FMT_YUYV,
|
|
.depth = 16,
|
|
.pm = 0x00,
|
|
.bswap = 1,
|
|
.yuv = 1,
|
|
},{
|
|
.name = "4:2:2 packed, UYVY",
|
|
.fourcc = V4L2_PIX_FMT_UYVY,
|
|
.depth = 16,
|
|
.pm = 0x00,
|
|
.yuv = 1,
|
|
},{
|
|
.name = "4:2:2 planar, Y-Cb-Cr",
|
|
.fourcc = V4L2_PIX_FMT_YUV422P,
|
|
.depth = 16,
|
|
.pm = 0x09,
|
|
.yuv = 1,
|
|
.planar = 1,
|
|
.hshift = 1,
|
|
.vshift = 0,
|
|
},{
|
|
.name = "4:2:0 planar, Y-Cb-Cr",
|
|
.fourcc = V4L2_PIX_FMT_YUV420,
|
|
.depth = 12,
|
|
.pm = 0x0a,
|
|
.yuv = 1,
|
|
.planar = 1,
|
|
.hshift = 1,
|
|
.vshift = 1,
|
|
},{
|
|
.name = "4:2:0 planar, Y-Cb-Cr",
|
|
.fourcc = V4L2_PIX_FMT_YVU420,
|
|
.depth = 12,
|
|
.pm = 0x0a,
|
|
.yuv = 1,
|
|
.planar = 1,
|
|
.uvswap = 1,
|
|
.hshift = 1,
|
|
.vshift = 1,
|
|
}
|
|
};
|
|
#define FORMATS ARRAY_SIZE(formats)
|
|
|
|
#define NORM_625_50 \
|
|
.h_start = 0, \
|
|
.h_stop = 719, \
|
|
.video_v_start = 24, \
|
|
.video_v_stop = 311, \
|
|
.vbi_v_start_0 = 7, \
|
|
.vbi_v_stop_0 = 22, \
|
|
.vbi_v_start_1 = 319, \
|
|
.src_timing = 4
|
|
|
|
#define NORM_525_60 \
|
|
.h_start = 0, \
|
|
.h_stop = 703, \
|
|
.video_v_start = 23, \
|
|
.video_v_stop = 262, \
|
|
.vbi_v_start_0 = 10, \
|
|
.vbi_v_stop_0 = 21, \
|
|
.vbi_v_start_1 = 273, \
|
|
.src_timing = 7
|
|
|
|
static struct saa7134_tvnorm tvnorms[] = {
|
|
{
|
|
.name = "PAL", /* autodetect */
|
|
.id = V4L2_STD_PAL,
|
|
NORM_625_50,
|
|
|
|
.sync_control = 0x18,
|
|
.luma_control = 0x40,
|
|
.chroma_ctrl1 = 0x81,
|
|
.chroma_gain = 0x2a,
|
|
.chroma_ctrl2 = 0x06,
|
|
.vgate_misc = 0x1c,
|
|
|
|
},{
|
|
.name = "PAL-BG",
|
|
.id = V4L2_STD_PAL_BG,
|
|
NORM_625_50,
|
|
|
|
.sync_control = 0x18,
|
|
.luma_control = 0x40,
|
|
.chroma_ctrl1 = 0x81,
|
|
.chroma_gain = 0x2a,
|
|
.chroma_ctrl2 = 0x06,
|
|
.vgate_misc = 0x1c,
|
|
|
|
},{
|
|
.name = "PAL-I",
|
|
.id = V4L2_STD_PAL_I,
|
|
NORM_625_50,
|
|
|
|
.sync_control = 0x18,
|
|
.luma_control = 0x40,
|
|
.chroma_ctrl1 = 0x81,
|
|
.chroma_gain = 0x2a,
|
|
.chroma_ctrl2 = 0x06,
|
|
.vgate_misc = 0x1c,
|
|
|
|
},{
|
|
.name = "PAL-DK",
|
|
.id = V4L2_STD_PAL_DK,
|
|
NORM_625_50,
|
|
|
|
.sync_control = 0x18,
|
|
.luma_control = 0x40,
|
|
.chroma_ctrl1 = 0x81,
|
|
.chroma_gain = 0x2a,
|
|
.chroma_ctrl2 = 0x06,
|
|
.vgate_misc = 0x1c,
|
|
|
|
},{
|
|
.name = "NTSC",
|
|
.id = V4L2_STD_NTSC,
|
|
NORM_525_60,
|
|
|
|
.sync_control = 0x59,
|
|
.luma_control = 0x40,
|
|
.chroma_ctrl1 = 0x89,
|
|
.chroma_gain = 0x2a,
|
|
.chroma_ctrl2 = 0x0e,
|
|
.vgate_misc = 0x18,
|
|
|
|
},{
|
|
.name = "SECAM",
|
|
.id = V4L2_STD_SECAM,
|
|
NORM_625_50,
|
|
|
|
.sync_control = 0x18,
|
|
.luma_control = 0x1b,
|
|
.chroma_ctrl1 = 0xd1,
|
|
.chroma_gain = 0x80,
|
|
.chroma_ctrl2 = 0x00,
|
|
.vgate_misc = 0x1c,
|
|
|
|
},{
|
|
.name = "SECAM-DK",
|
|
.id = V4L2_STD_SECAM_DK,
|
|
NORM_625_50,
|
|
|
|
.sync_control = 0x18,
|
|
.luma_control = 0x1b,
|
|
.chroma_ctrl1 = 0xd1,
|
|
.chroma_gain = 0x80,
|
|
.chroma_ctrl2 = 0x00,
|
|
.vgate_misc = 0x1c,
|
|
|
|
},{
|
|
.name = "SECAM-L",
|
|
.id = V4L2_STD_SECAM_L,
|
|
NORM_625_50,
|
|
|
|
.sync_control = 0x18,
|
|
.luma_control = 0x1b,
|
|
.chroma_ctrl1 = 0xd1,
|
|
.chroma_gain = 0x80,
|
|
.chroma_ctrl2 = 0x00,
|
|
.vgate_misc = 0x1c,
|
|
|
|
},{
|
|
.name = "SECAM-Lc",
|
|
.id = V4L2_STD_SECAM_LC,
|
|
NORM_625_50,
|
|
|
|
.sync_control = 0x18,
|
|
.luma_control = 0x1b,
|
|
.chroma_ctrl1 = 0xd1,
|
|
.chroma_gain = 0x80,
|
|
.chroma_ctrl2 = 0x00,
|
|
.vgate_misc = 0x1c,
|
|
|
|
},{
|
|
.name = "PAL-M",
|
|
.id = V4L2_STD_PAL_M,
|
|
NORM_525_60,
|
|
|
|
.sync_control = 0x59,
|
|
.luma_control = 0x40,
|
|
.chroma_ctrl1 = 0xb9,
|
|
.chroma_gain = 0x2a,
|
|
.chroma_ctrl2 = 0x0e,
|
|
.vgate_misc = 0x18,
|
|
|
|
},{
|
|
.name = "PAL-Nc",
|
|
.id = V4L2_STD_PAL_Nc,
|
|
NORM_625_50,
|
|
|
|
.sync_control = 0x18,
|
|
.luma_control = 0x40,
|
|
.chroma_ctrl1 = 0xa1,
|
|
.chroma_gain = 0x2a,
|
|
.chroma_ctrl2 = 0x06,
|
|
.vgate_misc = 0x1c,
|
|
|
|
},{
|
|
.name = "PAL-60",
|
|
.id = V4L2_STD_PAL_60,
|
|
|
|
.h_start = 0,
|
|
.h_stop = 719,
|
|
.video_v_start = 23,
|
|
.video_v_stop = 262,
|
|
.vbi_v_start_0 = 10,
|
|
.vbi_v_stop_0 = 21,
|
|
.vbi_v_start_1 = 273,
|
|
.src_timing = 7,
|
|
|
|
.sync_control = 0x18,
|
|
.luma_control = 0x40,
|
|
.chroma_ctrl1 = 0x81,
|
|
.chroma_gain = 0x2a,
|
|
.chroma_ctrl2 = 0x06,
|
|
.vgate_misc = 0x1c,
|
|
}
|
|
};
|
|
#define TVNORMS ARRAY_SIZE(tvnorms)
|
|
|
|
#define V4L2_CID_PRIVATE_INVERT (V4L2_CID_PRIVATE_BASE + 0)
|
|
#define V4L2_CID_PRIVATE_Y_ODD (V4L2_CID_PRIVATE_BASE + 1)
|
|
#define V4L2_CID_PRIVATE_Y_EVEN (V4L2_CID_PRIVATE_BASE + 2)
|
|
#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_PRIVATE_BASE + 3)
|
|
#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 4)
|
|
|
|
static const struct v4l2_queryctrl no_ctrl = {
|
|
.name = "42",
|
|
.flags = V4L2_CTRL_FLAG_DISABLED,
|
|
};
|
|
static const struct v4l2_queryctrl video_ctrls[] = {
|
|
/* --- video --- */
|
|
{
|
|
.id = V4L2_CID_BRIGHTNESS,
|
|
.name = "Brightness",
|
|
.minimum = 0,
|
|
.maximum = 255,
|
|
.step = 1,
|
|
.default_value = 128,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
},{
|
|
.id = V4L2_CID_CONTRAST,
|
|
.name = "Contrast",
|
|
.minimum = 0,
|
|
.maximum = 127,
|
|
.step = 1,
|
|
.default_value = 68,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
},{
|
|
.id = V4L2_CID_SATURATION,
|
|
.name = "Saturation",
|
|
.minimum = 0,
|
|
.maximum = 127,
|
|
.step = 1,
|
|
.default_value = 64,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
},{
|
|
.id = V4L2_CID_HUE,
|
|
.name = "Hue",
|
|
.minimum = -128,
|
|
.maximum = 127,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
},{
|
|
.id = V4L2_CID_HFLIP,
|
|
.name = "Mirror",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
},
|
|
/* --- 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 = -15,
|
|
.maximum = 15,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
},
|
|
/* --- private --- */
|
|
{
|
|
.id = V4L2_CID_PRIVATE_INVERT,
|
|
.name = "Invert",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
},{
|
|
.id = V4L2_CID_PRIVATE_Y_ODD,
|
|
.name = "y offset odd field",
|
|
.minimum = 0,
|
|
.maximum = 128,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
},{
|
|
.id = V4L2_CID_PRIVATE_Y_EVEN,
|
|
.name = "y offset even field",
|
|
.minimum = 0,
|
|
.maximum = 128,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
},{
|
|
.id = V4L2_CID_PRIVATE_AUTOMUTE,
|
|
.name = "automute",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.default_value = 1,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
}
|
|
};
|
|
static const unsigned int CTRLS = ARRAY_SIZE(video_ctrls);
|
|
|
|
static const struct v4l2_queryctrl* ctrl_by_id(unsigned int id)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < CTRLS; i++)
|
|
if (video_ctrls[i].id == id)
|
|
return video_ctrls+i;
|
|
return NULL;
|
|
}
|
|
|
|
static struct saa7134_format* format_by_fourcc(unsigned int fourcc)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < FORMATS; i++)
|
|
if (formats[i].fourcc == fourcc)
|
|
return formats+i;
|
|
return NULL;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
/* resource management */
|
|
|
|
static int res_get(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int bit)
|
|
{
|
|
if (fh->resources & bit)
|
|
/* have it already allocated */
|
|
return 1;
|
|
|
|
/* is it free? */
|
|
mutex_lock(&dev->lock);
|
|
if (dev->resources & bit) {
|
|
/* no, someone else uses it */
|
|
mutex_unlock(&dev->lock);
|
|
return 0;
|
|
}
|
|
/* it's free, grab it */
|
|
fh->resources |= bit;
|
|
dev->resources |= bit;
|
|
dprintk("res: get %d\n",bit);
|
|
mutex_unlock(&dev->lock);
|
|
return 1;
|
|
}
|
|
|
|
static int res_check(struct saa7134_fh *fh, unsigned int bit)
|
|
{
|
|
return (fh->resources & bit);
|
|
}
|
|
|
|
static int res_locked(struct saa7134_dev *dev, unsigned int bit)
|
|
{
|
|
return (dev->resources & bit);
|
|
}
|
|
|
|
static
|
|
void res_free(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int bits)
|
|
{
|
|
BUG_ON((fh->resources & bits) != bits);
|
|
|
|
mutex_lock(&dev->lock);
|
|
fh->resources &= ~bits;
|
|
dev->resources &= ~bits;
|
|
dprintk("res: put %d\n",bits);
|
|
mutex_unlock(&dev->lock);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void set_tvnorm(struct saa7134_dev *dev, struct saa7134_tvnorm *norm)
|
|
{
|
|
dprintk("set tv norm = %s\n",norm->name);
|
|
dev->tvnorm = norm;
|
|
|
|
/* setup cropping */
|
|
dev->crop_bounds.left = norm->h_start;
|
|
dev->crop_defrect.left = norm->h_start;
|
|
dev->crop_bounds.width = norm->h_stop - norm->h_start +1;
|
|
dev->crop_defrect.width = norm->h_stop - norm->h_start +1;
|
|
|
|
dev->crop_bounds.top = (norm->vbi_v_stop_0+1)*2;
|
|
dev->crop_defrect.top = norm->video_v_start*2;
|
|
dev->crop_bounds.height = ((norm->id & V4L2_STD_525_60) ? 524 : 624)
|
|
- dev->crop_bounds.top;
|
|
dev->crop_defrect.height = (norm->video_v_stop - norm->video_v_start +1)*2;
|
|
|
|
dev->crop_current = dev->crop_defrect;
|
|
|
|
saa7134_set_tvnorm_hw(dev);
|
|
}
|
|
|
|
static void video_mux(struct saa7134_dev *dev, int input)
|
|
{
|
|
dprintk("video input = %d [%s]\n", input, card_in(dev, input).name);
|
|
dev->ctl_input = input;
|
|
set_tvnorm(dev, dev->tvnorm);
|
|
saa7134_tvaudio_setinput(dev, &card_in(dev, input));
|
|
}
|
|
|
|
|
|
static void saa7134_set_decoder(struct saa7134_dev *dev)
|
|
{
|
|
int luma_control, sync_control, mux;
|
|
|
|
struct saa7134_tvnorm *norm = dev->tvnorm;
|
|
mux = card_in(dev, dev->ctl_input).vmux;
|
|
|
|
luma_control = norm->luma_control;
|
|
sync_control = norm->sync_control;
|
|
|
|
if (mux > 5)
|
|
luma_control |= 0x80; /* svideo */
|
|
if (noninterlaced || dev->nosignal)
|
|
sync_control |= 0x20;
|
|
|
|
/* setup video decoder */
|
|
saa_writeb(SAA7134_INCR_DELAY, 0x08);
|
|
saa_writeb(SAA7134_ANALOG_IN_CTRL1, 0xc0 | mux);
|
|
saa_writeb(SAA7134_ANALOG_IN_CTRL2, 0x00);
|
|
|
|
saa_writeb(SAA7134_ANALOG_IN_CTRL3, 0x90);
|
|
saa_writeb(SAA7134_ANALOG_IN_CTRL4, 0x90);
|
|
saa_writeb(SAA7134_HSYNC_START, 0xeb);
|
|
saa_writeb(SAA7134_HSYNC_STOP, 0xe0);
|
|
saa_writeb(SAA7134_SOURCE_TIMING1, norm->src_timing);
|
|
|
|
saa_writeb(SAA7134_SYNC_CTRL, sync_control);
|
|
saa_writeb(SAA7134_LUMA_CTRL, luma_control);
|
|
saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright);
|
|
|
|
saa_writeb(SAA7134_DEC_LUMA_CONTRAST,
|
|
dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast);
|
|
|
|
saa_writeb(SAA7134_DEC_CHROMA_SATURATION,
|
|
dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation);
|
|
|
|
saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue);
|
|
saa_writeb(SAA7134_CHROMA_CTRL1, norm->chroma_ctrl1);
|
|
saa_writeb(SAA7134_CHROMA_GAIN, norm->chroma_gain);
|
|
|
|
saa_writeb(SAA7134_CHROMA_CTRL2, norm->chroma_ctrl2);
|
|
saa_writeb(SAA7134_MODE_DELAY_CTRL, 0x00);
|
|
|
|
saa_writeb(SAA7134_ANALOG_ADC, 0x01);
|
|
saa_writeb(SAA7134_VGATE_START, 0x11);
|
|
saa_writeb(SAA7134_VGATE_STOP, 0xfe);
|
|
saa_writeb(SAA7134_MISC_VGATE_MSB, norm->vgate_misc);
|
|
saa_writeb(SAA7134_RAW_DATA_GAIN, 0x40);
|
|
saa_writeb(SAA7134_RAW_DATA_OFFSET, 0x80);
|
|
}
|
|
|
|
void saa7134_set_tvnorm_hw(struct saa7134_dev *dev)
|
|
{
|
|
saa7134_set_decoder(dev);
|
|
|
|
if (card_in(dev, dev->ctl_input).tv)
|
|
saa_call_all(dev, tuner, s_std, dev->tvnorm->id);
|
|
/* Set the correct norm for the saa6752hs. This function
|
|
does nothing if there is no saa6752hs. */
|
|
saa_call_empress(dev, tuner, s_std, dev->tvnorm->id);
|
|
}
|
|
|
|
static void set_h_prescale(struct saa7134_dev *dev, int task, int prescale)
|
|
{
|
|
static const struct {
|
|
int xpsc;
|
|
int xacl;
|
|
int xc2_1;
|
|
int xdcg;
|
|
int vpfy;
|
|
} vals[] = {
|
|
/* XPSC XACL XC2_1 XDCG VPFY */
|
|
{ 1, 0, 0, 0, 0 },
|
|
{ 2, 2, 1, 2, 2 },
|
|
{ 3, 4, 1, 3, 2 },
|
|
{ 4, 8, 1, 4, 2 },
|
|
{ 5, 8, 1, 4, 2 },
|
|
{ 6, 8, 1, 4, 3 },
|
|
{ 7, 8, 1, 4, 3 },
|
|
{ 8, 15, 0, 4, 3 },
|
|
{ 9, 15, 0, 4, 3 },
|
|
{ 10, 16, 1, 5, 3 },
|
|
};
|
|
static const int count = ARRAY_SIZE(vals);
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
if (vals[i].xpsc == prescale)
|
|
break;
|
|
if (i == count)
|
|
return;
|
|
|
|
saa_writeb(SAA7134_H_PRESCALE(task), vals[i].xpsc);
|
|
saa_writeb(SAA7134_ACC_LENGTH(task), vals[i].xacl);
|
|
saa_writeb(SAA7134_LEVEL_CTRL(task),
|
|
(vals[i].xc2_1 << 3) | (vals[i].xdcg));
|
|
saa_andorb(SAA7134_FIR_PREFILTER_CTRL(task), 0x0f,
|
|
(vals[i].vpfy << 2) | vals[i].vpfy);
|
|
}
|
|
|
|
static void set_v_scale(struct saa7134_dev *dev, int task, int yscale)
|
|
{
|
|
int val,mirror;
|
|
|
|
saa_writeb(SAA7134_V_SCALE_RATIO1(task), yscale & 0xff);
|
|
saa_writeb(SAA7134_V_SCALE_RATIO2(task), yscale >> 8);
|
|
|
|
mirror = (dev->ctl_mirror) ? 0x02 : 0x00;
|
|
if (yscale < 2048) {
|
|
/* LPI */
|
|
dprintk("yscale LPI yscale=%d\n",yscale);
|
|
saa_writeb(SAA7134_V_FILTER(task), 0x00 | mirror);
|
|
saa_writeb(SAA7134_LUMA_CONTRAST(task), 0x40);
|
|
saa_writeb(SAA7134_CHROMA_SATURATION(task), 0x40);
|
|
} else {
|
|
/* ACM */
|
|
val = 0x40 * 1024 / yscale;
|
|
dprintk("yscale ACM yscale=%d val=0x%x\n",yscale,val);
|
|
saa_writeb(SAA7134_V_FILTER(task), 0x01 | mirror);
|
|
saa_writeb(SAA7134_LUMA_CONTRAST(task), val);
|
|
saa_writeb(SAA7134_CHROMA_SATURATION(task), val);
|
|
}
|
|
saa_writeb(SAA7134_LUMA_BRIGHT(task), 0x80);
|
|
}
|
|
|
|
static void set_size(struct saa7134_dev *dev, int task,
|
|
int width, int height, int interlace)
|
|
{
|
|
int prescale,xscale,yscale,y_even,y_odd;
|
|
int h_start, h_stop, v_start, v_stop;
|
|
int div = interlace ? 2 : 1;
|
|
|
|
/* setup video scaler */
|
|
h_start = dev->crop_current.left;
|
|
v_start = dev->crop_current.top/2;
|
|
h_stop = (dev->crop_current.left + dev->crop_current.width -1);
|
|
v_stop = (dev->crop_current.top + dev->crop_current.height -1)/2;
|
|
|
|
saa_writeb(SAA7134_VIDEO_H_START1(task), h_start & 0xff);
|
|
saa_writeb(SAA7134_VIDEO_H_START2(task), h_start >> 8);
|
|
saa_writeb(SAA7134_VIDEO_H_STOP1(task), h_stop & 0xff);
|
|
saa_writeb(SAA7134_VIDEO_H_STOP2(task), h_stop >> 8);
|
|
saa_writeb(SAA7134_VIDEO_V_START1(task), v_start & 0xff);
|
|
saa_writeb(SAA7134_VIDEO_V_START2(task), v_start >> 8);
|
|
saa_writeb(SAA7134_VIDEO_V_STOP1(task), v_stop & 0xff);
|
|
saa_writeb(SAA7134_VIDEO_V_STOP2(task), v_stop >> 8);
|
|
|
|
prescale = dev->crop_current.width / width;
|
|
if (0 == prescale)
|
|
prescale = 1;
|
|
xscale = 1024 * dev->crop_current.width / prescale / width;
|
|
yscale = 512 * div * dev->crop_current.height / height;
|
|
dprintk("prescale=%d xscale=%d yscale=%d\n",prescale,xscale,yscale);
|
|
set_h_prescale(dev,task,prescale);
|
|
saa_writeb(SAA7134_H_SCALE_INC1(task), xscale & 0xff);
|
|
saa_writeb(SAA7134_H_SCALE_INC2(task), xscale >> 8);
|
|
set_v_scale(dev,task,yscale);
|
|
|
|
saa_writeb(SAA7134_VIDEO_PIXELS1(task), width & 0xff);
|
|
saa_writeb(SAA7134_VIDEO_PIXELS2(task), width >> 8);
|
|
saa_writeb(SAA7134_VIDEO_LINES1(task), height/div & 0xff);
|
|
saa_writeb(SAA7134_VIDEO_LINES2(task), height/div >> 8);
|
|
|
|
/* deinterlace y offsets */
|
|
y_odd = dev->ctl_y_odd;
|
|
y_even = dev->ctl_y_even;
|
|
saa_writeb(SAA7134_V_PHASE_OFFSET0(task), y_odd);
|
|
saa_writeb(SAA7134_V_PHASE_OFFSET1(task), y_even);
|
|
saa_writeb(SAA7134_V_PHASE_OFFSET2(task), y_odd);
|
|
saa_writeb(SAA7134_V_PHASE_OFFSET3(task), y_even);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
struct cliplist {
|
|
__u16 position;
|
|
__u8 enable;
|
|
__u8 disable;
|
|
};
|
|
|
|
static void set_cliplist(struct saa7134_dev *dev, int reg,
|
|
struct cliplist *cl, int entries, char *name)
|
|
{
|
|
__u8 winbits = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < entries; i++) {
|
|
winbits |= cl[i].enable;
|
|
winbits &= ~cl[i].disable;
|
|
if (i < 15 && cl[i].position == cl[i+1].position)
|
|
continue;
|
|
saa_writeb(reg + 0, winbits);
|
|
saa_writeb(reg + 2, cl[i].position & 0xff);
|
|
saa_writeb(reg + 3, cl[i].position >> 8);
|
|
dprintk("clip: %s winbits=%02x pos=%d\n",
|
|
name,winbits,cl[i].position);
|
|
reg += 8;
|
|
}
|
|
for (; reg < 0x400; reg += 8) {
|
|
saa_writeb(reg+ 0, 0);
|
|
saa_writeb(reg + 1, 0);
|
|
saa_writeb(reg + 2, 0);
|
|
saa_writeb(reg + 3, 0);
|
|
}
|
|
}
|
|
|
|
static int clip_range(int val)
|
|
{
|
|
if (val < 0)
|
|
val = 0;
|
|
return val;
|
|
}
|
|
|
|
/* Sort into smallest position first order */
|
|
static int cliplist_cmp(const void *a, const void *b)
|
|
{
|
|
const struct cliplist *cla = a;
|
|
const struct cliplist *clb = b;
|
|
if (cla->position < clb->position)
|
|
return -1;
|
|
if (cla->position > clb->position)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int setup_clipping(struct saa7134_dev *dev, struct v4l2_clip *clips,
|
|
int nclips, int interlace)
|
|
{
|
|
struct cliplist col[16], row[16];
|
|
int cols = 0, rows = 0, i;
|
|
int div = interlace ? 2 : 1;
|
|
|
|
memset(col, 0, sizeof(col));
|
|
memset(row, 0, sizeof(row));
|
|
for (i = 0; i < nclips && i < 8; i++) {
|
|
col[cols].position = clip_range(clips[i].c.left);
|
|
col[cols].enable = (1 << i);
|
|
cols++;
|
|
col[cols].position = clip_range(clips[i].c.left+clips[i].c.width);
|
|
col[cols].disable = (1 << i);
|
|
cols++;
|
|
row[rows].position = clip_range(clips[i].c.top / div);
|
|
row[rows].enable = (1 << i);
|
|
rows++;
|
|
row[rows].position = clip_range((clips[i].c.top + clips[i].c.height)
|
|
/ div);
|
|
row[rows].disable = (1 << i);
|
|
rows++;
|
|
}
|
|
sort(col, cols, sizeof col[0], cliplist_cmp, NULL);
|
|
sort(row, rows, sizeof row[0], cliplist_cmp, NULL);
|
|
set_cliplist(dev,0x380,col,cols,"cols");
|
|
set_cliplist(dev,0x384,row,rows,"rows");
|
|
return 0;
|
|
}
|
|
|
|
static int verify_preview(struct saa7134_dev *dev, struct v4l2_window *win)
|
|
{
|
|
enum v4l2_field field;
|
|
int maxw, maxh;
|
|
|
|
if (NULL == dev->ovbuf.base)
|
|
return -EINVAL;
|
|
if (NULL == dev->ovfmt)
|
|
return -EINVAL;
|
|
if (win->w.width < 48 || win->w.height < 32)
|
|
return -EINVAL;
|
|
if (win->clipcount > 2048)
|
|
return -EINVAL;
|
|
|
|
field = win->field;
|
|
maxw = dev->crop_current.width;
|
|
maxh = dev->crop_current.height;
|
|
|
|
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;
|
|
}
|
|
|
|
win->field = field;
|
|
if (win->w.width > maxw)
|
|
win->w.width = maxw;
|
|
if (win->w.height > maxh)
|
|
win->w.height = maxh;
|
|
return 0;
|
|
}
|
|
|
|
static int start_preview(struct saa7134_dev *dev, struct saa7134_fh *fh)
|
|
{
|
|
unsigned long base,control,bpl;
|
|
int err;
|
|
|
|
err = verify_preview(dev,&fh->win);
|
|
if (0 != err)
|
|
return err;
|
|
|
|
dev->ovfield = fh->win.field;
|
|
dprintk("start_preview %dx%d+%d+%d %s field=%s\n",
|
|
fh->win.w.width,fh->win.w.height,
|
|
fh->win.w.left,fh->win.w.top,
|
|
dev->ovfmt->name,v4l2_field_names[dev->ovfield]);
|
|
|
|
/* setup window + clipping */
|
|
set_size(dev,TASK_B,fh->win.w.width,fh->win.w.height,
|
|
V4L2_FIELD_HAS_BOTH(dev->ovfield));
|
|
setup_clipping(dev,fh->clips,fh->nclips,
|
|
V4L2_FIELD_HAS_BOTH(dev->ovfield));
|
|
if (dev->ovfmt->yuv)
|
|
saa_andorb(SAA7134_DATA_PATH(TASK_B), 0x3f, 0x03);
|
|
else
|
|
saa_andorb(SAA7134_DATA_PATH(TASK_B), 0x3f, 0x01);
|
|
saa_writeb(SAA7134_OFMT_VIDEO_B, dev->ovfmt->pm | 0x20);
|
|
|
|
/* dma: setup channel 1 (= Video Task B) */
|
|
base = (unsigned long)dev->ovbuf.base;
|
|
base += dev->ovbuf.fmt.bytesperline * fh->win.w.top;
|
|
base += dev->ovfmt->depth/8 * fh->win.w.left;
|
|
bpl = dev->ovbuf.fmt.bytesperline;
|
|
control = SAA7134_RS_CONTROL_BURST_16;
|
|
if (dev->ovfmt->bswap)
|
|
control |= SAA7134_RS_CONTROL_BSWAP;
|
|
if (dev->ovfmt->wswap)
|
|
control |= SAA7134_RS_CONTROL_WSWAP;
|
|
if (V4L2_FIELD_HAS_BOTH(dev->ovfield)) {
|
|
saa_writel(SAA7134_RS_BA1(1),base);
|
|
saa_writel(SAA7134_RS_BA2(1),base+bpl);
|
|
saa_writel(SAA7134_RS_PITCH(1),bpl*2);
|
|
saa_writel(SAA7134_RS_CONTROL(1),control);
|
|
} else {
|
|
saa_writel(SAA7134_RS_BA1(1),base);
|
|
saa_writel(SAA7134_RS_BA2(1),base);
|
|
saa_writel(SAA7134_RS_PITCH(1),bpl);
|
|
saa_writel(SAA7134_RS_CONTROL(1),control);
|
|
}
|
|
|
|
/* start dma */
|
|
dev->ovenable = 1;
|
|
saa7134_set_dmabits(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stop_preview(struct saa7134_dev *dev, struct saa7134_fh *fh)
|
|
{
|
|
dev->ovenable = 0;
|
|
saa7134_set_dmabits(dev);
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static int buffer_activate(struct saa7134_dev *dev,
|
|
struct saa7134_buf *buf,
|
|
struct saa7134_buf *next)
|
|
{
|
|
unsigned long base,control,bpl;
|
|
unsigned long bpl_uv,lines_uv,base2,base3,tmp; /* planar */
|
|
|
|
dprintk("buffer_activate buf=%p\n",buf);
|
|
buf->vb.state = VIDEOBUF_ACTIVE;
|
|
buf->top_seen = 0;
|
|
|
|
set_size(dev,TASK_A,buf->vb.width,buf->vb.height,
|
|
V4L2_FIELD_HAS_BOTH(buf->vb.field));
|
|
if (buf->fmt->yuv)
|
|
saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x03);
|
|
else
|
|
saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x01);
|
|
saa_writeb(SAA7134_OFMT_VIDEO_A, buf->fmt->pm);
|
|
|
|
/* DMA: setup channel 0 (= Video Task A0) */
|
|
base = saa7134_buffer_base(buf);
|
|
if (buf->fmt->planar)
|
|
bpl = buf->vb.width;
|
|
else
|
|
bpl = (buf->vb.width * buf->fmt->depth) / 8;
|
|
control = SAA7134_RS_CONTROL_BURST_16 |
|
|
SAA7134_RS_CONTROL_ME |
|
|
(buf->pt->dma >> 12);
|
|
if (buf->fmt->bswap)
|
|
control |= SAA7134_RS_CONTROL_BSWAP;
|
|
if (buf->fmt->wswap)
|
|
control |= SAA7134_RS_CONTROL_WSWAP;
|
|
if (V4L2_FIELD_HAS_BOTH(buf->vb.field)) {
|
|
/* interlaced */
|
|
saa_writel(SAA7134_RS_BA1(0),base);
|
|
saa_writel(SAA7134_RS_BA2(0),base+bpl);
|
|
saa_writel(SAA7134_RS_PITCH(0),bpl*2);
|
|
} else {
|
|
/* non-interlaced */
|
|
saa_writel(SAA7134_RS_BA1(0),base);
|
|
saa_writel(SAA7134_RS_BA2(0),base);
|
|
saa_writel(SAA7134_RS_PITCH(0),bpl);
|
|
}
|
|
saa_writel(SAA7134_RS_CONTROL(0),control);
|
|
|
|
if (buf->fmt->planar) {
|
|
/* DMA: setup channel 4+5 (= planar task A) */
|
|
bpl_uv = bpl >> buf->fmt->hshift;
|
|
lines_uv = buf->vb.height >> buf->fmt->vshift;
|
|
base2 = base + bpl * buf->vb.height;
|
|
base3 = base2 + bpl_uv * lines_uv;
|
|
if (buf->fmt->uvswap)
|
|
tmp = base2, base2 = base3, base3 = tmp;
|
|
dprintk("uv: bpl=%ld lines=%ld base2/3=%ld/%ld\n",
|
|
bpl_uv,lines_uv,base2,base3);
|
|
if (V4L2_FIELD_HAS_BOTH(buf->vb.field)) {
|
|
/* interlaced */
|
|
saa_writel(SAA7134_RS_BA1(4),base2);
|
|
saa_writel(SAA7134_RS_BA2(4),base2+bpl_uv);
|
|
saa_writel(SAA7134_RS_PITCH(4),bpl_uv*2);
|
|
saa_writel(SAA7134_RS_BA1(5),base3);
|
|
saa_writel(SAA7134_RS_BA2(5),base3+bpl_uv);
|
|
saa_writel(SAA7134_RS_PITCH(5),bpl_uv*2);
|
|
} else {
|
|
/* non-interlaced */
|
|
saa_writel(SAA7134_RS_BA1(4),base2);
|
|
saa_writel(SAA7134_RS_BA2(4),base2);
|
|
saa_writel(SAA7134_RS_PITCH(4),bpl_uv);
|
|
saa_writel(SAA7134_RS_BA1(5),base3);
|
|
saa_writel(SAA7134_RS_BA2(5),base3);
|
|
saa_writel(SAA7134_RS_PITCH(5),bpl_uv);
|
|
}
|
|
saa_writel(SAA7134_RS_CONTROL(4),control);
|
|
saa_writel(SAA7134_RS_CONTROL(5),control);
|
|
}
|
|
|
|
/* start DMA */
|
|
saa7134_set_dmabits(dev);
|
|
mod_timer(&dev->video_q.timeout, jiffies+BUFFER_TIMEOUT);
|
|
return 0;
|
|
}
|
|
|
|
static int buffer_prepare(struct videobuf_queue *q,
|
|
struct videobuf_buffer *vb,
|
|
enum v4l2_field field)
|
|
{
|
|
struct saa7134_fh *fh = q->priv_data;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb);
|
|
unsigned int size;
|
|
int err;
|
|
|
|
/* sanity checks */
|
|
if (NULL == fh->fmt)
|
|
return -EINVAL;
|
|
if (fh->width < 48 ||
|
|
fh->height < 32 ||
|
|
fh->width/4 > dev->crop_current.width ||
|
|
fh->height/4 > dev->crop_current.height ||
|
|
fh->width > dev->crop_bounds.width ||
|
|
fh->height > dev->crop_bounds.height)
|
|
return -EINVAL;
|
|
size = (fh->width * fh->height * fh->fmt->depth) >> 3;
|
|
if (0 != buf->vb.baddr && buf->vb.bsize < size)
|
|
return -EINVAL;
|
|
|
|
dprintk("buffer_prepare [%d,size=%dx%d,bytes=%d,fields=%s,%s]\n",
|
|
vb->i,fh->width,fh->height,size,v4l2_field_names[field],
|
|
fh->fmt->name);
|
|
if (buf->vb.width != fh->width ||
|
|
buf->vb.height != fh->height ||
|
|
buf->vb.size != size ||
|
|
buf->vb.field != field ||
|
|
buf->fmt != fh->fmt) {
|
|
saa7134_dma_free(q,buf);
|
|
}
|
|
|
|
if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
|
|
struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb);
|
|
|
|
buf->vb.width = fh->width;
|
|
buf->vb.height = fh->height;
|
|
buf->vb.size = size;
|
|
buf->vb.field = field;
|
|
buf->fmt = fh->fmt;
|
|
buf->pt = &fh->pt_cap;
|
|
|
|
err = videobuf_iolock(q,&buf->vb,&dev->ovbuf);
|
|
if (err)
|
|
goto oops;
|
|
err = saa7134_pgtable_build(dev->pci,buf->pt,
|
|
dma->sglist,
|
|
dma->sglen,
|
|
saa7134_buffer_startpage(buf));
|
|
if (err)
|
|
goto oops;
|
|
}
|
|
buf->vb.state = VIDEOBUF_PREPARED;
|
|
buf->activate = buffer_activate;
|
|
return 0;
|
|
|
|
oops:
|
|
saa7134_dma_free(q,buf);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size)
|
|
{
|
|
struct saa7134_fh *fh = q->priv_data;
|
|
|
|
*size = fh->fmt->depth * fh->width * fh->height >> 3;
|
|
if (0 == *count)
|
|
*count = gbuffers;
|
|
*count = saa7134_buffer_count(*size,*count);
|
|
return 0;
|
|
}
|
|
|
|
static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb)
|
|
{
|
|
struct saa7134_fh *fh = q->priv_data;
|
|
struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb);
|
|
|
|
saa7134_buffer_queue(fh->dev,&fh->dev->video_q,buf);
|
|
}
|
|
|
|
static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb)
|
|
{
|
|
struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb);
|
|
|
|
saa7134_dma_free(q,buf);
|
|
}
|
|
|
|
static struct videobuf_queue_ops video_qops = {
|
|
.buf_setup = buffer_setup,
|
|
.buf_prepare = buffer_prepare,
|
|
.buf_queue = buffer_queue,
|
|
.buf_release = buffer_release,
|
|
};
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
int saa7134_g_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, struct v4l2_control *c)
|
|
{
|
|
const struct v4l2_queryctrl* ctrl;
|
|
|
|
ctrl = ctrl_by_id(c->id);
|
|
if (NULL == ctrl)
|
|
return -EINVAL;
|
|
switch (c->id) {
|
|
case V4L2_CID_BRIGHTNESS:
|
|
c->value = dev->ctl_bright;
|
|
break;
|
|
case V4L2_CID_HUE:
|
|
c->value = dev->ctl_hue;
|
|
break;
|
|
case V4L2_CID_CONTRAST:
|
|
c->value = dev->ctl_contrast;
|
|
break;
|
|
case V4L2_CID_SATURATION:
|
|
c->value = dev->ctl_saturation;
|
|
break;
|
|
case V4L2_CID_AUDIO_MUTE:
|
|
c->value = dev->ctl_mute;
|
|
break;
|
|
case V4L2_CID_AUDIO_VOLUME:
|
|
c->value = dev->ctl_volume;
|
|
break;
|
|
case V4L2_CID_PRIVATE_INVERT:
|
|
c->value = dev->ctl_invert;
|
|
break;
|
|
case V4L2_CID_HFLIP:
|
|
c->value = dev->ctl_mirror;
|
|
break;
|
|
case V4L2_CID_PRIVATE_Y_EVEN:
|
|
c->value = dev->ctl_y_even;
|
|
break;
|
|
case V4L2_CID_PRIVATE_Y_ODD:
|
|
c->value = dev->ctl_y_odd;
|
|
break;
|
|
case V4L2_CID_PRIVATE_AUTOMUTE:
|
|
c->value = dev->ctl_automute;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(saa7134_g_ctrl_internal);
|
|
|
|
static int saa7134_g_ctrl(struct file *file, void *priv, struct v4l2_control *c)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
|
|
return saa7134_g_ctrl_internal(fh->dev, fh, c);
|
|
}
|
|
|
|
int saa7134_s_ctrl_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, struct v4l2_control *c)
|
|
{
|
|
const struct v4l2_queryctrl* ctrl;
|
|
unsigned long flags;
|
|
int restart_overlay = 0;
|
|
int err;
|
|
|
|
/* When called from the empress code fh == NULL.
|
|
That needs to be fixed somehow, but for now this is
|
|
good enough. */
|
|
if (fh) {
|
|
err = v4l2_prio_check(&dev->prio, &fh->prio);
|
|
if (0 != err)
|
|
return err;
|
|
}
|
|
err = -EINVAL;
|
|
|
|
mutex_lock(&dev->lock);
|
|
|
|
ctrl = ctrl_by_id(c->id);
|
|
if (NULL == ctrl)
|
|
goto error;
|
|
|
|
dprintk("set_control name=%s val=%d\n",ctrl->name,c->value);
|
|
switch (ctrl->type) {
|
|
case V4L2_CTRL_TYPE_BOOLEAN:
|
|
case V4L2_CTRL_TYPE_MENU:
|
|
case V4L2_CTRL_TYPE_INTEGER:
|
|
if (c->value < ctrl->minimum)
|
|
c->value = ctrl->minimum;
|
|
if (c->value > ctrl->maximum)
|
|
c->value = ctrl->maximum;
|
|
break;
|
|
default:
|
|
/* nothing */;
|
|
};
|
|
switch (c->id) {
|
|
case V4L2_CID_BRIGHTNESS:
|
|
dev->ctl_bright = c->value;
|
|
saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright);
|
|
break;
|
|
case V4L2_CID_HUE:
|
|
dev->ctl_hue = c->value;
|
|
saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue);
|
|
break;
|
|
case V4L2_CID_CONTRAST:
|
|
dev->ctl_contrast = c->value;
|
|
saa_writeb(SAA7134_DEC_LUMA_CONTRAST,
|
|
dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast);
|
|
break;
|
|
case V4L2_CID_SATURATION:
|
|
dev->ctl_saturation = c->value;
|
|
saa_writeb(SAA7134_DEC_CHROMA_SATURATION,
|
|
dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation);
|
|
break;
|
|
case V4L2_CID_AUDIO_MUTE:
|
|
dev->ctl_mute = c->value;
|
|
saa7134_tvaudio_setmute(dev);
|
|
break;
|
|
case V4L2_CID_AUDIO_VOLUME:
|
|
dev->ctl_volume = c->value;
|
|
saa7134_tvaudio_setvolume(dev,dev->ctl_volume);
|
|
break;
|
|
case V4L2_CID_PRIVATE_INVERT:
|
|
dev->ctl_invert = c->value;
|
|
saa_writeb(SAA7134_DEC_LUMA_CONTRAST,
|
|
dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast);
|
|
saa_writeb(SAA7134_DEC_CHROMA_SATURATION,
|
|
dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation);
|
|
break;
|
|
case V4L2_CID_HFLIP:
|
|
dev->ctl_mirror = c->value;
|
|
restart_overlay = 1;
|
|
break;
|
|
case V4L2_CID_PRIVATE_Y_EVEN:
|
|
dev->ctl_y_even = c->value;
|
|
restart_overlay = 1;
|
|
break;
|
|
case V4L2_CID_PRIVATE_Y_ODD:
|
|
dev->ctl_y_odd = c->value;
|
|
restart_overlay = 1;
|
|
break;
|
|
case V4L2_CID_PRIVATE_AUTOMUTE:
|
|
{
|
|
struct v4l2_priv_tun_config tda9887_cfg;
|
|
|
|
tda9887_cfg.tuner = TUNER_TDA9887;
|
|
tda9887_cfg.priv = &dev->tda9887_conf;
|
|
|
|
dev->ctl_automute = c->value;
|
|
if (dev->tda9887_conf) {
|
|
if (dev->ctl_automute)
|
|
dev->tda9887_conf |= TDA9887_AUTOMUTE;
|
|
else
|
|
dev->tda9887_conf &= ~TDA9887_AUTOMUTE;
|
|
|
|
saa_call_all(dev, tuner, s_config, &tda9887_cfg);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
goto error;
|
|
}
|
|
if (restart_overlay && fh && res_check(fh, RESOURCE_OVERLAY)) {
|
|
spin_lock_irqsave(&dev->slock,flags);
|
|
stop_preview(dev,fh);
|
|
start_preview(dev,fh);
|
|
spin_unlock_irqrestore(&dev->slock,flags);
|
|
}
|
|
err = 0;
|
|
|
|
error:
|
|
mutex_unlock(&dev->lock);
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL_GPL(saa7134_s_ctrl_internal);
|
|
|
|
static int saa7134_s_ctrl(struct file *file, void *f, struct v4l2_control *c)
|
|
{
|
|
struct saa7134_fh *fh = f;
|
|
|
|
return saa7134_s_ctrl_internal(fh->dev, fh, c);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static struct videobuf_queue* saa7134_queue(struct saa7134_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 saa7134_resource(struct saa7134_fh *fh)
|
|
{
|
|
if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return RESOURCE_VIDEO;
|
|
|
|
if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE)
|
|
return RESOURCE_VBI;
|
|
|
|
BUG();
|
|
return 0;
|
|
}
|
|
|
|
static int video_open(struct file *file)
|
|
{
|
|
int minor = video_devdata(file)->minor;
|
|
struct saa7134_dev *dev;
|
|
struct saa7134_fh *fh;
|
|
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
int radio = 0;
|
|
|
|
mutex_lock(&saa7134_devlist_lock);
|
|
list_for_each_entry(dev, &saa7134_devlist, devlist) {
|
|
if (dev->video_dev && (dev->video_dev->minor == minor))
|
|
goto found;
|
|
if (dev->radio_dev && (dev->radio_dev->minor == minor)) {
|
|
radio = 1;
|
|
goto found;
|
|
}
|
|
if (dev->vbi_dev && (dev->vbi_dev->minor == minor)) {
|
|
type = V4L2_BUF_TYPE_VBI_CAPTURE;
|
|
goto found;
|
|
}
|
|
}
|
|
mutex_unlock(&saa7134_devlist_lock);
|
|
return -ENODEV;
|
|
|
|
found:
|
|
mutex_unlock(&saa7134_devlist_lock);
|
|
|
|
dprintk("open minor=%d radio=%d type=%s\n",minor,radio,
|
|
v4l2_type_names[type]);
|
|
|
|
/* allocate + initialize per filehandle data */
|
|
fh = kzalloc(sizeof(*fh),GFP_KERNEL);
|
|
if (NULL == fh)
|
|
return -ENOMEM;
|
|
|
|
file->private_data = fh;
|
|
fh->dev = dev;
|
|
fh->radio = radio;
|
|
fh->type = type;
|
|
fh->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24);
|
|
fh->width = 720;
|
|
fh->height = 576;
|
|
v4l2_prio_open(&dev->prio,&fh->prio);
|
|
|
|
videobuf_queue_sg_init(&fh->cap, &video_qops,
|
|
&dev->pci->dev, &dev->slock,
|
|
V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
|
V4L2_FIELD_INTERLACED,
|
|
sizeof(struct saa7134_buf),
|
|
fh);
|
|
videobuf_queue_sg_init(&fh->vbi, &saa7134_vbi_qops,
|
|
&dev->pci->dev, &dev->slock,
|
|
V4L2_BUF_TYPE_VBI_CAPTURE,
|
|
V4L2_FIELD_SEQ_TB,
|
|
sizeof(struct saa7134_buf),
|
|
fh);
|
|
saa7134_pgtable_alloc(dev->pci,&fh->pt_cap);
|
|
saa7134_pgtable_alloc(dev->pci,&fh->pt_vbi);
|
|
|
|
if (fh->radio) {
|
|
/* switch to radio mode */
|
|
saa7134_tvaudio_setinput(dev,&card(dev).radio);
|
|
saa_call_all(dev, tuner, s_radio);
|
|
} else {
|
|
/* switch to video/vbi mode */
|
|
video_mux(dev,dev->ctl_input);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
video_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
|
|
{
|
|
struct saa7134_fh *fh = file->private_data;
|
|
|
|
switch (fh->type) {
|
|
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
|
|
if (res_locked(fh->dev,RESOURCE_VIDEO))
|
|
return -EBUSY;
|
|
return videobuf_read_one(saa7134_queue(fh),
|
|
data, count, ppos,
|
|
file->f_flags & O_NONBLOCK);
|
|
case V4L2_BUF_TYPE_VBI_CAPTURE:
|
|
if (!res_get(fh->dev,fh,RESOURCE_VBI))
|
|
return -EBUSY;
|
|
return videobuf_read_stream(saa7134_queue(fh),
|
|
data, count, ppos, 1,
|
|
file->f_flags & O_NONBLOCK);
|
|
break;
|
|
default:
|
|
BUG();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static unsigned int
|
|
video_poll(struct file *file, struct poll_table_struct *wait)
|
|
{
|
|
struct saa7134_fh *fh = file->private_data;
|
|
struct videobuf_buffer *buf = NULL;
|
|
|
|
if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type)
|
|
return videobuf_poll_stream(file, &fh->vbi, wait);
|
|
|
|
if (res_check(fh,RESOURCE_VIDEO)) {
|
|
if (!list_empty(&fh->cap.stream))
|
|
buf = list_entry(fh->cap.stream.next, struct videobuf_buffer, stream);
|
|
} else {
|
|
mutex_lock(&fh->cap.vb_lock);
|
|
if (UNSET == fh->cap.read_off) {
|
|
/* need to capture a new frame */
|
|
if (res_locked(fh->dev,RESOURCE_VIDEO))
|
|
goto err;
|
|
if (0 != fh->cap.ops->buf_prepare(&fh->cap,fh->cap.read_buf,fh->cap.field))
|
|
goto err;
|
|
fh->cap.ops->buf_queue(&fh->cap,fh->cap.read_buf);
|
|
fh->cap.read_off = 0;
|
|
}
|
|
mutex_unlock(&fh->cap.vb_lock);
|
|
buf = fh->cap.read_buf;
|
|
}
|
|
|
|
if (!buf)
|
|
return POLLERR;
|
|
|
|
poll_wait(file, &buf->done, wait);
|
|
if (buf->state == VIDEOBUF_DONE ||
|
|
buf->state == VIDEOBUF_ERROR)
|
|
return POLLIN|POLLRDNORM;
|
|
return 0;
|
|
|
|
err:
|
|
mutex_unlock(&fh->cap.vb_lock);
|
|
return POLLERR;
|
|
}
|
|
|
|
static int video_release(struct file *file)
|
|
{
|
|
struct saa7134_fh *fh = file->private_data;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
unsigned long flags;
|
|
|
|
/* turn off overlay */
|
|
if (res_check(fh, RESOURCE_OVERLAY)) {
|
|
spin_lock_irqsave(&dev->slock,flags);
|
|
stop_preview(dev,fh);
|
|
spin_unlock_irqrestore(&dev->slock,flags);
|
|
res_free(dev,fh,RESOURCE_OVERLAY);
|
|
}
|
|
|
|
/* stop video capture */
|
|
if (res_check(fh, RESOURCE_VIDEO)) {
|
|
videobuf_streamoff(&fh->cap);
|
|
res_free(dev,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 (res_check(fh, RESOURCE_VBI)) {
|
|
videobuf_stop(&fh->vbi);
|
|
res_free(dev,fh,RESOURCE_VBI);
|
|
}
|
|
|
|
/* ts-capture will not work in planar mode, so turn it off Hac: 04.05*/
|
|
saa_andorb(SAA7134_OFMT_VIDEO_A, 0x1f, 0);
|
|
saa_andorb(SAA7134_OFMT_VIDEO_B, 0x1f, 0);
|
|
saa_andorb(SAA7134_OFMT_DATA_A, 0x1f, 0);
|
|
saa_andorb(SAA7134_OFMT_DATA_B, 0x1f, 0);
|
|
|
|
saa_call_all(dev, core, s_standby, 0);
|
|
|
|
/* free stuff */
|
|
videobuf_mmap_free(&fh->cap);
|
|
videobuf_mmap_free(&fh->vbi);
|
|
saa7134_pgtable_free(dev->pci,&fh->pt_cap);
|
|
saa7134_pgtable_free(dev->pci,&fh->pt_vbi);
|
|
|
|
v4l2_prio_close(&dev->prio,&fh->prio);
|
|
file->private_data = NULL;
|
|
kfree(fh);
|
|
return 0;
|
|
}
|
|
|
|
static int video_mmap(struct file *file, struct vm_area_struct * vma)
|
|
{
|
|
struct saa7134_fh *fh = file->private_data;
|
|
|
|
return videobuf_mmap_mapper(saa7134_queue(fh), vma);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static int saa7134_try_get_set_fmt_vbi_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
struct saa7134_tvnorm *norm = dev->tvnorm;
|
|
|
|
f->fmt.vbi.sampling_rate = 6750000 * 4;
|
|
f->fmt.vbi.samples_per_line = 2048 /* VBI_LINE_LENGTH */;
|
|
f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
|
|
f->fmt.vbi.offset = 64 * 4;
|
|
f->fmt.vbi.start[0] = norm->vbi_v_start_0;
|
|
f->fmt.vbi.count[0] = norm->vbi_v_stop_0 - norm->vbi_v_start_0 +1;
|
|
f->fmt.vbi.start[1] = norm->vbi_v_start_1;
|
|
f->fmt.vbi.count[1] = f->fmt.vbi.count[0];
|
|
f->fmt.vbi.flags = 0; /* VBI_UNSYNC VBI_INTERLACED */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_g_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
|
|
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;
|
|
}
|
|
|
|
static int saa7134_g_fmt_vid_overlay(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
|
|
if (saa7134_no_overlay > 0) {
|
|
printk(KERN_ERR "V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n");
|
|
return -EINVAL;
|
|
}
|
|
f->fmt.win = fh->win;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_try_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
struct saa7134_format *fmt;
|
|
enum v4l2_field field;
|
|
unsigned int maxw, maxh;
|
|
|
|
fmt = format_by_fourcc(f->fmt.pix.pixelformat);
|
|
if (NULL == fmt)
|
|
return -EINVAL;
|
|
|
|
field = f->fmt.pix.field;
|
|
maxw = min(dev->crop_current.width*4, dev->crop_bounds.width);
|
|
maxh = min(dev->crop_current.height*4, dev->crop_bounds.height);
|
|
|
|
if (V4L2_FIELD_ANY == field) {
|
|
field = (f->fmt.pix.height > maxh/2)
|
|
? V4L2_FIELD_INTERLACED
|
|
: V4L2_FIELD_BOTTOM;
|
|
}
|
|
switch (field) {
|
|
case V4L2_FIELD_TOP:
|
|
case V4L2_FIELD_BOTTOM:
|
|
maxh = maxh / 2;
|
|
break;
|
|
case V4L2_FIELD_INTERLACED:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static int saa7134_try_fmt_vid_overlay(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
if (saa7134_no_overlay > 0) {
|
|
printk(KERN_ERR "V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return verify_preview(dev, &f->fmt.win);
|
|
}
|
|
|
|
static int saa7134_s_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
int err;
|
|
|
|
err = saa7134_try_fmt_vid_cap(file, priv, f);
|
|
if (0 != err)
|
|
return err;
|
|
|
|
fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
|
|
fh->width = f->fmt.pix.width;
|
|
fh->height = f->fmt.pix.height;
|
|
fh->cap.field = f->fmt.pix.field;
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_s_fmt_vid_overlay(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
int err;
|
|
unsigned long flags;
|
|
|
|
if (saa7134_no_overlay > 0) {
|
|
printk(KERN_ERR "V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n");
|
|
return -EINVAL;
|
|
}
|
|
err = verify_preview(dev, &f->fmt.win);
|
|
if (0 != err)
|
|
return err;
|
|
|
|
mutex_lock(&dev->lock);
|
|
|
|
fh->win = f->fmt.win;
|
|
fh->nclips = f->fmt.win.clipcount;
|
|
|
|
if (fh->nclips > 8)
|
|
fh->nclips = 8;
|
|
|
|
if (copy_from_user(fh->clips, f->fmt.win.clips,
|
|
sizeof(struct v4l2_clip)*fh->nclips)) {
|
|
mutex_unlock(&dev->lock);
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (res_check(fh, RESOURCE_OVERLAY)) {
|
|
spin_lock_irqsave(&dev->slock, flags);
|
|
stop_preview(dev, fh);
|
|
start_preview(dev, fh);
|
|
spin_unlock_irqrestore(&dev->slock, flags);
|
|
}
|
|
|
|
mutex_unlock(&dev->lock);
|
|
return 0;
|
|
}
|
|
|
|
int saa7134_queryctrl(struct file *file, void *priv, struct v4l2_queryctrl *c)
|
|
{
|
|
const struct v4l2_queryctrl *ctrl;
|
|
|
|
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;
|
|
ctrl = ctrl_by_id(c->id);
|
|
*c = (NULL != ctrl) ? *ctrl : no_ctrl;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(saa7134_queryctrl);
|
|
|
|
static int saa7134_enum_input(struct file *file, void *priv,
|
|
struct v4l2_input *i)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
unsigned int n;
|
|
|
|
n = i->index;
|
|
if (n >= SAA7134_INPUT_MAX)
|
|
return -EINVAL;
|
|
if (NULL == card_in(dev, i->index).name)
|
|
return -EINVAL;
|
|
memset(i, 0, sizeof(*i));
|
|
i->index = n;
|
|
i->type = V4L2_INPUT_TYPE_CAMERA;
|
|
strcpy(i->name, card_in(dev, n).name);
|
|
if (card_in(dev, n).tv)
|
|
i->type = V4L2_INPUT_TYPE_TUNER;
|
|
i->audioset = 1;
|
|
if (n == dev->ctl_input) {
|
|
int v1 = saa_readb(SAA7134_STATUS_VIDEO1);
|
|
int v2 = saa_readb(SAA7134_STATUS_VIDEO2);
|
|
|
|
if (0 != (v1 & 0x40))
|
|
i->status |= V4L2_IN_ST_NO_H_LOCK;
|
|
if (0 != (v2 & 0x40))
|
|
i->status |= V4L2_IN_ST_NO_SYNC;
|
|
if (0 != (v2 & 0x0e))
|
|
i->status |= V4L2_IN_ST_MACROVISION;
|
|
}
|
|
i->std = SAA7134_NORMS;
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_g_input(struct file *file, void *priv, unsigned int *i)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
*i = dev->ctl_input;
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_s_input(struct file *file, void *priv, unsigned int i)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
int err;
|
|
|
|
err = v4l2_prio_check(&dev->prio, &fh->prio);
|
|
if (0 != err)
|
|
return err;
|
|
|
|
if (i < 0 || i >= SAA7134_INPUT_MAX)
|
|
return -EINVAL;
|
|
if (NULL == card_in(dev, i).name)
|
|
return -EINVAL;
|
|
mutex_lock(&dev->lock);
|
|
video_mux(dev, i);
|
|
mutex_unlock(&dev->lock);
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_querycap(struct file *file, void *priv,
|
|
struct v4l2_capability *cap)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
unsigned int tuner_type = dev->tuner_type;
|
|
|
|
strcpy(cap->driver, "saa7134");
|
|
strlcpy(cap->card, saa7134_boards[dev->board].name,
|
|
sizeof(cap->card));
|
|
sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
|
|
cap->version = SAA7134_VERSION_CODE;
|
|
cap->capabilities =
|
|
V4L2_CAP_VIDEO_CAPTURE |
|
|
V4L2_CAP_VBI_CAPTURE |
|
|
V4L2_CAP_READWRITE |
|
|
V4L2_CAP_STREAMING |
|
|
V4L2_CAP_TUNER;
|
|
if (saa7134_no_overlay <= 0)
|
|
cap->capabilities |= V4L2_CAP_VIDEO_OVERLAY;
|
|
|
|
if ((tuner_type == TUNER_ABSENT) || (tuner_type == UNSET))
|
|
cap->capabilities &= ~V4L2_CAP_TUNER;
|
|
return 0;
|
|
}
|
|
|
|
int saa7134_s_std_internal(struct saa7134_dev *dev, struct saa7134_fh *fh, v4l2_std_id *id)
|
|
{
|
|
unsigned long flags;
|
|
unsigned int i;
|
|
v4l2_std_id fixup;
|
|
int err;
|
|
|
|
/* When called from the empress code fh == NULL.
|
|
That needs to be fixed somehow, but for now this is
|
|
good enough. */
|
|
if (fh) {
|
|
err = v4l2_prio_check(&dev->prio, &fh->prio);
|
|
if (0 != err)
|
|
return err;
|
|
} else if (res_locked(dev, RESOURCE_OVERLAY)) {
|
|
/* Don't change the std from the mpeg device
|
|
if overlay is active. */
|
|
return -EBUSY;
|
|
}
|
|
|
|
for (i = 0; i < TVNORMS; i++)
|
|
if (*id == tvnorms[i].id)
|
|
break;
|
|
|
|
if (i == TVNORMS)
|
|
for (i = 0; i < TVNORMS; i++)
|
|
if (*id & tvnorms[i].id)
|
|
break;
|
|
if (i == TVNORMS)
|
|
return -EINVAL;
|
|
|
|
if ((*id & V4L2_STD_SECAM) && (secam[0] != '-')) {
|
|
if (secam[0] == 'L' || secam[0] == 'l') {
|
|
if (secam[1] == 'C' || secam[1] == 'c')
|
|
fixup = V4L2_STD_SECAM_LC;
|
|
else
|
|
fixup = V4L2_STD_SECAM_L;
|
|
} else {
|
|
if (secam[0] == 'D' || secam[0] == 'd')
|
|
fixup = V4L2_STD_SECAM_DK;
|
|
else
|
|
fixup = V4L2_STD_SECAM;
|
|
}
|
|
for (i = 0; i < TVNORMS; i++)
|
|
if (fixup == tvnorms[i].id)
|
|
break;
|
|
}
|
|
|
|
*id = tvnorms[i].id;
|
|
|
|
mutex_lock(&dev->lock);
|
|
if (fh && res_check(fh, RESOURCE_OVERLAY)) {
|
|
spin_lock_irqsave(&dev->slock, flags);
|
|
stop_preview(dev, fh);
|
|
spin_unlock_irqrestore(&dev->slock, flags);
|
|
|
|
set_tvnorm(dev, &tvnorms[i]);
|
|
|
|
spin_lock_irqsave(&dev->slock, flags);
|
|
start_preview(dev, fh);
|
|
spin_unlock_irqrestore(&dev->slock, flags);
|
|
} else
|
|
set_tvnorm(dev, &tvnorms[i]);
|
|
|
|
saa7134_tvaudio_do_scan(dev);
|
|
mutex_unlock(&dev->lock);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(saa7134_s_std_internal);
|
|
|
|
static int saa7134_s_std(struct file *file, void *priv, v4l2_std_id *id)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
|
|
return saa7134_s_std_internal(fh->dev, fh, id);
|
|
}
|
|
|
|
static int saa7134_g_std(struct file *file, void *priv, v4l2_std_id *id)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
*id = dev->tvnorm->id;
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_cropcap(struct file *file, void *priv,
|
|
struct v4l2_cropcap *cap)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
|
|
cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
|
|
return -EINVAL;
|
|
cap->bounds = dev->crop_bounds;
|
|
cap->defrect = dev->crop_defrect;
|
|
cap->pixelaspect.numerator = 1;
|
|
cap->pixelaspect.denominator = 1;
|
|
if (dev->tvnorm->id & V4L2_STD_525_60) {
|
|
cap->pixelaspect.numerator = 11;
|
|
cap->pixelaspect.denominator = 10;
|
|
}
|
|
if (dev->tvnorm->id & V4L2_STD_625_50) {
|
|
cap->pixelaspect.numerator = 54;
|
|
cap->pixelaspect.denominator = 59;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_g_crop(struct file *file, void *f, struct v4l2_crop *crop)
|
|
{
|
|
struct saa7134_fh *fh = f;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
|
|
crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
|
|
return -EINVAL;
|
|
crop->c = dev->crop_current;
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_s_crop(struct file *file, void *f, struct v4l2_crop *crop)
|
|
{
|
|
struct saa7134_fh *fh = f;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
struct v4l2_rect *b = &dev->crop_bounds;
|
|
|
|
if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
|
|
crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
|
|
return -EINVAL;
|
|
if (crop->c.height < 0)
|
|
return -EINVAL;
|
|
if (crop->c.width < 0)
|
|
return -EINVAL;
|
|
|
|
if (res_locked(fh->dev, RESOURCE_OVERLAY))
|
|
return -EBUSY;
|
|
if (res_locked(fh->dev, RESOURCE_VIDEO))
|
|
return -EBUSY;
|
|
|
|
if (crop->c.top < b->top)
|
|
crop->c.top = b->top;
|
|
if (crop->c.top > b->top + b->height)
|
|
crop->c.top = b->top + b->height;
|
|
if (crop->c.height > b->top - crop->c.top + b->height)
|
|
crop->c.height = b->top - crop->c.top + b->height;
|
|
|
|
if (crop->c.left < b->left)
|
|
crop->c.left = b->left;
|
|
if (crop->c.left > b->left + b->width)
|
|
crop->c.left = b->left + b->width;
|
|
if (crop->c.width > b->left - crop->c.left + b->width)
|
|
crop->c.width = b->left - crop->c.left + b->width;
|
|
|
|
dev->crop_current = crop->c;
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_g_tuner(struct file *file, void *priv,
|
|
struct v4l2_tuner *t)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
int n;
|
|
|
|
if (0 != t->index)
|
|
return -EINVAL;
|
|
memset(t, 0, sizeof(*t));
|
|
for (n = 0; n < SAA7134_INPUT_MAX; n++)
|
|
if (card_in(dev, n).tv)
|
|
break;
|
|
if (NULL != card_in(dev, n).name) {
|
|
strcpy(t->name, "Television");
|
|
t->type = V4L2_TUNER_ANALOG_TV;
|
|
t->capability = V4L2_TUNER_CAP_NORM |
|
|
V4L2_TUNER_CAP_STEREO |
|
|
V4L2_TUNER_CAP_LANG1 |
|
|
V4L2_TUNER_CAP_LANG2;
|
|
t->rangehigh = 0xffffffffUL;
|
|
t->rxsubchans = saa7134_tvaudio_getstereo(dev);
|
|
t->audmode = saa7134_tvaudio_rx2mode(t->rxsubchans);
|
|
}
|
|
if (0 != (saa_readb(SAA7134_STATUS_VIDEO1) & 0x03))
|
|
t->signal = 0xffff;
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_s_tuner(struct file *file, void *priv,
|
|
struct v4l2_tuner *t)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
int rx, mode, err;
|
|
|
|
err = v4l2_prio_check(&dev->prio, &fh->prio);
|
|
if (0 != err)
|
|
return err;
|
|
|
|
mode = dev->thread.mode;
|
|
if (UNSET == mode) {
|
|
rx = saa7134_tvaudio_getstereo(dev);
|
|
mode = saa7134_tvaudio_rx2mode(t->rxsubchans);
|
|
}
|
|
if (mode != t->audmode)
|
|
dev->thread.mode = t->audmode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_g_frequency(struct file *file, void *priv,
|
|
struct v4l2_frequency *f)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
|
|
f->frequency = dev->ctl_freq;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_s_frequency(struct file *file, void *priv,
|
|
struct v4l2_frequency *f)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
int err;
|
|
|
|
err = v4l2_prio_check(&dev->prio, &fh->prio);
|
|
if (0 != err)
|
|
return err;
|
|
|
|
if (0 != f->tuner)
|
|
return -EINVAL;
|
|
if (0 == fh->radio && V4L2_TUNER_ANALOG_TV != f->type)
|
|
return -EINVAL;
|
|
if (1 == fh->radio && V4L2_TUNER_RADIO != f->type)
|
|
return -EINVAL;
|
|
mutex_lock(&dev->lock);
|
|
dev->ctl_freq = f->frequency;
|
|
|
|
saa_call_all(dev, tuner, s_frequency, f);
|
|
|
|
saa7134_tvaudio_do_scan(dev);
|
|
mutex_unlock(&dev->lock);
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
|
|
{
|
|
strcpy(a->name, "audio");
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_s_audio(struct file *file, void *priv, struct v4l2_audio *a)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_g_priority(struct file *file, void *f, enum v4l2_priority *p)
|
|
{
|
|
struct saa7134_fh *fh = f;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
*p = v4l2_prio_max(&dev->prio);
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_s_priority(struct file *file, void *f,
|
|
enum v4l2_priority prio)
|
|
{
|
|
struct saa7134_fh *fh = f;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
return v4l2_prio_change(&dev->prio, &fh->prio, prio);
|
|
}
|
|
|
|
static int saa7134_enum_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_fmtdesc *f)
|
|
{
|
|
if (f->index >= FORMATS)
|
|
return -EINVAL;
|
|
|
|
strlcpy(f->description, formats[f->index].name,
|
|
sizeof(f->description));
|
|
|
|
f->pixelformat = formats[f->index].fourcc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_enum_fmt_vid_overlay(struct file *file, void *priv,
|
|
struct v4l2_fmtdesc *f)
|
|
{
|
|
if (saa7134_no_overlay > 0) {
|
|
printk(KERN_ERR "V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((f->index >= FORMATS) || formats[f->index].planar)
|
|
return -EINVAL;
|
|
|
|
strlcpy(f->description, formats[f->index].name,
|
|
sizeof(f->description));
|
|
|
|
f->pixelformat = formats[f->index].fourcc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_g_fbuf(struct file *file, void *f,
|
|
struct v4l2_framebuffer *fb)
|
|
{
|
|
struct saa7134_fh *fh = f;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
*fb = dev->ovbuf;
|
|
fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_s_fbuf(struct file *file, void *f,
|
|
struct v4l2_framebuffer *fb)
|
|
{
|
|
struct saa7134_fh *fh = f;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
struct saa7134_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;
|
|
|
|
/* ok, accept it */
|
|
dev->ovbuf = *fb;
|
|
dev->ovfmt = fmt;
|
|
if (0 == dev->ovbuf.fmt.bytesperline)
|
|
dev->ovbuf.fmt.bytesperline =
|
|
dev->ovbuf.fmt.width*fmt->depth/8;
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_overlay(struct file *file, void *f, unsigned int on)
|
|
{
|
|
struct saa7134_fh *fh = f;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
unsigned long flags;
|
|
|
|
if (on) {
|
|
if (saa7134_no_overlay > 0) {
|
|
dprintk("no_overlay\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!res_get(dev, fh, RESOURCE_OVERLAY))
|
|
return -EBUSY;
|
|
spin_lock_irqsave(&dev->slock, flags);
|
|
start_preview(dev, fh);
|
|
spin_unlock_irqrestore(&dev->slock, flags);
|
|
}
|
|
if (!on) {
|
|
if (!res_check(fh, RESOURCE_OVERLAY))
|
|
return -EINVAL;
|
|
spin_lock_irqsave(&dev->slock, flags);
|
|
stop_preview(dev, fh);
|
|
spin_unlock_irqrestore(&dev->slock, flags);
|
|
res_free(dev, fh, RESOURCE_OVERLAY);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_VIDEO_V4L1_COMPAT
|
|
static int vidiocgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf)
|
|
{
|
|
struct saa7134_fh *fh = file->private_data;
|
|
return videobuf_cgmbuf(saa7134_queue(fh), mbuf, 8);
|
|
}
|
|
#endif
|
|
|
|
static int saa7134_reqbufs(struct file *file, void *priv,
|
|
struct v4l2_requestbuffers *p)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
return videobuf_reqbufs(saa7134_queue(fh), p);
|
|
}
|
|
|
|
static int saa7134_querybuf(struct file *file, void *priv,
|
|
struct v4l2_buffer *b)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
return videobuf_querybuf(saa7134_queue(fh), b);
|
|
}
|
|
|
|
static int saa7134_qbuf(struct file *file, void *priv, struct v4l2_buffer *b)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
return videobuf_qbuf(saa7134_queue(fh), b);
|
|
}
|
|
|
|
static int saa7134_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
return videobuf_dqbuf(saa7134_queue(fh), b,
|
|
file->f_flags & O_NONBLOCK);
|
|
}
|
|
|
|
static int saa7134_streamon(struct file *file, void *priv,
|
|
enum v4l2_buf_type type)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
int res = saa7134_resource(fh);
|
|
|
|
if (!res_get(dev, fh, res))
|
|
return -EBUSY;
|
|
|
|
return videobuf_streamon(saa7134_queue(fh));
|
|
}
|
|
|
|
static int saa7134_streamoff(struct file *file, void *priv,
|
|
enum v4l2_buf_type type)
|
|
{
|
|
int err;
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
int res = saa7134_resource(fh);
|
|
|
|
err = videobuf_streamoff(saa7134_queue(fh));
|
|
if (err < 0)
|
|
return err;
|
|
res_free(dev, fh, res);
|
|
return 0;
|
|
}
|
|
|
|
static int saa7134_g_parm(struct file *file, void *fh,
|
|
struct v4l2_streamparm *parm)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
|
static int vidioc_g_register (struct file *file, void *priv,
|
|
struct v4l2_dbg_register *reg)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
if (!v4l2_chip_match_host(®->match))
|
|
return -EINVAL;
|
|
reg->val = saa_readb(reg->reg);
|
|
reg->size = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_register (struct file *file, void *priv,
|
|
struct v4l2_dbg_register *reg)
|
|
{
|
|
struct saa7134_fh *fh = priv;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
if (!v4l2_chip_match_host(®->match))
|
|
return -EINVAL;
|
|
saa_writeb(reg->reg&0xffffff, reg->val);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int radio_querycap(struct file *file, void *priv,
|
|
struct v4l2_capability *cap)
|
|
{
|
|
struct saa7134_fh *fh = file->private_data;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
strcpy(cap->driver, "saa7134");
|
|
strlcpy(cap->card, saa7134_boards[dev->board].name, sizeof(cap->card));
|
|
sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
|
|
cap->version = SAA7134_VERSION_CODE;
|
|
cap->capabilities = V4L2_CAP_TUNER;
|
|
return 0;
|
|
}
|
|
|
|
static int radio_g_tuner(struct file *file, void *priv,
|
|
struct v4l2_tuner *t)
|
|
{
|
|
struct saa7134_fh *fh = file->private_data;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
if (0 != t->index)
|
|
return -EINVAL;
|
|
|
|
memset(t, 0, sizeof(*t));
|
|
strcpy(t->name, "Radio");
|
|
t->type = V4L2_TUNER_RADIO;
|
|
|
|
saa_call_all(dev, tuner, g_tuner, t);
|
|
if (dev->input->amux == TV) {
|
|
t->signal = 0xf800 - ((saa_readb(0x581) & 0x1f) << 11);
|
|
t->rxsubchans = (saa_readb(0x529) & 0x08) ?
|
|
V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
|
|
}
|
|
return 0;
|
|
}
|
|
static int radio_s_tuner(struct file *file, void *priv,
|
|
struct v4l2_tuner *t)
|
|
{
|
|
struct saa7134_fh *fh = file->private_data;
|
|
struct saa7134_dev *dev = fh->dev;
|
|
|
|
if (0 != t->index)
|
|
return -EINVAL;
|
|
|
|
saa_call_all(dev, tuner, s_tuner, t);
|
|
return 0;
|
|
}
|
|
|
|
static int radio_enum_input(struct file *file, void *priv,
|
|
struct v4l2_input *i)
|
|
{
|
|
if (i->index != 0)
|
|
return -EINVAL;
|
|
|
|
strcpy(i->name, "Radio");
|
|
i->type = V4L2_INPUT_TYPE_TUNER;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int radio_g_input(struct file *filp, void *priv, unsigned int *i)
|
|
{
|
|
*i = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int radio_g_audio(struct file *file, void *priv,
|
|
struct v4l2_audio *a)
|
|
{
|
|
memset(a, 0, sizeof(*a));
|
|
strcpy(a->name, "Radio");
|
|
return 0;
|
|
}
|
|
|
|
static int radio_s_audio(struct file *file, void *priv,
|
|
struct v4l2_audio *a)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int radio_s_input(struct file *filp, void *priv, unsigned int i)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int radio_s_std(struct file *file, void *fh, v4l2_std_id *norm)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int radio_queryctrl(struct file *file, void *priv,
|
|
struct v4l2_queryctrl *c)
|
|
{
|
|
const struct v4l2_queryctrl *ctrl;
|
|
|
|
if (c->id < V4L2_CID_BASE ||
|
|
c->id >= V4L2_CID_LASTP1)
|
|
return -EINVAL;
|
|
if (c->id == V4L2_CID_AUDIO_MUTE) {
|
|
ctrl = ctrl_by_id(c->id);
|
|
*c = *ctrl;
|
|
} else
|
|
*c = no_ctrl;
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_file_operations video_fops =
|
|
{
|
|
.owner = THIS_MODULE,
|
|
.open = video_open,
|
|
.release = video_release,
|
|
.read = video_read,
|
|
.poll = video_poll,
|
|
.mmap = video_mmap,
|
|
.ioctl = video_ioctl2,
|
|
};
|
|
|
|
static const struct v4l2_ioctl_ops video_ioctl_ops = {
|
|
.vidioc_querycap = saa7134_querycap,
|
|
.vidioc_enum_fmt_vid_cap = saa7134_enum_fmt_vid_cap,
|
|
.vidioc_g_fmt_vid_cap = saa7134_g_fmt_vid_cap,
|
|
.vidioc_try_fmt_vid_cap = saa7134_try_fmt_vid_cap,
|
|
.vidioc_s_fmt_vid_cap = saa7134_s_fmt_vid_cap,
|
|
.vidioc_enum_fmt_vid_overlay = saa7134_enum_fmt_vid_overlay,
|
|
.vidioc_g_fmt_vid_overlay = saa7134_g_fmt_vid_overlay,
|
|
.vidioc_try_fmt_vid_overlay = saa7134_try_fmt_vid_overlay,
|
|
.vidioc_s_fmt_vid_overlay = saa7134_s_fmt_vid_overlay,
|
|
.vidioc_g_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap,
|
|
.vidioc_try_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap,
|
|
.vidioc_s_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap,
|
|
.vidioc_g_audio = saa7134_g_audio,
|
|
.vidioc_s_audio = saa7134_s_audio,
|
|
.vidioc_cropcap = saa7134_cropcap,
|
|
.vidioc_reqbufs = saa7134_reqbufs,
|
|
.vidioc_querybuf = saa7134_querybuf,
|
|
.vidioc_qbuf = saa7134_qbuf,
|
|
.vidioc_dqbuf = saa7134_dqbuf,
|
|
.vidioc_s_std = saa7134_s_std,
|
|
.vidioc_g_std = saa7134_g_std,
|
|
.vidioc_enum_input = saa7134_enum_input,
|
|
.vidioc_g_input = saa7134_g_input,
|
|
.vidioc_s_input = saa7134_s_input,
|
|
.vidioc_queryctrl = saa7134_queryctrl,
|
|
.vidioc_g_ctrl = saa7134_g_ctrl,
|
|
.vidioc_s_ctrl = saa7134_s_ctrl,
|
|
.vidioc_streamon = saa7134_streamon,
|
|
.vidioc_streamoff = saa7134_streamoff,
|
|
.vidioc_g_tuner = saa7134_g_tuner,
|
|
.vidioc_s_tuner = saa7134_s_tuner,
|
|
#ifdef CONFIG_VIDEO_V4L1_COMPAT
|
|
.vidiocgmbuf = vidiocgmbuf,
|
|
#endif
|
|
.vidioc_g_crop = saa7134_g_crop,
|
|
.vidioc_s_crop = saa7134_s_crop,
|
|
.vidioc_g_fbuf = saa7134_g_fbuf,
|
|
.vidioc_s_fbuf = saa7134_s_fbuf,
|
|
.vidioc_overlay = saa7134_overlay,
|
|
.vidioc_g_priority = saa7134_g_priority,
|
|
.vidioc_s_priority = saa7134_s_priority,
|
|
.vidioc_g_parm = saa7134_g_parm,
|
|
.vidioc_g_frequency = saa7134_g_frequency,
|
|
.vidioc_s_frequency = saa7134_s_frequency,
|
|
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
|
.vidioc_g_register = vidioc_g_register,
|
|
.vidioc_s_register = vidioc_s_register,
|
|
#endif
|
|
};
|
|
|
|
static const struct v4l2_file_operations radio_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = video_open,
|
|
.release = video_release,
|
|
.ioctl = video_ioctl2,
|
|
};
|
|
|
|
static const struct v4l2_ioctl_ops radio_ioctl_ops = {
|
|
.vidioc_querycap = radio_querycap,
|
|
.vidioc_g_tuner = radio_g_tuner,
|
|
.vidioc_enum_input = radio_enum_input,
|
|
.vidioc_g_audio = radio_g_audio,
|
|
.vidioc_s_tuner = radio_s_tuner,
|
|
.vidioc_s_audio = radio_s_audio,
|
|
.vidioc_s_input = radio_s_input,
|
|
.vidioc_s_std = radio_s_std,
|
|
.vidioc_queryctrl = radio_queryctrl,
|
|
.vidioc_g_input = radio_g_input,
|
|
.vidioc_g_ctrl = saa7134_g_ctrl,
|
|
.vidioc_s_ctrl = saa7134_s_ctrl,
|
|
.vidioc_g_frequency = saa7134_g_frequency,
|
|
.vidioc_s_frequency = saa7134_s_frequency,
|
|
};
|
|
|
|
/* ----------------------------------------------------------- */
|
|
/* exported stuff */
|
|
|
|
struct video_device saa7134_video_template = {
|
|
.name = "saa7134-video",
|
|
.fops = &video_fops,
|
|
.ioctl_ops = &video_ioctl_ops,
|
|
.minor = -1,
|
|
.tvnorms = SAA7134_NORMS,
|
|
.current_norm = V4L2_STD_PAL,
|
|
};
|
|
|
|
struct video_device saa7134_radio_template = {
|
|
.name = "saa7134-radio",
|
|
.fops = &radio_fops,
|
|
.ioctl_ops = &radio_ioctl_ops,
|
|
.minor = -1,
|
|
};
|
|
|
|
int saa7134_video_init1(struct saa7134_dev *dev)
|
|
{
|
|
/* sanitycheck insmod options */
|
|
if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME)
|
|
gbuffers = 2;
|
|
if (gbufsize < 0 || gbufsize > gbufsize_max)
|
|
gbufsize = gbufsize_max;
|
|
gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK;
|
|
|
|
/* put some sensible defaults into the data structures ... */
|
|
dev->ctl_bright = ctrl_by_id(V4L2_CID_BRIGHTNESS)->default_value;
|
|
dev->ctl_contrast = ctrl_by_id(V4L2_CID_CONTRAST)->default_value;
|
|
dev->ctl_hue = ctrl_by_id(V4L2_CID_HUE)->default_value;
|
|
dev->ctl_saturation = ctrl_by_id(V4L2_CID_SATURATION)->default_value;
|
|
dev->ctl_volume = ctrl_by_id(V4L2_CID_AUDIO_VOLUME)->default_value;
|
|
dev->ctl_mute = 1; // ctrl_by_id(V4L2_CID_AUDIO_MUTE)->default_value;
|
|
dev->ctl_invert = ctrl_by_id(V4L2_CID_PRIVATE_INVERT)->default_value;
|
|
dev->ctl_automute = ctrl_by_id(V4L2_CID_PRIVATE_AUTOMUTE)->default_value;
|
|
|
|
if (dev->tda9887_conf && dev->ctl_automute)
|
|
dev->tda9887_conf |= TDA9887_AUTOMUTE;
|
|
dev->automute = 0;
|
|
|
|
INIT_LIST_HEAD(&dev->video_q.queue);
|
|
init_timer(&dev->video_q.timeout);
|
|
dev->video_q.timeout.function = saa7134_buffer_timeout;
|
|
dev->video_q.timeout.data = (unsigned long)(&dev->video_q);
|
|
dev->video_q.dev = dev;
|
|
|
|
if (saa7134_boards[dev->board].video_out)
|
|
saa7134_videoport_init(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int saa7134_videoport_init(struct saa7134_dev *dev)
|
|
{
|
|
/* enable video output */
|
|
int vo = saa7134_boards[dev->board].video_out;
|
|
int video_reg;
|
|
unsigned int vid_port_opts = saa7134_boards[dev->board].vid_port_opts;
|
|
|
|
/* Configure videoport */
|
|
saa_writeb(SAA7134_VIDEO_PORT_CTRL0, video_out[vo][0]);
|
|
video_reg = video_out[vo][1];
|
|
if (vid_port_opts & SET_T_CODE_POLARITY_NON_INVERTED)
|
|
video_reg &= ~VP_T_CODE_P_INVERTED;
|
|
saa_writeb(SAA7134_VIDEO_PORT_CTRL1, video_reg);
|
|
saa_writeb(SAA7134_VIDEO_PORT_CTRL2, video_out[vo][2]);
|
|
saa_writeb(SAA7134_VIDEO_PORT_CTRL4, video_out[vo][4]);
|
|
video_reg = video_out[vo][5];
|
|
if (vid_port_opts & SET_CLOCK_NOT_DELAYED)
|
|
video_reg &= ~VP_CLK_CTRL2_DELAYED;
|
|
if (vid_port_opts & SET_CLOCK_INVERTED)
|
|
video_reg |= VP_CLK_CTRL1_INVERTED;
|
|
saa_writeb(SAA7134_VIDEO_PORT_CTRL5, video_reg);
|
|
video_reg = video_out[vo][6];
|
|
if (vid_port_opts & SET_VSYNC_OFF) {
|
|
video_reg &= ~VP_VS_TYPE_MASK;
|
|
video_reg |= VP_VS_TYPE_OFF;
|
|
}
|
|
saa_writeb(SAA7134_VIDEO_PORT_CTRL6, video_reg);
|
|
saa_writeb(SAA7134_VIDEO_PORT_CTRL7, video_out[vo][7]);
|
|
saa_writeb(SAA7134_VIDEO_PORT_CTRL8, video_out[vo][8]);
|
|
|
|
/* Start videoport */
|
|
saa_writeb(SAA7134_VIDEO_PORT_CTRL3, video_out[vo][3]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int saa7134_video_init2(struct saa7134_dev *dev)
|
|
{
|
|
/* init video hw */
|
|
set_tvnorm(dev,&tvnorms[0]);
|
|
video_mux(dev,0);
|
|
saa7134_tvaudio_setmute(dev);
|
|
saa7134_tvaudio_setvolume(dev,dev->ctl_volume);
|
|
return 0;
|
|
}
|
|
|
|
void saa7134_irq_video_signalchange(struct saa7134_dev *dev)
|
|
{
|
|
static const char *st[] = {
|
|
"(no signal)", "NTSC", "PAL", "SECAM" };
|
|
u32 st1,st2;
|
|
|
|
st1 = saa_readb(SAA7134_STATUS_VIDEO1);
|
|
st2 = saa_readb(SAA7134_STATUS_VIDEO2);
|
|
dprintk("DCSDT: pll: %s, sync: %s, norm: %s\n",
|
|
(st1 & 0x40) ? "not locked" : "locked",
|
|
(st2 & 0x40) ? "no" : "yes",
|
|
st[st1 & 0x03]);
|
|
dev->nosignal = (st1 & 0x40) || (st2 & 0x40) || !(st2 & 0x1);
|
|
|
|
if (dev->nosignal) {
|
|
/* no video signal -> mute audio */
|
|
if (dev->ctl_automute)
|
|
dev->automute = 1;
|
|
saa7134_tvaudio_setmute(dev);
|
|
} else {
|
|
/* wake up tvaudio audio carrier scan thread */
|
|
saa7134_tvaudio_do_scan(dev);
|
|
}
|
|
|
|
if ((st2 & 0x80) && !noninterlaced && !dev->nosignal)
|
|
saa_clearb(SAA7134_SYNC_CTRL, 0x20);
|
|
else
|
|
saa_setb(SAA7134_SYNC_CTRL, 0x20);
|
|
|
|
if (dev->mops && dev->mops->signal_change)
|
|
dev->mops->signal_change(dev);
|
|
}
|
|
|
|
|
|
void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status)
|
|
{
|
|
enum v4l2_field field;
|
|
|
|
spin_lock(&dev->slock);
|
|
if (dev->video_q.curr) {
|
|
dev->video_fieldcount++;
|
|
field = dev->video_q.curr->vb.field;
|
|
if (V4L2_FIELD_HAS_BOTH(field)) {
|
|
/* make sure we have seen both fields */
|
|
if ((status & 0x10) == 0x00) {
|
|
dev->video_q.curr->top_seen = 1;
|
|
goto done;
|
|
}
|
|
if (!dev->video_q.curr->top_seen)
|
|
goto done;
|
|
} else if (field == V4L2_FIELD_TOP) {
|
|
if ((status & 0x10) != 0x10)
|
|
goto done;
|
|
} else if (field == V4L2_FIELD_BOTTOM) {
|
|
if ((status & 0x10) != 0x00)
|
|
goto done;
|
|
}
|
|
dev->video_q.curr->vb.field_count = dev->video_fieldcount;
|
|
saa7134_buffer_finish(dev,&dev->video_q,VIDEOBUF_DONE);
|
|
}
|
|
saa7134_buffer_next(dev,&dev->video_q);
|
|
|
|
done:
|
|
spin_unlock(&dev->slock);
|
|
}
|
|
|
|
/* ----------------------------------------------------------- */
|
|
/*
|
|
* Local variables:
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|