From: Hans Verkuil Date: Mon, 28 Apr 2008 23:24:33 +0000 (-0300) Subject: V4L/DVB (7786): cx18: new driver for the Conexant CX23418 MPEG encoder chip X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=1c1e45d17b663d4749af456ab7c2fc1f36405ef8;p=GitHub%2Fexynos8895%2Fandroid_kernel_samsung_universal8895.git V4L/DVB (7786): cx18: new driver for the Conexant CX23418 MPEG encoder chip 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 Signed-off-by: Steven Toth Signed-off-by: Michael Krufky Signed-off-by: G. Andrew Walls Signed-off-by: Mauro Carvalho Chehab --- diff --git a/Documentation/video4linux/cx18.txt b/Documentation/video4linux/cx18.txt new file mode 100644 index 000000000000..077d56ec3f3d --- /dev/null +++ b/Documentation/video4linux/cx18.txt @@ -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 diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 7dda5d54716d..fe743aa7f645 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -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 diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 560dc32d4cb7..a352c6e31f0c 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -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 index 000000000000..be654a27bd3c --- /dev/null +++ b/drivers/media/video/cx18/Kconfig @@ -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 index 000000000000..b23d2e26120f --- /dev/null +++ b/drivers/media/video/cx18/Makefile @@ -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 index 000000000000..1adc404d955e --- /dev/null +++ b/drivers/media/video/cx18/cx18-audio.c @@ -0,0 +1,73 @@ +/* + * cx18 audio-related functions + * + * Derived from ivtv-audio.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..cb569a69379c --- /dev/null +++ b/drivers/media/video/cx18/cx18-audio.h @@ -0,0 +1,26 @@ +/* + * cx18 audio-related functions + * + * Derived from ivtv-audio.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..2dc3a5dd170e --- /dev/null +++ b/drivers/media/video/cx18/cx18-av-audio.c @@ -0,0 +1,361 @@ +/* + * cx18 ADEC audio functions + * + * Derived from cx25840-audio.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..66864904c99b --- /dev/null +++ b/drivers/media/video/cx18/cx18-av-core.c @@ -0,0 +1,879 @@ +/* + * cx18 ADEC audio functions + * + * Derived from cx25840-core.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..786901d72e9a --- /dev/null +++ b/drivers/media/video/cx18/cx18-av-core.h @@ -0,0 +1,318 @@ +/* + * cx18 ADEC header + * + * Derived from cx25840-core.h + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..526e142156cd --- /dev/null +++ b/drivers/media/video/cx18/cx18-av-firmware.c @@ -0,0 +1,120 @@ +/* + * cx18 ADEC firmware functions + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 + +#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 index 000000000000..d09f1daf4ebf --- /dev/null +++ b/drivers/media/video/cx18/cx18-av-vbi.c @@ -0,0 +1,413 @@ +/* + * cx18 ADEC VBI functions + * + * Derived from cx25840-vbi.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..f5e3ba1f5354 --- /dev/null +++ b/drivers/media/video/cx18/cx18-cards.c @@ -0,0 +1,277 @@ +/* + * cx18 functions to query card hardware + * + * Derived from ivtv-cards.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 + +/********************** 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 index 000000000000..bca249bdd337 --- /dev/null +++ b/drivers/media/video/cx18/cx18-cards.h @@ -0,0 +1,170 @@ +/* + * cx18 functions to query card hardware + * + * Derived from ivtv-cards.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..da299ae61cff --- /dev/null +++ b/drivers/media/video/cx18/cx18-controls.c @@ -0,0 +1,306 @@ +/* + * cx18 ioctl control functions + * + * Derived from ivtv-controls.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..6e985cf422a0 --- /dev/null +++ b/drivers/media/video/cx18/cx18-controls.h @@ -0,0 +1,24 @@ +/* + * cx18 ioctl control functions + * + * Derived from ivtv-controls.h + * + * Copyright (C) 2007 Hans Verkuil + + * 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 index 000000000000..9f31befc3139 --- /dev/null +++ b/drivers/media/video/cx18/cx18-driver.c @@ -0,0 +1,971 @@ +/* + * cx18 driver initialization and card probing + * + * Derived from ivtv-driver.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 + + +/* 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 index 000000000000..2ee939193bb7 --- /dev/null +++ b/drivers/media/video/cx18/cx18-driver.h @@ -0,0 +1,500 @@ +/* + * cx18 driver internal defines and structures + * + * Derived from ivtv-driver.h + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#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 index 000000000000..65efe69d939a --- /dev/null +++ b/drivers/media/video/cx18/cx18-dvb.c @@ -0,0 +1,288 @@ +/* + * cx18 functions for DVB support + * + * Copyright (c) 2008 Steven Toth + * + * 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 index 000000000000..d6a6ccda79a9 --- /dev/null +++ b/drivers/media/video/cx18/cx18-dvb.h @@ -0,0 +1,25 @@ +/* + * cx18 functions for DVB support + * + * Copyright (c) 2008 Steven Toth + * + * 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 index 000000000000..69303065a294 --- /dev/null +++ b/drivers/media/video/cx18/cx18-fileops.c @@ -0,0 +1,711 @@ +/* + * cx18 file operation functions + * + * Derived from ivtv-fileops.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..16cdafbd24c5 --- /dev/null +++ b/drivers/media/video/cx18/cx18-fileops.h @@ -0,0 +1,45 @@ +/* + * cx18 file operation functions + * + * Derived from ivtv-fileops.h + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..2694ce350631 --- /dev/null +++ b/drivers/media/video/cx18/cx18-firmware.c @@ -0,0 +1,373 @@ +/* + * cx18 firmware functions + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 + +#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 index 000000000000..38d4c05e8499 --- /dev/null +++ b/drivers/media/video/cx18/cx18-firmware.h @@ -0,0 +1,25 @@ +/* + * cx18 firmware functions + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..19253e6b8673 --- /dev/null +++ b/drivers/media/video/cx18/cx18-gpio.c @@ -0,0 +1,74 @@ +/* + * cx18 gpio functions + * + * Derived from ivtv-gpio.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..41bac8856b50 --- /dev/null +++ b/drivers/media/video/cx18/cx18-gpio.h @@ -0,0 +1,24 @@ +/* + * cx18 gpio functions + * + * Derived from ivtv-gpio.h + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..18c88d1e4833 --- /dev/null +++ b/drivers/media/video/cx18/cx18-i2c.c @@ -0,0 +1,431 @@ +/* + * cx18 I2C functions + * + * Derived from ivtv-i2c.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 + +#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 */ + 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 index 000000000000..113c3f9a2cc0 --- /dev/null +++ b/drivers/media/video/cx18/cx18-i2c.h @@ -0,0 +1,33 @@ +/* + * cx18 I2C functions + * + * Derived from ivtv-i2c.h + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..4a93af2e4bb6 --- /dev/null +++ b/drivers/media/video/cx18/cx18-ioctl.c @@ -0,0 +1,851 @@ +/* + * cx18 ioctl system call + * + * Derived from ivtv-ioctl.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 +#include +#include + +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 index 000000000000..0ee152d837d6 --- /dev/null +++ b/drivers/media/video/cx18/cx18-ioctl.h @@ -0,0 +1,30 @@ +/* + * cx18 ioctl system call + * + * Derived from ivtv-ioctl.h + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..6e14f8bda559 --- /dev/null +++ b/drivers/media/video/cx18/cx18-irq.c @@ -0,0 +1,179 @@ +/* + * cx18 interrupt handling + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..379f704f5cba --- /dev/null +++ b/drivers/media/video/cx18/cx18-irq.h @@ -0,0 +1,37 @@ +/* + * cx18 interrupt handling + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 index 000000000000..0c5f328bca54 --- /dev/null +++ b/drivers/media/video/cx18/cx18-mailbox.c @@ -0,0 +1,372 @@ +/* + * cx18 mailbox functions + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 + +#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, + s->handle, data[0], data[1]); + case CX2341X_ENC_SET_GOP_CLOSURE: + return 0; + case CX2341X_ENC_SET_AUDIO_PROPERTIES: + return cx18_vapi(cx, CX18_CPU_SET_AUDIO_PARAMETERS, 2, + s->handle, data[0]); + case CX2341X_ENC_MUTE_AUDIO: + return cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2, + s->handle, data[0]); + case CX2341X_ENC_SET_BIT_RATE: + return cx18_vapi(cx, CX18_CPU_SET_VIDEO_RATE, 5, + s->handle, data[0], data[1], data[2], data[3]); + case CX2341X_ENC_MUTE_VIDEO: + return cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, + s->handle, data[0]); + case CX2341X_ENC_SET_FRAME_DROP_RATE: + return cx18_vapi(cx, CX18_CPU_SET_SKIP_INPUT_FRAME, 2, + s->handle, data[0]); + case CX2341X_ENC_MISC: + return cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 4, + s->handle, data[0], data[1], data[2]); + case CX2341X_ENC_SET_DNR_FILTER_MODE: + cx->filter_mode = (data[0] & 3) | (data[1] << 2); + return cx18_set_filter_param(s); + case CX2341X_ENC_SET_DNR_FILTER_PROPS: + cx->spatial_strength = data[0]; + cx->temporal_strength = data[1]; + return cx18_set_filter_param(s); + case CX2341X_ENC_SET_SPATIAL_FILTER_TYPE: + return cx18_vapi(cx, CX18_CPU_SET_SPATIAL_FILTER_TYPE, 3, + s->handle, data[0], data[1]); + case CX2341X_ENC_SET_CORING_LEVELS: + return cx18_vapi(cx, CX18_CPU_SET_MEDIAN_CORING, 5, + s->handle, data[0], data[1], data[2], data[3]); + } + CX18_WARN("Unknown cmd %x\n", cmd); + return 0; +} + +int cx18_vapi_result(struct cx18 *cx, u32 data[MAX_MB_ARGUMENTS], + u32 cmd, int args, ...) +{ + va_list ap; + int i; + + va_start(ap, args); + for (i = 0; i < args; i++) + data[i] = va_arg(ap, u32); + va_end(ap); + return cx18_api(cx, cmd, args, data); +} + +int cx18_vapi(struct cx18 *cx, u32 cmd, int args, ...) +{ + u32 data[MAX_MB_ARGUMENTS]; + va_list ap; + int i; + + if (cx == NULL) { + CX18_ERR("cx == NULL (cmd=%x)\n", cmd); + return 0; + } + if (args > MAX_MB_ARGUMENTS) { + CX18_ERR("args too big (cmd=%x)\n", cmd); + args = MAX_MB_ARGUMENTS; + } + va_start(ap, args); + for (i = 0; i < args; i++) + data[i] = va_arg(ap, u32); + va_end(ap); + return cx18_api(cx, cmd, args, data); +} diff --git a/drivers/media/video/cx18/cx18-mailbox.h b/drivers/media/video/cx18/cx18-mailbox.h new file mode 100644 index 000000000000..d995641536b3 --- /dev/null +++ b/drivers/media/video/cx18/cx18-mailbox.h @@ -0,0 +1,73 @@ +/* + * cx18 mailbox functions + * + * Copyright (C) 2007 Hans Verkuil + * + * 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_MAILBOX_H_ +#define _CX18_MAILBOX_H_ + +/* mailbox max args */ +#define MAX_MB_ARGUMENTS 6 +/* compatibility, should be same as the define in cx2341x.h */ +#define CX2341X_MBOX_MAX_DATA 16 + +#define MB_RESERVED_HANDLE_0 0 +#define MB_RESERVED_HANDLE_1 0xFFFFFFFF + +struct cx18; + +/* The cx18_mailbox struct is the mailbox structure which is used for passing + messages between processors */ +struct cx18_mailbox { + /* The sender sets a handle in 'request' after he fills the command. The + 'request' should be different than 'ack'. The sender, also, generates + an interrupt on XPU2YPU_irq where XPU is the sender and YPU is the + receiver. */ + u32 request; + /* The receiver detects a new command when 'req' is different than 'ack'. + He sets 'ack' to the same value as 'req' to clear the command. He, also, + generates an interrupt on YPU2XPU_irq where XPU is the sender and YPU + is the receiver. */ + u32 ack; + u32 reserved[6]; + /* 'cmd' identifies the command. The list of these commands are in + cx23418.h */ + u32 cmd; + /* Each command can have up to 6 arguments */ + u32 args[MAX_MB_ARGUMENTS]; + /* The return code can be one of the codes in the file cx23418.h. If the + command is completed successfuly, the error will be ERR_SYS_SUCCESS. + If it is pending, the code is ERR_SYS_PENDING. If it failed, the error + code would indicate the task from which the error originated and will + be one of the errors in cx23418.h. In that case, the following + applies ((error & 0xff) != 0). + If the command is pending, the return will be passed in a MB from the + receiver to the sender. 'req' will be returned in args[0] */ + u32 error; +}; + +int cx18_api(struct cx18 *cx, u32 cmd, int args, u32 data[]); +int cx18_vapi_result(struct cx18 *cx, u32 data[MAX_MB_ARGUMENTS], u32 cmd, + int args, ...); +int cx18_vapi(struct cx18 *cx, u32 cmd, int args, ...); +int cx18_api_func(void *priv, u32 cmd, int in, int out, + u32 data[CX2341X_MBOX_MAX_DATA]); +long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb); + +#endif diff --git a/drivers/media/video/cx18/cx18-queue.c b/drivers/media/video/cx18/cx18-queue.c new file mode 100644 index 000000000000..65af1bb507ca --- /dev/null +++ b/drivers/media/video/cx18/cx18-queue.c @@ -0,0 +1,282 @@ +/* + * cx18 buffer queues + * + * Derived from ivtv-queue.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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-streams.h" +#include "cx18-queue.h" +#include "cx18-scb.h" + +int cx18_buf_copy_from_user(struct cx18_stream *s, struct cx18_buffer *buf, + const char __user *src, int copybytes) +{ + if (s->buf_size - buf->bytesused < copybytes) + copybytes = s->buf_size - buf->bytesused; + if (copy_from_user(buf->buf + buf->bytesused, src, copybytes)) + return -EFAULT; + buf->bytesused += copybytes; + return copybytes; +} + +void cx18_buf_swap(struct cx18_buffer *buf) +{ + int i; + + for (i = 0; i < buf->bytesused; i += 4) + swab32s((u32 *)(buf->buf + i)); +} + +void cx18_queue_init(struct cx18_queue *q) +{ + INIT_LIST_HEAD(&q->list); + q->buffers = 0; + q->length = 0; + q->bytesused = 0; +} + +void cx18_enqueue(struct cx18_stream *s, struct cx18_buffer *buf, + struct cx18_queue *q) +{ + unsigned long flags = 0; + + /* clear the buffer if it is going to be enqueued to the free queue */ + if (q == &s->q_free) { + buf->bytesused = 0; + buf->readpos = 0; + buf->b_flags = 0; + } + spin_lock_irqsave(&s->qlock, flags); + list_add_tail(&buf->list, &q->list); + q->buffers++; + q->length += s->buf_size; + q->bytesused += buf->bytesused - buf->readpos; + spin_unlock_irqrestore(&s->qlock, flags); +} + +struct cx18_buffer *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q) +{ + struct cx18_buffer *buf = NULL; + unsigned long flags = 0; + + spin_lock_irqsave(&s->qlock, flags); + if (!list_empty(&q->list)) { + buf = list_entry(q->list.next, struct cx18_buffer, list); + list_del_init(q->list.next); + q->buffers--; + q->length -= s->buf_size; + q->bytesused -= buf->bytesused - buf->readpos; + } + spin_unlock_irqrestore(&s->qlock, flags); + return buf; +} + +struct cx18_buffer *cx18_queue_find_buf(struct cx18_stream *s, u32 id, + u32 bytesused) +{ + struct cx18 *cx = s->cx; + struct list_head *p; + + list_for_each(p, &s->q_free.list) { + struct cx18_buffer *buf = + list_entry(p, struct cx18_buffer, list); + + if (buf->id != id) + continue; + buf->bytesused = bytesused; + /* the transport buffers are handled differently, + so there is no need to move them to the full queue */ + if (s->type == CX18_ENC_STREAM_TYPE_TS) + return buf; + s->q_free.buffers--; + s->q_free.length -= s->buf_size; + s->q_full.buffers++; + s->q_full.length += s->buf_size; + s->q_full.bytesused += buf->bytesused; + list_move_tail(&buf->list, &s->q_full.list); + return buf; + } + CX18_ERR("Cannot find buffer %d for stream %s\n", id, s->name); + return NULL; +} + +static void cx18_queue_move_buf(struct cx18_stream *s, struct cx18_queue *from, + struct cx18_queue *to, int clear, int full) +{ + struct cx18_buffer *buf = + list_entry(from->list.next, struct cx18_buffer, list); + + list_move_tail(from->list.next, &to->list); + from->buffers--; + from->length -= s->buf_size; + from->bytesused -= buf->bytesused - buf->readpos; + /* special handling for q_free */ + if (clear) + buf->bytesused = buf->readpos = buf->b_flags = 0; + else if (full) { + /* special handling for stolen buffers, assume + all bytes are used. */ + buf->bytesused = s->buf_size; + buf->readpos = buf->b_flags = 0; + } + to->buffers++; + to->length += s->buf_size; + to->bytesused += buf->bytesused - buf->readpos; +} + +/* Move 'needed_bytes' worth of buffers from queue 'from' into queue 'to'. + If 'needed_bytes' == 0, then move all buffers from 'from' into 'to'. + If 'steal' != NULL, then buffers may also taken from that queue if + needed. + + The buffer is automatically cleared if it goes to the free queue. It is + also cleared if buffers need to be taken from the 'steal' queue and + the 'from' queue is the free queue. + + When 'from' is q_free, then needed_bytes is compared to the total + available buffer length, otherwise needed_bytes is compared to the + bytesused value. For the 'steal' queue the total available buffer + length is always used. + + -ENOMEM is returned if the buffers could not be obtained, 0 if all + buffers where obtained from the 'from' list and if non-zero then + the number of stolen buffers is returned. */ +int cx18_queue_move(struct cx18_stream *s, struct cx18_queue *from, + struct cx18_queue *steal, struct cx18_queue *to, int needed_bytes) +{ + unsigned long flags; + int rc = 0; + int from_free = from == &s->q_free; + int to_free = to == &s->q_free; + int bytes_available; + + spin_lock_irqsave(&s->qlock, flags); + if (needed_bytes == 0) { + from_free = 1; + needed_bytes = from->length; + } + + bytes_available = from_free ? from->length : from->bytesused; + bytes_available += steal ? steal->length : 0; + + if (bytes_available < needed_bytes) { + spin_unlock_irqrestore(&s->qlock, flags); + return -ENOMEM; + } + if (from_free) { + u32 old_length = to->length; + + while (to->length - old_length < needed_bytes) { + if (list_empty(&from->list)) + from = steal; + if (from == steal) + rc++; /* keep track of 'stolen' buffers */ + cx18_queue_move_buf(s, from, to, 1, 0); + } + } else { + u32 old_bytesused = to->bytesused; + + while (to->bytesused - old_bytesused < needed_bytes) { + if (list_empty(&from->list)) + from = steal; + if (from == steal) + rc++; /* keep track of 'stolen' buffers */ + cx18_queue_move_buf(s, from, to, to_free, rc); + } + } + spin_unlock_irqrestore(&s->qlock, flags); + return rc; +} + +void cx18_flush_queues(struct cx18_stream *s) +{ + cx18_queue_move(s, &s->q_io, NULL, &s->q_free, 0); + cx18_queue_move(s, &s->q_full, NULL, &s->q_free, 0); +} + +int cx18_stream_alloc(struct cx18_stream *s) +{ + struct cx18 *cx = s->cx; + int i; + + if (s->buffers == 0) + return 0; + + CX18_DEBUG_INFO("Allocate %s stream: %d x %d buffers (%dkB total)\n", + s->name, s->buffers, s->buf_size, + s->buffers * s->buf_size / 1024); + + if (((char *)&cx->scb->cpu_mdl[cx->mdl_offset + s->buffers] - + (char *)cx->scb) > SCB_RESERVED_SIZE) { + unsigned bufsz = (((char *)cx->scb) + SCB_RESERVED_SIZE - + ((char *)cx->scb->cpu_mdl)); + + CX18_ERR("Too many buffers, cannot fit in SCB area\n"); + CX18_ERR("Max buffers = %zd\n", + bufsz / sizeof(struct cx18_mdl)); + return -ENOMEM; + } + + s->mdl_offset = cx->mdl_offset; + + /* allocate stream buffers. Initially all buffers are in q_free. */ + for (i = 0; i < s->buffers; i++) { + struct cx18_buffer *buf = + kzalloc(sizeof(struct cx18_buffer), GFP_KERNEL); + + if (buf == NULL) + break; + buf->buf = kmalloc(s->buf_size, GFP_KERNEL); + if (buf->buf == NULL) { + kfree(buf); + break; + } + buf->id = cx->buffer_id++; + INIT_LIST_HEAD(&buf->list); + buf->dma_handle = pci_map_single(s->cx->dev, + buf->buf, s->buf_size, s->dma); + cx18_buf_sync_for_cpu(s, buf); + cx18_enqueue(s, buf, &s->q_free); + } + if (i == s->buffers) { + cx->mdl_offset += s->buffers; + return 0; + } + CX18_ERR("Couldn't allocate buffers for %s stream\n", s->name); + cx18_stream_free(s); + return -ENOMEM; +} + +void cx18_stream_free(struct cx18_stream *s) +{ + struct cx18_buffer *buf; + + /* move all buffers to q_free */ + cx18_flush_queues(s); + + /* empty q_free */ + while ((buf = cx18_dequeue(s, &s->q_free))) { + pci_unmap_single(s->cx->dev, buf->dma_handle, + s->buf_size, s->dma); + kfree(buf->buf); + kfree(buf); + } +} diff --git a/drivers/media/video/cx18/cx18-queue.h b/drivers/media/video/cx18/cx18-queue.h new file mode 100644 index 000000000000..f86c8a6fa6e7 --- /dev/null +++ b/drivers/media/video/cx18/cx18-queue.h @@ -0,0 +1,59 @@ +/* + * cx18 buffer queues + * + * Derived from ivtv-queue.h + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 CX18_DMA_UNMAPPED ((u32) -1) + +/* cx18_buffer utility functions */ + +static inline void cx18_buf_sync_for_cpu(struct cx18_stream *s, + struct cx18_buffer *buf) +{ + pci_dma_sync_single_for_cpu(s->cx->dev, buf->dma_handle, + s->buf_size, s->dma); +} + +static inline void cx18_buf_sync_for_device(struct cx18_stream *s, + struct cx18_buffer *buf) +{ + pci_dma_sync_single_for_device(s->cx->dev, buf->dma_handle, + s->buf_size, s->dma); +} + +int cx18_buf_copy_from_user(struct cx18_stream *s, struct cx18_buffer *buf, + const char __user *src, int copybytes); +void cx18_buf_swap(struct cx18_buffer *buf); + +/* cx18_queue utility functions */ +void cx18_queue_init(struct cx18_queue *q); +void cx18_enqueue(struct cx18_stream *s, struct cx18_buffer *buf, + struct cx18_queue *q); +struct cx18_buffer *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q); +int cx18_queue_move(struct cx18_stream *s, struct cx18_queue *from, + struct cx18_queue *steal, struct cx18_queue *to, int needed_bytes); +struct cx18_buffer *cx18_queue_find_buf(struct cx18_stream *s, u32 id, + u32 bytesused); +void cx18_flush_queues(struct cx18_stream *s); + +/* cx18_stream utility functions */ +int cx18_stream_alloc(struct cx18_stream *s); +void cx18_stream_free(struct cx18_stream *s); diff --git a/drivers/media/video/cx18/cx18-scb.c b/drivers/media/video/cx18/cx18-scb.c new file mode 100644 index 000000000000..30bc803e30da --- /dev/null +++ b/drivers/media/video/cx18/cx18-scb.c @@ -0,0 +1,121 @@ +/* + * cx18 System Control Block initialization + * + * Copyright (C) 2007 Hans Verkuil + * + * 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" + +void cx18_init_scb(struct cx18 *cx) +{ + setup_page(SCB_OFFSET); + memset_io(cx->scb, 0, 0x10000); + + writel(IRQ_APU_TO_CPU, &cx->scb->apu2cpu_irq); + writel(IRQ_CPU_TO_APU_ACK, &cx->scb->cpu2apu_irq_ack); + writel(IRQ_HPU_TO_CPU, &cx->scb->hpu2cpu_irq); + writel(IRQ_CPU_TO_HPU_ACK, &cx->scb->cpu2hpu_irq_ack); + writel(IRQ_PPU_TO_CPU, &cx->scb->ppu2cpu_irq); + writel(IRQ_CPU_TO_PPU_ACK, &cx->scb->cpu2ppu_irq_ack); + writel(IRQ_EPU_TO_CPU, &cx->scb->epu2cpu_irq); + writel(IRQ_CPU_TO_EPU_ACK, &cx->scb->cpu2epu_irq_ack); + + writel(IRQ_CPU_TO_APU, &cx->scb->cpu2apu_irq); + writel(IRQ_APU_TO_CPU_ACK, &cx->scb->apu2cpu_irq_ack); + writel(IRQ_HPU_TO_APU, &cx->scb->hpu2apu_irq); + writel(IRQ_APU_TO_HPU_ACK, &cx->scb->apu2hpu_irq_ack); + writel(IRQ_PPU_TO_APU, &cx->scb->ppu2apu_irq); + writel(IRQ_APU_TO_PPU_ACK, &cx->scb->apu2ppu_irq_ack); + writel(IRQ_EPU_TO_APU, &cx->scb->epu2apu_irq); + writel(IRQ_APU_TO_EPU_ACK, &cx->scb->apu2epu_irq_ack); + + writel(IRQ_CPU_TO_HPU, &cx->scb->cpu2hpu_irq); + writel(IRQ_HPU_TO_CPU_ACK, &cx->scb->hpu2cpu_irq_ack); + writel(IRQ_APU_TO_HPU, &cx->scb->apu2hpu_irq); + writel(IRQ_HPU_TO_APU_ACK, &cx->scb->hpu2apu_irq_ack); + writel(IRQ_PPU_TO_HPU, &cx->scb->ppu2hpu_irq); + writel(IRQ_HPU_TO_PPU_ACK, &cx->scb->hpu2ppu_irq_ack); + writel(IRQ_EPU_TO_HPU, &cx->scb->epu2hpu_irq); + writel(IRQ_HPU_TO_EPU_ACK, &cx->scb->hpu2epu_irq_ack); + + writel(IRQ_CPU_TO_PPU, &cx->scb->cpu2ppu_irq); + writel(IRQ_PPU_TO_CPU_ACK, &cx->scb->ppu2cpu_irq_ack); + writel(IRQ_APU_TO_PPU, &cx->scb->apu2ppu_irq); + writel(IRQ_PPU_TO_APU_ACK, &cx->scb->ppu2apu_irq_ack); + writel(IRQ_HPU_TO_PPU, &cx->scb->hpu2ppu_irq); + writel(IRQ_PPU_TO_HPU_ACK, &cx->scb->ppu2hpu_irq_ack); + writel(IRQ_EPU_TO_PPU, &cx->scb->epu2ppu_irq); + writel(IRQ_PPU_TO_EPU_ACK, &cx->scb->ppu2epu_irq_ack); + + writel(IRQ_CPU_TO_EPU, &cx->scb->cpu2epu_irq); + writel(IRQ_EPU_TO_CPU_ACK, &cx->scb->epu2cpu_irq_ack); + writel(IRQ_APU_TO_EPU, &cx->scb->apu2epu_irq); + writel(IRQ_EPU_TO_APU_ACK, &cx->scb->epu2apu_irq_ack); + writel(IRQ_HPU_TO_EPU, &cx->scb->hpu2epu_irq); + writel(IRQ_EPU_TO_HPU_ACK, &cx->scb->epu2hpu_irq_ack); + writel(IRQ_PPU_TO_EPU, &cx->scb->ppu2epu_irq); + writel(IRQ_EPU_TO_PPU_ACK, &cx->scb->epu2ppu_irq_ack); + + writel(SCB_OFFSET + offsetof(struct cx18_scb, apu2cpu_mb), + &cx->scb->apu2cpu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, hpu2cpu_mb), + &cx->scb->hpu2cpu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, ppu2cpu_mb), + &cx->scb->ppu2cpu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, epu2cpu_mb), + &cx->scb->epu2cpu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, cpu2apu_mb), + &cx->scb->cpu2apu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, hpu2apu_mb), + &cx->scb->hpu2apu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, ppu2apu_mb), + &cx->scb->ppu2apu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, epu2apu_mb), + &cx->scb->epu2apu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, cpu2hpu_mb), + &cx->scb->cpu2hpu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, apu2hpu_mb), + &cx->scb->apu2hpu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, ppu2hpu_mb), + &cx->scb->ppu2hpu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, epu2hpu_mb), + &cx->scb->epu2hpu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, cpu2ppu_mb), + &cx->scb->cpu2ppu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, apu2ppu_mb), + &cx->scb->apu2ppu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, hpu2ppu_mb), + &cx->scb->hpu2ppu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, epu2ppu_mb), + &cx->scb->epu2ppu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, cpu2epu_mb), + &cx->scb->cpu2epu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, apu2epu_mb), + &cx->scb->apu2epu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, hpu2epu_mb), + &cx->scb->hpu2epu_mb_offset); + writel(SCB_OFFSET + offsetof(struct cx18_scb, ppu2epu_mb), + &cx->scb->ppu2epu_mb_offset); + + writel(SCB_OFFSET + offsetof(struct cx18_scb, cpu_state), + &cx->scb->ipc_offset); + + writel(1, &cx->scb->hpu_state); + writel(1, &cx->scb->epu_state); +} diff --git a/drivers/media/video/cx18/cx18-scb.h b/drivers/media/video/cx18/cx18-scb.h new file mode 100644 index 000000000000..86b4cb15d163 --- /dev/null +++ b/drivers/media/video/cx18/cx18-scb.h @@ -0,0 +1,285 @@ +/* + * cx18 System Control Block initialization + * + * Copyright (C) 2007 Hans Verkuil + * + * 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_SCB_H +#define CX18_SCB_H + +#include "cx18-mailbox.h" + +/* NOTE: All ACK interrupts are in the SW2 register. All non-ACK interrupts + are in the SW1 register. */ + +#define IRQ_APU_TO_CPU 0x00000001 +#define IRQ_CPU_TO_APU_ACK 0x00000001 +#define IRQ_HPU_TO_CPU 0x00000002 +#define IRQ_CPU_TO_HPU_ACK 0x00000002 +#define IRQ_PPU_TO_CPU 0x00000004 +#define IRQ_CPU_TO_PPU_ACK 0x00000004 +#define IRQ_EPU_TO_CPU 0x00000008 +#define IRQ_CPU_TO_EPU_ACK 0x00000008 + +#define IRQ_CPU_TO_APU 0x00000010 +#define IRQ_APU_TO_CPU_ACK 0x00000010 +#define IRQ_HPU_TO_APU 0x00000020 +#define IRQ_APU_TO_HPU_ACK 0x00000020 +#define IRQ_PPU_TO_APU 0x00000040 +#define IRQ_APU_TO_PPU_ACK 0x00000040 +#define IRQ_EPU_TO_APU 0x00000080 +#define IRQ_APU_TO_EPU_ACK 0x00000080 + +#define IRQ_CPU_TO_HPU 0x00000100 +#define IRQ_HPU_TO_CPU_ACK 0x00000100 +#define IRQ_APU_TO_HPU 0x00000200 +#define IRQ_HPU_TO_APU_ACK 0x00000200 +#define IRQ_PPU_TO_HPU 0x00000400 +#define IRQ_HPU_TO_PPU_ACK 0x00000400 +#define IRQ_EPU_TO_HPU 0x00000800 +#define IRQ_HPU_TO_EPU_ACK 0x00000800 + +#define IRQ_CPU_TO_PPU 0x00001000 +#define IRQ_PPU_TO_CPU_ACK 0x00001000 +#define IRQ_APU_TO_PPU 0x00002000 +#define IRQ_PPU_TO_APU_ACK 0x00002000 +#define IRQ_HPU_TO_PPU 0x00004000 +#define IRQ_PPU_TO_HPU_ACK 0x00004000 +#define IRQ_EPU_TO_PPU 0x00008000 +#define IRQ_PPU_TO_EPU_ACK 0x00008000 + +#define IRQ_CPU_TO_EPU 0x00010000 +#define IRQ_EPU_TO_CPU_ACK 0x00010000 +#define IRQ_APU_TO_EPU 0x00020000 +#define IRQ_EPU_TO_APU_ACK 0x00020000 +#define IRQ_HPU_TO_EPU 0x00040000 +#define IRQ_EPU_TO_HPU_ACK 0x00040000 +#define IRQ_PPU_TO_EPU 0x00080000 +#define IRQ_EPU_TO_PPU_ACK 0x00080000 + +#define SCB_OFFSET 0xDC0000 + +/* If Firmware uses fixed memory map, it shall not allocate the area + between SCB_OFFSET and SCB_OFFSET+SCB_RESERVED_SIZE-1 inclusive */ +#define SCB_RESERVED_SIZE 0x10000 + + +/* This structure is used by EPU to provide memory descriptors in its memory */ +struct cx18_mdl { + u32 paddr; /* Physical address of a buffer segment */ + u32 length; /* Length of the buffer segment */ +}; + +/* This structure is used by CPU to provide completed buffers information */ +struct cx18_mdl_ack { + u32 id; /* ID of a completed MDL */ + u32 data_used; /* Total data filled in the MDL for buffer 'id' */ +}; + +struct cx18_scb { + /* These fields form the System Control Block which is used at boot time + for localizing the IPC data as well as the code positions for all + processors. The offsets are from the start of this struct. */ + + /* Offset where to find the Inter-Processor Communication data */ + u32 ipc_offset; + u32 reserved01[7]; + /* Offset where to find the start of the CPU code */ + u32 cpu_code_offset; + u32 reserved02[3]; + /* Offset where to find the start of the APU code */ + u32 apu_code_offset; + u32 reserved03[3]; + /* Offset where to find the start of the HPU code */ + u32 hpu_code_offset; + u32 reserved04[3]; + /* Offset where to find the start of the PPU code */ + u32 ppu_code_offset; + u32 reserved05[3]; + + /* These fields form Inter-Processor Communication data which is used + by all processors to locate the information needed for communicating + with other processors */ + + /* Fields for CPU: */ + + /* bit 0: 1/0 processor ready/not ready. Set other bits to 0. */ + u32 cpu_state; + u32 reserved1[7]; + /* Offset to the mailbox used for sending commands from APU to CPU */ + u32 apu2cpu_mb_offset; + /* Value to write to register SW1 register set (0xC7003100) after the + command is ready */ + u32 apu2cpu_irq; + /* Value to write to register SW2 register set (0xC7003140) after the + command is cleared */ + u32 apu2cpu_irq_ack; + u32 reserved2[13]; + + u32 hpu2cpu_mb_offset; + u32 hpu2cpu_irq; + u32 hpu2cpu_irq_ack; + u32 reserved3[13]; + + u32 ppu2cpu_mb_offset; + u32 ppu2cpu_irq; + u32 ppu2cpu_irq_ack; + u32 reserved4[13]; + + u32 epu2cpu_mb_offset; + u32 epu2cpu_irq; + u32 epu2cpu_irq_ack; + u32 reserved5[13]; + u32 reserved6[8]; + + /* Fields for APU: */ + + u32 apu_state; + u32 reserved11[7]; + u32 cpu2apu_mb_offset; + u32 cpu2apu_irq; + u32 cpu2apu_irq_ack; + u32 reserved12[13]; + + u32 hpu2apu_mb_offset; + u32 hpu2apu_irq; + u32 hpu2apu_irq_ack; + u32 reserved13[13]; + + u32 ppu2apu_mb_offset; + u32 ppu2apu_irq; + u32 ppu2apu_irq_ack; + u32 reserved14[13]; + + u32 epu2apu_mb_offset; + u32 epu2apu_irq; + u32 epu2apu_irq_ack; + u32 reserved15[13]; + u32 reserved16[8]; + + /* Fields for HPU: */ + + u32 hpu_state; + u32 reserved21[7]; + u32 cpu2hpu_mb_offset; + u32 cpu2hpu_irq; + u32 cpu2hpu_irq_ack; + u32 reserved22[13]; + + u32 apu2hpu_mb_offset; + u32 apu2hpu_irq; + u32 apu2hpu_irq_ack; + u32 reserved23[13]; + + u32 ppu2hpu_mb_offset; + u32 ppu2hpu_irq; + u32 ppu2hpu_irq_ack; + u32 reserved24[13]; + + u32 epu2hpu_mb_offset; + u32 epu2hpu_irq; + u32 epu2hpu_irq_ack; + u32 reserved25[13]; + u32 reserved26[8]; + + /* Fields for PPU: */ + + u32 ppu_state; + u32 reserved31[7]; + u32 cpu2ppu_mb_offset; + u32 cpu2ppu_irq; + u32 cpu2ppu_irq_ack; + u32 reserved32[13]; + + u32 apu2ppu_mb_offset; + u32 apu2ppu_irq; + u32 apu2ppu_irq_ack; + u32 reserved33[13]; + + u32 hpu2ppu_mb_offset; + u32 hpu2ppu_irq; + u32 hpu2ppu_irq_ack; + u32 reserved34[13]; + + u32 epu2ppu_mb_offset; + u32 epu2ppu_irq; + u32 epu2ppu_irq_ack; + u32 reserved35[13]; + u32 reserved36[8]; + + /* Fields for EPU: */ + + u32 epu_state; + u32 reserved41[7]; + u32 cpu2epu_mb_offset; + u32 cpu2epu_irq; + u32 cpu2epu_irq_ack; + u32 reserved42[13]; + + u32 apu2epu_mb_offset; + u32 apu2epu_irq; + u32 apu2epu_irq_ack; + u32 reserved43[13]; + + u32 hpu2epu_mb_offset; + u32 hpu2epu_irq; + u32 hpu2epu_irq_ack; + u32 reserved44[13]; + + u32 ppu2epu_mb_offset; + u32 ppu2epu_irq; + u32 ppu2epu_irq_ack; + u32 reserved45[13]; + u32 reserved46[8]; + + u32 semaphores[8]; /* Semaphores */ + + u32 reserved50[32]; /* Reserved for future use */ + + struct cx18_mailbox apu2cpu_mb; + struct cx18_mailbox hpu2cpu_mb; + struct cx18_mailbox ppu2cpu_mb; + struct cx18_mailbox epu2cpu_mb; + + struct cx18_mailbox cpu2apu_mb; + struct cx18_mailbox hpu2apu_mb; + struct cx18_mailbox ppu2apu_mb; + struct cx18_mailbox epu2apu_mb; + + struct cx18_mailbox cpu2hpu_mb; + struct cx18_mailbox apu2hpu_mb; + struct cx18_mailbox ppu2hpu_mb; + struct cx18_mailbox epu2hpu_mb; + + struct cx18_mailbox cpu2ppu_mb; + struct cx18_mailbox apu2ppu_mb; + struct cx18_mailbox hpu2ppu_mb; + struct cx18_mailbox epu2ppu_mb; + + struct cx18_mailbox cpu2epu_mb; + struct cx18_mailbox apu2epu_mb; + struct cx18_mailbox hpu2epu_mb; + struct cx18_mailbox ppu2epu_mb; + + struct cx18_mdl_ack cpu_mdl_ack[CX18_MAX_STREAMS][2]; + struct cx18_mdl cpu_mdl[1]; +}; + +void cx18_init_scb(struct cx18 *cx); + +#endif diff --git a/drivers/media/video/cx18/cx18-streams.c b/drivers/media/video/cx18/cx18-streams.c new file mode 100644 index 000000000000..afb141b2027a --- /dev/null +++ b/drivers/media/video/cx18/cx18-streams.c @@ -0,0 +1,566 @@ +/* + * cx18 init/start/stop/exit stream functions + * + * Derived from ivtv-streams.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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-mailbox.h" +#include "cx18-i2c.h" +#include "cx18-queue.h" +#include "cx18-ioctl.h" +#include "cx18-streams.h" +#include "cx18-cards.h" +#include "cx18-scb.h" +#include "cx18-av-core.h" +#include "cx18-dvb.h" + +#define CX18_DSP0_INTERRUPT_MASK 0xd0004C + +static struct file_operations cx18_v4l2_enc_fops = { + .owner = THIS_MODULE, + .read = cx18_v4l2_read, + .open = cx18_v4l2_open, + .ioctl = cx18_v4l2_ioctl, + .release = cx18_v4l2_close, + .poll = cx18_v4l2_enc_poll, +}; + +/* offset from 0 to register ts v4l2 minors on */ +#define CX18_V4L2_ENC_TS_OFFSET 16 +/* offset from 0 to register pcm v4l2 minors on */ +#define CX18_V4L2_ENC_PCM_OFFSET 24 +/* offset from 0 to register yuv v4l2 minors on */ +#define CX18_V4L2_ENC_YUV_OFFSET 32 + +static struct { + const char *name; + int vfl_type; + int minor_offset; + int dma; + enum v4l2_buf_type buf_type; + struct file_operations *fops; +} cx18_stream_info[] = { + { /* CX18_ENC_STREAM_TYPE_MPG */ + "encoder MPEG", + VFL_TYPE_GRABBER, 0, + PCI_DMA_FROMDEVICE, V4L2_BUF_TYPE_VIDEO_CAPTURE, + &cx18_v4l2_enc_fops + }, + { /* CX18_ENC_STREAM_TYPE_TS */ + "TS", + VFL_TYPE_GRABBER, -1, + PCI_DMA_FROMDEVICE, V4L2_BUF_TYPE_VIDEO_CAPTURE, + &cx18_v4l2_enc_fops + }, + { /* CX18_ENC_STREAM_TYPE_YUV */ + "encoder YUV", + VFL_TYPE_GRABBER, CX18_V4L2_ENC_YUV_OFFSET, + PCI_DMA_FROMDEVICE, V4L2_BUF_TYPE_VIDEO_CAPTURE, + &cx18_v4l2_enc_fops + }, + { /* CX18_ENC_STREAM_TYPE_VBI */ + "encoder VBI", + VFL_TYPE_VBI, 0, + PCI_DMA_FROMDEVICE, V4L2_BUF_TYPE_VBI_CAPTURE, + &cx18_v4l2_enc_fops + }, + { /* CX18_ENC_STREAM_TYPE_PCM */ + "encoder PCM audio", + VFL_TYPE_GRABBER, CX18_V4L2_ENC_PCM_OFFSET, + PCI_DMA_FROMDEVICE, V4L2_BUF_TYPE_PRIVATE, + &cx18_v4l2_enc_fops + }, + { /* CX18_ENC_STREAM_TYPE_IDX */ + "encoder IDX", + VFL_TYPE_GRABBER, -1, + PCI_DMA_FROMDEVICE, V4L2_BUF_TYPE_VIDEO_CAPTURE, + &cx18_v4l2_enc_fops + }, + { /* CX18_ENC_STREAM_TYPE_RAD */ + "encoder radio", + VFL_TYPE_RADIO, 0, + PCI_DMA_NONE, V4L2_BUF_TYPE_PRIVATE, + &cx18_v4l2_enc_fops + }, +}; + +static void cx18_stream_init(struct cx18 *cx, int type) +{ + struct cx18_stream *s = &cx->streams[type]; + struct video_device *dev = s->v4l2dev; + u32 max_size = cx->options.megabytes[type] * 1024 * 1024; + + /* we need to keep v4l2dev, so restore it afterwards */ + memset(s, 0, sizeof(*s)); + s->v4l2dev = dev; + + /* initialize cx18_stream fields */ + s->cx = cx; + s->type = type; + s->name = cx18_stream_info[type].name; + s->handle = 0xffffffff; + + s->dma = cx18_stream_info[type].dma; + s->buf_size = cx->stream_buf_size[type]; + if (s->buf_size) + s->buffers = max_size / s->buf_size; + if (s->buffers > 63) { + /* Each stream has a maximum of 63 buffers, + ensure we do not exceed that. */ + s->buffers = 63; + s->buf_size = (max_size / s->buffers) & ~0xfff; + } + spin_lock_init(&s->qlock); + init_waitqueue_head(&s->waitq); + s->id = -1; + cx18_queue_init(&s->q_free); + cx18_queue_init(&s->q_full); + cx18_queue_init(&s->q_io); +} + +static int cx18_prep_dev(struct cx18 *cx, int type) +{ + struct cx18_stream *s = &cx->streams[type]; + u32 cap = cx->v4l2_cap; + int minor_offset = cx18_stream_info[type].minor_offset; + int minor; + + /* These four fields are always initialized. If v4l2dev == NULL, then + this stream is not in use. In that case no other fields but these + four can be used. */ + s->v4l2dev = NULL; + s->cx = cx; + s->type = type; + s->name = cx18_stream_info[type].name; + + /* Check whether the radio is supported */ + if (type == CX18_ENC_STREAM_TYPE_RAD && !(cap & V4L2_CAP_RADIO)) + return 0; + + /* Check whether VBI is supported */ + if (type == CX18_ENC_STREAM_TYPE_VBI && + !(cap & (V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE))) + return 0; + + /* card number + user defined offset + device offset */ + minor = cx->num + cx18_first_minor + minor_offset; + + /* User explicitly selected 0 buffers for these streams, so don't + create them. */ + if (cx18_stream_info[type].dma != PCI_DMA_NONE && + cx->options.megabytes[type] == 0) { + CX18_INFO("Disabled %s device\n", cx18_stream_info[type].name); + return 0; + } + + cx18_stream_init(cx, type); + + if (minor_offset == -1) + return 0; + + /* allocate and initialize the v4l2 video device structure */ + s->v4l2dev = video_device_alloc(); + if (s->v4l2dev == NULL) { + CX18_ERR("Couldn't allocate v4l2 video_device for %s\n", + s->name); + return -ENOMEM; + } + + s->v4l2dev->type = + VID_TYPE_CAPTURE | VID_TYPE_TUNER | VID_TYPE_TELETEXT | + VID_TYPE_CLIPPING | VID_TYPE_SCALES | VID_TYPE_MPEG_ENCODER; + snprintf(s->v4l2dev->name, sizeof(s->v4l2dev->name), "cx18%d %s", + cx->num, s->name); + + s->v4l2dev->minor = minor; + s->v4l2dev->dev = &cx->dev->dev; + s->v4l2dev->fops = cx18_stream_info[type].fops; + s->v4l2dev->release = video_device_release; + + return 0; +} + +/* Initialize v4l2 variables and register v4l2 devices */ +int cx18_streams_setup(struct cx18 *cx) +{ + int type; + + /* Setup V4L2 Devices */ + for (type = 0; type < CX18_MAX_STREAMS; type++) { + /* Prepare device */ + if (cx18_prep_dev(cx, type)) + break; + + /* Allocate Stream */ + if (cx18_stream_alloc(&cx->streams[type])) + break; + } + if (type == CX18_MAX_STREAMS) + return 0; + + /* One or more streams could not be initialized. Clean 'em all up. */ + cx18_streams_cleanup(cx); + return -ENOMEM; +} + +static int cx18_reg_dev(struct cx18 *cx, int type) +{ + struct cx18_stream *s = &cx->streams[type]; + int vfl_type = cx18_stream_info[type].vfl_type; + int minor; + + /* TODO: Shouldn't this be a VFL_TYPE_TRANSPORT or something? + * We need a VFL_TYPE_TS defined. + */ + if (strcmp("TS", s->name) == 0) { + /* just return if no DVB is supported */ + if ((cx->card->hw_all & CX18_HW_DVB) == 0) + return 0; + if (cx18_dvb_register(s) < 0) { + CX18_ERR("DVB failed to register\n"); + return -EINVAL; + } + } + + if (s->v4l2dev == NULL) + return 0; + + minor = s->v4l2dev->minor; + + /* Register device. First try the desired minor, then any free one. */ + if (video_register_device(s->v4l2dev, vfl_type, minor) && + video_register_device(s->v4l2dev, vfl_type, -1)) { + CX18_ERR("Couldn't register v4l2 device for %s minor %d\n", + s->name, minor); + video_device_release(s->v4l2dev); + s->v4l2dev = NULL; + return -ENOMEM; + } + minor = s->v4l2dev->minor; + + switch (vfl_type) { + case VFL_TYPE_GRABBER: + CX18_INFO("Registered device video%d for %s (%d MB)\n", + minor, s->name, cx->options.megabytes[type]); + break; + + case VFL_TYPE_RADIO: + CX18_INFO("Registered device radio%d for %s\n", + minor - MINOR_VFL_TYPE_RADIO_MIN, s->name); + break; + + case VFL_TYPE_VBI: + if (cx->options.megabytes[type]) + CX18_INFO("Registered device vbi%d for %s (%d MB)\n", + minor - MINOR_VFL_TYPE_VBI_MIN, + s->name, cx->options.megabytes[type]); + else + CX18_INFO("Registered device vbi%d for %s\n", + minor - MINOR_VFL_TYPE_VBI_MIN, s->name); + break; + } + + return 0; +} + +/* Register v4l2 devices */ +int cx18_streams_register(struct cx18 *cx) +{ + int type; + int err = 0; + + /* Register V4L2 devices */ + for (type = 0; type < CX18_MAX_STREAMS; type++) + err |= cx18_reg_dev(cx, type); + + if (err == 0) + return 0; + + /* One or more streams could not be initialized. Clean 'em all up. */ + cx18_streams_cleanup(cx); + return -ENOMEM; +} + +/* Unregister v4l2 devices */ +void cx18_streams_cleanup(struct cx18 *cx) +{ + struct video_device *vdev; + int type; + + /* Teardown all streams */ + for (type = 0; type < CX18_MAX_STREAMS; type++) { + if (cx->streams[type].dvb.enabled) + cx18_dvb_unregister(&cx->streams[type]); + + vdev = cx->streams[type].v4l2dev; + + cx->streams[type].v4l2dev = NULL; + if (vdev == NULL) + continue; + + cx18_stream_free(&cx->streams[type]); + + /* Unregister device */ + video_unregister_device(vdev); + } +} + +static void cx18_vbi_setup(struct cx18_stream *s) +{ + struct cx18 *cx = s->cx; + int raw = cx->vbi.sliced_in->service_set == 0; + u32 data[CX2341X_MBOX_MAX_DATA]; + int lines; + + if (cx->is_60hz) { + cx->vbi.count = 12; + cx->vbi.start[0] = 10; + cx->vbi.start[1] = 273; + } else { /* PAL/SECAM */ + cx->vbi.count = 18; + cx->vbi.start[0] = 6; + cx->vbi.start[1] = 318; + } + + /* setup VBI registers */ + cx18_av_cmd(cx, VIDIOC_S_FMT, &cx->vbi.in); + + /* determine number of lines and total number of VBI bytes. + A raw line takes 1443 bytes: 2 * 720 + 4 byte frame header - 1 + The '- 1' byte is probably an unused U or V byte. Or something... + A sliced line takes 51 bytes: 4 byte frame header, 4 byte internal + header, 42 data bytes + checksum (to be confirmed) */ + if (raw) { + lines = cx->vbi.count * 2; + } else { + lines = cx->is_60hz ? 24 : 38; + if (cx->is_60hz) + lines += 2; + } + + cx->vbi.enc_size = lines * + (raw ? cx->vbi.raw_size : cx->vbi.sliced_size); + + data[0] = s->handle; + /* Lines per field */ + data[1] = (lines / 2) | ((lines / 2) << 16); + /* bytes per line */ + data[2] = (raw ? cx->vbi.raw_size : cx->vbi.sliced_size); + /* Every X number of frames a VBI interrupt arrives + (frames as in 25 or 30 fps) */ + data[3] = 1; + /* Setup VBI for the cx25840 digitizer */ + if (raw) { + data[4] = 0x20602060; + data[5] = 0x30703070; + } else { + data[4] = 0xB0F0B0F0; + data[5] = 0xA0E0A0E0; + } + + CX18_DEBUG_INFO("Setup VBI h: %d lines %x bpl %d fr %d %x %x\n", + data[0], data[1], data[2], data[3], data[4], data[5]); + + if (s->type == CX18_ENC_STREAM_TYPE_VBI) + cx18_api(cx, CX18_CPU_SET_RAW_VBI_PARAM, 6, data); +} + +int cx18_start_v4l2_encode_stream(struct cx18_stream *s) +{ + u32 data[MAX_MB_ARGUMENTS]; + struct cx18 *cx = s->cx; + struct list_head *p; + int ts = 0; + int captype = 0; + + if (s->v4l2dev == NULL && s->dvb.enabled == 0) + return -EINVAL; + + CX18_DEBUG_INFO("Start encoder stream %s\n", s->name); + + switch (s->type) { + case CX18_ENC_STREAM_TYPE_MPG: + captype = CAPTURE_CHANNEL_TYPE_MPEG; + cx->mpg_data_received = cx->vbi_data_inserted = 0; + cx->dualwatch_jiffies = jiffies; + cx->dualwatch_stereo_mode = cx->params.audio_properties & 0x300; + cx->search_pack_header = 0; + break; + + case CX18_ENC_STREAM_TYPE_TS: + captype = CAPTURE_CHANNEL_TYPE_TS; + ts = 1; + break; + case CX18_ENC_STREAM_TYPE_YUV: + captype = CAPTURE_CHANNEL_TYPE_YUV; + break; + case CX18_ENC_STREAM_TYPE_PCM: + captype = CAPTURE_CHANNEL_TYPE_PCM; + break; + case CX18_ENC_STREAM_TYPE_VBI: + captype = cx->vbi.sliced_in->service_set ? + CAPTURE_CHANNEL_TYPE_SLICED_VBI : CAPTURE_CHANNEL_TYPE_VBI; + cx->vbi.frame = 0; + cx->vbi.inserted_frame = 0; + memset(cx->vbi.sliced_mpeg_size, + 0, sizeof(cx->vbi.sliced_mpeg_size)); + break; + default: + return -EINVAL; + } + s->buffers_stolen = 0; + + /* mute/unmute video */ + cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, + s->handle, !!test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)); + + /* Clear Streamoff flags in case left from last capture */ + clear_bit(CX18_F_S_STREAMOFF, &s->s_flags); + + cx18_vapi_result(cx, data, CX18_CREATE_TASK, 1, CPU_CMD_MASK_CAPTURE); + s->handle = data[0]; + cx18_vapi(cx, CX18_CPU_SET_CHANNEL_TYPE, 2, s->handle, captype); + + if (atomic_read(&cx->capturing) == 0 && !ts) { + /* Stuff from Windows, we don't know what it is */ + cx18_vapi(cx, CX18_CPU_SET_VER_CROP_LINE, 2, s->handle, 0); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 3, s->handle, 3, 1); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 3, s->handle, 8, 0); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 3, s->handle, 4, 1); + cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 2, s->handle, 12); + + cx18_vapi(cx, CX18_CPU_SET_CAPTURE_LINE_NO, 3, + s->handle, cx->digitizer, cx->digitizer); + + /* Setup VBI */ + if (cx->v4l2_cap & V4L2_CAP_VBI_CAPTURE) + cx18_vbi_setup(s); + + /* assign program index info. + Mask 7: select I/P/B, Num_req: 400 max */ + cx18_vapi_result(cx, data, CX18_CPU_SET_INDEXTABLE, 1, 0); + + /* Setup API for Stream */ + cx2341x_update(cx, cx18_api_func, NULL, &cx->params); + } + + if (atomic_read(&cx->capturing) == 0) { + clear_bit(CX18_F_I_EOS, &cx->i_flags); + write_reg(7, CX18_DSP0_INTERRUPT_MASK); + } + + cx18_vapi(cx, CX18_CPU_DE_SET_MDL_ACK, 3, s->handle, + (void *)&cx->scb->cpu_mdl_ack[s->type][0] - cx->enc_mem, + (void *)&cx->scb->cpu_mdl_ack[s->type][1] - cx->enc_mem); + + list_for_each(p, &s->q_free.list) { + struct cx18_buffer *buf = list_entry(p, struct cx18_buffer, list); + + writel(buf->dma_handle, &cx->scb->cpu_mdl[buf->id].paddr); + writel(s->buf_size, &cx->scb->cpu_mdl[buf->id].length); + 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); + } + /* begin_capture */ + if (cx18_vapi(cx, CX18_CPU_CAPTURE_START, 1, s->handle)) { + CX18_DEBUG_WARN("Error starting capture!\n"); + cx18_vapi(cx, CX18_DESTROY_TASK, 1, s->handle); + return -EINVAL; + } + + /* you're live! sit back and await interrupts :) */ + atomic_inc(&cx->capturing); + return 0; +} + +void cx18_stop_all_captures(struct cx18 *cx) +{ + int i; + + for (i = CX18_MAX_STREAMS - 1; i >= 0; i--) { + struct cx18_stream *s = &cx->streams[i]; + + if (s->v4l2dev == NULL && s->dvb.enabled == 0) + continue; + if (test_bit(CX18_F_S_STREAMING, &s->s_flags)) + cx18_stop_v4l2_encode_stream(s, 0); + } +} + +int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end) +{ + struct cx18 *cx = s->cx; + unsigned long then; + + if (s->v4l2dev == NULL && s->dvb.enabled == 0) + return -EINVAL; + + /* This function assumes that you are allowed to stop the capture + and that we are actually capturing */ + + CX18_DEBUG_INFO("Stop Capture\n"); + + if (atomic_read(&cx->capturing) == 0) + return 0; + + if (s->type == CX18_ENC_STREAM_TYPE_MPG) + cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 2, s->handle, !gop_end); + else + cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 1, s->handle); + + then = jiffies; + + if (s->type == CX18_ENC_STREAM_TYPE_MPG && gop_end) { + CX18_INFO("ignoring gop_end: not (yet?) supported by the firmware\n"); + } + + atomic_dec(&cx->capturing); + + /* Clear capture and no-read bits */ + clear_bit(CX18_F_S_STREAMING, &s->s_flags); + + cx18_vapi(cx, CX18_DESTROY_TASK, 1, s->handle); + s->handle = 0xffffffff; + + if (atomic_read(&cx->capturing) > 0) + return 0; + + write_reg(5, CX18_DSP0_INTERRUPT_MASK); + wake_up(&s->waitq); + + return 0; +} + +u32 cx18_find_handle(struct cx18 *cx) +{ + int i; + + /* find first available handle to be used for global settings */ + for (i = 0; i < CX18_MAX_STREAMS; i++) { + struct cx18_stream *s = &cx->streams[i]; + + if (s->v4l2dev && s->handle) + return s->handle; + } + return 0; +} diff --git a/drivers/media/video/cx18/cx18-streams.h b/drivers/media/video/cx18/cx18-streams.h new file mode 100644 index 000000000000..8c7ba7d2fa79 --- /dev/null +++ b/drivers/media/video/cx18/cx18-streams.h @@ -0,0 +1,33 @@ +/* + * cx18 init/start/stop/exit stream functions + * + * Derived from ivtv-streams.h + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 + */ + +u32 cx18_find_handle(struct cx18 *cx); +int cx18_streams_setup(struct cx18 *cx); +int cx18_streams_register(struct cx18 *cx); +void cx18_streams_cleanup(struct cx18 *cx); + +/* Capture related */ +int cx18_start_v4l2_encode_stream(struct cx18_stream *s); +int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end); + +void cx18_stop_all_captures(struct cx18 *cx); diff --git a/drivers/media/video/cx18/cx18-vbi.c b/drivers/media/video/cx18/cx18-vbi.c new file mode 100644 index 000000000000..4bece9c02f7d --- /dev/null +++ b/drivers/media/video/cx18/cx18-vbi.c @@ -0,0 +1,208 @@ +/* + * cx18 Vertical Blank Interval support functions + * + * Derived from ivtv-vbi.c + * + * Copyright (C) 2007 Hans Verkuil + * + * 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-vbi.h" +#include "cx18-ioctl.h" +#include "cx18-queue.h" +#include "cx18-av-core.h" + +static void copy_vbi_data(struct cx18 *cx, int lines, u32 pts_stamp) +{ + int line = 0; + int i; + u32 linemask[2] = { 0, 0 }; + unsigned short size; + static const u8 mpeg_hdr_data[] = { + 0x00, 0x00, 0x01, 0xba, 0x44, 0x00, 0x0c, 0x66, + 0x24, 0x01, 0x01, 0xd1, 0xd3, 0xfa, 0xff, 0xff, + 0x00, 0x00, 0x01, 0xbd, 0x00, 0x1a, 0x84, 0x80, + 0x07, 0x21, 0x00, 0x5d, 0x63, 0xa7, 0xff, 0xff + }; + const int sd = sizeof(mpeg_hdr_data); /* start of vbi data */ + int idx = cx->vbi.frame % CX18_VBI_FRAMES; + u8 *dst = &cx->vbi.sliced_mpeg_data[idx][0]; + + for (i = 0; i < lines; i++) { + struct v4l2_sliced_vbi_data *sdata = cx->vbi.sliced_data + i; + int f, l; + + if (sdata->id == 0) + continue; + + l = sdata->line - 6; + f = sdata->field; + if (f) + l += 18; + if (l < 32) + linemask[0] |= (1 << l); + else + linemask[1] |= (1 << (l - 32)); + dst[sd + 12 + line * 43] = service2vbi(sdata->id); + memcpy(dst + sd + 12 + line * 43 + 1, sdata->data, 42); + line++; + } + memcpy(dst, mpeg_hdr_data, sizeof(mpeg_hdr_data)); + if (line == 36) { + /* All lines are used, so there is no space for the linemask + (the max size of the VBI data is 36 * 43 + 4 bytes). + So in this case we use the magic number 'ITV0'. */ + memcpy(dst + sd, "ITV0", 4); + memcpy(dst + sd + 4, dst + sd + 12, line * 43); + size = 4 + ((43 * line + 3) & ~3); + } else { + memcpy(dst + sd, "cx0", 4); + memcpy(dst + sd + 4, &linemask[0], 8); + size = 12 + ((43 * line + 3) & ~3); + } + dst[4+16] = (size + 10) >> 8; + dst[5+16] = (size + 10) & 0xff; + dst[9+16] = 0x21 | ((pts_stamp >> 29) & 0x6); + dst[10+16] = (pts_stamp >> 22) & 0xff; + dst[11+16] = 1 | ((pts_stamp >> 14) & 0xff); + dst[12+16] = (pts_stamp >> 7) & 0xff; + dst[13+16] = 1 | ((pts_stamp & 0x7f) << 1); + cx->vbi.sliced_mpeg_size[idx] = sd + size; +} + +/* Compress raw VBI format, removes leading SAV codes and surplus space + after the field. + Returns new compressed size. */ +static u32 compress_raw_buf(struct cx18 *cx, u8 *buf, u32 size) +{ + u32 line_size = cx->vbi.raw_decoder_line_size; + u32 lines = cx->vbi.count; + u8 sav1 = cx->vbi.raw_decoder_sav_odd_field; + u8 sav2 = cx->vbi.raw_decoder_sav_even_field; + u8 *q = buf; + u8 *p; + int i; + + for (i = 0; i < lines; i++) { + p = buf + i * line_size; + + /* Look for SAV code */ + if (p[0] != 0xff || p[1] || p[2] || + (p[3] != sav1 && p[3] != sav2)) + break; + memcpy(q, p + 4, line_size - 4); + q += line_size - 4; + } + return lines * (line_size - 4); +} + + +/* Compressed VBI format, all found sliced blocks put next to one another + Returns new compressed size */ +static u32 compress_sliced_buf(struct cx18 *cx, u32 line, u8 *buf, + u32 size, u8 sav) +{ + u32 line_size = cx->vbi.sliced_decoder_line_size; + struct v4l2_decode_vbi_line vbi; + int i; + + /* find the first valid line */ + for (i = 0; i < size; i++, buf++) { + if (buf[0] == 0xff && !buf[1] && !buf[2] && buf[3] == sav) + break; + } + + size -= i; + if (size < line_size) + return line; + for (i = 0; i < size / line_size; i++) { + u8 *p = buf + i * line_size; + + /* Look for SAV code */ + if (p[0] != 0xff || p[1] || p[2] || p[3] != sav) + continue; + vbi.p = p + 4; + cx18_av_cmd(cx, VIDIOC_INT_DECODE_VBI_LINE, &vbi); + if (vbi.type) { + cx->vbi.sliced_data[line].id = vbi.type; + cx->vbi.sliced_data[line].field = vbi.is_second_field; + cx->vbi.sliced_data[line].line = vbi.line; + memcpy(cx->vbi.sliced_data[line].data, vbi.p, 42); + line++; + } + } + return line; +} + +void cx18_process_vbi_data(struct cx18 *cx, struct cx18_buffer *buf, + u64 pts_stamp, int streamtype) +{ + u8 *p = (u8 *) buf->buf; + u32 size = buf->bytesused; + int lines; + + if (streamtype != CX18_ENC_STREAM_TYPE_VBI) + return; + + /* Raw VBI data */ + if (cx->vbi.sliced_in->service_set == 0) { + u8 type; + + cx18_buf_swap(buf); + + type = p[3]; + + size = buf->bytesused = compress_raw_buf(cx, p, size); + + /* second field of the frame? */ + if (type == cx->vbi.raw_decoder_sav_even_field) { + /* Dirty hack needed for backwards + compatibility of old VBI software. */ + p += size - 4; + memcpy(p, &cx->vbi.frame, 4); + cx->vbi.frame++; + } + return; + } + + /* Sliced VBI data with data insertion */ + cx18_buf_swap(buf); + + /* first field */ + lines = compress_sliced_buf(cx, 0, p, size / 2, + cx->vbi.sliced_decoder_sav_odd_field); + /* second field */ + /* experimentation shows that the second half does not always + begin at the exact address. So start a bit earlier + (hence 32). */ + lines = compress_sliced_buf(cx, lines, p + size / 2 - 32, + size / 2 + 32, cx->vbi.sliced_decoder_sav_even_field); + /* always return at least one empty line */ + if (lines == 0) { + cx->vbi.sliced_data[0].id = 0; + cx->vbi.sliced_data[0].line = 0; + cx->vbi.sliced_data[0].field = 0; + lines = 1; + } + buf->bytesused = size = lines * sizeof(cx->vbi.sliced_data[0]); + memcpy(p, &cx->vbi.sliced_data[0], size); + + if (cx->vbi.insert_mpeg) + copy_vbi_data(cx, lines, pts_stamp); + cx->vbi.frame++; +} diff --git a/drivers/media/video/cx18/cx18-vbi.h b/drivers/media/video/cx18/cx18-vbi.h new file mode 100644 index 000000000000..c56ff7d28f20 --- /dev/null +++ b/drivers/media/video/cx18/cx18-vbi.h @@ -0,0 +1,26 @@ +/* + * cx18 Vertical Blank Interval support functions + * + * Derived from ivtv-vbi.h + * + * Copyright (C) 2007 Hans Verkuil + * + * 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_process_vbi_data(struct cx18 *cx, struct cx18_buffer *buf, + u64 pts_stamp, int streamtype); +int cx18_used_line(struct cx18 *cx, int line, int field); diff --git a/drivers/media/video/cx18/cx18-version.h b/drivers/media/video/cx18/cx18-version.h new file mode 100644 index 000000000000..d5c7a6f968dd --- /dev/null +++ b/drivers/media/video/cx18/cx18-version.h @@ -0,0 +1,34 @@ +/* + * cx18 driver version information + * + * Copyright (C) 2007 Hans Verkuil + * + * 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_VERSION_H +#define CX18_VERSION_H + +#define CX18_DRIVER_NAME "cx18" +#define CX18_DRIVER_VERSION_MAJOR 1 +#define CX18_DRIVER_VERSION_MINOR 0 +#define CX18_DRIVER_VERSION_PATCHLEVEL 0 + +#define CX18_VERSION __stringify(CX18_DRIVER_VERSION_MAJOR) "." __stringify(CX18_DRIVER_VERSION_MINOR) "." __stringify(CX18_DRIVER_VERSION_PATCHLEVEL) +#define CX18_DRIVER_VERSION KERNEL_VERSION(CX18_DRIVER_VERSION_MAJOR, \ + CX18_DRIVER_VERSION_MINOR, CX18_DRIVER_VERSION_PATCHLEVEL) + +#endif diff --git a/drivers/media/video/cx18/cx18-video.c b/drivers/media/video/cx18/cx18-video.c new file mode 100644 index 000000000000..2e5c41939330 --- /dev/null +++ b/drivers/media/video/cx18/cx18-video.c @@ -0,0 +1,45 @@ +/* + * cx18 video interface functions + * + * Copyright (C) 2007 Hans Verkuil + * + * 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-video.h" +#include "cx18-av-core.h" +#include "cx18-cards.h" + +void cx18_video_set_io(struct cx18 *cx) +{ + struct v4l2_routing route; + int inp = cx->active_input; + u32 type; + + route.input = cx->card->video_inputs[inp].video_input; + route.output = 0; + cx18_av_cmd(cx, VIDIOC_INT_S_VIDEO_ROUTING, &route); + + type = cx->card->video_inputs[inp].video_type; + + if (type == CX18_CARD_INPUT_VID_TUNER) + route.input = 0; /* Tuner */ + else if (type < CX18_CARD_INPUT_COMPOSITE1) + route.input = 2; /* S-Video */ + else + route.input = 1; /* Composite */ +} diff --git a/drivers/media/video/cx18/cx18-video.h b/drivers/media/video/cx18/cx18-video.h new file mode 100644 index 000000000000..529006a06e5c --- /dev/null +++ b/drivers/media/video/cx18/cx18-video.h @@ -0,0 +1,22 @@ +/* + * cx18 video interface functions + * + * Copyright (C) 2007 Hans Verkuil + * + * 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_video_set_io(struct cx18 *cx); diff --git a/drivers/media/video/cx18/cx23418.h b/drivers/media/video/cx18/cx23418.h new file mode 100644 index 000000000000..33f78da9dba8 --- /dev/null +++ b/drivers/media/video/cx18/cx23418.h @@ -0,0 +1,458 @@ +/* + * cx18 header containing common defines. + * + * Copyright (C) 2007 Hans Verkuil + * + * 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 CX23418_H +#define CX23418_H + +#include + +#define MGR_CMD_MASK 0x40000000 +/* The MSB of the command code indicates that this is the completion of a + command */ +#define MGR_CMD_MASK_ACK (MGR_CMD_MASK | 0x80000000) + +/* Description: This command creates a new instance of a certain task + IN[0] - Task ID. This is one of the XPU_CMD_MASK_YYY where XPU is + the processor on which the task YYY will be created + OUT[0] - Task handle. This handle is passed along with commands to + dispatch to the right instance of the task + ReturnCode - One of the ERR_SYS_... */ +#define CX18_CREATE_TASK (MGR_CMD_MASK | 0x0001) + +/* Description: This command destroys an instance of a task + IN[0] - Task handle. Hanlde of the task to destroy + ReturnCode - One of the ERR_SYS_... */ +#define CX18_DESTROY_TASK (MGR_CMD_MASK | 0x0002) + +/* All commands for CPU have the following mask set */ +#define CPU_CMD_MASK 0x20000000 +#define CPU_CMD_MASK_ACK (CPU_CMD_MASK | 0x80000000) +#define CPU_CMD_MASK_CAPTURE (CPU_CMD_MASK | 0x00020000) +#define CPU_CMD_MASK_TS (CPU_CMD_MASK | 0x00040000) + +#define EPU_CMD_MASK 0x02000000 +#define EPU_CMD_MASK_DEBUG (EPU_CMD_MASK | 0x000000) +#define EPU_CMD_MASK_DE (EPU_CMD_MASK | 0x040000) + +/* Description: This command indicates that a Memory Descriptor List has been + filled with the requested channel type + IN[0] - Task handle. Handle of the task + IN[1] - Offset of the MDL_ACK from the beginning of the local DDR. + IN[2] - Number of CNXT_MDL_ACK structures in the array pointed to by IN[1] + ReturnCode - One of the ERR_DE_... */ +#define CX18_EPU_DMA_DONE (EPU_CMD_MASK_DE | 0x0001) + +/* Something interesting happened + IN[0] - A value to log + IN[1] - An offset of a string in the MiniMe memory; + 0/zero/NULL means "I have nothing to say" */ +#define CX18_EPU_DEBUG (EPU_CMD_MASK_DEBUG | 0x0003) + +/* Description: This command starts streaming with the set channel type + IN[0] - Task handle. Handle of the task to start + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_CAPTURE_START (CPU_CMD_MASK_CAPTURE | 0x0002) + +/* Description: This command stops streaming with the set channel type + IN[0] - Task handle. Handle of the task to stop + IN[1] - 0 = stop at end of GOP, 1 = stop at end of frame (MPEG only) + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_CAPTURE_STOP (CPU_CMD_MASK_CAPTURE | 0x0003) + +/* Description: This command pauses streaming with the set channel type + IN[0] - Task handle. Handle of the task to pause + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_CAPTURE_PAUSE (CPU_CMD_MASK_CAPTURE | 0x0007) + +/* Description: This command resumes streaming with the set channel type + IN[0] - Task handle. Handle of the task to resume + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_CAPTURE_RESUME (CPU_CMD_MASK_CAPTURE | 0x0008) + +#define CAPTURE_CHANNEL_TYPE_NONE 0 +#define CAPTURE_CHANNEL_TYPE_MPEG 1 +#define CAPTURE_CHANNEL_TYPE_INDEX 2 +#define CAPTURE_CHANNEL_TYPE_YUV 3 +#define CAPTURE_CHANNEL_TYPE_PCM 4 +#define CAPTURE_CHANNEL_TYPE_VBI 5 +#define CAPTURE_CHANNEL_TYPE_SLICED_VBI 6 +#define CAPTURE_CHANNEL_TYPE_TS 7 +#define CAPTURE_CHANNEL_TYPE_MAX 15 + +/* Description: This command sets the channel type. This can only be done + when stopped. + IN[0] - Task handle. Handle of the task to start + IN[1] - Channel Type. See Below. + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_CHANNEL_TYPE (CPU_CMD_MASK_CAPTURE + 1) + +/* Description: Set stream output type + IN[0] - task handle. Handle of the task to start + IN[1] - type + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_STREAM_OUTPUT_TYPE (CPU_CMD_MASK_CAPTURE | 0x0012) + +/* Description: Set video input resolution and frame rate + IN[0] - task handle + IN[1] - reserved + IN[2] - reserved + IN[3] - reserved + IN[4] - reserved + IN[5] - frame rate, 0 - 29.97f/s, 1 - 25f/s + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VIDEO_IN (CPU_CMD_MASK_CAPTURE | 0x0004) + +/* Description: Set video frame rate + IN[0] - task handle. Handle of the task to start + IN[1] - video bit rate mode + IN[2] - video average rate + IN[3] - video peak rate + IN[4] - system mux rate + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VIDEO_RATE (CPU_CMD_MASK_CAPTURE | 0x0005) + +/* Description: Set video output resolution + IN[0] - task handle + IN[1] - horizontal size + IN[2] - vertical size + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VIDEO_RESOLUTION (CPU_CMD_MASK_CAPTURE | 0x0006) + +/* Description: This command set filter parameters + IN[0] - Task handle. Handle of the task + IN[1] - type, 0 - temporal, 1 - spatial, 2 - median + IN[2] - mode, temporal/spatial: 0 - disable, 1 - static, 2 - dynamic + median: 0 = disable, 1 = horizontal, 2 = vertical, + 3 = horizontal/vertical, 4 = diagonal + IN[3] - strength, temporal 0 - 31, spatial 0 - 15 + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_FILTER_PARAM (CPU_CMD_MASK_CAPTURE | 0x0009) + +/* Description: This command set spatial filter type + IN[0] - Task handle. + IN[1] - luma type: 0 = disable, 1 = 1D horizontal only, 2 = 1D vertical only, + 3 = 2D H/V separable, 4 = 2D symmetric non-separable + IN[2] - chroma type: 0 - diable, 1 = 1D horizontal + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_SPATIAL_FILTER_TYPE (CPU_CMD_MASK_CAPTURE | 0x000C) + +/* Description: This command set coring levels for median filter + IN[0] - Task handle. + IN[1] - luma_high + IN[2] - luma_low + IN[3] - chroma_high + IN[4] - chroma_low + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_MEDIAN_CORING (CPU_CMD_MASK_CAPTURE | 0x000E) + +/* Description: This command set the picture type mask for index file + IN[0] - 0 = disable index file output + 1 = output I picture + 2 = P picture + 4 = B picture + other = illegal */ +#define CX18_CPU_SET_INDEXTABLE (CPU_CMD_MASK_CAPTURE | 0x0010) + +/* Description: Set audio parameters + IN[0] - task handle. Handle of the task to start + IN[1] - audio parameter + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_AUDIO_PARAMETERS (CPU_CMD_MASK_CAPTURE | 0x0011) + +/* Description: Set video mute + IN[0] - task handle. Handle of the task to start + IN[1] - bit31-24: muteYvalue + bit23-16: muteUvalue + bit15-8: muteVvalue + bit0: 1:mute, 0: unmute + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VIDEO_MUTE (CPU_CMD_MASK_CAPTURE | 0x0013) + +/* Description: Set audio mute + IN[0] - task handle. Handle of the task to start + IN[1] - mute/unmute + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_AUDIO_MUTE (CPU_CMD_MASK_CAPTURE | 0x0014) + +/* Description: Set stream output type + IN[0] - task handle. Handle of the task to start + IN[1] - subType + SET_INITIAL_SCR 1 + SET_QUALITY_MODE 2 + SET_VIM_PROTECT_MODE 3 + SET_PTS_CORRECTION 4 + SET_USB_FLUSH_MODE 5 + SET_MERAQPAR_ENABLE 6 + SET_NAV_PACK_INSERTION 7 + SET_SCENE_CHANGE_ENABLE 8 + IN[2] - parameter 1 + IN[3] - parameter 2 + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_MISC_PARAMETERS (CPU_CMD_MASK_CAPTURE | 0x0015) + +/* Description: Set raw VBI parameters + IN[0] - Task handle + IN[1] - No. of input lines per field: + bit[15:0]: field 1, + bit[31:16]: field 2 + IN[2] - No. of input bytes per line + IN[3] - No. of output frames per transfer + IN[4] - start code + IN[5] - stop code + ReturnCode */ +#define CX18_CPU_SET_RAW_VBI_PARAM (CPU_CMD_MASK_CAPTURE | 0x0016) + +/* Description: Set capture line No. + IN[0] - task handle. Handle of the task to start + IN[1] - height1 + IN[2] - height2 + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_CAPTURE_LINE_NO (CPU_CMD_MASK_CAPTURE | 0x0017) + +/* Description: Set copyright + IN[0] - task handle. Handle of the task to start + IN[1] - copyright + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_COPYRIGHT (CPU_CMD_MASK_CAPTURE | 0x0018) + +/* Description: Set audio PID + IN[0] - task handle. Handle of the task to start + IN[1] - PID + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_AUDIO_PID (CPU_CMD_MASK_CAPTURE | 0x0019) + +/* Description: Set video PID + IN[0] - task handle. Handle of the task to start + IN[1] - PID + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VIDEO_PID (CPU_CMD_MASK_CAPTURE | 0x001A) + +/* Description: Set Vertical Crop Line + IN[0] - task handle. Handle of the task to start + IN[1] - Line + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_VER_CROP_LINE (CPU_CMD_MASK_CAPTURE | 0x001B) + +/* Description: Set COP structure + IN[0] - task handle. Handle of the task to start + IN[1] - M + IN[2] - N + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_GOP_STRUCTURE (CPU_CMD_MASK_CAPTURE | 0x001C) + +/* Description: Set Scene Change Detection + IN[0] - task handle. Handle of the task to start + IN[1] - scene change + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_SCENE_CHANGE_DETECTION (CPU_CMD_MASK_CAPTURE | 0x001D) + +/* Description: Set Aspect Ratio + IN[0] - task handle. Handle of the task to start + IN[1] - AspectRatio + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_ASPECT_RATIO (CPU_CMD_MASK_CAPTURE | 0x001E) + +/* Description: Set Skip Input Frame + IN[0] - task handle. Handle of the task to start + IN[1] - skip input frames + ReturnCode - One of the ERR_CAPTURE_... */ +#define CX18_CPU_SET_SKIP_INPUT_FRAME (CPU_CMD_MASK_CAPTURE | 0x001F) + +/* Description: Set sliced VBI parameters - + Note This API will only apply to MPEG and Sliced VBI Channels + IN[0] - Task handle + IN[1] - output type, 0 - CC, 1 - Moji, 2 - Teletext + IN[2] - start / stop line + bit[15:0] start line number + bit[31:16] stop line number + IN[3] - number of output frames per interrupt + IN[4] - VBI insertion mode + bit 0: output user data, 1 - enable + bit 1: output private stream, 1 - enable + bit 2: mux option, 0 - in GOP, 1 - in picture + bit[7:0] private stream ID + IN[5] - insertion period while mux option is in picture + ReturnCode - VBI data offset */ +#define CX18_CPU_SET_SLICED_VBI_PARAM (CPU_CMD_MASK_CAPTURE | 0x0020) + +/* Description: Set the user data place holder + IN[0] - type of data (0 for user) + IN[1] - Stuffing period + IN[2] - ID data size in word (less than 10) + IN[3] - Pointer to ID buffer */ +#define CX18_CPU_SET_USERDATA_PLACE_HOLDER (CPU_CMD_MASK_CAPTURE | 0x0021) + + +/* Description: + In[0] Task Handle + return parameter: + Out[0] Reserved + Out[1] Video PTS bit[32:2] of last output video frame. + Out[2] Video PTS bit[ 1:0] of last output video frame. + Out[3] Hardware Video PTS counter bit[31:0], + these bits get incremented on every 90kHz clock tick. + Out[4] Hardware Video PTS counter bit32, + these bits get incremented on every 90kHz clock tick. + ReturnCode */ +#define CX18_CPU_GET_ENC_PTS (CPU_CMD_MASK_CAPTURE | 0x0022) + +/* Below is the list of commands related to the data exchange */ +#define CPU_CMD_MASK_DE (CPU_CMD_MASK | 0x040000) + +/* Description: This command provides the physical base address of the local + DDR as viewed by EPU + IN[0] - Physical offset where EPU has the local DDR mapped + ReturnCode - One of the ERR_DE_... */ +#define CPU_CMD_DE_SetBase (CPU_CMD_MASK_DE | 0x0001) + +/* Description: This command provides the offsets in the device memory where + the 2 cx18_mdl_ack blocks reside + IN[0] - Task handle. Handle of the task to start + IN[1] - Offset of the first cx18_mdl_ack from the beginning of the + local DDR. + IN[2] - Offset of the second cx18_mdl_ack from the beginning of the + local DDR. + ReturnCode - One of the ERR_DE_... */ +#define CX18_CPU_DE_SET_MDL_ACK (CPU_CMD_MASK_DE | 0x0002) + +/* Description: This command provides the offset to a Memory Descriptor List + IN[0] - Task handle. Handle of the task to start + IN[1] - Offset of the MDL from the beginning of the local DDR. + IN[2] - Number of cx18_mdl structures in the array pointed to by IN[1] + IN[3] - Buffer ID + IN[4] - Total buffer length + ReturnCode - One of the ERR_DE_... */ +#define CX18_CPU_DE_SET_MDL (CPU_CMD_MASK_DE | 0x0005) + +/* Description: This command requests return of all current Memory + Descriptor Lists to the driver + IN[0] - Task handle. Handle of the task to start + ReturnCode - One of the ERR_DE_... */ +/* #define CX18_CPU_DE_ReleaseMDL (CPU_CMD_MASK_DE | 0x0006) */ + +/* Description: This command signals the cpu that the dat buffer has been + consumed and ready for re-use. + IN[0] - Task handle. Handle of the task + IN[1] - Offset of the data block from the beginning of the local DDR. + IN[2] - Number of bytes in the data block + ReturnCode - One of the ERR_DE_... */ +/* #define CX18_CPU_DE_RELEASE_BUFFER (CPU_CMD_MASK_DE | 0x0007) */ + +/* No Error / Success */ +#define CNXT_OK 0x000000 + +/* Received unknown command */ +#define CXERR_UNK_CMD 0x000001 + +/* First parameter in the command is invalid */ +#define CXERR_INVALID_PARAM1 0x000002 + +/* Second parameter in the command is invalid */ +#define CXERR_INVALID_PARAM2 0x000003 + +/* Device interface is not open/found */ +#define CXERR_DEV_NOT_FOUND 0x000004 + +/* Requested function is not implemented/available */ +#define CXERR_NOTSUPPORTED 0x000005 + +/* Invalid pointer is provided */ +#define CXERR_BADPTR 0x000006 + +/* Unable to allocate memory */ +#define CXERR_NOMEM 0x000007 + +/* Object/Link not found */ +#define CXERR_LINK 0x000008 + +/* Device busy, command cannot be executed */ +#define CXERR_BUSY 0x000009 + +/* File/device/handle is not open. */ +#define CXERR_NOT_OPEN 0x00000A + +/* Value is out of range */ +#define CXERR_OUTOFRANGE 0x00000B + +/* Buffer overflow */ +#define CXERR_OVERFLOW 0x00000C + +/* Version mismatch */ +#define CXERR_BADVER 0x00000D + +/* Operation timed out */ +#define CXERR_TIMEOUT 0x00000E + +/* Operation aborted */ +#define CXERR_ABORT 0x00000F + +/* Specified I2C device not found for read/write */ +#define CXERR_I2CDEV_NOTFOUND 0x000010 + +/* Error in I2C data xfer (but I2C device is present) */ +#define CXERR_I2CDEV_XFERERR 0x000011 + +/* Chanel changing component not ready */ +#define CXERR_CHANNELNOTREADY 0x000012 + +/* PPU (Presensation/Decoder) mail box is corrupted */ +#define CXERR_PPU_MB_CORRUPT 0x000013 + +/* CPU (Capture/Encoder) mail box is corrupted */ +#define CXERR_CPU_MB_CORRUPT 0x000014 + +/* APU (Audio) mail box is corrupted */ +#define CXERR_APU_MB_CORRUPT 0x000015 + +/* Unable to open file for reading */ +#define CXERR_FILE_OPEN_READ 0x000016 + +/* Unable to open file for writing */ +#define CXERR_FILE_OPEN_WRITE 0x000017 + +/* Unable to find the I2C section specified */ +#define CXERR_I2C_BADSECTION 0x000018 + +/* Error in I2C data xfer (but I2C device is present) */ +#define CXERR_I2CDEV_DATALOW 0x000019 + +/* Error in I2C data xfer (but I2C device is present) */ +#define CXERR_I2CDEV_CLOCKLOW 0x00001A + +/* No Interrupt received from HW (for I2C access) */ +#define CXERR_NO_HW_I2C_INTR 0x00001B + +/* RPU is not ready to accept commands! */ +#define CXERR_RPU_NOT_READY 0x00001C + +/* RPU is not ready to accept commands! */ +#define CXERR_RPU_NO_ACK 0x00001D + +/* The are no buffers ready. Try again soon! */ +#define CXERR_NODATA_AGAIN 0x00001E + +/* The stream is stopping. Function not alllowed now! */ +#define CXERR_STOPPING_STATUS 0x00001F + +/* Trying to access hardware when the power is turned OFF */ +#define CXERR_DEVPOWER_OFF 0x000020 + +#endif /* CX23418_H */ diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h index 0ea0bd85c036..2a527742701a 100644 --- a/include/media/v4l2-chip-ident.h +++ b/include/media/v4l2-chip-ident.h @@ -64,6 +64,7 @@ enum { /* Conexant MPEG encoder/decoders: reserved range 410-420 */ V4L2_IDENT_CX23415 = 415, V4L2_IDENT_CX23416 = 416, + V4L2_IDENT_CX23418 = 418, /* module vp27smpx: just ident 2700 */ V4L2_IDENT_VP27SMPX = 2700,