V4L/DVB (7786): cx18: new driver for the Conexant CX23418 MPEG encoder chip
Hans Verkuil [Mon, 28 Apr 2008 23:24:33 +0000 (20:24 -0300)]
Many thanks to Steve Toth from Hauppauge and Nattu Dakshinamurthy from
Conexant for their support. I am in particular thankful to Hauppauge
since without their help this driver would not exist. It should also
be noted that Steve did the work to get the DVB part up and running.
Thank you!

Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: Steven Toth <stoth@hauppauge.com>
Signed-off-by: Michael Krufky <mkrufky@linuxtv.org>
Signed-off-by: G. Andrew Walls <awalls@radix.net>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>

47 files changed:
Documentation/video4linux/cx18.txt [new file with mode: 0644]
drivers/media/video/Kconfig
drivers/media/video/Makefile
drivers/media/video/cx18/Kconfig [new file with mode: 0644]
drivers/media/video/cx18/Makefile [new file with mode: 0644]
drivers/media/video/cx18/cx18-audio.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-audio.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-av-audio.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-av-core.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-av-core.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-av-firmware.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-av-vbi.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-cards.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-cards.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-controls.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-controls.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-driver.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-driver.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-dvb.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-dvb.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-fileops.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-fileops.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-firmware.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-firmware.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-gpio.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-gpio.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-i2c.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-i2c.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-ioctl.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-ioctl.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-irq.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-irq.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-mailbox.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-mailbox.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-queue.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-queue.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-scb.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-scb.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-streams.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-streams.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-vbi.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-vbi.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-version.h [new file with mode: 0644]
drivers/media/video/cx18/cx18-video.c [new file with mode: 0644]
drivers/media/video/cx18/cx18-video.h [new file with mode: 0644]
drivers/media/video/cx18/cx23418.h [new file with mode: 0644]
include/media/v4l2-chip-ident.h

diff --git a/Documentation/video4linux/cx18.txt b/Documentation/video4linux/cx18.txt
new file mode 100644 (file)
index 0000000..077d56e
--- /dev/null
@@ -0,0 +1,34 @@
+Some notes regarding the cx18 driver for the Conexant CX23418 MPEG
+encoder chip:
+
+1) The only hardware currently supported is the Hauppauge HVR-1600.
+
+2) Some people have problems getting the i2c bus to work. Cause unknown.
+   The symptom is that the eeprom cannot be read and the card is
+   unusable.
+
+3) The audio from the analog tuner is mono only. Probably caused by
+   incorrect audio register information in the datasheet. We are
+   waiting for updated information from Conexant.
+
+4) VBI (raw or sliced) has not yet been implemented.
+
+5) MPEG indexing is not yet implemented.
+
+6) The driver is still a bit rough around the edges, this should
+   improve over time.
+
+
+Firmware:
+
+The firmware needs to be extracted from the Windows Hauppauge HVR-1600
+driver, available here:
+
+http://hauppauge.lightpath.net/software/install_cd/hauppauge_cd_3.4d1.zip
+
+Unzip, then copy the following files to the firmware directory
+and rename them as follows:
+
+Drivers/Driver18/hcw18apu.rom -> v4l-cx23418-apu.fw
+Drivers/Driver18/hcw18enc.rom -> v4l-cx23418-cpu.fw
+Drivers/Driver18/hcw18mlC.rom -> v4l-cx23418-dig.fw
index 7dda5d5..fe743aa 100644 (file)
@@ -748,6 +748,8 @@ source "drivers/media/video/au0828/Kconfig"
 
 source "drivers/media/video/ivtv/Kconfig"
 
+source "drivers/media/video/cx18/Kconfig"
+
 config VIDEO_M32R_AR
        tristate "AR devices"
        depends on M32R && VIDEO_V4L1
index 560dc32..a352c6e 100644 (file)
@@ -124,6 +124,7 @@ obj-$(CONFIG_USB_VICAM)         += usbvideo/
 obj-$(CONFIG_USB_QUICKCAM_MESSENGER)   += usbvideo/
 
 obj-$(CONFIG_VIDEO_IVTV) += ivtv/
+obj-$(CONFIG_VIDEO_CX18) += cx18/
 
 obj-$(CONFIG_VIDEO_VIVI) += vivi.o
 obj-$(CONFIG_VIDEO_CX23885) += cx23885/
diff --git a/drivers/media/video/cx18/Kconfig b/drivers/media/video/cx18/Kconfig
new file mode 100644 (file)
index 0000000..be654a2
--- /dev/null
@@ -0,0 +1,20 @@
+config VIDEO_CX18
+       tristate "Conexant cx23418 MPEG encoder support"
+       depends on VIDEO_V4L2 && DVB_CORE && PCI && I2C && EXPERIMENTAL
+       select I2C_ALGOBIT
+       select FW_LOADER
+       select VIDEO_IR
+       select VIDEO_TUNER
+       select VIDEO_TVEEPROM
+       select VIDEO_CX2341X
+       select VIDEO_CS5345
+       select DVB_S5H1409
+       ---help---
+         This is a video4linux driver for Conexant cx23418 based
+         PCI combo video recorder devices.
+
+         This is used in devices such as the Hauppauge HVR-1600
+         cards.
+
+         To compile this driver as a module, choose M here: the
+         module will be called cx18.
diff --git a/drivers/media/video/cx18/Makefile b/drivers/media/video/cx18/Makefile
new file mode 100644 (file)
index 0000000..b23d2e2
--- /dev/null
@@ -0,0 +1,11 @@
+cx18-objs    := cx18-driver.o cx18-cards.o cx18-i2c.o cx18-firmware.o cx18-gpio.o \
+       cx18-queue.o cx18-streams.o cx18-fileops.o cx18-ioctl.o cx18-controls.o \
+       cx18-mailbox.o cx18-vbi.o cx18-audio.o cx18-video.o cx18-irq.o \
+       cx18-av-core.o cx18-av-audio.o cx18-av-firmware.o cx18-av-vbi.o cx18-scb.o \
+       cx18-dvb.o
+
+obj-$(CONFIG_VIDEO_CX18) += cx18.o
+
+EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core
+EXTRA_CFLAGS += -Idrivers/media/dvb/frontends
+EXTRA_CFLAGS += -Idrivers/media/common/tuners
diff --git a/drivers/media/video/cx18/cx18-audio.c b/drivers/media/video/cx18/cx18-audio.c
new file mode 100644 (file)
index 0000000..1adc404
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ *  cx18 audio-related functions
+ *
+ *  Derived from ivtv-audio.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include "cx18-driver.h"
+#include "cx18-i2c.h"
+#include "cx18-cards.h"
+#include "cx18-audio.h"
+
+/* Selects the audio input and output according to the current
+   settings. */
+int cx18_audio_set_io(struct cx18 *cx)
+{
+       struct v4l2_routing route;
+       u32 audio_input;
+       int mux_input;
+
+       /* Determine which input to use */
+       if (test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) {
+               audio_input = cx->card->radio_input.audio_input;
+               mux_input = cx->card->radio_input.muxer_input;
+       } else {
+               audio_input =
+                       cx->card->audio_inputs[cx->audio_input].audio_input;
+               mux_input =
+                       cx->card->audio_inputs[cx->audio_input].muxer_input;
+       }
+
+       /* handle muxer chips */
+       route.input = mux_input;
+       route.output = 0;
+       cx18_i2c_hw(cx, cx->card->hw_muxer, VIDIOC_INT_S_AUDIO_ROUTING, &route);
+
+       route.input = audio_input;
+       return cx18_i2c_hw(cx, cx->card->hw_audio_ctrl,
+                       VIDIOC_INT_S_AUDIO_ROUTING, &route);
+}
+
+void cx18_audio_set_route(struct cx18 *cx, struct v4l2_routing *route)
+{
+       cx18_i2c_hw(cx, cx->card->hw_audio_ctrl,
+                       VIDIOC_INT_S_AUDIO_ROUTING, route);
+}
+
+void cx18_audio_set_audio_clock_freq(struct cx18 *cx, u8 freq)
+{
+       static u32 freqs[3] = { 44100, 48000, 32000 };
+
+       /* The audio clock of the digitizer must match the codec sample
+          rate otherwise you get some very strange effects. */
+       if (freq > 2)
+               return;
+       cx18_call_i2c_clients(cx, VIDIOC_INT_AUDIO_CLOCK_FREQ, &freqs[freq]);
+}
diff --git a/drivers/media/video/cx18/cx18-audio.h b/drivers/media/video/cx18/cx18-audio.h
new file mode 100644 (file)
index 0000000..cb569a6
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *  cx18 audio-related functions
+ *
+ *  Derived from ivtv-audio.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+int cx18_audio_set_io(struct cx18 *cx);
+void cx18_audio_set_route(struct cx18 *cx, struct v4l2_routing *route);
+void cx18_audio_set_audio_clock_freq(struct cx18 *cx, u8 freq);
diff --git a/drivers/media/video/cx18/cx18-av-audio.c b/drivers/media/video/cx18/cx18-av-audio.c
new file mode 100644 (file)
index 0000000..2dc3a5d
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ *  cx18 ADEC audio functions
+ *
+ *  Derived from cx25840-audio.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+#include "cx18-driver.h"
+
+static int set_audclk_freq(struct cx18 *cx, u32 freq)
+{
+       struct cx18_av_state *state = &cx->av_state;
+
+       if (freq != 32000 && freq != 44100 && freq != 48000)
+               return -EINVAL;
+
+       /* common for all inputs and rates */
+       /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x10 */
+       cx18_av_write(cx, 0x127, 0x50);
+
+       if (state->aud_input != CX18_AV_AUDIO_SERIAL) {
+               switch (freq) {
+               case 32000:
+                       /* VID_PLL and AUX_PLL */
+                       cx18_av_write4(cx, 0x108, 0x1006040f);
+
+                       /* AUX_PLL_FRAC */
+                       cx18_av_write4(cx, 0x110, 0x01bb39ee);
+
+                       /* src3/4/6_ctl = 0x0801f77f */
+                       cx18_av_write4(cx, 0x900, 0x0801f77f);
+                       cx18_av_write4(cx, 0x904, 0x0801f77f);
+                       cx18_av_write4(cx, 0x90c, 0x0801f77f);
+                       break;
+
+               case 44100:
+                       /* VID_PLL and AUX_PLL */
+                       cx18_av_write4(cx, 0x108, 0x1009040f);
+
+                       /* AUX_PLL_FRAC */
+                       cx18_av_write4(cx, 0x110, 0x00ec6bd6);
+
+                       /* src3/4/6_ctl = 0x08016d59 */
+                       cx18_av_write4(cx, 0x900, 0x08016d59);
+                       cx18_av_write4(cx, 0x904, 0x08016d59);
+                       cx18_av_write4(cx, 0x90c, 0x08016d59);
+                       break;
+
+               case 48000:
+                       /* VID_PLL and AUX_PLL */
+                       cx18_av_write4(cx, 0x108, 0x100a040f);
+
+                       /* AUX_PLL_FRAC */
+                       cx18_av_write4(cx, 0x110, 0x0098d6e5);
+
+                       /* src3/4/6_ctl = 0x08014faa */
+                       cx18_av_write4(cx, 0x900, 0x08014faa);
+                       cx18_av_write4(cx, 0x904, 0x08014faa);
+                       cx18_av_write4(cx, 0x90c, 0x08014faa);
+                       break;
+               }
+       } else {
+               switch (freq) {
+               case 32000:
+                       /* VID_PLL and AUX_PLL */
+                       cx18_av_write4(cx, 0x108, 0x1e08040f);
+
+                       /* AUX_PLL_FRAC */
+                       cx18_av_write4(cx, 0x110, 0x012a0869);
+
+                       /* src1_ctl = 0x08010000 */
+                       cx18_av_write4(cx, 0x8f8, 0x08010000);
+
+                       /* src3/4/6_ctl = 0x08020000 */
+                       cx18_av_write4(cx, 0x900, 0x08020000);
+                       cx18_av_write4(cx, 0x904, 0x08020000);
+                       cx18_av_write4(cx, 0x90c, 0x08020000);
+
+                       /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x14 */
+                       cx18_av_write(cx, 0x127, 0x54);
+                       break;
+
+               case 44100:
+                       /* VID_PLL and AUX_PLL */
+                       cx18_av_write4(cx, 0x108, 0x1809040f);
+
+                       /* AUX_PLL_FRAC */
+                       cx18_av_write4(cx, 0x110, 0x00ec6bd6);
+
+                       /* src1_ctl = 0x08010000 */
+                       cx18_av_write4(cx, 0x8f8, 0x080160cd);
+
+                       /* src3/4/6_ctl = 0x08020000 */
+                       cx18_av_write4(cx, 0x900, 0x08017385);
+                       cx18_av_write4(cx, 0x904, 0x08017385);
+                       cx18_av_write4(cx, 0x90c, 0x08017385);
+                       break;
+
+               case 48000:
+                       /* VID_PLL and AUX_PLL */
+                       cx18_av_write4(cx, 0x108, 0x180a040f);
+
+                       /* AUX_PLL_FRAC */
+                       cx18_av_write4(cx, 0x110, 0x0098d6e5);
+
+                       /* src1_ctl = 0x08010000 */
+                       cx18_av_write4(cx, 0x8f8, 0x08018000);
+
+                       /* src3/4/6_ctl = 0x08020000 */
+                       cx18_av_write4(cx, 0x900, 0x08015555);
+                       cx18_av_write4(cx, 0x904, 0x08015555);
+                       cx18_av_write4(cx, 0x90c, 0x08015555);
+                       break;
+               }
+       }
+
+       state->audclk_freq = freq;
+
+       return 0;
+}
+
+void cx18_av_audio_set_path(struct cx18 *cx)
+{
+       struct cx18_av_state *state = &cx->av_state;
+
+       /* stop microcontroller */
+       cx18_av_and_or(cx, 0x803, ~0x10, 0);
+
+       /* assert soft reset */
+       cx18_av_and_or(cx, 0x810, ~0x1, 0x01);
+
+       /* Mute everything to prevent the PFFT! */
+       cx18_av_write(cx, 0x8d3, 0x1f);
+
+       if (state->aud_input == CX18_AV_AUDIO_SERIAL) {
+               /* Set Path1 to Serial Audio Input */
+               cx18_av_write4(cx, 0x8d0, 0x01011012);
+
+               /* The microcontroller should not be started for the
+                * non-tuner inputs: autodetection is specific for
+                * TV audio. */
+       } else {
+               /* Set Path1 to Analog Demod Main Channel */
+               cx18_av_write4(cx, 0x8d0, 0x1f063870);
+       }
+
+       set_audclk_freq(cx, state->audclk_freq);
+
+       /* deassert soft reset */
+       cx18_av_and_or(cx, 0x810, ~0x1, 0x00);
+
+       if (state->aud_input != CX18_AV_AUDIO_SERIAL) {
+               /* When the microcontroller detects the
+                * audio format, it will unmute the lines */
+               cx18_av_and_or(cx, 0x803, ~0x10, 0x10);
+       }
+}
+
+static int get_volume(struct cx18 *cx)
+{
+       /* Volume runs +18dB to -96dB in 1/2dB steps
+        * change to fit the msp3400 -114dB to +12dB range */
+
+       /* check PATH1_VOLUME */
+       int vol = 228 - cx18_av_read(cx, 0x8d4);
+       vol = (vol / 2) + 23;
+       return vol << 9;
+}
+
+static void set_volume(struct cx18 *cx, int volume)
+{
+       /* First convert the volume to msp3400 values (0-127) */
+       int vol = volume >> 9;
+       /* now scale it up to cx18_av values
+        * -114dB to -96dB maps to 0
+        * this should be 19, but in my testing that was 4dB too loud */
+       if (vol <= 23)
+               vol = 0;
+       else
+               vol -= 23;
+
+       /* PATH1_VOLUME */
+       cx18_av_write(cx, 0x8d4, 228 - (vol * 2));
+}
+
+static int get_bass(struct cx18 *cx)
+{
+       /* bass is 49 steps +12dB to -12dB */
+
+       /* check PATH1_EQ_BASS_VOL */
+       int bass = cx18_av_read(cx, 0x8d9) & 0x3f;
+       bass = (((48 - bass) * 0xffff) + 47) / 48;
+       return bass;
+}
+
+static void set_bass(struct cx18 *cx, int bass)
+{
+       /* PATH1_EQ_BASS_VOL */
+       cx18_av_and_or(cx, 0x8d9, ~0x3f, 48 - (bass * 48 / 0xffff));
+}
+
+static int get_treble(struct cx18 *cx)
+{
+       /* treble is 49 steps +12dB to -12dB */
+
+       /* check PATH1_EQ_TREBLE_VOL */
+       int treble = cx18_av_read(cx, 0x8db) & 0x3f;
+       treble = (((48 - treble) * 0xffff) + 47) / 48;
+       return treble;
+}
+
+static void set_treble(struct cx18 *cx, int treble)
+{
+       /* PATH1_EQ_TREBLE_VOL */
+       cx18_av_and_or(cx, 0x8db, ~0x3f, 48 - (treble * 48 / 0xffff));
+}
+
+static int get_balance(struct cx18 *cx)
+{
+       /* balance is 7 bit, 0 to -96dB */
+
+       /* check PATH1_BAL_LEVEL */
+       int balance = cx18_av_read(cx, 0x8d5) & 0x7f;
+       /* check PATH1_BAL_LEFT */
+       if ((cx18_av_read(cx, 0x8d5) & 0x80) == 0)
+               balance = 0x80 - balance;
+       else
+               balance = 0x80 + balance;
+       return balance << 8;
+}
+
+static void set_balance(struct cx18 *cx, int balance)
+{
+       int bal = balance >> 8;
+       if (bal > 0x80) {
+               /* PATH1_BAL_LEFT */
+               cx18_av_and_or(cx, 0x8d5, 0x7f, 0x80);
+               /* PATH1_BAL_LEVEL */
+               cx18_av_and_or(cx, 0x8d5, ~0x7f, bal & 0x7f);
+       } else {
+               /* PATH1_BAL_LEFT */
+               cx18_av_and_or(cx, 0x8d5, 0x7f, 0x00);
+               /* PATH1_BAL_LEVEL */
+               cx18_av_and_or(cx, 0x8d5, ~0x7f, 0x80 - bal);
+       }
+}
+
+static int get_mute(struct cx18 *cx)
+{
+       /* check SRC1_MUTE_EN */
+       return cx18_av_read(cx, 0x8d3) & 0x2 ? 1 : 0;
+}
+
+static void set_mute(struct cx18 *cx, int mute)
+{
+       struct cx18_av_state *state = &cx->av_state;
+
+       if (state->aud_input != CX18_AV_AUDIO_SERIAL) {
+               /* Must turn off microcontroller in order to mute sound.
+                * Not sure if this is the best method, but it does work.
+                * If the microcontroller is running, then it will undo any
+                * changes to the mute register. */
+               if (mute) {
+                       /* disable microcontroller */
+                       cx18_av_and_or(cx, 0x803, ~0x10, 0x00);
+                       cx18_av_write(cx, 0x8d3, 0x1f);
+               } else {
+                       /* enable microcontroller */
+                       cx18_av_and_or(cx, 0x803, ~0x10, 0x10);
+               }
+       } else {
+               /* SRC1_MUTE_EN */
+               cx18_av_and_or(cx, 0x8d3, ~0x2, mute ? 0x02 : 0x00);
+       }
+}
+
+int cx18_av_audio(struct cx18 *cx, unsigned int cmd, void *arg)
+{
+       struct cx18_av_state *state = &cx->av_state;
+       struct v4l2_control *ctrl = arg;
+       int retval;
+
+       switch (cmd) {
+       case VIDIOC_INT_AUDIO_CLOCK_FREQ:
+               if (state->aud_input != CX18_AV_AUDIO_SERIAL) {
+                       cx18_av_and_or(cx, 0x803, ~0x10, 0);
+                       cx18_av_write(cx, 0x8d3, 0x1f);
+               }
+               cx18_av_and_or(cx, 0x810, ~0x1, 1);
+               retval = set_audclk_freq(cx, *(u32 *)arg);
+               cx18_av_and_or(cx, 0x810, ~0x1, 0);
+               if (state->aud_input != CX18_AV_AUDIO_SERIAL)
+                       cx18_av_and_or(cx, 0x803, ~0x10, 0x10);
+               return retval;
+
+       case VIDIOC_G_CTRL:
+               switch (ctrl->id) {
+               case V4L2_CID_AUDIO_VOLUME:
+                       ctrl->value = get_volume(cx);
+                       break;
+               case V4L2_CID_AUDIO_BASS:
+                       ctrl->value = get_bass(cx);
+                       break;
+               case V4L2_CID_AUDIO_TREBLE:
+                       ctrl->value = get_treble(cx);
+                       break;
+               case V4L2_CID_AUDIO_BALANCE:
+                       ctrl->value = get_balance(cx);
+                       break;
+               case V4L2_CID_AUDIO_MUTE:
+                       ctrl->value = get_mute(cx);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               break;
+
+       case VIDIOC_S_CTRL:
+               switch (ctrl->id) {
+               case V4L2_CID_AUDIO_VOLUME:
+                       set_volume(cx, ctrl->value);
+                       break;
+               case V4L2_CID_AUDIO_BASS:
+                       set_bass(cx, ctrl->value);
+                       break;
+               case V4L2_CID_AUDIO_TREBLE:
+                       set_treble(cx, ctrl->value);
+                       break;
+               case V4L2_CID_AUDIO_BALANCE:
+                       set_balance(cx, ctrl->value);
+                       break;
+               case V4L2_CID_AUDIO_MUTE:
+                       set_mute(cx, ctrl->value);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
diff --git a/drivers/media/video/cx18/cx18-av-core.c b/drivers/media/video/cx18/cx18-av-core.c
new file mode 100644 (file)
index 0000000..6686490
--- /dev/null
@@ -0,0 +1,879 @@
+/*
+ *  cx18 ADEC audio functions
+ *
+ *  Derived from cx25840-core.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+#include "cx18-driver.h"
+
+int cx18_av_write(struct cx18 *cx, u16 addr, u8 value)
+{
+       u32 x = readl(cx->reg_mem + 0xc40000 + (addr & ~3));
+       u32 mask = 0xff;
+       int shift = (addr & 3) * 8;
+
+       x = (x & ~(mask << shift)) | ((u32)value << shift);
+       writel(x, cx->reg_mem + 0xc40000 + (addr & ~3));
+       return 0;
+}
+
+int cx18_av_write4(struct cx18 *cx, u16 addr, u32 value)
+{
+       writel(value, cx->reg_mem + 0xc40000 + addr);
+       return 0;
+}
+
+u8 cx18_av_read(struct cx18 *cx, u16 addr)
+{
+       u32 x = readl(cx->reg_mem + 0xc40000 + (addr & ~3));
+       int shift = (addr & 3) * 8;
+
+       return (x >> shift) & 0xff;
+}
+
+u32 cx18_av_read4(struct cx18 *cx, u16 addr)
+{
+       return readl(cx->reg_mem + 0xc40000 + addr);
+}
+
+int cx18_av_and_or(struct cx18 *cx, u16 addr, unsigned and_mask,
+                  u8 or_value)
+{
+       return cx18_av_write(cx, addr,
+                            (cx18_av_read(cx, addr) & and_mask) |
+                            or_value);
+}
+
+int cx18_av_and_or4(struct cx18 *cx, u16 addr, u32 and_mask,
+                  u32 or_value)
+{
+       return cx18_av_write4(cx, addr,
+                            (cx18_av_read4(cx, addr) & and_mask) |
+                            or_value);
+}
+
+/* ----------------------------------------------------------------------- */
+
+static int set_input(struct cx18 *cx, enum cx18_av_video_input vid_input,
+                                       enum cx18_av_audio_input aud_input);
+static void log_audio_status(struct cx18 *cx);
+static void log_video_status(struct cx18 *cx);
+
+/* ----------------------------------------------------------------------- */
+
+static void cx18_av_initialize(struct cx18 *cx)
+{
+       u32 v;
+
+       cx18_av_loadfw(cx);
+       /* Stop 8051 code execution */
+       cx18_av_write4(cx, CXADEC_DL_CTL, 0x03000000);
+
+       /* initallize the PLL by toggling sleep bit */
+       v = cx18_av_read4(cx, CXADEC_HOST_REG1);
+       /* enable sleep mode */
+       cx18_av_write4(cx, CXADEC_HOST_REG1, v | 1);
+       /* disable sleep mode */
+       cx18_av_write4(cx, CXADEC_HOST_REG1, v & 0xfffe);
+
+       /* initialize DLLs */
+       v = cx18_av_read4(cx, CXADEC_DLL1_DIAG_CTRL) & 0xE1FFFEFF;
+       /* disable FLD */
+       cx18_av_write4(cx, CXADEC_DLL1_DIAG_CTRL, v);
+       /* enable FLD */
+       cx18_av_write4(cx, CXADEC_DLL1_DIAG_CTRL, v | 0x10000100);
+
+       v = cx18_av_read4(cx, CXADEC_DLL2_DIAG_CTRL) & 0xE1FFFEFF;
+       /* disable FLD */
+       cx18_av_write4(cx, CXADEC_DLL2_DIAG_CTRL, v);
+       /* enable FLD */
+       cx18_av_write4(cx, CXADEC_DLL2_DIAG_CTRL, v | 0x06000100);
+
+       /* set analog bias currents. Set Vreg to 1.20V. */
+       cx18_av_write4(cx, CXADEC_AFE_DIAG_CTRL1, 0x000A1802);
+
+       v = cx18_av_read4(cx, CXADEC_AFE_DIAG_CTRL3) | 1;
+       /* enable TUNE_FIL_RST */
+       cx18_av_write4(cx, CXADEC_AFE_DIAG_CTRL3, v);
+       /* disable TUNE_FIL_RST */
+       cx18_av_write4(cx, CXADEC_AFE_DIAG_CTRL3, v & 0xFFFFFFFE);
+
+       /* enable 656 output */
+       cx18_av_and_or4(cx, CXADEC_PIN_CTRL1, ~0, 0x040C00);
+
+       /* video output drive strength */
+       cx18_av_and_or4(cx, CXADEC_PIN_CTRL2, ~0, 0x2);
+
+       /* reset video */
+       cx18_av_write4(cx, CXADEC_SOFT_RST_CTRL, 0x8000);
+       cx18_av_write4(cx, CXADEC_SOFT_RST_CTRL, 0);
+
+       /* set video to auto-detect */
+       /* Clear bits 11-12 to enable slow locking mode.  Set autodetect mode */
+       /* set the comb notch = 1 */
+       cx18_av_and_or4(cx, CXADEC_MODE_CTRL, 0xFFF7E7F0, 0x02040800);
+
+       /* Enable wtw_en in CRUSH_CTRL (Set bit 22) */
+       /* Enable maj_sel in CRUSH_CTRL (Set bit 20) */
+       cx18_av_and_or4(cx, CXADEC_CRUSH_CTRL, ~0, 0x00500000);
+
+       /* Set VGA_TRACK_RANGE to 0x20 */
+       cx18_av_and_or4(cx, CXADEC_DFE_CTRL2, 0xFFFF00FF, 0x00002000);
+
+       /* Enable VBI capture */
+       cx18_av_write4(cx, CXADEC_OUT_CTRL1, 0x4010253F);
+       /* cx18_av_write4(cx, CXADEC_OUT_CTRL1, 0x4010253E); */
+
+       /* Set the video input.
+          The setting in MODE_CTRL gets lost when we do the above setup */
+       /* EncSetSignalStd(dwDevNum, pEnc->dwSigStd); */
+       /* EncSetVideoInput(dwDevNum, pEnc->VidIndSelection); */
+
+       v = cx18_av_read4(cx, CXADEC_AFE_CTRL);
+       v &= 0xFFFBFFFF;            /* turn OFF bit 18 for droop_comp_ch1 */
+       v &= 0xFFFF7FFF;            /* turn OFF bit 9 for clamp_sel_ch1 */
+       v &= 0xFFFFFFFE;            /* turn OFF bit 0 for 12db_ch1 */
+       /* v |= 0x00000001;*/            /* turn ON bit 0 for 12db_ch1 */
+       cx18_av_write4(cx, CXADEC_AFE_CTRL, v);
+
+/*     if(dwEnable && dw3DCombAvailable) { */
+/*             CxDevWrReg(CXADEC_SRC_COMB_CFG, 0x7728021F); */
+/*    } else { */
+/*             CxDevWrReg(CXADEC_SRC_COMB_CFG, 0x6628021F); */
+/*    } */
+       cx18_av_write4(cx, CXADEC_SRC_COMB_CFG, 0x6628021F);
+}
+
+/* ----------------------------------------------------------------------- */
+
+static void input_change(struct cx18 *cx)
+{
+       struct cx18_av_state *state = &cx->av_state;
+       v4l2_std_id std = state->std;
+
+       /* Follow step 8c and 8d of section 3.16 in the cx18_av datasheet */
+       if (std & V4L2_STD_SECAM)
+               cx18_av_write(cx, 0x402, 0);
+       else {
+               cx18_av_write(cx, 0x402, 0x04);
+               cx18_av_write(cx, 0x49f, (std & V4L2_STD_NTSC) ? 0x14 : 0x11);
+       }
+       cx18_av_and_or(cx, 0x401, ~0x60, 0);
+       cx18_av_and_or(cx, 0x401, ~0x60, 0x60);
+
+       if (std & V4L2_STD_525_60) {
+               if (std == V4L2_STD_NTSC_M_JP) {
+                       /* Japan uses EIAJ audio standard */
+                       cx18_av_write(cx, 0x808, 0xf7);
+               } else if (std == V4L2_STD_NTSC_M_KR) {
+                       /* South Korea uses A2 audio standard */
+                       cx18_av_write(cx, 0x808, 0xf8);
+               } else {
+                       /* Others use the BTSC audio standard */
+                       cx18_av_write(cx, 0x808, 0xf6);
+               }
+               cx18_av_write(cx, 0x80b, 0x00);
+       } else if (std & V4L2_STD_PAL) {
+               /* Follow tuner change procedure for PAL */
+               cx18_av_write(cx, 0x808, 0xff);
+               cx18_av_write(cx, 0x80b, 0x03);
+       } else if (std & V4L2_STD_SECAM) {
+               /* Select autodetect for SECAM */
+               cx18_av_write(cx, 0x808, 0xff);
+               cx18_av_write(cx, 0x80b, 0x03);
+       }
+
+       if (cx18_av_read(cx, 0x803) & 0x10) {
+               /* restart audio decoder microcontroller */
+               cx18_av_and_or(cx, 0x803, ~0x10, 0x00);
+               cx18_av_and_or(cx, 0x803, ~0x10, 0x10);
+       }
+}
+
+static int set_input(struct cx18 *cx, enum cx18_av_video_input vid_input,
+                                       enum cx18_av_audio_input aud_input)
+{
+       struct cx18_av_state *state = &cx->av_state;
+       u8 is_composite = (vid_input >= CX18_AV_COMPOSITE1 &&
+                          vid_input <= CX18_AV_COMPOSITE8);
+       u8 reg;
+
+       CX18_DEBUG_INFO("decoder set video input %d, audio input %d\n",
+                       vid_input, aud_input);
+
+       if (is_composite) {
+               reg = 0xf0 + (vid_input - CX18_AV_COMPOSITE1);
+       } else {
+               int luma = vid_input & 0xf0;
+               int chroma = vid_input & 0xf00;
+
+               if ((vid_input & ~0xff0) ||
+                   luma < CX18_AV_SVIDEO_LUMA1 ||
+                   luma > CX18_AV_SVIDEO_LUMA4 ||
+                   chroma < CX18_AV_SVIDEO_CHROMA4 ||
+                   chroma > CX18_AV_SVIDEO_CHROMA8) {
+                       CX18_ERR("0x%04x is not a valid video input!\n",
+                                       vid_input);
+                       return -EINVAL;
+               }
+               reg = 0xf0 + ((luma - CX18_AV_SVIDEO_LUMA1) >> 4);
+               if (chroma >= CX18_AV_SVIDEO_CHROMA7) {
+                       reg &= 0x3f;
+                       reg |= (chroma - CX18_AV_SVIDEO_CHROMA7) >> 2;
+               } else {
+                       reg &= 0xcf;
+                       reg |= (chroma - CX18_AV_SVIDEO_CHROMA4) >> 4;
+               }
+       }
+
+       switch (aud_input) {
+       case CX18_AV_AUDIO_SERIAL:
+               /* do nothing, use serial audio input */
+               break;
+       case CX18_AV_AUDIO4: reg &= ~0x30; break;
+       case CX18_AV_AUDIO5: reg &= ~0x30; reg |= 0x10; break;
+       case CX18_AV_AUDIO6: reg &= ~0x30; reg |= 0x20; break;
+       case CX18_AV_AUDIO7: reg &= ~0xc0; break;
+       case CX18_AV_AUDIO8: reg &= ~0xc0; reg |= 0x40; break;
+
+       default:
+               CX18_ERR("0x%04x is not a valid audio input!\n", aud_input);
+               return -EINVAL;
+       }
+
+       cx18_av_write(cx, 0x103, reg);
+       /* Set INPUT_MODE to Composite (0) or S-Video (1) */
+       cx18_av_and_or(cx, 0x401, ~0x6, is_composite ? 0 : 0x02);
+       /* Set CH_SEL_ADC2 to 1 if input comes from CH3 */
+       cx18_av_and_or(cx, 0x102, ~0x2, (reg & 0x80) == 0 ? 2 : 0);
+       /* Set DUAL_MODE_ADC2 to 1 if input comes from both CH2 and CH3 */
+       if ((reg & 0xc0) != 0xc0 && (reg & 0x30) != 0x30)
+               cx18_av_and_or(cx, 0x102, ~0x4, 4);
+       else
+               cx18_av_and_or(cx, 0x102, ~0x4, 0);
+       /*cx18_av_and_or4(cx, 0x104, ~0x001b4180, 0x00004180);*/
+
+       state->vid_input = vid_input;
+       state->aud_input = aud_input;
+       cx18_av_audio_set_path(cx);
+       input_change(cx);
+       return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static int set_v4lstd(struct cx18 *cx)
+{
+       struct cx18_av_state *state = &cx->av_state;
+       u8 fmt = 0;     /* zero is autodetect */
+       u8 pal_m = 0;
+
+       /* First tests should be against specific std */
+       if (state->std == V4L2_STD_NTSC_M_JP) {
+               fmt = 0x2;
+       } else if (state->std == V4L2_STD_NTSC_443) {
+               fmt = 0x3;
+       } else if (state->std == V4L2_STD_PAL_M) {
+               pal_m = 1;
+               fmt = 0x5;
+       } else if (state->std == V4L2_STD_PAL_N) {
+               fmt = 0x6;
+       } else if (state->std == V4L2_STD_PAL_Nc) {
+               fmt = 0x7;
+       } else if (state->std == V4L2_STD_PAL_60) {
+               fmt = 0x8;
+       } else {
+               /* Then, test against generic ones */
+               if (state->std & V4L2_STD_NTSC)
+                       fmt = 0x1;
+               else if (state->std & V4L2_STD_PAL)
+                       fmt = 0x4;
+               else if (state->std & V4L2_STD_SECAM)
+                       fmt = 0xc;
+       }
+
+       CX18_DEBUG_INFO("changing video std to fmt %i\n", fmt);
+
+       /* Follow step 9 of section 3.16 in the cx18_av datasheet.
+          Without this PAL may display a vertical ghosting effect.
+          This happens for example with the Yuan MPC622. */
+       if (fmt >= 4 && fmt < 8) {
+               /* Set format to NTSC-M */
+               cx18_av_and_or(cx, 0x400, ~0xf, 1);
+               /* Turn off LCOMB */
+               cx18_av_and_or(cx, 0x47b, ~6, 0);
+       }
+       cx18_av_and_or(cx, 0x400, ~0xf, fmt);
+       cx18_av_and_or(cx, 0x403, ~0x3, pal_m);
+       cx18_av_vbi_setup(cx);
+       input_change(cx);
+       return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static int set_v4lctrl(struct cx18 *cx, struct v4l2_control *ctrl)
+{
+       switch (ctrl->id) {
+       case V4L2_CID_BRIGHTNESS:
+               if (ctrl->value < 0 || ctrl->value > 255) {
+                       CX18_ERR("invalid brightness setting %d\n",
+                                   ctrl->value);
+                       return -ERANGE;
+               }
+
+               cx18_av_write(cx, 0x414, ctrl->value - 128);
+               break;
+
+       case V4L2_CID_CONTRAST:
+               if (ctrl->value < 0 || ctrl->value > 127) {
+                       CX18_ERR("invalid contrast setting %d\n",
+                                   ctrl->value);
+                       return -ERANGE;
+               }
+
+               cx18_av_write(cx, 0x415, ctrl->value << 1);
+               break;
+
+       case V4L2_CID_SATURATION:
+               if (ctrl->value < 0 || ctrl->value > 127) {
+                       CX18_ERR("invalid saturation setting %d\n",
+                                   ctrl->value);
+                       return -ERANGE;
+               }
+
+               cx18_av_write(cx, 0x420, ctrl->value << 1);
+               cx18_av_write(cx, 0x421, ctrl->value << 1);
+               break;
+
+       case V4L2_CID_HUE:
+               if (ctrl->value < -127 || ctrl->value > 127) {
+                       CX18_ERR("invalid hue setting %d\n", ctrl->value);
+                       return -ERANGE;
+               }
+
+               cx18_av_write(cx, 0x422, ctrl->value);
+               break;
+
+       case V4L2_CID_AUDIO_VOLUME:
+       case V4L2_CID_AUDIO_BASS:
+       case V4L2_CID_AUDIO_TREBLE:
+       case V4L2_CID_AUDIO_BALANCE:
+       case V4L2_CID_AUDIO_MUTE:
+               return cx18_av_audio(cx, VIDIOC_S_CTRL, ctrl);
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int get_v4lctrl(struct cx18 *cx, struct v4l2_control *ctrl)
+{
+       switch (ctrl->id) {
+       case V4L2_CID_BRIGHTNESS:
+               ctrl->value = (s8)cx18_av_read(cx, 0x414) + 128;
+               break;
+       case V4L2_CID_CONTRAST:
+               ctrl->value = cx18_av_read(cx, 0x415) >> 1;
+               break;
+       case V4L2_CID_SATURATION:
+               ctrl->value = cx18_av_read(cx, 0x420) >> 1;
+               break;
+       case V4L2_CID_HUE:
+               ctrl->value = (s8)cx18_av_read(cx, 0x422);
+               break;
+       case V4L2_CID_AUDIO_VOLUME:
+       case V4L2_CID_AUDIO_BASS:
+       case V4L2_CID_AUDIO_TREBLE:
+       case V4L2_CID_AUDIO_BALANCE:
+       case V4L2_CID_AUDIO_MUTE:
+               return cx18_av_audio(cx, VIDIOC_G_CTRL, ctrl);
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static int get_v4lfmt(struct cx18 *cx, struct v4l2_format *fmt)
+{
+       switch (fmt->type) {
+       case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+               return cx18_av_vbi(cx, VIDIOC_G_FMT, fmt);
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int set_v4lfmt(struct cx18 *cx, struct v4l2_format *fmt)
+{
+       struct cx18_av_state *state = &cx->av_state;
+       struct v4l2_pix_format *pix;
+       int HSC, VSC, Vsrc, Hsrc, filter, Vlines;
+       int is_50Hz = !(state->std & V4L2_STD_525_60);
+
+       switch (fmt->type) {
+       case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+               pix = &(fmt->fmt.pix);
+
+               Vsrc = (cx18_av_read(cx, 0x476) & 0x3f) << 4;
+               Vsrc |= (cx18_av_read(cx, 0x475) & 0xf0) >> 4;
+
+               Hsrc = (cx18_av_read(cx, 0x472) & 0x3f) << 4;
+               Hsrc |= (cx18_av_read(cx, 0x471) & 0xf0) >> 4;
+
+               Vlines = pix->height + (is_50Hz ? 4 : 7);
+
+               if ((pix->width * 16 < Hsrc) || (Hsrc < pix->width) ||
+                   (Vlines * 8 < Vsrc) || (Vsrc < Vlines)) {
+                       CX18_ERR("%dx%d is not a valid size!\n",
+                                   pix->width, pix->height);
+                       return -ERANGE;
+               }
+
+               HSC = (Hsrc * (1 << 20)) / pix->width - (1 << 20);
+               VSC = (1 << 16) - (Vsrc * (1 << 9) / Vlines - (1 << 9));
+               VSC &= 0x1fff;
+
+               if (pix->width >= 385)
+                       filter = 0;
+               else if (pix->width > 192)
+                       filter = 1;
+               else if (pix->width > 96)
+                       filter = 2;
+               else
+                       filter = 3;
+
+               CX18_DEBUG_INFO("decoder set size %dx%d -> scale  %ux%u\n",
+                           pix->width, pix->height, HSC, VSC);
+
+               /* HSCALE=HSC */
+               cx18_av_write(cx, 0x418, HSC & 0xff);
+               cx18_av_write(cx, 0x419, (HSC >> 8) & 0xff);
+               cx18_av_write(cx, 0x41a, HSC >> 16);
+               /* VSCALE=VSC */
+               cx18_av_write(cx, 0x41c, VSC & 0xff);
+               cx18_av_write(cx, 0x41d, VSC >> 8);
+               /* VS_INTRLACE=1 VFILT=filter */
+               cx18_av_write(cx, 0x41e, 0x8 | filter);
+               break;
+
+       case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+               return cx18_av_vbi(cx, VIDIOC_S_FMT, fmt);
+
+       case V4L2_BUF_TYPE_VBI_CAPTURE:
+               return cx18_av_vbi(cx, VIDIOC_S_FMT, fmt);
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+int cx18_av_cmd(struct cx18 *cx, unsigned int cmd, void *arg)
+{
+       struct cx18_av_state *state = &cx->av_state;
+       struct v4l2_tuner *vt = arg;
+       struct v4l2_routing *route = arg;
+
+       /* ignore these commands */
+       switch (cmd) {
+       case TUNER_SET_TYPE_ADDR:
+               return 0;
+       }
+
+       if (!state->is_initialized) {
+               CX18_DEBUG_INFO("cmd %08x triggered fw load\n", cmd);
+               /* initialize on first use */
+               state->is_initialized = 1;
+               cx18_av_initialize(cx);
+       }
+
+       switch (cmd) {
+       case VIDIOC_INT_DECODE_VBI_LINE:
+               return cx18_av_vbi(cx, cmd, arg);
+
+       case VIDIOC_INT_AUDIO_CLOCK_FREQ:
+               return cx18_av_audio(cx, cmd, arg);
+
+       case VIDIOC_STREAMON:
+               CX18_DEBUG_INFO("enable output\n");
+               cx18_av_write(cx, 0x115, 0x8c);
+               cx18_av_write(cx, 0x116, 0x07);
+               break;
+
+       case VIDIOC_STREAMOFF:
+               CX18_DEBUG_INFO("disable output\n");
+               cx18_av_write(cx, 0x115, 0x00);
+               cx18_av_write(cx, 0x116, 0x00);
+               break;
+
+       case VIDIOC_LOG_STATUS:
+               log_video_status(cx);
+               log_audio_status(cx);
+               break;
+
+       case VIDIOC_G_CTRL:
+               return get_v4lctrl(cx, (struct v4l2_control *)arg);
+
+       case VIDIOC_S_CTRL:
+               return set_v4lctrl(cx, (struct v4l2_control *)arg);
+
+       case VIDIOC_QUERYCTRL:
+       {
+               struct v4l2_queryctrl *qc = arg;
+
+               switch (qc->id) {
+               case V4L2_CID_BRIGHTNESS:
+               case V4L2_CID_CONTRAST:
+               case V4L2_CID_SATURATION:
+               case V4L2_CID_HUE:
+                       return v4l2_ctrl_query_fill_std(qc);
+               default:
+                       break;
+               }
+
+               switch (qc->id) {
+               case V4L2_CID_AUDIO_VOLUME:
+               case V4L2_CID_AUDIO_MUTE:
+               case V4L2_CID_AUDIO_BALANCE:
+               case V4L2_CID_AUDIO_BASS:
+               case V4L2_CID_AUDIO_TREBLE:
+                       return v4l2_ctrl_query_fill_std(qc);
+               default:
+                       return -EINVAL;
+               }
+               return -EINVAL;
+       }
+
+       case VIDIOC_G_STD:
+               *(v4l2_std_id *)arg = state->std;
+               break;
+
+       case VIDIOC_S_STD:
+               if (state->radio == 0 && state->std == *(v4l2_std_id *)arg)
+                       return 0;
+               state->radio = 0;
+               state->std = *(v4l2_std_id *)arg;
+               return set_v4lstd(cx);
+
+       case AUDC_SET_RADIO:
+               state->radio = 1;
+               break;
+
+       case VIDIOC_INT_G_VIDEO_ROUTING:
+               route->input = state->vid_input;
+               route->output = 0;
+               break;
+
+       case VIDIOC_INT_S_VIDEO_ROUTING:
+               return set_input(cx, route->input, state->aud_input);
+
+       case VIDIOC_INT_G_AUDIO_ROUTING:
+               route->input = state->aud_input;
+               route->output = 0;
+               break;
+
+       case VIDIOC_INT_S_AUDIO_ROUTING:
+               return set_input(cx, state->vid_input, route->input);
+
+       case VIDIOC_S_FREQUENCY:
+               input_change(cx);
+               break;
+
+       case VIDIOC_G_TUNER:
+       {
+               u8 vpres = cx18_av_read(cx, 0x40e) & 0x20;
+               u8 mode;
+               int val = 0;
+
+               if (state->radio)
+                       break;
+
+               vt->signal = vpres ? 0xffff : 0x0;
+
+               vt->capability |=
+                   V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 |
+                   V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP;
+
+               mode = cx18_av_read(cx, 0x804);
+
+               /* get rxsubchans and audmode */
+               if ((mode & 0xf) == 1)
+                       val |= V4L2_TUNER_SUB_STEREO;
+               else
+                       val |= V4L2_TUNER_SUB_MONO;
+
+               if (mode == 2 || mode == 4)
+                       val = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2;
+
+               if (mode & 0x10)
+                       val |= V4L2_TUNER_SUB_SAP;
+
+               vt->rxsubchans = val;
+               vt->audmode = state->audmode;
+               break;
+       }
+
+       case VIDIOC_S_TUNER:
+               if (state->radio)
+                       break;
+
+               switch (vt->audmode) {
+               case V4L2_TUNER_MODE_MONO:
+                       /* mono      -> mono
+                          stereo    -> mono
+                          bilingual -> lang1 */
+                       cx18_av_and_or(cx, 0x809, ~0xf, 0x00);
+                       break;
+               case V4L2_TUNER_MODE_STEREO:
+               case V4L2_TUNER_MODE_LANG1:
+                       /* mono      -> mono
+                          stereo    -> stereo
+                          bilingual -> lang1 */
+                       cx18_av_and_or(cx, 0x809, ~0xf, 0x04);
+                       break;
+               case V4L2_TUNER_MODE_LANG1_LANG2:
+                       /* mono      -> mono
+                          stereo    -> stereo
+                          bilingual -> lang1/lang2 */
+                       cx18_av_and_or(cx, 0x809, ~0xf, 0x07);
+                       break;
+               case V4L2_TUNER_MODE_LANG2:
+                       /* mono      -> mono
+                          stereo    -> stereo
+                          bilingual -> lang2 */
+                       cx18_av_and_or(cx, 0x809, ~0xf, 0x01);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               state->audmode = vt->audmode;
+               break;
+
+       case VIDIOC_G_FMT:
+               return get_v4lfmt(cx, (struct v4l2_format *)arg);
+
+       case VIDIOC_S_FMT:
+               return set_v4lfmt(cx, (struct v4l2_format *)arg);
+
+       case VIDIOC_INT_RESET:
+               cx18_av_initialize(cx);
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+/* ----------------------------------------------------------------------- */
+
+static void log_video_status(struct cx18 *cx)
+{
+       static const char *const fmt_strs[] = {
+               "0x0",
+               "NTSC-M", "NTSC-J", "NTSC-4.43",
+               "PAL-BDGHI", "PAL-M", "PAL-N", "PAL-Nc", "PAL-60",
+               "0x9", "0xA", "0xB",
+               "SECAM",
+               "0xD", "0xE", "0xF"
+       };
+
+       struct cx18_av_state *state = &cx->av_state;
+       u8 vidfmt_sel = cx18_av_read(cx, 0x400) & 0xf;
+       u8 gen_stat1 = cx18_av_read(cx, 0x40d);
+       u8 gen_stat2 = cx18_av_read(cx, 0x40e);
+       int vid_input = state->vid_input;
+
+       CX18_INFO("Video signal:              %spresent\n",
+                   (gen_stat2 & 0x20) ? "" : "not ");
+       CX18_INFO("Detected format:           %s\n",
+                   fmt_strs[gen_stat1 & 0xf]);
+
+       CX18_INFO("Specified standard:        %s\n",
+                   vidfmt_sel ? fmt_strs[vidfmt_sel] : "automatic detection");
+
+       if (vid_input >= CX18_AV_COMPOSITE1 &&
+           vid_input <= CX18_AV_COMPOSITE8) {
+               CX18_INFO("Specified video input:     Composite %d\n",
+                       vid_input - CX18_AV_COMPOSITE1 + 1);
+       } else {
+               CX18_INFO("Specified video input:     S-Video (Luma In%d, Chroma In%d)\n",
+                       (vid_input & 0xf0) >> 4, (vid_input & 0xf00) >> 8);
+       }
+
+       CX18_INFO("Specified audioclock freq: %d Hz\n", state->audclk_freq);
+}
+
+/* ----------------------------------------------------------------------- */
+
+static void log_audio_status(struct cx18 *cx)
+{
+       struct cx18_av_state *state = &cx->av_state;
+       u8 download_ctl = cx18_av_read(cx, 0x803);
+       u8 mod_det_stat0 = cx18_av_read(cx, 0x805);
+       u8 mod_det_stat1 = cx18_av_read(cx, 0x804);
+       u8 audio_config = cx18_av_read(cx, 0x808);
+       u8 pref_mode = cx18_av_read(cx, 0x809);
+       u8 afc0 = cx18_av_read(cx, 0x80b);
+       u8 mute_ctl = cx18_av_read(cx, 0x8d3);
+       int aud_input = state->aud_input;
+       char *p;
+
+       switch (mod_det_stat0) {
+       case 0x00: p = "mono"; break;
+       case 0x01: p = "stereo"; break;
+       case 0x02: p = "dual"; break;
+       case 0x04: p = "tri"; break;
+       case 0x10: p = "mono with SAP"; break;
+       case 0x11: p = "stereo with SAP"; break;
+       case 0x12: p = "dual with SAP"; break;
+       case 0x14: p = "tri with SAP"; break;
+       case 0xfe: p = "forced mode"; break;
+       default: p = "not defined";
+       }
+       CX18_INFO("Detected audio mode:       %s\n", p);
+
+       switch (mod_det_stat1) {
+       case 0x00: p = "BTSC"; break;
+       case 0x01: p = "EIAJ"; break;
+       case 0x02: p = "A2-M"; break;
+       case 0x03: p = "A2-BG"; break;
+       case 0x04: p = "A2-DK1"; break;
+       case 0x05: p = "A2-DK2"; break;
+       case 0x06: p = "A2-DK3"; break;
+       case 0x07: p = "A1 (6.0 MHz FM Mono)"; break;
+       case 0x08: p = "AM-L"; break;
+       case 0x09: p = "NICAM-BG"; break;
+       case 0x0a: p = "NICAM-DK"; break;
+       case 0x0b: p = "NICAM-I"; break;
+       case 0x0c: p = "NICAM-L"; break;
+       case 0x0d: p = "BTSC/EIAJ/A2-M Mono (4.5 MHz FMMono)"; break;
+       case 0xff: p = "no detected audio standard"; break;
+       default: p = "not defined";
+       }
+       CX18_INFO("Detected audio standard:   %s\n", p);
+       CX18_INFO("Audio muted:               %s\n",
+                   (mute_ctl & 0x2) ? "yes" : "no");
+       CX18_INFO("Audio microcontroller:     %s\n",
+                   (download_ctl & 0x10) ? "running" : "stopped");
+
+       switch (audio_config >> 4) {
+       case 0x00: p = "BTSC"; break;
+       case 0x01: p = "EIAJ"; break;
+       case 0x02: p = "A2-M"; break;
+       case 0x03: p = "A2-BG"; break;
+       case 0x04: p = "A2-DK1"; break;
+       case 0x05: p = "A2-DK2"; break;
+       case 0x06: p = "A2-DK3"; break;
+       case 0x07: p = "A1 (6.0 MHz FM Mono)"; break;
+       case 0x08: p = "AM-L"; break;
+       case 0x09: p = "NICAM-BG"; break;
+       case 0x0a: p = "NICAM-DK"; break;
+       case 0x0b: p = "NICAM-I"; break;
+       case 0x0c: p = "NICAM-L"; break;
+       case 0x0d: p = "FM radio"; break;
+       case 0x0f: p = "automatic detection"; break;
+       default: p = "undefined";
+       }
+       CX18_INFO("Configured audio standard: %s\n", p);
+
+       if ((audio_config >> 4) < 0xF) {
+               switch (audio_config & 0xF) {
+               case 0x00: p = "MONO1 (LANGUAGE A/Mono L+R channel for BTSC, EIAJ, A2)"; break;
+               case 0x01: p = "MONO2 (LANGUAGE B)"; break;
+               case 0x02: p = "MONO3 (STEREO forced MONO)"; break;
+               case 0x03: p = "MONO4 (NICAM ANALOG-Language C/Analog Fallback)"; break;
+               case 0x04: p = "STEREO"; break;
+               case 0x05: p = "DUAL1 (AB)"; break;
+               case 0x06: p = "DUAL2 (AC) (FM)"; break;
+               case 0x07: p = "DUAL3 (BC) (FM)"; break;
+               case 0x08: p = "DUAL4 (AC) (AM)"; break;
+               case 0x09: p = "DUAL5 (BC) (AM)"; break;
+               case 0x0a: p = "SAP"; break;
+               default: p = "undefined";
+               }
+               CX18_INFO("Configured audio mode:     %s\n", p);
+       } else {
+               switch (audio_config & 0xF) {
+               case 0x00: p = "BG"; break;
+               case 0x01: p = "DK1"; break;
+               case 0x02: p = "DK2"; break;
+               case 0x03: p = "DK3"; break;
+               case 0x04: p = "I"; break;
+               case 0x05: p = "L"; break;
+               case 0x06: p = "BTSC"; break;
+               case 0x07: p = "EIAJ"; break;
+               case 0x08: p = "A2-M"; break;
+               case 0x09: p = "FM Radio"; break;
+               case 0x0f: p = "automatic standard and mode detection"; break;
+               default: p = "undefined";
+               }
+               CX18_INFO("Configured audio system:   %s\n", p);
+       }
+
+       if (aud_input)
+               CX18_INFO("Specified audio input:     Tuner (In%d)\n",
+                               aud_input);
+       else
+               CX18_INFO("Specified audio input:     External\n");
+
+       switch (pref_mode & 0xf) {
+       case 0: p = "mono/language A"; break;
+       case 1: p = "language B"; break;
+       case 2: p = "language C"; break;
+       case 3: p = "analog fallback"; break;
+       case 4: p = "stereo"; break;
+       case 5: p = "language AC"; break;
+       case 6: p = "language BC"; break;
+       case 7: p = "language AB"; break;
+       default: p = "undefined";
+       }
+       CX18_INFO("Preferred audio mode:      %s\n", p);
+
+       if ((audio_config & 0xf) == 0xf) {
+               switch ((afc0 >> 2) & 0x1) {
+               case 0: p = "system DK"; break;
+               case 1: p = "system L"; break;
+               }
+               CX18_INFO("Selected 65 MHz format:    %s\n", p);
+
+               switch (afc0 & 0x3) {
+               case 0: p = "BTSC"; break;
+               case 1: p = "EIAJ"; break;
+               case 2: p = "A2-M"; break;
+               default: p = "undefined";
+               }
+               CX18_INFO("Selected 45 MHz format:    %s\n", p);
+       }
+}
diff --git a/drivers/media/video/cx18/cx18-av-core.h b/drivers/media/video/cx18/cx18-av-core.h
new file mode 100644 (file)
index 0000000..786901d
--- /dev/null
@@ -0,0 +1,318 @@
+/*
+ *  cx18 ADEC header
+ *
+ *  Derived from cx25840-core.h
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+#ifndef _CX18_AV_CORE_H_
+#define _CX18_AV_CORE_H_
+
+struct cx18;
+
+enum cx18_av_video_input {
+       /* Composite video inputs In1-In8 */
+       CX18_AV_COMPOSITE1 = 1,
+       CX18_AV_COMPOSITE2,
+       CX18_AV_COMPOSITE3,
+       CX18_AV_COMPOSITE4,
+       CX18_AV_COMPOSITE5,
+       CX18_AV_COMPOSITE6,
+       CX18_AV_COMPOSITE7,
+       CX18_AV_COMPOSITE8,
+
+       /* S-Video inputs consist of one luma input (In1-In4) ORed with one
+          chroma input (In5-In8) */
+       CX18_AV_SVIDEO_LUMA1 = 0x10,
+       CX18_AV_SVIDEO_LUMA2 = 0x20,
+       CX18_AV_SVIDEO_LUMA3 = 0x30,
+       CX18_AV_SVIDEO_LUMA4 = 0x40,
+       CX18_AV_SVIDEO_CHROMA4 = 0x400,
+       CX18_AV_SVIDEO_CHROMA5 = 0x500,
+       CX18_AV_SVIDEO_CHROMA6 = 0x600,
+       CX18_AV_SVIDEO_CHROMA7 = 0x700,
+       CX18_AV_SVIDEO_CHROMA8 = 0x800,
+
+       /* S-Video aliases for common luma/chroma combinations */
+       CX18_AV_SVIDEO1 = 0x510,
+       CX18_AV_SVIDEO2 = 0x620,
+       CX18_AV_SVIDEO3 = 0x730,
+       CX18_AV_SVIDEO4 = 0x840,
+};
+
+enum cx18_av_audio_input {
+       /* Audio inputs: serial or In4-In8 */
+       CX18_AV_AUDIO_SERIAL,
+       CX18_AV_AUDIO4 = 4,
+       CX18_AV_AUDIO5,
+       CX18_AV_AUDIO6,
+       CX18_AV_AUDIO7,
+       CX18_AV_AUDIO8,
+};
+
+struct cx18_av_state {
+       int radio;
+       v4l2_std_id std;
+       enum cx18_av_video_input vid_input;
+       enum cx18_av_audio_input aud_input;
+       u32 audclk_freq;
+       int audmode;
+       int vbi_line_offset;
+       u32 id;
+       u32 rev;
+       int is_initialized;
+};
+
+
+/* Registers */
+#define CXADEC_CHIP_TYPE_TIGER     0x837
+#define CXADEC_CHIP_TYPE_MAKO      0x843
+
+#define CXADEC_HOST_REG1           0x000
+#define CXADEC_HOST_REG2           0x001
+
+#define CXADEC_CHIP_CTRL           0x100
+#define CXADEC_AFE_CTRL            0x104
+#define CXADEC_PLL_CTRL1           0x108
+#define CXADEC_VID_PLL_FRAC        0x10C
+#define CXADEC_AUX_PLL_FRAC        0x110
+#define CXADEC_PIN_CTRL1           0x114
+#define CXADEC_PIN_CTRL2           0x118
+#define CXADEC_PIN_CFG1            0x11C
+#define CXADEC_PIN_CFG2            0x120
+
+#define CXADEC_PIN_CFG3            0x124
+#define CXADEC_I2S_MCLK            0x127
+
+#define CXADEC_AUD_LOCK1           0x128
+#define CXADEC_AUD_LOCK2           0x12C
+#define CXADEC_POWER_CTRL          0x130
+#define CXADEC_AFE_DIAG_CTRL1      0x134
+#define CXADEC_AFE_DIAG_CTRL2      0x138
+#define CXADEC_AFE_DIAG_CTRL3      0x13C
+#define CXADEC_PLL_DIAG_CTRL       0x140
+#define CXADEC_TEST_CTRL1          0x144
+#define CXADEC_TEST_CTRL2          0x148
+#define CXADEC_BIST_STAT           0x14C
+#define CXADEC_DLL1_DIAG_CTRL      0x158
+#define CXADEC_DLL2_DIAG_CTRL      0x15C
+
+/* IR registers */
+#define CXADEC_IR_CTRL_REG         0x200
+#define CXADEC_IR_TXCLK_REG        0x204
+#define CXADEC_IR_RXCLK_REG        0x208
+#define CXADEC_IR_CDUTY_REG        0x20C
+#define CXADEC_IR_STAT_REG         0x210
+#define CXADEC_IR_IRQEN_REG        0x214
+#define CXADEC_IR_FILTER_REG       0x218
+#define CXADEC_IR_FIFO_REG         0x21C
+
+/* Video Registers */
+#define CXADEC_MODE_CTRL           0x400
+#define CXADEC_OUT_CTRL1           0x404
+#define CXADEC_OUT_CTRL2           0x408
+#define CXADEC_GEN_STAT            0x40C
+#define CXADEC_INT_STAT_MASK       0x410
+#define CXADEC_LUMA_CTRL           0x414
+
+#define CXADEC_BRIGHTNESS_CTRL_BYTE 0x414
+#define CXADEC_CONTRAST_CTRL_BYTE  0x415
+#define CXADEC_LUMA_CTRL_BYTE_3    0x416
+
+#define CXADEC_HSCALE_CTRL         0x418
+#define CXADEC_VSCALE_CTRL         0x41C
+
+#define CXADEC_CHROMA_CTRL         0x420
+
+#define CXADEC_USAT_CTRL_BYTE      0x420
+#define CXADEC_VSAT_CTRL_BYTE      0x421
+#define CXADEC_HUE_CTRL_BYTE       0x422
+
+#define CXADEC_VBI_LINE_CTRL1      0x424
+#define CXADEC_VBI_LINE_CTRL2      0x428
+#define CXADEC_VBI_LINE_CTRL3      0x42C
+#define CXADEC_VBI_LINE_CTRL4      0x430
+#define CXADEC_VBI_LINE_CTRL5      0x434
+#define CXADEC_VBI_FC_CFG          0x438
+#define CXADEC_VBI_MISC_CFG1       0x43C
+#define CXADEC_VBI_MISC_CFG2       0x440
+#define CXADEC_VBI_PAY1            0x444
+#define CXADEC_VBI_PAY2            0x448
+#define CXADEC_VBI_CUST1_CFG1      0x44C
+#define CXADEC_VBI_CUST1_CFG2      0x450
+#define CXADEC_VBI_CUST1_CFG3      0x454
+#define CXADEC_VBI_CUST2_CFG1      0x458
+#define CXADEC_VBI_CUST2_CFG2      0x45C
+#define CXADEC_VBI_CUST2_CFG3      0x460
+#define CXADEC_VBI_CUST3_CFG1      0x464
+#define CXADEC_VBI_CUST3_CFG2      0x468
+#define CXADEC_VBI_CUST3_CFG3      0x46C
+#define CXADEC_HORIZ_TIM_CTRL      0x470
+#define CXADEC_VERT_TIM_CTRL       0x474
+#define CXADEC_SRC_COMB_CFG        0x478
+#define CXADEC_CHROMA_VBIOFF_CFG   0x47C
+#define CXADEC_FIELD_COUNT         0x480
+#define CXADEC_MISC_TIM_CTRL       0x484
+#define CXADEC_DFE_CTRL1           0x488
+#define CXADEC_DFE_CTRL2           0x48C
+#define CXADEC_DFE_CTRL3           0x490
+#define CXADEC_PLL_CTRL2           0x494
+#define CXADEC_HTL_CTRL            0x498
+#define CXADEC_COMB_CTRL           0x49C
+#define CXADEC_CRUSH_CTRL          0x4A0
+#define CXADEC_SOFT_RST_CTRL       0x4A4
+#define CXADEC_MV_DT_CTRL2         0x4A8
+#define CXADEC_MV_DT_CTRL3         0x4AC
+#define CXADEC_MISC_DIAG_CTRL      0x4B8
+
+#define CXADEC_DL_CTL              0x800
+#define CXADEC_DL_CTL_ADDRESS_LOW  0x800   /* Byte 1 in DL_CTL */
+#define CXADEC_DL_CTL_ADDRESS_HIGH 0x801   /* Byte 2 in DL_CTL */
+#define CXADEC_DL_CTL_DATA         0x802   /* Byte 3 in DL_CTL */
+#define CXADEC_DL_CTL_CONTROL      0x803   /* Byte 4 in DL_CTL */
+
+#define CXADEC_STD_DET_STATUS      0x804
+
+#define CXADEC_STD_DET_CTL         0x808
+#define CXADEC_STD_DET_CTL_AUD_CTL   0x808 /* Byte 1 in STD_DET_CTL */
+#define CXADEC_STD_DET_CTL_PREF_MODE 0x809 /* Byte 2 in STD_DET_CTL */
+
+#define CXADEC_DW8051_INT          0x80C
+#define CXADEC_GENERAL_CTL         0x810
+#define CXADEC_AAGC_CTL            0x814
+#define CXADEC_IF_SRC_CTL          0x818
+#define CXADEC_ANLOG_DEMOD_CTL     0x81C
+#define CXADEC_ROT_FREQ_CTL        0x820
+#define CXADEC_FM1_CTL             0x824
+#define CXADEC_PDF_CTL             0x828
+#define CXADEC_DFT1_CTL1           0x82C
+#define CXADEC_DFT1_CTL2           0x830
+#define CXADEC_DFT_STATUS          0x834
+#define CXADEC_DFT2_CTL1           0x838
+#define CXADEC_DFT2_CTL2           0x83C
+#define CXADEC_DFT2_STATUS         0x840
+#define CXADEC_DFT3_CTL1           0x844
+#define CXADEC_DFT3_CTL2           0x848
+#define CXADEC_DFT3_STATUS         0x84C
+#define CXADEC_DFT4_CTL1           0x850
+#define CXADEC_DFT4_CTL2           0x854
+#define CXADEC_DFT4_STATUS         0x858
+#define CXADEC_AM_MTS_DET          0x85C
+#define CXADEC_ANALOG_MUX_CTL      0x860
+#define CXADEC_DIG_PLL_CTL1        0x864
+#define CXADEC_DIG_PLL_CTL2        0x868
+#define CXADEC_DIG_PLL_CTL3        0x86C
+#define CXADEC_DIG_PLL_CTL4        0x870
+#define CXADEC_DIG_PLL_CTL5        0x874
+#define CXADEC_DEEMPH_GAIN_CTL     0x878
+#define CXADEC_DEEMPH_COEF1        0x87C
+#define CXADEC_DEEMPH_COEF2        0x880
+#define CXADEC_DBX1_CTL1           0x884
+#define CXADEC_DBX1_CTL2           0x888
+#define CXADEC_DBX1_STATUS         0x88C
+#define CXADEC_DBX2_CTL1           0x890
+#define CXADEC_DBX2_CTL2           0x894
+#define CXADEC_DBX2_STATUS         0x898
+#define CXADEC_AM_FM_DIFF          0x89C
+
+/* NICAM registers go here */
+#define CXADEC_NICAM_STATUS        0x8C8
+#define CXADEC_DEMATRIX_CTL        0x8CC
+
+#define CXADEC_PATH1_CTL1          0x8D0
+#define CXADEC_PATH1_VOL_CTL       0x8D4
+#define CXADEC_PATH1_EQ_CTL        0x8D8
+#define CXADEC_PATH1_SC_CTL        0x8DC
+
+#define CXADEC_PATH2_CTL1          0x8E0
+#define CXADEC_PATH2_VOL_CTL       0x8E4
+#define CXADEC_PATH2_EQ_CTL        0x8E8
+#define CXADEC_PATH2_SC_CTL        0x8EC
+
+#define CXADEC_SRC_CTL             0x8F0
+#define CXADEC_SRC_LF_COEF         0x8F4
+#define CXADEC_SRC1_CTL            0x8F8
+#define CXADEC_SRC2_CTL            0x8FC
+#define CXADEC_SRC3_CTL            0x900
+#define CXADEC_SRC4_CTL            0x904
+#define CXADEC_SRC5_CTL            0x908
+#define CXADEC_SRC6_CTL            0x90C
+
+#define CXADEC_BASEBAND_OUT_SEL    0x910
+#define CXADEC_I2S_IN_CTL          0x914
+#define CXADEC_I2S_OUT_CTL         0x918
+#define CXADEC_AC97_CTL            0x91C
+#define CXADEC_QAM_PDF             0x920
+#define CXADEC_QAM_CONST_DEC       0x924
+#define CXADEC_QAM_ROTATOR_FREQ    0x948
+
+/* Bit defintions / settings used in Mako Audio */
+#define CXADEC_PREF_MODE_MONO_LANGA        0
+#define CXADEC_PREF_MODE_MONO_LANGB        1
+#define CXADEC_PREF_MODE_MONO_LANGC        2
+#define CXADEC_PREF_MODE_FALLBACK          3
+#define CXADEC_PREF_MODE_STEREO            4
+#define CXADEC_PREF_MODE_DUAL_LANG_AC      5
+#define CXADEC_PREF_MODE_DUAL_LANG_BC      6
+#define CXADEC_PREF_MODE_DUAL_LANG_AB      7
+
+
+#define CXADEC_DETECT_STEREO               1
+#define CXADEC_DETECT_DUAL                 2
+#define CXADEC_DETECT_TRI                  4
+#define CXADEC_DETECT_SAP                  0x10
+#define CXADEC_DETECT_NO_SIGNAL            0xFF
+
+#define CXADEC_SELECT_AUDIO_STANDARD_BG    0xF0  /* NICAM BG and A2 BG */
+#define CXADEC_SELECT_AUDIO_STANDARD_DK1   0xF1  /* NICAM DK and A2 DK */
+#define CXADEC_SELECT_AUDIO_STANDARD_DK2   0xF2
+#define CXADEC_SELECT_AUDIO_STANDARD_DK3   0xF3
+#define CXADEC_SELECT_AUDIO_STANDARD_I     0xF4  /* NICAM I and A1 */
+#define CXADEC_SELECT_AUDIO_STANDARD_L     0xF5  /* NICAM L and System L AM */
+#define CXADEC_SELECT_AUDIO_STANDARD_BTSC  0xF6
+#define CXADEC_SELECT_AUDIO_STANDARD_EIAJ  0xF7
+#define CXADEC_SELECT_AUDIO_STANDARD_A2_M  0xF8  /* A2 M */
+#define CXADEC_SELECT_AUDIO_STANDARD_FM    0xF9  /* FM radio */
+#define CXADEC_SELECT_AUDIO_STANDARD_AUTO  0xFF  /* Auto detect */
+
+/* ----------------------------------------------------------------------- */
+/* cx18_av-core.c                                                         */
+int cx18_av_write(struct cx18 *cx, u16 addr, u8 value);
+int cx18_av_write4(struct cx18 *cx, u16 addr, u32 value);
+u8 cx18_av_read(struct cx18 *cx, u16 addr);
+u32 cx18_av_read4(struct cx18 *cx, u16 addr);
+int cx18_av_and_or(struct cx18 *cx, u16 addr, unsigned mask, u8 value);
+int cx18_av_and_or4(struct cx18 *cx, u16 addr, u32 mask, u32 value);
+int cx18_av_cmd(struct cx18 *cx, unsigned int cmd, void *arg);
+
+/* ----------------------------------------------------------------------- */
+/* cx18_av-firmware.c                                                      */
+int cx18_av_loadfw(struct cx18 *cx);
+
+/* ----------------------------------------------------------------------- */
+/* cx18_av-audio.c                                                         */
+int cx18_av_audio(struct cx18 *cx, unsigned int cmd, void *arg);
+void cx18_av_audio_set_path(struct cx18 *cx);
+
+/* ----------------------------------------------------------------------- */
+/* cx18_av-vbi.c                                                           */
+void cx18_av_vbi_setup(struct cx18 *cx);
+int cx18_av_vbi(struct cx18 *cx, unsigned int cmd, void *arg);
+
+#endif
diff --git a/drivers/media/video/cx18/cx18-av-firmware.c b/drivers/media/video/cx18/cx18-av-firmware.c
new file mode 100644 (file)
index 0000000..526e142
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ *  cx18 ADEC firmware functions
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+#include "cx18-driver.h"
+#include <linux/firmware.h>
+
+#define FWFILE "v4l-cx23418-dig.fw"
+
+int cx18_av_loadfw(struct cx18 *cx)
+{
+       const struct firmware *fw = NULL;
+       u32 size;
+       u32 v;
+       u8 *ptr;
+       int i;
+
+       if (request_firmware(&fw, FWFILE, &cx->dev->dev) != 0) {
+               CX18_ERR("unable to open firmware %s\n", FWFILE);
+               return -EINVAL;
+       }
+
+       cx18_av_write4(cx, CXADEC_CHIP_CTRL, 0x00010000);
+       cx18_av_write(cx, CXADEC_STD_DET_CTL, 0xf6); /* Byte 0 */
+
+       /* Reset the Mako core (Register is undocumented.) */
+       cx18_av_write4(cx, 0x8100, 0x00010000);
+
+       /* Put the 8051 in reset and enable firmware upload */
+       cx18_av_write4(cx, CXADEC_DL_CTL, 0x0F000000);
+
+       ptr = fw->data;
+       size = fw->size;
+
+       for (i = 0; i < size; i++) {
+               u32 dl_control = 0x0F000000 | ((u32)ptr[i] << 16);
+               u32 value = 0;
+               int retries;
+
+               for (retries = 0; retries < 5; retries++) {
+                       cx18_av_write4(cx, CXADEC_DL_CTL, dl_control);
+                       value = cx18_av_read4(cx, CXADEC_DL_CTL);
+                       if ((value & 0x3F00) == (dl_control & 0x3F00))
+                               break;
+               }
+               if (retries >= 5) {
+                       CX18_ERR("unable to load firmware %s\n", FWFILE);
+                       release_firmware(fw);
+                       return -EIO;
+               }
+       }
+
+       cx18_av_write4(cx, CXADEC_DL_CTL, 0x13000000 | fw->size);
+
+       /* Output to the 416 */
+       cx18_av_and_or4(cx, CXADEC_PIN_CTRL1, ~0, 0x78000);
+
+       /* Audio input control 1 set to Sony mode */
+       /* Audio output input 2 is 0 for slave operation input */
+       /* 0xC4000914[5]: 0 = left sample on WS=0, 1 = left sample on WS=1 */
+       /* 0xC4000914[7]: 0 = Philips mode, 1 = Sony mode (1st SCK rising edge
+          after WS transition for first bit of audio word. */
+       cx18_av_write4(cx, CXADEC_I2S_IN_CTL, 0x000000A0);
+
+       /* Audio output control 1 is set to Sony mode */
+       /* Audio output control 2 is set to 1 for master mode */
+       /* 0xC4000918[5]: 0 = left sample on WS=0, 1 = left sample on WS=1 */
+       /* 0xC4000918[7]: 0 = Philips mode, 1 = Sony mode (1st SCK rising edge
+          after WS transition for first bit of audio word. */
+       /* 0xC4000918[8]: 0 = slave operation, 1 = master (SCK_OUT and WS_OUT
+          are generated) */
+       cx18_av_write4(cx, CXADEC_I2S_OUT_CTL, 0x000001A0);
+
+       /* set alt I2s master clock to /16 and enable alt divider i2s
+          passthrough */
+       cx18_av_write4(cx, CXADEC_PIN_CFG3, 0x5000B687);
+
+       cx18_av_write4(cx, CXADEC_STD_DET_CTL, 0x000000F6);
+       /* CxDevWrReg(CXADEC_STD_DET_CTL, 0x000000FF); */
+
+       /* Set bit 0 in register 0x9CC to signify that this is MiniMe. */
+       /* Register 0x09CC is defined by the Merlin firmware, and doesn't
+          have a name in the spec. */
+       cx18_av_write4(cx, 0x09CC, 1);
+
+#define CX18_AUDIO_ENABLE              0xc72014
+       v = read_reg(CX18_AUDIO_ENABLE);
+       /* If bit 11 is 1 */
+       if (v & 0x800)
+               write_reg(v & 0xFFFFFBFF, CX18_AUDIO_ENABLE); /* Clear bit 10 */
+
+       /* Enable WW auto audio standard detection */
+       v = cx18_av_read4(cx, CXADEC_STD_DET_CTL);
+       v |= 0xFF;   /* Auto by default */
+       v |= 0x400;  /* Stereo by default */
+       v |= 0x14000000;
+       cx18_av_write4(cx, CXADEC_STD_DET_CTL, v);
+
+       release_firmware(fw);
+
+       CX18_INFO("loaded %s firmware (%d bytes)\n", FWFILE, size);
+       return 0;
+}
diff --git a/drivers/media/video/cx18/cx18-av-vbi.c b/drivers/media/video/cx18/cx18-av-vbi.c
new file mode 100644 (file)
index 0000000..d09f1da
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ *  cx18 ADEC VBI functions
+ *
+ *  Derived from cx25840-vbi.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301, USA.
+ */
+
+
+#include "cx18-driver.h"
+
+static int odd_parity(u8 c)
+{
+       c ^= (c >> 4);
+       c ^= (c >> 2);
+       c ^= (c >> 1);
+
+       return c & 1;
+}
+
+static int decode_vps(u8 *dst, u8 *p)
+{
+       static const u8 biphase_tbl[] = {
+               0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4,
+               0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0,
+               0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96,
+               0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2,
+               0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94,
+               0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0,
+               0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4,
+               0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0,
+               0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5,
+               0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1,
+               0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87,
+               0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3,
+               0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85,
+               0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1,
+               0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5,
+               0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1,
+               0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4,
+               0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0,
+               0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86,
+               0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2,
+               0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84,
+               0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0,
+               0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4,
+               0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0,
+               0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4,
+               0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0,
+               0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96,
+               0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2,
+               0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94,
+               0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0,
+               0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4,
+               0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0,
+       };
+
+       u8 c, err = 0;
+       int i;
+
+       for (i = 0; i < 2 * 13; i += 2) {
+               err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]];
+               c = (biphase_tbl[p[i + 1]] & 0xf) |
+                   ((biphase_tbl[p[i]] & 0xf) << 4);
+               dst[i / 2] = c;
+       }
+
+       return err & 0xf0;
+}
+
+void cx18_av_vbi_setup(struct cx18 *cx)
+{
+       struct cx18_av_state *state = &cx->av_state;
+       v4l2_std_id std = state->std;
+       int hblank, hactive, burst, vblank, vactive, sc;
+       int vblank656, src_decimation;
+       int luma_lpf, uv_lpf, comb;
+       u32 pll_int, pll_frac, pll_post;
+
+       /* datasheet startup, step 8d */
+       if (std & ~V4L2_STD_NTSC)
+               cx18_av_write(cx, 0x49f, 0x11);
+       else
+               cx18_av_write(cx, 0x49f, 0x14);
+
+       if (std & V4L2_STD_625_50) {
+               hblank = 0x084;
+               hactive = 0x2d0;
+               burst = 0x5d;
+               vblank = 0x024;
+               vactive = 0x244;
+               vblank656 = 0x28;
+               src_decimation = 0x21f;
+
+               luma_lpf = 2;
+               if (std & V4L2_STD_SECAM) {
+                       uv_lpf = 0;
+                       comb = 0;
+                       sc = 0x0a425f;
+               } else if (std == V4L2_STD_PAL_Nc) {
+                       uv_lpf = 1;
+                       comb = 0x20;
+                       sc = 556453;
+               } else {
+                       uv_lpf = 1;
+                       comb = 0x20;
+                       sc = 0x0a8263;
+               }
+       } else {
+               hactive = 720;
+               hblank = 122;
+               vactive = 487;
+               luma_lpf = 1;
+               uv_lpf = 1;
+
+               src_decimation = 0x21f;
+               if (std == V4L2_STD_PAL_60) {
+                       vblank = 26;
+                       vblank656 = 26;
+                       burst = 0x5b;
+                       luma_lpf = 2;
+                       comb = 0x20;
+                       sc = 0x0a8263;
+               } else if (std == V4L2_STD_PAL_M) {
+                       vblank = 20;
+                       vblank656 = 24;
+                       burst = 0x61;
+                       comb = 0x20;
+
+                       sc = 555452;
+               } else {
+                       vblank = 26;
+                       vblank656 = 26;
+                       burst = 0x5b;
+                       comb = 0x66;
+                       sc = 556063;
+               }
+       }
+
+       /* DEBUG: Displays configured PLL frequency */
+       pll_int = cx18_av_read(cx, 0x108);
+       pll_frac = cx18_av_read4(cx, 0x10c) & 0x1ffffff;
+       pll_post = cx18_av_read(cx, 0x109);
+       CX18_DEBUG_INFO("PLL regs = int: %u, frac: %u, post: %u\n",
+                       pll_int, pll_frac, pll_post);
+
+       if (pll_post) {
+               int fin, fsc;
+               int pll = 28636363L * ((((u64)pll_int) << 25) + pll_frac);
+
+               pll >>= 25;
+               pll /= pll_post;
+               CX18_DEBUG_INFO("PLL = %d.%06d MHz\n",
+                                       pll / 1000000, pll % 1000000);
+               CX18_DEBUG_INFO("PLL/8 = %d.%06d MHz\n",
+                                       pll / 8000000, (pll / 8) % 1000000);
+
+               fin = ((u64)src_decimation * pll) >> 12;
+               CX18_DEBUG_INFO("ADC Sampling freq = %d.%06d MHz\n",
+                                       fin / 1000000, fin % 1000000);
+
+               fsc = (((u64)sc) * pll) >> 24L;
+               CX18_DEBUG_INFO("Chroma sub-carrier freq = %d.%06d MHz\n",
+                                       fsc / 1000000, fsc % 1000000);
+
+               CX18_DEBUG_INFO("hblank %i, hactive %i, "
+                       "vblank %i , vactive %i, vblank656 %i, src_dec %i,"
+                       "burst 0x%02x, luma_lpf %i, uv_lpf %i, comb 0x%02x,"
+                       " sc 0x%06x\n",
+                       hblank, hactive, vblank, vactive, vblank656,
+                       src_decimation, burst, luma_lpf, uv_lpf, comb, sc);
+       }
+
+       /* Sets horizontal blanking delay and active lines */
+       cx18_av_write(cx, 0x470, hblank);
+       cx18_av_write(cx, 0x471, 0xff & (((hblank >> 8) & 0x3) |
+                                               (hactive << 4)));
+       cx18_av_write(cx, 0x472, hactive >> 4);
+
+       /* Sets burst gate delay */
+       cx18_av_write(cx, 0x473, burst);
+
+       /* Sets vertical blanking delay and active duration */
+       cx18_av_write(cx, 0x474, vblank);
+       cx18_av_write(cx, 0x475, 0xff & (((vblank >> 8) & 0x3) |
+                                               (vactive << 4)));
+       cx18_av_write(cx, 0x476, vactive >> 4);
+       cx18_av_write(cx, 0x477, vblank656);
+
+       /* Sets src decimation rate */
+       cx18_av_write(cx, 0x478, 0xff & src_decimation);
+       cx18_av_write(cx, 0x479, 0xff & (src_decimation >> 8));
+
+       /* Sets Luma and UV Low pass filters */
+       cx18_av_write(cx, 0x47a, luma_lpf << 6 | ((uv_lpf << 4) & 0x30));
+
+       /* Enables comb filters */
+       cx18_av_write(cx, 0x47b, comb);
+
+       /* Sets SC Step*/
+       cx18_av_write(cx, 0x47c, sc);
+       cx18_av_write(cx, 0x47d, 0xff & sc >> 8);
+       cx18_av_write(cx, 0x47e, 0xff & sc >> 16);
+
+       /* Sets VBI parameters */
+       if (std & V4L2_STD_625_50) {
+               cx18_av_write(cx, 0x47f, 0x01);
+               state->vbi_line_offset = 5;
+       } else {
+               cx18_av_write(cx, 0x47f, 0x00);
+               state->vbi_line_offset = 8;
+       }
+}
+
+int cx18_av_vbi(struct cx18 *cx, unsigned int cmd, void *arg)
+{
+       struct cx18_av_state *state = &cx->av_state;
+       struct v4l2_format *fmt;
+       struct v4l2_sliced_vbi_format *svbi;
+
+       switch (cmd) {
+       case VIDIOC_G_FMT:
+       {
+               static u16 lcr2vbi[] = {
+                       0, V4L2_SLICED_TELETEXT_B, 0,   /* 1 */
+                       0, V4L2_SLICED_WSS_625, 0,      /* 4 */
+                       V4L2_SLICED_CAPTION_525,        /* 6 */
+                       0, 0, V4L2_SLICED_VPS, 0, 0,    /* 9 */
+                       0, 0, 0, 0
+               };
+               int is_pal = !(state->std & V4L2_STD_525_60);
+               int i;
+
+               fmt = arg;
+               if (fmt->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE)
+                       return -EINVAL;
+               svbi = &fmt->fmt.sliced;
+               memset(svbi, 0, sizeof(*svbi));
+               /* we're done if raw VBI is active */
+               if ((cx18_av_read(cx, 0x404) & 0x10) == 0)
+                       break;
+
+               if (is_pal) {
+                       for (i = 7; i <= 23; i++) {
+                               u8 v = cx18_av_read(cx, 0x424 + i - 7);
+
+                               svbi->service_lines[0][i] = lcr2vbi[v >> 4];
+                               svbi->service_lines[1][i] = lcr2vbi[v & 0xf];
+                               svbi->service_set |= svbi->service_lines[0][i] |
+                                       svbi->service_lines[1][i];
+                       }
+               } else {
+                       for (i = 10; i <= 21; i++) {
+                               u8 v = cx18_av_read(cx, 0x424 + i - 10);
+
+                               svbi->service_lines[0][i] = lcr2vbi[v >> 4];
+                               svbi->service_lines[1][i] = lcr2vbi[v & 0xf];
+                               svbi->service_set |= svbi->service_lines[0][i] |
+                                       svbi->service_lines[1][i];
+                       }
+               }
+               break;
+       }
+
+       case VIDIOC_S_FMT:
+       {
+               int is_pal = !(state->std & V4L2_STD_525_60);
+               int vbi_offset = is_pal ? 1 : 0;
+               int i, x;
+               u8 lcr[24];
+
+               fmt = arg;
+               if (fmt->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE)
+                       return -EINVAL;
+               svbi = &fmt->fmt.sliced;
+               if (svbi->service_set == 0) {
+                       /* raw VBI */
+                       memset(svbi, 0, sizeof(*svbi));
+
+                       /* Setup VBI */
+                       cx18_av_vbi_setup(cx);
+
+                       /* VBI Offset */
+                       cx18_av_write(cx, 0x47f, vbi_offset);
+                       cx18_av_write(cx, 0x404, 0x2e);
+                       break;
+               }
+
+               for (x = 0; x <= 23; x++)
+                       lcr[x] = 0x00;
+
+               /* Setup VBI */
+               cx18_av_vbi_setup(cx);
+
+               /* Sliced VBI */
+               cx18_av_write(cx, 0x404, 0x32); /* Ancillary data */
+               cx18_av_write(cx, 0x406, 0x13);
+               cx18_av_write(cx, 0x47f, vbi_offset);
+
+               if (is_pal) {
+                       for (i = 0; i <= 6; i++)
+                               svbi->service_lines[0][i] =
+                                       svbi->service_lines[1][i] = 0;
+               } else {
+                       for (i = 0; i <= 9; i++)
+                               svbi->service_lines[0][i] =
+                                       svbi->service_lines[1][i] = 0;
+
+                       for (i = 22; i <= 23; i++)
+                               svbi->service_lines[0][i] =
+                                       svbi->service_lines[1][i] = 0;
+               }
+
+               for (i = 7; i <= 23; i++) {
+                       for (x = 0; x <= 1; x++) {
+                               switch (svbi->service_lines[1-x][i]) {
+                               case V4L2_SLICED_TELETEXT_B:
+                                       lcr[i] |= 1 << (4 * x);
+                                       break;
+                               case V4L2_SLICED_WSS_625:
+                                       lcr[i] |= 4 << (4 * x);
+                                       break;
+                               case V4L2_SLICED_CAPTION_525:
+                                       lcr[i] |= 6 << (4 * x);
+                                       break;
+                               case V4L2_SLICED_VPS:
+                                       lcr[i] |= 9 << (4 * x);
+                                       break;
+                               }
+                       }
+               }
+
+               if (is_pal) {
+                       for (x = 1, i = 0x424; i <= 0x434; i++, x++)
+                               cx18_av_write(cx, i, lcr[6 + x]);
+               } else {
+                       for (x = 1, i = 0x424; i <= 0x430; i++, x++)
+                               cx18_av_write(cx, i, lcr[9 + x]);
+                       for (i = 0x431; i <= 0x434; i++)
+                               cx18_av_write(cx, i, 0);
+               }
+
+               cx18_av_write(cx, 0x43c, 0x16);
+               cx18_av_write(cx, 0x474, is_pal ? 0x2a : 0x22);
+               break;
+       }
+
+       case VIDIOC_INT_DECODE_VBI_LINE:
+       {
+               struct v4l2_decode_vbi_line *vbi = arg;
+               u8 *p = vbi->p;
+               int id1, id2, l, err = 0;
+
+               if (p[0] || p[1] != 0xff || p[2] != 0xff ||
+                   (p[3] != 0x55 && p[3] != 0x91)) {
+                       vbi->line = vbi->type = 0;
+                       break;
+               }
+
+               p += 4;
+               id1 = p[-1];
+               id2 = p[0] & 0xf;
+               l = p[2] & 0x3f;
+               l += state->vbi_line_offset;
+               p += 4;
+
+               switch (id2) {
+               case 1:
+                       id2 = V4L2_SLICED_TELETEXT_B;
+                       break;
+               case 4:
+                       id2 = V4L2_SLICED_WSS_625;
+                       break;
+               case 6:
+                       id2 = V4L2_SLICED_CAPTION_525;
+                       err = !odd_parity(p[0]) || !odd_parity(p[1]);
+                       break;
+               case 9:
+                       id2 = V4L2_SLICED_VPS;
+                       if (decode_vps(p, p) != 0)
+                               err = 1;
+                       break;
+               default:
+                       id2 = 0;
+                       err = 1;
+                       break;
+               }
+
+               vbi->type = err ? 0 : id2;
+               vbi->line = err ? 0 : l;
+               vbi->is_second_field = err ? 0 : (id1 == 0x55);
+               vbi->p = p;
+               break;
+       }
+       }
+
+       return 0;
+}
diff --git a/drivers/media/video/cx18/cx18-cards.c b/drivers/media/video/cx18/cx18-cards.c
new file mode 100644 (file)
index 0000000..f5e3ba1
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ *  cx18 functions to query card hardware
+ *
+ *  Derived from ivtv-cards.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include "cx18-driver.h"
+#include "cx18-cards.h"
+#include "cx18-i2c.h"
+#include <media/cs5345.h>
+
+/********************** card configuration *******************************/
+
+/* usual i2c tuner addresses to probe */
+static struct cx18_card_tuner_i2c cx18_i2c_std = {
+       .radio = { I2C_CLIENT_END },
+       .demod = { 0x43, I2C_CLIENT_END },
+       .tv    = { 0x61, 0x60, I2C_CLIENT_END },
+};
+
+/* Please add new PCI IDs to: http://pci-ids.ucw.cz/iii
+   This keeps the PCI ID database up to date. Note that the entries
+   must be added under vendor 0x4444 (Conexant) as subsystem IDs.
+   New vendor IDs should still be added to the vendor ID list. */
+
+/* Hauppauge HVR-1600 cards */
+
+/* Note: for Hauppauge cards the tveeprom information is used instead
+   of PCI IDs */
+static const struct cx18_card cx18_card_hvr1600_esmt = {
+       .type = CX18_CARD_HVR_1600_ESMT,
+       .name = "Hauppauge HVR-1600",
+       .comment = "DVB & VBI are not yet supported\n",
+       .v4l2_capabilities = CX18_CAP_ENCODER,
+       .hw_audio_ctrl = CX18_HW_CX23418,
+       .hw_muxer = CX18_HW_CS5345,
+       .hw_all = CX18_HW_TVEEPROM | CX18_HW_TUNER | CX18_HW_CS5345,
+       .video_inputs = {
+               { CX18_CARD_INPUT_VID_TUNER,  0, CX23418_COMPOSITE7 },
+               { CX18_CARD_INPUT_SVIDEO1,    1, CX23418_SVIDEO1    },
+               { CX18_CARD_INPUT_COMPOSITE1, 1, CX23418_COMPOSITE3 },
+               { CX18_CARD_INPUT_SVIDEO2,    2, CX23418_SVIDEO2    },
+               { CX18_CARD_INPUT_COMPOSITE2, 2, CX23418_COMPOSITE4 },
+       },
+       .audio_inputs = {
+               { CX18_CARD_INPUT_AUD_TUNER,
+                 CX23418_AUDIO8, CS5345_IN_1 | CS5345_MCLK_1_5 },
+               { CX18_CARD_INPUT_LINE_IN1,
+                 CX23418_AUDIO_SERIAL, CS5345_IN_2 },
+               { CX18_CARD_INPUT_LINE_IN2,
+                 CX23418_AUDIO_SERIAL, CS5345_IN_2 },
+       },
+       .radio_input = { CX18_CARD_INPUT_AUD_TUNER,
+                        CX23418_AUDIO_SERIAL, 0 },
+       .ddr = {
+               /* ESMT M13S128324A-5B memory */
+               .chip_config = 0x003,
+               .refresh = 0x30c,
+               .timing1 = 0x44220e82,
+               .timing2 = 0x08,
+               .tune_lane = 0,
+               .initial_emrs = 0,
+       },
+       .gpio_init.initial_value = 0x3001,
+       .gpio_init.direction = 0x3001,
+       .i2c = &cx18_i2c_std,
+};
+
+static const struct cx18_card cx18_card_hvr1600_samsung = {
+       .type = CX18_CARD_HVR_1600_SAMSUNG,
+       .name = "Hauppauge HVR-1600 (Preproduction)",
+       .comment = "DVB & VBI are not yet supported\n",
+       .v4l2_capabilities = CX18_CAP_ENCODER,
+       .hw_audio_ctrl = CX18_HW_CX23418,
+       .hw_muxer = CX18_HW_CS5345,
+       .hw_all = CX18_HW_TVEEPROM | CX18_HW_TUNER | CX18_HW_CS5345,
+       .video_inputs = {
+               { CX18_CARD_INPUT_VID_TUNER,  0, CX23418_COMPOSITE7 },
+               { CX18_CARD_INPUT_SVIDEO1,    1, CX23418_SVIDEO1    },
+               { CX18_CARD_INPUT_COMPOSITE1, 1, CX23418_COMPOSITE3 },
+               { CX18_CARD_INPUT_SVIDEO2,    2, CX23418_SVIDEO2    },
+               { CX18_CARD_INPUT_COMPOSITE2, 2, CX23418_COMPOSITE4 },
+       },
+       .audio_inputs = {
+               { CX18_CARD_INPUT_AUD_TUNER,
+                 CX23418_AUDIO8, CS5345_IN_1 | CS5345_MCLK_1_5 },
+               { CX18_CARD_INPUT_LINE_IN1,
+                 CX23418_AUDIO_SERIAL, CS5345_IN_2 },
+               { CX18_CARD_INPUT_LINE_IN2,
+                 CX23418_AUDIO_SERIAL, CS5345_IN_2 },
+       },
+       .radio_input = { CX18_CARD_INPUT_AUD_TUNER,
+                        CX23418_AUDIO_SERIAL, 0 },
+       .ddr = {
+               /* Samsung K4D263238G-VC33 memory */
+               .chip_config = 0x003,
+               .refresh = 0x30c,
+               .timing1 = 0x23230b73,
+               .timing2 = 0x08,
+               .tune_lane = 0,
+               .initial_emrs = 2,
+       },
+       .gpio_init.initial_value = 0x3001,
+       .gpio_init.direction = 0x3001,
+       .i2c = &cx18_i2c_std,
+};
+
+/* ------------------------------------------------------------------------- */
+
+/* Compro VideoMate H900: not working at the moment! */
+
+static const struct cx18_card_pci_info cx18_pci_h900[] = {
+       { PCI_DEVICE_ID_CX23418, CX18_PCI_ID_COMPRO, 0xe100 },
+       { 0, 0, 0 }
+};
+
+static const struct cx18_card cx18_card_h900 = {
+       .type = CX18_CARD_COMPRO_H900,
+       .name = "Compro VideoMate H900",
+       .comment = "Not yet supported!\n",
+       .v4l2_capabilities = 0,
+       .hw_audio_ctrl = CX18_HW_CX23418,
+       .hw_all = CX18_HW_TUNER,
+       .video_inputs = {
+               { CX18_CARD_INPUT_VID_TUNER,  0, CX23418_COMPOSITE7 },
+               { CX18_CARD_INPUT_SVIDEO1,    1, CX23418_SVIDEO1    },
+               { CX18_CARD_INPUT_COMPOSITE1, 1, CX23418_COMPOSITE3 },
+       },
+       .audio_inputs = {
+               { CX18_CARD_INPUT_AUD_TUNER,
+                 CX23418_AUDIO8, 0 },
+               { CX18_CARD_INPUT_LINE_IN1,
+                 CX23418_AUDIO_SERIAL, 0 },
+       },
+       .radio_input = { CX18_CARD_INPUT_AUD_TUNER,
+                        CX23418_AUDIO_SERIAL, 0 },
+       .tuners = {
+               { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 },
+       },
+       .ddr = {
+               /* EtronTech EM6A9160TS-5G memory */
+               .chip_config = 0x50003,
+               .refresh = 0x753,
+               .timing1 = 0x24330e84,
+               .timing2 = 0x1f,
+               .tune_lane = 0,
+               .initial_emrs = 0,
+       },
+       .pci_list = cx18_pci_h900,
+       .i2c = &cx18_i2c_std,
+};
+
+/* ------------------------------------------------------------------------- */
+
+/* Yuan MPC718: not working at the moment! */
+
+static const struct cx18_card_pci_info cx18_pci_mpc718[] = {
+       { PCI_DEVICE_ID_CX23418, CX18_PCI_ID_YUAN, 0x0718 },
+       { 0, 0, 0 }
+};
+
+static const struct cx18_card cx18_card_mpc718 = {
+       .type = CX18_CARD_YUAN_MPC718,
+       .name = "Yuan MPC718",
+       .comment = "Not yet supported!\n",
+       .v4l2_capabilities = 0,
+       .hw_audio_ctrl = CX18_HW_CX23418,
+       .hw_all = CX18_HW_TUNER,
+       .video_inputs = {
+               { CX18_CARD_INPUT_VID_TUNER,  0, CX23418_COMPOSITE7 },
+               { CX18_CARD_INPUT_SVIDEO1,    1, CX23418_SVIDEO1    },
+               { CX18_CARD_INPUT_COMPOSITE1, 1, CX23418_COMPOSITE3 },
+       },
+       .audio_inputs = {
+               { CX18_CARD_INPUT_AUD_TUNER,
+                 CX23418_AUDIO8, 0 },
+               { CX18_CARD_INPUT_LINE_IN1,
+                 CX23418_AUDIO_SERIAL, 0 },
+       },
+       .radio_input = { CX18_CARD_INPUT_AUD_TUNER,
+                        CX23418_AUDIO_SERIAL, 0 },
+       .tuners = {
+               /* XC3028 tuner */
+               { .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 },
+       },
+       /* tuner reset */
+       .gpio_init = { .direction = 0x1000, .initial_value = 0x1000 },
+       .ddr = {
+               /* Probably Samsung K4D263238G-VC33 memory */
+               .chip_config = 0x003,
+               .refresh = 0x30c,
+               .timing1 = 0x23230b73,
+               .timing2 = 0x08,
+               .tune_lane = 0,
+               .initial_emrs = 2,
+       },
+       .pci_list = cx18_pci_mpc718,
+       .i2c = &cx18_i2c_std,
+};
+
+static const struct cx18_card *cx18_card_list[] = {
+       &cx18_card_hvr1600_esmt,
+       &cx18_card_hvr1600_samsung,
+       &cx18_card_h900,
+       &cx18_card_mpc718,
+};
+
+const struct cx18_card *cx18_get_card(u16 index)
+{
+       if (index >= ARRAY_SIZE(cx18_card_list))
+               return NULL;
+       return cx18_card_list[index];
+}
+
+int cx18_get_input(struct cx18 *cx, u16 index, struct v4l2_input *input)
+{
+       const struct cx18_card_video_input *card_input =
+               cx->card->video_inputs + index;
+       static const char * const input_strs[] = {
+               "Tuner 1",
+               "S-Video 1",
+               "S-Video 2",
+               "Composite 1",
+               "Composite 2",
+               "Composite 3"
+       };
+
+       memset(input, 0, sizeof(*input));
+       if (index >= cx->nof_inputs)
+               return -EINVAL;
+       input->index = index;
+       strlcpy(input->name, input_strs[card_input->video_type - 1],
+                       sizeof(input->name));
+       input->type = (card_input->video_type == CX18_CARD_INPUT_VID_TUNER ?
+                       V4L2_INPUT_TYPE_TUNER : V4L2_INPUT_TYPE_CAMERA);
+       input->audioset = (1 << cx->nof_audio_inputs) - 1;
+       input->std = (input->type == V4L2_INPUT_TYPE_TUNER) ?
+                               cx->tuner_std : V4L2_STD_ALL;
+       return 0;
+}
+
+int cx18_get_audio_input(struct cx18 *cx, u16 index, struct v4l2_audio *audio)
+{
+       const struct cx18_card_audio_input *aud_input =
+               cx->card->audio_inputs + index;
+       static const char * const input_strs[] = {
+               "Tuner 1",
+               "Line In 1",
+               "Line In 2"
+       };
+
+       memset(audio, 0, sizeof(*audio));
+       if (index >= cx->nof_audio_inputs)
+               return -EINVAL;
+       strlcpy(audio->name, input_strs[aud_input->audio_type - 1],
+                       sizeof(audio->name));
+       audio->index = index;
+       audio->capability = V4L2_AUDCAP_STEREO;
+       return 0;
+}
diff --git a/drivers/media/video/cx18/cx18-cards.h b/drivers/media/video/cx18/cx18-cards.h
new file mode 100644 (file)
index 0000000..bca249b
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ *  cx18 functions to query card hardware
+ *
+ *  Derived from ivtv-cards.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+/* hardware flags */
+#define CX18_HW_TUNER     (1 << 0)
+#define CX18_HW_TVEEPROM  (1 << 1)
+#define CX18_HW_CS5345    (1 << 2)
+#define CX18_HW_GPIO      (1 << 3)
+#define CX18_HW_CX23418   (1 << 4)
+#define CX18_HW_DVB      (1 << 5)
+
+/* video inputs */
+#define        CX18_CARD_INPUT_VID_TUNER       1
+#define        CX18_CARD_INPUT_SVIDEO1         2
+#define        CX18_CARD_INPUT_SVIDEO2         3
+#define        CX18_CARD_INPUT_COMPOSITE1      4
+#define        CX18_CARD_INPUT_COMPOSITE2      5
+#define        CX18_CARD_INPUT_COMPOSITE3      6
+
+enum cx34180_video_input {
+       /* Composite video inputs In1-In8 */
+       CX23418_COMPOSITE1 = 1,
+       CX23418_COMPOSITE2,
+       CX23418_COMPOSITE3,
+       CX23418_COMPOSITE4,
+       CX23418_COMPOSITE5,
+       CX23418_COMPOSITE6,
+       CX23418_COMPOSITE7,
+       CX23418_COMPOSITE8,
+
+       /* S-Video inputs consist of one luma input (In1-In4) ORed with one
+          chroma input (In5-In8) */
+       CX23418_SVIDEO_LUMA1 = 0x10,
+       CX23418_SVIDEO_LUMA2 = 0x20,
+       CX23418_SVIDEO_LUMA3 = 0x30,
+       CX23418_SVIDEO_LUMA4 = 0x40,
+       CX23418_SVIDEO_CHROMA4 = 0x400,
+       CX23418_SVIDEO_CHROMA5 = 0x500,
+       CX23418_SVIDEO_CHROMA6 = 0x600,
+       CX23418_SVIDEO_CHROMA7 = 0x700,
+       CX23418_SVIDEO_CHROMA8 = 0x800,
+
+       /* S-Video aliases for common luma/chroma combinations */
+       CX23418_SVIDEO1 = 0x510,
+       CX23418_SVIDEO2 = 0x620,
+       CX23418_SVIDEO3 = 0x730,
+       CX23418_SVIDEO4 = 0x840,
+};
+
+/* audio inputs */
+#define        CX18_CARD_INPUT_AUD_TUNER       1
+#define        CX18_CARD_INPUT_LINE_IN1        2
+#define        CX18_CARD_INPUT_LINE_IN2        3
+
+#define CX18_CARD_MAX_VIDEO_INPUTS 6
+#define CX18_CARD_MAX_AUDIO_INPUTS 3
+#define CX18_CARD_MAX_TUNERS      2
+
+enum cx23418_audio_input {
+       /* Audio inputs: serial or In4-In8 */
+       CX23418_AUDIO_SERIAL,
+       CX23418_AUDIO4 = 4,
+       CX23418_AUDIO5,
+       CX23418_AUDIO6,
+       CX23418_AUDIO7,
+       CX23418_AUDIO8,
+};
+
+/* V4L2 capability aliases */
+#define CX18_CAP_ENCODER (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | \
+                         V4L2_CAP_AUDIO | V4L2_CAP_READWRITE)
+/* | V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE) not yet */
+
+struct cx18_card_video_input {
+       u8  video_type;         /* video input type */
+       u8  audio_index;        /* index in cx18_card_audio_input array */
+       u16 video_input;        /* hardware video input */
+};
+
+struct cx18_card_audio_input {
+       u8  audio_type;         /* audio input type */
+       u32 audio_input;        /* hardware audio input */
+       u16 muxer_input;        /* hardware muxer input for boards with a
+                                  multiplexer chip */
+};
+
+struct cx18_card_pci_info {
+       u16 device;
+       u16 subsystem_vendor;
+       u16 subsystem_device;
+};
+
+/* GPIO definitions */
+
+/* The mask is the set of bits used by the operation */
+
+struct cx18_gpio_init { /* set initial GPIO DIR and OUT values */
+       u16 direction;  /* DIR setting. Leave to 0 if no init is needed */
+       u16 initial_value;
+};
+
+struct cx18_card_tuner {
+       v4l2_std_id std;        /* standard for which the tuner is suitable */
+       int         tuner;      /* tuner ID (from tuner.h) */
+};
+
+struct cx18_card_tuner_i2c {
+       unsigned short radio[2];/* radio tuner i2c address to probe */
+       unsigned short demod[2];/* demodulator i2c address to probe */
+       unsigned short tv[4];   /* tv tuner i2c addresses to probe */
+};
+
+struct cx18_ddr {              /* DDR config data */
+       u32 chip_config;
+       u32 refresh;
+       u32 timing1;
+       u32 timing2;
+       u32 tune_lane;
+       u32 initial_emrs;
+};
+
+/* for card information/parameters */
+struct cx18_card {
+       int type;
+       char *name;
+       char *comment;
+       u32 v4l2_capabilities;
+       u32 hw_audio_ctrl;      /* hardware used for the V4L2 controls (only
+                                  1 dev allowed) */
+       u32 hw_muxer;           /* hardware used to multiplex audio input */
+       u32 hw_all;             /* all hardware used by the board */
+       struct cx18_card_video_input video_inputs[CX18_CARD_MAX_VIDEO_INPUTS];
+       struct cx18_card_audio_input audio_inputs[CX18_CARD_MAX_AUDIO_INPUTS];
+       struct cx18_card_audio_input radio_input;
+
+       /* GPIO card-specific settings */
+       struct cx18_gpio_init           gpio_init;
+
+       struct cx18_card_tuner tuners[CX18_CARD_MAX_TUNERS];
+       struct cx18_card_tuner_i2c *i2c;
+
+       struct cx18_ddr ddr;
+
+       /* list of device and subsystem vendor/devices that
+          correspond to this card type. */
+       const struct cx18_card_pci_info *pci_list;
+};
+
+int cx18_get_input(struct cx18 *cx, u16 index, struct v4l2_input *input);
+int cx18_get_audio_input(struct cx18 *cx, u16 index, struct v4l2_audio *input);
+const struct cx18_card *cx18_get_card(u16 index);
diff --git a/drivers/media/video/cx18/cx18-controls.c b/drivers/media/video/cx18/cx18-controls.c
new file mode 100644 (file)
index 0000000..da299ae
--- /dev/null
@@ -0,0 +1,306 @@
+/*
+ *  cx18 ioctl control functions
+ *
+ *  Derived from ivtv-controls.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include "cx18-driver.h"
+#include "cx18-av-core.h"
+#include "cx18-cards.h"
+#include "cx18-ioctl.h"
+#include "cx18-audio.h"
+#include "cx18-i2c.h"
+#include "cx18-mailbox.h"
+#include "cx18-controls.h"
+
+static const u32 user_ctrls[] = {
+       V4L2_CID_USER_CLASS,
+       V4L2_CID_BRIGHTNESS,
+       V4L2_CID_CONTRAST,
+       V4L2_CID_SATURATION,
+       V4L2_CID_HUE,
+       V4L2_CID_AUDIO_VOLUME,
+       V4L2_CID_AUDIO_BALANCE,
+       V4L2_CID_AUDIO_BASS,
+       V4L2_CID_AUDIO_TREBLE,
+       V4L2_CID_AUDIO_MUTE,
+       V4L2_CID_AUDIO_LOUDNESS,
+       0
+};
+
+static const u32 *ctrl_classes[] = {
+       user_ctrls,
+       cx2341x_mpeg_ctrls,
+       NULL
+};
+
+static int cx18_queryctrl(struct cx18 *cx, struct v4l2_queryctrl *qctrl)
+{
+       const char *name;
+
+       CX18_DEBUG_IOCTL("VIDIOC_QUERYCTRL(%08x)\n", qctrl->id);
+
+       qctrl->id = v4l2_ctrl_next(ctrl_classes, qctrl->id);
+       if (qctrl->id == 0)
+               return -EINVAL;
+
+       switch (qctrl->id) {
+       /* Standard V4L2 controls */
+       case V4L2_CID_BRIGHTNESS:
+       case V4L2_CID_HUE:
+       case V4L2_CID_SATURATION:
+       case V4L2_CID_CONTRAST:
+               if (cx18_av_cmd(cx, VIDIOC_QUERYCTRL, qctrl))
+                       qctrl->flags |= V4L2_CTRL_FLAG_DISABLED;
+               return 0;
+
+       case V4L2_CID_AUDIO_VOLUME:
+       case V4L2_CID_AUDIO_MUTE:
+       case V4L2_CID_AUDIO_BALANCE:
+       case V4L2_CID_AUDIO_BASS:
+       case V4L2_CID_AUDIO_TREBLE:
+       case V4L2_CID_AUDIO_LOUDNESS:
+               if (cx18_i2c_hw(cx, cx->card->hw_audio_ctrl, VIDIOC_QUERYCTRL, qctrl))
+                       qctrl->flags |= V4L2_CTRL_FLAG_DISABLED;
+               return 0;
+
+       default:
+               if (cx2341x_ctrl_query(&cx->params, qctrl))
+                       qctrl->flags |= V4L2_CTRL_FLAG_DISABLED;
+               return 0;
+       }
+       strncpy(qctrl->name, name, sizeof(qctrl->name) - 1);
+       qctrl->name[sizeof(qctrl->name) - 1] = 0;
+       return 0;
+}
+
+static int cx18_querymenu(struct cx18 *cx, struct v4l2_querymenu *qmenu)
+{
+       struct v4l2_queryctrl qctrl;
+
+       qctrl.id = qmenu->id;
+       cx18_queryctrl(cx, &qctrl);
+       return v4l2_ctrl_query_menu(qmenu, &qctrl, cx2341x_ctrl_get_menu(qmenu->id));
+}
+
+static int cx18_s_ctrl(struct cx18 *cx, struct v4l2_control *vctrl)
+{
+       s32 v = vctrl->value;
+
+       CX18_DEBUG_IOCTL("VIDIOC_S_CTRL(%08x, %x)\n", vctrl->id, v);
+
+       switch (vctrl->id) {
+               /* Standard V4L2 controls */
+       case V4L2_CID_BRIGHTNESS:
+       case V4L2_CID_HUE:
+       case V4L2_CID_SATURATION:
+       case V4L2_CID_CONTRAST:
+               return cx18_av_cmd(cx, VIDIOC_S_CTRL, vctrl);
+
+       case V4L2_CID_AUDIO_VOLUME:
+       case V4L2_CID_AUDIO_MUTE:
+       case V4L2_CID_AUDIO_BALANCE:
+       case V4L2_CID_AUDIO_BASS:
+       case V4L2_CID_AUDIO_TREBLE:
+       case V4L2_CID_AUDIO_LOUDNESS:
+               return cx18_i2c_hw(cx, cx->card->hw_audio_ctrl, VIDIOC_S_CTRL, vctrl);
+
+       default:
+               CX18_DEBUG_IOCTL("invalid control %x\n", vctrl->id);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int cx18_g_ctrl(struct cx18 *cx, struct v4l2_control *vctrl)
+{
+       CX18_DEBUG_IOCTL("VIDIOC_G_CTRL(%08x)\n", vctrl->id);
+
+       switch (vctrl->id) {
+               /* Standard V4L2 controls */
+       case V4L2_CID_BRIGHTNESS:
+       case V4L2_CID_HUE:
+       case V4L2_CID_SATURATION:
+       case V4L2_CID_CONTRAST:
+               return cx18_av_cmd(cx, VIDIOC_G_CTRL, vctrl);
+
+       case V4L2_CID_AUDIO_VOLUME:
+       case V4L2_CID_AUDIO_MUTE:
+       case V4L2_CID_AUDIO_BALANCE:
+       case V4L2_CID_AUDIO_BASS:
+       case V4L2_CID_AUDIO_TREBLE:
+       case V4L2_CID_AUDIO_LOUDNESS:
+               return cx18_i2c_hw(cx, cx->card->hw_audio_ctrl, VIDIOC_G_CTRL, vctrl);
+       default:
+               CX18_DEBUG_IOCTL("invalid control %x\n", vctrl->id);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int cx18_setup_vbi_fmt(struct cx18 *cx, enum v4l2_mpeg_stream_vbi_fmt fmt)
+{
+       if (!(cx->v4l2_cap & V4L2_CAP_SLICED_VBI_CAPTURE))
+               return -EINVAL;
+       if (atomic_read(&cx->capturing) > 0)
+               return -EBUSY;
+
+       /* First try to allocate sliced VBI buffers if needed. */
+       if (fmt && cx->vbi.sliced_mpeg_data[0] == NULL) {
+               int i;
+
+               for (i = 0; i < CX18_VBI_FRAMES; i++) {
+                       /* Yuck, hardcoded. Needs to be a define */
+                       cx->vbi.sliced_mpeg_data[i] = kmalloc(2049, GFP_KERNEL);
+                       if (cx->vbi.sliced_mpeg_data[i] == NULL) {
+                               while (--i >= 0) {
+                                       kfree(cx->vbi.sliced_mpeg_data[i]);
+                                       cx->vbi.sliced_mpeg_data[i] = NULL;
+                               }
+                               return -ENOMEM;
+                       }
+               }
+       }
+
+       cx->vbi.insert_mpeg = fmt;
+
+       if (cx->vbi.insert_mpeg == 0)
+               return 0;
+       /* Need sliced data for mpeg insertion */
+       if (get_service_set(cx->vbi.sliced_in) == 0) {
+               if (cx->is_60hz)
+                       cx->vbi.sliced_in->service_set = V4L2_SLICED_CAPTION_525;
+               else
+                       cx->vbi.sliced_in->service_set = V4L2_SLICED_WSS_625;
+               expand_service_set(cx->vbi.sliced_in, cx->is_50hz);
+       }
+       return 0;
+}
+
+int cx18_control_ioctls(struct cx18 *cx, unsigned int cmd, void *arg)
+{
+       struct v4l2_control ctrl;
+
+       switch (cmd) {
+       case VIDIOC_QUERYMENU:
+               CX18_DEBUG_IOCTL("VIDIOC_QUERYMENU\n");
+               return cx18_querymenu(cx, arg);
+
+       case VIDIOC_QUERYCTRL:
+               return cx18_queryctrl(cx, arg);
+
+       case VIDIOC_S_CTRL:
+               return cx18_s_ctrl(cx, arg);
+
+       case VIDIOC_G_CTRL:
+               return cx18_g_ctrl(cx, arg);
+
+       case VIDIOC_S_EXT_CTRLS:
+       {
+               struct v4l2_ext_controls *c = arg;
+
+               if (c->ctrl_class == V4L2_CTRL_CLASS_USER) {
+                       int i;
+                       int err = 0;
+
+                       for (i = 0; i < c->count; i++) {
+                               ctrl.id = c->controls[i].id;
+                               ctrl.value = c->controls[i].value;
+                               err = cx18_s_ctrl(cx, &ctrl);
+                               c->controls[i].value = ctrl.value;
+                               if (err) {
+                                       c->error_idx = i;
+                                       break;
+                               }
+                       }
+                       return err;
+               }
+               CX18_DEBUG_IOCTL("VIDIOC_S_EXT_CTRLS\n");
+               if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) {
+                       struct cx2341x_mpeg_params p = cx->params;
+                       int err = cx2341x_ext_ctrls(&p, atomic_read(&cx->capturing), arg, cmd);
+
+                       if (err)
+                               return err;
+
+                       if (p.video_encoding != cx->params.video_encoding) {
+                               int is_mpeg1 = p.video_encoding ==
+                                               V4L2_MPEG_VIDEO_ENCODING_MPEG_1;
+                               struct v4l2_format fmt;
+
+                               /* fix videodecoder resolution */
+                               fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+                               fmt.fmt.pix.width = cx->params.width / (is_mpeg1 ? 2 : 1);
+                               fmt.fmt.pix.height = cx->params.height;
+                               cx18_av_cmd(cx, VIDIOC_S_FMT, &fmt);
+                       }
+                       err = cx2341x_update(cx, cx18_api_func, &cx->params, &p);
+                       if (!err && cx->params.stream_vbi_fmt != p.stream_vbi_fmt)
+                               err = cx18_setup_vbi_fmt(cx, p.stream_vbi_fmt);
+                       cx->params = p;
+                       cx->dualwatch_stereo_mode = p.audio_properties & 0x0300;
+                       cx18_audio_set_audio_clock_freq(cx, p.audio_properties & 0x03);
+                       return err;
+               }
+               return -EINVAL;
+       }
+
+       case VIDIOC_G_EXT_CTRLS:
+       {
+               struct v4l2_ext_controls *c = arg;
+
+               if (c->ctrl_class == V4L2_CTRL_CLASS_USER) {
+                       int i;
+                       int err = 0;
+
+                       for (i = 0; i < c->count; i++) {
+                               ctrl.id = c->controls[i].id;
+                               ctrl.value = c->controls[i].value;
+                               err = cx18_g_ctrl(cx, &ctrl);
+                               c->controls[i].value = ctrl.value;
+                               if (err) {
+                                       c->error_idx = i;
+                                       break;
+                               }
+                       }
+                       return err;
+               }
+               CX18_DEBUG_IOCTL("VIDIOC_G_EXT_CTRLS\n");
+               if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG)
+                       return cx2341x_ext_ctrls(&cx->params, 0, arg, cmd);
+               return -EINVAL;
+       }
+
+       case VIDIOC_TRY_EXT_CTRLS:
+       {
+               struct v4l2_ext_controls *c = arg;
+
+               CX18_DEBUG_IOCTL("VIDIOC_TRY_EXT_CTRLS\n");
+               if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG)
+                       return cx2341x_ext_ctrls(&cx->params,
+                                       atomic_read(&cx->capturing), arg, cmd);
+               return -EINVAL;
+       }
+
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
diff --git a/drivers/media/video/cx18/cx18-controls.h b/drivers/media/video/cx18/cx18-controls.h
new file mode 100644 (file)
index 0000000..6e985cf
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ *  cx18 ioctl control functions
+ *
+ *  Derived from ivtv-controls.h
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+
+ *  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
+ */
+
+int cx18_control_ioctls(struct cx18 *cx, unsigned int cmd, void *arg);
diff --git a/drivers/media/video/cx18/cx18-driver.c b/drivers/media/video/cx18/cx18-driver.c
new file mode 100644 (file)
index 0000000..9f31bef
--- /dev/null
@@ -0,0 +1,971 @@
+/*
+ *  cx18 driver initialization and card probing
+ *
+ *  Derived from ivtv-driver.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include "cx18-driver.h"
+#include "cx18-version.h"
+#include "cx18-cards.h"
+#include "cx18-i2c.h"
+#include "cx18-irq.h"
+#include "cx18-gpio.h"
+#include "cx18-firmware.h"
+#include "cx18-streams.h"
+#include "cx18-av-core.h"
+#include "cx18-scb.h"
+#include "cx18-mailbox.h"
+#include "cx18-ioctl.h"
+#include "tuner-xc2028.h"
+
+#include <media/tveeprom.h>
+
+
+/* var to keep track of the number of array elements in use */
+int cx18_cards_active;
+
+/* If you have already X v4l cards, then set this to X. This way
+   the device numbers stay matched. Example: you have a WinTV card
+   without radio and a Compro H900 with. Normally this would give a
+   video1 device together with a radio0 device for the Compro. By
+   setting this to 1 you ensure that radio0 is now also radio1. */
+int cx18_first_minor;
+
+/* Master variable for all cx18 info */
+struct cx18 *cx18_cards[CX18_MAX_CARDS];
+
+/* Protects cx18_cards_active */
+DEFINE_SPINLOCK(cx18_cards_lock);
+
+/* add your revision and whatnot here */
+static struct pci_device_id cx18_pci_tbl[] __devinitdata = {
+       {PCI_VENDOR_ID_CX, PCI_DEVICE_ID_CX23418,
+        PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {0,}
+};
+
+MODULE_DEVICE_TABLE(pci, cx18_pci_tbl);
+
+/* Parameter declarations */
+static int cardtype[CX18_MAX_CARDS];
+static int tuner[CX18_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1,
+                                    -1, -1, -1, -1, -1, -1, -1, -1,
+                                    -1, -1, -1, -1, -1, -1, -1, -1,
+                                    -1, -1, -1, -1, -1, -1, -1, -1 };
+static int radio[CX18_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1,
+                                    -1, -1, -1, -1, -1, -1, -1, -1,
+                                    -1, -1, -1, -1, -1, -1, -1, -1,
+                                    -1, -1, -1, -1, -1, -1, -1, -1 };
+
+static int cardtype_c = 1;
+static int tuner_c = 1;
+static int radio_c = 1;
+static char pal[] = "--";
+static char secam[] = "--";
+static char ntsc[] = "-";
+
+/* Buffers */
+static int enc_mpg_buffers = CX18_DEFAULT_ENC_MPG_BUFFERS;
+static int enc_ts_buffers = CX18_DEFAULT_ENC_TS_BUFFERS;
+static int enc_yuv_buffers = CX18_DEFAULT_ENC_YUV_BUFFERS;
+static int enc_vbi_buffers = CX18_DEFAULT_ENC_VBI_BUFFERS;
+static int enc_pcm_buffers = CX18_DEFAULT_ENC_PCM_BUFFERS;
+
+static int cx18_pci_latency = 1;
+
+int cx18_debug;
+
+module_param_array(tuner, int, &tuner_c, 0644);
+module_param_array(radio, bool, &radio_c, 0644);
+module_param_array(cardtype, int, &cardtype_c, 0644);
+module_param_string(pal, pal, sizeof(pal), 0644);
+module_param_string(secam, secam, sizeof(secam), 0644);
+module_param_string(ntsc, ntsc, sizeof(ntsc), 0644);
+module_param_named(debug, cx18_debug, int, 0644);
+module_param(cx18_pci_latency, int, 0644);
+module_param(cx18_first_minor, int, 0644);
+
+module_param(enc_mpg_buffers, int, 0644);
+module_param(enc_ts_buffers, int, 0644);
+module_param(enc_yuv_buffers, int, 0644);
+module_param(enc_vbi_buffers, int, 0644);
+module_param(enc_pcm_buffers, int, 0644);
+
+MODULE_PARM_DESC(tuner, "Tuner type selection,\n"
+                       "\t\t\tsee tuner.h for values");
+MODULE_PARM_DESC(radio,
+                "Enable or disable the radio. Use only if autodetection\n"
+                "\t\t\tfails. 0 = disable, 1 = enable");
+MODULE_PARM_DESC(cardtype,
+                "Only use this option if your card is not detected properly.\n"
+                "\t\tSpecify card type:\n"
+                "\t\t\t 1 = Hauppauge HVR 1600 (ESMT memory)\n"
+                "\t\t\t 2 = Hauppauge HVR 1600 (Samsung memory)\n"
+                "\t\t\t 3 = Compro VideoMate H900\n"
+                "\t\t\t 4 = Yuan MPC718\n"
+                "\t\t\t 0 = Autodetect (default)\n"
+                "\t\t\t-1 = Ignore this card\n\t\t");
+MODULE_PARM_DESC(pal, "Set PAL standard: B, G, H, D, K, I, M, N, Nc, 60");
+MODULE_PARM_DESC(secam, "Set SECAM standard: B, G, H, D, K, L, LC");
+MODULE_PARM_DESC(ntsc, "Set NTSC standard: M, J, K");
+MODULE_PARM_DESC(debug,
+                "Debug level (bitmask). Default: 0\n"
+                "\t\t\t  1/0x0001: warning\n"
+                "\t\t\t  2/0x0002: info\n"
+                "\t\t\t  4/0x0004: mailbox\n"
+                "\t\t\t  8/0x0008: dma\n"
+                "\t\t\t 16/0x0010: ioctl\n"
+                "\t\t\t 32/0x0020: file\n"
+                "\t\t\t 64/0x0040: i2c\n"
+                "\t\t\t128/0x0080: irq\n"
+                "\t\t\t256/0x0100: high volume\n");
+MODULE_PARM_DESC(cx18_pci_latency,
+                "Change the PCI latency to 64 if lower: 0 = No, 1 = Yes,\n"
+                "\t\t\tDefault: Yes");
+MODULE_PARM_DESC(enc_mpg_buffers,
+                "Encoder MPG Buffers (in MB)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_MPG_BUFFERS));
+MODULE_PARM_DESC(enc_ts_buffers,
+                "Encoder TS Buffers (in MB)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_TS_BUFFERS));
+MODULE_PARM_DESC(enc_yuv_buffers,
+                "Encoder YUV Buffers (in MB)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_YUV_BUFFERS));
+MODULE_PARM_DESC(enc_vbi_buffers,
+                "Encoder VBI Buffers (in MB)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_VBI_BUFFERS));
+MODULE_PARM_DESC(enc_pcm_buffers,
+                "Encoder PCM buffers (in MB)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_PCM_BUFFERS));
+
+MODULE_PARM_DESC(cx18_first_minor, "Set minor assigned to first card");
+
+MODULE_AUTHOR("Hans Verkuil");
+MODULE_DESCRIPTION("CX23418 driver");
+MODULE_SUPPORTED_DEVICE("CX23418 MPEG2 encoder");
+MODULE_LICENSE("GPL");
+
+MODULE_VERSION(CX18_VERSION);
+
+int cx18_waitq(wait_queue_head_t *waitq)
+{
+       DEFINE_WAIT(wait);
+
+       prepare_to_wait(waitq, &wait, TASK_INTERRUPTIBLE);
+       schedule();
+       finish_wait(waitq, &wait);
+       return signal_pending(current) ? -EINTR : 0;
+}
+
+/* Generic utility functions */
+int cx18_msleep_timeout(unsigned int msecs, int intr)
+{
+       int timeout = msecs_to_jiffies(msecs);
+       int sig;
+
+       do {
+               set_current_state(intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
+               timeout = schedule_timeout(timeout);
+               sig = intr ? signal_pending(current) : 0;
+       } while (!sig && timeout);
+       return sig;
+}
+
+/* Release ioremapped memory */
+static void cx18_iounmap(struct cx18 *cx)
+{
+       if (cx == NULL)
+               return;
+
+       /* Release io memory */
+       if (cx->enc_mem != NULL) {
+               CX18_DEBUG_INFO("releasing enc_mem\n");
+               iounmap(cx->enc_mem);
+               cx->enc_mem = NULL;
+       }
+}
+
+/* Hauppauge card? get values from tveeprom */
+void cx18_read_eeprom(struct cx18 *cx, struct tveeprom *tv)
+{
+       u8 eedata[256];
+
+       cx->i2c_client[0].addr = 0xA0 >> 1;
+       tveeprom_read(&cx->i2c_client[0], eedata, sizeof(eedata));
+       tveeprom_hauppauge_analog(&cx->i2c_client[0], tv, eedata);
+}
+
+static void cx18_process_eeprom(struct cx18 *cx)
+{
+       struct tveeprom tv;
+
+       cx18_read_eeprom(cx, &tv);
+
+       /* Many thanks to Steven Toth from Hauppauge for providing the
+          model numbers */
+       switch (tv.model) {
+       case 74000 ... 74099:
+               cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT);
+               break;
+       case 74700 ... 74799:
+               cx->card = cx18_get_card(CX18_CARD_HVR_1600_SAMSUNG);
+               break;
+       case 0:
+               CX18_ERR("Invalid EEPROM\n");
+               return;
+       default:
+               CX18_ERR("Unknown model %d, defaulting to HVR-1600\n", tv.model);
+               cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT);
+               break;
+       }
+
+       cx->v4l2_cap = cx->card->v4l2_capabilities;
+       cx->card_name = cx->card->name;
+       cx->card_i2c = cx->card->i2c;
+
+       CX18_INFO("Autodetected %s\n", cx->card_name);
+
+       if (tv.tuner_type == TUNER_ABSENT)
+               CX18_ERR("tveeprom cannot autodetect tuner!");
+
+       if (cx->options.tuner == -1)
+               cx->options.tuner = tv.tuner_type;
+       if (cx->options.radio == -1)
+               cx->options.radio = (tv.has_radio != 0);
+
+       if (cx->std != 0)
+               /* user specified tuner standard */
+               return;
+
+       /* autodetect tuner standard */
+       if (tv.tuner_formats & V4L2_STD_PAL) {
+               CX18_DEBUG_INFO("PAL tuner detected\n");
+               cx->std |= V4L2_STD_PAL_BG | V4L2_STD_PAL_H;
+       } else if (tv.tuner_formats & V4L2_STD_NTSC) {
+               CX18_DEBUG_INFO("NTSC tuner detected\n");
+               cx->std |= V4L2_STD_NTSC_M;
+       } else if (tv.tuner_formats & V4L2_STD_SECAM) {
+               CX18_DEBUG_INFO("SECAM tuner detected\n");
+               cx->std |= V4L2_STD_SECAM_L;
+       } else {
+               CX18_INFO("No tuner detected, default to NTSC-M\n");
+               cx->std |= V4L2_STD_NTSC_M;
+       }
+}
+
+static v4l2_std_id cx18_parse_std(struct cx18 *cx)
+{
+       switch (pal[0]) {
+       case '6':
+               return V4L2_STD_PAL_60;
+       case 'b':
+       case 'B':
+       case 'g':
+       case 'G':
+               return V4L2_STD_PAL_BG;
+       case 'h':
+       case 'H':
+               return V4L2_STD_PAL_H;
+       case 'n':
+       case 'N':
+               if (pal[1] == 'c' || pal[1] == 'C')
+                       return V4L2_STD_PAL_Nc;
+               return V4L2_STD_PAL_N;
+       case 'i':
+       case 'I':
+               return V4L2_STD_PAL_I;
+       case 'd':
+       case 'D':
+       case 'k':
+       case 'K':
+               return V4L2_STD_PAL_DK;
+       case 'M':
+       case 'm':
+               return V4L2_STD_PAL_M;
+       case '-':
+               break;
+       default:
+               CX18_WARN("pal= argument not recognised\n");
+               return 0;
+       }
+
+       switch (secam[0]) {
+       case 'b':
+       case 'B':
+       case 'g':
+       case 'G':
+       case 'h':
+       case 'H':
+               return V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H;
+       case 'd':
+       case 'D':
+       case 'k':
+       case 'K':
+               return V4L2_STD_SECAM_DK;
+       case 'l':
+       case 'L':
+               if (secam[1] == 'C' || secam[1] == 'c')
+                       return V4L2_STD_SECAM_LC;
+               return V4L2_STD_SECAM_L;
+       case '-':
+               break;
+       default:
+               CX18_WARN("secam= argument not recognised\n");
+               return 0;
+       }
+
+       switch (ntsc[0]) {
+       case 'm':
+       case 'M':
+               return V4L2_STD_NTSC_M;
+       case 'j':
+       case 'J':
+               return V4L2_STD_NTSC_M_JP;
+       case 'k':
+       case 'K':
+               return V4L2_STD_NTSC_M_KR;
+       case '-':
+               break;
+       default:
+               CX18_WARN("ntsc= argument not recognised\n");
+               return 0;
+       }
+
+       /* no match found */
+       return 0;
+}
+
+static void cx18_process_options(struct cx18 *cx)
+{
+       int i, j;
+
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_buffers;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_TS] = enc_ts_buffers;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_buffers;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_VBI] = enc_vbi_buffers;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_buffers;
+       cx->options.cardtype = cardtype[cx->num];
+       cx->options.tuner = tuner[cx->num];
+       cx->options.radio = radio[cx->num];
+
+       cx->std = cx18_parse_std(cx);
+       if (cx->options.cardtype == -1) {
+               CX18_INFO("Ignore card\n");
+               return;
+       }
+       cx->card = cx18_get_card(cx->options.cardtype - 1);
+       if (cx->card)
+               CX18_INFO("User specified %s card\n", cx->card->name);
+       else if (cx->options.cardtype != 0)
+               CX18_ERR("Unknown user specified type, trying to autodetect card\n");
+       if (cx->card == NULL) {
+               if (cx->dev->subsystem_vendor == CX18_PCI_ID_HAUPPAUGE) {
+                       cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT);
+                       CX18_INFO("Autodetected Hauppauge card\n");
+               }
+       }
+       if (cx->card == NULL) {
+               for (i = 0; (cx->card = cx18_get_card(i)); i++) {
+                       if (cx->card->pci_list == NULL)
+                               continue;
+                       for (j = 0; cx->card->pci_list[j].device; j++) {
+                               if (cx->dev->device !=
+                                   cx->card->pci_list[j].device)
+                                       continue;
+                               if (cx->dev->subsystem_vendor !=
+                                   cx->card->pci_list[j].subsystem_vendor)
+                                       continue;
+                               if (cx->dev->subsystem_device !=
+                                   cx->card->pci_list[j].subsystem_device)
+                                       continue;
+                               CX18_INFO("Autodetected %s card\n", cx->card->name);
+                               goto done;
+                       }
+               }
+       }
+done:
+
+       if (cx->card == NULL) {
+               cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT);
+               CX18_ERR("Unknown card: vendor/device: %04x/%04x\n",
+                    cx->dev->vendor, cx->dev->device);
+               CX18_ERR("              subsystem vendor/device: %04x/%04x\n",
+                    cx->dev->subsystem_vendor, cx->dev->subsystem_device);
+               CX18_ERR("Defaulting to %s card\n", cx->card->name);
+               CX18_ERR("Please mail the vendor/device and subsystem vendor/device IDs and what kind of\n");
+               CX18_ERR("card you have to the ivtv-devel mailinglist (www.ivtvdriver.org)\n");
+               CX18_ERR("Prefix your subject line with [UNKNOWN CX18 CARD].\n");
+       }
+       cx->v4l2_cap = cx->card->v4l2_capabilities;
+       cx->card_name = cx->card->name;
+       cx->card_i2c = cx->card->i2c;
+}
+
+/* Precondition: the cx18 structure has been memset to 0. Only
+   the dev and num fields have been filled in.
+   No assumptions on the card type may be made here (see cx18_init_struct2
+   for that).
+ */
+static int __devinit cx18_init_struct1(struct cx18 *cx)
+{
+       cx->base_addr = pci_resource_start(cx->dev, 0);
+
+       mutex_init(&cx->serialize_lock);
+       mutex_init(&cx->i2c_bus_lock[0]);
+       mutex_init(&cx->i2c_bus_lock[1]);
+
+       spin_lock_init(&cx->lock);
+       spin_lock_init(&cx->dma_reg_lock);
+
+       /* start counting open_id at 1 */
+       cx->open_id = 1;
+
+       /* Initial settings */
+       cx2341x_fill_defaults(&cx->params);
+       cx->temporal_strength = cx->params.video_temporal_filter;
+       cx->spatial_strength = cx->params.video_spatial_filter;
+       cx->filter_mode = cx->params.video_spatial_filter_mode |
+               (cx->params.video_temporal_filter_mode << 1) |
+               (cx->params.video_median_filter_type << 2);
+       cx->params.port = CX2341X_PORT_MEMORY;
+       cx->params.capabilities = CX2341X_CAP_HAS_SLICED_VBI;
+       init_waitqueue_head(&cx->cap_w);
+       init_waitqueue_head(&cx->mb_apu_waitq);
+       init_waitqueue_head(&cx->mb_cpu_waitq);
+       init_waitqueue_head(&cx->mb_epu_waitq);
+       init_waitqueue_head(&cx->mb_hpu_waitq);
+       init_waitqueue_head(&cx->dma_waitq);
+
+       /* VBI */
+       cx->vbi.in.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE;
+       cx->vbi.sliced_in = &cx->vbi.in.fmt.sliced;
+       cx->vbi.raw_size = 1456;
+       cx->vbi.raw_decoder_line_size = 1456;
+       cx->vbi.raw_decoder_sav_odd_field = 0x20;
+       cx->vbi.raw_decoder_sav_even_field = 0x60;
+       cx->vbi.sliced_decoder_line_size = 272;
+       cx->vbi.sliced_decoder_sav_odd_field = 0xB0;
+       cx->vbi.sliced_decoder_sav_even_field = 0xF0;
+       return 0;
+}
+
+/* Second initialization part. Here the card type has been
+   autodetected. */
+static void __devinit cx18_init_struct2(struct cx18 *cx)
+{
+       int i;
+
+       for (i = 0; i < CX18_CARD_MAX_VIDEO_INPUTS; i++)
+               if (cx->card->video_inputs[i].video_type == 0)
+                       break;
+       cx->nof_inputs = i;
+       for (i = 0; i < CX18_CARD_MAX_AUDIO_INPUTS; i++)
+               if (cx->card->audio_inputs[i].audio_type == 0)
+                       break;
+       cx->nof_audio_inputs = i;
+
+       /* Find tuner input */
+       for (i = 0; i < cx->nof_inputs; i++) {
+               if (cx->card->video_inputs[i].video_type ==
+                               CX18_CARD_INPUT_VID_TUNER)
+                       break;
+       }
+       if (i == cx->nof_inputs)
+               i = 0;
+       cx->active_input = i;
+       cx->audio_input = cx->card->video_inputs[i].audio_index;
+       cx->av_state.vid_input = CX18_AV_COMPOSITE7;
+       cx->av_state.aud_input = CX18_AV_AUDIO8;
+       cx->av_state.audclk_freq = 48000;
+       cx->av_state.audmode = V4L2_TUNER_MODE_LANG1;
+       cx->av_state.vbi_line_offset = 8;
+}
+
+static int cx18_setup_pci(struct cx18 *cx, struct pci_dev *dev,
+                         const struct pci_device_id *pci_id)
+{
+       u16 cmd;
+       unsigned char pci_latency;
+
+       CX18_DEBUG_INFO("Enabling pci device\n");
+
+       if (pci_enable_device(dev)) {
+               CX18_ERR("Can't enable device %d!\n", cx->num);
+               return -EIO;
+       }
+       if (pci_set_dma_mask(dev, 0xffffffff)) {
+               CX18_ERR("No suitable DMA available on card %d.\n", cx->num);
+               return -EIO;
+       }
+       if (!request_mem_region(cx->base_addr, CX18_MEM_SIZE, "cx18 encoder")) {
+               CX18_ERR("Cannot request encoder memory region on card %d.\n", cx->num);
+               return -EIO;
+       }
+
+       /* Check for bus mastering */
+       pci_read_config_word(dev, PCI_COMMAND, &cmd);
+       cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
+       pci_write_config_word(dev, PCI_COMMAND, cmd);
+
+       pci_read_config_byte(dev, PCI_CLASS_REVISION, &cx->card_rev);
+       pci_read_config_byte(dev, PCI_LATENCY_TIMER, &pci_latency);
+
+       if (pci_latency < 64 && cx18_pci_latency) {
+               CX18_INFO("Unreasonably low latency timer, "
+                              "setting to 64 (was %d)\n", pci_latency);
+               pci_write_config_byte(dev, PCI_LATENCY_TIMER, 64);
+               pci_read_config_byte(dev, PCI_LATENCY_TIMER, &pci_latency);
+       }
+       /* This config space value relates to DMA latencies. The
+          default value 0x8080 is too low however and will lead
+          to DMA errors. 0xffff is the max value which solves
+          these problems. */
+       pci_write_config_dword(dev, 0x40, 0xffff);
+
+       CX18_DEBUG_INFO("cx%d (rev %d) at %02x:%02x.%x, "
+                  "irq: %d, latency: %d, memory: 0x%lx\n",
+                  cx->dev->device, cx->card_rev, dev->bus->number,
+                  PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn),
+                  cx->dev->irq, pci_latency, (unsigned long)cx->base_addr);
+
+       return 0;
+}
+
+static u32 cx18_request_module(struct cx18 *cx, u32 hw,
+               const char *name, u32 id)
+{
+       if ((hw & id) == 0)
+               return hw;
+       if (request_module(name) != 0) {
+               CX18_ERR("Failed to load module %s\n", name);
+               return hw & ~id;
+       }
+       CX18_DEBUG_INFO("Loaded module %s\n", name);
+       return hw;
+}
+
+static void cx18_load_and_init_modules(struct cx18 *cx)
+{
+       u32 hw = cx->card->hw_all;
+       int i;
+
+       /* load modules */
+#ifndef CONFIG_VIDEO_TUNER
+       hw = cx18_request_module(cx, hw, "tuner", CX18_HW_TUNER);
+#endif
+#ifndef CONFIG_VIDEO_CS5345
+       hw = cx18_request_module(cx, hw, "cs5345", CX18_HW_CS5345);
+#endif
+
+       /* check which i2c devices are actually found */
+       for (i = 0; i < 32; i++) {
+               u32 device = 1 << i;
+
+               if (!(device & hw))
+                       continue;
+               if (device == CX18_HW_GPIO || device == CX18_HW_TVEEPROM ||
+                   device == CX18_HW_CX23418 || device == CX18_HW_DVB) {
+                       /* These 'devices' do not use i2c probing */
+                       cx->hw_flags |= device;
+                       continue;
+               }
+               cx18_i2c_register(cx, i);
+               if (cx18_i2c_hw_addr(cx, device) > 0)
+                       cx->hw_flags |= device;
+       }
+
+       hw = cx->hw_flags;
+}
+
+static int __devinit cx18_probe(struct pci_dev *dev,
+                               const struct pci_device_id *pci_id)
+{
+       int retval = 0;
+       int vbi_buf_size;
+       u32 devtype;
+       struct cx18 *cx;
+
+       spin_lock(&cx18_cards_lock);
+
+       /* Make sure we've got a place for this card */
+       if (cx18_cards_active == CX18_MAX_CARDS) {
+               printk(KERN_ERR "cx18:  Maximum number of cards detected (%d).\n",
+                             cx18_cards_active);
+               spin_unlock(&cx18_cards_lock);
+               return -ENOMEM;
+       }
+
+       cx = kzalloc(sizeof(struct cx18), GFP_ATOMIC);
+       if (cx == 0) {
+               spin_unlock(&cx18_cards_lock);
+               return -ENOMEM;
+       }
+       cx18_cards[cx18_cards_active] = cx;
+       cx->dev = dev;
+       cx->num = cx18_cards_active++;
+       snprintf(cx->name, sizeof(cx->name) - 1, "cx18-%d", cx->num);
+       CX18_INFO("Initializing card #%d\n", cx->num);
+
+       spin_unlock(&cx18_cards_lock);
+
+       cx18_process_options(cx);
+       if (cx->options.cardtype == -1) {
+               retval = -ENODEV;
+               goto err;
+       }
+       if (cx18_init_struct1(cx)) {
+               retval = -ENOMEM;
+               goto err;
+       }
+
+       CX18_DEBUG_INFO("base addr: 0x%08x\n", cx->base_addr);
+
+       /* PCI Device Setup */
+       retval = cx18_setup_pci(cx, dev, pci_id);
+       if (retval != 0) {
+               if (retval == -EIO)
+                       goto free_workqueue;
+               else if (retval == -ENXIO)
+                       goto free_mem;
+       }
+       /* save cx in the pci struct for later use */
+       pci_set_drvdata(dev, cx);
+
+       /* map io memory */
+       CX18_DEBUG_INFO("attempting ioremap at 0x%08x len 0x%08x\n",
+                  cx->base_addr + CX18_MEM_OFFSET, CX18_MEM_SIZE);
+       cx->enc_mem = ioremap_nocache(cx->base_addr + CX18_MEM_OFFSET,
+                                      CX18_MEM_SIZE);
+       if (!cx->enc_mem) {
+               CX18_ERR("ioremap failed, perhaps increasing __VMALLOC_RESERVE in page.h\n");
+               CX18_ERR("or disabling CONFIG_HIGHMEM4G into the kernel would help\n");
+               retval = -ENOMEM;
+               goto free_mem;
+       }
+       cx->reg_mem = cx->enc_mem + CX18_REG_OFFSET;
+       devtype = read_reg(0xC72028);
+       switch (devtype & 0xff000000) {
+       case 0xff000000:
+               CX18_INFO("cx23418 revision %08x (A)\n", devtype);
+               break;
+       case 0x01000000:
+               CX18_INFO("cx23418 revision %08x (B)\n", devtype);
+               break;
+       default:
+               CX18_INFO("cx23418 revision %08x (Unknown)\n", devtype);
+               break;
+       }
+
+       cx18_init_power(cx, 1);
+       cx18_init_memory(cx);
+
+       cx->scb = (struct cx18_scb *)(cx->enc_mem + SCB_OFFSET);
+       cx18_init_scb(cx);
+
+       cx18_gpio_init(cx);
+
+       /* active i2c  */
+       CX18_DEBUG_INFO("activating i2c...\n");
+       if (init_cx18_i2c(cx)) {
+               CX18_ERR("Could not initialize i2c\n");
+               goto free_map;
+       }
+
+       CX18_DEBUG_INFO("Active card count: %d.\n", cx18_cards_active);
+
+       if (cx->card->hw_all & CX18_HW_TVEEPROM) {
+               /* Based on the model number the cardtype may be changed.
+                  The PCI IDs are not always reliable. */
+               cx18_process_eeprom(cx);
+       }
+       if (cx->card->comment)
+               CX18_INFO("%s", cx->card->comment);
+       if (cx->card->v4l2_capabilities == 0) {
+               retval = -ENODEV;
+               goto free_i2c;
+       }
+       cx18_init_memory(cx);
+
+       /* Register IRQ */
+       retval = request_irq(cx->dev->irq, cx18_irq_handler,
+                            IRQF_SHARED | IRQF_DISABLED, cx->name, (void *)cx);
+       if (retval) {
+               CX18_ERR("Failed to register irq %d\n", retval);
+               goto free_i2c;
+       }
+
+       if (cx->std == 0)
+               cx->std = V4L2_STD_NTSC_M;
+
+       if (cx->options.tuner == -1) {
+               int i;
+
+               for (i = 0; i < CX18_CARD_MAX_TUNERS; i++) {
+                       if ((cx->std & cx->card->tuners[i].std) == 0)
+                               continue;
+                       cx->options.tuner = cx->card->tuners[i].tuner;
+                       break;
+               }
+       }
+       /* if no tuner was found, then pick the first tuner in the card list */
+       if (cx->options.tuner == -1 && cx->card->tuners[0].std) {
+               cx->std = cx->card->tuners[0].std;
+               cx->options.tuner = cx->card->tuners[0].tuner;
+       }
+       if (cx->options.radio == -1)
+               cx->options.radio = (cx->card->radio_input.audio_type != 0);
+
+       /* The card is now fully identified, continue with card-specific
+          initialization. */
+       cx18_init_struct2(cx);
+
+       cx18_load_and_init_modules(cx);
+
+       if (cx->std & V4L2_STD_525_60) {
+               cx->is_60hz = 1;
+               cx->is_out_60hz = 1;
+       } else {
+               cx->is_50hz = 1;
+               cx->is_out_50hz = 1;
+       }
+       cx->params.video_gop_size = cx->is_60hz ? 15 : 12;
+
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_MPG] = 0x08000;
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_TS] = 0x08000;
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_PCM] = 0x01200;
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_YUV] = 0x20000;
+       vbi_buf_size = cx->vbi.raw_size * (cx->is_60hz ? 24 : 36) / 2;
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_VBI] = vbi_buf_size;
+
+       if (cx->options.radio > 0)
+               cx->v4l2_cap |= V4L2_CAP_RADIO;
+
+       retval = cx18_streams_setup(cx);
+       if (retval) {
+               CX18_ERR("Error %d setting up streams\n", retval);
+               goto free_irq;
+       }
+       retval = cx18_streams_register(cx);
+       if (retval) {
+               CX18_ERR("Error %d registering devices\n", retval);
+               goto free_streams;
+       }
+
+       if (cx->options.tuner > -1) {
+               struct tuner_setup setup;
+
+               setup.addr = ADDR_UNSET;
+               setup.type = cx->options.tuner;
+               setup.mode_mask = T_ANALOG_TV;  /* matches TV tuners */
+               setup.tuner_callback = (setup.type == TUNER_XC2028) ?
+                       cx18_reset_tuner_gpio : NULL;
+               cx18_call_i2c_clients(cx, TUNER_SET_TYPE_ADDR, &setup);
+               if (setup.type == TUNER_XC2028) {
+                       static struct xc2028_ctrl ctrl = {
+                               .fname = XC2028_DEFAULT_FIRMWARE,
+                               .max_len = 64,
+                       };
+                       struct v4l2_priv_tun_config cfg = {
+                               .tuner = cx->options.tuner,
+                               .priv = &ctrl,
+                       };
+                       cx18_call_i2c_clients(cx, TUNER_SET_CONFIG, &cfg);
+               }
+       }
+
+       /* The tuner is fixed to the standard. The other inputs (e.g. S-Video)
+          are not. */
+       cx->tuner_std = cx->std;
+
+       cx18_init_on_first_open(cx);
+
+       CX18_INFO("Initialized card #%d: %s\n", cx->num, cx->card_name);
+
+       return 0;
+
+free_streams:
+       cx18_streams_cleanup(cx);
+free_irq:
+       free_irq(cx->dev->irq, (void *)cx);
+free_i2c:
+       exit_cx18_i2c(cx);
+free_map:
+       cx18_iounmap(cx);
+free_mem:
+       release_mem_region(cx->base_addr, CX18_MEM_SIZE);
+free_workqueue:
+err:
+       if (retval == 0)
+               retval = -ENODEV;
+       CX18_ERR("Error %d on initialization\n", retval);
+
+       kfree(cx18_cards[cx18_cards_active]);
+       cx18_cards[cx18_cards_active] = NULL;
+       return retval;
+}
+
+int cx18_init_on_first_open(struct cx18 *cx)
+{
+       int video_input;
+       int fw_retry_count = 3;
+       struct v4l2_frequency vf;
+
+       if (test_bit(CX18_F_I_FAILED, &cx->i_flags))
+               return -ENXIO;
+
+       if (test_and_set_bit(CX18_F_I_INITED, &cx->i_flags))
+               return 0;
+
+       while (--fw_retry_count > 0) {
+               /* load firmware */
+               if (cx18_firmware_init(cx) == 0)
+                       break;
+               if (fw_retry_count > 1)
+                       CX18_WARN("Retry loading firmware\n");
+       }
+
+       if (fw_retry_count == 0) {
+               set_bit(CX18_F_I_FAILED, &cx->i_flags);
+               return -ENXIO;
+       }
+       set_bit(CX18_F_I_LOADED_FW, &cx->i_flags);
+
+       /* Init the firmware twice to work around a silicon bug
+        * transport related. */
+
+       fw_retry_count = 3;
+       while (--fw_retry_count > 0) {
+               /* load firmware */
+               if (cx18_firmware_init(cx) == 0)
+                       break;
+               if (fw_retry_count > 1)
+                       CX18_WARN("Retry loading firmware\n");
+       }
+
+       if (fw_retry_count == 0) {
+               set_bit(CX18_F_I_FAILED, &cx->i_flags);
+               return -ENXIO;
+       }
+
+       vf.tuner = 0;
+       vf.type = V4L2_TUNER_ANALOG_TV;
+       vf.frequency = 6400; /* the tuner 'baseline' frequency */
+
+       /* Set initial frequency. For PAL/SECAM broadcasts no
+          'default' channel exists AFAIK. */
+       if (cx->std == V4L2_STD_NTSC_M_JP)
+               vf.frequency = 1460;    /* ch. 1 91250*16/1000 */
+       else if (cx->std & V4L2_STD_NTSC_M)
+               vf.frequency = 1076;    /* ch. 4 67250*16/1000 */
+
+       video_input = cx->active_input;
+       cx->active_input++;     /* Force update of input */
+       cx18_v4l2_ioctls(cx, NULL, VIDIOC_S_INPUT, &video_input);
+
+       /* Let the VIDIOC_S_STD ioctl do all the work, keeps the code
+          in one place. */
+       cx->std++;              /* Force full standard initialization */
+       cx18_v4l2_ioctls(cx, NULL, VIDIOC_S_STD, &cx->tuner_std);
+       cx18_v4l2_ioctls(cx, NULL, VIDIOC_S_FREQUENCY, &vf);
+       return 0;
+}
+
+static void cx18_remove(struct pci_dev *pci_dev)
+{
+       struct cx18 *cx = pci_get_drvdata(pci_dev);
+
+       CX18_DEBUG_INFO("Removing Card #%d\n", cx->num);
+
+       /* Stop all captures */
+       CX18_DEBUG_INFO("Stopping all streams\n");
+       if (atomic_read(&cx->capturing) > 0)
+               cx18_stop_all_captures(cx);
+
+       /* Interrupts */
+       sw1_irq_disable(IRQ_CPU_TO_EPU | IRQ_APU_TO_EPU);
+       sw2_irq_disable(IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK);
+
+       cx18_halt_firmware(cx);
+
+       cx18_streams_cleanup(cx);
+
+       exit_cx18_i2c(cx);
+
+       free_irq(cx->dev->irq, (void *)cx);
+
+       if (cx->dev)
+               cx18_iounmap(cx);
+
+       release_mem_region(cx->base_addr, CX18_MEM_SIZE);
+
+       pci_disable_device(cx->dev);
+
+       CX18_INFO("Removed %s, card #%d\n", cx->card_name, cx->num);
+}
+
+/* define a pci_driver for card detection */
+static struct pci_driver cx18_pci_driver = {
+      .name =     "cx18",
+      .id_table = cx18_pci_tbl,
+      .probe =    cx18_probe,
+      .remove =   cx18_remove,
+};
+
+static int module_start(void)
+{
+       printk(KERN_INFO "cx18:  Start initialization, version %s\n", CX18_VERSION);
+
+       memset(cx18_cards, 0, sizeof(cx18_cards));
+
+       /* Validate parameters */
+       if (cx18_first_minor < 0 || cx18_first_minor >= CX18_MAX_CARDS) {
+               printk(KERN_ERR "cx18:  Exiting, ivtv_first_minor must be between 0 and %d\n",
+                    CX18_MAX_CARDS - 1);
+               return -1;
+       }
+
+       if (cx18_debug < 0 || cx18_debug > 511) {
+               cx18_debug = 0;
+               printk(KERN_INFO "cx18:   Debug value must be >= 0 and <= 511!\n");
+       }
+
+       if (pci_register_driver(&cx18_pci_driver)) {
+               printk(KERN_ERR "cx18:   Error detecting PCI card\n");
+               return -ENODEV;
+       }
+       printk(KERN_INFO "cx18:  End initialization\n");
+       return 0;
+}
+
+static void module_cleanup(void)
+{
+       int i;
+
+       pci_unregister_driver(&cx18_pci_driver);
+
+       for (i = 0; i < cx18_cards_active; i++) {
+               if (cx18_cards[i] == NULL)
+                       continue;
+               kfree(cx18_cards[i]);
+       }
+}
+
+module_init(module_start);
+module_exit(module_cleanup);
diff --git a/drivers/media/video/cx18/cx18-driver.h b/drivers/media/video/cx18/cx18-driver.h
new file mode 100644 (file)
index 0000000..2ee9391
--- /dev/null
@@ -0,0 +1,500 @@
+/*
+ *  cx18 driver internal defines and structures
+ *
+ *  Derived from ivtv-driver.h
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#ifndef CX18_DRIVER_H
+#define CX18_DRIVER_H
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+#include <linux/list.h>
+#include <linux/unistd.h>
+#include <linux/byteorder/swab.h>
+#include <linux/pagemap.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+
+#include <linux/dvb/video.h>
+#include <linux/dvb/audio.h>
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+#include "cx18-mailbox.h"
+#include "cx18-av-core.h"
+#include "cx23418.h"
+
+/* DVB */
+#include "demux.h"
+#include "dmxdev.h"
+#include "dvb_demux.h"
+#include "dvb_frontend.h"
+#include "dvb_net.h"
+#include "dvbdev.h"
+
+#ifndef CONFIG_PCI
+#  error "This driver requires kernel PCI support."
+#endif
+
+#define CX18_MEM_OFFSET        0x00000000
+#define CX18_MEM_SIZE  0x04000000
+#define CX18_REG_OFFSET        0x02000000
+
+/* Maximum cx18 driver instances. */
+#define CX18_MAX_CARDS 32
+
+/* Supported cards */
+#define CX18_CARD_HVR_1600_ESMT              0 /* Hauppauge HVR 1600 (ESMT memory) */
+#define CX18_CARD_HVR_1600_SAMSUNG    1        /* Hauppauge HVR 1600 (Samsung memory) */
+#define CX18_CARD_COMPRO_H900        2 /* Compro VideoMate H900 */
+#define CX18_CARD_YUAN_MPC718        3 /* Yuan MPC718 */
+#define CX18_CARD_LAST                       3
+
+#define CX18_ENC_STREAM_TYPE_MPG  0
+#define CX18_ENC_STREAM_TYPE_TS   1
+#define CX18_ENC_STREAM_TYPE_YUV  2
+#define CX18_ENC_STREAM_TYPE_VBI  3
+#define CX18_ENC_STREAM_TYPE_PCM  4
+#define CX18_ENC_STREAM_TYPE_IDX  5
+#define CX18_ENC_STREAM_TYPE_RAD  6
+#define CX18_MAX_STREAMS         7
+
+/* system vendor and device IDs */
+#define PCI_VENDOR_ID_CX      0x14f1
+#define PCI_DEVICE_ID_CX23418 0x5b7a
+
+/* subsystem vendor ID */
+#define CX18_PCI_ID_HAUPPAUGE          0x0070
+#define CX18_PCI_ID_COMPRO             0x185b
+#define CX18_PCI_ID_YUAN               0x12ab
+
+/* ======================================================================== */
+/* ========================== START USER SETTABLE DMA VARIABLES =========== */
+/* ======================================================================== */
+
+/* DMA Buffers, Default size in MB allocated */
+#define CX18_DEFAULT_ENC_TS_BUFFERS  1
+#define CX18_DEFAULT_ENC_MPG_BUFFERS 2
+#define CX18_DEFAULT_ENC_IDX_BUFFERS 1
+#define CX18_DEFAULT_ENC_YUV_BUFFERS 2
+#define CX18_DEFAULT_ENC_VBI_BUFFERS 1
+#define CX18_DEFAULT_ENC_PCM_BUFFERS 1
+
+/* i2c stuff */
+#define I2C_CLIENTS_MAX 16
+
+/* debugging */
+
+/* Flag to turn on high volume debugging */
+#define CX18_DBGFLG_WARN  (1 << 0)
+#define CX18_DBGFLG_INFO  (1 << 1)
+#define CX18_DBGFLG_API   (1 << 2)
+#define CX18_DBGFLG_DMA   (1 << 3)
+#define CX18_DBGFLG_IOCTL (1 << 4)
+#define CX18_DBGFLG_FILE  (1 << 5)
+#define CX18_DBGFLG_I2C   (1 << 6)
+#define CX18_DBGFLG_IRQ   (1 << 7)
+/* Flag to turn on high volume debugging */
+#define CX18_DBGFLG_HIGHVOL (1 << 8)
+
+/* NOTE: extra space before comma in 'cx->num , ## args' is required for
+   gcc-2.95, otherwise it won't compile. */
+#define CX18_DEBUG(x, type, fmt, args...) \
+       do { \
+               if ((x) & cx18_debug) \
+                       printk(KERN_INFO "cx18-%d " type ": " fmt, cx->num , ## args); \
+       } while (0)
+#define CX18_DEBUG_WARN(fmt, args...)  CX18_DEBUG(CX18_DBGFLG_WARN, "warning", fmt , ## args)
+#define CX18_DEBUG_INFO(fmt, args...)  CX18_DEBUG(CX18_DBGFLG_INFO, "info", fmt , ## args)
+#define CX18_DEBUG_API(fmt, args...)   CX18_DEBUG(CX18_DBGFLG_API, "api", fmt , ## args)
+#define CX18_DEBUG_DMA(fmt, args...)   CX18_DEBUG(CX18_DBGFLG_DMA, "dma", fmt , ## args)
+#define CX18_DEBUG_IOCTL(fmt, args...) CX18_DEBUG(CX18_DBGFLG_IOCTL, "ioctl", fmt , ## args)
+#define CX18_DEBUG_FILE(fmt, args...)  CX18_DEBUG(CX18_DBGFLG_FILE, "file", fmt , ## args)
+#define CX18_DEBUG_I2C(fmt, args...)   CX18_DEBUG(CX18_DBGFLG_I2C, "i2c", fmt , ## args)
+#define CX18_DEBUG_IRQ(fmt, args...)   CX18_DEBUG(CX18_DBGFLG_IRQ, "irq", fmt , ## args)
+
+#define CX18_DEBUG_HIGH_VOL(x, type, fmt, args...) \
+       do { \
+               if (((x) & cx18_debug) && (cx18_debug & CX18_DBGFLG_HIGHVOL)) \
+                       printk(KERN_INFO "cx18%d " type ": " fmt, cx->num , ## args); \
+       } while (0)
+#define CX18_DEBUG_HI_WARN(fmt, args...)  CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_WARN, "warning", fmt , ## args)
+#define CX18_DEBUG_HI_INFO(fmt, args...)  CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_INFO, "info", fmt , ## args)
+#define CX18_DEBUG_HI_API(fmt, args...)   CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_API, "api", fmt , ## args)
+#define CX18_DEBUG_HI_DMA(fmt, args...)   CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_DMA, "dma", fmt , ## args)
+#define CX18_DEBUG_HI_IOCTL(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_IOCTL, "ioctl", fmt , ## args)
+#define CX18_DEBUG_HI_FILE(fmt, args...)  CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_FILE, "file", fmt , ## args)
+#define CX18_DEBUG_HI_I2C(fmt, args...)   CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_I2C, "i2c", fmt , ## args)
+#define CX18_DEBUG_HI_IRQ(fmt, args...)   CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_IRQ, "irq", fmt , ## args)
+
+/* Standard kernel messages */
+#define CX18_ERR(fmt, args...)      printk(KERN_ERR  "cx18-%d: " fmt, cx->num , ## args)
+#define CX18_WARN(fmt, args...)     printk(KERN_WARNING "cx18-%d: " fmt, cx->num , ## args)
+#define CX18_INFO(fmt, args...)     printk(KERN_INFO "cx18-%d: " fmt, cx->num , ## args)
+
+/* Values for CX18_API_DEC_PLAYBACK_SPEED mpeg_frame_type_mask parameter: */
+#define MPEG_FRAME_TYPE_IFRAME 1
+#define MPEG_FRAME_TYPE_IFRAME_PFRAME 3
+#define MPEG_FRAME_TYPE_ALL 7
+
+#define CX18_MAX_PGM_INDEX (400)
+
+extern int cx18_debug;
+
+
+struct cx18_options {
+       int megabytes[CX18_MAX_STREAMS]; /* Size in megabytes of each stream */
+       int cardtype;           /* force card type on load */
+       int tuner;              /* set tuner on load */
+       int radio;              /* enable/disable radio */
+};
+
+/* per-buffer bit flags */
+#define CX18_F_B_NEED_BUF_SWAP  0      /* this buffer should be byte swapped */
+
+/* per-stream, s_flags */
+#define CX18_F_S_CLAIMED       3       /* this stream is claimed */
+#define CX18_F_S_STREAMING      4      /* the fw is decoding/encoding this stream */
+#define CX18_F_S_INTERNAL_USE  5       /* this stream is used internally (sliced VBI processing) */
+#define CX18_F_S_STREAMOFF     7       /* signal end of stream EOS */
+#define CX18_F_S_APPL_IO        8      /* this stream is used read/written by an application */
+
+/* per-cx18, i_flags */
+#define CX18_F_I_LOADED_FW     0       /* Loaded the firmware the first time */
+#define CX18_F_I_EOS           4       /* End of encoder stream reached */
+#define CX18_F_I_RADIO_USER    5       /* The radio tuner is selected */
+#define CX18_F_I_ENC_PAUSED    13      /* the encoder is paused */
+#define CX18_F_I_INITED                21      /* set after first open */
+#define CX18_F_I_FAILED                22      /* set if first open failed */
+
+/* These are the VBI types as they appear in the embedded VBI private packets. */
+#define CX18_SLICED_TYPE_TELETEXT_B     (1)
+#define CX18_SLICED_TYPE_CAPTION_525    (4)
+#define CX18_SLICED_TYPE_WSS_625        (5)
+#define CX18_SLICED_TYPE_VPS            (7)
+
+struct cx18_buffer {
+       struct list_head list;
+       dma_addr_t dma_handle;
+       u32 id;
+       unsigned long b_flags;
+       char *buf;
+
+       u32 bytesused;
+       u32 readpos;
+};
+
+struct cx18_queue {
+       struct list_head list;
+       u32 buffers;
+       u32 length;
+       u32 bytesused;
+};
+
+struct cx18_dvb {
+       struct dmx_frontend hw_frontend;
+       struct dmx_frontend mem_frontend;
+       struct dmxdev dmxdev;
+       struct dvb_adapter dvb_adapter;
+       struct dvb_demux demux;
+       struct dvb_frontend *fe;
+       struct dvb_net dvbnet;
+       int enabled;
+       int feeding;
+
+       struct mutex feedlock;
+
+};
+
+struct cx18;    /* forward reference */
+struct cx18_scb; /* forward reference */
+
+struct cx18_stream {
+       /* These first four fields are always set, even if the stream
+          is not actually created. */
+       struct video_device *v4l2dev;   /* NULL when stream not created */
+       struct cx18 *cx;                /* for ease of use */
+       const char *name;               /* name of the stream */
+       int type;                       /* stream type */
+       u32 handle;                     /* task handle */
+       unsigned mdl_offset;
+
+       u32 id;
+       spinlock_t qlock;       /* locks access to the queues */
+       unsigned long s_flags;  /* status flags, see above */
+       int dma;                /* can be PCI_DMA_TODEVICE,
+                                  PCI_DMA_FROMDEVICE or
+                                  PCI_DMA_NONE */
+       u64 dma_pts;
+       wait_queue_head_t waitq;
+
+       /* Buffer Stats */
+       u32 buffers;
+       u32 buf_size;
+       u32 buffers_stolen;
+
+       /* Buffer Queues */
+       struct cx18_queue q_free;       /* free buffers */
+       struct cx18_queue q_full;       /* full buffers */
+       struct cx18_queue q_io;         /* waiting for I/O */
+
+       /* DVB / Digital Transport */
+       struct cx18_dvb dvb;
+};
+
+struct cx18_open_id {
+       u32 open_id;
+       int type;
+       enum v4l2_priority prio;
+       struct cx18 *cx;
+};
+
+/* forward declaration of struct defined in cx18-cards.h */
+struct cx18_card;
+
+
+#define CX18_VBI_FRAMES 32
+
+/* VBI data */
+struct vbi_info {
+       u32 enc_size;
+       u32 frame;
+       u8 cc_data_odd[256];
+       u8 cc_data_even[256];
+       int cc_pos;
+       u8 cc_no_update;
+       u8 vps[5];
+       u8 vps_found;
+       int wss;
+       u8 wss_found;
+       u8 wss_no_update;
+       u32 raw_decoder_line_size;
+       u8 raw_decoder_sav_odd_field;
+       u8 raw_decoder_sav_even_field;
+       u32 sliced_decoder_line_size;
+       u8 sliced_decoder_sav_odd_field;
+       u8 sliced_decoder_sav_even_field;
+       struct v4l2_format in;
+       /* convenience pointer to sliced struct in vbi_in union */
+       struct v4l2_sliced_vbi_format *sliced_in;
+       u32 service_set_in;
+       int insert_mpeg;
+
+       /* Buffer for the maximum of 2 * 18 * packet_size sliced VBI lines.
+          One for /dev/vbi0 and one for /dev/vbi8 */
+       struct v4l2_sliced_vbi_data sliced_data[36];
+
+       /* Buffer for VBI data inserted into MPEG stream.
+          The first byte is a dummy byte that's never used.
+          The next 16 bytes contain the MPEG header for the VBI data,
+          the remainder is the actual VBI data.
+          The max size accepted by the MPEG VBI reinsertion turns out
+          to be 1552 bytes, which happens to be 4 + (1 + 42) * (2 * 18) bytes,
+          where 4 is a four byte header, 42 is the max sliced VBI payload, 1 is
+          a single line header byte and 2 * 18 is the number of VBI lines per frame.
+
+          However, it seems that the data must be 1K aligned, so we have to
+          pad the data until the 1 or 2 K boundary.
+
+          This pointer array will allocate 2049 bytes to store each VBI frame. */
+       u8 *sliced_mpeg_data[CX18_VBI_FRAMES];
+       u32 sliced_mpeg_size[CX18_VBI_FRAMES];
+       struct cx18_buffer sliced_mpeg_buf;
+       u32 inserted_frame;
+
+       u32 start[2], count;
+       u32 raw_size;
+       u32 sliced_size;
+};
+
+/* Per cx23418, per I2C bus private algo callback data */
+struct cx18_i2c_algo_callback_data {
+       struct cx18 *cx;
+       int bus_index;   /* 0 or 1 for the cx23418's 1st or 2nd I2C bus */
+};
+
+/* Struct to hold info about cx18 cards */
+struct cx18 {
+       int num;                /* board number, -1 during init! */
+       char name[8];           /* board name for printk and interrupts (e.g. 'cx180') */
+       struct pci_dev *dev;    /* PCI device */
+       const struct cx18_card *card;   /* card information */
+       const char *card_name;  /* full name of the card */
+       const struct cx18_card_tuner_i2c *card_i2c; /* i2c addresses to probe for tuner */
+       u8 is_50hz;
+       u8 is_60hz;
+       u8 is_out_50hz;
+       u8 is_out_60hz;
+       u8 nof_inputs;          /* number of video inputs */
+       u8 nof_audio_inputs;    /* number of audio inputs */
+       u16 buffer_id;          /* buffer ID counter */
+       u32 v4l2_cap;           /* V4L2 capabilities of card */
+       u32 hw_flags;           /* Hardware description of the board */
+       unsigned mdl_offset;
+       struct cx18_scb *scb;   /* pointer to SCB */
+
+       struct cx18_av_state av_state;
+
+       /* codec settings */
+       struct cx2341x_mpeg_params params;
+       u32 filter_mode;
+       u32 temporal_strength;
+       u32 spatial_strength;
+
+       /* dualwatch */
+       unsigned long dualwatch_jiffies;
+       u16 dualwatch_stereo_mode;
+
+       /* Digitizer type */
+       int digitizer;          /* 0x00EF = saa7114 0x00FO = saa7115 0x0106 = mic */
+
+       struct mutex serialize_lock;    /* mutex used to serialize open/close/start/stop/ioctl operations */
+       struct cx18_options options;    /* User options */
+       int stream_buf_size[CX18_MAX_STREAMS]; /* Stream buffer size */
+       struct cx18_stream streams[CX18_MAX_STREAMS];   /* Stream data */
+       unsigned long i_flags;  /* global cx18 flags */
+       atomic_t capturing;     /* count number of active capture streams */
+       spinlock_t lock;        /* lock access to this struct */
+       int search_pack_header;
+
+       spinlock_t dma_reg_lock; /* lock access to DMA engine registers */
+
+       int open_id;            /* incremented each time an open occurs, used as
+                                  unique ID. Starts at 1, so 0 can be used as
+                                  uninitialized value in the stream->id. */
+
+       u32 base_addr;
+       struct v4l2_prio_state prio;
+
+       u8 card_rev;
+       void __iomem *enc_mem, *reg_mem;
+
+       struct vbi_info vbi;
+
+       u32 pgm_info_offset;
+       u32 pgm_info_num;
+       u32 pgm_info_write_idx;
+       u32 pgm_info_read_idx;
+       struct v4l2_enc_idx_entry pgm_info[CX18_MAX_PGM_INDEX];
+
+       u64 mpg_data_received;
+       u64 vbi_data_inserted;
+
+       wait_queue_head_t mb_apu_waitq;
+       wait_queue_head_t mb_cpu_waitq;
+       wait_queue_head_t mb_epu_waitq;
+       wait_queue_head_t mb_hpu_waitq;
+       wait_queue_head_t cap_w;
+       /* when the current DMA is finished this queue is woken up */
+       wait_queue_head_t dma_waitq;
+
+       /* i2c */
+       struct i2c_adapter i2c_adap[2];
+       struct i2c_algo_bit_data i2c_algo[2];
+       struct cx18_i2c_algo_callback_data i2c_algo_cb_data[2];
+       struct i2c_client i2c_client[2];
+       struct mutex i2c_bus_lock[2];
+       struct i2c_client *i2c_clients[I2C_CLIENTS_MAX];
+
+       /* v4l2 and User settings */
+
+       /* codec settings */
+       u32 audio_input;
+       u32 active_input;
+       u32 active_output;
+       v4l2_std_id std;
+       v4l2_std_id tuner_std;  /* The norm of the tuner (fixed) */
+};
+
+/* Globals */
+extern struct cx18 *cx18_cards[];
+extern int cx18_cards_active;
+extern int cx18_first_minor;
+extern spinlock_t cx18_cards_lock;
+
+/*==============Prototypes==================*/
+
+/* Return non-zero if a signal is pending */
+int cx18_msleep_timeout(unsigned int msecs, int intr);
+
+/* Wait on queue, returns -EINTR if interrupted */
+int cx18_waitq(wait_queue_head_t *waitq);
+
+/* Read Hauppauge eeprom */
+struct tveeprom; /* forward reference */
+void cx18_read_eeprom(struct cx18 *cx, struct tveeprom *tv);
+
+/* First-open initialization: load firmware, etc. */
+int cx18_init_on_first_open(struct cx18 *cx);
+
+/* This is a PCI post thing, where if the pci register is not read, then
+   the write doesn't always take effect right away. By reading back the
+   register any pending PCI writes will be performed (in order), and so
+   you can be sure that the writes are guaranteed to be done.
+
+   Rarely needed, only in some timing sensitive cases.
+   Apparently if this is not done some motherboards seem
+   to kill the firmware and get into the broken state until computer is
+   rebooted. */
+#define write_sync(val, reg) \
+       do { writel(val, reg); readl(reg); } while (0)
+
+#define read_reg(reg) readl(cx->reg_mem + (reg))
+#define write_reg(val, reg) writel(val, cx->reg_mem + (reg))
+#define write_reg_sync(val, reg) \
+       do { write_reg(val, reg); read_reg(reg); } while (0)
+
+#define read_enc(addr) readl(cx->enc_mem + (u32)(addr))
+#define write_enc(val, addr) writel(val, cx->enc_mem + (u32)(addr))
+#define write_enc_sync(val, addr) \
+       do { write_enc(val, addr); read_enc(addr); } while (0)
+
+#define sw1_irq_enable(val) do { \
+       write_reg(val, SW1_INT_STATUS); \
+       write_reg(read_reg(SW1_INT_ENABLE_PCI) | (val), SW1_INT_ENABLE_PCI); \
+} while (0)
+
+#define sw1_irq_disable(val) \
+       write_reg(read_reg(SW1_INT_ENABLE_PCI) & ~(val), SW1_INT_ENABLE_PCI);
+
+#define sw2_irq_enable(val) do { \
+       write_reg(val, SW2_INT_STATUS); \
+       write_reg(read_reg(SW2_INT_ENABLE_PCI) | (val), SW2_INT_ENABLE_PCI); \
+} while (0)
+
+#define sw2_irq_disable(val) \
+       write_reg(read_reg(SW2_INT_ENABLE_PCI) & ~(val), SW2_INT_ENABLE_PCI);
+
+#define setup_page(addr) do { \
+    u32 val = read_reg(0xD000F8) & ~0x1f00; \
+    write_reg(val | (((addr) >> 17) & 0x1f00), 0xD000F8); \
+} while (0)
+
+#endif /* CX18_DRIVER_H */
diff --git a/drivers/media/video/cx18/cx18-dvb.c b/drivers/media/video/cx18/cx18-dvb.c
new file mode 100644 (file)
index 0000000..65efe69
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ *  cx18 functions for DVB support
+ *
+ *  Copyright (c) 2008 Steven Toth <stoth@hauppauge.com>
+ *
+ *  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 "cx18-version.h"
+#include "cx18-dvb.h"
+#include "cx18-streams.h"
+#include "cx18-cards.h"
+#include "s5h1409.h"
+
+/* Wait until the MXL500X driver is merged */
+#ifdef HAVE_MXL500X
+#include "mxl500x.h"
+#endif
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define CX18_REG_DMUX_NUM_PORT_0_CONTROL 0xd5a000
+
+#ifdef HAVE_MXL500X
+static struct mxl500x_config hauppauge_hvr1600_tuner = {
+       .delsys    = MXL500x_MODE_ATSC,
+       .octf      = MXL500x_OCTF_CH,
+       .xtal_freq = 16000000,
+       .iflo_freq = 5380000,
+       .ref_freq  = 322800000,
+       .rssi_ena  = MXL_RSSI_ENABLE,
+       .addr      = 0xC6 >> 1,
+};
+
+static struct s5h1409_config hauppauge_hvr1600_config = {
+       .demod_address = 0x32 >> 1,
+       .output_mode   = S5H1409_SERIAL_OUTPUT,
+       .gpio          = S5H1409_GPIO_ON,
+       .qam_if        = 44000,
+       .inversion     = S5H1409_INVERSION_OFF,
+       .status_mode   = S5H1409_DEMODLOCKING,
+       .mpeg_timing   = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK
+
+};
+#endif
+
+static int dvb_register(struct cx18_stream *stream);
+
+/* Kernel DVB framework calls this when the feed needs to start.
+ * The CX18 framework should enable the transport DMA handling
+ * and queue processing.
+ */
+static int cx18_dvb_start_feed(struct dvb_demux_feed *feed)
+{
+       struct dvb_demux *demux = feed->demux;
+       struct cx18_stream *stream = (struct cx18_stream *) demux->priv;
+       struct cx18 *cx = stream->cx;
+       int ret = -EINVAL;
+       u32 v;
+
+       CX18_DEBUG_INFO("Start feed: pid = 0x%x index = %d\n",
+                       feed->pid, feed->index);
+       switch (cx->card->type) {
+       case CX18_CARD_HVR_1600_ESMT:
+       case CX18_CARD_HVR_1600_SAMSUNG:
+               v = read_reg(CX18_REG_DMUX_NUM_PORT_0_CONTROL);
+               v |= 0x00400000; /* Serial Mode */
+               v |= 0x00002000; /* Data Length - Byte */
+               v |= 0x00010000; /* Error - Polarity */
+               v |= 0x00020000; /* Error - Passthru */
+               v |= 0x000c0000; /* Error - Ignore */
+               write_reg(v, CX18_REG_DMUX_NUM_PORT_0_CONTROL);
+               break;
+
+       default:
+               /* Assumption - Parallel transport - Signalling
+                * undefined or default.
+                */
+               break;
+       }
+
+       if (!demux->dmx.frontend)
+               return -EINVAL;
+
+       if (stream) {
+               mutex_lock(&stream->dvb.feedlock);
+               if (stream->dvb.feeding++ == 0) {
+                       CX18_DEBUG_INFO("Starting Transport DMA\n");
+                       ret = cx18_start_v4l2_encode_stream(stream);
+               } else
+                       ret = 0;
+               mutex_unlock(&stream->dvb.feedlock);
+       }
+
+       return ret;
+}
+
+/* Kernel DVB framework calls this when the feed needs to stop. */
+static int cx18_dvb_stop_feed(struct dvb_demux_feed *feed)
+{
+       struct dvb_demux *demux = feed->demux;
+       struct cx18_stream *stream = (struct cx18_stream *)demux->priv;
+       struct cx18 *cx = stream->cx;
+       int ret = -EINVAL;
+
+       CX18_DEBUG_INFO("Stop feed: pid = 0x%x index = %d\n",
+                       feed->pid, feed->index);
+
+       if (stream) {
+               mutex_lock(&stream->dvb.feedlock);
+               if (--stream->dvb.feeding == 0) {
+                       CX18_DEBUG_INFO("Stopping Transport DMA\n");
+                       ret = cx18_stop_v4l2_encode_stream(stream, 0);
+               } else
+                       ret = 0;
+               mutex_unlock(&stream->dvb.feedlock);
+       }
+
+       return ret;
+}
+
+int cx18_dvb_register(struct cx18_stream *stream)
+{
+       struct cx18 *cx = stream->cx;
+       struct cx18_dvb *dvb = &stream->dvb;
+       struct dvb_adapter *dvb_adapter;
+       struct dvb_demux *dvbdemux;
+       struct dmx_demux *dmx;
+       int ret;
+
+       if (!dvb)
+               return -EINVAL;
+
+       ret = dvb_register_adapter(&dvb->dvb_adapter,
+                       CX18_DRIVER_NAME,
+                       THIS_MODULE, &cx->dev->dev, adapter_nr);
+       if (ret < 0)
+               goto err_out;
+
+       dvb_adapter = &dvb->dvb_adapter;
+
+       dvbdemux = &dvb->demux;
+
+       dvbdemux->priv = (void *)stream;
+
+       dvbdemux->filternum = 256;
+       dvbdemux->feednum = 256;
+       dvbdemux->start_feed = cx18_dvb_start_feed;
+       dvbdemux->stop_feed = cx18_dvb_stop_feed;
+       dvbdemux->dmx.capabilities = (DMX_TS_FILTERING |
+               DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING);
+       ret = dvb_dmx_init(dvbdemux);
+       if (ret < 0)
+               goto err_dvb_unregister_adapter;
+
+       dmx = &dvbdemux->dmx;
+
+       dvb->hw_frontend.source = DMX_FRONTEND_0;
+       dvb->mem_frontend.source = DMX_MEMORY_FE;
+       dvb->dmxdev.filternum = 256;
+       dvb->dmxdev.demux = dmx;
+
+       ret = dvb_dmxdev_init(&dvb->dmxdev, dvb_adapter);
+       if (ret < 0)
+               goto err_dvb_dmx_release;
+
+       ret = dmx->add_frontend(dmx, &dvb->hw_frontend);
+       if (ret < 0)
+               goto err_dvb_dmxdev_release;
+
+       ret = dmx->add_frontend(dmx, &dvb->mem_frontend);
+       if (ret < 0)
+               goto err_remove_hw_frontend;
+
+       ret = dmx->connect_frontend(dmx, &dvb->hw_frontend);
+       if (ret < 0)
+               goto err_remove_mem_frontend;
+
+       ret = dvb_register(stream);
+       if (ret < 0)
+               goto err_disconnect_frontend;
+
+       dvb_net_init(dvb_adapter, &dvb->dvbnet, dmx);
+
+       CX18_INFO("DVB Frontend registered\n");
+       mutex_init(&dvb->feedlock);
+       dvb->enabled = 1;
+       return ret;
+
+err_disconnect_frontend:
+       dmx->disconnect_frontend(dmx);
+err_remove_mem_frontend:
+       dmx->remove_frontend(dmx, &dvb->mem_frontend);
+err_remove_hw_frontend:
+       dmx->remove_frontend(dmx, &dvb->hw_frontend);
+err_dvb_dmxdev_release:
+       dvb_dmxdev_release(&dvb->dmxdev);
+err_dvb_dmx_release:
+       dvb_dmx_release(dvbdemux);
+err_dvb_unregister_adapter:
+       dvb_unregister_adapter(dvb_adapter);
+err_out:
+       return ret;
+}
+
+void cx18_dvb_unregister(struct cx18_stream *stream)
+{
+       struct cx18 *cx = stream->cx;
+       struct cx18_dvb *dvb = &stream->dvb;
+       struct dvb_adapter *dvb_adapter;
+       struct dvb_demux *dvbdemux;
+       struct dmx_demux *dmx;
+
+       CX18_INFO("unregister DVB\n");
+
+       dvb_adapter = &dvb->dvb_adapter;
+       dvbdemux = &dvb->demux;
+       dmx = &dvbdemux->dmx;
+
+       dmx->close(dmx);
+       dvb_net_release(&dvb->dvbnet);
+       dmx->remove_frontend(dmx, &dvb->mem_frontend);
+       dmx->remove_frontend(dmx, &dvb->hw_frontend);
+       dvb_dmxdev_release(&dvb->dmxdev);
+       dvb_dmx_release(dvbdemux);
+       dvb_unregister_frontend(dvb->fe);
+       dvb_frontend_detach(dvb->fe);
+       dvb_unregister_adapter(dvb_adapter);
+}
+
+/* All the DVB attach calls go here, this function get's modified
+ * for each new card. No other function in this file needs
+ * to change.
+ */
+static int dvb_register(struct cx18_stream *stream)
+{
+       struct cx18_dvb *dvb = &stream->dvb;
+       struct cx18 *cx = stream->cx;
+       int ret = 0;
+
+       switch (cx->card->type) {
+/* Wait until the MXL500X driver is merged */
+#ifdef HAVE_MXL500X
+       case CX18_CARD_HVR_1600_ESMT:
+       case CX18_CARD_HVR_1600_SAMSUNG:
+               dvb->fe = dvb_attach(s5h1409_attach,
+                       &hauppauge_hvr1600_config,
+                       &cx->i2c_adap[0]);
+               if (dvb->fe != NULL) {
+                       dvb_attach(mxl500x_attach, dvb->fe,
+                               &hauppauge_hvr1600_tuner,
+                               &cx->i2c_adap[0]);
+                       ret = 0;
+               }
+               break;
+#endif
+       default:
+               /* No Digital Tv Support */
+               break;
+       }
+
+       if (dvb->fe == NULL) {
+               CX18_ERR("frontend initialization failed\n");
+               return -1;
+       }
+
+       ret = dvb_register_frontend(&dvb->dvb_adapter, dvb->fe);
+       if (ret < 0) {
+               if (dvb->fe->ops.release)
+                       dvb->fe->ops.release(dvb->fe);
+               return ret;
+       }
+
+       return ret;
+}
diff --git a/drivers/media/video/cx18/cx18-dvb.h b/drivers/media/video/cx18/cx18-dvb.h
new file mode 100644 (file)
index 0000000..d6a6ccd
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ *  cx18 functions for DVB support
+ *
+ *  Copyright (c) 2008 Steven Toth <stoth@hauppauge.com>
+ *
+ *  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 "cx18-driver.h"
+
+int cx18_dvb_register(struct cx18_stream *stream);
+void cx18_dvb_unregister(struct cx18_stream *stream);
diff --git a/drivers/media/video/cx18/cx18-fileops.c b/drivers/media/video/cx18/cx18-fileops.c
new file mode 100644 (file)
index 0000000..6930306
--- /dev/null
@@ -0,0 +1,711 @@
+/*
+ *  cx18 file operation functions
+ *
+ *  Derived from ivtv-fileops.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include "cx18-driver.h"
+#include "cx18-fileops.h"
+#include "cx18-i2c.h"
+#include "cx18-queue.h"
+#include "cx18-vbi.h"
+#include "cx18-audio.h"
+#include "cx18-mailbox.h"
+#include "cx18-scb.h"
+#include "cx18-streams.h"
+#include "cx18-controls.h"
+#include "cx18-ioctl.h"
+#include "cx18-cards.h"
+
+/* This function tries to claim the stream for a specific file descriptor.
+   If no one else is using this stream then the stream is claimed and
+   associated VBI streams are also automatically claimed.
+   Possible error returns: -EBUSY if someone else has claimed
+   the stream or 0 on success. */
+int cx18_claim_stream(struct cx18_open_id *id, int type)
+{
+       struct cx18 *cx = id->cx;
+       struct cx18_stream *s = &cx->streams[type];
+       struct cx18_stream *s_vbi;
+       int vbi_type;
+
+       if (test_and_set_bit(CX18_F_S_CLAIMED, &s->s_flags)) {
+               /* someone already claimed this stream */
+               if (s->id == id->open_id) {
+                       /* yes, this file descriptor did. So that's OK. */
+                       return 0;
+               }
+               if (s->id == -1 && type == CX18_ENC_STREAM_TYPE_VBI) {
+                       /* VBI is handled already internally, now also assign
+                          the file descriptor to this stream for external
+                          reading of the stream. */
+                       s->id = id->open_id;
+                       CX18_DEBUG_INFO("Start Read VBI\n");
+                       return 0;
+               }
+               /* someone else is using this stream already */
+               CX18_DEBUG_INFO("Stream %d is busy\n", type);
+               return -EBUSY;
+       }
+       s->id = id->open_id;
+
+       /* CX18_DEC_STREAM_TYPE_MPG needs to claim CX18_DEC_STREAM_TYPE_VBI,
+          CX18_ENC_STREAM_TYPE_MPG needs to claim CX18_ENC_STREAM_TYPE_VBI
+          (provided VBI insertion is on and sliced VBI is selected), for all
+          other streams we're done */
+       if (type == CX18_ENC_STREAM_TYPE_MPG &&
+                  cx->vbi.insert_mpeg && cx->vbi.sliced_in->service_set) {
+               vbi_type = CX18_ENC_STREAM_TYPE_VBI;
+       } else {
+               return 0;
+       }
+       s_vbi = &cx->streams[vbi_type];
+
+       set_bit(CX18_F_S_CLAIMED, &s_vbi->s_flags);
+
+       /* mark that it is used internally */
+       set_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags);
+       return 0;
+}
+
+/* This function releases a previously claimed stream. It will take into
+   account associated VBI streams. */
+void cx18_release_stream(struct cx18_stream *s)
+{
+       struct cx18 *cx = s->cx;
+       struct cx18_stream *s_vbi;
+
+       s->id = -1;
+       if (s->type == CX18_ENC_STREAM_TYPE_VBI &&
+               test_bit(CX18_F_S_INTERNAL_USE, &s->s_flags)) {
+               /* this stream is still in use internally */
+               return;
+       }
+       if (!test_and_clear_bit(CX18_F_S_CLAIMED, &s->s_flags)) {
+               CX18_DEBUG_WARN("Release stream %s not in use!\n", s->name);
+               return;
+       }
+
+       cx18_flush_queues(s);
+
+       /* CX18_ENC_STREAM_TYPE_MPG needs to release CX18_ENC_STREAM_TYPE_VBI,
+          for all other streams we're done */
+       if (s->type == CX18_ENC_STREAM_TYPE_MPG)
+               s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
+       else
+               return;
+
+       /* clear internal use flag */
+       if (!test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags)) {
+               /* was already cleared */
+               return;
+       }
+       if (s_vbi->id != -1) {
+               /* VBI stream still claimed by a file descriptor */
+               return;
+       }
+       clear_bit(CX18_F_S_CLAIMED, &s_vbi->s_flags);
+       cx18_flush_queues(s_vbi);
+}
+
+static void cx18_dualwatch(struct cx18 *cx)
+{
+       struct v4l2_tuner vt;
+       u16 new_bitmap;
+       u16 new_stereo_mode;
+       const u16 stereo_mask = 0x0300;
+       const u16 dual = 0x0200;
+
+       new_stereo_mode = cx->params.audio_properties & stereo_mask;
+       memset(&vt, 0, sizeof(vt));
+       cx18_call_i2c_clients(cx, VIDIOC_G_TUNER, &vt);
+       if (vt.audmode == V4L2_TUNER_MODE_LANG1_LANG2 &&
+                       (vt.rxsubchans & V4L2_TUNER_SUB_LANG2))
+               new_stereo_mode = dual;
+
+       if (new_stereo_mode == cx->dualwatch_stereo_mode)
+               return;
+
+       new_bitmap = new_stereo_mode | (cx->params.audio_properties & ~stereo_mask);
+
+       CX18_DEBUG_INFO("dualwatch: change stereo flag from 0x%x to 0x%x. new audio_bitmask=0x%ux\n",
+                          cx->dualwatch_stereo_mode, new_stereo_mode, new_bitmap);
+
+       if (cx18_vapi(cx, CX18_CPU_SET_AUDIO_PARAMETERS, 2,
+                               cx18_find_handle(cx), new_bitmap) == 0) {
+               cx->dualwatch_stereo_mode = new_stereo_mode;
+               return;
+       }
+       CX18_DEBUG_INFO("dualwatch: changing stereo flag failed\n");
+}
+
+
+static struct cx18_buffer *cx18_get_buffer(struct cx18_stream *s, int non_block, int *err)
+{
+       struct cx18 *cx = s->cx;
+       struct cx18_stream *s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
+       struct cx18_buffer *buf;
+       DEFINE_WAIT(wait);
+
+       *err = 0;
+       while (1) {
+               if (s->type == CX18_ENC_STREAM_TYPE_MPG) {
+
+                       if (time_after(jiffies, cx->dualwatch_jiffies + msecs_to_jiffies(1000))) {
+                               cx->dualwatch_jiffies = jiffies;
+                               cx18_dualwatch(cx);
+                       }
+                       if (test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) &&
+                           !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) {
+                               while ((buf = cx18_dequeue(s_vbi, &s_vbi->q_full))) {
+                                       /* byteswap and process VBI data */
+/*                                     cx18_process_vbi_data(cx, buf, s_vbi->dma_pts, s_vbi->type); */
+                                       cx18_enqueue(s_vbi, buf, &s_vbi->q_free);
+                               }
+                       }
+                       buf = &cx->vbi.sliced_mpeg_buf;
+                       if (buf->readpos != buf->bytesused)
+                               return buf;
+               }
+
+               /* do we have leftover data? */
+               buf = cx18_dequeue(s, &s->q_io);
+               if (buf)
+                       return buf;
+
+               /* do we have new data? */
+               buf = cx18_dequeue(s, &s->q_full);
+               if (buf) {
+                       if (!test_and_clear_bit(CX18_F_B_NEED_BUF_SWAP,
+                                               &buf->b_flags))
+                               return buf;
+                       if (s->type == CX18_ENC_STREAM_TYPE_MPG)
+                               /* byteswap MPG data */
+                               cx18_buf_swap(buf);
+                       else {
+                               /* byteswap and process VBI data */
+                               cx18_process_vbi_data(cx, buf,
+                                               s->dma_pts, s->type);
+                       }
+                       return buf;
+               }
+
+               /* return if end of stream */
+               if (!test_bit(CX18_F_S_STREAMING, &s->s_flags)) {
+                       CX18_DEBUG_INFO("EOS %s\n", s->name);
+                       return NULL;
+               }
+
+               /* return if file was opened with O_NONBLOCK */
+               if (non_block) {
+                       *err = -EAGAIN;
+                       return NULL;
+               }
+
+               /* wait for more data to arrive */
+               prepare_to_wait(&s->waitq, &wait, TASK_INTERRUPTIBLE);
+               /* New buffers might have become available before we were added
+                  to the waitqueue */
+               if (!s->q_full.buffers)
+                       schedule();
+               finish_wait(&s->waitq, &wait);
+               if (signal_pending(current)) {
+                       /* return if a signal was received */
+                       CX18_DEBUG_INFO("User stopped %s\n", s->name);
+                       *err = -EINTR;
+                       return NULL;
+               }
+       }
+}
+
+static void cx18_setup_sliced_vbi_buf(struct cx18 *cx)
+{
+       int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES;
+
+       cx->vbi.sliced_mpeg_buf.buf = cx->vbi.sliced_mpeg_data[idx];
+       cx->vbi.sliced_mpeg_buf.bytesused = cx->vbi.sliced_mpeg_size[idx];
+       cx->vbi.sliced_mpeg_buf.readpos = 0;
+}
+
+static size_t cx18_copy_buf_to_user(struct cx18_stream *s,
+               struct cx18_buffer *buf, char __user *ubuf, size_t ucount)
+{
+       struct cx18 *cx = s->cx;
+       size_t len = buf->bytesused - buf->readpos;
+
+       if (len > ucount)
+               len = ucount;
+       if (cx->vbi.insert_mpeg && s->type == CX18_ENC_STREAM_TYPE_MPG &&
+           cx->vbi.sliced_in->service_set && buf != &cx->vbi.sliced_mpeg_buf) {
+               const char *start = buf->buf + buf->readpos;
+               const char *p = start + 1;
+               const u8 *q;
+               u8 ch = cx->search_pack_header ? 0xba : 0xe0;
+               int stuffing, i;
+
+               while (start + len > p) {
+                       q = memchr(p, 0, start + len - p);
+                       if (q == NULL)
+                               break;
+                       p = q + 1;
+                       if ((char *)q + 15 >= buf->buf + buf->bytesused ||
+                           q[1] != 0 || q[2] != 1 || q[3] != ch)
+                               continue;
+                       if (!cx->search_pack_header) {
+                               if ((q[6] & 0xc0) != 0x80)
+                                       continue;
+                               if (((q[7] & 0xc0) == 0x80 &&
+                                    (q[9] & 0xf0) == 0x20) ||
+                                   ((q[7] & 0xc0) == 0xc0 &&
+                                    (q[9] & 0xf0) == 0x30)) {
+                                       ch = 0xba;
+                                       cx->search_pack_header = 1;
+                                       p = q + 9;
+                               }
+                               continue;
+                       }
+                       stuffing = q[13] & 7;
+                       /* all stuffing bytes must be 0xff */
+                       for (i = 0; i < stuffing; i++)
+                               if (q[14 + i] != 0xff)
+                                       break;
+                       if (i == stuffing &&
+                           (q[4] & 0xc4) == 0x44 &&
+                           (q[12] & 3) == 3 &&
+                           q[14 + stuffing] == 0 &&
+                           q[15 + stuffing] == 0 &&
+                           q[16 + stuffing] == 1) {
+                               cx->search_pack_header = 0;
+                               len = (char *)q - start;
+                               cx18_setup_sliced_vbi_buf(cx);
+                               break;
+                       }
+               }
+       }
+       if (copy_to_user(ubuf, (u8 *)buf->buf + buf->readpos, len)) {
+               CX18_DEBUG_WARN("copy %zd bytes to user failed for %s\n",
+                               len, s->name);
+               return -EFAULT;
+       }
+       buf->readpos += len;
+       if (s->type == CX18_ENC_STREAM_TYPE_MPG &&
+           buf != &cx->vbi.sliced_mpeg_buf)
+               cx->mpg_data_received += len;
+       return len;
+}
+
+static ssize_t cx18_read(struct cx18_stream *s, char __user *ubuf,
+               size_t tot_count, int non_block)
+{
+       struct cx18 *cx = s->cx;
+       size_t tot_written = 0;
+       int single_frame = 0;
+
+       if (atomic_read(&cx->capturing) == 0 && s->id == -1) {
+               /* shouldn't happen */
+               CX18_DEBUG_WARN("Stream %s not initialized before read\n",
+                               s->name);
+               return -EIO;
+       }
+
+       /* Each VBI buffer is one frame, the v4l2 API says that for VBI the
+          frames should arrive one-by-one, so make sure we never output more
+          than one VBI frame at a time */
+       if (s->type == CX18_ENC_STREAM_TYPE_VBI &&
+           cx->vbi.sliced_in->service_set)
+               single_frame = 1;
+
+       for (;;) {
+               struct cx18_buffer *buf;
+               int rc;
+
+               buf = cx18_get_buffer(s, non_block, &rc);
+               /* if there is no data available... */
+               if (buf == NULL) {
+                       /* if we got data, then return that regardless */
+                       if (tot_written)
+                               break;
+                       /* EOS condition */
+                       if (rc == 0) {
+                               clear_bit(CX18_F_S_STREAMOFF, &s->s_flags);
+                               clear_bit(CX18_F_S_APPL_IO, &s->s_flags);
+                               cx18_release_stream(s);
+                       }
+                       /* set errno */
+                       return rc;
+               }
+
+               rc = cx18_copy_buf_to_user(s, buf, ubuf + tot_written,
+                               tot_count - tot_written);
+
+               if (buf != &cx->vbi.sliced_mpeg_buf) {
+                       if (buf->readpos == buf->bytesused) {
+                               cx18_buf_sync_for_device(s, buf);
+                               cx18_enqueue(s, buf, &s->q_free);
+                               cx18_vapi(cx, CX18_CPU_DE_SET_MDL, 5,
+                                       s->handle,
+                                       (void *)&cx->scb->cpu_mdl[buf->id] - cx->enc_mem,
+                                       1, buf->id, s->buf_size);
+                       } else
+                               cx18_enqueue(s, buf, &s->q_io);
+               } else if (buf->readpos == buf->bytesused) {
+                       int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES;
+
+                       cx->vbi.sliced_mpeg_size[idx] = 0;
+                       cx->vbi.inserted_frame++;
+                       cx->vbi_data_inserted += buf->bytesused;
+               }
+               if (rc < 0)
+                       return rc;
+               tot_written += rc;
+
+               if (tot_written == tot_count || single_frame)
+                       break;
+       }
+       return tot_written;
+}
+
+static ssize_t cx18_read_pos(struct cx18_stream *s, char __user *ubuf,
+               size_t count, loff_t *pos, int non_block)
+{
+       ssize_t rc = count ? cx18_read(s, ubuf, count, non_block) : 0;
+       struct cx18 *cx = s->cx;
+
+       CX18_DEBUG_HI_FILE("read %zd from %s, got %zd\n", count, s->name, rc);
+       if (rc > 0)
+               pos += rc;
+       return rc;
+}
+
+int cx18_start_capture(struct cx18_open_id *id)
+{
+       struct cx18 *cx = id->cx;
+       struct cx18_stream *s = &cx->streams[id->type];
+       struct cx18_stream *s_vbi;
+
+       if (s->type == CX18_ENC_STREAM_TYPE_RAD) {
+               /* you cannot read from these stream types. */
+               return -EPERM;
+       }
+
+       /* Try to claim this stream. */
+       if (cx18_claim_stream(id, s->type))
+               return -EBUSY;
+
+       /* If capture is already in progress, then we also have to
+          do nothing extra. */
+       if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) ||
+           test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) {
+               set_bit(CX18_F_S_APPL_IO, &s->s_flags);
+               return 0;
+       }
+
+       /* Start VBI capture if required */
+       s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
+       if (s->type == CX18_ENC_STREAM_TYPE_MPG &&
+           test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) &&
+           !test_and_set_bit(CX18_F_S_STREAMING, &s_vbi->s_flags)) {
+               /* Note: the CX18_ENC_STREAM_TYPE_VBI is claimed
+                  automatically when the MPG stream is claimed.
+                  We only need to start the VBI capturing. */
+               if (cx18_start_v4l2_encode_stream(s_vbi)) {
+                       CX18_DEBUG_WARN("VBI capture start failed\n");
+
+                       /* Failure, clean up and return an error */
+                       clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags);
+                       clear_bit(CX18_F_S_STREAMING, &s->s_flags);
+                       /* also releases the associated VBI stream */
+                       cx18_release_stream(s);
+                       return -EIO;
+               }
+               CX18_DEBUG_INFO("VBI insertion started\n");
+       }
+
+       /* Tell the card to start capturing */
+       if (!cx18_start_v4l2_encode_stream(s)) {
+               /* We're done */
+               set_bit(CX18_F_S_APPL_IO, &s->s_flags);
+               /* Resume a possibly paused encoder */
+               if (test_and_clear_bit(CX18_F_I_ENC_PAUSED, &cx->i_flags))
+                       cx18_vapi(cx, CX18_CPU_CAPTURE_PAUSE, 1, s->handle);
+               return 0;
+       }
+
+       /* failure, clean up */
+       CX18_DEBUG_WARN("Failed to start capturing for stream %s\n", s->name);
+
+       /* Note: the CX18_ENC_STREAM_TYPE_VBI is released
+          automatically when the MPG stream is released.
+          We only need to stop the VBI capturing. */
+       if (s->type == CX18_ENC_STREAM_TYPE_MPG &&
+           test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags)) {
+               cx18_stop_v4l2_encode_stream(s_vbi, 0);
+               clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags);
+       }
+       clear_bit(CX18_F_S_STREAMING, &s->s_flags);
+       cx18_release_stream(s);
+       return -EIO;
+}
+
+ssize_t cx18_v4l2_read(struct file *filp, char __user *buf, size_t count,
+               loff_t *pos)
+{
+       struct cx18_open_id *id = filp->private_data;
+       struct cx18 *cx = id->cx;
+       struct cx18_stream *s = &cx->streams[id->type];
+       int rc;
+
+       CX18_DEBUG_HI_FILE("read %zd bytes from %s\n", count, s->name);
+
+       mutex_lock(&cx->serialize_lock);
+       rc = cx18_start_capture(id);
+       mutex_unlock(&cx->serialize_lock);
+       if (rc)
+               return rc;
+       return cx18_read_pos(s, buf, count, pos, filp->f_flags & O_NONBLOCK);
+}
+
+unsigned int cx18_v4l2_enc_poll(struct file *filp, poll_table *wait)
+{
+       struct cx18_open_id *id = filp->private_data;
+       struct cx18 *cx = id->cx;
+       struct cx18_stream *s = &cx->streams[id->type];
+       int eof = test_bit(CX18_F_S_STREAMOFF, &s->s_flags);
+
+       /* Start a capture if there is none */
+       if (!eof && !test_bit(CX18_F_S_STREAMING, &s->s_flags)) {
+               int rc;
+
+               mutex_lock(&cx->serialize_lock);
+               rc = cx18_start_capture(id);
+               mutex_unlock(&cx->serialize_lock);
+               if (rc) {
+                       CX18_DEBUG_INFO("Could not start capture for %s (%d)\n",
+                                       s->name, rc);
+                       return POLLERR;
+               }
+               CX18_DEBUG_FILE("Encoder poll started capture\n");
+       }
+
+       /* add stream's waitq to the poll list */
+       CX18_DEBUG_HI_FILE("Encoder poll\n");
+       poll_wait(filp, &s->waitq, wait);
+
+       if (s->q_full.length || s->q_io.length)
+               return POLLIN | POLLRDNORM;
+       if (eof)
+               return POLLHUP;
+       return 0;
+}
+
+void cx18_stop_capture(struct cx18_open_id *id, int gop_end)
+{
+       struct cx18 *cx = id->cx;
+       struct cx18_stream *s = &cx->streams[id->type];
+
+       CX18_DEBUG_IOCTL("close() of %s\n", s->name);
+
+       /* 'Unclaim' this stream */
+
+       /* Stop capturing */
+       if (test_bit(CX18_F_S_STREAMING, &s->s_flags)) {
+               struct cx18_stream *s_vbi =
+                       &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
+
+               CX18_DEBUG_INFO("close stopping capture\n");
+               /* Special case: a running VBI capture for VBI insertion
+                  in the mpeg stream. Need to stop that too. */
+               if (id->type == CX18_ENC_STREAM_TYPE_MPG &&
+                   test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags) &&
+                   !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) {
+                       CX18_DEBUG_INFO("close stopping embedded VBI capture\n");
+                       cx18_stop_v4l2_encode_stream(s_vbi, 0);
+               }
+               if (id->type == CX18_ENC_STREAM_TYPE_VBI &&
+                   test_bit(CX18_F_S_INTERNAL_USE, &s->s_flags))
+                       /* Also used internally, don't stop capturing */
+                       s->id = -1;
+               else
+                       cx18_stop_v4l2_encode_stream(s, gop_end);
+       }
+       if (!gop_end) {
+               clear_bit(CX18_F_S_APPL_IO, &s->s_flags);
+               clear_bit(CX18_F_S_STREAMOFF, &s->s_flags);
+               cx18_release_stream(s);
+       }
+}
+
+int cx18_v4l2_close(struct inode *inode, struct file *filp)
+{
+       struct cx18_open_id *id = filp->private_data;
+       struct cx18 *cx = id->cx;
+       struct cx18_stream *s = &cx->streams[id->type];
+
+       CX18_DEBUG_IOCTL("close() of %s\n", s->name);
+
+       v4l2_prio_close(&cx->prio, &id->prio);
+
+       /* Easy case first: this stream was never claimed by us */
+       if (s->id != id->open_id) {
+               kfree(id);
+               return 0;
+       }
+
+       /* 'Unclaim' this stream */
+
+       /* Stop radio */
+       mutex_lock(&cx->serialize_lock);
+       if (id->type == CX18_ENC_STREAM_TYPE_RAD) {
+               /* Closing radio device, return to TV mode */
+               cx18_mute(cx);
+               /* Mark that the radio is no longer in use */
+               clear_bit(CX18_F_I_RADIO_USER, &cx->i_flags);
+               /* Switch tuner to TV */
+               cx18_call_i2c_clients(cx, VIDIOC_S_STD, &cx->std);
+               /* Select correct audio input (i.e. TV tuner or Line in) */
+               cx18_audio_set_io(cx);
+               if (atomic_read(&cx->capturing) > 0) {
+                       /* Undo video mute */
+                       cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, s->handle,
+                               cx->params.video_mute |
+                                       (cx->params.video_mute_yuv << 8));
+               }
+               /* Done! Unmute and continue. */
+               cx18_unmute(cx);
+               cx18_release_stream(s);
+       } else {
+               cx18_stop_capture(id, 0);
+       }
+       kfree(id);
+       mutex_unlock(&cx->serialize_lock);
+       return 0;
+}
+
+static int cx18_serialized_open(struct cx18_stream *s, struct file *filp)
+{
+       struct cx18 *cx = s->cx;
+       struct cx18_open_id *item;
+
+       CX18_DEBUG_FILE("open %s\n", s->name);
+
+       /* Allocate memory */
+       item = kmalloc(sizeof(struct cx18_open_id), GFP_KERNEL);
+       if (NULL == item) {
+               CX18_DEBUG_WARN("nomem on v4l2 open\n");
+               return -ENOMEM;
+       }
+       item->cx = cx;
+       item->type = s->type;
+       v4l2_prio_open(&cx->prio, &item->prio);
+
+       item->open_id = cx->open_id++;
+       filp->private_data = item;
+
+       if (item->type == CX18_ENC_STREAM_TYPE_RAD) {
+               /* Try to claim this stream */
+               if (cx18_claim_stream(item, item->type)) {
+                       /* No, it's already in use */
+                       kfree(item);
+                       return -EBUSY;
+               }
+
+               if (!test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) {
+                       if (atomic_read(&cx->capturing) > 0) {
+                               /* switching to radio while capture is
+                                  in progress is not polite */
+                               cx18_release_stream(s);
+                               kfree(item);
+                               return -EBUSY;
+                       }
+               }
+
+               /* Mark that the radio is being used. */
+               set_bit(CX18_F_I_RADIO_USER, &cx->i_flags);
+               /* We have the radio */
+               cx18_mute(cx);
+               /* Switch tuner to radio */
+               cx18_call_i2c_clients(cx, AUDC_SET_RADIO, NULL);
+               /* Select the correct audio input (i.e. radio tuner) */
+               cx18_audio_set_io(cx);
+               /* Done! Unmute and continue. */
+               cx18_unmute(cx);
+       }
+       return 0;
+}
+
+int cx18_v4l2_open(struct inode *inode, struct file *filp)
+{
+       int res, x, y = 0;
+       struct cx18 *cx = NULL;
+       struct cx18_stream *s = NULL;
+       int minor = iminor(inode);
+
+       /* Find which card this open was on */
+       spin_lock(&cx18_cards_lock);
+       for (x = 0; cx == NULL && x < cx18_cards_active; x++) {
+               /* find out which stream this open was on */
+               for (y = 0; y < CX18_MAX_STREAMS; y++) {
+                       s = &cx18_cards[x]->streams[y];
+                       if (s->v4l2dev && s->v4l2dev->minor == minor) {
+                               cx = cx18_cards[x];
+                               break;
+                       }
+               }
+       }
+       spin_unlock(&cx18_cards_lock);
+
+       if (cx == NULL) {
+               /* Couldn't find a device registered
+                  on that minor, shouldn't happen! */
+               printk(KERN_WARNING "No cx18 device found on minor %d\n",
+                               minor);
+               return -ENXIO;
+       }
+
+       mutex_lock(&cx->serialize_lock);
+       if (cx18_init_on_first_open(cx)) {
+               CX18_ERR("Failed to initialize on minor %d\n", minor);
+               mutex_unlock(&cx->serialize_lock);
+               return -ENXIO;
+       }
+       res = cx18_serialized_open(s, filp);
+       mutex_unlock(&cx->serialize_lock);
+       return res;
+}
+
+void cx18_mute(struct cx18 *cx)
+{
+       if (atomic_read(&cx->capturing))
+               cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2,
+                               cx18_find_handle(cx), 1);
+       CX18_DEBUG_INFO("Mute\n");
+}
+
+void cx18_unmute(struct cx18 *cx)
+{
+       if (atomic_read(&cx->capturing)) {
+               cx18_msleep_timeout(100, 0);
+               cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 2,
+                               cx18_find_handle(cx), 12);
+               cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2,
+                               cx18_find_handle(cx), 0);
+       }
+       CX18_DEBUG_INFO("Unmute\n");
+}
diff --git a/drivers/media/video/cx18/cx18-fileops.h b/drivers/media/video/cx18/cx18-fileops.h
new file mode 100644 (file)
index 0000000..16cdafb
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ *  cx18 file operation functions
+ *
+ *  Derived from ivtv-fileops.h
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+/* Testing/Debugging */
+int cx18_v4l2_open(struct inode *inode, struct file *filp);
+ssize_t cx18_v4l2_read(struct file *filp, char __user *buf, size_t count,
+                     loff_t *pos);
+ssize_t cx18_v4l2_write(struct file *filp, const char __user *buf, size_t count,
+                      loff_t *pos);
+int cx18_v4l2_close(struct inode *inode, struct file *filp);
+unsigned int cx18_v4l2_enc_poll(struct file *filp, poll_table *wait);
+int cx18_start_capture(struct cx18_open_id *id);
+void cx18_stop_capture(struct cx18_open_id *id, int gop_end);
+void cx18_mute(struct cx18 *cx);
+void cx18_unmute(struct cx18 *cx);
+
+/* Utilities */
+
+/* Try to claim a stream for the filehandle. Return 0 on success,
+   -EBUSY if stream already claimed. Once a stream is claimed, it
+   remains claimed until the associated filehandle is closed. */
+int cx18_claim_stream(struct cx18_open_id *id, int type);
+
+/* Release a previously claimed stream. */
+void cx18_release_stream(struct cx18_stream *s);
diff --git a/drivers/media/video/cx18/cx18-firmware.c b/drivers/media/video/cx18/cx18-firmware.c
new file mode 100644 (file)
index 0000000..2694ce3
--- /dev/null
@@ -0,0 +1,373 @@
+/*
+ *  cx18 firmware functions
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include "cx18-driver.h"
+#include "cx18-scb.h"
+#include "cx18-irq.h"
+#include "cx18-firmware.h"
+#include "cx18-cards.h"
+#include <linux/firmware.h>
+
+#define CX18_PROC_SOFT_RESET           0xc70010
+#define CX18_DDR_SOFT_RESET            0xc70014
+#define CX18_CLOCK_SELECT1             0xc71000
+#define CX18_CLOCK_SELECT2             0xc71004
+#define CX18_HALF_CLOCK_SELECT1        0xc71008
+#define CX18_HALF_CLOCK_SELECT2        0xc7100C
+#define CX18_CLOCK_POLARITY1           0xc71010
+#define CX18_CLOCK_POLARITY2           0xc71014
+#define CX18_ADD_DELAY_ENABLE1         0xc71018
+#define CX18_ADD_DELAY_ENABLE2         0xc7101C
+#define CX18_CLOCK_ENABLE1             0xc71020
+#define CX18_CLOCK_ENABLE2             0xc71024
+
+#define CX18_REG_BUS_TIMEOUT_EN        0xc72024
+
+#define CX18_AUDIO_ENABLE              0xc72014
+#define CX18_REG_BUS_TIMEOUT_EN        0xc72024
+
+#define CX18_FAST_CLOCK_PLL_INT        0xc78000
+#define CX18_FAST_CLOCK_PLL_FRAC       0xc78004
+#define CX18_FAST_CLOCK_PLL_POST       0xc78008
+#define CX18_FAST_CLOCK_PLL_PRESCALE   0xc7800C
+#define CX18_FAST_CLOCK_PLL_ADJUST_BANDWIDTH 0xc78010
+
+#define CX18_SLOW_CLOCK_PLL_INT        0xc78014
+#define CX18_SLOW_CLOCK_PLL_FRAC       0xc78018
+#define CX18_SLOW_CLOCK_PLL_POST       0xc7801C
+#define CX18_MPEG_CLOCK_PLL_INT                0xc78040
+#define CX18_MPEG_CLOCK_PLL_FRAC       0xc78044
+#define CX18_MPEG_CLOCK_PLL_POST       0xc78048
+#define CX18_PLL_POWER_DOWN            0xc78088
+#define CX18_SW1_INT_STATUS             0xc73104
+#define CX18_SW1_INT_ENABLE_PCI         0xc7311C
+#define CX18_SW2_INT_SET                0xc73140
+#define CX18_SW2_INT_STATUS             0xc73144
+#define CX18_ADEC_CONTROL              0xc78120
+
+#define CX18_DDR_REQUEST_ENABLE        0xc80000
+#define CX18_DDR_CHIP_CONFIG           0xc80004
+#define CX18_DDR_REFRESH               0xc80008
+#define CX18_DDR_TIMING1               0xc8000C
+#define CX18_DDR_TIMING2               0xc80010
+#define CX18_DDR_POWER_REG             0xc8001C
+
+#define CX18_DDR_TUNE_LANE             0xc80048
+#define CX18_DDR_INITIAL_EMRS          0xc80054
+#define CX18_DDR_MB_PER_ROW_7          0xc8009C
+#define CX18_DDR_BASE_63_ADDR          0xc804FC
+
+#define CX18_WMB_CLIENT02              0xc90108
+#define CX18_WMB_CLIENT05              0xc90114
+#define CX18_WMB_CLIENT06              0xc90118
+#define CX18_WMB_CLIENT07              0xc9011C
+#define CX18_WMB_CLIENT08              0xc90120
+#define CX18_WMB_CLIENT09              0xc90124
+#define CX18_WMB_CLIENT10              0xc90128
+#define CX18_WMB_CLIENT11              0xc9012C
+#define CX18_WMB_CLIENT12              0xc90130
+#define CX18_WMB_CLIENT13              0xc90134
+#define CX18_WMB_CLIENT14              0xc90138
+
+#define CX18_DSP0_INTERRUPT_MASK       0xd0004C
+
+/* Encoder/decoder firmware sizes */
+#define CX18_FW_CPU_SIZE               (174716)
+#define CX18_FW_APU_SIZE               (141200)
+
+#define APU_ROM_SYNC1 0x6D676553 /* "mgeS" */
+#define APU_ROM_SYNC2 0x72646548 /* "rdeH" */
+
+struct cx18_apu_rom_seghdr {
+       u32 sync1;
+       u32 sync2;
+       u32 addr;
+       u32 size;
+};
+
+static int load_cpu_fw_direct(const char *fn, u8 __iomem *mem, struct cx18 *cx, long size)
+{
+       const struct firmware *fw = NULL;
+       int retries = 3;
+       int i, j;
+       u32 __iomem *dst = (u32 __iomem *)mem;
+       const u32 *src;
+
+retry:
+       if (!retries || request_firmware(&fw, fn, &cx->dev->dev)) {
+               CX18_ERR("Unable to open firmware %s (must be %ld bytes)\n",
+                               fn, size);
+               CX18_ERR("Did you put the firmware in the hotplug firmware directory?\n");
+               return -ENOMEM;
+       }
+
+       src = (const u32 *)fw->data;
+
+       if (fw->size != size) {
+               /* Due to race conditions in firmware loading (esp. with
+                  udev <0.95) the wrong file was sometimes loaded. So we check
+                  filesizes to see if at least the right-sized file was
+                  loaded. If not, then we retry. */
+               CX18_INFO("retry: file loaded was not %s (expected size %ld, got %zd)\n",
+                               fn, size, fw->size);
+               release_firmware(fw);
+               retries--;
+               goto retry;
+       }
+       for (i = 0; i < fw->size; i += 4096) {
+               setup_page(i);
+               for (j = i; j < fw->size && j < i + 4096; j += 4) {
+                       /* no need for endianness conversion on the ppc */
+                       __raw_writel(*src, dst);
+                       if (__raw_readl(dst) != *src) {
+                               CX18_ERR("Mismatch at offset %x\n", i);
+                               release_firmware(fw);
+                               return -EIO;
+                       }
+                       dst++;
+                       src++;
+               }
+       }
+       if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags))
+               CX18_INFO("loaded %s firmware (%zd bytes)\n", fn, fw->size);
+       release_firmware(fw);
+       return size;
+}
+
+static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx, long size)
+{
+       const struct firmware *fw = NULL;
+       int retries = 3;
+       int i, j;
+       const u32 *src;
+       struct cx18_apu_rom_seghdr seghdr;
+       const u8 *vers;
+       u32 offset = 0;
+       u32 apu_version = 0;
+       int sz;
+
+retry:
+       if (!retries || request_firmware(&fw, fn, &cx->dev->dev)) {
+               CX18_ERR("unable to open firmware %s (must be %ld bytes)\n",
+                               fn, size);
+               CX18_ERR("did you put the firmware in the hotplug firmware directory?\n");
+               return -ENOMEM;
+       }
+
+       src = (const u32 *)fw->data;
+       vers = fw->data + sizeof(seghdr);
+       sz = fw->size;
+
+       if (fw->size != size) {
+               /* Due to race conditions in firmware loading (esp. with
+                  udev <0.95) the wrong file was sometimes loaded. So we check
+                  filesizes to see if at least the right-sized file was
+                  loaded. If not, then we retry. */
+               CX18_INFO("retry: file loaded was not %s (expected size %ld, got %zd)\n",
+                              fn, size, fw->size);
+               release_firmware(fw);
+               retries--;
+               goto retry;
+       }
+       apu_version = (vers[0] << 24) | (vers[4] << 16) | vers[32];
+       while (offset + sizeof(seghdr) < size) {
+               /* TODO: byteswapping */
+               memcpy(&seghdr, src + offset / 4, sizeof(seghdr));
+               offset += sizeof(seghdr);
+               if (seghdr.sync1 != APU_ROM_SYNC1 ||
+                   seghdr.sync2 != APU_ROM_SYNC2) {
+                       offset += seghdr.size;
+                       continue;
+               }
+               CX18_DEBUG_INFO("load segment %x-%x\n", seghdr.addr,
+                               seghdr.addr + seghdr.size - 1);
+               if (offset + seghdr.size > sz)
+                       break;
+               for (i = 0; i < seghdr.size; i += 4096) {
+                       setup_page(offset + i);
+                       for (j = i; j < seghdr.size && j < i + 4096; j += 4) {
+                               /* no need for endianness conversion on the ppc */
+                               __raw_writel(src[(offset + j) / 4], dst + seghdr.addr + j);
+                               if (__raw_readl(dst + seghdr.addr + j) != src[(offset + j) / 4]) {
+                                       CX18_ERR("Mismatch at offset %x\n", offset + j);
+                                       release_firmware(fw);
+                                       return -EIO;
+                               }
+                       }
+               }
+               offset += seghdr.size;
+       }
+       if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags))
+               CX18_INFO("loaded %s firmware V%08x (%zd bytes)\n",
+                               fn, apu_version, fw->size);
+       release_firmware(fw);
+       /* Clear bit0 for APU to start from 0 */
+       write_reg(read_reg(0xc72030) & ~1, 0xc72030);
+       return size;
+}
+
+void cx18_halt_firmware(struct cx18 *cx)
+{
+       CX18_DEBUG_INFO("Preparing for firmware halt.\n");
+       write_reg(0x000F000F, CX18_PROC_SOFT_RESET); /* stop the fw */
+       write_reg(0x00020002, CX18_ADEC_CONTROL);
+}
+
+void cx18_init_power(struct cx18 *cx, int lowpwr)
+{
+       /* power-down Spare and AOM PLLs */
+       /* power-up fast, slow and mpeg PLLs */
+       write_reg(0x00000008, CX18_PLL_POWER_DOWN);
+
+       /* ADEC out of sleep */
+       write_reg(0x00020000, CX18_ADEC_CONTROL);
+
+       /* The fast clock is at 200/245 MHz */
+       write_reg(lowpwr ? 0xD : 0x11, CX18_FAST_CLOCK_PLL_INT);
+       write_reg(lowpwr ? 0x1EFBF37 : 0x038E3D7, CX18_FAST_CLOCK_PLL_FRAC);
+
+       write_reg(2, CX18_FAST_CLOCK_PLL_POST);
+       write_reg(1, CX18_FAST_CLOCK_PLL_PRESCALE);
+       write_reg(4, CX18_FAST_CLOCK_PLL_ADJUST_BANDWIDTH);
+
+       /* set slow clock to 125/120 MHz */
+       write_reg(lowpwr ? 0x11 : 0x10, CX18_SLOW_CLOCK_PLL_INT);
+       write_reg(lowpwr ? 0xEBAF05 : 0x18618A8, CX18_SLOW_CLOCK_PLL_FRAC);
+       write_reg(4, CX18_SLOW_CLOCK_PLL_POST);
+
+       /* mpeg clock pll 54MHz */
+       write_reg(0xF, CX18_MPEG_CLOCK_PLL_INT);
+       write_reg(0x2BCFEF, CX18_MPEG_CLOCK_PLL_FRAC);
+       write_reg(8, CX18_MPEG_CLOCK_PLL_POST);
+
+       /* Defaults */
+       /* APU = SC or SC/2 = 125/62.5 */
+       /* EPU = SC = 125 */
+       /* DDR = FC = 180 */
+       /* ENC = SC = 125 */
+       /* AI1 = SC = 125 */
+       /* VIM2 = disabled */
+       /* PCI = FC/2 = 90 */
+       /* AI2 = disabled */
+       /* DEMUX = disabled */
+       /* AO = SC/2 = 62.5 */
+       /* SER = 54MHz */
+       /* VFC = disabled */
+       /* USB = disabled */
+
+       write_reg(lowpwr ? 0xFFFF0020 : 0x00060004, CX18_CLOCK_SELECT1);
+       write_reg(lowpwr ? 0xFFFF0004 : 0x00060006, CX18_CLOCK_SELECT2);
+
+       write_reg(0xFFFF0002, CX18_HALF_CLOCK_SELECT1);
+       write_reg(0xFFFF0104, CX18_HALF_CLOCK_SELECT2);
+
+       write_reg(0xFFFF9026, CX18_CLOCK_ENABLE1);
+       write_reg(0xFFFF3105, CX18_CLOCK_ENABLE2);
+}
+
+void cx18_init_memory(struct cx18 *cx)
+{
+       cx18_msleep_timeout(10, 0);
+       write_reg(0x10000, CX18_DDR_SOFT_RESET);
+       cx18_msleep_timeout(10, 0);
+
+       write_reg(cx->card->ddr.chip_config, CX18_DDR_CHIP_CONFIG);
+
+       cx18_msleep_timeout(10, 0);
+
+       write_reg(cx->card->ddr.refresh, CX18_DDR_REFRESH);
+       write_reg(cx->card->ddr.timing1, CX18_DDR_TIMING1);
+       write_reg(cx->card->ddr.timing2, CX18_DDR_TIMING2);
+
+       cx18_msleep_timeout(10, 0);
+
+       /* Initialize DQS pad time */
+       write_reg(cx->card->ddr.tune_lane, CX18_DDR_TUNE_LANE);
+       write_reg(cx->card->ddr.initial_emrs, CX18_DDR_INITIAL_EMRS);
+
+       cx18_msleep_timeout(10, 0);
+
+       write_reg(0x20000, CX18_DDR_SOFT_RESET);
+       cx18_msleep_timeout(10, 0);
+
+       /* use power-down mode when idle */
+       write_reg(0x00000010, CX18_DDR_POWER_REG);
+
+       write_reg(0x10001, CX18_REG_BUS_TIMEOUT_EN);
+
+       write_reg(0x48, CX18_DDR_MB_PER_ROW_7);
+       write_reg(0xE0000, CX18_DDR_BASE_63_ADDR);
+
+       write_reg(0x00000101, CX18_WMB_CLIENT02);  /* AO */
+       write_reg(0x00000101, CX18_WMB_CLIENT09);  /* AI2 */
+       write_reg(0x00000101, CX18_WMB_CLIENT05);  /* VIM1 */
+       write_reg(0x00000101, CX18_WMB_CLIENT06);  /* AI1 */
+       write_reg(0x00000101, CX18_WMB_CLIENT07);  /* 3D comb */
+       write_reg(0x00000101, CX18_WMB_CLIENT10);  /* ME */
+       write_reg(0x00000101, CX18_WMB_CLIENT12);  /* ENC */
+       write_reg(0x00000101, CX18_WMB_CLIENT13);  /* PK */
+       write_reg(0x00000101, CX18_WMB_CLIENT11);  /* RC */
+       write_reg(0x00000101, CX18_WMB_CLIENT14);  /* AVO */
+}
+
+int cx18_firmware_init(struct cx18 *cx)
+{
+       /* Allow chip to control CLKRUN */
+       write_reg(0x5, CX18_DSP0_INTERRUPT_MASK);
+
+       write_reg(0x000F000F, CX18_PROC_SOFT_RESET); /* stop the fw */
+
+       cx18_msleep_timeout(1, 0);
+
+       sw1_irq_enable(IRQ_CPU_TO_EPU | IRQ_APU_TO_EPU);
+       sw2_irq_enable(IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK);
+
+       /* Only if the processor is not running */
+       if (read_reg(CX18_PROC_SOFT_RESET) & 8) {
+               int sz = load_apu_fw_direct("v4l-cx23418-apu.fw",
+                              cx->enc_mem, cx, CX18_FW_APU_SIZE);
+
+               sz = sz <= 0 ? sz : load_cpu_fw_direct("v4l-cx23418-cpu.fw",
+                                       cx->enc_mem, cx, CX18_FW_CPU_SIZE);
+
+               if (sz > 0) {
+                       int retries = 0;
+
+                       /* start the CPU */
+                       write_reg(0x00080000, CX18_PROC_SOFT_RESET);
+                       while (retries++ < 50) { /* Loop for max 500mS */
+                               if ((read_reg(CX18_PROC_SOFT_RESET) & 1) == 0)
+                                       break;
+                               cx18_msleep_timeout(10, 0);
+                       }
+                       cx18_msleep_timeout(200, 0);
+                       if (retries == 51) {
+                               CX18_ERR("Could not start the CPU\n");
+                               return -EIO;
+                       }
+               }
+               if (sz <= 0)
+                       return -EIO;
+       }
+       /* initialize GPIO */
+       write_reg(0x14001400, 0xC78110);
+       return 0;
+}
diff --git a/drivers/media/video/cx18/cx18-firmware.h b/drivers/media/video/cx18/cx18-firmware.h
new file mode 100644 (file)
index 0000000..38d4c05
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ *  cx18 firmware functions
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+int cx18_firmware_init(struct cx18 *cx);
+void cx18_halt_firmware(struct cx18 *cx);
+void cx18_init_memory(struct cx18 *cx);
+void cx18_init_power(struct cx18 *cx, int lowpwr);
diff --git a/drivers/media/video/cx18/cx18-gpio.c b/drivers/media/video/cx18/cx18-gpio.c
new file mode 100644 (file)
index 0000000..19253e6
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ *  cx18 gpio functions
+ *
+ *  Derived from ivtv-gpio.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include "cx18-driver.h"
+#include "cx18-cards.h"
+#include "cx18-gpio.h"
+#include "tuner-xc2028.h"
+
+/********************* GPIO stuffs *********************/
+
+/* GPIO registers */
+#define CX18_REG_GPIO_IN     0xc72010
+#define CX18_REG_GPIO_OUT1   0xc78100
+#define CX18_REG_GPIO_DIR1   0xc78108
+#define CX18_REG_GPIO_OUT2   0xc78104
+#define CX18_REG_GPIO_DIR2   0xc7810c
+
+/*
+ * HVR-1600 GPIO pins, courtesy of Hauppauge:
+ *
+ * gpio0: zilog ir process reset pin
+ * gpio1: zilog programming pin (you should never use this)
+ * gpio12: cx24227 reset pin
+ * gpio13: cs5345 reset pin
+*/
+
+void cx18_gpio_init(struct cx18 *cx)
+{
+       if (cx->card->gpio_init.direction == 0)
+               return;
+
+       CX18_DEBUG_INFO("GPIO initial dir: %08x out: %08x\n",
+                  read_reg(CX18_REG_GPIO_DIR1), read_reg(CX18_REG_GPIO_OUT1));
+
+       /* init output data then direction */
+       write_reg(cx->card->gpio_init.direction << 16, CX18_REG_GPIO_DIR1);
+       write_reg(0, CX18_REG_GPIO_DIR2);
+       write_reg((cx->card->gpio_init.direction << 16) |
+                       cx->card->gpio_init.initial_value, CX18_REG_GPIO_OUT1);
+       write_reg(0, CX18_REG_GPIO_OUT2);
+}
+
+/* Xceive tuner reset function */
+int cx18_reset_tuner_gpio(void *dev, int cmd, int value)
+{
+       struct i2c_algo_bit_data *algo = dev;
+       struct cx18 *cx = algo->data;
+/*     int curdir, curout;*/
+
+       if (cmd != XC2028_TUNER_RESET)
+               return 0;
+       CX18_DEBUG_INFO("Resetting tuner\n");
+       return 0;
+}
diff --git a/drivers/media/video/cx18/cx18-gpio.h b/drivers/media/video/cx18/cx18-gpio.h
new file mode 100644 (file)
index 0000000..41bac88
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ *  cx18 gpio functions
+ *
+ *  Derived from ivtv-gpio.h
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+void cx18_gpio_init(struct cx18 *cx);
+int cx18_reset_tuner_gpio(void *dev, int cmd, int value);
diff --git a/drivers/media/video/cx18/cx18-i2c.c b/drivers/media/video/cx18/cx18-i2c.c
new file mode 100644 (file)
index 0000000..18c88d1
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+ *  cx18 I2C functions
+ *
+ *  Derived from ivtv-i2c.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include "cx18-driver.h"
+#include "cx18-cards.h"
+#include "cx18-gpio.h"
+#include "cx18-av-core.h"
+
+#include <media/ir-kbd-i2c.h>
+
+#define CX18_REG_I2C_1_WR   0xf15000
+#define CX18_REG_I2C_1_RD   0xf15008
+#define CX18_REG_I2C_2_WR   0xf25100
+#define CX18_REG_I2C_2_RD   0xf25108
+
+#define SETSCL_BIT      0x0001
+#define SETSDL_BIT      0x0002
+#define GETSCL_BIT      0x0004
+#define GETSDL_BIT      0x0008
+
+#ifndef I2C_ADAP_CLASS_TV_ANALOG
+#define I2C_ADAP_CLASS_TV_ANALOG I2C_CLASS_TV_ANALOG
+#endif
+
+#define CX18_CS5345_I2C_ADDR           0x4c
+
+/* This array should match the CX18_HW_ defines */
+static const u8 hw_driverids[] = {
+       I2C_DRIVERID_TUNER,
+       I2C_DRIVERID_TVEEPROM,
+       I2C_DRIVERID_CS5345,
+       0,              /* CX18_HW_GPIO dummy driver ID */
+       0               /* CX18_HW_CX23418 dummy driver ID */
+};
+
+/* This array should match the CX18_HW_ defines */
+static const u8 hw_addrs[] = {
+       0,
+       0,
+       CX18_CS5345_I2C_ADDR,
+       0,              /* CX18_HW_GPIO dummy driver ID */
+       0,              /* CX18_HW_CX23418 dummy driver ID */
+};
+
+/* This array should match the CX18_HW_ defines */
+/* This might well become a card-specific array */
+static const u8 hw_bus[] = {
+       0,
+       0,
+       0,
+       0,              /* CX18_HW_GPIO dummy driver ID */
+       0,              /* CX18_HW_CX23418 dummy driver ID */
+};
+
+/* This array should match the CX18_HW_ defines */
+static const char * const hw_drivernames[] = {
+       "tuner",
+       "tveeprom",
+       "cs5345",
+       "gpio",
+       "cx23418",
+};
+
+int cx18_i2c_register(struct cx18 *cx, unsigned idx)
+{
+       struct i2c_board_info info;
+       struct i2c_client *c;
+       u8 id, bus;
+       int i;
+
+       CX18_DEBUG_I2C("i2c client register\n");
+       if (idx >= ARRAY_SIZE(hw_driverids) || hw_driverids[idx] == 0)
+               return -1;
+       id = hw_driverids[idx];
+       bus = hw_bus[idx];
+       memset(&info, 0, sizeof(info));
+       strlcpy(info.driver_name, hw_drivernames[idx],
+                       sizeof(info.driver_name));
+       info.addr = hw_addrs[idx];
+       for (i = 0; i < I2C_CLIENTS_MAX; i++)
+               if (cx->i2c_clients[i] == NULL)
+                       break;
+
+       if (i == I2C_CLIENTS_MAX) {
+               CX18_ERR("insufficient room for new I2C client!\n");
+               return -ENOMEM;
+       }
+
+       if (id != I2C_DRIVERID_TUNER) {
+               c = i2c_new_device(&cx->i2c_adap[bus], &info);
+               if (c->driver == NULL)
+                       i2c_unregister_device(c);
+               else
+                       cx->i2c_clients[i] = c;
+               return cx->i2c_clients[i] ? 0 : -ENODEV;
+       }
+
+       /* special tuner handling */
+       c = i2c_new_probed_device(&cx->i2c_adap[1], &info, cx->card_i2c->radio);
+       if (c && c->driver == NULL)
+               i2c_unregister_device(c);
+       else if (c)
+               cx->i2c_clients[i++] = c;
+       c = i2c_new_probed_device(&cx->i2c_adap[1], &info, cx->card_i2c->demod);
+       if (c && c->driver == NULL)
+               i2c_unregister_device(c);
+       else if (c)
+               cx->i2c_clients[i++] = c;
+       c = i2c_new_probed_device(&cx->i2c_adap[1], &info, cx->card_i2c->tv);
+       if (c && c->driver == NULL)
+               i2c_unregister_device(c);
+       else if (c)
+               cx->i2c_clients[i++] = c;
+       return 0;
+}
+
+static int attach_inform(struct i2c_client *client)
+{
+       return 0;
+}
+
+static int detach_inform(struct i2c_client *client)
+{
+       int i;
+       struct cx18 *cx = (struct cx18 *)i2c_get_adapdata(client->adapter);
+
+       CX18_DEBUG_I2C("i2c client detach\n");
+       for (i = 0; i < I2C_CLIENTS_MAX; i++) {
+               if (cx->i2c_clients[i] == client) {
+                       cx->i2c_clients[i] = NULL;
+                       break;
+               }
+       }
+       CX18_DEBUG_I2C("i2c detach [client=%s,%s]\n",
+                  client->name, (i < I2C_CLIENTS_MAX) ? "ok" : "failed");
+
+       return 0;
+}
+
+static void cx18_setscl(void *data, int state)
+{
+       struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx;
+       int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index;
+       u32 addr = bus_index ? CX18_REG_I2C_2_WR : CX18_REG_I2C_1_WR;
+       u32 r = read_reg(addr);
+
+       if (state)
+               write_reg_sync(r | SETSCL_BIT, addr);
+       else
+               write_reg_sync(r & ~SETSCL_BIT, addr);
+}
+
+static void cx18_setsda(void *data, int state)
+{
+       struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx;
+       int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index;
+       u32 addr = bus_index ? CX18_REG_I2C_2_WR : CX18_REG_I2C_1_WR;
+       u32 r = read_reg(addr);
+
+       if (state)
+               write_reg_sync(r | SETSDL_BIT, addr);
+       else
+               write_reg_sync(r & ~SETSDL_BIT, addr);
+}
+
+static int cx18_getscl(void *data)
+{
+       struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx;
+       int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index;
+       u32 addr = bus_index ? CX18_REG_I2C_2_RD : CX18_REG_I2C_1_RD;
+
+       return read_reg(addr) & GETSCL_BIT;
+}
+
+static int cx18_getsda(void *data)
+{
+       struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx;
+       int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index;
+       u32 addr = bus_index ? CX18_REG_I2C_2_RD : CX18_REG_I2C_1_RD;
+
+       return read_reg(addr) & GETSDL_BIT;
+}
+
+/* template for i2c-bit-algo */
+static struct i2c_adapter cx18_i2c_adap_template = {
+       .name = "cx18 i2c driver",
+       .id = I2C_HW_B_CX2341X,
+       .algo = NULL,                   /* set by i2c-algo-bit */
+       .algo_data = NULL,              /* filled from template */
+       .client_register = attach_inform,
+       .client_unregister = detach_inform,
+       .owner = THIS_MODULE,
+};
+
+#define CX18_SCL_PERIOD (10) /* usecs. 10 usec is period for a 100 KHz clock */
+#define CX18_ALGO_BIT_TIMEOUT (2) /* seconds */
+
+static struct i2c_algo_bit_data cx18_i2c_algo_template = {
+       .setsda         = cx18_setsda,
+       .setscl         = cx18_setscl,
+       .getsda         = cx18_getsda,
+       .getscl         = cx18_getscl,
+       .udelay         = CX18_SCL_PERIOD/2,       /* 1/2 clock period in usec*/
+       .timeout        = CX18_ALGO_BIT_TIMEOUT*HZ /* jiffies */
+};
+
+static struct i2c_client cx18_i2c_client_template = {
+       .name = "cx18 internal",
+};
+
+int cx18_call_i2c_client(struct cx18 *cx, int addr, unsigned cmd, void *arg)
+{
+       struct i2c_client *client;
+       int retval;
+       int i;
+
+       CX18_DEBUG_I2C("call_i2c_client addr=%02x\n", addr);
+       for (i = 0; i < I2C_CLIENTS_MAX; i++) {
+               client = cx->i2c_clients[i];
+               if (client == NULL || client->driver == NULL ||
+                               client->driver->command == NULL)
+                       continue;
+               if (addr == client->addr) {
+                       retval = client->driver->command(client, cmd, arg);
+                       return retval;
+               }
+       }
+       if (cmd != VIDIOC_G_CHIP_IDENT)
+               CX18_ERR("i2c addr 0x%02x not found for cmd 0x%x!\n",
+                              addr, cmd);
+       return -ENODEV;
+}
+
+/* Find the i2c device based on the driver ID and return
+   its i2c address or -ENODEV if no matching device was found. */
+static int cx18_i2c_id_addr(struct cx18 *cx, u32 id)
+{
+       struct i2c_client *client;
+       int retval = -ENODEV;
+       int i;
+
+       for (i = 0; i < I2C_CLIENTS_MAX; i++) {
+               client = cx->i2c_clients[i];
+               if (client == NULL || client->driver == NULL)
+                       continue;
+               if (id == client->driver->id) {
+                       retval = client->addr;
+                       break;
+               }
+       }
+       return retval;
+}
+
+/* Find the i2c device name matching the DRIVERID */
+static const char *cx18_i2c_id_name(u32 id)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(hw_driverids); i++)
+               if (hw_driverids[i] == id)
+                       return hw_drivernames[i];
+       return "unknown device";
+}
+
+/* Find the i2c device name matching the CX18_HW_ flag */
+static const char *cx18_i2c_hw_name(u32 hw)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(hw_driverids); i++)
+               if (1 << i == hw)
+                       return hw_drivernames[i];
+       return "unknown device";
+}
+
+/* Find the i2c device matching the CX18_HW_ flag and return
+   its i2c address or -ENODEV if no matching device was found. */
+int cx18_i2c_hw_addr(struct cx18 *cx, u32 hw)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(hw_driverids); i++)
+               if (1 << i == hw)
+                       return cx18_i2c_id_addr(cx, hw_driverids[i]);
+       return -ENODEV;
+}
+
+/* Calls i2c device based on CX18_HW_ flag. If hw == 0, then do nothing.
+   If hw == CX18_HW_GPIO then call the gpio handler. */
+int cx18_i2c_hw(struct cx18 *cx, u32 hw, unsigned int cmd, void *arg)
+{
+       int addr;
+
+       if (hw == CX18_HW_GPIO || hw == 0)
+               return 0;
+       if (hw == CX18_HW_CX23418)
+               return cx18_av_cmd(cx, cmd, arg);
+
+       addr = cx18_i2c_hw_addr(cx, hw);
+       if (addr < 0) {
+               CX18_ERR("i2c hardware 0x%08x (%s) not found for cmd 0x%x!\n",
+                              hw, cx18_i2c_hw_name(hw), cmd);
+               return addr;
+       }
+       return cx18_call_i2c_client(cx, addr, cmd, arg);
+}
+
+/* Calls i2c device based on I2C driver ID. */
+int cx18_i2c_id(struct cx18 *cx, u32 id, unsigned int cmd, void *arg)
+{
+       int addr;
+
+       addr = cx18_i2c_id_addr(cx, id);
+       if (addr < 0) {
+               if (cmd != VIDIOC_G_CHIP_IDENT)
+                       CX18_ERR("i2c ID 0x%08x (%s) not found for cmd 0x%x!\n",
+                               id, cx18_i2c_id_name(id), cmd);
+               return addr;
+       }
+       return cx18_call_i2c_client(cx, addr, cmd, arg);
+}
+
+/* broadcast cmd for all I2C clients and for the gpio subsystem */
+void cx18_call_i2c_clients(struct cx18 *cx, unsigned int cmd, void *arg)
+{
+       if (cx->i2c_adap[0].algo == NULL || cx->i2c_adap[1].algo == NULL) {
+               CX18_ERR("adapter is not set\n");
+               return;
+       }
+       cx18_av_cmd(cx, cmd, arg);
+       i2c_clients_command(&cx->i2c_adap[0], cmd, arg);
+       i2c_clients_command(&cx->i2c_adap[1], cmd, arg);
+}
+
+/* init + register i2c algo-bit adapter */
+int init_cx18_i2c(struct cx18 *cx)
+{
+       int i;
+       CX18_DEBUG_I2C("i2c init\n");
+
+       for (i = 0; i < 2; i++) {
+               memcpy(&cx->i2c_adap[i], &cx18_i2c_adap_template,
+                       sizeof(struct i2c_adapter));
+               memcpy(&cx->i2c_algo[i], &cx18_i2c_algo_template,
+                       sizeof(struct i2c_algo_bit_data));
+               cx->i2c_algo_cb_data[i].cx = cx;
+               cx->i2c_algo_cb_data[i].bus_index = i;
+               cx->i2c_algo[i].data = &cx->i2c_algo_cb_data[i];
+               cx->i2c_adap[i].algo_data = &cx->i2c_algo[i];
+
+               sprintf(cx->i2c_adap[i].name + strlen(cx->i2c_adap[i].name),
+                               " #%d-%d", cx->num, i);
+               i2c_set_adapdata(&cx->i2c_adap[i], cx);
+
+               memcpy(&cx->i2c_client[i], &cx18_i2c_client_template,
+                       sizeof(struct i2c_client));
+               sprintf(cx->i2c_client[i].name +
+                               strlen(cx->i2c_client[i].name), "%d", i);
+               cx->i2c_client[i].adapter = &cx->i2c_adap[i];
+               cx->i2c_adap[i].dev.parent = &cx->dev->dev;
+       }
+
+       if (read_reg(CX18_REG_I2C_2_WR) != 0x0003c02f) {
+               /* Reset/Unreset I2C hardware block */
+               write_reg(0x10000000, 0xc71004); /* Clock select 220MHz */
+               write_reg_sync(0x10001000, 0xc71024); /* Clock Enable */
+       }
+       /* courtesy of Steven Toth <stoth@hauppauge.com> */
+       write_reg_sync(0x00c00000, 0xc7001c);
+       mdelay(10);
+       write_reg_sync(0x00c000c0, 0xc7001c);
+       mdelay(10);
+       write_reg_sync(0x00c00000, 0xc7001c);
+
+       write_reg_sync(0x00c00000, 0xc730c8); /* Set to edge-triggered intrs. */
+       write_reg_sync(0x00c00000, 0xc730c4); /* Clear any stale intrs */
+
+       /* Hw I2C1 Clock Freq ~100kHz */
+       write_reg_sync(0x00021c0f & ~4, CX18_REG_I2C_1_WR);
+       cx18_setscl(&cx->i2c_algo_cb_data[0], 1);
+       cx18_setsda(&cx->i2c_algo_cb_data[0], 1);
+
+       /* Hw I2C2 Clock Freq ~100kHz */
+       write_reg_sync(0x00021c0f & ~4, CX18_REG_I2C_2_WR);
+       cx18_setscl(&cx->i2c_algo_cb_data[1], 1);
+       cx18_setsda(&cx->i2c_algo_cb_data[1], 1);
+
+       return i2c_bit_add_bus(&cx->i2c_adap[0]) ||
+               i2c_bit_add_bus(&cx->i2c_adap[1]);
+}
+
+void exit_cx18_i2c(struct cx18 *cx)
+{
+       int i;
+       CX18_DEBUG_I2C("i2c exit\n");
+       write_reg(read_reg(CX18_REG_I2C_1_WR) | 4, CX18_REG_I2C_1_WR);
+       write_reg(read_reg(CX18_REG_I2C_2_WR) | 4, CX18_REG_I2C_2_WR);
+
+       for (i = 0; i < 2; i++) {
+               i2c_del_adapter(&cx->i2c_adap[i]);
+       }
+}
+
+/*
+   Hauppauge HVR1600 should have:
+   32 cx24227
+   98 unknown
+   a0 eeprom
+   c2 tuner
+   e? zilog ir
+   */
diff --git a/drivers/media/video/cx18/cx18-i2c.h b/drivers/media/video/cx18/cx18-i2c.h
new file mode 100644 (file)
index 0000000..113c3f9
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ *  cx18 I2C functions
+ *
+ *  Derived from ivtv-i2c.h
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+int cx18_i2c_hw_addr(struct cx18 *cx, u32 hw);
+int cx18_i2c_hw(struct cx18 *cx, u32 hw, unsigned int cmd, void *arg);
+int cx18_i2c_id(struct cx18 *cx, u32 id, unsigned int cmd, void *arg);
+int cx18_call_i2c_client(struct cx18 *cx, int addr, unsigned cmd, void *arg);
+void cx18_call_i2c_clients(struct cx18 *cx, unsigned int cmd, void *arg);
+int cx18_i2c_register(struct cx18 *cx, unsigned idx);
+
+/* init + register i2c algo-bit adapter */
+int init_cx18_i2c(struct cx18 *cx);
+void exit_cx18_i2c(struct cx18 *cx);
diff --git a/drivers/media/video/cx18/cx18-ioctl.c b/drivers/media/video/cx18/cx18-ioctl.c
new file mode 100644 (file)
index 0000000..4a93af2
--- /dev/null
@@ -0,0 +1,851 @@
+/*
+ *  cx18 ioctl system call
+ *
+ *  Derived from ivtv-ioctl.c
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include "cx18-driver.h"
+#include "cx18-version.h"
+#include "cx18-mailbox.h"
+#include "cx18-i2c.h"
+#include "cx18-queue.h"
+#include "cx18-fileops.h"
+#include "cx18-vbi.h"
+#include "cx18-audio.h"
+#include "cx18-video.h"
+#include "cx18-streams.h"
+#include "cx18-ioctl.h"
+#include "cx18-gpio.h"
+#include "cx18-controls.h"
+#include "cx18-cards.h"
+#include "cx18-av-core.h"
+#include <media/tveeprom.h>
+#include <media/v4l2-chip-ident.h>
+#include <linux/i2c-id.h>
+
+u16 service2vbi(int type)
+{
+       switch (type) {
+       case V4L2_SLICED_TELETEXT_B:
+               return CX18_SLICED_TYPE_TELETEXT_B;
+       case V4L2_SLICED_CAPTION_525:
+               return CX18_SLICED_TYPE_CAPTION_525;
+       case V4L2_SLICED_WSS_625:
+               return CX18_SLICED_TYPE_WSS_625;
+       case V4L2_SLICED_VPS:
+               return CX18_SLICED_TYPE_VPS;
+       default:
+               return 0;
+       }
+}
+
+static int valid_service_line(int field, int line, int is_pal)
+{
+       return (is_pal && line >= 6 && (line != 23 || field == 0)) ||
+              (!is_pal && line >= 10 && line < 22);
+}
+
+static u16 select_service_from_set(int field, int line, u16 set, int is_pal)
+{
+       u16 valid_set = (is_pal ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525);
+       int i;
+
+       set = set & valid_set;
+       if (set == 0 || !valid_service_line(field, line, is_pal))
+               return 0;
+       if (!is_pal) {
+               if (line == 21 && (set & V4L2_SLICED_CAPTION_525))
+                       return V4L2_SLICED_CAPTION_525;
+       } else {
+               if (line == 16 && field == 0 && (set & V4L2_SLICED_VPS))
+                       return V4L2_SLICED_VPS;
+               if (line == 23 && field == 0 && (set & V4L2_SLICED_WSS_625))
+                       return V4L2_SLICED_WSS_625;
+               if (line == 23)
+                       return 0;
+       }
+       for (i = 0; i < 32; i++) {
+               if ((1 << i) & set)
+                       return 1 << i;
+       }
+       return 0;
+}
+
+void expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal)
+{
+       u16 set = fmt->service_set;
+       int f, l;
+
+       fmt->service_set = 0;
+       for (f = 0; f < 2; f++) {
+               for (l = 0; l < 24; l++)
+                       fmt->service_lines[f][l] = select_service_from_set(f, l, set, is_pal);
+       }
+}
+
+static int check_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal)
+{
+       int f, l;
+       u16 set = 0;
+
+       for (f = 0; f < 2; f++) {
+               for (l = 0; l < 24; l++) {
+                       fmt->service_lines[f][l] = select_service_from_set(f, l, fmt->service_lines[f][l], is_pal);
+                       set |= fmt->service_lines[f][l];
+               }
+       }
+       return set != 0;
+}
+
+u16 get_service_set(struct v4l2_sliced_vbi_format *fmt)
+{
+       int f, l;
+       u16 set = 0;
+
+       for (f = 0; f < 2; f++) {
+               for (l = 0; l < 24; l++)
+                       set |= fmt->service_lines[f][l];
+       }
+       return set;
+}
+
+static const struct {
+       v4l2_std_id  std;
+       char        *name;
+} enum_stds[] = {
+       { V4L2_STD_PAL_BG | V4L2_STD_PAL_H, "PAL-BGH" },
+       { V4L2_STD_PAL_DK,    "PAL-DK"    },
+       { V4L2_STD_PAL_I,     "PAL-I"     },
+       { V4L2_STD_PAL_M,     "PAL-M"     },
+       { V4L2_STD_PAL_N,     "PAL-N"     },
+       { V4L2_STD_PAL_Nc,    "PAL-Nc"    },
+       { V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H, "SECAM-BGH" },
+       { V4L2_STD_SECAM_DK,  "SECAM-DK"  },
+       { V4L2_STD_SECAM_L,   "SECAM-L"   },
+       { V4L2_STD_SECAM_LC,  "SECAM-L'"  },
+       { V4L2_STD_NTSC_M,    "NTSC-M"    },
+       { V4L2_STD_NTSC_M_JP, "NTSC-J"    },
+       { V4L2_STD_NTSC_M_KR, "NTSC-K"    },
+};
+
+static const struct v4l2_standard cx18_std_60hz = {
+       .frameperiod = {.numerator = 1001, .denominator = 30000},
+       .framelines = 525,
+};
+
+static const struct v4l2_standard cx18_std_50hz = {
+       .frameperiod = { .numerator = 1, .denominator = 25 },
+       .framelines = 625,
+};
+
+static int cx18_cxc(struct cx18 *cx, unsigned int cmd, void *arg)
+{
+       struct v4l2_register *regs = arg;
+       unsigned long flags;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+       if (regs->reg >= CX18_MEM_OFFSET + CX18_MEM_SIZE)
+               return -EINVAL;
+
+       spin_lock_irqsave(&cx18_cards_lock, flags);
+       if (cmd == VIDIOC_DBG_G_REGISTER)
+               regs->val = read_enc(regs->reg);
+       else
+               write_enc(regs->val, regs->reg);
+       spin_unlock_irqrestore(&cx18_cards_lock, flags);
+       return 0;
+}
+
+static int cx18_get_fmt(struct cx18 *cx, int streamtype, struct v4l2_format *fmt)
+{
+       switch (fmt->type) {
+       case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+               fmt->fmt.pix.width = cx->params.width;
+               fmt->fmt.pix.height = cx->params.height;
+               fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+               fmt->fmt.pix.field = V4L2_FIELD_INTERLACED;
+               if (streamtype == CX18_ENC_STREAM_TYPE_YUV) {
+                       fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_HM12;
+                       /* YUV size is (Y=(h*w) + UV=(h*(w/2))) */
+                       fmt->fmt.pix.sizeimage =
+                               fmt->fmt.pix.height * fmt->fmt.pix.width +
+                               fmt->fmt.pix.height * (fmt->fmt.pix.width / 2);
+               } else {
+                       fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;
+                       fmt->fmt.pix.sizeimage = 128 * 1024;
+               }
+               break;
+
+       case V4L2_BUF_TYPE_VBI_CAPTURE:
+               fmt->fmt.vbi.sampling_rate = 27000000;
+               fmt->fmt.vbi.offset = 248;
+               fmt->fmt.vbi.samples_per_line = cx->vbi.raw_decoder_line_size - 4;
+               fmt->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+               fmt->fmt.vbi.start[0] = cx->vbi.start[0];
+               fmt->fmt.vbi.start[1] = cx->vbi.start[1];
+               fmt->fmt.vbi.count[0] = fmt->fmt.vbi.count[1] = cx->vbi.count;
+               break;
+
+       case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+       {
+               struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced;
+
+               vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36;
+               memset(vbifmt->reserved, 0, sizeof(vbifmt->reserved));
+               memset(vbifmt->service_lines, 0, sizeof(vbifmt->service_lines));
+
+               cx18_av_cmd(cx, VIDIOC_G_FMT, fmt);
+               vbifmt->service_set = get_service_set(vbifmt);
+               break;
+       }
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int cx18_try_or_set_fmt(struct cx18 *cx, int streamtype,
+               struct v4l2_format *fmt, int set_fmt)
+{
+       struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced;
+       u16 set;
+
+       /* set window size */
+       if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+               int w = fmt->fmt.pix.width;
+               int h = fmt->fmt.pix.height;
+
+               if (w > 720)
+                       w = 720;
+               else if (w < 1)
+                       w = 1;
+               if (h > (cx->is_50hz ? 576 : 480))
+                       h = (cx->is_50hz ? 576 : 480);
+               else if (h < 2)
+                       h = 2;
+               cx18_get_fmt(cx, streamtype, fmt);
+               fmt->fmt.pix.width = w;
+               fmt->fmt.pix.height = h;
+
+               if (!set_fmt || (cx->params.width == w && cx->params.height == h))
+                       return 0;
+               if (atomic_read(&cx->capturing) > 0)
+                       return -EBUSY;
+
+               cx->params.width = w;
+               cx->params.height = h;
+               if (w != 720 || h != (cx->is_50hz ? 576 : 480))
+                       cx->params.video_temporal_filter = 0;
+               else
+                       cx->params.video_temporal_filter = 8;
+               cx18_av_cmd(cx, VIDIOC_S_FMT, fmt);
+               return cx18_get_fmt(cx, streamtype, fmt);
+       }
+
+       /* set raw VBI format */
+       if (fmt->type == V4L2_BUF_TYPE_VBI_CAPTURE) {
+               if (set_fmt && streamtype == CX18_ENC_STREAM_TYPE_VBI &&
+                   cx->vbi.sliced_in->service_set &&
+                   atomic_read(&cx->capturing) > 0)
+                       return -EBUSY;
+               if (set_fmt) {
+                       cx->vbi.sliced_in->service_set = 0;
+                       cx18_av_cmd(cx, VIDIOC_S_FMT, &cx->vbi.in);
+               }
+               return cx18_get_fmt(cx, streamtype, fmt);
+       }
+
+       /* any else but sliced VBI capture is an error */
+       if (fmt->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE)
+               return -EINVAL;
+
+       /* TODO: implement sliced VBI, for now silently return 0 */
+       return 0;
+
+       /* set sliced VBI capture format */
+       vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36;
+       memset(vbifmt->reserved, 0, sizeof(vbifmt->reserved));
+
+       if (vbifmt->service_set)
+               expand_service_set(vbifmt, cx->is_50hz);
+       set = check_service_set(vbifmt, cx->is_50hz);
+       vbifmt->service_set = get_service_set(vbifmt);
+
+       if (!set_fmt)
+               return 0;
+       if (set == 0)
+               return -EINVAL;
+       if (atomic_read(&cx->capturing) > 0 && cx->vbi.sliced_in->service_set == 0)
+               return -EBUSY;
+       cx18_av_cmd(cx, VIDIOC_S_FMT, fmt);
+       memcpy(cx->vbi.sliced_in, vbifmt, sizeof(*cx->vbi.sliced_in));
+       return 0;
+}
+
+static int cx18_debug_ioctls(struct file *filp, unsigned int cmd, void *arg)
+{
+       struct cx18_open_id *id = (struct cx18_open_id *)filp->private_data;
+       struct cx18 *cx = id->cx;
+       struct v4l2_register *reg = arg;
+
+       switch (cmd) {
+       /* ioctls to allow direct access to the encoder registers for testing */
+       case VIDIOC_DBG_G_REGISTER:
+               if (v4l2_chip_match_host(reg->match_type, reg->match_chip))
+                       return cx18_cxc(cx, cmd, arg);
+               if (reg->match_type == V4L2_CHIP_MATCH_I2C_DRIVER)
+                       return cx18_i2c_id(cx, reg->match_chip, cmd, arg);
+               return cx18_call_i2c_client(cx, reg->match_chip, cmd, arg);
+
+       case VIDIOC_DBG_S_REGISTER:
+               if (v4l2_chip_match_host(reg->match_type, reg->match_chip))
+                       return cx18_cxc(cx, cmd, arg);
+               if (reg->match_type == V4L2_CHIP_MATCH_I2C_DRIVER)
+                       return cx18_i2c_id(cx, reg->match_chip, cmd, arg);
+               return cx18_call_i2c_client(cx, reg->match_chip, cmd, arg);
+
+       case VIDIOC_G_CHIP_IDENT: {
+               struct v4l2_chip_ident *chip = arg;
+
+               chip->ident = V4L2_IDENT_NONE;
+               chip->revision = 0;
+               if (reg->match_type == V4L2_CHIP_MATCH_HOST) {
+                       if (v4l2_chip_match_host(reg->match_type, reg->match_chip)) {
+                               struct v4l2_chip_ident *chip = arg;
+
+                               chip->ident = V4L2_IDENT_CX23418;
+                       }
+                       return 0;
+               }
+               if (reg->match_type == V4L2_CHIP_MATCH_I2C_DRIVER)
+                       return cx18_i2c_id(cx, reg->match_chip, cmd, arg);
+               if (reg->match_type == V4L2_CHIP_MATCH_I2C_ADDR)
+                       return cx18_call_i2c_client(cx, reg->match_chip, cmd, arg);
+               return -EINVAL;
+       }
+
+       case VIDIOC_INT_S_AUDIO_ROUTING: {
+               struct v4l2_routing *route = arg;
+
+               cx18_audio_set_route(cx, route);
+               break;
+       }
+
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+int cx18_v4l2_ioctls(struct cx18 *cx, struct file *filp, unsigned cmd, void *arg)
+{
+       struct cx18_open_id *id = NULL;
+
+       if (filp)
+               id = (struct cx18_open_id *)filp->private_data;
+
+       switch (cmd) {
+       case VIDIOC_G_PRIORITY:
+       {
+               enum v4l2_priority *p = arg;
+
+               *p = v4l2_prio_max(&cx->prio);
+               break;
+       }
+
+       case VIDIOC_S_PRIORITY:
+       {
+               enum v4l2_priority *prio = arg;
+
+               return v4l2_prio_change(&cx->prio, &id->prio, *prio);
+       }
+
+       case VIDIOC_QUERYCAP:{
+               struct v4l2_capability *vcap = arg;
+
+               memset(vcap, 0, sizeof(*vcap));
+               strlcpy(vcap->driver, CX18_DRIVER_NAME, sizeof(vcap->driver));
+               strlcpy(vcap->card, cx->card_name, sizeof(vcap->card));
+               strlcpy(vcap->bus_info, pci_name(cx->dev), sizeof(vcap->bus_info));
+               vcap->version = CX18_DRIVER_VERSION;        /* version */
+               vcap->capabilities = cx->v4l2_cap;          /* capabilities */
+
+               /* reserved.. must set to 0! */
+               vcap->reserved[0] = vcap->reserved[1] =
+                       vcap->reserved[2] = vcap->reserved[3] = 0;
+               break;
+       }
+
+       case VIDIOC_ENUMAUDIO:{
+               struct v4l2_audio *vin = arg;
+
+               return cx18_get_audio_input(cx, vin->index, vin);
+       }
+
+       case VIDIOC_G_AUDIO:{
+               struct v4l2_audio *vin = arg;
+
+               vin->index = cx->audio_input;
+               return cx18_get_audio_input(cx, vin->index, vin);
+       }
+
+       case VIDIOC_S_AUDIO:{
+               struct v4l2_audio *vout = arg;
+
+               if (vout->index >= cx->nof_audio_inputs)
+                       return -EINVAL;
+               cx->audio_input = vout->index;
+               cx18_audio_set_io(cx);
+               break;
+       }
+
+       case VIDIOC_ENUMINPUT:{
+               struct v4l2_input *vin = arg;
+
+               /* set it to defaults from our table */
+               return cx18_get_input(cx, vin->index, vin);
+       }
+
+       case VIDIOC_TRY_FMT:
+       case VIDIOC_S_FMT: {
+               struct v4l2_format *fmt = arg;
+
+               return cx18_try_or_set_fmt(cx, id->type, fmt, cmd == VIDIOC_S_FMT);
+       }
+
+       case VIDIOC_G_FMT: {
+               struct v4l2_format *fmt = arg;
+               int type = fmt->type;
+
+               memset(fmt, 0, sizeof(*fmt));
+               fmt->type = type;
+               return cx18_get_fmt(cx, id->type, fmt);
+       }
+
+       case VIDIOC_CROPCAP: {
+               struct v4l2_cropcap *cropcap = arg;
+
+               if (cropcap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+                       return -EINVAL;
+               cropcap->bounds.top = cropcap->bounds.left = 0;
+               cropcap->bounds.width = 720;
+               cropcap->bounds.height = cx->is_50hz ? 576 : 480;
+               cropcap->pixelaspect.numerator = cx->is_50hz ? 59 : 10;
+               cropcap->pixelaspect.denominator = cx->is_50hz ? 54 : 11;
+               cropcap->defrect = cropcap->bounds;
+               return 0;
+       }
+
+       case VIDIOC_S_CROP: {
+               struct v4l2_crop *crop = arg;
+
+               if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+                       return -EINVAL;
+               return cx18_av_cmd(cx, VIDIOC_S_CROP, arg);
+       }
+
+       case VIDIOC_G_CROP: {
+               struct v4l2_crop *crop = arg;
+
+               if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+                       return -EINVAL;
+               return cx18_av_cmd(cx, VIDIOC_G_CROP, arg);
+       }
+
+       case VIDIOC_ENUM_FMT: {
+               static struct v4l2_fmtdesc formats[] = {
+                       { 0, 0, 0,
+                         "HM12 (YUV 4:1:1)", V4L2_PIX_FMT_HM12,
+                         { 0, 0, 0, 0 }
+                       },
+                       { 1, 0, V4L2_FMT_FLAG_COMPRESSED,
+                         "MPEG", V4L2_PIX_FMT_MPEG,
+                         { 0, 0, 0, 0 }
+                       }
+               };
+               struct v4l2_fmtdesc *fmt = arg;
+               enum v4l2_buf_type type = fmt->type;
+
+               switch (type) {
+               case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               if (fmt->index > 1)
+                       return -EINVAL;
+               *fmt = formats[fmt->index];
+               fmt->type = type;
+               return 0;
+       }
+
+       case VIDIOC_G_INPUT:{
+               *(int *)arg = cx->active_input;
+               break;
+       }
+
+       case VIDIOC_S_INPUT:{
+               int inp = *(int *)arg;
+
+               if (inp < 0 || inp >= cx->nof_inputs)
+                       return -EINVAL;
+
+               if (inp == cx->active_input) {
+                       CX18_DEBUG_INFO("Input unchanged\n");
+                       break;
+               }
+               CX18_DEBUG_INFO("Changing input from %d to %d\n",
+                               cx->active_input, inp);
+
+               cx->active_input = inp;
+               /* Set the audio input to whatever is appropriate for the
+                  input type. */
+               cx->audio_input = cx->card->video_inputs[inp].audio_index;
+
+               /* prevent others from messing with the streams until
+                  we're finished changing inputs. */
+               cx18_mute(cx);
+               cx18_video_set_io(cx);
+               cx18_audio_set_io(cx);
+               cx18_unmute(cx);
+               break;
+       }
+
+       case VIDIOC_G_FREQUENCY:{
+               struct v4l2_frequency *vf = arg;
+
+               if (vf->tuner != 0)
+                       return -EINVAL;
+               cx18_call_i2c_clients(cx, cmd, arg);
+               break;
+       }
+
+       case VIDIOC_S_FREQUENCY:{
+               struct v4l2_frequency vf = *(struct v4l2_frequency *)arg;
+
+               if (vf.tuner != 0)
+                       return -EINVAL;
+
+               cx18_mute(cx);
+               CX18_DEBUG_INFO("v4l2 ioctl: set frequency %d\n", vf.frequency);
+               cx18_call_i2c_clients(cx, cmd, &vf);
+               cx18_unmute(cx);
+               break;
+       }
+
+       case VIDIOC_ENUMSTD:{
+               struct v4l2_standard *vs = arg;
+               int idx = vs->index;
+
+               if (idx < 0 || idx >= ARRAY_SIZE(enum_stds))
+                       return -EINVAL;
+
+               *vs = (enum_stds[idx].std & V4L2_STD_525_60) ?
+                               cx18_std_60hz : cx18_std_50hz;
+               vs->index = idx;
+               vs->id = enum_stds[idx].std;
+               strlcpy(vs->name, enum_stds[idx].name, sizeof(vs->name));
+               break;
+       }
+
+       case VIDIOC_G_STD:{
+               *(v4l2_std_id *) arg = cx->std;
+               break;
+       }
+
+       case VIDIOC_S_STD: {
+               v4l2_std_id std = *(v4l2_std_id *) arg;
+
+               if ((std & V4L2_STD_ALL) == 0)
+                       return -EINVAL;
+
+               if (std == cx->std)
+                       break;
+
+               if (test_bit(CX18_F_I_RADIO_USER, &cx->i_flags) ||
+                   atomic_read(&cx->capturing) > 0) {
+                       /* Switching standard would turn off the radio or mess
+                          with already running streams, prevent that by
+                          returning EBUSY. */
+                       return -EBUSY;
+               }
+
+               cx->std = std;
+               cx->is_60hz = (std & V4L2_STD_525_60) ? 1 : 0;
+               cx->params.is_50hz = cx->is_50hz = !cx->is_60hz;
+               cx->params.width = 720;
+               cx->params.height = cx->is_50hz ? 576 : 480;
+               cx->vbi.count = cx->is_50hz ? 18 : 12;
+               cx->vbi.start[0] = cx->is_50hz ? 6 : 10;
+               cx->vbi.start[1] = cx->is_50hz ? 318 : 273;
+               cx->vbi.sliced_decoder_line_size = cx->is_60hz ? 272 : 284;
+               CX18_DEBUG_INFO("Switching standard to %llx.\n", (unsigned long long)cx->std);
+
+               /* Tuner */
+               cx18_call_i2c_clients(cx, VIDIOC_S_STD, &cx->std);
+               break;
+       }
+
+       case VIDIOC_S_TUNER: {  /* Setting tuner can only set audio mode */
+               struct v4l2_tuner *vt = arg;
+
+               if (vt->index != 0)
+                       return -EINVAL;
+
+               cx18_call_i2c_clients(cx, VIDIOC_S_TUNER, vt);
+               break;
+       }
+
+       case VIDIOC_G_TUNER: {
+               struct v4l2_tuner *vt = arg;
+
+               if (vt->index != 0)
+                       return -EINVAL;
+
+               memset(vt, 0, sizeof(*vt));
+               cx18_call_i2c_clients(cx, VIDIOC_G_TUNER, vt);
+
+               if (test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) {
+                       strlcpy(vt->name, "cx18 Radio Tuner", sizeof(vt->name));
+                       vt->type = V4L2_TUNER_RADIO;
+               } else {
+                       strlcpy(vt->name, "cx18 TV Tuner", sizeof(vt->name));
+                       vt->type = V4L2_TUNER_ANALOG_TV;
+               }
+               break;
+       }
+
+       case VIDIOC_G_SLICED_VBI_CAP: {
+               struct v4l2_sliced_vbi_cap *cap = arg;
+               int set = cx->is_50hz ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525;
+               int f, l;
+               enum v4l2_buf_type type = cap->type;
+
+               memset(cap, 0, sizeof(*cap));
+               cap->type = type;
+               if (type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) {
+                       for (f = 0; f < 2; f++) {
+                               for (l = 0; l < 24; l++) {
+                                       if (valid_service_line(f, l, cx->is_50hz))
+                                               cap->service_lines[f][l] = set;
+                               }
+                       }
+                       return 0;
+               }
+               return -EINVAL;
+       }
+
+       case VIDIOC_ENCODER_CMD:
+       case VIDIOC_TRY_ENCODER_CMD: {
+               struct v4l2_encoder_cmd *enc = arg;
+               int try = cmd == VIDIOC_TRY_ENCODER_CMD;
+
+               memset(&enc->raw, 0, sizeof(enc->raw));
+               switch (enc->cmd) {
+               case V4L2_ENC_CMD_START:
+                       enc->flags = 0;
+                       if (try)
+                               return 0;
+                       return cx18_start_capture(id);
+
+               case V4L2_ENC_CMD_STOP:
+                       enc->flags &= V4L2_ENC_CMD_STOP_AT_GOP_END;
+                       if (try)
+                               return 0;
+                       cx18_stop_capture(id, enc->flags & V4L2_ENC_CMD_STOP_AT_GOP_END);
+                       return 0;
+
+               case V4L2_ENC_CMD_PAUSE:
+                       enc->flags = 0;
+                       if (try)
+                               return 0;
+                       if (!atomic_read(&cx->capturing))
+                               return -EPERM;
+                       if (test_and_set_bit(CX18_F_I_ENC_PAUSED, &cx->i_flags))
+                               return 0;
+                       cx18_mute(cx);
+                       cx18_vapi(cx, CX18_CPU_CAPTURE_PAUSE, 1, cx18_find_handle(cx));
+                       break;
+
+               case V4L2_ENC_CMD_RESUME:
+                       enc->flags = 0;
+                       if (try)
+                               return 0;
+                       if (!atomic_read(&cx->capturing))
+                               return -EPERM;
+                       if (!test_and_clear_bit(CX18_F_I_ENC_PAUSED, &cx->i_flags))
+                               return 0;
+                       cx18_vapi(cx, CX18_CPU_CAPTURE_RESUME, 1, cx18_find_handle(cx));
+                       cx18_unmute(cx);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               break;
+       }
+
+       case VIDIOC_LOG_STATUS:
+       {
+               struct v4l2_input vidin;
+               struct v4l2_audio audin;
+               int i;
+
+               CX18_INFO("=================  START STATUS CARD #%d  =================\n", cx->num);
+               if (cx->hw_flags & CX18_HW_TVEEPROM) {
+                       struct tveeprom tv;
+
+                       cx18_read_eeprom(cx, &tv);
+               }
+               cx18_call_i2c_clients(cx, VIDIOC_LOG_STATUS, NULL);
+               cx18_get_input(cx, cx->active_input, &vidin);
+               cx18_get_audio_input(cx, cx->audio_input, &audin);
+               CX18_INFO("Video Input: %s\n", vidin.name);
+               CX18_INFO("Audio Input: %s\n", audin.name);
+               CX18_INFO("Tuner: %s\n",
+                       test_bit(CX18_F_I_RADIO_USER, &cx->i_flags) ?
+                       "Radio" : "TV");
+               cx2341x_log_status(&cx->params, cx->name);
+               CX18_INFO("Status flags: 0x%08lx\n", cx->i_flags);
+               for (i = 0; i < CX18_MAX_STREAMS; i++) {
+                       struct cx18_stream *s = &cx->streams[i];
+
+                       if (s->v4l2dev == NULL || s->buffers == 0)
+                               continue;
+                       CX18_INFO("Stream %s: status 0x%04lx, %d%% of %d KiB (%d buffers) in use\n",
+                               s->name, s->s_flags,
+                               (s->buffers - s->q_free.buffers) * 100 / s->buffers,
+                               (s->buffers * s->buf_size) / 1024, s->buffers);
+               }
+               CX18_INFO("Read MPEG/VBI: %lld/%lld bytes\n",
+                               (long long)cx->mpg_data_received,
+                               (long long)cx->vbi_data_inserted);
+               CX18_INFO("==================  END STATUS CARD #%d  ==================\n", cx->num);
+               break;
+       }
+
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int cx18_v4l2_do_ioctl(struct inode *inode, struct file *filp,
+                             unsigned int cmd, void *arg)
+{
+       struct cx18_open_id *id = (struct cx18_open_id *)filp->private_data;
+       struct cx18 *cx = id->cx;
+       int ret;
+
+       /* check priority */
+       switch (cmd) {
+       case VIDIOC_S_CTRL:
+       case VIDIOC_S_STD:
+       case VIDIOC_S_INPUT:
+       case VIDIOC_S_TUNER:
+       case VIDIOC_S_FREQUENCY:
+       case VIDIOC_S_FMT:
+       case VIDIOC_S_CROP:
+       case VIDIOC_S_EXT_CTRLS:
+               ret = v4l2_prio_check(&cx->prio, &id->prio);
+               if (ret)
+                       return ret;
+       }
+
+       switch (cmd) {
+       case VIDIOC_DBG_G_REGISTER:
+       case VIDIOC_DBG_S_REGISTER:
+       case VIDIOC_G_CHIP_IDENT:
+       case VIDIOC_INT_S_AUDIO_ROUTING:
+       case VIDIOC_INT_RESET:
+               if (cx18_debug & CX18_DBGFLG_IOCTL) {
+                       printk(KERN_INFO "cx18%d ioctl: ", cx->num);
+                       v4l_printk_ioctl(cmd);
+               }
+               return cx18_debug_ioctls(filp, cmd, arg);
+
+       case VIDIOC_G_PRIORITY:
+       case VIDIOC_S_PRIORITY:
+       case VIDIOC_QUERYCAP:
+       case VIDIOC_ENUMINPUT:
+       case VIDIOC_G_INPUT:
+       case VIDIOC_S_INPUT:
+       case VIDIOC_G_FMT:
+       case VIDIOC_S_FMT:
+       case VIDIOC_TRY_FMT:
+       case VIDIOC_ENUM_FMT:
+       case VIDIOC_CROPCAP:
+       case VIDIOC_G_CROP:
+       case VIDIOC_S_CROP:
+       case VIDIOC_G_FREQUENCY:
+       case VIDIOC_S_FREQUENCY:
+       case VIDIOC_ENUMSTD:
+       case VIDIOC_G_STD:
+       case VIDIOC_S_STD:
+       case VIDIOC_S_TUNER:
+       case VIDIOC_G_TUNER:
+       case VIDIOC_ENUMAUDIO:
+       case VIDIOC_S_AUDIO:
+       case VIDIOC_G_AUDIO:
+       case VIDIOC_G_SLICED_VBI_CAP:
+       case VIDIOC_LOG_STATUS:
+       case VIDIOC_G_ENC_INDEX:
+       case VIDIOC_ENCODER_CMD:
+       case VIDIOC_TRY_ENCODER_CMD:
+               if (cx18_debug & CX18_DBGFLG_IOCTL) {
+                       printk(KERN_INFO "cx18%d ioctl: ", cx->num);
+                       v4l_printk_ioctl(cmd);
+               }
+               return cx18_v4l2_ioctls(cx, filp, cmd, arg);
+
+       case VIDIOC_QUERYMENU:
+       case VIDIOC_QUERYCTRL:
+       case VIDIOC_S_CTRL:
+       case VIDIOC_G_CTRL:
+       case VIDIOC_S_EXT_CTRLS:
+       case VIDIOC_G_EXT_CTRLS:
+       case VIDIOC_TRY_EXT_CTRLS:
+               if (cx18_debug & CX18_DBGFLG_IOCTL) {
+                       printk(KERN_INFO "cx18%d ioctl: ", cx->num);
+                       v4l_printk_ioctl(cmd);
+               }
+               return cx18_control_ioctls(cx, cmd, arg);
+
+       case 0x00005401:        /* Handle isatty() calls */
+               return -EINVAL;
+       default:
+               return v4l_compat_translate_ioctl(inode, filp, cmd, arg,
+                                                  cx18_v4l2_do_ioctl);
+       }
+       return 0;
+}
+
+int cx18_v4l2_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,
+                   unsigned long arg)
+{
+       struct cx18_open_id *id = (struct cx18_open_id *)filp->private_data;
+       struct cx18 *cx = id->cx;
+       int res;
+
+       mutex_lock(&cx->serialize_lock);
+       res = video_usercopy(inode, filp, cmd, arg, cx18_v4l2_do_ioctl);
+       mutex_unlock(&cx->serialize_lock);
+       return res;
+}
diff --git a/drivers/media/video/cx18/cx18-ioctl.h b/drivers/media/video/cx18/cx18-ioctl.h
new file mode 100644 (file)
index 0000000..0ee152d
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ *  cx18 ioctl system call
+ *
+ *  Derived from ivtv-ioctl.h
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+u16 service2vbi(int type);
+void expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal);
+u16 get_service_set(struct v4l2_sliced_vbi_format *fmt);
+int cx18_v4l2_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,
+                   unsigned long arg);
+int cx18_v4l2_ioctls(struct cx18 *cx, struct file *filp, unsigned cmd,
+                    void *arg);
diff --git a/drivers/media/video/cx18/cx18-irq.c b/drivers/media/video/cx18/cx18-irq.c
new file mode 100644 (file)
index 0000000..6e14f8b
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ *  cx18 interrupt handling
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include "cx18-driver.h"
+#include "cx18-firmware.h"
+#include "cx18-fileops.h"
+#include "cx18-queue.h"
+#include "cx18-irq.h"
+#include "cx18-ioctl.h"
+#include "cx18-mailbox.h"
+#include "cx18-vbi.h"
+#include "cx18-scb.h"
+
+#define DMA_MAGIC_COOKIE 0x000001fe
+
+static void epu_dma_done(struct cx18 *cx, struct cx18_mailbox *mb)
+{
+       u32 handle = mb->args[0];
+       struct cx18_stream *s = NULL;
+       struct cx18_buffer *buf;
+       u32 off;
+       int i;
+       int id;
+
+       for (i = 0; i < CX18_MAX_STREAMS; i++) {
+               s = &cx->streams[i];
+               if ((handle == s->handle) && (s->dvb.enabled))
+                       break;
+               if (s->v4l2dev && handle == s->handle)
+                       break;
+       }
+       if (i == CX18_MAX_STREAMS) {
+               CX18_WARN("DMA done for unknown handle %d for stream %s\n",
+                       handle, s->name);
+               mb->error = CXERR_NOT_OPEN;
+               mb->cmd = 0;
+               cx18_mb_ack(cx, mb);
+               return;
+       }
+
+       off = mb->args[1];
+       if (mb->args[2] != 1)
+               CX18_WARN("Ack struct = %d for %s\n",
+                       mb->args[2], s->name);
+       id = read_enc(off);
+       buf = cx18_queue_find_buf(s, id, read_enc(off + 4));
+       CX18_DEBUG_HI_DMA("DMA DONE for %s (buffer %d)\n", s->name, id);
+       if (buf) {
+               cx18_buf_sync_for_cpu(s, buf);
+               if (s->type == CX18_ENC_STREAM_TYPE_TS && s->dvb.enabled) {
+                       /* process the buffer here */
+                       CX18_DEBUG_HI_DMA("TS recv and sent bytesused=%d\n",
+                                       buf->bytesused);
+
+                       dvb_dmx_swfilter(&s->dvb.demux, buf->buf,
+                                       buf->bytesused);
+
+                       cx18_buf_sync_for_device(s, buf);
+                       cx18_vapi(cx, CX18_CPU_DE_SET_MDL, 5, s->handle,
+                           (void *)&cx->scb->cpu_mdl[buf->id] - cx->enc_mem,
+                           1, buf->id, s->buf_size);
+               } else
+                       set_bit(CX18_F_B_NEED_BUF_SWAP, &buf->b_flags);
+       } else {
+               CX18_WARN("Could not find buf %d for stream %s\n",
+                               read_enc(off), s->name);
+       }
+       mb->error = 0;
+       mb->cmd = 0;
+       cx18_mb_ack(cx, mb);
+       wake_up(&cx->dma_waitq);
+       if (s->id != -1)
+               wake_up(&s->waitq);
+}
+
+static void epu_debug(struct cx18 *cx, struct cx18_mailbox *mb)
+{
+       char str[256] = { 0 };
+       char *p;
+
+       if (mb->args[1]) {
+               setup_page(mb->args[1]);
+               memcpy_fromio(str, cx->enc_mem + mb->args[1], 252);
+               str[252] = 0;
+       }
+       cx18_mb_ack(cx, mb);
+       CX18_DEBUG_INFO("%x %s\n", mb->args[0], str);
+       p = strchr(str, '.');
+       if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags) && p && p > str)
+               CX18_INFO("FW version: %s\n", p - 1);
+}
+
+static void hpu_cmd(struct cx18 *cx, u32 sw1)
+{
+       struct cx18_mailbox mb;
+
+       if (sw1 & IRQ_CPU_TO_EPU) {
+               memcpy_fromio(&mb, &cx->scb->cpu2epu_mb, sizeof(mb));
+               mb.error = 0;
+
+               switch (mb.cmd) {
+               case CX18_EPU_DMA_DONE:
+                       epu_dma_done(cx, &mb);
+                       break;
+               case CX18_EPU_DEBUG:
+                       epu_debug(cx, &mb);
+                       break;
+               default:
+                       CX18_WARN("Unexpected mailbox command %08x\n", mb.cmd);
+                       break;
+               }
+       }
+       if (sw1 & (IRQ_APU_TO_EPU | IRQ_HPU_TO_EPU))
+               CX18_WARN("Unexpected interrupt %08x\n", sw1);
+}
+
+irqreturn_t cx18_irq_handler(int irq, void *dev_id)
+{
+       struct cx18 *cx = (struct cx18 *)dev_id;
+       u32 sw1, sw1_mask;
+       u32 sw2, sw2_mask;
+       u32 hw2, hw2_mask;
+
+       spin_lock(&cx->dma_reg_lock);
+
+       hw2_mask = read_reg(HW2_INT_MASK5_PCI);
+       hw2 = read_reg(HW2_INT_CLR_STATUS) & hw2_mask;
+       sw2_mask = read_reg(SW2_INT_ENABLE_PCI) | IRQ_EPU_TO_HPU_ACK;
+       sw2 = read_reg(SW2_INT_STATUS) & sw2_mask;
+       sw1_mask = read_reg(SW1_INT_ENABLE_PCI) | IRQ_EPU_TO_HPU;
+       sw1 = read_reg(SW1_INT_STATUS) & sw1_mask;
+
+       write_reg(sw2&sw2_mask, SW2_INT_STATUS);
+       write_reg(sw1&sw1_mask, SW1_INT_STATUS);
+       write_reg(hw2&hw2_mask, HW2_INT_CLR_STATUS);
+
+       if (sw1 || sw2 || hw2)
+               CX18_DEBUG_HI_IRQ("SW1: %x  SW2: %x  HW2: %x\n", sw1, sw2, hw2);
+
+       /* To do: interrupt-based I2C handling
+       if (hw2 & 0x00c00000) {
+       }
+       */
+
+       if (sw2) {
+               if (sw2 & (cx->scb->cpu2hpu_irq_ack | cx->scb->cpu2epu_irq_ack))
+                       wake_up(&cx->mb_cpu_waitq);
+               if (sw2 & (cx->scb->apu2hpu_irq_ack | cx->scb->apu2epu_irq_ack))
+                       wake_up(&cx->mb_apu_waitq);
+               if (sw2 & cx->scb->epu2hpu_irq_ack)
+                       wake_up(&cx->mb_epu_waitq);
+               if (sw2 & cx->scb->hpu2epu_irq_ack)
+                       wake_up(&cx->mb_hpu_waitq);
+       }
+
+       if (sw1)
+               hpu_cmd(cx, sw1);
+       spin_unlock(&cx->dma_reg_lock);
+
+       return (hw2 | sw1 | sw2) ? IRQ_HANDLED : IRQ_NONE;
+}
diff --git a/drivers/media/video/cx18/cx18-irq.h b/drivers/media/video/cx18/cx18-irq.h
new file mode 100644 (file)
index 0000000..379f704
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *  cx18 interrupt handling
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#define HW2_I2C1_INT                   (1 << 22)
+#define HW2_I2C2_INT                   (1 << 23)
+#define HW2_INT_CLR_STATUS             0xc730c4
+#define HW2_INT_MASK5_PCI              0xc730e4
+#define SW1_INT_SET                     0xc73100
+#define SW1_INT_STATUS                  0xc73104
+#define SW1_INT_ENABLE_PCI              0xc7311c
+#define SW2_INT_SET                     0xc73140
+#define SW2_INT_STATUS                  0xc73144
+#define SW2_INT_ENABLE_PCI              0xc7315c
+
+irqreturn_t cx18_irq_handler(int irq, void *dev_id);
+
+void cx18_irq_work_handler(struct work_struct *work);
+void cx18_dma_stream_dec_prepare(struct cx18_stream *s, u32 offset, int lock);
+void cx18_unfinished_dma(unsigned long arg);
diff --git a/drivers/media/video/cx18/cx18-mailbox.c b/drivers/media/video/cx18/cx18-mailbox.c
new file mode 100644 (file)
index 0000000..0c5f328
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ *  cx18 mailbox functions
+ *
+ *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ *  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
+ */
+
+#include <stdarg.h>
+
+#include "cx18-driver.h"
+#include "cx18-scb.h"
+#include "cx18-irq.h"
+#include "cx18-mailbox.h"
+
+#define API_FAST (1 << 2) /* Short timeout */
+#define API_SLOW (1 << 3) /* Additional 300ms timeout */
+
+#define APU 0
+#define CPU 1
+#define EPU 2
+#define HPU 3
+
+struct cx18_api_info {
+       u32 cmd;
+       u8 flags;               /* Flags, see above */
+       u8 rpu;                 /* Processing unit */
+       const char *name;       /* The name of the command */
+};
+
+#define API_ENTRY(rpu, x, f) { (x), (f), (rpu), #x }
+
+static const struct cx18_api_info api_info[] = {
+       /* MPEG encoder API */
+       API_ENTRY(CPU, CX18_CPU_SET_CHANNEL_TYPE,               0),
+       API_ENTRY(CPU, CX18_EPU_DEBUG,                          0),
+       API_ENTRY(CPU, CX18_CREATE_TASK,                        0),
+       API_ENTRY(CPU, CX18_DESTROY_TASK,                       0),
+       API_ENTRY(CPU, CX18_CPU_CAPTURE_START,                  API_SLOW),
+       API_ENTRY(CPU, CX18_CPU_CAPTURE_STOP,                   API_SLOW),
+       API_ENTRY(CPU, CX18_CPU_CAPTURE_PAUSE,                  0),
+       API_ENTRY(CPU, CX18_CPU_CAPTURE_RESUME,                 0),
+       API_ENTRY(CPU, CX18_CPU_SET_CHANNEL_TYPE,               0),
+       API_ENTRY(CPU, CX18_CPU_SET_STREAM_OUTPUT_TYPE,         0),
+       API_ENTRY(CPU, CX18_CPU_SET_VIDEO_IN,                   0),
+       API_ENTRY(CPU, CX18_CPU_SET_VIDEO_RATE,                 0),
+       API_ENTRY(CPU, CX18_CPU_SET_VIDEO_RESOLUTION,           0),
+       API_ENTRY(CPU, CX18_CPU_SET_FILTER_PARAM,               0),
+       API_ENTRY(CPU, CX18_CPU_SET_SPATIAL_FILTER_TYPE,        0),
+       API_ENTRY(CPU, CX18_CPU_SET_MEDIAN_CORING,              0),
+       API_ENTRY(CPU, CX18_CPU_SET_INDEXTABLE,                 0),
+       API_ENTRY(CPU, CX18_CPU_SET_AUDIO_PARAMETERS,           0),
+       API_ENTRY(CPU, CX18_CPU_SET_VIDEO_MUTE,                 0),
+       API_ENTRY(CPU, CX18_CPU_SET_AUDIO_MUTE,                 0),
+       API_ENTRY(CPU, CX18_CPU_SET_MISC_PARAMETERS,            0),
+       API_ENTRY(CPU, CX18_CPU_SET_RAW_VBI_PARAM,              API_SLOW),
+       API_ENTRY(CPU, CX18_CPU_SET_CAPTURE_LINE_NO,            0),
+       API_ENTRY(CPU, CX18_CPU_SET_COPYRIGHT,                  0),
+       API_ENTRY(CPU, CX18_CPU_SET_AUDIO_PID,                  0),
+       API_ENTRY(CPU, CX18_CPU_SET_VIDEO_PID,                  0),
+       API_ENTRY(CPU, CX18_CPU_SET_VER_CROP_LINE,              0),
+       API_ENTRY(CPU, CX18_CPU_SET_GOP_STRUCTURE,              0),
+       API_ENTRY(CPU, CX18_CPU_SET_SCENE_CHANGE_DETECTION,     0),
+       API_ENTRY(CPU, CX18_CPU_SET_ASPECT_RATIO,               0),
+       API_ENTRY(CPU, CX18_CPU_SET_SKIP_INPUT_FRAME,           0),
+       API_ENTRY(CPU, CX18_CPU_SET_SLICED_VBI_PARAM,           0),
+       API_ENTRY(CPU, CX18_CPU_SET_USERDATA_PLACE_HOLDER,      0),
+       API_ENTRY(CPU, CX18_CPU_GET_ENC_PTS,                    0),
+       API_ENTRY(CPU, CX18_CPU_DE_SET_MDL_ACK,                 0),
+       API_ENTRY(CPU, CX18_CPU_DE_SET_MDL,                     API_FAST),
+       API_ENTRY(0, 0,                                         0),
+};
+
+static const struct cx18_api_info *find_api_info(u32 cmd)
+{
+       int i;
+
+       for (i = 0; api_info[i].cmd; i++)
+               if (api_info[i].cmd == cmd)
+                       return &api_info[i];
+       return NULL;
+}
+
+static struct cx18_mailbox *cx18_mb_is_complete(struct cx18 *cx, int rpu,
+               u32 *state, u32 *irq, u32 *req)
+{
+       struct cx18_mailbox *mb = NULL;
+       int wait_count = 0;
+       u32 ack;
+
+       switch (rpu) {
+       case APU:
+               mb = &cx->scb->epu2apu_mb;
+               *state = readl(&cx->scb->apu_state);
+               *irq = readl(&cx->scb->epu2apu_irq);
+               break;
+
+       case CPU:
+               mb = &cx->scb->epu2cpu_mb;
+               *state = readl(&cx->scb->cpu_state);
+               *irq = readl(&cx->scb->epu2cpu_irq);
+               break;
+
+       case HPU:
+               mb = &cx->scb->epu2hpu_mb;
+               *state = readl(&cx->scb->hpu_state);
+               *irq = readl(&cx->scb->epu2hpu_irq);
+               break;
+       }
+
+       if (mb == NULL)
+               return mb;
+
+       do {
+               *req = readl(&mb->request);
+               ack = readl(&mb->ack);
+               wait_count++;
+       } while (*req != ack && wait_count < 600);
+
+       if (*req == ack) {
+               (*req)++;
+               if (*req == 0 || *req == 0xffffffff)
+                       *req = 1;
+               return mb;
+       }
+       return NULL;
+}
+
+long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb)
+{
+       const struct cx18_api_info *info = find_api_info(mb->cmd);
+       struct cx18_mailbox *ack_mb;
+       u32 ack_irq;
+       u8 rpu = CPU;
+
+       if (info == NULL && mb->cmd) {
+               CX18_WARN("Cannot ack unknown command %x\n", mb->cmd);
+               return -EINVAL;
+       }
+       if (info)
+               rpu = info->rpu;
+
+       switch (rpu) {
+       case HPU:
+               ack_irq = IRQ_EPU_TO_HPU_ACK;
+               ack_mb = &cx->scb->hpu2epu_mb;
+               break;
+       case APU:
+               ack_irq = IRQ_EPU_TO_APU_ACK;
+               ack_mb = &cx->scb->apu2epu_mb;
+               break;
+       case CPU:
+               ack_irq = IRQ_EPU_TO_CPU_ACK;
+               ack_mb = &cx->scb->cpu2epu_mb;
+               break;
+       default:
+               CX18_WARN("Unknown RPU for command %x\n", mb->cmd);
+               return -EINVAL;
+       }
+
+       setup_page(SCB_OFFSET);
+       write_sync(mb->request, &ack_mb->ack);
+       write_reg(ack_irq, SW2_INT_SET);
+       return 0;
+}
+
+
+static int cx18_api_call(struct cx18 *cx, u32 cmd, int args, u32 data[])
+{
+       const struct cx18_api_info *info = find_api_info(cmd);
+       u32 state = 0, irq = 0, req, oldreq, err;
+       struct cx18_mailbox *mb;
+       wait_queue_head_t *waitq;
+       int timeout = 100;
+       int cnt = 0;
+       int sig = 0;
+       int i;
+
+       if (info == NULL) {
+               CX18_WARN("unknown cmd %x\n", cmd);
+               return -EINVAL;
+       }
+
+       if (cmd == CX18_CPU_DE_SET_MDL)
+               CX18_DEBUG_HI_API("%s\n", info->name);
+       else
+               CX18_DEBUG_API("%s\n", info->name);
+       setup_page(SCB_OFFSET);
+       mb = cx18_mb_is_complete(cx, info->rpu, &state, &irq, &req);
+
+       if (mb == NULL) {
+               CX18_ERR("mb %s busy\n", info->name);
+               return -EBUSY;
+       }
+
+       oldreq = req - 1;
+       writel(cmd, &mb->cmd);
+       for (i = 0; i < args; i++)
+               writel(data[i], &mb->args[i]);
+       writel(0, &mb->error);
+       writel(req, &mb->request);
+
+       switch (info->rpu) {
+       case APU: waitq = &cx->mb_apu_waitq; break;
+       case CPU: waitq = &cx->mb_cpu_waitq; break;
+       case EPU: waitq = &cx->mb_epu_waitq; break;
+       case HPU: waitq = &cx->mb_hpu_waitq; break;
+       default: return -EINVAL;
+       }
+       if (info->flags & API_FAST)
+               timeout /= 2;
+       write_reg(irq, SW1_INT_SET);
+
+       while (!sig && readl(&mb->ack) != readl(&mb->request) && cnt < 660) {
+               if (cnt > 200 && !in_atomic())
+                       sig = cx18_msleep_timeout(10, 1);
+               cnt++;
+       }
+       if (sig)
+               return -EINTR;
+       if (cnt == 660) {
+               writel(oldreq, &mb->request);
+               CX18_ERR("mb %s failed\n", info->name);
+               return -EINVAL;
+       }
+       for (i = 0; i < MAX_MB_ARGUMENTS; i++)
+               data[i] = readl(&mb->args[i]);
+       err = readl(&mb->error);
+       if (!in_atomic() && (info->flags & API_SLOW))
+               cx18_msleep_timeout(300, 0);
+       if (err)
+               CX18_DEBUG_API("mailbox error %08x for command %s\n", err,
+                               info->name);
+       return err ? -EIO : 0;
+}
+
+int cx18_api(struct cx18 *cx, u32 cmd, int args, u32 data[])
+{
+       int res = cx18_api_call(cx, cmd, args, data);
+
+       /* Allow a single retry, probably already too late though.
+          If there is no free mailbox then that is usually an indication
+          of a more serious problem. */
+       return (res == -EBUSY) ? cx18_api_call(cx, cmd, args, data) : res;
+}
+
+static int cx18_set_filter_param(struct cx18_stream *s)
+{
+       struct cx18 *cx = s->cx;
+       u32 mode;
+       int ret;
+
+       mode = (cx->filter_mode & 1) ? 2 : (cx->spatial_strength ? 1 : 0);
+       ret = cx18_vapi(cx, CX18_CPU_SET_FILTER_PARAM, 4,
+                       s->handle, 1, mode, cx->spatial_strength);
+       mode = (cx->filter_mode & 2) ? 2 : (cx->temporal_strength ? 1 : 0);
+       ret = ret ? ret : cx18_vapi(cx, CX18_CPU_SET_FILTER_PARAM, 4,
+                       s->handle, 0, mode, cx->temporal_strength);
+       ret = ret ? ret : cx18_vapi(cx, CX18_CPU_SET_FILTER_PARAM, 4,
+                       s->handle, 2, cx->filter_mode >> 2, 0);
+       return ret;
+}
+
+int cx18_api_func(void *priv, u32 cmd, int in, int out,
+               u32 data[CX2341X_MBOX_MAX_DATA])
+{
+       struct cx18 *cx = priv;
+       struct cx18_stream *s = &cx->streams[CX18_ENC_STREAM_TYPE_MPG];
+
+       switch (cmd) {
+       case CX2341X_ENC_SET_OUTPUT_PORT:
+               return 0;
+       case CX2341X_ENC_SET_FRAME_RATE:
+               return cx18_vapi(cx, CX18_CPU_SET_VIDEO_IN, 6,
+                               s->handle, 0, 0, 0, 0, data[0]);
+       case CX2341X_ENC_SET_FRAME_SIZE:
+               return cx18_vapi(cx, CX18_CPU_SET_VIDEO_RESOLUTION, 3,
+                               s->handle, data[1], data[0]);
+       case CX2341X_ENC_SET_STREAM_TYPE:
+               return cx18_vapi(cx, CX18_CPU_SET_STREAM_OUTPUT_TYPE, 2,
+                               s->handle, data[0]);
+       case CX2341X_ENC_SET_ASPECT_RATIO:
+               return cx18_vapi(cx, CX18_CPU_SET_ASPECT_RATIO, 2,
+                               s->handle, data[0]);
+
+       case CX2341X_ENC_SET_GOP_PROPERTIES:
+               return cx18_vapi(cx, CX18_CPU_SET_GOP_STRUCTURE, 3,
+   &