V4L/DVB (7002): cx25840: Add basic CX23885 AVCore support
The cx23885/7/8 PCIe bridge has an internal AVCore modelled on
the cx2584x family. Many of the registers positions are identical
but some moved. The register values are also different because
the different bridges run at different clock rates.
Signed-off-by: Steven Toth <stoth@hauppauge.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
diff --git a/drivers/media/video/cx25840/cx25840-audio.c b/drivers/media/video/cx25840/cx25840-audio.c
index 51fc0af..d6421e1 100644
--- a/drivers/media/video/cx25840/cx25840-audio.c
+++ b/drivers/media/video/cx25840/cx25840-audio.c
@@ -32,11 +32,17 @@
/* common for all inputs and rates */
/* SA_MCLK_SEL=1, SA_MCLK_DIV=0x10 */
- cx25840_write(client, 0x127, 0x50);
+ if (!state->is_cx23885)
+ cx25840_write(client, 0x127, 0x50);
if (state->aud_input != CX25840_AUDIO_SERIAL) {
switch (freq) {
case 32000:
+ if (state->is_cx23885) {
+ /* We don't have register values
+ * so avoid destroying registers. */
+ break;
+ }
/* VID_PLL and AUX_PLL */
cx25840_write4(client, 0x108, 0x1006040f);
@@ -53,6 +59,11 @@
break;
case 44100:
+ if (state->is_cx23885) {
+ /* We don't have register values
+ * so avoid destroying registers. */
+ break;
+ }
/* VID_PLL and AUX_PLL */
cx25840_write4(client, 0x108, 0x1009040f);
@@ -69,6 +80,11 @@
break;
case 48000:
+ if (state->is_cx23885) {
+ /* We don't have register values
+ * so avoid destroying registers. */
+ break;
+ }
/* VID_PLL and AUX_PLL */
cx25840_write4(client, 0x108, 0x100a040f);
@@ -87,6 +103,11 @@
} else {
switch (freq) {
case 32000:
+ if (state->is_cx23885) {
+ /* We don't have register values
+ * so avoid destroying registers. */
+ break;
+ }
/* VID_PLL and AUX_PLL */
cx25840_write4(client, 0x108, 0x1e08040f);
@@ -109,6 +130,12 @@
break;
case 44100:
+ if (state->is_cx23885) {
+ /* We don't have register values
+ * so avoid destroying registers. */
+ break;
+ }
+
/* VID_PLL and AUX_PLL */
cx25840_write4(client, 0x108, 0x1809040f);
@@ -128,22 +155,33 @@
break;
case 48000:
- /* VID_PLL and AUX_PLL */
- cx25840_write4(client, 0x108, 0x180a040f);
+ if (!state->is_cx23885) {
+ /* VID_PLL and AUX_PLL */
+ cx25840_write4(client, 0x108, 0x180a040f);
- /* AUX_PLL_FRAC */
- cx25840_write4(client, 0x110, 0x0098d6e5);
+ /* AUX_PLL_FRAC */
+ cx25840_write4(client, 0x110, 0x0098d6e5);
+ }
if (state->is_cx25836)
break;
- /* src1_ctl = 0x08010000 */
- cx25840_write4(client, 0x8f8, 0x08018000);
+ if (!state->is_cx23885) {
+ /* src1_ctl */
+ cx25840_write4(client, 0x8f8, 0x08018000);
- /* src3/4/6_ctl = 0x08020000 */
- cx25840_write4(client, 0x900, 0x08015555);
- cx25840_write4(client, 0x904, 0x08015555);
- cx25840_write4(client, 0x90c, 0x08015555);
+ /* src3/4/6_ctl */
+ cx25840_write4(client, 0x900, 0x08015555);
+ cx25840_write4(client, 0x904, 0x08015555);
+ cx25840_write4(client, 0x90c, 0x08015555);
+ } else {
+
+ cx25840_write4(client, 0x8f8, 0x0801867c);
+
+ cx25840_write4(client, 0x900, 0x08014faa);
+ cx25840_write4(client, 0x904, 0x08014faa);
+ cx25840_write4(client, 0x90c, 0x08014faa);
+ }
break;
}
}
@@ -188,6 +226,11 @@
/* deassert soft reset */
cx25840_and_or(client, 0x810, ~0x1, 0x00);
+
+ if (state->is_cx23885) {
+ /* Ensure the controller is running when we exit */
+ cx25840_and_or(client, 0x803, ~0x10, 0x10);
+ }
}
static int get_volume(struct i2c_client *client)
diff --git a/drivers/media/video/cx25840/cx25840-core.c b/drivers/media/video/cx25840/cx25840-core.c
index 0d3d24a..756a1ee 100644
--- a/drivers/media/video/cx25840/cx25840-core.c
+++ b/drivers/media/video/cx25840/cx25840-core.c
@@ -13,6 +13,8 @@
* NTSC sliced VBI support by Christopher Neufeld <television@cneufeld.ca>
* with additional fixes by Hans Verkuil <hverkuil@xs4all.nl>.
*
+ * CX23885 support by Steven Toth <stoth@hauppauge.com>.
+ *
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
@@ -255,6 +257,96 @@
cx25840_and_or(client, 0x803, ~0x10, 0x10);
}
+static void cx23885_initialize(struct i2c_client *client)
+{
+ DEFINE_WAIT(wait);
+ struct cx25840_state *state = i2c_get_clientdata(client);
+ struct workqueue_struct *q;
+
+ /* Internal Reset */
+ cx25840_and_or(client, 0x102, ~0x01, 0x01);
+ cx25840_and_or(client, 0x102, ~0x01, 0x00);
+
+ /* Stop microcontroller */
+ cx25840_and_or(client, 0x803, ~0x10, 0x00);
+
+ /* DIF in reset? */
+ cx25840_write(client, 0x398, 0);
+
+ /* Trust the default xtal, no division */
+ /* This changes for the cx23888 products */
+ cx25840_write(client, 0x2, 0x76);
+
+ /* Bring down the regulator for AUX clk */
+ cx25840_write(client, 0x1, 0x40);
+
+ /* Sys PLL frac */
+ cx25840_write4(client, 0x11c, 0x01d1744c);
+
+ /* Sys PLL int */
+ cx25840_write4(client, 0x118, 0x00000416);
+
+ /* Disable DIF bypass */
+ cx25840_write4(client, 0x33c, 0x00000001);
+
+ /* DIF Src phase inc */
+ cx25840_write4(client, 0x340, 0x0df7df83);
+
+ /* Vid PLL frac */
+ cx25840_write4(client, 0x10c, 0x01b6db7b);
+
+ /* Vid PLL int */
+ cx25840_write4(client, 0x108, 0x00000512);
+
+ /* Luma */
+ cx25840_write4(client, 0x414, 0x00107d12);
+
+ /* Chroma */
+ cx25840_write4(client, 0x420, 0x3d008282);
+
+ /* Aux PLL frac */
+ cx25840_write4(client, 0x114, 0x017dbf48);
+
+ /* Aux PLL int */
+ cx25840_write4(client, 0x110, 0x000a030e);
+
+ /* ADC2 input select */
+ cx25840_write(client, 0x102, 0x10);
+
+ /* VIN1 & VIN5 */
+ cx25840_write(client, 0x103, 0x11);
+
+ /* Enable format auto detect */
+ cx25840_write(client, 0x400, 0);
+ /* Fast subchroma lock */
+ /* White crush, Chroma AGC & Chroma Killer enabled */
+ cx25840_write(client, 0x401, 0xe8);
+
+ /* Select AFE clock pad output source */
+ cx25840_write(client, 0x144, 0x05);
+
+ /* Do the firmware load in a work handler to prevent.
+ Otherwise the kernel is blocked waiting for the
+ bit-banging i2c interface to finish uploading the
+ firmware. */
+ INIT_WORK(&state->fw_work, cx25840_work_handler);
+ init_waitqueue_head(&state->fw_wait);
+ q = create_singlethread_workqueue("cx25840_fw");
+ prepare_to_wait(&state->fw_wait, &wait, TASK_UNINTERRUPTIBLE);
+ queue_work(q, &state->fw_work);
+ schedule();
+ finish_wait(&state->fw_wait, &wait);
+ destroy_workqueue(q);
+
+ cx25840_vbi_setup(client);
+
+ /* (re)set input */
+ set_input(client, state->vid_input, state->aud_input);
+
+ /* start microcontroller */
+ cx25840_and_or(client, 0x803, ~0x10, 0x10);
+}
+
/* ----------------------------------------------------------------------- */
static void input_change(struct i2c_client *client)
@@ -318,9 +410,22 @@
vid_input <= CX25840_COMPOSITE8);
u8 reg;
- v4l_dbg(1, cx25840_debug, client, "decoder set video input %d, audio input %d\n",
- vid_input, aud_input);
+ v4l_dbg(1, cx25840_debug, client,
+ "decoder set video input %d, audio input %d\n",
+ vid_input, aud_input);
+ if (vid_input >= CX25840_VIN1_CH1) {
+ v4l_dbg(1, cx25840_debug, client, "vid_input 0x%x\n",
+ vid_input);
+ reg = vid_input & 0xff;
+ if ((vid_input & CX25840_SVIDEO_ON) == CX25840_SVIDEO_ON)
+ is_composite = 0;
+ else
+ is_composite = 1;
+
+ v4l_dbg(1, cx25840_debug, client, "mux cfg 0x%x comp=%d\n",
+ reg, is_composite);
+ } else
if (is_composite) {
reg = 0xf0 + (vid_input - CX25840_COMPOSITE1);
} else {
@@ -330,7 +435,8 @@
if ((vid_input & ~0xff0) ||
luma < CX25840_SVIDEO_LUMA1 || luma > CX25840_SVIDEO_LUMA4 ||
chroma < CX25840_SVIDEO_CHROMA4 || chroma > CX25840_SVIDEO_CHROMA8) {
- v4l_err(client, "0x%04x is not a valid video input!\n", vid_input);
+ v4l_err(client, "0x%04x is not a valid video input!\n",
+ vid_input);
return -EINVAL;
}
reg = 0xf0 + ((luma - CX25840_SVIDEO_LUMA1) >> 4);
@@ -343,31 +449,49 @@
}
}
- switch (aud_input) {
- case CX25840_AUDIO_SERIAL:
- /* do nothing, use serial audio input */
- break;
- case CX25840_AUDIO4: reg &= ~0x30; break;
- case CX25840_AUDIO5: reg &= ~0x30; reg |= 0x10; break;
- case CX25840_AUDIO6: reg &= ~0x30; reg |= 0x20; break;
- case CX25840_AUDIO7: reg &= ~0xc0; break;
- case CX25840_AUDIO8: reg &= ~0xc0; reg |= 0x40; break;
+ /* The caller has previously prepared the correct routing
+ * configuration in reg (for the cx23885) so we have no
+ * need to attempt to flip bits for earlier av decoders.
+ */
+ if (!state->is_cx23885) {
+ switch (aud_input) {
+ case CX25840_AUDIO_SERIAL:
+ /* do nothing, use serial audio input */
+ break;
+ case CX25840_AUDIO4: reg &= ~0x30; break;
+ case CX25840_AUDIO5: reg &= ~0x30; reg |= 0x10; break;
+ case CX25840_AUDIO6: reg &= ~0x30; reg |= 0x20; break;
+ case CX25840_AUDIO7: reg &= ~0xc0; break;
+ case CX25840_AUDIO8: reg &= ~0xc0; reg |= 0x40; break;
- default:
- v4l_err(client, "0x%04x is not a valid audio input!\n", aud_input);
- return -EINVAL;
+ default:
+ v4l_err(client, "0x%04x is not a valid audio input!\n",
+ aud_input);
+ return -EINVAL;
+ }
}
cx25840_write(client, 0x103, reg);
+
/* Set INPUT_MODE to Composite (0) or S-Video (1) */
cx25840_and_or(client, 0x401, ~0x6, is_composite ? 0 : 0x02);
- /* Set CH_SEL_ADC2 to 1 if input comes from CH3 */
- cx25840_and_or(client, 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)
- cx25840_and_or(client, 0x102, ~0x4, 4);
- else
- cx25840_and_or(client, 0x102, ~0x4, 0);
+
+ if (!state->is_cx23885) {
+ /* Set CH_SEL_ADC2 to 1 if input comes from CH3 */
+ cx25840_and_or(client, 0x102, ~0x2, (reg & 0x80) == 0 ? 2 : 0);
+ /* Set DUAL_MODE_ADC2 to 1 if input comes from both CH2&CH3 */
+ if ((reg & 0xc0) != 0xc0 && (reg & 0x30) != 0x30)
+ cx25840_and_or(client, 0x102, ~0x4, 4);
+ else
+ cx25840_and_or(client, 0x102, ~0x4, 0);
+ } else {
+ if (is_composite)
+ /* ADC2 input select channel 2 */
+ cx25840_and_or(client, 0x102, ~0x2, 0);
+ else
+ /* ADC2 input select channel 3 */
+ cx25840_and_or(client, 0x102, ~0x2, 2);
+ }
state->vid_input = vid_input;
state->aud_input = aud_input;
@@ -375,6 +499,25 @@
cx25840_audio_set_path(client);
input_change(client);
}
+
+ if (state->is_cx23885) {
+ /* Audio channel 1 src : Parallel 1 */
+ cx25840_write(client, 0x124, 0x03);
+
+ /* Select AFE clock pad output source */
+ cx25840_write(client, 0x144, 0x05);
+
+ /* I2S_IN_CTL: I2S_IN_SONY_MODE, LEFT SAMPLE on WS=1 */
+ cx25840_write(client, 0x914, 0xa0);
+
+ /* I2S_OUT_CTL:
+ * I2S_IN_SONY_MODE, LEFT SAMPLE on WS=1
+ * I2S_OUT_MASTER_MODE = Master
+ */
+ cx25840_write(client, 0x918, 0xa0);
+ cx25840_write(client, 0x919, 0x01);
+ }
+
return 0;
}
@@ -853,6 +996,8 @@
state->is_initialized = 1;
if (state->is_cx25836)
cx25836_initialize(client);
+ else if (state->is_cx23885)
+ cx23885_initialize(client);
else
cx25840_initialize(client);
}
@@ -870,6 +1015,7 @@
return -EINVAL;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
+
if (cmd == VIDIOC_DBG_G_REGISTER)
reg->val = cx25840_read(client, reg->reg & 0x0fff);
else
@@ -886,14 +1032,26 @@
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);
+ if (state->is_cx23885) {
+ u8 v = (cx25840_read(client, 0x421) | 0x0b);
+ cx25840_write(client, 0x421, v);
+ } else {
+ cx25840_write(client, 0x115,
+ state->is_cx25836 ? 0x0c : 0x8c);
+ cx25840_write(client, 0x116,
+ state->is_cx25836 ? 0x04 : 0x07);
+ }
break;
case VIDIOC_STREAMOFF:
v4l_dbg(1, cx25840_debug, client, "disable output\n");
- cx25840_write(client, 0x115, 0x00);
- cx25840_write(client, 0x116, 0x00);
+ if (state->is_cx23885) {
+ u8 v = cx25840_read(client, 0x421) & ~(0x0b);
+ cx25840_write(client, 0x421, v);
+ } else {
+ cx25840_write(client, 0x115, 0x00);
+ cx25840_write(client, 0x116, 0x00);
+ }
break;
case VIDIOC_LOG_STATUS:
@@ -1056,6 +1214,8 @@
case VIDIOC_INT_RESET:
if (state->is_cx25836)
cx25836_initialize(client);
+ else if (state->is_cx23885)
+ cx23885_initialize(client);
else
cx25840_initialize(client);
break;
@@ -1086,6 +1246,7 @@
device_id = cx25840_read(client, 0x101) << 8;
device_id |= cx25840_read(client, 0x100);
+ v4l_dbg(1, cx25840_debug, client, "device_id = 0x%04x\n", device_id);
/* The high byte of the device ID should be
* 0x83 for the cx2583x and 0x84 for the cx2584x */
@@ -1094,6 +1255,10 @@
}
else if ((device_id & 0xff00) == 0x8400) {
id = V4L2_IDENT_CX25840 + ((device_id >> 4) & 0xf);
+ } else if (device_id == 0x0000) {
+ id = V4L2_IDENT_CX25836 + ((device_id >> 4) & 0xf) - 6;
+ } else if (device_id == 0x1313) {
+ id = V4L2_IDENT_CX25836 + ((device_id >> 4) & 0xf) - 6;
}
else {
v4l_dbg(1, cx25840_debug, client, "cx25840 not found\n");
@@ -1115,6 +1280,7 @@
i2c_set_clientdata(client, state);
state->c = client;
state->is_cx25836 = ((device_id & 0xff00) == 0x8300);
+ state->is_cx23885 = (device_id == 0x0000) || (device_id == 0x1313);
state->vid_input = CX25840_COMPOSITE7;
state->aud_input = CX25840_AUDIO8;
state->audclk_freq = 48000;
@@ -1124,6 +1290,7 @@
state->vbi_line_offset = 8;
state->id = id;
state->rev = device_id;
+
return 0;
}
diff --git a/drivers/media/video/cx25840/cx25840-core.h b/drivers/media/video/cx25840/cx25840-core.h
index ea669b1..95093ed 100644
--- a/drivers/media/video/cx25840/cx25840-core.h
+++ b/drivers/media/video/cx25840/cx25840-core.h
@@ -47,6 +47,7 @@
u32 id;
u32 rev;
int is_cx25836;
+ int is_cx23885;
int is_initialized;
wait_queue_head_t fw_wait; /* wake up when the fw load is finished */
struct work_struct fw_work; /* work entry for fw load */
diff --git a/drivers/media/video/cx25840/cx25840-firmware.c b/drivers/media/video/cx25840/cx25840-firmware.c
index e852024..1ddf724 100644
--- a/drivers/media/video/cx25840/cx25840-firmware.c
+++ b/drivers/media/video/cx25840/cx25840-firmware.c
@@ -24,6 +24,7 @@
#include "cx25840-core.h"
#define FWFILE "v4l-cx25840.fw"
+#define FWFILE_CX23885 "v4l-cx23885-avcore-01.fw"
/*
* Mike Isely <isely@pobox.com> - The FWSEND parameter controls the
@@ -92,10 +93,14 @@
int cx25840_loadfw(struct i2c_client *client)
{
+ struct cx25840_state *state = i2c_get_clientdata(client);
const struct firmware *fw = NULL;
u8 buffer[4], *ptr;
int size, send, retval;
+ if (state->is_cx23885)
+ firmware = FWFILE_CX23885;
+
if (request_firmware(&fw, firmware, FWDEV(client)) != 0) {
v4l_err(client, "unable to open firmware %s\n", firmware);
return -EINVAL;
diff --git a/include/media/cx25840.h b/include/media/cx25840.h
index 8e7e52d..cd599ad 100644
--- a/include/media/cx25840.h
+++ b/include/media/cx25840.h
@@ -49,6 +49,25 @@
CX25840_SVIDEO2 = 0x620,
CX25840_SVIDEO3 = 0x730,
CX25840_SVIDEO4 = 0x840,
+
+ /* Allow frames to specify specific input configurations */
+ CX25840_VIN1_CH1 = 0x80000000,
+ CX25840_VIN2_CH1 = 0x80000001,
+ CX25840_VIN3_CH1 = 0x80000002,
+ CX25840_VIN4_CH1 = 0x80000003,
+ CX25840_VIN5_CH1 = 0x80000004,
+ CX25840_VIN6_CH1 = 0x80000005,
+ CX25840_VIN7_CH1 = 0x80000006,
+ CX25840_VIN8_CH1 = 0x80000007,
+ CX25840_VIN4_CH2 = 0x80000000,
+ CX25840_VIN5_CH2 = 0x80000010,
+ CX25840_VIN6_CH2 = 0x80000020,
+ CX25840_NONE_CH2 = 0x80000030,
+ CX25840_VIN7_CH3 = 0x80000000,
+ CX25840_VIN8_CH3 = 0x80000040,
+ CX25840_NONE0_CH3 = 0x80000080,
+ CX25840_NONE1_CH3 = 0x800000c0,
+ CX25840_SVIDEO_ON = 0x80000100,
};
enum cx25840_audio_input {