*
* VBI support by Hans Verkuil <hverkuil@xs4all.nl>.
*
- * NTSC sliced VBI support by Christopher Neufeld <television@cneufeld.ca>
- * with additional fixes 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
#include <linux/slab.h>
#include <linux/videodev2.h>
#include <linux/i2c.h>
+#include <media/audiochip.h>
#include <media/v4l2-common.h>
-#include <media/cx25840.h>
-#include "cx25840-core.h"
+#include "cx25840.h"
MODULE_DESCRIPTION("Conexant CX25840 audio/video decoder driver");
MODULE_AUTHOR("Ulf Eklund, Chris Kennedy, Hans Verkuil, Tyler Trafford");
static unsigned short normal_i2c[] = { 0x88 >> 1, I2C_CLIENT_END };
-int cx25840_debug;
+static int cx25840_debug;
module_param_named(debug,cx25840_debug, int, 0644);
if (i2c_master_recv(client, buffer, 4) < 4)
return 0;
- return (buffer[3] << 24) | (buffer[2] << 16) |
- (buffer[1] << 8) | buffer[0];
+ return (buffer[0] << 24) | (buffer[1] << 16) |
+ (buffer[2] << 8) | buffer[3];
}
-int cx25840_and_or(struct i2c_client *client, u16 addr, unsigned and_mask,
+int cx25840_and_or(struct i2c_client *client, u16 addr, u8 and_mask,
u8 or_value)
{
return cx25840_write(client, addr,
static int set_input(struct i2c_client *client, enum cx25840_video_input vid_input,
enum cx25840_audio_input aud_input);
-static void log_audio_status(struct i2c_client *client);
-static void log_video_status(struct i2c_client *client);
+static void log_status(struct i2c_client *client);
/* ----------------------------------------------------------------------- */
cx25840_write(client, 0x15d, 0xe1);
}
-static void cx25836_initialize(struct i2c_client *client)
-{
- /* reset configuration is described on page 3-77 of the CX25836 datasheet */
- /* 2. */
- cx25840_and_or(client, 0x000, ~0x01, 0x01);
- cx25840_and_or(client, 0x000, ~0x01, 0x00);
- /* 3a. */
- cx25840_and_or(client, 0x15a, ~0x70, 0x00);
- /* 3b. */
- cx25840_and_or(client, 0x15b, ~0x1e, 0x06);
- /* 3c. */
- cx25840_and_or(client, 0x159, ~0x02, 0x02);
- /* 3d. */
- /* There should be a 10-us delay here, but since the
- i2c bus already has a 10-us delay we don't need to do
- anything */
- /* 3e. */
- cx25840_and_or(client, 0x159, ~0x02, 0x00);
- /* 3f. */
- cx25840_and_or(client, 0x159, ~0xc0, 0xc0);
- /* 3g. */
- cx25840_and_or(client, 0x159, ~0x01, 0x00);
- cx25840_and_or(client, 0x159, ~0x01, 0x01);
- /* 3h. */
- cx25840_and_or(client, 0x15b, ~0x1e, 0x10);
-}
-
static void cx25840_initialize(struct i2c_client *client, int loadfw)
{
struct cx25840_state *state = i2c_get_clientdata(client);
cx25840_write(client, 0x4a5, 0x00);
cx25840_write(client, 0x402, 0x00);
/* 8. */
- cx25840_and_or(client, 0x401, ~0x18, 0);
- cx25840_and_or(client, 0x4a2, ~0x10, 0x10);
- /* steps 8c and 8d are done in change_input() */
+ 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);
struct cx25840_state *state = i2c_get_clientdata(client);
v4l2_std_id std = cx25840_get_v4lstd(client);
- /* Follow step 8c and 8d of section 3.16 in the cx25840 datasheet */
- if (std & V4L2_STD_SECAM) {
- cx25840_write(client, 0x402, 0);
- }
- else {
- cx25840_write(client, 0x402, 0x04);
- cx25840_write(client, 0x49f, (std & V4L2_STD_NTSC) ? 0x14 : 0x11);
- }
- cx25840_and_or(client, 0x401, ~0x60, 0);
- cx25840_and_or(client, 0x401, ~0x60, 0x60);
-
- if (std & V4L2_STD_525_60) {
+ /* Note: perhaps V4L2_STD_PAL_M should be handled as V4L2_STD_NTSC
+ instead of V4L2_STD_PAL. Someone needs to test this. */
+ 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) {
/* Certain Hauppauge PVR150 models have a hardware bug
that causes audio to drop out. For these models the
audio standard must be set explicitly.
cx25840_write(client, 0x808, hw_fix ? 0x1f : 0xf6);
}
cx25840_write(client, 0x80b, 0x00);
- } else 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);
}
if (cx25840_read(client, 0x803) & 0x10) {
state->vid_input = vid_input;
state->aud_input = aud_input;
- if (!state->is_cx25836) {
- cx25840_audio_set_path(client);
- input_change(client);
- }
+ cx25840_audio_set_path(client);
+ input_change(client);
return 0;
}
}
}
- v4l_dbg(1, cx25840_debug, client, "changing video std to fmt %i\n",fmt);
-
- /* Follow step 9 of section 3.16 in the cx25840 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 */
- cx25840_and_or(client, 0x400, ~0xf, 1);
- /* Turn off LCOMB */
- cx25840_and_or(client, 0x47b, ~6, 0);
- }
cx25840_and_or(client, 0x400, ~0xf, fmt);
cx25840_vbi_setup(client);
return 0;
v4l2_std_id cx25840_get_v4lstd(struct i2c_client * client)
{
- struct cx25840_state *state = i2c_get_clientdata(client);
/* check VID_FMT_SEL first */
u8 fmt = cx25840_read(client, 0x400) & 0xf;
}
switch (fmt) {
- case 0x1:
- {
- /* if the audio std is A2-M, then this is the South Korean
- NTSC standard */
- if (!state->is_cx25836 && cx25840_read(client, 0x805) == 2)
- return V4L2_STD_NTSC_M_KR;
- return V4L2_STD_NTSC_M;
- }
+ case 0x1: return V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_KR;
case 0x2: return V4L2_STD_NTSC_M_JP;
case 0x3: return V4L2_STD_NTSC_443;
case 0x4: return V4L2_STD_PAL;
case V4L2_CID_AUDIO_TREBLE:
case V4L2_CID_AUDIO_BALANCE:
case V4L2_CID_AUDIO_MUTE:
- if (state->is_cx25836)
- return -EINVAL;
return cx25840_audio(client, VIDIOC_S_CTRL, ctrl);
default:
case V4L2_CID_AUDIO_TREBLE:
case V4L2_CID_AUDIO_BALANCE:
case V4L2_CID_AUDIO_MUTE:
- if (state->is_cx25836)
- return -EINVAL;
return cx25840_audio(client, VIDIOC_G_CTRL, ctrl);
default:
return -EINVAL;
/* ----------------------------------------------------------------------- */
+static struct v4l2_queryctrl cx25840_qctrl[] = {
+ {
+ .id = V4L2_CID_BRIGHTNESS,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Brightness",
+ .minimum = 0,
+ .maximum = 255,
+ .step = 1,
+ .default_value = 128,
+ .flags = 0,
+ }, {
+ .id = V4L2_CID_CONTRAST,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Contrast",
+ .minimum = 0,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 64,
+ .flags = 0,
+ }, {
+ .id = V4L2_CID_SATURATION,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Saturation",
+ .minimum = 0,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 64,
+ .flags = 0,
+ }, {
+ .id = V4L2_CID_HUE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Hue",
+ .minimum = -128,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0,
+ }, {
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 65535/100,
+ .default_value = 58880,
+ .flags = 0,
+ }, {
+ .id = V4L2_CID_AUDIO_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Balance",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 65535/100,
+ .default_value = 32768,
+ .flags = 0,
+ }, {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+ .flags = 0,
+ }, {
+ .id = V4L2_CID_AUDIO_BASS,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Bass",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 65535/100,
+ .default_value = 32768,
+ }, {
+ .id = V4L2_CID_AUDIO_TREBLE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Treble",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 65535/100,
+ .default_value = 32768,
+ },
+};
+
+/* ----------------------------------------------------------------------- */
+
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;
- struct v4l2_routing *route = arg;
switch (cmd) {
#ifdef CONFIG_VIDEO_ADV_DEBUG
case VIDIOC_STREAMON:
v4l_dbg(1, cx25840_debug, client, "enable output\n");
- cx25840_write(client, 0x115, state->is_cx25836 ? 0x0c : 0x8c);
- cx25840_write(client, 0x116, state->is_cx25836 ? 0x04 : 0x07);
+ cx25840_write(client, 0x115, 0x8c);
+ cx25840_write(client, 0x116, 0x07);
break;
case VIDIOC_STREAMOFF:
break;
case VIDIOC_LOG_STATUS:
- log_video_status(client);
- if (!state->is_cx25836)
- log_audio_status(client);
+ log_status(client);
break;
case VIDIOC_G_CTRL:
case VIDIOC_QUERYCTRL:
{
struct v4l2_queryctrl *qc = arg;
+ int i;
- 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;
- }
- if (state->is_cx25836)
- return -EINVAL;
-
- 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;
- }
+ for (i = 0; i < ARRAY_SIZE(cx25840_qctrl); i++)
+ if (qc->id && qc->id == cx25840_qctrl[i].id) {
+ memcpy(qc, &cx25840_qctrl[i], sizeof(*qc));
+ return 0;
+ }
return -EINVAL;
}
state->radio = 1;
break;
- case VIDIOC_INT_G_VIDEO_ROUTING:
- route->input = state->vid_input;
- route->output = 0;
+ case VIDIOC_G_INPUT:
+ *(int *)arg = state->vid_input;
break;
- case VIDIOC_INT_S_VIDEO_ROUTING:
- return set_input(client, route->input, state->aud_input);
+ case VIDIOC_S_INPUT:
+ return set_input(client, *(enum cx25840_video_input *)arg, state->aud_input);
- case VIDIOC_INT_G_AUDIO_ROUTING:
- if (state->is_cx25836)
- return -EINVAL;
- route->input = state->aud_input;
- route->output = 0;
- break;
+ case VIDIOC_S_AUDIO:
+ {
+ struct v4l2_audio *input = arg;
- case VIDIOC_INT_S_AUDIO_ROUTING:
- if (state->is_cx25836)
- return -EINVAL;
- return set_input(client, state->vid_input, route->input);
+ return set_input(client, state->vid_input, input->index);
+ }
+
+ case VIDIOC_G_AUDIO:
+ {
+ struct v4l2_audio *input = arg;
+
+ memset(input, 0, sizeof(*input));
+ input->index = state->aud_input;
+ break;
+ }
case VIDIOC_S_FREQUENCY:
- if (!state->is_cx25836) {
- input_change(client);
- }
+ input_change(client);
break;
case VIDIOC_G_TUNER:
{
- u8 vpres = cx25840_read(client, 0x40e) & 0x20;
- u8 mode;
+ u8 mode = cx25840_read(client, 0x804);
+ u8 pref = cx25840_read(client, 0x809) & 0xf;
+ u8 vpres = cx25840_read(client, 0x80a) & 0x10;
int val = 0;
if (state->radio)
break;
- vt->signal = vpres ? 0xffff : 0x0;
- if (state->is_cx25836)
- break;
-
vt->capability |=
V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 |
V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP;
- mode = cx25840_read(client, 0x804);
+ vt->signal = vpres ? 0xffff : 0x0;
/* get rxsubchans and audmode */
if ((mode & 0xf) == 1)
val |= V4L2_TUNER_SUB_MONO;
if (mode == 2 || mode == 4)
- val = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2;
+ val |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2;
if (mode & 0x10)
val |= V4L2_TUNER_SUB_SAP;
vt->rxsubchans = val;
- vt->audmode = state->audmode;
+
+ 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:
- if (state->radio || state->is_cx25836)
- break;
-
switch (vt->audmode) {
case V4L2_TUNER_MODE_MONO:
- /* mono -> mono
- stereo -> mono
- bilingual -> lang1 */
+ case V4L2_TUNER_MODE_LANG1:
+ /* Force PREF_MODE to MONO */
cx25840_and_or(client, 0x809, ~0xf, 0x00);
break;
case V4L2_TUNER_MODE_STEREO:
- case V4L2_TUNER_MODE_LANG1:
- /* mono -> mono
- stereo -> stereo
- bilingual -> lang1 */
+ /* Force PREF_MODE to STEREO */
cx25840_and_or(client, 0x809, ~0xf, 0x04);
break;
- case V4L2_TUNER_MODE_LANG1_LANG2:
- /* mono -> mono
- stereo -> stereo
- bilingual -> lang1/lang2 */
- cx25840_and_or(client, 0x809, ~0xf, 0x07);
- break;
case V4L2_TUNER_MODE_LANG2:
- /* mono -> mono
- stereo -> stereo
- bilingual -> lang2 */
+ /* Force PREF_MODE to LANG2 */
cx25840_and_or(client, 0x809, ~0xf, 0x01);
break;
- default:
- return -EINVAL;
}
- state->audmode = vt->audmode;
break;
case VIDIOC_G_FMT:
return set_v4lfmt(client, (struct v4l2_format *)arg);
case VIDIOC_INT_RESET:
- if (state->is_cx25836)
- cx25836_initialize(client);
- else
- cx25840_initialize(client, 0);
+ cx25840_initialize(client, 0);
break;
case VIDIOC_INT_G_CHIP_IDENT:
- *(enum v4l2_chip_ident *)arg = state->id;
+ *(enum v4l2_chip_ident *)arg =
+ V4L2_IDENT_CX25840 + ((cx25840_read(client, 0x100) >> 4) & 0xf);
break;
default:
{
struct i2c_client *client;
struct cx25840_state *state;
- enum v4l2_chip_ident id;
u16 device_id;
/* Check if the adapter supports the needed features
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
return 0;
- state = kzalloc(sizeof(struct cx25840_state), GFP_KERNEL);
- if (state == 0)
+ client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
+ if (client == 0)
return -ENOMEM;
- client = &state->c;
client->addr = address;
client->adapter = adapter;
client->driver = &i2c_driver_cx25840;
device_id |= cx25840_read(client, 0x100);
/* The high byte of the device ID should be
- * 0x83 for the cx2583x and 0x84 for the cx2584x */
- if ((device_id & 0xff00) == 0x8300) {
- id = V4L2_IDENT_CX25836 + ((device_id >> 4) & 0xf) - 6;
- state->is_cx25836 = 1;
- }
- else if ((device_id & 0xff00) == 0x8400) {
- id = V4L2_IDENT_CX25840 + ((device_id >> 4) & 0xf);
- state->is_cx25836 = 0;
- }
- else {
+ * 0x84 if chip is present */
+ if ((device_id & 0xff00) != 0x8400) {
v4l_dbg(1, cx25840_debug, client, "cx25840 not found\n");
- kfree(state);
+ kfree(client);
return 0;
}
(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->vid_input = CX25840_COMPOSITE7;
state->aud_input = CX25840_AUDIO8;
state->audclk_freq = 48000;
state->pvr150_workaround = 0;
- state->audmode = V4L2_TUNER_MODE_LANG1;
- state->vbi_line_offset = 8;
- state->id = id;
- if (state->is_cx25836)
- cx25836_initialize(client);
- else
- cx25840_initialize(client, 1);
+ cx25840_initialize(client, 1);
i2c_attach_client(client);
}
kfree(state);
+ kfree(client);
return 0;
}
/* ----------------------------------------------------------------------- */
-static void log_video_status(struct i2c_client *client)
+static void log_status(struct i2c_client *client)
{
static const char *const fmt_strs[] = {
"0x0",
};
struct cx25840_state *state = i2c_get_clientdata(client);
+ u8 microctrl_vidfmt = cx25840_read(client, 0x80a);
u8 vidfmt_sel = cx25840_read(client, 0x400) & 0xf;
u8 gen_stat1 = cx25840_read(client, 0x40d);
- u8 gen_stat2 = cx25840_read(client, 0x40e);
- int vid_input = state->vid_input;
-
- v4l_info(client, "Video signal: %spresent\n",
- (gen_stat2 & 0x20) ? "" : "not ");
- v4l_info(client, "Detected format: %s\n",
- fmt_strs[gen_stat1 & 0xf]);
-
- v4l_info(client, "Specified standard: %s\n",
- vidfmt_sel ? fmt_strs[vidfmt_sel] : "automatic detection");
-
- if (vid_input >= CX25840_COMPOSITE1 &&
- vid_input <= CX25840_COMPOSITE8) {
- v4l_info(client, "Specified video input: Composite %d\n",
- vid_input - CX25840_COMPOSITE1 + 1);
- } else {
- v4l_info(client, "Specified video input: S-Video (Luma In%d, Chroma In%d)\n",
- (vid_input & 0xf0) >> 4, (vid_input & 0xf00) >> 8);
- }
-
- v4l_info(client, "Specified audioclock freq: %d Hz\n", state->audclk_freq);
-}
-
-/* ----------------------------------------------------------------------- */
-
-static void log_audio_status(struct i2c_client *client)
-{
- struct cx25840_state *state = i2c_get_clientdata(client);
u8 download_ctl = cx25840_read(client, 0x803);
u8 mod_det_stat0 = cx25840_read(client, 0x804);
u8 mod_det_stat1 = cx25840_read(client, 0x805);
u8 pref_mode = cx25840_read(client, 0x809);
u8 afc0 = cx25840_read(client, 0x80b);
u8 mute_ctl = cx25840_read(client, 0x8d3);
+ int vid_input = state->vid_input;
int aud_input = state->aud_input;
char *p;
+ v4l_info(client, "Video signal: %spresent\n",
+ (microctrl_vidfmt & 0x10) ? "" : "not ");
+ v4l_info(client, "Detected format: %s\n",
+ fmt_strs[gen_stat1 & 0xf]);
+
switch (mod_det_stat0) {
case 0x00: p = "mono"; break;
case 0x01: p = "stereo"; break;
v4l_info(client, "Configured audio system: %s\n", p);
}
+ v4l_info(client, "Specified standard: %s\n",
+ vidfmt_sel ? fmt_strs[vidfmt_sel] : "automatic detection");
+
+ if (vid_input >= CX25840_COMPOSITE1 &&
+ vid_input <= CX25840_COMPOSITE8) {
+ v4l_info(client, "Specified video input: Composite %d\n",
+ vid_input - CX25840_COMPOSITE1 + 1);
+ } else {
+ v4l_info(client, "Specified video input: S-Video (Luma In%d, Chroma In%d)\n",
+ (vid_input & 0xf0) >> 4, (vid_input & 0xf00) >> 8);
+ }
if (aud_input) {
v4l_info(client, "Specified audio input: Tuner (In%d)\n", aud_input);
} else {
v4l_info(client, "Specified audio input: External\n");
}
+ v4l_info(client, "Specified audioclock freq: %d Hz\n", state->audclk_freq);
+
switch (pref_mode & 0xf) {
case 0: p = "mono/language A"; break;
case 1: p = "language B"; break;