mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-17 11:29:00 +07:00
301e22d691
Add a new audio mode V4L2_TUNER_MODE_LANG1_LANG2 (used by VIDIOC_G/S_TUNER). This mode allows the user to select both languages of a bilingual transmission, one language on the left, one on the right audio channel. If there is no bilingual transmission, or it is not supported, then this mode should act like V4L2_TUNER_MODE_STEREO. This mode is introduced for PVR-like drivers where it is useful to be able to record both languages of a bilingual broadcast. Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
910 lines
26 KiB
C
910 lines
26 KiB
C
/*
|
|
* av7110_v4l.c: av7110 video4linux interface for DVB and Siemens DVB-C analog module
|
|
*
|
|
* Copyright (C) 1999-2002 Ralph Metzler
|
|
* & Marcus Metzler for convergence integrated media GmbH
|
|
*
|
|
* originally based on code by:
|
|
* Copyright (C) 1998,1999 Christian Theiss <mistert@rz.fh-augsburg.de>
|
|
*
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
* the project's page is at http://www.linuxtv.org/dvb/
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/types.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/byteorder/swabb.h>
|
|
#include <linux/smp_lock.h>
|
|
|
|
#include "av7110.h"
|
|
#include "av7110_hw.h"
|
|
#include "av7110_av.h"
|
|
|
|
int msp_writereg(struct av7110 *av7110, u8 dev, u16 reg, u16 val)
|
|
{
|
|
u8 msg[5] = { dev, reg >> 8, reg & 0xff, val >> 8 , val & 0xff };
|
|
struct i2c_msg msgs = { .flags = 0, .addr = 0x40, .len = 5, .buf = msg };
|
|
|
|
if (i2c_transfer(&av7110->i2c_adap, &msgs, 1) != 1) {
|
|
dprintk(1, "dvb-ttpci: failed @ card %d, %u = %u\n",
|
|
av7110->dvb_adapter.num, reg, val);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int msp_readreg(struct av7110 *av7110, u8 dev, u16 reg, u16 *val)
|
|
{
|
|
u8 msg1[3] = { dev, reg >> 8, reg & 0xff };
|
|
u8 msg2[2];
|
|
struct i2c_msg msgs[2] = {
|
|
{ .flags = 0, .addr = 0x40, .len = 3, .buf = msg1 },
|
|
{ .flags = I2C_M_RD, .addr = 0x40, .len = 2, .buf = msg2 }
|
|
};
|
|
|
|
if (i2c_transfer(&av7110->i2c_adap, &msgs[0], 2) != 2) {
|
|
dprintk(1, "dvb-ttpci: failed @ card %d, %u\n",
|
|
av7110->dvb_adapter.num, reg);
|
|
return -EIO;
|
|
}
|
|
*val = (msg2[0] << 8) | msg2[1];
|
|
return 0;
|
|
}
|
|
|
|
static struct v4l2_input inputs[4] = {
|
|
{
|
|
.index = 0,
|
|
.name = "DVB",
|
|
.type = V4L2_INPUT_TYPE_CAMERA,
|
|
.audioset = 1,
|
|
.tuner = 0, /* ignored */
|
|
.std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M,
|
|
.status = 0,
|
|
}, {
|
|
.index = 1,
|
|
.name = "Television",
|
|
.type = V4L2_INPUT_TYPE_TUNER,
|
|
.audioset = 2,
|
|
.tuner = 0,
|
|
.std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M,
|
|
.status = 0,
|
|
}, {
|
|
.index = 2,
|
|
.name = "Video",
|
|
.type = V4L2_INPUT_TYPE_CAMERA,
|
|
.audioset = 0,
|
|
.tuner = 0,
|
|
.std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M,
|
|
.status = 0,
|
|
}, {
|
|
.index = 3,
|
|
.name = "Y/C",
|
|
.type = V4L2_INPUT_TYPE_CAMERA,
|
|
.audioset = 0,
|
|
.tuner = 0,
|
|
.std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M,
|
|
.status = 0,
|
|
}
|
|
};
|
|
|
|
static int ves1820_writereg(struct saa7146_dev *dev, u8 addr, u8 reg, u8 data)
|
|
{
|
|
u8 buf[] = { 0x00, reg, data };
|
|
struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = buf, .len = 3 };
|
|
|
|
dprintk(4, "dev: %p\n", dev);
|
|
|
|
if (1 != saa7146_i2c_transfer(dev, &msg, 1, 1))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int stv0297_writereg(struct saa7146_dev *dev, u8 addr, u8 reg, u8 data)
|
|
{
|
|
u8 buf [] = { reg, data };
|
|
struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = buf, .len = 2 };
|
|
|
|
if (1 != saa7146_i2c_transfer(dev, &msg, 1, 1))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int tuner_write(struct saa7146_dev *dev, u8 addr, u8 data [4])
|
|
{
|
|
struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = data, .len = 4 };
|
|
|
|
dprintk(4, "dev: %p\n", dev);
|
|
|
|
if (1 != saa7146_i2c_transfer(dev, &msg, 1, 1))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int ves1820_set_tv_freq(struct saa7146_dev *dev, u32 freq)
|
|
{
|
|
u32 div;
|
|
u8 config;
|
|
u8 buf[4];
|
|
|
|
dprintk(4, "freq: 0x%08x\n", freq);
|
|
|
|
/* magic number: 614. tuning with the frequency given by v4l2
|
|
is always off by 614*62.5 = 38375 kHz...*/
|
|
div = freq + 614;
|
|
|
|
buf[0] = (div >> 8) & 0x7f;
|
|
buf[1] = div & 0xff;
|
|
buf[2] = 0x8e;
|
|
|
|
if (freq < (u32) (16 * 168.25))
|
|
config = 0xa0;
|
|
else if (freq < (u32) (16 * 447.25))
|
|
config = 0x90;
|
|
else
|
|
config = 0x30;
|
|
config &= ~0x02;
|
|
|
|
buf[3] = config;
|
|
|
|
return tuner_write(dev, 0x61, buf);
|
|
}
|
|
|
|
static int stv0297_set_tv_freq(struct saa7146_dev *dev, u32 freq)
|
|
{
|
|
u32 div;
|
|
u8 data[4];
|
|
|
|
div = (freq + 38900000 + 31250) / 62500;
|
|
|
|
data[0] = (div >> 8) & 0x7f;
|
|
data[1] = div & 0xff;
|
|
data[2] = 0xce;
|
|
|
|
if (freq < 45000000)
|
|
return -EINVAL;
|
|
else if (freq < 137000000)
|
|
data[3] = 0x01;
|
|
else if (freq < 403000000)
|
|
data[3] = 0x02;
|
|
else if (freq < 860000000)
|
|
data[3] = 0x04;
|
|
else
|
|
return -EINVAL;
|
|
|
|
stv0297_writereg(dev, 0x1C, 0x87, 0x78);
|
|
stv0297_writereg(dev, 0x1C, 0x86, 0xc8);
|
|
return tuner_write(dev, 0x63, data);
|
|
}
|
|
|
|
|
|
|
|
static struct saa7146_standard analog_standard[];
|
|
static struct saa7146_standard dvb_standard[];
|
|
static struct saa7146_standard standard[];
|
|
|
|
static struct v4l2_audio msp3400_v4l2_audio = {
|
|
.index = 0,
|
|
.name = "Television",
|
|
.capability = V4L2_AUDCAP_STEREO
|
|
};
|
|
|
|
static int av7110_dvb_c_switch(struct saa7146_fh *fh)
|
|
{
|
|
struct saa7146_dev *dev = fh->dev;
|
|
struct saa7146_vv *vv = dev->vv_data;
|
|
struct av7110 *av7110 = (struct av7110*)dev->ext_priv;
|
|
u16 adswitch;
|
|
int source, sync, err;
|
|
|
|
dprintk(4, "%p\n", av7110);
|
|
|
|
if ((vv->video_status & STATUS_OVERLAY) != 0) {
|
|
vv->ov_suspend = vv->video_fh;
|
|
err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */
|
|
if (err != 0) {
|
|
dprintk(2, "suspending video failed\n");
|
|
vv->ov_suspend = NULL;
|
|
}
|
|
}
|
|
|
|
if (0 != av7110->current_input) {
|
|
dprintk(1, "switching to analog TV:\n");
|
|
adswitch = 1;
|
|
source = SAA7146_HPS_SOURCE_PORT_B;
|
|
sync = SAA7146_HPS_SYNC_PORT_B;
|
|
memcpy(standard, analog_standard, sizeof(struct saa7146_standard) * 2);
|
|
|
|
switch (av7110->current_input) {
|
|
case 1:
|
|
dprintk(1, "switching SAA7113 to Analog Tuner Input.\n");
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0000); // loudspeaker source
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0000); // headphone source
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0000); // SCART 1 source
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x000e, 0x3000); // FM matrix, mono
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x4f00); // loudspeaker + headphone
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x4f00); // SCART 1 volume
|
|
|
|
if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) {
|
|
if (ves1820_writereg(dev, 0x09, 0x0f, 0x60))
|
|
dprintk(1, "setting band in demodulator failed.\n");
|
|
} else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) {
|
|
saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); // TDA9198 pin9(STD)
|
|
saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); // TDA9198 pin30(VIF)
|
|
}
|
|
if (i2c_writereg(av7110, 0x48, 0x02, 0xd0) != 1)
|
|
dprintk(1, "saa7113 write failed @ card %d", av7110->dvb_adapter.num);
|
|
break;
|
|
case 2:
|
|
dprintk(1, "switching SAA7113 to Video AV CVBS Input.\n");
|
|
if (i2c_writereg(av7110, 0x48, 0x02, 0xd2) != 1)
|
|
dprintk(1, "saa7113 write failed @ card %d", av7110->dvb_adapter.num);
|
|
break;
|
|
case 3:
|
|
dprintk(1, "switching SAA7113 to Video AV Y/C Input.\n");
|
|
if (i2c_writereg(av7110, 0x48, 0x02, 0xd9) != 1)
|
|
dprintk(1, "saa7113 write failed @ card %d", av7110->dvb_adapter.num);
|
|
break;
|
|
default:
|
|
dprintk(1, "switching SAA7113 to Input: AV7110: SAA7113: invalid input.\n");
|
|
}
|
|
} else {
|
|
adswitch = 0;
|
|
source = SAA7146_HPS_SOURCE_PORT_A;
|
|
sync = SAA7146_HPS_SYNC_PORT_A;
|
|
memcpy(standard, dvb_standard, sizeof(struct saa7146_standard) * 2);
|
|
dprintk(1, "switching DVB mode\n");
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0220); // loudspeaker source
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0220); // headphone source
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0220); // SCART 1 source
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x000e, 0x3000); // FM matrix, mono
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x7f00); // loudspeaker + headphone
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x7f00); // SCART 1 volume
|
|
|
|
if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) {
|
|
if (ves1820_writereg(dev, 0x09, 0x0f, 0x20))
|
|
dprintk(1, "setting band in demodulator failed.\n");
|
|
} else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) {
|
|
saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTLO); // TDA9198 pin9(STD)
|
|
saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); // TDA9198 pin30(VIF)
|
|
}
|
|
}
|
|
|
|
/* hmm, this does not do anything!? */
|
|
if (av7110_fw_cmd(av7110, COMTYPE_AUDIODAC, ADSwitch, 1, adswitch))
|
|
dprintk(1, "ADSwitch error\n");
|
|
|
|
saa7146_set_hps_source_and_sync(dev, source, sync);
|
|
|
|
if (vv->ov_suspend != NULL) {
|
|
saa7146_start_preview(vv->ov_suspend);
|
|
vv->ov_suspend = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int av7110_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg)
|
|
{
|
|
struct saa7146_dev *dev = fh->dev;
|
|
struct av7110 *av7110 = (struct av7110*) dev->ext_priv;
|
|
dprintk(4, "saa7146_dev: %p\n", dev);
|
|
|
|
switch (cmd) {
|
|
case VIDIOC_G_TUNER:
|
|
{
|
|
struct v4l2_tuner *t = arg;
|
|
u16 stereo_det;
|
|
s8 stereo;
|
|
|
|
dprintk(2, "VIDIOC_G_TUNER: %d\n", t->index);
|
|
|
|
if (!av7110->analog_tuner_flags || t->index != 0)
|
|
return -EINVAL;
|
|
|
|
memset(t, 0, sizeof(*t));
|
|
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 | V4L2_TUNER_CAP_SAP;
|
|
t->rangelow = 772; /* 48.25 MHZ / 62.5 kHz = 772, see fi1216mk2-specs, page 2 */
|
|
t->rangehigh = 13684; /* 855.25 MHz / 62.5 kHz = 13684 */
|
|
/* FIXME: add the real signal strength here */
|
|
t->signal = 0xffff;
|
|
t->afc = 0;
|
|
|
|
// FIXME: standard / stereo detection is still broken
|
|
msp_readreg(av7110, MSP_RD_DEM, 0x007e, &stereo_det);
|
|
dprintk(1, "VIDIOC_G_TUNER: msp3400 TV standard detection: 0x%04x\n", stereo_det);
|
|
msp_readreg(av7110, MSP_RD_DSP, 0x0018, &stereo_det);
|
|
dprintk(1, "VIDIOC_G_TUNER: msp3400 stereo detection: 0x%04x\n", stereo_det);
|
|
stereo = (s8)(stereo_det >> 8);
|
|
if (stereo > 0x10) {
|
|
/* stereo */
|
|
t->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO;
|
|
t->audmode = V4L2_TUNER_MODE_STEREO;
|
|
}
|
|
else if (stereo < -0x10) {
|
|
/* bilingual */
|
|
t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2;
|
|
t->audmode = V4L2_TUNER_MODE_LANG1;
|
|
}
|
|
else /* mono */
|
|
t->rxsubchans = V4L2_TUNER_SUB_MONO;
|
|
|
|
return 0;
|
|
}
|
|
case VIDIOC_S_TUNER:
|
|
{
|
|
struct v4l2_tuner *t = arg;
|
|
u16 fm_matrix, src;
|
|
dprintk(2, "VIDIOC_S_TUNER: %d\n", t->index);
|
|
|
|
if (!av7110->analog_tuner_flags || av7110->current_input != 1)
|
|
return -EINVAL;
|
|
|
|
switch (t->audmode) {
|
|
case V4L2_TUNER_MODE_STEREO:
|
|
dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_STEREO\n");
|
|
fm_matrix = 0x3001; // stereo
|
|
src = 0x0020;
|
|
break;
|
|
case V4L2_TUNER_MODE_LANG1_LANG2:
|
|
dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG1_LANG2\n");
|
|
fm_matrix = 0x3000; // bilingual
|
|
src = 0x0020;
|
|
break;
|
|
case V4L2_TUNER_MODE_LANG1:
|
|
dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG1\n");
|
|
fm_matrix = 0x3000; // mono
|
|
src = 0x0000;
|
|
break;
|
|
case V4L2_TUNER_MODE_LANG2:
|
|
dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG2\n");
|
|
fm_matrix = 0x3000; // mono
|
|
src = 0x0010;
|
|
break;
|
|
default: /* case V4L2_TUNER_MODE_MONO: */
|
|
dprintk(2, "VIDIOC_S_TUNER: TDA9840_SET_MONO\n");
|
|
fm_matrix = 0x3000; // mono
|
|
src = 0x0030;
|
|
break;
|
|
}
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x000e, fm_matrix);
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0008, src);
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0009, src);
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x000a, src);
|
|
return 0;
|
|
}
|
|
case VIDIOC_G_FREQUENCY:
|
|
{
|
|
struct v4l2_frequency *f = arg;
|
|
|
|
dprintk(2, "VIDIOC_G_FREQ: freq:0x%08x.\n", f->frequency);
|
|
|
|
if (!av7110->analog_tuner_flags || av7110->current_input != 1)
|
|
return -EINVAL;
|
|
|
|
memset(f, 0, sizeof(*f));
|
|
f->type = V4L2_TUNER_ANALOG_TV;
|
|
f->frequency = av7110->current_freq;
|
|
return 0;
|
|
}
|
|
case VIDIOC_S_FREQUENCY:
|
|
{
|
|
struct v4l2_frequency *f = arg;
|
|
|
|
dprintk(2, "VIDIOC_S_FREQUENCY: freq:0x%08x.\n", f->frequency);
|
|
|
|
if (!av7110->analog_tuner_flags || av7110->current_input != 1)
|
|
return -EINVAL;
|
|
|
|
if (V4L2_TUNER_ANALOG_TV != f->type)
|
|
return -EINVAL;
|
|
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0xffe0); // fast mute
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0xffe0);
|
|
|
|
/* tune in desired frequency */
|
|
if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) {
|
|
ves1820_set_tv_freq(dev, f->frequency);
|
|
} else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) {
|
|
stv0297_set_tv_freq(dev, f->frequency);
|
|
}
|
|
av7110->current_freq = f->frequency;
|
|
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0015, 0x003f); // start stereo detection
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0015, 0x0000);
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x4f00); // loudspeaker + headphone
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x4f00); // SCART 1 volume
|
|
return 0;
|
|
}
|
|
case VIDIOC_ENUMINPUT:
|
|
{
|
|
struct v4l2_input *i = arg;
|
|
|
|
dprintk(2, "VIDIOC_ENUMINPUT: %d\n", i->index);
|
|
|
|
if (av7110->analog_tuner_flags) {
|
|
if (i->index < 0 || i->index >= 4)
|
|
return -EINVAL;
|
|
} else {
|
|
if (i->index != 0)
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(i, &inputs[i->index], sizeof(struct v4l2_input));
|
|
|
|
return 0;
|
|
}
|
|
case VIDIOC_G_INPUT:
|
|
{
|
|
int *input = (int *)arg;
|
|
*input = av7110->current_input;
|
|
dprintk(2, "VIDIOC_G_INPUT: %d\n", *input);
|
|
return 0;
|
|
}
|
|
case VIDIOC_S_INPUT:
|
|
{
|
|
int input = *(int *)arg;
|
|
|
|
dprintk(2, "VIDIOC_S_INPUT: %d\n", input);
|
|
|
|
if (!av7110->analog_tuner_flags)
|
|
return 0;
|
|
|
|
if (input < 0 || input >= 4)
|
|
return -EINVAL;
|
|
|
|
av7110->current_input = input;
|
|
return av7110_dvb_c_switch(fh);
|
|
}
|
|
case VIDIOC_G_AUDIO:
|
|
{
|
|
struct v4l2_audio *a = arg;
|
|
|
|
dprintk(2, "VIDIOC_G_AUDIO: %d\n", a->index);
|
|
if (a->index != 0)
|
|
return -EINVAL;
|
|
memcpy(a, &msp3400_v4l2_audio, sizeof(struct v4l2_audio));
|
|
break;
|
|
}
|
|
case VIDIOC_S_AUDIO:
|
|
{
|
|
struct v4l2_audio *a = arg;
|
|
dprintk(2, "VIDIOC_S_AUDIO: %d\n", a->index);
|
|
break;
|
|
}
|
|
case VIDIOC_G_SLICED_VBI_CAP:
|
|
{
|
|
struct v4l2_sliced_vbi_cap *cap = arg;
|
|
dprintk(2, "VIDIOC_G_SLICED_VBI_CAP\n");
|
|
memset(cap, 0, sizeof *cap);
|
|
if (FW_VERSION(av7110->arm_app) >= 0x2623) {
|
|
cap->service_set = V4L2_SLICED_WSS_625;
|
|
cap->service_lines[0][23] = V4L2_SLICED_WSS_625;
|
|
}
|
|
break;
|
|
}
|
|
case VIDIOC_G_FMT:
|
|
{
|
|
struct v4l2_format *f = arg;
|
|
dprintk(2, "VIDIOC_G_FMT:\n");
|
|
if (f->type != V4L2_BUF_TYPE_SLICED_VBI_OUTPUT ||
|
|
FW_VERSION(av7110->arm_app) < 0x2623)
|
|
return -EAGAIN; /* handled by core driver */
|
|
memset(&f->fmt.sliced, 0, sizeof f->fmt.sliced);
|
|
if (av7110->wssMode) {
|
|
f->fmt.sliced.service_set = V4L2_SLICED_WSS_625;
|
|
f->fmt.sliced.service_lines[0][23] = V4L2_SLICED_WSS_625;
|
|
f->fmt.sliced.io_size = sizeof (struct v4l2_sliced_vbi_data);
|
|
}
|
|
break;
|
|
}
|
|
case VIDIOC_S_FMT:
|
|
{
|
|
struct v4l2_format *f = arg;
|
|
dprintk(2, "VIDIOC_S_FMT\n");
|
|
if (f->type != V4L2_BUF_TYPE_SLICED_VBI_OUTPUT ||
|
|
FW_VERSION(av7110->arm_app) < 0x2623)
|
|
return -EAGAIN; /* handled by core driver */
|
|
if (f->fmt.sliced.service_set != V4L2_SLICED_WSS_625 &&
|
|
f->fmt.sliced.service_lines[0][23] != V4L2_SLICED_WSS_625) {
|
|
memset(&f->fmt.sliced, 0, sizeof f->fmt.sliced);
|
|
/* WSS controlled by firmware */
|
|
av7110->wssMode = 0;
|
|
av7110->wssData = 0;
|
|
return av7110_fw_cmd(av7110, COMTYPE_ENCODER,
|
|
SetWSSConfig, 1, 0);
|
|
} else {
|
|
memset(&f->fmt.sliced, 0, sizeof f->fmt.sliced);
|
|
f->fmt.sliced.service_set = V4L2_SLICED_WSS_625;
|
|
f->fmt.sliced.service_lines[0][23] = V4L2_SLICED_WSS_625;
|
|
f->fmt.sliced.io_size = sizeof (struct v4l2_sliced_vbi_data);
|
|
/* WSS controlled by userspace */
|
|
av7110->wssMode = 1;
|
|
av7110->wssData = 0;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
printk("no such ioctl\n");
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int av7110_vbi_reset(struct inode *inode, struct file *file)
|
|
{
|
|
struct saa7146_fh *fh = file->private_data;
|
|
struct saa7146_dev *dev = fh->dev;
|
|
struct av7110 *av7110 = (struct av7110*) dev->ext_priv;
|
|
|
|
dprintk(2, "%s\n", __FUNCTION__);
|
|
av7110->wssMode = 0;
|
|
av7110->wssData = 0;
|
|
if (FW_VERSION(av7110->arm_app) < 0x2623)
|
|
return 0;
|
|
else
|
|
return av7110_fw_cmd(av7110, COMTYPE_ENCODER, SetWSSConfig, 1, 0);
|
|
}
|
|
|
|
static ssize_t av7110_vbi_write(struct file *file, const char __user *data, size_t count, loff_t *ppos)
|
|
{
|
|
struct saa7146_fh *fh = file->private_data;
|
|
struct saa7146_dev *dev = fh->dev;
|
|
struct av7110 *av7110 = (struct av7110*) dev->ext_priv;
|
|
struct v4l2_sliced_vbi_data d;
|
|
int rc;
|
|
|
|
dprintk(2, "%s\n", __FUNCTION__);
|
|
if (FW_VERSION(av7110->arm_app) < 0x2623 || !av7110->wssMode || count != sizeof d)
|
|
return -EINVAL;
|
|
if (copy_from_user(&d, data, count))
|
|
return -EFAULT;
|
|
if ((d.id != 0 && d.id != V4L2_SLICED_WSS_625) || d.field != 0 || d.line != 23)
|
|
return -EINVAL;
|
|
if (d.id)
|
|
av7110->wssData = ((d.data[1] << 8) & 0x3f00) | d.data[0];
|
|
else
|
|
av7110->wssData = 0x8000;
|
|
rc = av7110_fw_cmd(av7110, COMTYPE_ENCODER, SetWSSConfig, 2, 1, av7110->wssData);
|
|
return (rc < 0) ? rc : count;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* INITIALIZATION
|
|
****************************************************************************/
|
|
|
|
static struct saa7146_extension_ioctls ioctls[] = {
|
|
{ VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE },
|
|
{ VIDIOC_G_INPUT, SAA7146_EXCLUSIVE },
|
|
{ VIDIOC_S_INPUT, SAA7146_EXCLUSIVE },
|
|
{ VIDIOC_G_FREQUENCY, SAA7146_EXCLUSIVE },
|
|
{ VIDIOC_S_FREQUENCY, SAA7146_EXCLUSIVE },
|
|
{ VIDIOC_G_TUNER, SAA7146_EXCLUSIVE },
|
|
{ VIDIOC_S_TUNER, SAA7146_EXCLUSIVE },
|
|
{ VIDIOC_G_AUDIO, SAA7146_EXCLUSIVE },
|
|
{ VIDIOC_S_AUDIO, SAA7146_EXCLUSIVE },
|
|
{ VIDIOC_G_SLICED_VBI_CAP, SAA7146_EXCLUSIVE },
|
|
{ VIDIOC_G_FMT, SAA7146_BEFORE },
|
|
{ VIDIOC_S_FMT, SAA7146_BEFORE },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static u8 saa7113_init_regs[] = {
|
|
0x02, 0xd0,
|
|
0x03, 0x23,
|
|
0x04, 0x00,
|
|
0x05, 0x00,
|
|
0x06, 0xe9,
|
|
0x07, 0x0d,
|
|
0x08, 0x98,
|
|
0x09, 0x02,
|
|
0x0a, 0x80,
|
|
0x0b, 0x40,
|
|
0x0c, 0x40,
|
|
0x0d, 0x00,
|
|
0x0e, 0x01,
|
|
0x0f, 0x7c,
|
|
0x10, 0x48,
|
|
0x11, 0x0c,
|
|
0x12, 0x8b,
|
|
0x13, 0x1a,
|
|
0x14, 0x00,
|
|
0x15, 0x00,
|
|
0x16, 0x00,
|
|
0x17, 0x00,
|
|
0x18, 0x00,
|
|
0x19, 0x00,
|
|
0x1a, 0x00,
|
|
0x1b, 0x00,
|
|
0x1c, 0x00,
|
|
0x1d, 0x00,
|
|
0x1e, 0x00,
|
|
|
|
0x41, 0x77,
|
|
0x42, 0x77,
|
|
0x43, 0x77,
|
|
0x44, 0x77,
|
|
0x45, 0x77,
|
|
0x46, 0x77,
|
|
0x47, 0x77,
|
|
0x48, 0x77,
|
|
0x49, 0x77,
|
|
0x4a, 0x77,
|
|
0x4b, 0x77,
|
|
0x4c, 0x77,
|
|
0x4d, 0x77,
|
|
0x4e, 0x77,
|
|
0x4f, 0x77,
|
|
0x50, 0x77,
|
|
0x51, 0x77,
|
|
0x52, 0x77,
|
|
0x53, 0x77,
|
|
0x54, 0x77,
|
|
0x55, 0x77,
|
|
0x56, 0x77,
|
|
0x57, 0xff,
|
|
|
|
0xff
|
|
};
|
|
|
|
|
|
static struct saa7146_ext_vv av7110_vv_data_st;
|
|
static struct saa7146_ext_vv av7110_vv_data_c;
|
|
|
|
int av7110_init_analog_module(struct av7110 *av7110)
|
|
{
|
|
u16 version1, version2;
|
|
|
|
if (i2c_writereg(av7110, 0x80, 0x0, 0x80) != 1
|
|
|| i2c_writereg(av7110, 0x80, 0x0, 0) != 1)
|
|
return -ENODEV;
|
|
|
|
printk("dvb-ttpci: DVB-C analog module @ card %d detected, initializing MSP3400\n",
|
|
av7110->dvb_adapter.num);
|
|
av7110->adac_type = DVB_ADAC_MSP34x0;
|
|
msleep(100); // the probing above resets the msp...
|
|
msp_readreg(av7110, MSP_RD_DSP, 0x001e, &version1);
|
|
msp_readreg(av7110, MSP_RD_DSP, 0x001f, &version2);
|
|
dprintk(1, "dvb-ttpci: @ card %d MSP3400 version 0x%04x 0x%04x\n",
|
|
av7110->dvb_adapter.num, version1, version2);
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0013, 0x0c00);
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x7f00); // loudspeaker + headphone
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0220); // loudspeaker source
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0220); // headphone source
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0004, 0x7f00); // loudspeaker volume
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0220); // SCART 1 source
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x7f00); // SCART 1 volume
|
|
msp_writereg(av7110, MSP_WR_DSP, 0x000d, 0x4800); // prescale SCART
|
|
|
|
if (i2c_writereg(av7110, 0x48, 0x01, 0x00)!=1) {
|
|
INFO(("saa7113 not accessible.\n"));
|
|
} else {
|
|
u8 *i = saa7113_init_regs;
|
|
|
|
if ((av7110->dev->pci->subsystem_vendor == 0x110a) && (av7110->dev->pci->subsystem_device == 0x0000)) {
|
|
/* Fujitsu/Siemens DVB-Cable */
|
|
av7110->analog_tuner_flags |= ANALOG_TUNER_VES1820;
|
|
} else if ((av7110->dev->pci->subsystem_vendor == 0x13c2) && (av7110->dev->pci->subsystem_device == 0x0002)) {
|
|
/* Hauppauge/TT DVB-C premium */
|
|
av7110->analog_tuner_flags |= ANALOG_TUNER_VES1820;
|
|
} else if ((av7110->dev->pci->subsystem_vendor == 0x13c2) && (av7110->dev->pci->subsystem_device == 0x000A)) {
|
|
/* Hauppauge/TT DVB-C premium */
|
|
av7110->analog_tuner_flags |= ANALOG_TUNER_STV0297;
|
|
}
|
|
|
|
/* setup for DVB by default */
|
|
if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) {
|
|
if (ves1820_writereg(av7110->dev, 0x09, 0x0f, 0x20))
|
|
dprintk(1, "setting band in demodulator failed.\n");
|
|
} else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) {
|
|
saa7146_setgpio(av7110->dev, 1, SAA7146_GPIO_OUTLO); // TDA9198 pin9(STD)
|
|
saa7146_setgpio(av7110->dev, 3, SAA7146_GPIO_OUTLO); // TDA9198 pin30(VIF)
|
|
}
|
|
|
|
/* init the saa7113 */
|
|
while (*i != 0xff) {
|
|
if (i2c_writereg(av7110, 0x48, i[0], i[1]) != 1) {
|
|
dprintk(1, "saa7113 initialization failed @ card %d", av7110->dvb_adapter.num);
|
|
break;
|
|
}
|
|
i += 2;
|
|
}
|
|
/* setup msp for analog sound: B/G Dual-FM */
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x00bb, 0x02d0); // AD_CV
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0001, 3); // FIR1
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0001, 18); // FIR1
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0001, 27); // FIR1
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0001, 48); // FIR1
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0001, 66); // FIR1
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0001, 72); // FIR1
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0005, 4); // FIR2
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0005, 64); // FIR2
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0005, 0); // FIR2
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0005, 3); // FIR2
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0005, 18); // FIR2
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0005, 27); // FIR2
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0005, 48); // FIR2
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0005, 66); // FIR2
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0005, 72); // FIR2
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0083, 0xa000); // MODE_REG
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0093, 0x00aa); // DCO1_LO 5.74MHz
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x009b, 0x04fc); // DCO1_HI
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x00a3, 0x038e); // DCO2_LO 5.5MHz
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x00ab, 0x04c6); // DCO2_HI
|
|
msp_writereg(av7110, MSP_WR_DEM, 0x0056, 0); // LOAD_REG 1/2
|
|
}
|
|
|
|
memcpy(standard, dvb_standard, sizeof(struct saa7146_standard) * 2);
|
|
/* set dd1 stream a & b */
|
|
saa7146_write(av7110->dev, DD1_STREAM_B, 0x00000000);
|
|
saa7146_write(av7110->dev, DD1_INIT, 0x03000700);
|
|
saa7146_write(av7110->dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int av7110_init_v4l(struct av7110 *av7110)
|
|
{
|
|
struct saa7146_dev* dev = av7110->dev;
|
|
int ret;
|
|
|
|
/* special case DVB-C: these cards have an analog tuner
|
|
plus need some special handling, so we have separate
|
|
saa7146_ext_vv data for these... */
|
|
if (av7110->analog_tuner_flags)
|
|
ret = saa7146_vv_init(dev, &av7110_vv_data_c);
|
|
else
|
|
ret = saa7146_vv_init(dev, &av7110_vv_data_st);
|
|
|
|
if (ret) {
|
|
ERR(("cannot init capture device. skipping.\n"));
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (saa7146_register_device(&av7110->v4l_dev, dev, "av7110", VFL_TYPE_GRABBER)) {
|
|
ERR(("cannot register capture device. skipping.\n"));
|
|
saa7146_vv_release(dev);
|
|
return -ENODEV;
|
|
}
|
|
if (saa7146_register_device(&av7110->vbi_dev, dev, "av7110", VFL_TYPE_VBI)) {
|
|
ERR(("cannot register vbi v4l2 device. skipping.\n"));
|
|
} else {
|
|
if (av7110->analog_tuner_flags)
|
|
av7110->analog_tuner_flags |= ANALOG_TUNER_VBI;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int av7110_exit_v4l(struct av7110 *av7110)
|
|
{
|
|
saa7146_unregister_device(&av7110->v4l_dev, av7110->dev);
|
|
if (av7110->analog_tuner_flags & ANALOG_TUNER_VBI)
|
|
saa7146_unregister_device(&av7110->vbi_dev, av7110->dev);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: these values are experimental values that look better than the
|
|
values from the latest "official" driver -- at least for me... (MiHu) */
|
|
static struct saa7146_standard standard[] = {
|
|
{
|
|
.name = "PAL", .id = V4L2_STD_PAL_BG,
|
|
.v_offset = 0x15, .v_field = 288,
|
|
.h_offset = 0x48, .h_pixels = 708,
|
|
.v_max_out = 576, .h_max_out = 768,
|
|
}, {
|
|
.name = "NTSC", .id = V4L2_STD_NTSC,
|
|
.v_offset = 0x10, .v_field = 244,
|
|
.h_offset = 0x40, .h_pixels = 708,
|
|
.v_max_out = 480, .h_max_out = 640,
|
|
}
|
|
};
|
|
|
|
static struct saa7146_standard analog_standard[] = {
|
|
{
|
|
.name = "PAL", .id = V4L2_STD_PAL_BG,
|
|
.v_offset = 0x1b, .v_field = 288,
|
|
.h_offset = 0x08, .h_pixels = 708,
|
|
.v_max_out = 576, .h_max_out = 768,
|
|
}, {
|
|
.name = "NTSC", .id = V4L2_STD_NTSC,
|
|
.v_offset = 0x10, .v_field = 244,
|
|
.h_offset = 0x40, .h_pixels = 708,
|
|
.v_max_out = 480, .h_max_out = 640,
|
|
}
|
|
};
|
|
|
|
static struct saa7146_standard dvb_standard[] = {
|
|
{
|
|
.name = "PAL", .id = V4L2_STD_PAL_BG,
|
|
.v_offset = 0x14, .v_field = 288,
|
|
.h_offset = 0x48, .h_pixels = 708,
|
|
.v_max_out = 576, .h_max_out = 768,
|
|
}, {
|
|
.name = "NTSC", .id = V4L2_STD_NTSC,
|
|
.v_offset = 0x10, .v_field = 244,
|
|
.h_offset = 0x40, .h_pixels = 708,
|
|
.v_max_out = 480, .h_max_out = 640,
|
|
}
|
|
};
|
|
|
|
static int std_callback(struct saa7146_dev* dev, struct saa7146_standard *std)
|
|
{
|
|
struct av7110 *av7110 = (struct av7110*) dev->ext_priv;
|
|
|
|
if (std->id & V4L2_STD_PAL) {
|
|
av7110->vidmode = VIDEO_MODE_PAL;
|
|
av7110_set_vidmode(av7110, av7110->vidmode);
|
|
}
|
|
else if (std->id & V4L2_STD_NTSC) {
|
|
av7110->vidmode = VIDEO_MODE_NTSC;
|
|
av7110_set_vidmode(av7110, av7110->vidmode);
|
|
}
|
|
else
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct saa7146_ext_vv av7110_vv_data_st = {
|
|
.inputs = 1,
|
|
.audios = 1,
|
|
.capabilities = V4L2_CAP_SLICED_VBI_OUTPUT,
|
|
.flags = 0,
|
|
|
|
.stds = &standard[0],
|
|
.num_stds = ARRAY_SIZE(standard),
|
|
.std_callback = &std_callback,
|
|
|
|
.ioctls = &ioctls[0],
|
|
.ioctl = av7110_ioctl,
|
|
|
|
.vbi_fops.open = av7110_vbi_reset,
|
|
.vbi_fops.release = av7110_vbi_reset,
|
|
.vbi_fops.write = av7110_vbi_write,
|
|
};
|
|
|
|
static struct saa7146_ext_vv av7110_vv_data_c = {
|
|
.inputs = 1,
|
|
.audios = 1,
|
|
.capabilities = V4L2_CAP_TUNER | V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_OUTPUT,
|
|
.flags = SAA7146_USE_PORT_B_FOR_VBI,
|
|
|
|
.stds = &standard[0],
|
|
.num_stds = ARRAY_SIZE(standard),
|
|
.std_callback = &std_callback,
|
|
|
|
.ioctls = &ioctls[0],
|
|
.ioctl = av7110_ioctl,
|
|
|
|
.vbi_fops.open = av7110_vbi_reset,
|
|
.vbi_fops.release = av7110_vbi_reset,
|
|
.vbi_fops.write = av7110_vbi_write,
|
|
};
|
|
|