summaryrefslogtreecommitdiff
path: root/drivers/media/video/cx18
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/video/cx18')
-rw-r--r--drivers/media/video/cx18/cx18-audio.c44
-rw-r--r--drivers/media/video/cx18/cx18-av-core.c234
-rw-r--r--drivers/media/video/cx18/cx18-av-firmware.c82
-rw-r--r--drivers/media/video/cx18/cx18-controls.c6
-rw-r--r--drivers/media/video/cx18/cx18-driver.c97
-rw-r--r--drivers/media/video/cx18/cx18-driver.h17
-rw-r--r--drivers/media/video/cx18/cx18-dvb.c1
-rw-r--r--drivers/media/video/cx18/cx18-fileops.c7
-rw-r--r--drivers/media/video/cx18/cx18-mailbox.c114
-rw-r--r--drivers/media/video/cx18/cx18-mailbox.h2
-rw-r--r--drivers/media/video/cx18/cx18-queue.c85
-rw-r--r--drivers/media/video/cx18/cx18-streams.c35
-rw-r--r--drivers/media/video/cx18/cx18-streams.h20
-rw-r--r--drivers/media/video/cx18/cx18-version.h2
14 files changed, 544 insertions, 202 deletions
diff --git a/drivers/media/video/cx18/cx18-audio.c b/drivers/media/video/cx18/cx18-audio.c
index 7a8ad5963de8..35268923911c 100644
--- a/drivers/media/video/cx18/cx18-audio.c
+++ b/drivers/media/video/cx18/cx18-audio.c
@@ -26,14 +26,18 @@
#include "cx18-cards.h"
#include "cx18-audio.h"
-#define CX18_AUDIO_ENABLE 0xc72014
+#define CX18_AUDIO_ENABLE 0xc72014
+#define CX18_AI1_MUX_MASK 0x30
+#define CX18_AI1_MUX_I2S1 0x00
+#define CX18_AI1_MUX_I2S2 0x10
+#define CX18_AI1_MUX_843_I2S 0x20
/* Selects the audio input and output according to the current
settings. */
int cx18_audio_set_io(struct cx18 *cx)
{
const struct cx18_card_audio_input *in;
- u32 val;
+ u32 u, v;
int err;
/* Determine which input to use */
@@ -52,9 +56,37 @@ int cx18_audio_set_io(struct cx18 *cx)
return err;
/* FIXME - this internal mux should be abstracted to a subdev */
- val = cx18_read_reg(cx, CX18_AUDIO_ENABLE) & ~0x30;
- val |= (in->audio_input > CX18_AV_AUDIO_SERIAL2) ? 0x20 :
- (in->audio_input << 4);
- cx18_write_reg_expect(cx, val | 0xb00, CX18_AUDIO_ENABLE, val, 0x30);
+ u = cx18_read_reg(cx, CX18_AUDIO_ENABLE);
+ v = u & ~CX18_AI1_MUX_MASK;
+ switch (in->audio_input) {
+ case CX18_AV_AUDIO_SERIAL1:
+ v |= CX18_AI1_MUX_I2S1;
+ break;
+ case CX18_AV_AUDIO_SERIAL2:
+ v |= CX18_AI1_MUX_I2S2;
+ break;
+ default:
+ v |= CX18_AI1_MUX_843_I2S;
+ break;
+ }
+ if (v == u) {
+ /* force a toggle of some AI1 MUX control bits */
+ u &= ~CX18_AI1_MUX_MASK;
+ switch (in->audio_input) {
+ case CX18_AV_AUDIO_SERIAL1:
+ u |= CX18_AI1_MUX_843_I2S;
+ break;
+ case CX18_AV_AUDIO_SERIAL2:
+ u |= CX18_AI1_MUX_843_I2S;
+ break;
+ default:
+ u |= CX18_AI1_MUX_I2S1;
+ break;
+ }
+ cx18_write_reg_expect(cx, u | 0xb00, CX18_AUDIO_ENABLE,
+ u, CX18_AI1_MUX_MASK);
+ }
+ cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE,
+ v, CX18_AI1_MUX_MASK);
return 0;
}
diff --git a/drivers/media/video/cx18/cx18-av-core.c b/drivers/media/video/cx18/cx18-av-core.c
index cf2bd888a429..2b07b156340b 100644
--- a/drivers/media/video/cx18/cx18-av-core.c
+++ b/drivers/media/video/cx18/cx18-av-core.c
@@ -99,9 +99,39 @@ int cx18_av_and_or4(struct cx18 *cx, u16 addr, u32 and_mask,
or_value);
}
-static void cx18_av_initialize(struct cx18 *cx)
+static int cx18_av_init(struct v4l2_subdev *sd, u32 val)
{
- struct cx18_av_state *state = &cx->av_state;
+ struct cx18 *cx = v4l2_get_subdevdata(sd);
+
+ /*
+ * The crystal freq used in calculations in this driver will be
+ * 28.636360 MHz.
+ * Aim to run the PLLs' VCOs near 400 MHz to minimze errors.
+ */
+
+ /*
+ * VDCLK Integer = 0x0f, Post Divider = 0x04
+ * AIMCLK Integer = 0x0e, Post Divider = 0x16
+ */
+ cx18_av_write4(cx, CXADEC_PLL_CTRL1, 0x160e040f);
+
+ /* VDCLK Fraction = 0x2be2fe */
+ /* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz before post divide */
+ cx18_av_write4(cx, CXADEC_VID_PLL_FRAC, 0x002be2fe);
+
+ /* AIMCLK Fraction = 0x05227ad */
+ /* xtal * 0xe.2913d68/0x16 = 48000 * 384: 406 MHz pre post-div*/
+ cx18_av_write4(cx, CXADEC_AUX_PLL_FRAC, 0x005227ad);
+
+ /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x16 */
+ cx18_av_write(cx, CXADEC_I2S_MCLK, 0x56);
+ return 0;
+}
+
+static void cx18_av_initialize(struct v4l2_subdev *sd)
+{
+ struct cx18_av_state *state = to_cx18_av_state(sd);
+ struct cx18 *cx = v4l2_get_subdevdata(sd);
u32 v;
cx18_av_loadfw(cx);
@@ -150,6 +180,26 @@ static void cx18_av_initialize(struct cx18 *cx)
cx18_av_write4(cx, CXADEC_SOFT_RST_CTRL, 0x8000);
cx18_av_write4(cx, CXADEC_SOFT_RST_CTRL, 0);
+ /*
+ * Disable Video Auto-config of the Analog Front End and Video PLL.
+ *
+ * Since we only use BT.656 pixel mode, which works for both 525 and 625
+ * line systems, it's just easier for us to set registers
+ * 0x102 (CXADEC_CHIP_CTRL), 0x104-0x106 (CXADEC_AFE_CTRL),
+ * 0x108-0x109 (CXADEC_PLL_CTRL1), and 0x10c-0x10f (CXADEC_VID_PLL_FRAC)
+ * ourselves, than to run around cleaning up after the auto-config.
+ *
+ * (Note: my CX23418 chip doesn't seem to let the ACFG_DIS bit
+ * get set to 1, but OTOH, it doesn't seem to do AFE and VID PLL
+ * autoconfig either.)
+ *
+ * As a default, also turn off Dual mode for ADC2 and set ADC2 to CH3.
+ */
+ cx18_av_and_or4(cx, CXADEC_CHIP_CTRL, 0xFFFBFFFF, 0x00120000);
+
+ /* Setup the Video and and Aux/Audio PLLs */
+ cx18_av_init(sd, 0);
+
/* set video to auto-detect */
/* Clear bits 11-12 to enable slow locking mode. Set autodetect mode */
/* set the comb notch = 1 */
@@ -176,12 +226,23 @@ static void cx18_av_initialize(struct cx18 *cx)
/* 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);
+ /*
+ * Analog Front End (AFE)
+ * Default to luma on ch1/ADC1, chroma on ch2/ADC2, SIF on ch3/ADC2
+ * bypass_ch[1-3] use filter
+ * droop_comp_ch[1-3] disable
+ * clamp_en_ch[1-3] disable
+ * aud_in_sel ADC2
+ * luma_in_sel ADC1
+ * chroma_in_sel ADC2
+ * clamp_sel_ch[2-3] midcode
+ * clamp_sel_ch1 video decoder
+ * vga_sel_ch3 audio decoder
+ * vga_sel_ch[1-2] video decoder
+ * half_bw_ch[1-3] disable
+ * +12db_ch[1-3] disable
+ */
+ cx18_av_and_or4(cx, CXADEC_AFE_CTRL, 0xFF000000, 0x00005D00);
/* if(dwEnable && dw3DCombAvailable) { */
/* CxDevWrReg(CXADEC_SRC_COMB_CFG, 0x7728021F); */
@@ -195,50 +256,18 @@ static void cx18_av_initialize(struct cx18 *cx)
static int cx18_av_reset(struct v4l2_subdev *sd, u32 val)
{
- struct cx18 *cx = v4l2_get_subdevdata(sd);
-
- cx18_av_initialize(cx);
- return 0;
-}
-
-static int cx18_av_init(struct v4l2_subdev *sd, u32 val)
-{
- struct cx18 *cx = v4l2_get_subdevdata(sd);
-
- /*
- * The crystal freq used in calculations in this driver will be
- * 28.636360 MHz.
- * Aim to run the PLLs' VCOs near 400 MHz to minimze errors.
- */
-
- /*
- * VDCLK Integer = 0x0f, Post Divider = 0x04
- * AIMCLK Integer = 0x0e, Post Divider = 0x16
- */
- cx18_av_write4(cx, CXADEC_PLL_CTRL1, 0x160e040f);
-
- /* VDCLK Fraction = 0x2be2fe */
- /* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz before post divide */
- cx18_av_write4(cx, CXADEC_VID_PLL_FRAC, 0x002be2fe);
-
- /* AIMCLK Fraction = 0x05227ad */
- /* xtal * 0xe.2913d68/0x16 = 48000 * 384: 406 MHz pre post-div*/
- cx18_av_write4(cx, CXADEC_AUX_PLL_FRAC, 0x005227ad);
-
- /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x16 */
- cx18_av_write(cx, CXADEC_I2S_MCLK, 0x56);
+ cx18_av_initialize(sd);
return 0;
}
static int cx18_av_load_fw(struct v4l2_subdev *sd)
{
struct cx18_av_state *state = to_cx18_av_state(sd);
- struct cx18 *cx = v4l2_get_subdevdata(sd);
if (!state->is_initialized) {
/* initialize on first use */
state->is_initialized = 1;
- cx18_av_initialize(cx);
+ cx18_av_initialize(sd);
}
return 0;
}
@@ -470,16 +499,23 @@ static int set_input(struct cx18 *cx, enum cx18_av_video_input vid_input,
{
struct cx18_av_state *state = &cx->av_state;
struct v4l2_subdev *sd = &state->sd;
- u8 is_composite = (vid_input >= CX18_AV_COMPOSITE1 &&
- vid_input <= CX18_AV_COMPOSITE8);
- u8 reg;
- u8 v;
+
+ enum analog_signal_type {
+ NONE, CVBS, Y, C, SIF, Pb, Pr
+ } ch[3] = {NONE, NONE, NONE};
+
+ u8 afe_mux_cfg;
+ u8 adc2_cfg;
+ u32 afe_cfg;
+ int i;
CX18_DEBUG_INFO_DEV(sd, "decoder set video input %d, audio input %d\n",
vid_input, aud_input);
- if (is_composite) {
- reg = 0xf0 + (vid_input - CX18_AV_COMPOSITE1);
+ if (vid_input >= CX18_AV_COMPOSITE1 &&
+ vid_input <= CX18_AV_COMPOSITE8) {
+ afe_mux_cfg = 0xf0 + (vid_input - CX18_AV_COMPOSITE1);
+ ch[0] = CVBS;
} else {
int luma = vid_input & 0xf0;
int chroma = vid_input & 0xf00;
@@ -493,26 +529,45 @@ static int set_input(struct cx18 *cx, enum cx18_av_video_input vid_input,
vid_input);
return -EINVAL;
}
- reg = 0xf0 + ((luma - CX18_AV_SVIDEO_LUMA1) >> 4);
+ afe_mux_cfg = 0xf0 + ((luma - CX18_AV_SVIDEO_LUMA1) >> 4);
+ ch[0] = Y;
if (chroma >= CX18_AV_SVIDEO_CHROMA7) {
- reg &= 0x3f;
- reg |= (chroma - CX18_AV_SVIDEO_CHROMA7) >> 2;
+ afe_mux_cfg &= 0x3f;
+ afe_mux_cfg |= (chroma - CX18_AV_SVIDEO_CHROMA7) >> 2;
+ ch[2] = C;
} else {
- reg &= 0xcf;
- reg |= (chroma - CX18_AV_SVIDEO_CHROMA4) >> 4;
+ afe_mux_cfg &= 0xcf;
+ afe_mux_cfg |= (chroma - CX18_AV_SVIDEO_CHROMA4) >> 4;
+ ch[1] = C;
}
}
+ /* TODO: LeadTek WinFast DVR3100 H & WinFast PVR2100 can do Y/Pb/Pr */
switch (aud_input) {
case CX18_AV_AUDIO_SERIAL1:
case CX18_AV_AUDIO_SERIAL2:
/* 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;
+ case CX18_AV_AUDIO4:
+ afe_mux_cfg &= ~0x30;
+ ch[1] = SIF;
+ break;
+ case CX18_AV_AUDIO5:
+ afe_mux_cfg = (afe_mux_cfg & ~0x30) | 0x10;
+ ch[1] = SIF;
+ break;
+ case CX18_AV_AUDIO6:
+ afe_mux_cfg = (afe_mux_cfg & ~0x30) | 0x20;
+ ch[1] = SIF;
+ break;
+ case CX18_AV_AUDIO7:
+ afe_mux_cfg &= ~0xc0;
+ ch[2] = SIF;
+ break;
+ case CX18_AV_AUDIO8:
+ afe_mux_cfg = (afe_mux_cfg & ~0xc0) | 0x40;
+ ch[2] = SIF;
+ break;
default:
CX18_ERR_DEV(sd, "0x%04x is not a valid audio input!\n",
@@ -520,24 +575,65 @@ static int set_input(struct cx18 *cx, enum cx18_av_video_input vid_input,
return -EINVAL;
}
- cx18_av_write_expect(cx, 0x103, reg, reg, 0xf7);
+ /* Set up analog front end multiplexers */
+ cx18_av_write_expect(cx, 0x103, afe_mux_cfg, afe_mux_cfg, 0xf7);
/* Set INPUT_MODE to Composite (0) or S-Video (1) */
- cx18_av_and_or(cx, 0x401, ~0x6, is_composite ? 0 : 0x02);
+ cx18_av_and_or(cx, 0x401, ~0x6, ch[0] == CVBS ? 0 : 0x02);
/* Set CH_SEL_ADC2 to 1 if input comes from CH3 */
- v = cx18_av_read(cx, 0x102);
- if (reg & 0x80)
- v &= ~0x2;
+ adc2_cfg = cx18_av_read(cx, 0x102);
+ if (ch[2] == NONE)
+ adc2_cfg &= ~0x2; /* No sig on CH3, set ADC2 to CH2 for input */
else
- v |= 0x2;
+ adc2_cfg |= 0x2; /* Signal on CH3, set ADC2 to CH3 for input */
+
/* Set DUAL_MODE_ADC2 to 1 if input comes from both CH2 and CH3 */
- if ((reg & 0xc0) != 0xc0 && (reg & 0x30) != 0x30)
- v |= 0x4;
+ if (ch[1] != NONE && ch[2] != NONE)
+ adc2_cfg |= 0x4; /* Set dual mode */
else
- v &= ~0x4;
- cx18_av_write_expect(cx, 0x102, v, v, 0x17);
+ adc2_cfg &= ~0x4; /* Clear dual mode */
+ cx18_av_write_expect(cx, 0x102, adc2_cfg, adc2_cfg, 0x17);
+
+ /* Configure the analog front end */
+ afe_cfg = cx18_av_read4(cx, CXADEC_AFE_CTRL);
+ afe_cfg &= 0xff000000;
+ afe_cfg |= 0x00005000; /* CHROMA_IN, AUD_IN: ADC2; LUMA_IN: ADC1 */
+ if (ch[1] != NONE && ch[2] != NONE)
+ afe_cfg |= 0x00000030; /* half_bw_ch[2-3] since in dual mode */
+
+ for (i = 0; i < 3; i++) {
+ switch (ch[i]) {
+ default:
+ case NONE:
+ /* CLAMP_SEL = Fixed to midcode clamp level */
+ afe_cfg |= (0x00000200 << i);
+ break;
+ case CVBS:
+ case Y:
+ if (i > 0)
+ afe_cfg |= 0x00002000; /* LUMA_IN_SEL: ADC2 */
+ break;
+ case C:
+ case Pb:
+ case Pr:
+ /* CLAMP_SEL = Fixed to midcode clamp level */
+ afe_cfg |= (0x00000200 << i);
+ if (i == 0 && ch[i] == C)
+ afe_cfg &= ~0x00001000; /* CHROMA_IN_SEL ADC1 */
+ break;
+ case SIF:
+ /*
+ * VGA_GAIN_SEL = Audio Decoder
+ * CLAMP_SEL = Fixed to midcode clamp level
+ */
+ afe_cfg |= (0x00000240 << i);
+ if (i == 0)
+ afe_cfg &= ~0x00004000; /* AUD_IN_SEL ADC1 */
+ break;
+ }
+ }
- /*cx18_av_and_or4(cx, 0x104, ~0x001b4180, 0x00004180);*/
+ cx18_av_write4(cx, CXADEC_AFE_CTRL, afe_cfg);
state->vid_input = vid_input;
state->aud_input = aud_input;
diff --git a/drivers/media/video/cx18/cx18-av-firmware.c b/drivers/media/video/cx18/cx18-av-firmware.c
index 49a55cc8d839..b9e8cc5d264a 100644
--- a/drivers/media/video/cx18/cx18-av-firmware.c
+++ b/drivers/media/video/cx18/cx18-av-firmware.c
@@ -24,15 +24,63 @@
#include "cx18-io.h"
#include <linux/firmware.h>
-#define CX18_AUDIO_ENABLE 0xc72014
+#define CX18_AUDIO_ENABLE 0xc72014
+#define CX18_AI1_MUX_MASK 0x30
+#define CX18_AI1_MUX_I2S1 0x00
+#define CX18_AI1_MUX_I2S2 0x10
+#define CX18_AI1_MUX_843_I2S 0x20
+#define CX18_AI1_MUX_INVALID 0x30
+
#define FWFILE "v4l-cx23418-dig.fw"
+static int cx18_av_verifyfw(struct cx18 *cx, const struct firmware *fw)
+{
+ struct v4l2_subdev *sd = &cx->av_state.sd;
+ int ret = 0;
+ const u8 *data;
+ u32 size;
+ int addr;
+ u32 expected, dl_control;
+
+ /* Ensure we put the 8051 in reset and enable firmware upload mode */
+ dl_control = cx18_av_read4(cx, CXADEC_DL_CTL);
+ do {
+ dl_control &= 0x00ffffff;
+ dl_control |= 0x0f000000;
+ cx18_av_write4_noretry(cx, CXADEC_DL_CTL, dl_control);
+ dl_control = cx18_av_read4(cx, CXADEC_DL_CTL);
+ } while ((dl_control & 0xff000000) != 0x0f000000);
+
+ /* Read and auto increment until at address 0x0000 */
+ while (dl_control & 0x3fff)
+ dl_control = cx18_av_read4(cx, CXADEC_DL_CTL);
+
+ data = fw->data;
+ size = fw->size;
+ for (addr = 0; addr < size; addr++) {
+ dl_control &= 0xffff3fff; /* ignore top 2 bits of address */
+ expected = 0x0f000000 | ((u32)data[addr] << 16) | addr;
+ if (expected != dl_control) {
+ CX18_ERR_DEV(sd, "verification of %s firmware load "
+ "failed: expected %#010x got %#010x\n",
+ FWFILE, expected, dl_control);
+ ret = -EIO;
+ break;
+ }
+ dl_control = cx18_av_read4(cx, CXADEC_DL_CTL);
+ }
+ if (ret == 0)
+ CX18_INFO_DEV(sd, "verified load of %s firmware (%d bytes)\n",
+ FWFILE, size);
+ return ret;
+}
+
int cx18_av_loadfw(struct cx18 *cx)
{
struct v4l2_subdev *sd = &cx->av_state.sd;
const struct firmware *fw = NULL;
u32 size;
- u32 v;
+ u32 u, v;
const u8 *ptr;
int i;
int retries1 = 0;
@@ -95,6 +143,12 @@ int cx18_av_loadfw(struct cx18 *cx)
}
cx18_av_write4_expect(cx, CXADEC_DL_CTL,
+ 0x03000000 | fw->size, 0x03000000, 0x13000000);
+
+ CX18_INFO_DEV(sd, "loaded %s firmware (%d bytes)\n", FWFILE, size);
+
+ if (cx18_av_verifyfw(cx, fw) == 0)
+ cx18_av_write4_expect(cx, CXADEC_DL_CTL,
0x13000000 | fw->size, 0x13000000, 0x13000000);
/* Output to the 416 */
@@ -135,6 +189,28 @@ int cx18_av_loadfw(struct cx18 *cx)
cx18_write_reg_expect(cx, v & 0xFFFFFBFF, CX18_AUDIO_ENABLE,
0, 0x400);
+ /* Toggle the AI1 MUX */
+ v = cx18_read_reg(cx, CX18_AUDIO_ENABLE);
+ u = v & CX18_AI1_MUX_MASK;
+ v &= ~CX18_AI1_MUX_MASK;
+ if (u == CX18_AI1_MUX_843_I2S || u == CX18_AI1_MUX_INVALID) {
+ /* Switch to I2S1 */
+ v |= CX18_AI1_MUX_I2S1;
+ cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE,
+ v, CX18_AI1_MUX_MASK);
+ /* Switch back to the A/V decoder core I2S output */
+ v = (v & ~CX18_AI1_MUX_MASK) | CX18_AI1_MUX_843_I2S;
+ } else {
+ /* Switch to the A/V decoder core I2S output */
+ v |= CX18_AI1_MUX_843_I2S;
+ cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE,
+ v, CX18_AI1_MUX_MASK);
+ /* Switch back to I2S1 or I2S2 */
+ v = (v & ~CX18_AI1_MUX_MASK) | u;
+ }
+ cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE,
+ v, CX18_AI1_MUX_MASK);
+
/* Enable WW auto audio standard detection */
v = cx18_av_read4(cx, CXADEC_STD_DET_CTL);
v |= 0xFF; /* Auto by default */
@@ -143,7 +219,5 @@ int cx18_av_loadfw(struct cx18 *cx)
cx18_av_write4_expect(cx, CXADEC_STD_DET_CTL, v, v, 0x3F00FFFF);
release_firmware(fw);
-
- CX18_INFO_DEV(sd, "loaded %s firmware (%d bytes)\n", FWFILE, size);
return 0;
}
diff --git a/drivers/media/video/cx18/cx18-controls.c b/drivers/media/video/cx18/cx18-controls.c
index 82fc2f9d4021..8e35c3aed544 100644
--- a/drivers/media/video/cx18/cx18-controls.c
+++ b/drivers/media/video/cx18/cx18-controls.c
@@ -176,8 +176,10 @@ static int cx18_setup_vbi_fmt(struct cx18 *cx,
return -EBUSY;
if (fmt != V4L2_MPEG_STREAM_VBI_FMT_IVTV ||
- type != V4L2_MPEG_STREAM_TYPE_MPEG2_PS) {
- /* We don't do VBI insertion aside from IVTV format in a PS */
+ !(type == V4L2_MPEG_STREAM_TYPE_MPEG2_PS ||
+ type == V4L2_MPEG_STREAM_TYPE_MPEG2_DVD ||
+ type == V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD)) {
+ /* Only IVTV fmt VBI insertion & only MPEG-2 PS type streams */
cx->vbi.insert_mpeg = V4L2_MPEG_STREAM_VBI_FMT_NONE;
CX18_DEBUG_INFO("disabled insertion of sliced VBI data into "
"the MPEG stream\n");
diff --git a/drivers/media/video/cx18/cx18-driver.c b/drivers/media/video/cx18/cx18-driver.c
index 49b1c3d7b1a8..bc6a8f9abd3d 100644
--- a/drivers/media/video/cx18/cx18-driver.c
+++ b/drivers/media/video/cx18/cx18-driver.c
@@ -30,6 +30,7 @@
#include "cx18-irq.h"
#include "cx18-gpio.h"
#include "cx18-firmware.h"
+#include "cx18-queue.h"
#include "cx18-streams.h"
#include "cx18-av-core.h"
#include "cx18-scb.h"
@@ -312,7 +313,7 @@ static void cx18_process_eeprom(struct cx18 *cx)
CX18_INFO("Autodetected %s\n", cx->card_name);
if (tv.tuner_type == TUNER_ABSENT)
- CX18_ERR("tveeprom cannot autodetect tuner!");
+ CX18_ERR("tveeprom cannot autodetect tuner!\n");
if (cx->options.tuner == -1)
cx->options.tuner = tv.tuner_type;
@@ -546,6 +547,40 @@ done:
cx->card_i2c = cx->card->i2c;
}
+static int __devinit cx18_create_in_workq(struct cx18 *cx)
+{
+ snprintf(cx->in_workq_name, sizeof(cx->in_workq_name), "%s-in",
+ cx->v4l2_dev.name);
+ cx->in_work_queue = create_singlethread_workqueue(cx->in_workq_name);
+ if (cx->in_work_queue == NULL) {
+ CX18_ERR("Unable to create incoming mailbox handler thread\n");
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static int __devinit cx18_create_out_workq(struct cx18 *cx)
+{
+ snprintf(cx->out_workq_name, sizeof(cx->out_workq_name), "%s-out",
+ cx->v4l2_dev.name);
+ cx->out_work_queue = create_workqueue(cx->out_workq_name);
+ if (cx->out_work_queue == NULL) {
+ CX18_ERR("Unable to create outgoing mailbox handler threads\n");
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void __devinit cx18_init_in_work_orders(struct cx18 *cx)
+{
+ int i;
+ for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++) {
+ cx->in_work_order[i].cx = cx;
+ cx->in_work_order[i].str = cx->epu_debug_str;
+ INIT_WORK(&cx->in_work_order[i].work, cx18_in_work_handler);
+ }
+}
+
/* Precondition: the cx18 structure has been memset to 0. Only
the dev and instance fields have been filled in.
No assumptions on the card type may be made here (see cx18_init_struct2
@@ -553,7 +588,7 @@ done:
*/
static int __devinit cx18_init_struct1(struct cx18 *cx)
{
- int i;
+ int ret;
cx->base_addr = pci_resource_start(cx->pci_dev, 0);
@@ -562,18 +597,18 @@ static int __devinit cx18_init_struct1(struct cx18 *cx)
mutex_init(&cx->epu2apu_mb_lock);
mutex_init(&cx->epu2cpu_mb_lock);
- cx->work_queue = create_singlethread_workqueue(cx->v4l2_dev.name);
- if (cx->work_queue == NULL) {
- CX18_ERR("Unable to create work hander thread\n");
- return -ENOMEM;
- }
+ ret = cx18_create_out_workq(cx);
+ if (ret)
+ return ret;
- for (i = 0; i < CX18_MAX_EPU_WORK_ORDERS; i++) {
- cx->epu_work_order[i].cx = cx;
- cx->epu_work_order[i].str = cx->epu_debug_str;
- INIT_WORK(&cx->epu_work_order[i].work, cx18_epu_work_handler);
+ ret = cx18_create_in_workq(cx);
+ if (ret) {
+ destroy_workqueue(cx->out_work_queue);
+ return ret;
}
+ cx18_init_in_work_orders(cx);
+
/* start counting open_id at 1 */
cx->open_id = 1;
@@ -759,17 +794,17 @@ static int __devinit cx18_probe(struct pci_dev *pci_dev,
retval = -ENODEV;
goto err;
}
- if (cx18_init_struct1(cx)) {
- retval = -ENOMEM;
+
+ retval = cx18_init_struct1(cx);
+ if (retval)
goto err;
- }
CX18_DEBUG_INFO("base addr: 0x%08x\n", cx->base_addr);
/* PCI Device Setup */
retval = cx18_setup_pci(cx, pci_dev, pci_id);
if (retval != 0)
- goto free_workqueue;
+ goto free_workqueues;
/* map io memory */
CX18_DEBUG_INFO("attempting ioremap at 0x%08x len 0x%08x\n",
@@ -943,8 +978,9 @@ free_map:
cx18_iounmap(cx);
free_mem:
release_mem_region(cx->base_addr, CX18_MEM_SIZE);
-free_workqueue:
- destroy_workqueue(cx->work_queue);
+free_workqueues:
+ destroy_workqueue(cx->in_work_queue);
+ destroy_workqueue(cx->out_work_queue);
err:
if (retval == 0)
retval = -ENODEV;
@@ -1053,11 +1089,19 @@ int cx18_init_on_first_open(struct cx18 *cx)
return 0;
}
-static void cx18_cancel_epu_work_orders(struct cx18 *cx)
+static void cx18_cancel_in_work_orders(struct cx18 *cx)
{
int i;
- for (i = 0; i < CX18_MAX_EPU_WORK_ORDERS; i++)
- cancel_work_sync(&cx->epu_work_order[i].work);
+ for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++)
+ cancel_work_sync(&cx->in_work_order[i].work);
+}
+
+static void cx18_cancel_out_work_orders(struct cx18 *cx)
+{
+ int i;
+ for (i = 0; i < CX18_MAX_STREAMS; i++)
+ if (&cx->streams[i].video_dev != NULL)
+ cancel_work_sync(&cx->streams[i].out_work_order);
}
static void cx18_remove(struct pci_dev *pci_dev)
@@ -1073,15 +1117,20 @@ static void cx18_remove(struct pci_dev *pci_dev)
if (atomic_read(&cx->tot_capturing) > 0)
cx18_stop_all_captures(cx);
- /* Interrupts */
+ /* Stop interrupts that cause incoming work to be queued */
cx18_sw1_irq_disable(cx, IRQ_CPU_TO_EPU | IRQ_APU_TO_EPU);
+
+ /* Incoming work can cause outgoing work, so clean up incoming first */
+ cx18_cancel_in_work_orders(cx);
+ cx18_cancel_out_work_orders(cx);
+
+ /* Stop ack interrupts that may have been needed for work to finish */
cx18_sw2_irq_disable(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK);
cx18_halt_firmware(cx);
- cx18_cancel_epu_work_orders(cx);
-
- destroy_workqueue(cx->work_queue);
+ destroy_workqueue(cx->in_work_queue);
+ destroy_workqueue(cx->out_work_queue);
cx18_streams_cleanup(cx, 1);
diff --git a/drivers/media/video/cx18/cx18-driver.h b/drivers/media/video/cx18/cx18-driver.h
index ece4f281ef42..f89b82367963 100644
--- a/drivers/media/video/cx18/cx18-driver.h
+++ b/drivers/media/video/cx18/cx18-driver.h
@@ -254,6 +254,7 @@ struct cx18_options {
#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 */
+#define CX18_F_S_STOPPING 9 /* telling the fw to stop capturing */
/* per-cx18, i_flags */
#define CX18_F_I_LOADED_FW 0 /* Loaded firmware 1st time */
@@ -285,6 +286,7 @@ struct cx18_queue {
struct list_head list;
atomic_t buffers;
u32 bytesused;
+ spinlock_t lock;
};
struct cx18_dvb {
@@ -305,7 +307,7 @@ struct cx18_scb; /* forward reference */
#define CX18_MAX_MDL_ACKS 2
-#define CX18_MAX_EPU_WORK_ORDERS (CX18_MAX_FW_MDLS_PER_STREAM + 7)
+#define CX18_MAX_IN_WORK_ORDERS (CX18_MAX_FW_MDLS_PER_STREAM + 7)
/* CPU_DE_RELEASE_MDL can burst CX18_MAX_FW_MDLS_PER_STREAM orders in a group */
#define CX18_F_EWO_MB_STALE_UPON_RECEIPT 0x1
@@ -313,7 +315,7 @@ struct cx18_scb; /* forward reference */
#define CX18_F_EWO_MB_STALE \
(CX18_F_EWO_MB_STALE_UPON_RECEIPT | CX18_F_EWO_MB_STALE_WHILE_PROC)
-struct cx18_epu_work_order {
+struct cx18_in_work_order {
struct work_struct work;
atomic_t pending;
struct cx18 *cx;
@@ -337,7 +339,6 @@ struct cx18_stream {
unsigned mdl_offset;
u32 id;
- struct mutex 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
@@ -353,6 +354,8 @@ struct cx18_stream {
struct cx18_queue q_busy; /* busy buffers - in use by firmware */
struct cx18_queue q_full; /* full buffers - data for user apps */
+ struct work_struct out_work_order;
+
/* DVB / Digital Transport */
struct cx18_dvb dvb;
};
@@ -568,10 +571,14 @@ struct cx18 {
u32 sw2_irq_mask;
u32 hw2_irq_mask;
- struct workqueue_struct *work_queue;
- struct cx18_epu_work_order epu_work_order[CX18_MAX_EPU_WORK_ORDERS];
+ struct workqueue_struct *in_work_queue;
+ char in_workq_name[11]; /* "cx18-NN-in" */
+ struct cx18_in_work_order in_work_order[CX18_MAX_IN_WORK_ORDERS];
char epu_debug_str[256]; /* CX18_EPU_DEBUG is rare: use shared space */
+ struct workqueue_struct *out_work_queue;
+ char out_workq_name[12]; /* "cx18-NN-out" */
+
/* i2c */
struct i2c_adapter i2c_adap[2];
struct i2c_algo_bit_data i2c_algo[2];
diff --git a/drivers/media/video/cx18/cx18-dvb.c b/drivers/media/video/cx18/cx18-dvb.c
index 3b86f57cd15a..e7285a1096f2 100644
--- a/drivers/media/video/cx18/cx18-dvb.c
+++ b/drivers/media/video/cx18/cx18-dvb.c
@@ -23,6 +23,7 @@
#include "cx18-version.h"
#include "cx18-dvb.h"
#include "cx18-io.h"
+#include "cx18-queue.h"
#include "cx18-streams.h"
#include "cx18-cards.h"
#include "s5h1409.h"
diff --git a/drivers/media/video/cx18/cx18-fileops.c b/drivers/media/video/cx18/cx18-fileops.c
index b3889c0b2697..29969c18949c 100644
--- a/drivers/media/video/cx18/cx18-fileops.c
+++ b/drivers/media/video/cx18/cx18-fileops.c
@@ -265,8 +265,13 @@ static size_t cx18_copy_buf_to_user(struct cx18_stream *s,
* an MPEG-2 Program Pack start code, and provide only
* up to that point to the user, so it's easy to insert VBI data
* the next time around.
+ *
+ * This will not work for an MPEG-2 TS and has only been
+ * verified by analysis to work for an MPEG-2 PS. Helen Buus
+ * pointed out this works for the CX23416 MPEG-2 DVD compatible
+ * stream, and research indicates both the MPEG 2 SVCD and DVD
+ * stream types use an MPEG-2 PS container.
*/
- /* FIXME - This only works for an MPEG-2 PS, not a TS */
/*
* An MPEG-2 Program Stream (PS) is a series of
* MPEG-2 Program Packs terminated by an
diff --git a/drivers/media/video/cx18/cx18-mailbox.c b/drivers/media/video/cx18/cx18-mailbox.c
index 2226e5791e99..afe46c3d4057 100644
--- a/drivers/media/video/cx18/cx18-mailbox.c
+++ b/drivers/media/video/cx18/cx18-mailbox.c
@@ -131,7 +131,7 @@ static void dump_mb(struct cx18 *cx, struct cx18_mailbox *mb, char *name)
* Functions that run in a work_queue work handling context
*/
-static void epu_dma_done(struct cx18 *cx, struct cx18_epu_work_order *order)
+static void epu_dma_done(struct cx18 *cx, struct cx18_in_work_order *order)
{
u32 handle, mdl_ack_count, id;
struct cx18_mailbox *mb;
@@ -191,29 +191,30 @@ static void epu_dma_done(struct cx18 *cx, struct cx18_epu_work_order *order)
if (buf == NULL) {
CX18_WARN("Could not find buf %d for stream %s\n",
id, s->name);
- /* Put as many buffers as possible back into fw use */
- cx18_stream_load_fw_queue(s);
continue;
}
- if (s->type == CX18_ENC_STREAM_TYPE_TS && s->dvb.enabled) {
- CX18_DEBUG_HI_DMA("TS recv bytesused = %d\n",
- buf->bytesused);
- dvb_dmx_swfilter(&s->dvb.demux, buf->buf,
- buf->bytesused);
+ CX18_DEBUG_HI_DMA("%s recv bytesused = %d\n",
+ s->name, buf->bytesused);
+
+ if (s->type != CX18_ENC_STREAM_TYPE_TS)
+ cx18_enqueue(s, buf, &s->q_full);
+ else {
+ if (s->dvb.enabled)
+ dvb_dmx_swfilter(&s->dvb.demux, buf->buf,
+ buf->bytesused);
+ cx18_enqueue(s, buf, &s->q_free);
}
- /* Put as many buffers as possible back into fw use */
- cx18_stream_load_fw_queue(s);
- /* Put back TS buffer, since it was removed from all queues */
- if (s->type == CX18_ENC_STREAM_TYPE_TS)
- cx18_stream_put_buf_fw(s, buf);
}
+ /* Put as many buffers as possible back into fw use */
+ cx18_stream_load_fw_queue(s);
+
wake_up(&cx->dma_waitq);
if (s->id != -1)
wake_up(&s->waitq);
}
-static void epu_debug(struct cx18 *cx, struct cx18_epu_work_order *order)
+static void epu_debug(struct cx18 *cx, struct cx18_in_work_order *order)
{
char *p;
char *str = order->str;
@@ -224,7 +225,7 @@ static void epu_debug(struct cx18 *cx, struct cx18_epu_work_order *order)
CX18_INFO("FW version: %s\n", p - 1);
}
-static void epu_cmd(struct cx18 *cx, struct cx18_epu_work_order *order)
+static void epu_cmd(struct cx18 *cx, struct cx18_in_work_order *order)
{
switch (order->rpu) {
case CPU:
@@ -253,18 +254,18 @@ static void epu_cmd(struct cx18 *cx, struct cx18_epu_work_order *order)
}
static
-void free_epu_work_order(struct cx18 *cx, struct cx18_epu_work_order *order)
+void free_in_work_order(struct cx18 *cx, struct cx18_in_work_order *order)
{
atomic_set(&order->pending, 0);
}
-void cx18_epu_work_handler(struct work_struct *work)
+void cx18_in_work_handler(struct work_struct *work)
{
- struct cx18_epu_work_order *order =
- container_of(work, struct cx18_epu_work_order, work);
+ struct cx18_in_work_order *order =
+ container_of(work, struct cx18_in_work_order, work);
struct cx18 *cx = order->cx;
epu_cmd(cx, order);
- free_epu_work_order(cx, order);
+ free_in_work_order(cx, order);
}
@@ -272,7 +273,7 @@ void cx18_epu_work_handler(struct work_struct *work)
* Functions that run in an interrupt handling context
*/
-static void mb_ack_irq(struct cx18 *cx, struct cx18_epu_work_order *order)
+static void mb_ack_irq(struct cx18 *cx, struct cx18_in_work_order *order)
{
struct cx18_mailbox __iomem *ack_mb;
u32 ack_irq, req;
@@ -308,7 +309,7 @@ static void mb_ack_irq(struct cx18 *cx, struct cx18_epu_work_order *order)
return;
}
-static int epu_dma_done_irq(struct cx18 *cx, struct cx18_epu_work_order *order)
+static int epu_dma_done_irq(struct cx18 *cx, struct cx18_in_work_order *order)
{
u32 handle, mdl_ack_offset, mdl_ack_count;
struct cx18_mailbox *mb;
@@ -334,7 +335,7 @@ static int epu_dma_done_irq(struct cx18 *cx, struct cx18_epu_work_order *order)
}
static
-int epu_debug_irq(struct cx18 *cx, struct cx18_epu_work_order *order)
+int epu_debug_irq(struct cx18 *cx, struct cx18_in_work_order *order)
{
u32 str_offset;
char *str = order->str;
@@ -355,7 +356,7 @@ int epu_debug_irq(struct cx18 *cx, struct cx18_epu_work_order *order)
}
static inline
-int epu_cmd_irq(struct cx18 *cx, struct cx18_epu_work_order *order)
+int epu_cmd_irq(struct cx18 *cx, struct cx18_in_work_order *order)
{
int ret = -1;
@@ -387,12 +388,12 @@ int epu_cmd_irq(struct cx18 *cx, struct cx18_epu_work_order *order)
}
static inline
-struct cx18_epu_work_order *alloc_epu_work_order_irq(struct cx18 *cx)
+struct cx18_in_work_order *alloc_in_work_order_irq(struct cx18 *cx)
{
int i;
- struct cx18_epu_work_order *order = NULL;
+ struct cx18_in_work_order *order = NULL;
- for (i = 0; i < CX18_MAX_EPU_WORK_ORDERS; i++) {
+ for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++) {
/*
* We only need "pending" atomic to inspect its contents,
* and need not do a check and set because:
@@ -401,8 +402,8 @@ struct cx18_epu_work_order *alloc_epu_work_order_irq(struct cx18 *cx)
* 2. "pending" is only set here, and we're serialized because
* we're called in an IRQ handler context.
*/
- if (atomic_read(&cx->epu_work_order[i].pending) == 0) {
- order = &cx->epu_work_order[i];
+ if (atomic_read(&cx->in_work_order[i].pending) == 0) {
+ order = &cx->in_work_order[i];
atomic_set(&order->pending, 1);
break;
}
@@ -414,7 +415,7 @@ void cx18_api_epu_cmd_irq(struct cx18 *cx, int rpu)
{
struct cx18_mailbox __iomem *mb;
struct cx18_mailbox *order_mb;
- struct cx18_epu_work_order *order;
+ struct cx18_in_work_order *order;
int submit;
switch (rpu) {
@@ -428,7 +429,7 @@ void cx18_api_epu_cmd_irq(struct cx18 *cx, int rpu)
return;
}
- order = alloc_epu_work_order_irq(cx);
+ order = alloc_in_work_order_irq(cx);
if (order == NULL) {
CX18_WARN("Unable to find blank work order form to schedule "
"incoming mailbox command processing\n");
@@ -461,7 +462,7 @@ void cx18_api_epu_cmd_irq(struct cx18 *cx, int rpu)
*/
submit = epu_cmd_irq(cx, order);
if (submit > 0) {
- queue_work(cx->work_queue, &order->work);
+ queue_work(cx->in_work_queue, &order->work);
}
}
@@ -478,9 +479,10 @@ static int cx18_api_call(struct cx18 *cx, u32 cmd, int args, u32 data[])
u32 __iomem *xpu_state;
wait_queue_head_t *waitq;
struct mutex *mb_lock;
- long int timeout, ret;
+ unsigned long int t0, timeout, ret;
int i;
char argstr[MAX_MB_ARGUMENTS*11+1];
+ DEFINE_WAIT(w);
if (info == NULL) {
CX18_WARN("unknown cmd %x\n", cmd);
@@ -562,25 +564,49 @@ static int cx18_api_call(struct cx18 *cx, u32 cmd, int args, u32 data[])
CX18_DEBUG_HI_IRQ("sending interrupt SW1: %x to send %s\n",
irq, info->name);
+
+ /* So we don't miss the wakeup, prepare to wait before notifying fw */
+ prepare_to_wait(waitq, &w, TASK_UNINTERRUPTIBLE);
cx18_write_reg_expect(cx, irq, SW1_INT_SET, irq, irq);
- ret = wait_event_timeout(
- *waitq,
- cx18_readl(cx, &mb->ack) == cx18_readl(cx, &mb->request),
- timeout);
+ t0 = jiffies;
+ ack = cx18_readl(cx, &mb->ack);
+ if (ack != req) {
+ schedule_timeout(timeout);
+ ret = jiffies - t0;
+ ack = cx18_readl(cx, &mb->ack);
+ } else {
+ ret = jiffies - t0;
+ }
- if (ret == 0) {
- /* Timed out */
+ finish_wait(waitq, &w);
+
+ if (req != ack) {
mutex_unlock(mb_lock);
- CX18_DEBUG_WARN("sending %s timed out waiting %d msecs for RPU "
- "acknowledgement\n",
- info->name, jiffies_to_msecs(timeout));
+ if (ret >= timeout) {
+ /* Timed out */
+ CX18_DEBUG_WARN("sending %s timed out waiting %d msecs "
+ "for RPU acknowledgement\n",
+ info->name, jiffies_to_msecs(ret));
+ } else {
+ CX18_DEBUG_WARN("woken up before mailbox ack was ready "
+ "after submitting %s to RPU. only "
+ "waited %d msecs on req %u but awakened"
+ " with unmatched ack %u\n",
+ info->name,
+ jiffies_to_msecs(ret),
+ req, ack);
+ }
return -EINVAL;
}
- if (ret != timeout)
+ if (ret >= timeout)
+ CX18_DEBUG_WARN("failed to be awakened upon RPU acknowledgment "
+ "sending %s; timed out waiting %d msecs\n",
+ info->name, jiffies_to_msecs(ret));
+ else
CX18_DEBUG_HI_API("waited %u msecs for %s to be acked\n",
- jiffies_to_msecs(timeout-ret), info->name);
+ jiffies_to_msecs(ret), info->name);
/* Collect data returned by the XPU */
for (i = 0; i < MAX_MB_ARGUMENTS; i++)
diff --git a/drivers/media/video/cx18/cx18-mailbox.h b/drivers/media/video/cx18/cx18-mailbox.h
index ce2b6686aa00..e23aaac5b280 100644
--- a/drivers/media/video/cx18/cx18-mailbox.h
+++ b/drivers/media/video/cx18/cx18-mailbox.h
@@ -95,6 +95,6 @@ int cx18_api_func(void *priv, u32 cmd, int in, int out,
void cx18_api_epu_cmd_irq(struct cx18 *cx, int rpu);
-void cx18_epu_work_handler(struct work_struct *work);
+void cx18_in_work_handler(struct work_struct *work);
#endif
diff --git a/drivers/media/video/cx18/cx18-queue.c b/drivers/media/video/cx18/cx18-queue.c
index 3046b8e74345..fa1ed7897d97 100644
--- a/drivers/media/video/cx18/cx18-queue.c
+++ b/drivers/media/video/cx18/cx18-queue.c
@@ -23,8 +23,8 @@
*/
#include "cx18-driver.h"
-#include "cx18-streams.h"
#include "cx18-queue.h"
+#include "cx18-streams.h"
#include "cx18-scb.h"
void cx18_buf_swap(struct cx18_buffer *buf)
@@ -53,13 +53,13 @@ struct cx18_queue *_cx18_enqueue(struct cx18_stream *s, struct cx18_buffer *buf,
buf->skipped = 0;
}
- mutex_lock(&s->qlock);
-
/* q_busy is restricted to a max buffer count imposed by firmware */
if (q == &s->q_busy &&
atomic_read(&q->buffers) >= CX18_MAX_FW_MDLS_PER_STREAM)
q = &s->q_free;
+ spin_lock(&q->lock);
+
if (to_front)
list_add(&buf->list, &q->list); /* LIFO */
else
@@ -67,7 +67,7 @@ struct cx18_queue *_cx18_enqueue(struct cx18_stream *s, struct cx18_buffer *buf,
q->bytesused += buf->bytesused - buf->readpos;
atomic_inc(&q->buffers);
- mutex_unlock(&s->qlock);
+ spin_unlock(&q->lock);
return q;
}
@@ -75,7 +75,7 @@ struct cx18_buffer *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q)
{
struct cx18_buffer *buf = NULL;
- mutex_lock(&s->qlock);
+ spin_lock(&q->lock);
if (!list_empty(&q->list)) {
buf = list_first_entry(&q->list, struct cx18_buffer, list);
list_del_init(&buf->list);
@@ -83,7 +83,7 @@ struct cx18_buffer *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q)
buf->skipped = 0;
atomic_dec(&q->buffers);
}
- mutex_unlock(&s->qlock);
+ spin_unlock(&q->lock);
return buf;
}
@@ -94,9 +94,23 @@ struct cx18_buffer *cx18_queue_get_buf(struct cx18_stream *s, u32 id,
struct cx18_buffer *buf;
struct cx18_buffer *tmp;
struct cx18_buffer *ret = NULL;
-
- mutex_lock(&s->qlock);
+ LIST_HEAD(sweep_up);
+
+ /*
+ * We don't have to acquire multiple q locks here, because we are
+ * serialized by the single threaded work handler.
+ * Buffers from the firmware will thus remain in order as
+ * they are moved from q_busy to q_full or to the dvb ring buffer.
+ */
+ spin_lock(&s->q_busy.lock);
list_for_each_entry_safe(buf, tmp, &s->q_busy.list, list) {
+ /*
+ * We should find what the firmware told us is done,
+ * right at the front of the queue. If we don't, we likely have
+ * missed a buffer done message from the firmware.
+ * Once we skip a buffer repeatedly, relative to the size of
+ * q_busy, we have high confidence we've missed it.
+ */
if (buf->id != id) {
buf->skipped++;
if (buf->skipped >= atomic_read(&s->q_busy.buffers)-1) {
@@ -105,38 +119,41 @@ struct cx18_buffer *cx18_queue_get_buf(struct cx18_stream *s, u32 id,
"times - it must have dropped out of "
"rotation\n", s->name, buf->id,
buf->skipped);
- /* move it to q_free */
- list_move_tail(&buf->list, &s->q_free.list);
- buf->bytesused = buf->readpos = buf->b_flags =
- buf->skipped = 0;
+ /* Sweep it up to put it back into rotation */
+ list_move_tail(&buf->list, &sweep_up);
atomic_dec(&s->q_busy.buffers);
- atomic_inc(&s->q_free.buffers);
}
continue;
}
-
- buf->bytesused = bytesused;
- /* Sync the buffer before we release the qlock */
- cx18_buf_sync_for_cpu(s, buf);
- if (s->type == CX18_ENC_STREAM_TYPE_TS) {
- /*
- * TS doesn't use q_full. As we pull the buffer off of
- * the queue here, the caller will have to put it back.
- */
- list_del_init(&buf->list);
- } else {
- /* Move buffer from q_busy to q_full */
- list_move_tail(&buf->list, &s->q_full.list);
- set_bit(CX18_F_B_NEED_BUF_SWAP, &buf->b_flags);
- s->q_full.bytesused += buf->bytesused;
- atomic_inc(&s->q_full.buffers);
- }
+ /*
+ * We pull the desired buffer off of the queue here. Something
+ * will have to put it back on a queue later.
+ */
+ list_del_init(&buf->list);
atomic_dec(&s->q_busy.buffers);
-
ret = buf;
break;
}
- mutex_unlock(&s->qlock);
+ spin_unlock(&s->q_busy.lock);
+
+ /*
+ * We found the buffer for which we were looking. Get it ready for
+ * the caller to put on q_full or in the dvb ring buffer.
+ */
+ if (ret != NULL) {
+ ret->bytesused = bytesused;
+ ret->skipped = 0;
+ /* readpos and b_flags were 0'ed when the buf went on q_busy */
+ cx18_buf_sync_for_cpu(s, ret);
+ if (s->type != CX18_ENC_STREAM_TYPE_TS)
+ set_bit(CX18_F_B_NEED_BUF_SWAP, &ret->b_flags);
+ }
+
+ /* Put any buffers the firmware is ignoring back into normal rotation */
+ list_for_each_entry_safe(buf, tmp, &sweep_up, list) {
+ list_del_init(&buf->list);
+ cx18_enqueue(s, buf, &s->q_free);
+ }
return ret;
}
@@ -148,7 +165,7 @@ static void cx18_queue_flush(struct cx18_stream *s, struct cx18_queue *q)
if (q == &s->q_free)
return;
- mutex_lock(&s->qlock);
+ spin_lock(&q->lock);
while (!list_empty(&q->list)) {
buf = list_first_entry(&q->list, struct cx18_buffer, list);
list_move_tail(&buf->list, &s->q_free.list);
@@ -156,7 +173,7 @@ static void cx18_queue_flush(struct cx18_stream *s, struct cx18_queue *q)
atomic_inc(&s->q_free.buffers);
}
cx18_queue_init(q);
- mutex_unlock(&s->qlock);
+ spin_unlock(&q->lock);
}
void cx18_flush_queues(struct cx18_stream *s)
diff --git a/drivers/media/video/cx18/cx18-streams.c b/drivers/media/video/cx18/cx18-streams.c
index 0932b76b2373..41a1b2618aac 100644
--- a/drivers/media/video/cx18/cx18-streams.c
+++ b/drivers/media/video/cx18/cx18-streams.c
@@ -116,12 +116,16 @@ static void cx18_stream_init(struct cx18 *cx, int type)
s->buffers = cx->stream_buffers[type];
s->buf_size = cx->stream_buf_size[type];
- mutex_init(&s->qlock);
init_waitqueue_head(&s->waitq);
s->id = -1;
+ spin_lock_init(&s->q_free.lock);
cx18_queue_init(&s->q_free);
+ spin_lock_init(&s->q_busy.lock);
cx18_queue_init(&s->q_busy);
+ spin_lock_init(&s->q_full.lock);
cx18_queue_init(&s->q_full);
+
+ INIT_WORK(&s->out_work_order, cx18_out_work_handler);
}
static int cx18_prep_dev(struct cx18 *cx, int type)
@@ -431,14 +435,16 @@ static void cx18_vbi_setup(struct cx18_stream *s)
cx18_api(cx, CX18_CPU_SET_RAW_VBI_PARAM, 6, data);
}
-struct cx18_queue *cx18_stream_put_buf_fw(struct cx18_stream *s,
- struct cx18_buffer *buf)
+static
+struct cx18_queue *_cx18_stream_put_buf_fw(struct cx18_stream *s,
+ struct cx18_buffer *buf)
{
struct cx18 *cx = s->cx;
struct cx18_queue *q;
/* Don't give it to the firmware, if we're not running a capture */
if (s->handle == CX18_INVALID_TASK_HANDLE ||
+ test_bit(CX18_F_S_STOPPING, &s->s_flags) ||
!test_bit(CX18_F_S_STREAMING, &s->s_flags))
return cx18_enqueue(s, buf, &s->q_free);
@@ -453,7 +459,8 @@ struct cx18_queue *cx18_stream_put_buf_fw(struct cx18_stream *s,
return q;
}
-void cx18_stream_load_fw_queue(struct cx18_stream *s)
+static
+void _cx18_stream_load_fw_queue(struct cx18_stream *s)
{
struct cx18_queue *q;
struct cx18_buffer *buf;
@@ -467,11 +474,19 @@ void cx18_stream_load_fw_queue(struct cx18_stream *s)
buf = cx18_dequeue(s, &s->q_free);
if (buf == NULL)
break;
- q = cx18_stream_put_buf_fw(s, buf);
+ q = _cx18_stream_put_buf_fw(s, buf);
} while (atomic_read(&s->q_busy.buffers) < CX18_MAX_FW_MDLS_PER_STREAM
&& q == &s->q_busy);
}
+void cx18_out_work_handler(struct work_struct *work)
+{
+ struct cx18_stream *s =
+ container_of(work, struct cx18_stream, out_work_order);
+
+ _cx18_stream_load_fw_queue(s);
+}
+
int cx18_start_v4l2_encode_stream(struct cx18_stream *s)
{
u32 data[MAX_MB_ARGUMENTS];
@@ -600,19 +615,20 @@ int cx18_start_v4l2_encode_stream(struct cx18_stream *s)
/* Init all the cpu_mdls for this stream */
cx18_flush_queues(s);
- mutex_lock(&s->qlock);
+ spin_lock(&s->q_free.lock);
list_for_each_entry(buf, &s->q_free.list, list) {
cx18_writel(cx, buf->dma_handle,
&cx->scb->cpu_mdl[buf->id].paddr);
cx18_writel(cx, s->buf_size, &cx->scb->cpu_mdl[buf->id].length);
}
- mutex_unlock(&s->qlock);
- cx18_stream_load_fw_queue(s);
+ spin_unlock(&s->q_free.lock);
+ _cx18_stream_load_fw_queue(s);
/* begin_capture */
if (cx18_vapi(cx, CX18_CPU_CAPTURE_START, 1, s->handle)) {
CX18_DEBUG_WARN("Error starting capture!\n");
/* Ensure we're really not capturing before releasing MDLs */
+ set_bit(CX18_F_S_STOPPING, &s->s_flags);
if (s->type == CX18_ENC_STREAM_TYPE_MPG)
cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 2, s->handle, 1);
else
@@ -622,6 +638,7 @@ int cx18_start_v4l2_encode_stream(struct cx18_stream *s)
cx18_vapi(cx, CX18_CPU_DE_RELEASE_MDL, 1, s->handle);
cx18_vapi(cx, CX18_DESTROY_TASK, 1, s->handle);
s->handle = CX18_INVALID_TASK_HANDLE;
+ clear_bit(CX18_F_S_STOPPING, &s->s_flags);
if (atomic_read(&cx->tot_capturing) == 0) {
set_bit(CX18_F_I_EOS, &cx->i_flags);
cx18_write_reg(cx, 5, CX18_DSP0_INTERRUPT_MASK);
@@ -666,6 +683,7 @@ int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end)
if (atomic_read(&cx->tot_capturing) == 0)
return 0;
+ set_bit(CX18_F_S_STOPPING, &s->s_flags);
if (s->type == CX18_ENC_STREAM_TYPE_MPG)
cx18_vapi(cx, CX18_CPU_CAPTURE_STOP, 2, s->handle, !gop_end);
else
@@ -689,6 +707,7 @@ int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end)
cx18_vapi(cx, CX18_DESTROY_TASK, 1, s->handle);
s->handle = CX18_INVALID_TASK_HANDLE;
+ clear_bit(CX18_F_S_STOPPING, &s->s_flags);
if (atomic_read(&cx->tot_capturing) > 0)
return 0;
diff --git a/drivers/media/video/cx18/cx18-streams.h b/drivers/media/video/cx18/cx18-streams.h
index 420e0a172945..1afc3fd9d822 100644
--- a/drivers/media/video/cx18/cx18-streams.h
+++ b/drivers/media/video/cx18/cx18-streams.h
@@ -28,10 +28,24 @@ int cx18_streams_setup(struct cx18 *cx);
int cx18_streams_register(struct cx18 *cx);
void cx18_streams_cleanup(struct cx18 *cx, int unregister);
+/* Related to submission of buffers to firmware */
+static inline void cx18_stream_load_fw_queue(struct cx18_stream *s)
+{
+ struct cx18 *cx = s->cx;
+ queue_work(cx->out_work_queue, &s->out_work_order);
+}
+
+static inline void cx18_stream_put_buf_fw(struct cx18_stream *s,
+ struct cx18_buffer *buf)
+{
+ /* Put buf on q_free; the out work handler will move buf(s) to q_busy */
+ cx18_enqueue(s, buf, &s->q_free);
+ cx18_stream_load_fw_queue(s);
+}
+
+void cx18_out_work_handler(struct work_struct *work);
+
/* Capture related */
-void cx18_stream_load_fw_queue(struct cx18_stream *s);
-struct cx18_queue *cx18_stream_put_buf_fw(struct cx18_stream *s,
- struct cx18_buffer *buf);
int cx18_start_v4l2_encode_stream(struct cx18_stream *s);
int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end);
diff --git a/drivers/media/video/cx18/cx18-version.h b/drivers/media/video/cx18/cx18-version.h
index bd9bd44da791..45494b094e7f 100644
--- a/drivers/media/video/cx18/cx18-version.h
+++ b/drivers/media/video/cx18/cx18-version.h
@@ -24,7 +24,7 @@
#define CX18_DRIVER_NAME "cx18"
#define CX18_DRIVER_VERSION_MAJOR 1
-#define CX18_DRIVER_VERSION_MINOR 1
+#define CX18_DRIVER_VERSION_MINOR 2
#define CX18_DRIVER_VERSION_PATCHLEVEL 0
#define CX18_VERSION __stringify(CX18_DRIVER_VERSION_MAJOR) "." __stringify(CX18_DRIVER_VERSION_MINOR) "." __stringify(CX18_DRIVER_VERSION_PATCHLEVEL)