[media] cx23885: Hauppauge HVR1850 Analog driver support

First in a series of patches that adds support to the cx23885 driver
for CX23888 analog video handling. Raw and MPEG video support is
being added for the HVR1850 driver in the patch, and the following
series of patches.

Some basic cx23885 driver cleanup. Partly to add HVR1850 support
and partly to allow -417.c V4L2 calls to be routed through thr
driver core and handled in a single place.

Make a number of core driver functions available to the -417.c
driver to streamline the driver.

Add the analog tuner ops definition so we can reach/tune the
hardware when we need to. Added the tff field so 888 based cards
(which have a weird field ordering issue) can be accomodated
and worked around in the driver.

Signed-off-by: Steven Toth <stoth@kernellabs.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/drivers/media/video/cx23885/cx23885-cards.c b/drivers/media/video/cx23885/cx23885-cards.c
index ac03c26..dc7864e 100644
--- a/drivers/media/video/cx23885/cx23885-cards.c
+++ b/drivers/media/video/cx23885/cx23885-cards.c
@@ -335,8 +335,33 @@
 	},
 	[CX23885_BOARD_HAUPPAUGE_HVR1850] = {
 		.name		= "Hauppauge WinTV-HVR1850",
+		.porta		= CX23885_ANALOG_VIDEO,
 		.portb		= CX23885_MPEG_ENCODER,
 		.portc		= CX23885_MPEG_DVB,
+		.tuner_type	= TUNER_ABSENT,
+		.tuner_addr	= 0x42, /* 0x84 >> 1 */
+		.force_bff	= 1,
+		.input          = {{
+			.type   = CX23885_VMUX_TELEVISION,
+			.vmux   =	CX25840_VIN7_CH3 |
+					CX25840_VIN5_CH2 |
+					CX25840_VIN2_CH1 |
+					CX25840_DIF_ON,
+			.amux   = CX25840_AUDIO8,
+		}, {
+			.type   = CX23885_VMUX_COMPOSITE1,
+			.vmux   =	CX25840_VIN7_CH3 |
+					CX25840_VIN4_CH2 |
+					CX25840_VIN6_CH1,
+			.amux   = CX25840_AUDIO7,
+		}, {
+			.type   = CX23885_VMUX_SVIDEO,
+			.vmux   =	CX25840_VIN7_CH3 |
+					CX25840_VIN4_CH2 |
+					CX25840_VIN8_CH1 |
+					CX25840_SVIDEO_ON,
+			.amux   = CX25840_AUDIO7,
+		} },
 	},
 	[CX23885_BOARD_COMPRO_VIDEOMATE_E800] = {
 		.name		= "Compro VideoMate E800",
@@ -1402,6 +1427,7 @@
 		ts1->ts_clk_en_val = 0x1; /* Enable TS_CLK */
 		ts1->src_sel_val   = CX23885_SRC_SEL_PARALLEL_MPEG_VIDEO;
 		break;
+	case CX23885_BOARD_HAUPPAUGE_HVR1850:
 	case CX23885_BOARD_HAUPPAUGE_HVR1800:
 		/* Defaults for VID B - Analog encoder */
 		/* DREQ_POL, SMODE, PUNC_CLK, MCLK_POL Serial bus + punc clk */
@@ -1412,6 +1438,7 @@
 		/* APB_TSVALERR_POL (active low)*/
 		ts1->vld_misc_val    = 0x2000;
 		ts1->hw_sop_ctrl_val = (0x47 << 16 | 188 << 4 | 0xc);
+		cx_write(0x130184, 0xc);
 
 		/* Defaults for VID C */
 		ts2->gen_ctrl_val  = 0xc; /* Serial bus + punctured clock */
@@ -1466,7 +1493,6 @@
 	case CX23885_BOARD_HAUPPAUGE_HVR1275:
 	case CX23885_BOARD_HAUPPAUGE_HVR1255:
 	case CX23885_BOARD_HAUPPAUGE_HVR1210:
-	case CX23885_BOARD_HAUPPAUGE_HVR1850:
 	case CX23885_BOARD_COMPRO_VIDEOMATE_E800:
 	case CX23885_BOARD_HAUPPAUGE_HVR1290:
 	case CX23885_BOARD_GOTVIEW_X5_3D_HYBRID:
diff --git a/drivers/media/video/cx23885/cx23885-core.c b/drivers/media/video/cx23885/cx23885-core.c
index 40e68b2..6ad2270 100644
--- a/drivers/media/video/cx23885/cx23885-core.c
+++ b/drivers/media/video/cx23885/cx23885-core.c
@@ -206,12 +206,12 @@
 		.cnt2_reg	= DMA1_CNT2,
 	},
 	[SRAM_CH02] = {
-		.name		= "ch2",
-		.cmds_start	= 0x0,
-		.ctrl_start	= 0x0,
-		.cdt		= 0x0,
-		.fifo_start	= 0x0,
-		.fifo_size	= 0x0,
+		.name		= "VID A (VBI)",
+		.cmds_start	= 0x10050,
+		.ctrl_start	= 0x105F0,
+		.cdt		= 0x10810,
+		.fifo_start	= 0x3000,
+		.fifo_size	= 0x1000,
 		.ptr1_reg	= DMA2_PTR1,
 		.ptr2_reg	= DMA2_PTR2,
 		.cnt1_reg	= DMA2_CNT1,
@@ -266,12 +266,12 @@
 		.cnt2_reg	= DMA5_CNT2,
 	},
 	[SRAM_CH07] = {
-		.name		= "ch7",
-		.cmds_start	= 0x0,
-		.ctrl_start	= 0x0,
-		.cdt		= 0x0,
-		.fifo_start	= 0x0,
-		.fifo_size	= 0x0,
+		.name		= "TV Audio",
+		.cmds_start	= 0x10190,
+		.ctrl_start	= 0x106B0,
+		.cdt		= 0x10930,
+		.fifo_start	= 0x7000,
+		.fifo_size	= 0x1000,
 		.ptr1_reg	= DMA6_PTR1,
 		.ptr2_reg	= DMA6_PTR2,
 		.cnt1_reg	= DMA6_CNT1,
diff --git a/drivers/media/video/cx23885/cx23885-dvb.c b/drivers/media/video/cx23885/cx23885-dvb.c
index 28d51d8..a390622 100644
--- a/drivers/media/video/cx23885/cx23885-dvb.c
+++ b/drivers/media/video/cx23885/cx23885-dvb.c
@@ -1026,6 +1026,20 @@
 		}
 		break;
 	case CX23885_BOARD_HAUPPAUGE_HVR1850:
+		i2c_bus = &dev->i2c_bus[0];
+		fe0->dvb.frontend = dvb_attach(s5h1411_attach,
+			&hcw_s5h1411_config,
+			&i2c_bus->i2c_adap);
+		if (fe0->dvb.frontend != NULL)
+			dvb_attach(tda18271_attach, fe0->dvb.frontend,
+				0x60, &dev->i2c_bus[0].i2c_adap,
+				&hauppauge_tda18271_config);
+
+		tda18271_attach(&dev->ts1.analog_fe,
+			0x60, &dev->i2c_bus[1].i2c_adap,
+			&hauppauge_tda18271_config);
+
+		break;
 	case CX23885_BOARD_HAUPPAUGE_HVR1290:
 		i2c_bus = &dev->i2c_bus[0];
 		fe0->dvb.frontend = dvb_attach(s5h1411_attach,
diff --git a/drivers/media/video/cx23885/cx23885-video.c b/drivers/media/video/cx23885/cx23885-video.c
index 7f3b973..e85412d 100644
--- a/drivers/media/video/cx23885/cx23885-video.c
+++ b/drivers/media/video/cx23885/cx23885-video.c
@@ -316,7 +316,7 @@
 			__func__, bc);
 }
 
-static int cx23885_set_tvnorm(struct cx23885_dev *dev, v4l2_std_id norm)
+int cx23885_set_tvnorm(struct cx23885_dev *dev, v4l2_std_id norm)
 {
 	dprintk(1, "%s(norm = 0x%08x) name: [%s]\n",
 		__func__,
@@ -504,7 +504,8 @@
 			INPUT(input)->vmux, 0, 0);
 
 	if ((dev->board == CX23885_BOARD_HAUPPAUGE_HVR1800) ||
-		(dev->board == CX23885_BOARD_MPX885)) {
+		(dev->board == CX23885_BOARD_MPX885) ||
+		(dev->board == CX23885_BOARD_HAUPPAUGE_HVR1850)) {
 		/* Configure audio routing */
 		v4l2_subdev_call(dev->sd_cx25840, audio, s_routing,
 			INPUT(input)->amux, 0, 0);
@@ -650,6 +651,7 @@
 	int rc, init_buffer = 0;
 	u32 line0_offset, line1_offset;
 	struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb);
+	int field_tff;
 
 	BUG_ON(NULL == fh->fmt);
 	if (fh->width  < 48 || fh->width  > norm_maxw(dev->tvnorm) ||
@@ -691,15 +693,25 @@
 					 buf->bpl, 0, buf->vb.height);
 			break;
 		case V4L2_FIELD_INTERLACED:
-			if (dev->tvnorm & V4L2_STD_NTSC) {
+			if (dev->tvnorm & V4L2_STD_NTSC)
+				/* NTSC or  */
+				field_tff = 1;
+			else
+				field_tff = 0;
+
+			if (cx23885_boards[dev->board].force_bff)
+				/* PAL / SECAM OR 888 in NTSC MODE */
+				field_tff = 0;
+
+			if (field_tff) {
 				/* cx25840 transmits NTSC bottom field first */
-				dprintk(1, "%s() Creating NTSC risc\n",
+				dprintk(1, "%s() Creating TFF/NTSC risc\n",
 					__func__);
 				line0_offset = buf->bpl;
 				line1_offset = 0;
 			} else {
 				/* All other formats are top field first */
-				dprintk(1, "%s() Creating PAL/SECAM risc\n",
+				dprintk(1, "%s() Creating BFF/PAL/SECAM risc\n",
 					__func__);
 				line0_offset = 0;
 				line1_offset = buf->bpl;
@@ -982,6 +994,8 @@
 	}
 
 	videobuf_mmap_free(&fh->vidq);
+	videobuf_mmap_free(&fh->vbiq);
+
 	file->private_data = NULL;
 	kfree(fh);
 
@@ -1003,7 +1017,7 @@
 /* ------------------------------------------------------------------ */
 /* VIDEO CTRL IOCTLS                                                  */
 
-static int cx23885_get_control(struct cx23885_dev *dev,
+int cx23885_get_control(struct cx23885_dev *dev,
 	struct v4l2_control *ctl)
 {
 	dprintk(1, "%s() calling cx25840(VIDIOC_G_CTRL)\n", __func__);
@@ -1011,7 +1025,7 @@
 	return 0;
 }
 
-static int cx23885_set_control(struct cx23885_dev *dev,
+int cx23885_set_control(struct cx23885_dev *dev,
 	struct v4l2_control *ctl)
 {
 	dprintk(1, "%s() calling cx25840(VIDIOC_S_CTRL)\n", __func__);
@@ -1230,6 +1244,16 @@
 	return 0;
 }
 
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id)
+{
+	struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev;
+	dprintk(1, "%s()\n", __func__);
+
+	call_all(dev, core, g_std, id);
+
+	return 0;
+}
+
 static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *tvnorms)
 {
 	struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev;
@@ -1242,7 +1266,7 @@
 	return 0;
 }
 
-static int cx23885_enum_input(struct cx23885_dev *dev, struct v4l2_input *i)
+int cx23885_enum_input(struct cx23885_dev *dev, struct v4l2_input *i)
 {
 	static const char *iname[] = {
 		[CX23885_VMUX_COMPOSITE1] = "Composite1",
@@ -1290,7 +1314,7 @@
 	return cx23885_enum_input(dev, i);
 }
 
-static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+int cx23885_get_input(struct file *file, void *priv, unsigned int *i)
 {
 	struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev;
 
@@ -1299,7 +1323,12 @@
 	return 0;
 }
 
-static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	return cx23885_get_input(file, priv, i);
+}
+
+int cx23885_set_input(struct file *file, void *priv, unsigned int i)
 {
 	struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev;
 
@@ -1323,6 +1352,11 @@
 	return 0;
 }
 
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+	return cx23885_set_input(file, priv, i);
+}
+
 static int vidioc_log_status(struct file *file, void *priv)
 {
 	struct cx23885_fh  *fh  = priv;
@@ -1330,11 +1364,11 @@
 
 	printk(KERN_INFO
 		"%s/0: ============  START LOG STATUS  ============\n",
-	       dev->name);
+		dev->name);
 	call_all(dev, core, log_status);
 	printk(KERN_INFO
 		"%s/0: =============  END LOG STATUS  =============\n",
-	       dev->name);
+		dev->name);
 	return 0;
 }
 
@@ -1472,6 +1506,8 @@
 
 static int cx23885_set_freq(struct cx23885_dev *dev, struct v4l2_frequency *f)
 {
+	struct v4l2_control ctrl;
+
 	if (unlikely(UNSET == dev->tuner_type))
 		return -EINVAL;
 	if (unlikely(f->tuner != 0))
@@ -1480,29 +1516,102 @@
 	mutex_lock(&dev->lock);
 	dev->freq = f->frequency;
 
+	/* I need to mute audio here */
+	ctrl.id = V4L2_CID_AUDIO_MUTE;
+	ctrl.value = 1;
+	cx23885_set_control(dev, &ctrl);
+
 	call_all(dev, tuner, s_frequency, f);
 
 	/* When changing channels it is required to reset TVAUDIO */
-	msleep(10);
+	msleep(100);
+
+	/* I need to unmute audio here */
+	ctrl.value = 0;
+	cx23885_set_control(dev, &ctrl);
 
 	mutex_unlock(&dev->lock);
 
 	return 0;
 }
 
-static int vidioc_s_frequency(struct file *file, void *priv,
-				struct v4l2_frequency *f)
+static int cx23885_set_freq_via_ops(struct cx23885_dev *dev,
+	struct v4l2_frequency *f)
+{
+	struct v4l2_control ctrl;
+	struct videobuf_dvb_frontend *vfe;
+	struct dvb_frontend *fe;
+	int err = 0;
+
+	struct analog_parameters params = {
+		.mode      = V4L2_TUNER_ANALOG_TV,
+		.audmode   = V4L2_TUNER_MODE_STEREO,
+		.std       = dev->tvnorm,
+		.frequency = f->frequency
+	};
+
+	mutex_lock(&dev->lock);
+	dev->freq = f->frequency;
+
+	/* I need to mute audio here */
+	ctrl.id = V4L2_CID_AUDIO_MUTE;
+	ctrl.value = 1;
+	cx23885_set_control(dev, &ctrl);
+
+	/* If HVR1850 */
+	dprintk(1, "%s() frequency=%d tuner=%d std=0x%llx\n", __func__,
+		params.frequency, f->tuner, params.std);
+
+	vfe = videobuf_dvb_get_frontend(&dev->ts2.frontends, 1);
+	if (!vfe)
+		err = -EINVAL;
+
+	fe = vfe->dvb.frontend;
+
+	if (dev->board == CX23885_BOARD_HAUPPAUGE_HVR1850)
+		fe = &dev->ts1.analog_fe;
+
+	if (fe && fe->ops.tuner_ops.set_analog_params) {
+		call_all(dev, core, s_std, dev->tvnorm);
+		fe->ops.tuner_ops.set_analog_params(fe, &params);
+	}
+	else
+		printk(KERN_ERR "%s() No analog tuner, aborting\n", __func__);
+
+	/* When changing channels it is required to reset TVAUDIO */
+	msleep(100);
+
+	/* I need to unmute audio here */
+	ctrl.value = 0;
+	cx23885_set_control(dev, &ctrl);
+
+	mutex_unlock(&dev->lock);
+
+	return 0;
+}
+
+int cx23885_set_frequency(struct file *file, void *priv,
+	struct v4l2_frequency *f)
 {
 	struct cx23885_fh *fh = priv;
 	struct cx23885_dev *dev = fh->dev;
+	int ret;
 
-	if (unlikely(0 == fh->radio && f->type != V4L2_TUNER_ANALOG_TV))
-		return -EINVAL;
-	if (unlikely(1 == fh->radio && f->type != V4L2_TUNER_RADIO))
-		return -EINVAL;
+	switch (dev->board) {
+	case CX23885_BOARD_HAUPPAUGE_HVR1850:
+		ret = cx23885_set_freq_via_ops(dev, f);
+		break;
+	default:
+		ret = cx23885_set_freq(dev, f);
+	}
 
-	return
-		cx23885_set_freq(dev, f);
+	return ret;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+	struct v4l2_frequency *f)
+{
+	return cx23885_set_frequency(file, priv, f);
 }
 
 /* ----------------------------------------------------------- */
@@ -1614,6 +1723,8 @@
 	.vidioc_qbuf          = vidioc_qbuf,
 	.vidioc_dqbuf         = vidioc_dqbuf,
 	.vidioc_s_std         = vidioc_s_std,
+	.vidioc_g_std         = vidioc_g_std,
+	.vidioc_querystd      = vidioc_g_std,
 	.vidioc_enum_input    = vidioc_enum_input,
 	.vidioc_g_input       = vidioc_g_input,
 	.vidioc_s_input       = vidioc_s_input,
diff --git a/drivers/media/video/cx23885/cx23885.h b/drivers/media/video/cx23885/cx23885.h
index 519f40d..78fdb84 100644
--- a/drivers/media/video/cx23885/cx23885.h
+++ b/drivers/media/video/cx23885/cx23885.h
@@ -227,6 +227,8 @@
 	u32			clk_freq;
 	struct cx23885_input    input[MAX_CX23885_INPUT];
 	int			ci_type; /* for NetUP */
+	/* Force bottom field first during DMA (888 workaround) */
+	u32                     force_bff;
 };
 
 struct cx23885_subid {
@@ -311,6 +313,9 @@
 	u32                        num_frontends;
 	void                (*gate_ctrl)(struct cx23885_tsport *port, int open);
 	void                       *port_priv;
+
+	/* Workaround for a temp dvb_frontend that the tuner can attached to */
+	struct dvb_frontend analog_fe;
 };
 
 struct cx23885_kernel_ir {
@@ -575,6 +580,13 @@
 extern int cx23885_video_irq(struct cx23885_dev *dev, u32 status);
 extern void cx23885_video_wakeup(struct cx23885_dev *dev,
 	struct cx23885_dmaqueue *q, u32 count);
+int cx23885_enum_input(struct cx23885_dev *dev, struct v4l2_input *i);
+int cx23885_set_input(struct file *file, void *priv, unsigned int i);
+int cx23885_get_input(struct file *file, void *priv, unsigned int *i);
+int cx23885_set_frequency(struct file *file, void *priv, struct v4l2_frequency *f);
+int cx23885_set_control(struct cx23885_dev *dev, struct v4l2_control *ctl);
+int cx23885_get_control(struct cx23885_dev *dev, struct v4l2_control *ctl);
+int cx23885_set_tvnorm(struct cx23885_dev *dev, v4l2_std_id norm);
 
 /* ----------------------------------------------------------- */
 /* cx23885-vbi.c                                               */