diff options
author | Hans Verkuil <hverkuil@xs4all.nl> | 2005-11-13 16:07:56 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2005-11-13 18:14:19 -0800 |
commit | bd985160a9f4623fdb24fcfeb36fe59e1b8f7b57 (patch) | |
tree | a674501ba55b0d06c88f86ac07949d3fd8338165 /drivers | |
parent | b2f0648ffda862d53f04f0a05979f3fa530d63c9 (diff) |
[PATCH] v4l: (946) adds support for cx25840 video decoder
Adds support for cx25840 video decoder.
Driver authors: Hans Verkuil, Chris Kennedy, Tyler Trafford, Ulf Eklund.
Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: Chris Kennedy <c@groovy.org>
Signed-off-by: Tyler Trafford <tatrafford@comcast.net>
Thanks-to: Ulf Eklund <ivtv@eklund.to>.
Signed-off-by: Mauro Carvalho Chehab <mchehab@brturbo.com.br>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/media/video/cx25840/cx25840-audio.c | 369 | ||||
-rw-r--r-- | drivers/media/video/cx25840/cx25840-core.c | 1024 | ||||
-rw-r--r-- | drivers/media/video/cx25840/cx25840-firmware.c | 167 | ||||
-rw-r--r-- | drivers/media/video/cx25840/cx25840-vbi.c | 315 | ||||
-rw-r--r-- | drivers/media/video/cx25840/cx25840.h | 85 |
5 files changed, 1960 insertions, 0 deletions
diff --git a/drivers/media/video/cx25840/cx25840-audio.c b/drivers/media/video/cx25840/cx25840-audio.c new file mode 100644 index 00000000000..3905580b194 --- /dev/null +++ b/drivers/media/video/cx25840/cx25840-audio.c @@ -0,0 +1,369 @@ +/* cx25840 audio functions + * + * 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 <linux/videodev2.h> +#include <linux/i2c.h> +#include <media/audiochip.h> +#include <media/i2c-compat.h> +#include <media/v4l2-common.h> + +#include "cx25840.h" + +inline static int set_audclk_freq(struct i2c_client *client, + enum v4l2_audio_clock_freq freq) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + /* assert soft reset */ + cx25840_and_or(client, 0x810, ~0x1, 0x01); + + /* common for all inputs and rates */ + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x10 */ + cx25840_write(client, 0x127, 0x50); + + switch (state->audio_input) { + case AUDIO_TUNER: + switch (freq) { + case V4L2_AUDCLK_32_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f040610); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0xee39bb01); + + /* src3/4/6_ctl = 0x0801f77f */ + cx25840_write4(client, 0x900, 0x7ff70108); + cx25840_write4(client, 0x904, 0x7ff70108); + cx25840_write4(client, 0x90c, 0x7ff70108); + break; + + case V4L2_AUDCLK_441_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f040910); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0xd66bec00); + + /* src3/4/6_ctl = 0x08016d59 */ + cx25840_write4(client, 0x900, 0x596d0108); + cx25840_write4(client, 0x904, 0x596d0108); + cx25840_write4(client, 0x90c, 0x596d0108); + break; + + case V4L2_AUDCLK_48_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f040a10); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0xe5d69800); + + /* src3/4/6_ctl = 0x08014faa */ + cx25840_write4(client, 0x900, 0xaa4f0108); + cx25840_write4(client, 0x904, 0xaa4f0108); + cx25840_write4(client, 0x90c, 0xaa4f0108); + break; + } + break; + + case AUDIO_EXTERN_1: + case AUDIO_EXTERN_2: + case AUDIO_INTERN: + case AUDIO_RADIO: + switch (freq) { + case V4L2_AUDCLK_32_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f04081e); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0x69082a01); + + /* src1_ctl = 0x08010000 */ + cx25840_write4(client, 0x8f8, 0x00000108); + + /* src3/4/6_ctl = 0x08020000 */ + cx25840_write4(client, 0x900, 0x00000208); + cx25840_write4(client, 0x904, 0x00000208); + cx25840_write4(client, 0x90c, 0x00000208); + + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x14 */ + cx25840_write(client, 0x127, 0x54); + break; + + case V4L2_AUDCLK_441_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f040918); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0xd66bec00); + + /* src1_ctl = 0x08010000 */ + cx25840_write4(client, 0x8f8, 0xcd600108); + + /* src3/4/6_ctl = 0x08020000 */ + cx25840_write4(client, 0x900, 0x85730108); + cx25840_write4(client, 0x904, 0x85730108); + cx25840_write4(client, 0x90c, 0x85730108); + break; + + case V4L2_AUDCLK_48_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f040a18); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0xe5d69800); + + /* src1_ctl = 0x08010000 */ + cx25840_write4(client, 0x8f8, 0x00800108); + + /* src3/4/6_ctl = 0x08020000 */ + cx25840_write4(client, 0x900, 0x55550108); + cx25840_write4(client, 0x904, 0x55550108); + cx25840_write4(client, 0x90c, 0x55550108); + break; + } + break; + } + + /* deassert soft reset */ + cx25840_and_or(client, 0x810, ~0x1, 0x00); + + state->audclk_freq = freq; + + return 0; +} + +static int set_input(struct i2c_client *client, int audio_input) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + cx25840_dbg("set audio input (%d)\n", audio_input); + + /* stop microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0); + + /* Mute everything to prevent the PFFT! */ + cx25840_write(client, 0x8d3, 0x1f); + + switch (audio_input) { + case AUDIO_TUNER: + /* Set Path1 to Analog Demod Main Channel */ + cx25840_write4(client, 0x8d0, 0x7038061f); + + /* When the microcontroller detects the + * audio format, it will unmute the lines */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); + break; + + case AUDIO_EXTERN_1: + case AUDIO_EXTERN_2: + case AUDIO_INTERN: + case AUDIO_RADIO: + /* Set Path1 to Serial Audio Input */ + cx25840_write4(client, 0x8d0, 0x12100101); + + /* The microcontroller should not be started for the + * non-tuner inputs: autodetection is specific for + * TV audio. */ + break; + + default: + cx25840_dbg("Invalid audio input selection %d\n", audio_input); + return -EINVAL; + } + + state->audio_input = audio_input; + + return set_audclk_freq(client, state->audclk_freq); +} + +inline static int get_volume(struct i2c_client *client) +{ + /* Volume runs +18dB to -96dB in 1/2dB steps + * change to fit the msp3400 -114dB to +12dB range */ + + /* check PATH1_VOLUME */ + int vol = 228 - cx25840_read(client, 0x8d4); + vol = (vol / 2) + 23; + return vol << 9; +} + +inline static void set_volume(struct i2c_client *client, int volume) +{ + /* First convert the volume to msp3400 values (0-127) */ + int vol = volume >> 9; + /* now scale it up to cx25840 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 */ + cx25840_write(client, 0x8d4, 228 - (vol * 2)); +} + +inline static int get_bass(struct i2c_client *client) +{ + /* bass is 49 steps +12dB to -12dB */ + + /* check PATH1_EQ_BASS_VOL */ + int bass = cx25840_read(client, 0x8d9) & 0x3f; + bass = (((48 - bass) * 0xffff) + 47) / 48; + return bass; +} + +inline static void set_bass(struct i2c_client *client, int bass) +{ + /* PATH1_EQ_BASS_VOL */ + cx25840_and_or(client, 0x8d9, ~0x3f, 48 - (bass * 48 / 0xffff)); +} + +inline static int get_treble(struct i2c_client *client) +{ + /* treble is 49 steps +12dB to -12dB */ + + /* check PATH1_EQ_TREBLE_VOL */ + int treble = cx25840_read(client, 0x8db) & 0x3f; + treble = (((48 - treble) * 0xffff) + 47) / 48; + return treble; +} + +inline static void set_treble(struct i2c_client *client, int treble) +{ + /* PATH1_EQ_TREBLE_VOL */ + cx25840_and_or(client, 0x8db, ~0x3f, 48 - (treble * 48 / 0xffff)); +} + +inline static int get_balance(struct i2c_client *client) +{ + /* balance is 7 bit, 0 to -96dB */ + + /* check PATH1_BAL_LEVEL */ + int balance = cx25840_read(client, 0x8d5) & 0x7f; + /* check PATH1_BAL_LEFT */ + if ((cx25840_read(client, 0x8d5) & 0x80) == 0) + balance = 0x80 - balance; + else + balance = 0x80 + balance; + return balance << 8; +} + +inline static void set_balance(struct i2c_client *client, int balance) +{ + int bal = balance >> 8; + if (bal > 0x80) { + /* PATH1_BAL_LEFT */ + cx25840_and_or(client, 0x8d5, 0x7f, 0x80); + /* PATH1_BAL_LEVEL */ + cx25840_and_or(client, 0x8d5, ~0x7f, bal & 0x7f); + } else { + /* PATH1_BAL_LEFT */ + cx25840_and_or(client, 0x8d5, 0x7f, 0x00); + /* PATH1_BAL_LEVEL */ + cx25840_and_or(client, 0x8d5, ~0x7f, 0x80 - bal); + } +} + +inline static int get_mute(struct i2c_client *client) +{ + /* check SRC1_MUTE_EN */ + return cx25840_read(client, 0x8d3) & 0x2 ? 1 : 0; +} + +inline static void set_mute(struct i2c_client *client, int mute) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + if (state->audio_input == AUDIO_TUNER) { + /* 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 */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + cx25840_write(client, 0x8d3, 0x1f); + } else { + /* enable microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); + } + } else { + /* SRC1_MUTE_EN */ + cx25840_and_or(client, 0x8d3, ~0x2, mute ? 0x02 : 0x00); + } +} + +int cx25840_audio(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct v4l2_control *ctrl = arg; + + switch (cmd) { + case AUDC_SET_INPUT: + return set_input(client, *(int *)arg); + case VIDIOC_INT_AUDIO_CLOCK_FREQ: + return set_audclk_freq(client, *(enum v4l2_audio_clock_freq *)arg); + case VIDIOC_G_CTRL: + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + ctrl->value = get_volume(client); + break; + case V4L2_CID_AUDIO_BASS: + ctrl->value = get_bass(client); + break; + case V4L2_CID_AUDIO_TREBLE: + ctrl->value = get_treble(client); + break; + case V4L2_CID_AUDIO_BALANCE: + ctrl->value = get_balance(client); + break; + case V4L2_CID_AUDIO_MUTE: + ctrl->value = get_mute(client); + break; + default: + return -EINVAL; + } + break; + case VIDIOC_S_CTRL: + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + set_volume(client, ctrl->value); + break; + case V4L2_CID_AUDIO_BASS: + set_bass(client, ctrl->value); + break; + case V4L2_CID_AUDIO_TREBLE: + set_treble(client, ctrl->value); + break; + case V4L2_CID_AUDIO_BALANCE: + set_balance(client, ctrl->value); + break; + case V4L2_CID_AUDIO_MUTE: + set_mute(client, ctrl->value); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} diff --git a/drivers/media/video/cx25840/cx25840-core.c b/drivers/media/video/cx25840/cx25840-core.c new file mode 100644 index 00000000000..805273e5f91 --- /dev/null +++ b/drivers/media/video/cx25840/cx25840-core.c @@ -0,0 +1,1024 @@ +/* cx25840 - Conexant CX25840 audio/video decoder driver + * + * Copyright (C) 2004 Ulf Eklund + * + * Based on the saa7115 driver and on the first verison of Chris Kennedy's + * cx25840 driver. + * + * Changes by Tyler Trafford <tatrafford@comcast.net> + * - cleanup/rewrite for V4L2 API (2005) + * + * VBI support by Hans Verkuil <hverkuil@xs4all.nl>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <media/audiochip.h> +#include <media/i2c-compat.h> +#include <media/v4l2-common.h> + +#include "cx25840.h" + +MODULE_DESCRIPTION("Conexant CX25840 audio/video decoder driver"); +MODULE_AUTHOR("Ulf Eklund <ivtv@eklund.to>"); +MODULE_AUTHOR("Chris Kennedy <c@groovy.org>"); +MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); +MODULE_AUTHOR("Tyler Trafford <tatrafford@comcast.net>"); +MODULE_LICENSE("GPL"); + +static unsigned short normal_i2c[] = { 0x88 >> 1, I2C_CLIENT_END }; + + +int cx25840_debug = 0; + +module_param(cx25840_debug, bool, 0644); + +MODULE_PARM_DESC(cx25840_debug, "Debugging messages [0=Off (default) 1=On]"); + +I2C_CLIENT_INSMOD; + +/* ----------------------------------------------------------------------- */ + +int cx25840_write(struct i2c_client *client, u16 addr, u8 value) +{ + u8 buffer[3]; + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + buffer[2] = value; + return i2c_master_send(client, buffer, 3); +} + +int cx25840_write4(struct i2c_client *client, u16 addr, u32 value) +{ + u8 buffer[6]; + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + buffer[2] = value >> 24; + buffer[3] = (value >> 16) & 0xff; + buffer[4] = (value >> 8) & 0xff; + buffer[5] = value & 0xff; + return i2c_master_send(client, buffer, 6); +} + +u8 cx25840_read(struct i2c_client * client, u16 addr) +{ + u8 buffer[2]; + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + + if (i2c_master_send(client, buffer, 2) < 2) + return 0; + + if (i2c_master_recv(client, buffer, 1) < 1) + return 0; + + return buffer[0]; +} + +u32 cx25840_read4(struct i2c_client * client, u16 addr) +{ + u8 buffer[4]; + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + + if (i2c_master_send(client, buffer, 2) < 2) + return 0; + + if (i2c_master_recv(client, buffer, 4) < 4) + return 0; + + return (buffer[0] << 24) | (buffer[1] << 16) | + (buffer[2] << 8) | buffer[3]; +} + +int cx25840_and_or(struct i2c_client *client, u16 addr, u8 and_mask, + u8 or_value) +{ + return cx25840_write(client, addr, + (cx25840_read(client, addr) & and_mask) | + or_value); +} + +/* ----------------------------------------------------------------------- */ + +static int set_input(struct i2c_client *, enum cx25840_input); +static void input_change(struct i2c_client *); +static void log_status(struct i2c_client *client); + +/* ----------------------------------------------------------------------- */ + +static inline void init_dll1(struct i2c_client *client) +{ + /* This is the Hauppauge sequence used to + * initialize the Delay Lock Loop 1 (ADC DLL). */ + cx25840_write(client, 0x159, 0x23); + cx25840_write(client, 0x15a, 0x87); + cx25840_write(client, 0x15b, 0x06); + cx25840_write(client, 0x159, 0xe1); + cx25840_write(client, 0x15a, 0x86); + cx25840_write(client, 0x159, 0xe0); + cx25840_write(client, 0x159, 0xe1); + cx25840_write(client, 0x15b, 0x10); +} + +static inline void init_dll2(struct i2c_client *client) +{ + /* This is the Hauppauge sequence used to + * initialize the Delay Lock Loop 2 (ADC DLL). */ + cx25840_write(client, 0x15d, 0xe3); + cx25840_write(client, 0x15e, 0x86); + cx25840_write(client, 0x15f, 0x06); + cx25840_write(client, 0x15d, 0xe1); + cx25840_write(client, 0x15d, 0xe0); + cx25840_write(client, 0x15d, 0xe1); +} + +static void cx25840_initialize(struct i2c_client *client, int loadfw) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + /* datasheet startup in numbered steps, refer to page 3-77 */ + /* 2. */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + /* The default of this register should be 4, but I get 0 instead. + * Set this register to 4 manually. */ + cx25840_write(client, 0x000, 0x04); + /* 3. */ + init_dll1(client); + init_dll2(client); + cx25840_write(client, 0x136, 0x0a); + /* 4. */ + cx25840_write(client, 0x13c, 0x01); + cx25840_write(client, 0x13c, 0x00); + /* 5. */ + if (loadfw) + cx25840_loadfw(client); + /* 6. */ + cx25840_write(client, 0x115, 0x8c); + cx25840_write(client, 0x116, 0x07); + cx25840_write(client, 0x118, 0x02); + /* 7. */ + cx25840_write(client, 0x4a5, 0x80); + cx25840_write(client, 0x4a5, 0x00); + cx25840_write(client, 0x402, 0x00); + /* 8. */ + cx25840_write(client, 0x401, 0x18); + cx25840_write(client, 0x4a2, 0x10); + cx25840_write(client, 0x402, 0x04); + /* 10. */ + cx25840_write(client, 0x8d3, 0x1f); + cx25840_write(client, 0x8e3, 0x03); + + cx25840_vbi_setup(client); + + /* trial and error says these are needed to get audio */ + cx25840_write(client, 0x914, 0xa0); + cx25840_write(client, 0x918, 0xa0); + cx25840_write(client, 0x919, 0x01); + + /* stereo prefered */ + cx25840_write(client, 0x809, 0x04); + /* AC97 shift */ + cx25840_write(client, 0x8cf, 0x0f); + + /* (re)set video input */ + set_input(client, state->input); + /* (re)set audio input */ + cx25840_audio(client, AUDC_SET_INPUT, &state->audio_input); + + /* start microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); +} + +/* ----------------------------------------------------------------------- */ + +static void input_change(struct i2c_client *client) +{ + v4l2_std_id std = cx25840_get_v4lstd(client); + + if (std & V4L2_STD_PAL) { + /* Follow tuner change procedure for PAL */ + cx25840_write(client, 0x808, 0xff); + cx25840_write(client, 0x80b, 0x10); + } else if (std & V4L2_STD_SECAM) { + /* Select autodetect for SECAM */ + cx25840_write(client, 0x808, 0xff); + cx25840_write(client, 0x80b, 0x10); + } else if (std & V4L2_STD_NTSC) { + /* NTSC */ + cx25840_write(client, 0x808, 0xf6); + cx25840_write(client, 0x80b, 0x00); + } + + if (cx25840_read(client, 0x803) & 0x10) { + /* restart audio decoder microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + cx25840_and_or(client, 0x803, ~0x10, 0x10); + } +} + +static int set_input(struct i2c_client *client, enum cx25840_input input) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + cx25840_dbg("decoder set input (%d)\n", input); + + switch (input) { + case CX25840_TUNER: + cx25840_dbg("now setting Tuner input\n"); + + if (state->cardtype == CARDTYPE_PVR150) { + /* CH_SEL_ADC2=1 */ + cx25840_and_or(client, 0x102, ~0x2, 0x02); + } + + /* Video Input Control */ + if (state->cardtype == CARDTYPE_PG600) { + cx25840_write(client, 0x103, 0x11); + } else { + cx25840_write(client, 0x103, 0x46); + } + + /* INPUT_MODE=0 */ + cx25840_and_or(client, 0x401, ~0x6, 0x00); + break; + + case CX25840_COMPOSITE0: + case CX25840_COMPOSITE1: + cx25840_dbg("now setting Composite input\n"); + + /* Video Input Control */ + if (state->cardtype == CARDTYPE_PG600) { + cx25840_write(client, 0x103, 0x00); + } else { + cx25840_write(client, 0x103, 0x02); + } + + /* INPUT_MODE=0 */ + cx25840_and_or(client, 0x401, ~0x6, 0x00); + break; + + case CX25840_SVIDEO0: + case CX25840_SVIDEO1: + cx25840_dbg("now setting S-Video input\n"); + + /* CH_SEL_ADC2=0 */ + cx25840_and_or(client, 0x102, ~0x2, 0x00); + + /* Video Input Control */ + if (state->cardtype == CARDTYPE_PG600) { + cx25840_write(client, 0x103, 0x02); + } else { + cx25840_write(client, 0x103, 0x10); + } + + /* INPUT_MODE=1 */ + cx25840_and_or(client, 0x401, ~0x6, 0x02); + break; + + default: + cx25840_err("%d is not a valid input!\n", input); + return -EINVAL; + } + + state->input = input; + input_change(client); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int set_v4lstd(struct i2c_client *client, v4l2_std_id std) +{ + u8 fmt; + + switch (std) { + /* zero is autodetect */ + case 0: fmt = 0x0; break; + /* default ntsc to ntsc-m */ + case V4L2_STD_NTSC: + case V4L2_STD_NTSC_M: fmt = 0x1; break; + case V4L2_STD_NTSC_M_JP: fmt = 0x2; break; + case V4L2_STD_NTSC_443: fmt = 0x3; break; + case V4L2_STD_PAL: fmt = 0x4; break; + case V4L2_STD_PAL_M: fmt = 0x5; break; + case V4L2_STD_PAL_N: fmt = 0x6; break; + case V4L2_STD_PAL_Nc: fmt = 0x7; break; + case V4L2_STD_PAL_60: fmt = 0x8; break; + case V4L2_STD_SECAM: fmt = 0xc; break; + default: + return -ERANGE; + } + + cx25840_and_or(client, 0x400, ~0xf, fmt); + cx25840_vbi_setup(client); + return 0; +} + +v4l2_std_id cx25840_get_v4lstd(struct i2c_client * client) +{ + /* check VID_FMT_SEL first */ + u8 fmt = cx25840_read(client, 0x400) & 0xf; + + if (!fmt) { + /* check AFD_FMT_STAT if set to autodetect */ + fmt = cx25840_read(client, 0x40d) & 0xf; + } + + switch (fmt) { + case 0x1: return V4L2_STD_NTSC_M; + case 0x2: return V4L2_STD_NTSC_M_JP; + case 0x3: return V4L2_STD_NTSC_443; + case 0x4: return V4L2_STD_PAL; + case 0x5: return V4L2_STD_PAL_M; + case 0x6: return V4L2_STD_PAL_N; + case 0x7: return V4L2_STD_PAL_Nc; + case 0x8: return V4L2_STD_PAL_60; + case 0xc: return V4L2_STD_SECAM; + default: return V4L2_STD_UNKNOWN; + } +} + +/* ----------------------------------------------------------------------- */ + +static int set_v4lctrl(struct i2c_client *client, struct v4l2_control *ctrl) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + switch (ctrl->id) { + case CX25840_CID_CARDTYPE: + switch (ctrl->value) { + case CARDTYPE_PVR150: + case CARDTYPE_PG600: + state->cardtype = ctrl->value; + break; + default: + return -ERANGE; + } + + set_input(client, state->input); + break; + + case V4L2_CID_BRIGHTNESS: + if (ctrl->value < 0 || ctrl->value > 255) { + cx25840_err("invalid brightness setting %d\n", + ctrl->value); + return -ERANGE; + } + + cx25840_write(client, 0x414, ctrl->value - 128); + break; + + case V4L2_CID_CONTRAST: + if (ctrl->value < 0 || ctrl->value > 127) { + cx25840_err("invalid contrast setting %d\n", + ctrl->value); + return -ERANGE; + } + + cx25840_write(client, 0x415, ctrl->value << 1); + break; + + case V4L2_CID_SATURATION: + if (ctrl->value < 0 || ctrl->value > 127) { + cx25840_err("invalid saturation setting %d\n", + ctrl->value); + return -ERANGE; + } + + cx25840_write(client, 0x420, ctrl->value << 1); + cx25840_write(client, 0x421, ctrl->value << 1); + break; + + case V4L2_CID_HUE: + if (ctrl->value < -127 || ctrl->value > 127) { + cx25840_err("invalid hue setting %d\n", ctrl->value); + return -ERANGE; + } + + cx25840_write(client, 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 cx25840_audio(client, VIDIOC_S_CTRL, ctrl); + } + + return 0; +} + +static int get_v4lctrl(struct i2c_client *client, struct v4l2_control *ctrl) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + switch (ctrl->id) { + case CX25840_CID_CARDTYPE: + ctrl->value = state->cardtype; + break; + case V4L2_CID_BRIGHTNESS: + ctrl->value = cx25840_read(client, 0x414) + 128; + break; + case V4L2_CID_CONTRAST: + ctrl->value = cx25840_read(client, 0x415) >> 1; + break; + case V4L2_CID_SATURATION: + ctrl->value = cx25840_read(client, 0x420) >> 1; + break; + case V4L2_CID_HUE: + ctrl->value = cx25840_read(client, 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 cx25840_audio(client, VIDIOC_G_CTRL, ctrl); + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int get_v4lfmt(struct i2c_client *client, struct v4l2_format *fmt) +{ + switch (fmt->type) { + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + return cx25840_vbi(client, VIDIOC_G_FMT, fmt); + default: + return -EINVAL; + } + + return 0; +} + +static int set_v4lfmt(struct i2c_client *client, struct v4l2_format *fmt) +{ + struct v4l2_pix_format *pix; + int HSC, VSC, Vsrc, Hsrc, filter, Vlines; + int is_pal = !(cx25840_get_v4lstd(client) & V4L2_STD_NTSC); + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pix = &(fmt->fmt.pix); + + Vsrc = (cx25840_read(client, 0x476) & 0x3f) << 4; + Vsrc |= (cx25840_read(client, 0x475) & 0xf0) >> 4; + + Hsrc = (cx25840_read(client, 0x472) & 0x3f) << 4; + Hsrc |= (cx25840_read(client, 0x471) & 0xf0) >> 4; + + Vlines = pix->height + (is_pal ? 4 : 7); + + if ((pix->width * 16 < Hsrc) || (Hsrc < pix->width) || + (Vlines * 8 < Vsrc) || (Vsrc < Vlines)) { + cx25840_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; + + cx25840_dbg("decoder set size %dx%d -> scale %ux%u\n", + pix->width, pix->height, HSC, VSC); + + /* HSCALE=HSC */ + cx25840_write(client, 0x418, HSC & 0xff); + cx25840_write(client, 0x419, (HSC >> 8) & 0xff); + cx25840_write(client, 0x41a, HSC >> 16); + /* VSCALE=VSC */ + cx25840_write(client, 0x41c, VSC & 0xff); + cx25840_write(client, 0x41d, VSC >> 8); + /* VS_INTRLACE=1 VFILT=filter */ + cx25840_write(client, 0x41e, 0x8 | filter); + break; + + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + return cx25840_vbi(client, VIDIOC_S_FMT, fmt); + + case V4L2_BUF_TYPE_VBI_CAPTURE: + return cx25840_vbi(client, VIDIOC_S_FMT, fmt); + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int cx25840_command(struct i2c_client *client, unsigned int cmd, + void *arg) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + struct v4l2_tuner *vt = arg; + int result = 0; + + switch (cmd) { + case 0: + break; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + /* ioctls to allow direct access to the + * cx25840 registers for testing */ + case VIDIOC_INT_G_REGISTER: + { + struct v4l2_register *reg = arg; + + if (reg->i2c_id != I2C_DRIVERID_CX25840) + return -EINVAL; + reg->val = cx25840_read(client, reg->reg & 0x0fff); + break; + } + + case VIDIOC_INT_S_REGISTER: + { + struct v4l2_register *reg = arg; + + if (reg->i2c_id != I2C_DRIVERID_CX25840) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + cx25840_write(client, reg->reg & 0x0fff, reg->val & 0xff); + break; + } +#endif + + case VIDIOC_INT_DECODE_VBI_LINE: + return cx25840_vbi(client, cmd, arg); + + case VIDIOC_INT_AUDIO_CLOCK_FREQ: + case AUDC_SET_INPUT: + result = cx25840_audio(client, cmd, arg); + break; + + case VIDIOC_STREAMON: + cx25840_dbg("enable output\n"); + cx25840_write(client, 0x115, 0x8c); + cx25840_write(client, 0x116, 0x07); + break; + + case VIDIOC_STREAMOFF: + cx25840_dbg("disable output\n"); + cx25840_write(client, 0x115, 0x00); + cx25840_write(client, 0x116, 0x00); + break; + + case VIDIOC_LOG_STATUS: + log_status(client); + break; + + case VIDIOC_G_CTRL: + result = get_v4lctrl(client, (struct v4l2_control *)arg); + break; + + case VIDIOC_S_CTRL: + result = set_v4lctrl(client, (struct v4l2_control *)arg); + break; + + case VIDIOC_G_STD: + *(v4l2_std_id *)arg = cx25840_get_v4lstd(client); + break; + + case VIDIOC_S_STD: + result = set_v4lstd(client, *(v4l2_std_id *)arg); + break; + + case VIDIOC_G_INPUT: + *(int *)arg = state->input; + break; + + case VIDIOC_S_INPUT: + result = set_input(client, *(int *)arg); + break; + + case VIDIOC_S_FREQUENCY: + input_change(client); + break; + + case VIDIOC_G_TUNER: + { + u8 mode = cx25840_read(client, 0x804); + u8 pref = cx25840_read(client, 0x809) & 0xf; + u8 vpres = cx25840_read(client, 0x80a) & 0x10; + int val = 0; + + vt->capability |= + V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + + vt->signal = vpres ? 0xffff : 0x0; + + /* 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; + + switch (pref) { + case 0: + vt->audmode = V4L2_TUNER_MODE_MONO; + break; + case 1: + case 2: + vt->audmode = V4L2_TUNER_MODE_LANG2; + break; + case 4: + default: + vt->audmode = V4L2_TUNER_MODE_STEREO; + } + break; + } + + case VIDIOC_S_TUNER: + switch (vt->audmode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + /* Force PREF_MODE to MONO */ + cx25840_and_or(client, 0x809, ~0xf, 0x00); + break; + case V4L2_TUNER_MODE_STEREO: + /* Force PREF_MODE to STEREO */ + cx25840_and_or(client, 0x809, ~0xf, 0x04); + break; + case V4L2_TUNER_MODE_LANG2: + /* Force PREF_MODE to LANG2 */ + cx25840_and_or(client, 0x809, ~0xf, 0x01); + break; + } + break; + + case VIDIOC_G_FMT: + result = get_v4lfmt(client, (struct v4l2_format *)arg); + break; + + case VIDIOC_S_FMT: + result = set_v4lfmt(client, (struct v4l2_format *)arg); + break; + + case VIDIOC_INT_RESET: + cx25840_initialize(client, 0); + break; + + case VIDIOC_INT_G_CHIP_IDENT: + *(enum v4l2_chip_ident *)arg = + V4L2_IDENT_CX25840 + ((cx25840_read(client, 0x100) >> 4) & 0xf); + break; + + default: + cx25840_err("invalid ioctl %x\n", cmd); + return -EINVAL; + } + + return result; +} + +/* ----------------------------------------------------------------------- */ + +struct i2c_driver i2c_driver_cx25840; + +static int cx25840_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + struct i2c_client *client; + struct cx25840_state *state; + u16 device_id; + + /* Check if the adapter supports the needed features + * Not until kernel version 2.6.11 did the bit-algo + * correctly report that it would do an I2C-level xfer */ + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_cx25840; + client->flags = I2C_CLIENT_ALLOW_USE; + snprintf(client->name, sizeof(client->name) - 1, "cx25840"); + + cx25840_dbg("detecting cx25840 client on address 0x%x\n", address << 1); + + device_id = cx25840_read(client, 0x101) << 8; + device_id |= cx25840_read(client, 0x100); + + /* The high byte of the device ID should be + * 0x84 if chip is present */ + if ((device_id & 0xff00) != 0x8400) { + cx25840_dbg("cx25840 not found\n"); + kfree(client); + return 0; + } + + cx25840_info("cx25%3x-2%x found @ 0x%x (%s)\n", + (device_id & 0xfff0) >> 4, + (device_id & 0x0f) < 3 ? (device_id & 0x0f) + 1 : 3, + address << 1, adapter->name); + + state = kmalloc(sizeof(struct cx25840_state), GFP_KERNEL); + if (state == NULL) { + kfree(client); + return -ENOMEM; + } + + i2c_set_clientdata(client, state); + memset(state, 0, sizeof(struct cx25840_state)); + state->input = CX25840_TUNER; + state->audclk_freq = V4L2_AUDCLK_4 |