linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v7 0/6] [media] Add analog mode support for Medion MD95700
@ 2018-07-02 21:23 Maciej S. Szmigiero
  2018-07-02 21:23 ` [PATCH v7 1/6] ivtv: zero-initialize cx25840 platform data Maciej S. Szmigiero
                   ` (6 more replies)
  0 siblings, 7 replies; 14+ messages in thread
From: Maciej S. Szmigiero @ 2018-07-02 21:23 UTC (permalink / raw)
  To: Michael Krufky, Mauro Carvalho Chehab
  Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil

This series adds support for analog part of Medion 95700 in the cxusb
driver.

What works:
* Video capture at various sizes with sequential fields,
* Input switching (TV Tuner, Composite, S-Video),
* TV and radio tuning,
* Video standard switching and auto detection,
* Radio mode switching (stereo / mono),
* Unplugging while capturing,
* DVB / analog coexistence,
* Raw BT.656 stream support.

What does not work yet:
* Audio,
* VBI,
* Picture controls.

This series (as a one patch) was submitted for inclusion few years ago,
then waited few months in a patch queue.
Unfortunately, by the time it was supposed to be merged there
were enough changes in media that it was no longer mergeable.

I thought at that time that I will be able to rebase and retest it soon
but unfortunately up till now I was never able to find enough time to do
so.
Also, with the passing of time the implementation diverged more and
more from the current kernel code, necessitating even more reworking.

That last iteration can be found here:
https://patchwork.linuxtv.org/patch/8048/

Since that version there had been the following changes:
* Adaptation to changes in V4L2 / DVB core,

* Radio device was added, with a possibility to tune to a FM radio
station and switch between stereo and mono modes (tested by taping
audio signal directly at tuner output pin),

* DVB / analog coexistence was improved - resolved a few cases where
DVB core would switch off power or reset the tuner when the device
was still being used but in the analog mode,

* Fixed issues reported by v4l2-compliance,

* Switching to raw BT.656 mode is now done by a custom streaming
parameter set via VIDIOC_S_PARM ioctl instead of using a
V4L2_BUF_TYPE_PRIVATE buffer (which was removed from V4L2),

* General small code cleanups (like using BIT() or ARRAY_SIZE() macros
instead of open coding them, code formatting improvements, etc.).

Changes from v1:
* Only support configuration of cx25840 pins that the cxusb driver is
actually using so there is no need for an ugly CX25840_PIN() macro,

* Split cxusb changes into two patches: first one implementing
digital / analog coexistence in this driver, second one adding the
actual implementation of the analog mode,

* Fix a warning reported by kbuild test robot.

Changes from v2:
* Split out ivtv cx25840 platform data zero-initialization to a separate
commit,

* Add kernel-doc description of struct cx25840_state,

* Make sure that all variables used in CX25840_VCONFIG_OPTION() and
CX25840_VCONFIG_SET_BIT() macros are their explicit parameters,

* Split out some code from cxusb_medion_copy_field() and
cxusb_medion_v_complete_work() functions to separate ones to increase
their readability,

* Generate masks using GENMASK() and BIT() macros in cx25840.h and
cxusb.h.

Changes from v3:
Add SPDX tag to a newly added "cxusb-analog.c" file.

Changes from v4:
* Make analog support conditional on a new DVB_USB_CXUSB_ANALOG Kconfig
option,

* Use '//' comments in the header of a newly added "cxusb-analog.c"
file,

* Don't print errors on memory allocation failures,

* Get rid of the driver MODULE_VERSION(),

* Small formating fix of a one line.

Changes from v5:
Rebase onto current media_tree/master.

Changes from v6:
Adapt to commit 4eb2f55728abbe changing V4L2_SUBDEV_IO_PIN_* from bit
masks to bit numbers.
Thanks to Dan Carpenter's kbuild bot for catching this.

 drivers/media/i2c/cx25840/cx25840-core.c |  396 ++++-
 drivers/media/i2c/cx25840/cx25840-core.h |   46 +-
 drivers/media/i2c/cx25840/cx25840-vbi.c  |    3 +
 drivers/media/pci/ivtv/ivtv-i2c.c        |    1 +
 drivers/media/tuners/tuner-simple.c      |    5 +-
 drivers/media/usb/dvb-usb/Kconfig        |   16 +-
 drivers/media/usb/dvb-usb/Makefile       |    3 +
 drivers/media/usb/dvb-usb/cxusb-analog.c | 1914 ++++++++++++++++++++++
 drivers/media/usb/dvb-usb/cxusb.c        |  452 ++++-
 drivers/media/usb/dvb-usb/cxusb.h        |  154 ++
 drivers/media/usb/dvb-usb/dvb-usb-dvb.c  |   20 +-
 drivers/media/usb/dvb-usb/dvb-usb-init.c |   13 +
 drivers/media/usb/dvb-usb/dvb-usb.h      |    8 +
 include/media/drv-intf/cx25840.h         |   74 +-
 14 files changed, 3042 insertions(+), 63 deletions(-)
 create mode 100644 drivers/media/usb/dvb-usb/cxusb-analog.c

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v7 1/6] ivtv: zero-initialize cx25840 platform data
  2018-07-02 21:23 [PATCH v7 0/6] [media] Add analog mode support for Medion MD95700 Maciej S. Szmigiero
@ 2018-07-02 21:23 ` Maciej S. Szmigiero
  2018-07-02 21:23 ` [PATCH v7 2/6] cx25840: add kernel-doc description of struct cx25840_state Maciej S. Szmigiero
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Maciej S. Szmigiero @ 2018-07-02 21:23 UTC (permalink / raw)
  To: Michael Krufky, Mauro Carvalho Chehab
  Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil

We need to zero-initialize cx25840 platform data structure to make sure
that its future members do not contain random stack garbage.

Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
 drivers/media/pci/ivtv/ivtv-i2c.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/media/pci/ivtv/ivtv-i2c.c b/drivers/media/pci/ivtv/ivtv-i2c.c
index 522cd111e399..e9ce54dd5e01 100644
--- a/drivers/media/pci/ivtv/ivtv-i2c.c
+++ b/drivers/media/pci/ivtv/ivtv-i2c.c
@@ -293,6 +293,7 @@ int ivtv_i2c_register(struct ivtv *itv, unsigned idx)
 			.platform_data = &pdata,
 		};
 
+		memset(&pdata, 0, sizeof(pdata));
 		pdata.pvr150_workaround = itv->pvr150_workaround;
 		sd = v4l2_i2c_new_subdev_board(&itv->v4l2_dev, adap,
 				&cx25840_info, NULL);

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v7 2/6] cx25840: add kernel-doc description of struct cx25840_state
  2018-07-02 21:23 [PATCH v7 0/6] [media] Add analog mode support for Medion MD95700 Maciej S. Szmigiero
  2018-07-02 21:23 ` [PATCH v7 1/6] ivtv: zero-initialize cx25840 platform data Maciej S. Szmigiero
@ 2018-07-02 21:23 ` Maciej S. Szmigiero
  2018-07-02 21:23 ` [PATCH v7 3/6] cx25840: add pin to pad mapping and output format configuration Maciej S. Szmigiero
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Maciej S. Szmigiero @ 2018-07-02 21:23 UTC (permalink / raw)
  To: Michael Krufky, Mauro Carvalho Chehab
  Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil

This commit describes a device instance private data of the driver
(struct cx25840_state) in a kernel-doc style comment.

Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
 drivers/media/i2c/cx25840/cx25840-core.h | 33 ++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/drivers/media/i2c/cx25840/cx25840-core.h b/drivers/media/i2c/cx25840/cx25840-core.h
index fb13a624d2e3..c323b1af1f83 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.h
+++ b/drivers/media/i2c/cx25840/cx25840-core.h
@@ -45,6 +45,35 @@ enum cx25840_media_pads {
 	CX25840_NUM_PADS
 };
 
+/**
+ * struct cx25840_state - a device instance private data
+ * @c:			i2c_client struct representing this device
+ * @sd:		our V4L2 sub-device
+ * @hdl:		our V4L2 control handler
+ * @volume:		audio volume V4L2 control (non-cx2583x devices only)
+ * @mute:		audio mute V4L2 control (non-cx2583x devices only)
+ * @pvr150_workaround:	whether we enable workaround for Hauppauge PVR150
+ *			hardware bug (audio dropping out)
+ * @radio:		set if we are currently in the radio mode, otherwise
+ *			the current mode is non-radio (that is, video)
+ * @std:		currently set video standard
+ * @vid_input:		currently set video input
+ * @aud_input:		currently set audio input
+ * @audclk_freq:	currently set audio sample rate
+ * @audmode:		currently set audio mode (when in non-radio mode)
+ * @vbi_line_offset:	vbi line number offset
+ * @id:		exact device model
+ * @rev:		raw device id read from the chip
+ * @is_initialized:	whether we have already loaded firmware into the chip
+ *			and initialized it
+ * @vbi_regs_offset:	offset of vbi regs
+ * @fw_wait:		wait queue to wake an initalization function up when
+ *			firmware loading (on a separate workqueue) finishes
+ * @fw_work:		a work that actually loads the firmware on a separate
+ *			workqueue
+ * @ir_state:		a pointer to chip IR controller private data
+ * @pads:		array of supported chip pads (currently only a stub)
+ */
 struct cx25840_state {
 	struct i2c_client *c;
 	struct v4l2_subdev sd;
@@ -66,8 +95,8 @@ struct cx25840_state {
 	u32 rev;
 	int is_initialized;
 	unsigned vbi_regs_offset;
-	wait_queue_head_t fw_wait;    /* wake up when the fw load is finished */
-	struct work_struct fw_work;   /* work entry for fw load */
+	wait_queue_head_t fw_wait;
+	struct work_struct fw_work;
 	struct cx25840_ir_state *ir_state;
 #if defined(CONFIG_MEDIA_CONTROLLER)
 	struct media_pad	pads[CX25840_NUM_PADS];

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v7 3/6] cx25840: add pin to pad mapping and output format configuration
  2018-07-02 21:23 [PATCH v7 0/6] [media] Add analog mode support for Medion MD95700 Maciej S. Szmigiero
  2018-07-02 21:23 ` [PATCH v7 1/6] ivtv: zero-initialize cx25840 platform data Maciej S. Szmigiero
  2018-07-02 21:23 ` [PATCH v7 2/6] cx25840: add kernel-doc description of struct cx25840_state Maciej S. Szmigiero
@ 2018-07-02 21:23 ` Maciej S. Szmigiero
  2018-07-04  9:05   ` Hans Verkuil
  2018-07-02 21:23 ` [PATCH v7 4/6] tuner-simple: allow setting mono radio mode Maciej S. Szmigiero
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 14+ messages in thread
From: Maciej S. Szmigiero @ 2018-07-02 21:23 UTC (permalink / raw)
  To: Michael Krufky, Mauro Carvalho Chehab
  Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil

This commit adds pin to pad mapping and output format configuration support
in CX2584x-series chips to cx25840 driver.

This functionality is then used to allow disabling ivtv-specific hacks
(called a "generic mode"), so cx25840 driver can be used for other devices
not needing them without risking compatibility problems.

Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
 drivers/media/i2c/cx25840/cx25840-core.c | 396 ++++++++++++++++++++++-
 drivers/media/i2c/cx25840/cx25840-core.h |  13 +
 drivers/media/i2c/cx25840/cx25840-vbi.c  |   3 +
 include/media/drv-intf/cx25840.h         |  74 ++++-
 4 files changed, 484 insertions(+), 2 deletions(-)

diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c
index b168bf3635b6..0643c3b44a2b 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.c
+++ b/drivers/media/i2c/cx25840/cx25840-core.c
@@ -21,6 +21,9 @@
  * CX23888 DIF support for the HVR1850
  * Copyright (C) 2011 Steven Toth <stoth@kernellabs.com>
  *
+ * CX2584x pin to pad mapping and output format configuration support are
+ * Copyright (C) 2011 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
+ *
  * 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
@@ -316,6 +319,260 @@ static int cx23885_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
 	return 0;
 }
 
+static u8 cx25840_function_to_pad(struct i2c_client *client, u8 function)
+{
+	switch (function) {
+	case CX25840_PAD_ACTIVE:
+		return 1;
+
+	case CX25840_PAD_VACTIVE:
+		return 2;
+
+	case CX25840_PAD_CBFLAG:
+		return 3;
+
+	case CX25840_PAD_VID_DATA_EXT0:
+		return 4;
+
+	case CX25840_PAD_VID_DATA_EXT1:
+		return 5;
+
+	case CX25840_PAD_GPO0:
+		return 6;
+
+	case CX25840_PAD_GPO1:
+		return 7;
+
+	case CX25840_PAD_GPO2:
+		return 8;
+
+	case CX25840_PAD_GPO3:
+		return 9;
+
+	case CX25840_PAD_IRQ_N:
+		return 10;
+
+	case CX25840_PAD_AC_SYNC:
+		return 11;
+
+	case CX25840_PAD_AC_SDOUT:
+		return 12;
+
+	case CX25840_PAD_PLL_CLK:
+		return 13;
+
+	case CX25840_PAD_VRESET:
+		return 14;
+
+	default:
+		if (function != CX25840_PAD_DEFAULT)
+			v4l_err(client,
+				"invalid function %u, assuming default\n",
+				(unsigned int)function);
+		return 0;
+	}
+}
+
+static void cx25840_set_invert(u8 *pinctrl3, u8 *voutctrl4, u8 function,
+			       u8 pin, bool invert)
+{
+	switch (function) {
+	case CX25840_PAD_IRQ_N:
+		if (invert)
+			*pinctrl3 &= ~2;
+		else
+			*pinctrl3 |= 2;
+		break;
+
+	case CX25840_PAD_ACTIVE:
+		if (invert)
+			*voutctrl4 |= BIT(2);
+		else
+			*voutctrl4 &= ~BIT(2);
+		break;
+
+	case CX25840_PAD_VACTIVE:
+		if (invert)
+			*voutctrl4 |= BIT(5);
+		else
+			*voutctrl4 &= ~BIT(5);
+		break;
+
+	case CX25840_PAD_CBFLAG:
+		if (invert)
+			*voutctrl4 |= BIT(4);
+		else
+			*voutctrl4 &= ~BIT(4);
+		break;
+
+	case CX25840_PAD_VRESET:
+		if (invert)
+			*voutctrl4 |= BIT(0);
+		else
+			*voutctrl4 &= ~BIT(0);
+		break;
+	}
+
+	if (function != CX25840_PAD_DEFAULT)
+		return;
+
+	switch (pin) {
+	case CX25840_PIN_DVALID_PRGM0:
+		if (invert)
+			*voutctrl4 |= BIT(6);
+		else
+			*voutctrl4 &= ~BIT(6);
+		break;
+
+	case CX25840_PIN_HRESET_PRGM2:
+		if (invert)
+			*voutctrl4 |= BIT(1);
+		else
+			*voutctrl4 &= ~BIT(1);
+		break;
+	}
+}
+
+static int cx25840_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
+				   struct v4l2_subdev_io_pin_config *p)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	unsigned int i;
+	u8 pinctrl[6], pinconf[10], voutctrl4;
+
+	for (i = 0; i < 6; i++)
+		pinctrl[i] = cx25840_read(client, 0x114 + i);
+
+	for (i = 0; i < 10; i++)
+		pinconf[i] = cx25840_read(client, 0x11c + i);
+
+	voutctrl4 = cx25840_read(client, 0x407);
+
+	for (i = 0; i < n; i++) {
+		u8 strength = p[i].strength;
+
+		if (strength != CX25840_PIN_DRIVE_SLOW &&
+		    strength != CX25840_PIN_DRIVE_MEDIUM &&
+		    strength != CX25840_PIN_DRIVE_FAST) {
+
+			v4l_err(client,
+				"invalid drive speed for pin %u (%u), assuming fast\n",
+				(unsigned int)p[i].pin,
+				(unsigned int)strength);
+
+			strength = CX25840_PIN_DRIVE_FAST;
+		}
+
+		switch (p[i].pin) {
+		case CX25840_PIN_DVALID_PRGM0:
+			if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE))
+				pinctrl[0] &= ~BIT(6);
+			else
+				pinctrl[0] |= BIT(6);
+
+			pinconf[3] &= 0xf0;
+			pinconf[3] |= cx25840_function_to_pad(client,
+							      p[i].function);
+
+			cx25840_set_invert(&pinctrl[3], &voutctrl4,
+					   p[i].function,
+					   CX25840_PIN_DVALID_PRGM0,
+					   p[i].flags &
+					   BIT(V4L2_SUBDEV_IO_PIN_ACTIVE_LOW));
+
+			pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */
+			switch (strength) {
+			case CX25840_PIN_DRIVE_SLOW:
+				pinctrl[4] |= 1 << 2;
+				break;
+
+			case CX25840_PIN_DRIVE_FAST:
+				pinctrl[4] |= 2 << 2;
+				break;
+			}
+
+			break;
+
+		case CX25840_PIN_HRESET_PRGM2:
+			if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE))
+				pinctrl[1] &= ~BIT(0);
+			else
+				pinctrl[1] |= BIT(0);
+
+			pinconf[4] &= 0xf0;
+			pinconf[4] |= cx25840_function_to_pad(client,
+							      p[i].function);
+
+			cx25840_set_invert(&pinctrl[3], &voutctrl4,
+					   p[i].function,
+					   CX25840_PIN_HRESET_PRGM2,
+					   p[i].flags &
+					   BIT(V4L2_SUBDEV_IO_PIN_ACTIVE_LOW));
+
+			pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */
+			switch (strength) {
+			case CX25840_PIN_DRIVE_SLOW:
+				pinctrl[4] |= 1 << 2;
+				break;
+
+			case CX25840_PIN_DRIVE_FAST:
+				pinctrl[4] |= 2 << 2;
+				break;
+			}
+
+			break;
+
+		case CX25840_PIN_PLL_CLK_PRGM7:
+			if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE))
+				pinctrl[2] &= ~BIT(2);
+			else
+				pinctrl[2] |= BIT(2);
+
+			switch (p[i].function) {
+			case CX25840_PAD_XTI_X5_DLL:
+				pinconf[6] = 0;
+				break;
+
+			case CX25840_PAD_AUX_PLL:
+				pinconf[6] = 1;
+				break;
+
+			case CX25840_PAD_VID_PLL:
+				pinconf[6] = 5;
+				break;
+
+			case CX25840_PAD_XTI:
+				pinconf[6] = 2;
+				break;
+
+			default:
+				pinconf[6] = 3;
+				pinconf[6] |=
+					cx25840_function_to_pad(client,
+								p[i].function)
+					<< 4;
+			}
+
+			break;
+
+		default:
+			v4l_err(client, "invalid or unsupported pin %u\n",
+				(unsigned int)p[i].pin);
+			break;
+		}
+	}
+
+	cx25840_write(client, 0x407, voutctrl4);
+
+	for (i = 0; i < 6; i++)
+		cx25840_write(client, 0x114 + i, pinctrl[i]);
+
+	for (i = 0; i < 10; i++)
+		cx25840_write(client, 0x11c + i, pinconf[i]);
+
+	return 0;
+}
+
 static int common_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
 				      struct v4l2_subdev_io_pin_config *pincfg)
 {
@@ -323,6 +580,8 @@ static int common_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
 
 	if (is_cx2388x(state))
 		return cx23885_s_io_pin_config(sd, n, pincfg);
+	else if (is_cx2584x(state))
+		return cx25840_s_io_pin_config(sd, n, pincfg);
 	return 0;
 }
 
@@ -455,6 +714,22 @@ static void cx25840_initialize(struct i2c_client *client)
 	/* (re)set input */
 	set_input(client, state->vid_input, state->aud_input);
 
+	if (state->generic_mode) {
+		/* set datasheet video output defaults */
+		cx25840_vconfig(client, CX25840_VCONFIG_FMT_BT656 |
+				CX25840_VCONFIG_RES_8BIT |
+				CX25840_VCONFIG_VBIRAW_DISABLED |
+				CX25840_VCONFIG_ANCDATA_ENABLED |
+				CX25840_VCONFIG_TASKBIT_ONE |
+				CX25840_VCONFIG_ACTIVE_HORIZONTAL |
+				CX25840_VCONFIG_VALID_NORMAL |
+				CX25840_VCONFIG_HRESETW_NORMAL |
+				CX25840_VCONFIG_CLKGATE_NONE |
+				CX25840_VCONFIG_DCMODE_DWORDS |
+				CX25840_VCONFIG_IDID0S_NORMAL |
+				CX25840_VCONFIG_VIPCLAMP_DISABLED);
+	}
+
 	/* start microcontroller */
 	cx25840_and_or(client, 0x803, ~0x10, 0x10);
 }
@@ -1403,7 +1678,9 @@ static int cx25840_set_fmt(struct v4l2_subdev *sd,
 		Hsrc |= (cx25840_read(client, 0x471) & 0xf0) >> 4;
 	}
 
-	Vlines = fmt->height + (is_50Hz ? 4 : 7);
+	Vlines = fmt->height;
+	if (!state->generic_mode)
+		Vlines += is_50Hz ? 4 : 7;
 
 	/*
 	 * We keep 1 margin for the Vsrc < Vlines check since the
@@ -1647,6 +1924,119 @@ static void log_audio_status(struct i2c_client *client)
 	}
 }
 
+#define CX25840_VCONFIG_OPTION(state, cfg_in, opt_msk)			\
+	do {								\
+		if ((cfg_in) & (opt_msk)) {				\
+			(state)->vid_config &= ~(opt_msk);		\
+			(state)->vid_config |= (cfg_in) & (opt_msk);	\
+		}							\
+	} while (0)
+
+#define CX25840_VCONFIG_SET_BIT(state, opt_msk, voc, idx, bit, oneval)	\
+	do {								\
+		if ((state)->vid_config & (opt_msk)) {			\
+			if (((state)->vid_config & (opt_msk)) ==	\
+			    (oneval))					\
+				(voc)[idx] |= BIT(bit);		\
+			else						\
+				(voc)[idx] &= ~BIT(bit);		\
+		}							\
+	} while (0)
+
+int cx25840_vconfig(struct i2c_client *client, u32 cfg_in)
+{
+	struct cx25840_state *state = to_state(i2c_get_clientdata(client));
+	u8 voutctrl[3];
+	unsigned int i;
+
+	/* apply incoming options to the current state */
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_FMT_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_RES_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VBIRAW_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ANCDATA_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_TASKBIT_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ACTIVE_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VALID_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_HRESETW_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_CLKGATE_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_DCMODE_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_IDID0S_MASK);
+	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VIPCLAMP_MASK);
+
+	for (i = 0; i < 3; i++)
+		voutctrl[i] = cx25840_read(client, 0x404 + i);
+
+	/* apply state to hardware regs */
+	if (state->vid_config & CX25840_VCONFIG_FMT_MASK)
+		voutctrl[0] &= ~3;
+	switch (state->vid_config & CX25840_VCONFIG_FMT_MASK) {
+	case CX25840_VCONFIG_FMT_BT656:
+		voutctrl[0] |= 1;
+		break;
+
+	case CX25840_VCONFIG_FMT_VIP11:
+		voutctrl[0] |= 2;
+		break;
+
+	case CX25840_VCONFIG_FMT_VIP2:
+		voutctrl[0] |= 3;
+		break;
+
+	case CX25840_VCONFIG_FMT_BT601:
+		/* zero */
+	default:
+		break;
+	}
+
+	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_RES_MASK, voutctrl,
+				0, 2, CX25840_VCONFIG_RES_10BIT);
+	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VBIRAW_MASK, voutctrl,
+				0, 3, CX25840_VCONFIG_VBIRAW_ENABLED);
+	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ANCDATA_MASK, voutctrl,
+				0, 4, CX25840_VCONFIG_ANCDATA_ENABLED);
+	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_TASKBIT_MASK, voutctrl,
+				0, 5, CX25840_VCONFIG_TASKBIT_ONE);
+	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ACTIVE_MASK, voutctrl,
+				1, 2, CX25840_VCONFIG_ACTIVE_HORIZONTAL);
+	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VALID_MASK, voutctrl,
+				1, 3, CX25840_VCONFIG_VALID_ANDACTIVE);
+	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_HRESETW_MASK, voutctrl,
+				1, 4, CX25840_VCONFIG_HRESETW_PIXCLK);
+
+	if (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK)
+		voutctrl[1] &= ~(3 << 6);
+	switch (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK) {
+	case CX25840_VCONFIG_CLKGATE_VALID:
+		voutctrl[1] |= 2;
+		break;
+
+	case CX25840_VCONFIG_CLKGATE_VALIDACTIVE:
+		voutctrl[1] |= 3;
+		break;
+
+	case CX25840_VCONFIG_CLKGATE_NONE:
+		/* zero */
+	default:
+		break;
+	}
+
+
+	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_DCMODE_MASK, voutctrl,
+				2, 0, CX25840_VCONFIG_DCMODE_BYTES);
+	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_IDID0S_MASK, voutctrl,
+				2, 1, CX25840_VCONFIG_IDID0S_LINECNT);
+	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VIPCLAMP_MASK, voutctrl,
+				2, 4, CX25840_VCONFIG_VIPCLAMP_ENABLED);
+
+	for (i = 0; i < 3; i++)
+		cx25840_write(client, 0x404 + i, voutctrl[i]);
+
+	return 0;
+}
+
+#undef CX25840_VCONFIG_SET_BIT
+#undef CX25840_VCONFIG_OPTION
+
 /* ----------------------------------------------------------------------- */
 
 /* This load_fw operation must be called to load the driver's firmware.
@@ -1836,6 +2226,9 @@ static int cx25840_s_video_routing(struct v4l2_subdev *sd,
 	if (is_cx23888(state))
 		cx23888_std_setup(client);
 
+	if (is_cx2584x(state) && state->generic_mode)
+		cx25840_vconfig(client, config);
+
 	return set_input(client, input, state->aud_input);
 }
 
@@ -5335,6 +5728,7 @@ static int cx25840_probe(struct i2c_client *client,
 		struct cx25840_platform_data *pdata = client->dev.platform_data;
 
 		state->pvr150_workaround = pdata->pvr150_workaround;
+		state->generic_mode = pdata->generic_mode;
 	}
 
 	cx25840_ir_probe(sd);
diff --git a/drivers/media/i2c/cx25840/cx25840-core.h b/drivers/media/i2c/cx25840/cx25840-core.h
index c323b1af1f83..e5f52b8d6d9c 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.h
+++ b/drivers/media/i2c/cx25840/cx25840-core.h
@@ -54,10 +54,12 @@ enum cx25840_media_pads {
  * @mute:		audio mute V4L2 control (non-cx2583x devices only)
  * @pvr150_workaround:	whether we enable workaround for Hauppauge PVR150
  *			hardware bug (audio dropping out)
+ * @generic_mode:	whether we disable ivtv-specific hacks
  * @radio:		set if we are currently in the radio mode, otherwise
  *			the current mode is non-radio (that is, video)
  * @std:		currently set video standard
  * @vid_input:		currently set video input
+ * @vid_config:	currently set video output configuration
  * @aud_input:		currently set audio input
  * @audclk_freq:	currently set audio sample rate
  * @audmode:		currently set audio mode (when in non-radio mode)
@@ -84,9 +86,11 @@ struct cx25840_state {
 		struct v4l2_ctrl *mute;
 	};
 	int pvr150_workaround;
+	int generic_mode;
 	int radio;
 	v4l2_std_id std;
 	enum cx25840_video_input vid_input;
+	u32 vid_config;
 	enum cx25840_audio_input aud_input;
 	u32 audclk_freq;
 	int audmode;
@@ -119,6 +123,14 @@ static inline bool is_cx2583x(struct cx25840_state *state)
 	       state->id == CX25837;
 }
 
+static inline bool is_cx2584x(struct cx25840_state *state)
+{
+	return state->id == CX25840 ||
+	       state->id == CX25841 ||
+	       state->id == CX25842 ||
+	       state->id == CX25843;
+}
+
 static inline bool is_cx231xx(struct cx25840_state *state)
 {
 	return state->id == CX2310X_AV;
@@ -156,6 +168,7 @@ int cx25840_and_or(struct i2c_client *client, u16 addr, unsigned mask, u8 value)
 int cx25840_and_or4(struct i2c_client *client, u16 addr, u32 and_mask,
 		    u32 or_value);
 void cx25840_std_setup(struct i2c_client *client);
+int cx25840_vconfig(struct i2c_client *client, u32 cfg_in);
 
 /* ----------------------------------------------------------------------- */
 /* cx25850-firmware.c                                                      */
diff --git a/drivers/media/i2c/cx25840/cx25840-vbi.c b/drivers/media/i2c/cx25840/cx25840-vbi.c
index 8c99a79fb726..23b7c1fb28ab 100644
--- a/drivers/media/i2c/cx25840/cx25840-vbi.c
+++ b/drivers/media/i2c/cx25840/cx25840-vbi.c
@@ -95,6 +95,7 @@ int cx25840_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *
 	memset(svbi->service_lines, 0, sizeof(svbi->service_lines));
 	svbi->service_set = 0;
 	/* we're done if raw VBI is active */
+	/* this will have to be changed for generic_mode VBI */
 	if ((cx25840_read(client, 0x404) & 0x10) == 0)
 		return 0;
 
@@ -137,6 +138,7 @@ int cx25840_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt)
 		cx25840_write(client, 0x54f, vbi_offset);
 	else
 		cx25840_write(client, 0x47f, vbi_offset);
+	/* this will have to be changed for generic_mode VBI */
 	cx25840_write(client, 0x404, 0x2e);
 	return 0;
 }
@@ -157,6 +159,7 @@ int cx25840_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *
 	cx25840_std_setup(client);
 
 	/* Sliced VBI */
+	/* this will have to be changed for generic_mode VBI */
 	cx25840_write(client, 0x404, 0x32);	/* Ancillary data */
 	cx25840_write(client, 0x406, 0x13);
 	if (is_cx23888(state))
diff --git a/include/media/drv-intf/cx25840.h b/include/media/drv-intf/cx25840.h
index 783c5bdd63eb..dc104b4f09eb 100644
--- a/include/media/drv-intf/cx25840.h
+++ b/include/media/drv-intf/cx25840.h
@@ -88,6 +88,70 @@ enum cx25840_video_input {
 	CX25840_DIF_ON = 0x80000400,
 };
 
+/* arguments to video s_routing config param */
+#define CX25840_VCONFIG_FMT_SHIFT 0
+#define CX25840_VCONFIG_FMT_MASK GENMASK(2, 0)
+#define CX25840_VCONFIG_FMT_BT601 BIT(0)
+#define CX25840_VCONFIG_FMT_BT656 BIT(1)
+#define CX25840_VCONFIG_FMT_VIP11 GENMASK(1, 0)
+#define CX25840_VCONFIG_FMT_VIP2 BIT(2)
+
+#define CX25840_VCONFIG_RES_SHIFT 3
+#define CX25840_VCONFIG_RES_MASK GENMASK(4, 3)
+#define CX25840_VCONFIG_RES_8BIT BIT(3)
+#define CX25840_VCONFIG_RES_10BIT BIT(4)
+
+#define CX25840_VCONFIG_VBIRAW_SHIFT 5
+#define CX25840_VCONFIG_VBIRAW_MASK GENMASK(6, 5)
+#define CX25840_VCONFIG_VBIRAW_DISABLED BIT(5)
+#define CX25840_VCONFIG_VBIRAW_ENABLED BIT(6)
+
+#define CX25840_VCONFIG_ANCDATA_SHIFT 7
+#define CX25840_VCONFIG_ANCDATA_MASK GENMASK(8, 7)
+#define CX25840_VCONFIG_ANCDATA_DISABLED BIT(7)
+#define CX25840_VCONFIG_ANCDATA_ENABLED BIT(8)
+
+#define CX25840_VCONFIG_TASKBIT_SHIFT 9
+#define CX25840_VCONFIG_TASKBIT_MASK GENMASK(10, 9)
+#define CX25840_VCONFIG_TASKBIT_ZERO BIT(9)
+#define CX25840_VCONFIG_TASKBIT_ONE BIT(10)
+
+#define CX25840_VCONFIG_ACTIVE_SHIFT 11
+#define CX25840_VCONFIG_ACTIVE_MASK GENMASK(12, 11)
+#define CX25840_VCONFIG_ACTIVE_COMPOSITE BIT(11)
+#define CX25840_VCONFIG_ACTIVE_HORIZONTAL BIT(12)
+
+#define CX25840_VCONFIG_VALID_SHIFT 13
+#define CX25840_VCONFIG_VALID_MASK GENMASK(14, 13)
+#define CX25840_VCONFIG_VALID_NORMAL BIT(13)
+#define CX25840_VCONFIG_VALID_ANDACTIVE BIT(14)
+
+#define CX25840_VCONFIG_HRESETW_SHIFT 15
+#define CX25840_VCONFIG_HRESETW_MASK GENMASK(16, 15)
+#define CX25840_VCONFIG_HRESETW_NORMAL BIT(15)
+#define CX25840_VCONFIG_HRESETW_PIXCLK BIT(16)
+
+#define CX25840_VCONFIG_CLKGATE_SHIFT 17
+#define CX25840_VCONFIG_CLKGATE_MASK GENMASK(18, 17)
+#define CX25840_VCONFIG_CLKGATE_NONE BIT(17)
+#define CX25840_VCONFIG_CLKGATE_VALID BIT(18)
+#define CX25840_VCONFIG_CLKGATE_VALIDACTIVE GENMASK(18, 17)
+
+#define CX25840_VCONFIG_DCMODE_SHIFT 19
+#define CX25840_VCONFIG_DCMODE_MASK GENMASK(20, 19)
+#define CX25840_VCONFIG_DCMODE_DWORDS BIT(19)
+#define CX25840_VCONFIG_DCMODE_BYTES BIT(20)
+
+#define CX25840_VCONFIG_IDID0S_SHIFT 21
+#define CX25840_VCONFIG_IDID0S_MASK GENMASK(22, 21)
+#define CX25840_VCONFIG_IDID0S_NORMAL BIT(21)
+#define CX25840_VCONFIG_IDID0S_LINECNT BIT(22)
+
+#define CX25840_VCONFIG_VIPCLAMP_SHIFT 23
+#define CX25840_VCONFIG_VIPCLAMP_MASK GENMASK(24, 23)
+#define CX25840_VCONFIG_VIPCLAMP_ENABLED BIT(23)
+#define CX25840_VCONFIG_VIPCLAMP_DISABLED BIT(24)
+
 enum cx25840_audio_input {
 	/* Audio inputs: serial or In4-In8 */
 	CX25840_AUDIO_SERIAL,
@@ -180,9 +244,17 @@ enum cx23885_io_pad {
    audio autodetect fails on some channels for these models and the workaround
    is to select the audio standard explicitly. Many thanks to Hauppauge for
    providing this information.
-   This platform data only needs to be supplied by the ivtv driver. */
+   This platform data only needs to be supplied by the ivtv driver.
+
+   generic_mode disables some of the ivtv-related hacks in this driver,
+   enables setting video output config and sets it according to datasheet
+   defaults on initialization.
+   This flag is to be used for example with USB video capture devices
+   using this chip.
+*/
 struct cx25840_platform_data {
 	int pvr150_workaround;
+	int generic_mode;
 };
 
 #endif

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v7 4/6] tuner-simple: allow setting mono radio mode
  2018-07-02 21:23 [PATCH v7 0/6] [media] Add analog mode support for Medion MD95700 Maciej S. Szmigiero
                   ` (2 preceding siblings ...)
  2018-07-02 21:23 ` [PATCH v7 3/6] cx25840: add pin to pad mapping and output format configuration Maciej S. Szmigiero
@ 2018-07-02 21:23 ` Maciej S. Szmigiero
  2018-07-02 21:23 ` [PATCH v7 5/6] [media] cxusb: implement Medion MD95700 digital / analog coexistence Maciej S. Szmigiero
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 14+ messages in thread
From: Maciej S. Szmigiero @ 2018-07-02 21:23 UTC (permalink / raw)
  To: Michael Krufky, Mauro Carvalho Chehab
  Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil

For some types of tuners (Philips FMD1216ME(X) MK3 currently) we know that
letting TDA9887 output port 1 remain high (inactive) will switch FM radio
to mono mode.
Let's make use of this functionality - nothing changes for the default
stereo radio mode.

Tested on a Medion 95700 board which has a FMD1216ME tuner.

Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
 drivers/media/tuners/tuner-simple.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/media/tuners/tuner-simple.c b/drivers/media/tuners/tuner-simple.c
index 36b88f820239..29c1473f2e9f 100644
--- a/drivers/media/tuners/tuner-simple.c
+++ b/drivers/media/tuners/tuner-simple.c
@@ -670,6 +670,7 @@ static int simple_set_radio_freq(struct dvb_frontend *fe,
 	int rc, j;
 	struct tuner_params *t_params;
 	unsigned int freq = params->frequency;
+	bool mono = params->audmode == V4L2_TUNER_MODE_MONO;
 
 	tun = priv->tun;
 
@@ -736,8 +737,8 @@ static int simple_set_radio_freq(struct dvb_frontend *fe,
 			config |= TDA9887_PORT2_ACTIVE;
 		if (t_params->intercarrier_mode)
 			config |= TDA9887_INTERCARRIER;
-/*		if (t_params->port1_set_for_fm_mono)
-			config &= ~TDA9887_PORT1_ACTIVE;*/
+		if (t_params->port1_set_for_fm_mono && mono)
+			config &= ~TDA9887_PORT1_ACTIVE;
 		if (t_params->fm_gain_normal)
 			config |= TDA9887_GAIN_NORMAL;
 		if (t_params->radio_if == 2)

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v7 5/6] [media] cxusb: implement Medion MD95700 digital / analog coexistence
  2018-07-02 21:23 [PATCH v7 0/6] [media] Add analog mode support for Medion MD95700 Maciej S. Szmigiero
                   ` (3 preceding siblings ...)
  2018-07-02 21:23 ` [PATCH v7 4/6] tuner-simple: allow setting mono radio mode Maciej S. Szmigiero
@ 2018-07-02 21:23 ` Maciej S. Szmigiero
  2018-07-02 21:23 ` [PATCH v7 6/6] [media] cxusb: add analog mode support for Medion MD95700 Maciej S. Szmigiero
  2018-07-04  9:33 ` [PATCH v7 0/6] [media] Add " Hans Verkuil
  6 siblings, 0 replies; 14+ messages in thread
From: Maciej S. Szmigiero @ 2018-07-02 21:23 UTC (permalink / raw)
  To: Michael Krufky, Mauro Carvalho Chehab
  Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil

This patch prepares cxusb driver for supporting the analog part of
Medion 95700 (previously only the digital - DVB - mode was supported).

Specifically, it adds support for:
* switching the device between analog and digital modes of operation,
* enforcing that only one mode is active at the same time due to hardware
limitations.

Actual implementation of the analog mode will be provided by the next
commit.

Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
 drivers/media/usb/dvb-usb/cxusb.c        | 450 ++++++++++++++++++++---
 drivers/media/usb/dvb-usb/cxusb.h        |  48 +++
 drivers/media/usb/dvb-usb/dvb-usb-dvb.c  |  20 +-
 drivers/media/usb/dvb-usb/dvb-usb-init.c |  13 +
 drivers/media/usb/dvb-usb/dvb-usb.h      |   8 +
 5 files changed, 486 insertions(+), 53 deletions(-)

diff --git a/drivers/media/usb/dvb-usb/cxusb.c b/drivers/media/usb/dvb-usb/cxusb.c
index 5b51ed7d6243..e0d390789b58 100644
--- a/drivers/media/usb/dvb-usb/cxusb.c
+++ b/drivers/media/usb/dvb-usb/cxusb.c
@@ -16,6 +16,7 @@
  * Copyright (C) 2005 Patrick Boettcher (patrick.boettcher@posteo.de)
  * Copyright (C) 2006 Michael Krufky (mkrufky@linuxtv.org)
  * Copyright (C) 2006, 2007 Chris Pascoe (c.pascoe@itee.uq.edu.au)
+ * Copyright (C) 2011, 2017 Maciej S. Szmigiero (mail@maciej.szmigiero.name)
  *
  *   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
@@ -24,9 +25,12 @@
  * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
  */
 #include <media/tuner.h>
-#include <linux/vmalloc.h>
-#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/device.h>
 #include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
 
 #include "cxusb.h"
 
@@ -47,17 +51,45 @@
 #include "si2157.h"
 
 /* debug */
-static int dvb_usb_cxusb_debug;
+int dvb_usb_cxusb_debug;
 module_param_named(debug, dvb_usb_cxusb_debug, int, 0644);
-MODULE_PARM_DESC(debug, "set debugging level (1=rc (or-able))." DVB_USB_DEBUG_STATUS);
+MODULE_PARM_DESC(debug, "set debugging level (see cxusb.h)."
+		 DVB_USB_DEBUG_STATUS);
 
 DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
 
-#define deb_info(args...)   dprintk(dvb_usb_cxusb_debug, 0x03, args)
-#define deb_i2c(args...)    dprintk(dvb_usb_cxusb_debug, 0x02, args)
+#define deb_info(args...)   dprintk(dvb_usb_cxusb_debug, CXUSB_DBG_MISC, args)
+#define deb_i2c(args...)    dprintk(dvb_usb_cxusb_debug, CXUSB_DBG_I2C, args)
+
+enum cxusb_table_index {
+	MEDION_MD95700,
+	DVICO_BLUEBIRD_LG064F_COLD,
+	DVICO_BLUEBIRD_LG064F_WARM,
+	DVICO_BLUEBIRD_DUAL_1_COLD,
+	DVICO_BLUEBIRD_DUAL_1_WARM,
+	DVICO_BLUEBIRD_LGZ201_COLD,
+	DVICO_BLUEBIRD_LGZ201_WARM,
+	DVICO_BLUEBIRD_TH7579_COLD,
+	DVICO_BLUEBIRD_TH7579_WARM,
+	DIGITALNOW_BLUEBIRD_DUAL_1_COLD,
+	DIGITALNOW_BLUEBIRD_DUAL_1_WARM,
+	DVICO_BLUEBIRD_DUAL_2_COLD,
+	DVICO_BLUEBIRD_DUAL_2_WARM,
+	DVICO_BLUEBIRD_DUAL_4,
+	DVICO_BLUEBIRD_DVB_T_NANO_2,
+	DVICO_BLUEBIRD_DVB_T_NANO_2_NFW_WARM,
+	AVERMEDIA_VOLAR_A868R,
+	DVICO_BLUEBIRD_DUAL_4_REV_2,
+	CONEXANT_D680_DMB,
+	MYGICA_D689,
+	MYGICA_T230,
+	NR__cxusb_table_index
+};
+
+static struct usb_device_id cxusb_table[];
 
-static int cxusb_ctrl_msg(struct dvb_usb_device *d,
-			  u8 cmd, const u8 *wbuf, int wlen, u8 *rbuf, int rlen)
+int cxusb_ctrl_msg(struct dvb_usb_device *d,
+		   u8 cmd, const u8 *wbuf, int wlen, u8 *rbuf, int rlen)
 {
 	struct cxusb_state *st = d->priv;
 	int ret;
@@ -89,7 +121,8 @@ static void cxusb_gpio_tuner(struct dvb_usb_device *d, int onoff)
 	struct cxusb_state *st = d->priv;
 	u8 o[2], i;
 
-	if (st->gpio_write_state[GPIO_TUNER] == onoff)
+	if (st->gpio_write_state[GPIO_TUNER] == onoff &&
+	    !st->gpio_write_refresh[GPIO_TUNER])
 		return;
 
 	o[0] = GPIO_TUNER;
@@ -100,6 +133,7 @@ static void cxusb_gpio_tuner(struct dvb_usb_device *d, int onoff)
 		deb_info("gpio_write failed.\n");
 
 	st->gpio_write_state[GPIO_TUNER] = onoff;
+	st->gpio_write_refresh[GPIO_TUNER] = false;
 }
 
 static int cxusb_bluebird_gpio_rw(struct dvb_usb_device *d, u8 changemask,
@@ -259,7 +293,7 @@ static int cxusb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
 
 static u32 cxusb_i2c_func(struct i2c_adapter *adapter)
 {
-	return I2C_FUNC_I2C;
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
 }
 
 static struct i2c_algorithm cxusb_i2c_algo = {
@@ -267,15 +301,48 @@ static struct i2c_algorithm cxusb_i2c_algo = {
 	.functionality = cxusb_i2c_func,
 };
 
-static int cxusb_power_ctrl(struct dvb_usb_device *d, int onoff)
+static int _cxusb_power_ctrl(struct dvb_usb_device *d, int onoff)
 {
 	u8 b = 0;
+
+	deb_info("setting power %s\n", onoff ? "ON" : "OFF");
+
 	if (onoff)
 		return cxusb_ctrl_msg(d, CMD_POWER_ON, &b, 1, NULL, 0);
 	else
 		return cxusb_ctrl_msg(d, CMD_POWER_OFF, &b, 1, NULL, 0);
 }
 
+static int cxusb_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	bool is_medion = d->props.devices[0].warm_ids[0] ==
+		&cxusb_table[MEDION_MD95700];
+	int ret;
+
+	if (is_medion && !onoff) {
+		struct cxusb_medion_dev *cxdev = d->priv;
+
+		mutex_lock(&cxdev->open_lock);
+
+		if (cxdev->open_type == CXUSB_OPEN_ANALOG) {
+			deb_info("preventing DVB core from setting power OFF while we are in analog mode\n");
+			ret = -EBUSY;
+			goto ret_unlock;
+		}
+	}
+
+	ret = _cxusb_power_ctrl(d, onoff);
+
+ret_unlock:
+	if (is_medion && !onoff) {
+		struct cxusb_medion_dev *cxdev = d->priv;
+
+		mutex_unlock(&cxdev->open_lock);
+	}
+
+	return ret;
+}
+
 static int cxusb_aver_power_ctrl(struct dvb_usb_device *d, int onoff)
 {
 	int ret;
@@ -353,11 +420,26 @@ static int cxusb_d680_dmb_power_ctrl(struct dvb_usb_device *d, int onoff)
 
 static int cxusb_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
 {
+	struct dvb_usb_device *dvbdev = adap->dev;
+	bool is_medion = dvbdev->props.devices[0].warm_ids[0] ==
+		&cxusb_table[MEDION_MD95700];
 	u8 buf[2] = { 0x03, 0x00 };
+
+	if (is_medion && onoff) {
+		int ret;
+
+		ret = cxusb_medion_get(dvbdev, CXUSB_OPEN_DIGITAL);
+		if (ret != 0)
+			return ret;
+	}
+
 	if (onoff)
-		cxusb_ctrl_msg(adap->dev, CMD_STREAMING_ON, buf, 2, NULL, 0);
+		cxusb_ctrl_msg(dvbdev, CMD_STREAMING_ON, buf, 2, NULL, 0);
 	else
-		cxusb_ctrl_msg(adap->dev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
+		cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
+
+	if (is_medion && !onoff)
+		cxusb_medion_put(dvbdev);
 
 	return 0;
 }
@@ -630,9 +712,21 @@ static struct max2165_config mygica_d689_max2165_cfg = {
 /* Callbacks for DVB USB */
 static int cxusb_fmd1216me_tuner_attach(struct dvb_usb_adapter *adap)
 {
+	struct dvb_usb_device *dvbdev = adap->dev;
+	bool is_medion = dvbdev->props.devices[0].warm_ids[0] ==
+		&cxusb_table[MEDION_MD95700];
+
 	dvb_attach(simple_tuner_attach, adap->fe_adap[0].fe,
-		   &adap->dev->i2c_adap, 0x61,
+		   &dvbdev->i2c_adap, 0x61,
 		   TUNER_PHILIPS_FMD1216ME_MK3);
+
+	if (is_medion && adap->fe_adap[0].fe != NULL)
+		/*
+		 * make sure that DVB core won't put to sleep (reset, really)
+		 * tuner when we might be open in analog mode
+		 */
+		adap->fe_adap[0].fe->ops.tuner_ops.sleep = NULL;
+
 	return 0;
 }
 
@@ -736,20 +830,105 @@ static int cxusb_mygica_d689_tuner_attach(struct dvb_usb_adapter *adap)
 	return (fe == NULL) ? -EIO : 0;
 }
 
-static int cxusb_cx22702_frontend_attach(struct dvb_usb_adapter *adap)
+static int cxusb_medion_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire)
 {
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dvb_usb_device *dvbdev = adap->dev;
+
+	if (acquire)
+		return cxusb_medion_get(dvbdev, CXUSB_OPEN_DIGITAL);
+
+	cxusb_medion_put(dvbdev);
+
+	return 0;
+}
+
+static int cxusb_medion_set_mode(struct dvb_usb_device *dvbdev, bool digital)
+{
+	struct cxusb_state *st = dvbdev->priv;
+	int ret;
 	u8 b;
-	if (usb_set_interface(adap->dev->udev, 0, 6) < 0)
-		err("set interface failed");
+	unsigned int i;
+
+	/*
+	 * switching mode while doing an I2C transaction often causes
+	 * the device to crash
+	 */
+	mutex_lock(&dvbdev->i2c_mutex);
+
+	if (digital) {
+		ret = usb_set_interface(dvbdev->udev, 0, 6);
+		if (ret != 0) {
+			dev_err(&dvbdev->udev->dev,
+				"digital interface selection failed (%d)\n",
+				ret);
+			goto ret_unlock;
+		}
+	} else {
+		ret = usb_set_interface(dvbdev->udev, 0, 1);
+		if (ret != 0) {
+			dev_err(&dvbdev->udev->dev,
+				"analog interface selection failed (%d)\n",
+				ret);
+			goto ret_unlock;
+		}
+	}
+
+	/* pipes need to be cleared after setting interface */
+	ret = usb_clear_halt(dvbdev->udev, usb_rcvbulkpipe(dvbdev->udev, 1));
+	if (ret != 0)
+		dev_warn(&dvbdev->udev->dev,
+			 "clear halt on IN pipe failed (%d)\n",
+			 ret);
+
+	ret = usb_clear_halt(dvbdev->udev, usb_sndbulkpipe(dvbdev->udev, 1));
+	if (ret != 0)
+		dev_warn(&dvbdev->udev->dev,
+			 "clear halt on OUT pipe failed (%d)\n",
+			 ret);
+
+	ret = cxusb_ctrl_msg(dvbdev, digital ? CMD_DIGITAL : CMD_ANALOG,
+			     NULL, 0, &b, 1);
+	if (ret != 0) {
+		dev_err(&dvbdev->udev->dev, "mode switch failed (%d)\n",
+			ret);
+		goto ret_unlock;
+	}
+
+	/* mode switch seems to reset GPIO states */
+	for (i = 0; i < ARRAY_SIZE(st->gpio_write_refresh); i++)
+		st->gpio_write_refresh[i] = true;
 
-	cxusb_ctrl_msg(adap->dev, CMD_DIGITAL, NULL, 0, &b, 1);
+ret_unlock:
+	mutex_unlock(&dvbdev->i2c_mutex);
+
+	return ret;
+}
+
+static int cxusb_cx22702_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *dvbdev = adap->dev;
+	bool is_medion = dvbdev->props.devices[0].warm_ids[0] ==
+		&cxusb_table[MEDION_MD95700];
+
+	if (is_medion) {
+		int ret;
+
+		ret = cxusb_medion_set_mode(dvbdev, true);
+		if (ret)
+			return ret;
+	}
 
 	adap->fe_adap[0].fe = dvb_attach(cx22702_attach, &cxusb_cx22702_config,
-					 &adap->dev->i2c_adap);
-	if ((adap->fe_adap[0].fe) != NULL)
-		return 0;
+					 &dvbdev->i2c_adap);
+	if (adap->fe_adap[0].fe == NULL)
+		return -EIO;
 
-	return -EIO;
+	if (is_medion)
+		adap->fe_adap[0].fe->ops.ts_bus_ctrl =
+			cxusb_medion_fe_ts_bus_ctrl;
+
+	return 0;
 }
 
 static int cxusb_lgdt3303_frontend_attach(struct dvb_usb_adapter *adap)
@@ -1312,6 +1491,101 @@ static int bluebird_patch_dvico_firmware_download(struct usb_device *udev,
 	return -EINVAL;
 }
 
+int cxusb_medion_get(struct dvb_usb_device *dvbdev,
+		     enum cxusb_open_type open_type)
+{
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	int ret = 0;
+
+	mutex_lock(&cxdev->open_lock);
+
+	if (WARN_ON((cxdev->open_type == CXUSB_OPEN_INIT ||
+		     cxdev->open_type == CXUSB_OPEN_NONE) &&
+		    cxdev->open_ctr != 0)) {
+		ret = -EINVAL;
+		goto ret_unlock;
+	}
+
+	if (cxdev->open_type == CXUSB_OPEN_INIT) {
+		ret = -EAGAIN;
+		goto ret_unlock;
+	}
+
+	if (cxdev->open_ctr == 0) {
+		if (cxdev->open_type != open_type) {
+			deb_info("will acquire and switch to %s\n",
+				 open_type == CXUSB_OPEN_ANALOG ?
+				 "analog" : "digital");
+
+			if (open_type == CXUSB_OPEN_ANALOG) {
+				ret = _cxusb_power_ctrl(dvbdev, 1);
+				if (ret != 0)
+					dev_warn(&dvbdev->udev->dev,
+						 "powerup for analog switch failed (%d)\n",
+						 ret);
+
+				ret = cxusb_medion_set_mode(dvbdev, false);
+				if (ret != 0)
+					goto ret_unlock;
+
+				ret = cxusb_medion_analog_init(dvbdev);
+				if (ret != 0)
+					goto ret_unlock;
+			} else { /* digital */
+				ret = _cxusb_power_ctrl(dvbdev, 1);
+				if (ret != 0)
+					dev_warn(&dvbdev->udev->dev,
+						 "powerup for digital switch failed (%d)\n",
+						 ret);
+
+				ret = cxusb_medion_set_mode(dvbdev, true);
+				if (ret != 0)
+					goto ret_unlock;
+			}
+
+			cxdev->open_type = open_type;
+		} else
+			deb_info("reacquired idle %s\n",
+				 open_type == CXUSB_OPEN_ANALOG ?
+				 "analog" : "digital");
+
+		cxdev->open_ctr = 1;
+	} else if (cxdev->open_type == open_type) {
+		cxdev->open_ctr++;
+		deb_info("acquired %s\n", open_type == CXUSB_OPEN_ANALOG ?
+			 "analog" : "digital");
+	} else
+		ret = -EBUSY;
+
+ret_unlock:
+	mutex_unlock(&cxdev->open_lock);
+
+	return ret;
+}
+
+void cxusb_medion_put(struct dvb_usb_device *dvbdev)
+{
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	mutex_lock(&cxdev->open_lock);
+
+	if (cxdev->open_type == CXUSB_OPEN_INIT) {
+		WARN_ON(cxdev->open_ctr != 0);
+		cxdev->open_type = CXUSB_OPEN_NONE;
+		goto unlock;
+	}
+
+	if (!WARN_ON(cxdev->open_ctr < 1)) {
+		cxdev->open_ctr--;
+
+		deb_info("release %s\n", cxdev->open_type ==
+			 CXUSB_OPEN_ANALOG ? "analog" : "digital");
+	}
+
+unlock:
+	mutex_unlock(&cxdev->open_lock);
+}
+
 /* DVB USB Driver stuff */
 static struct dvb_usb_device_properties cxusb_medion_properties;
 static struct dvb_usb_device_properties cxusb_bluebird_lgh064f_properties;
@@ -1327,12 +1601,101 @@ static struct dvb_usb_device_properties cxusb_d680_dmb_properties;
 static struct dvb_usb_device_properties cxusb_mygica_d689_properties;
 static struct dvb_usb_device_properties cxusb_mygica_t230_properties;
 
+static int cxusb_medion_priv_init(struct dvb_usb_device *dvbdev)
+{
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	cxdev->dvbdev = dvbdev;
+	cxdev->open_type = CXUSB_OPEN_INIT;
+	mutex_init(&cxdev->open_lock);
+
+	return 0;
+}
+
+static void cxusb_medion_priv_destroy(struct dvb_usb_device *dvbdev)
+{
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	mutex_destroy(&cxdev->open_lock);
+}
+
+static bool cxusb_medion_check_altsetting(struct usb_host_interface *as)
+{
+	unsigned int ctr;
+
+	for (ctr = 0; ctr < as->desc.bNumEndpoints; ctr++) {
+		if ((as->endpoint[ctr].desc.bEndpointAddress &
+		     USB_ENDPOINT_NUMBER_MASK) != 2)
+			continue;
+
+		if (as->endpoint[ctr].desc.bEndpointAddress & USB_DIR_IN &&
+		    ((as->endpoint[ctr].desc.bmAttributes &
+		      USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_ISOC))
+			return true;
+
+		break;
+	}
+
+	return false;
+}
+
+static bool cxusb_medion_check_intf(struct usb_interface *intf)
+{
+	unsigned int ctr;
+
+	if (intf->num_altsetting < 2) {
+		dev_err(intf->usb_dev, "no alternate interface");
+
+		return false;
+	}
+
+	for (ctr = 0; ctr < intf->num_altsetting; ctr++) {
+		if (intf->altsetting[ctr].desc.bAlternateSetting != 1)
+			continue;
+
+		if (cxusb_medion_check_altsetting(&intf->altsetting[ctr]))
+			return true;
+
+		break;
+	}
+
+	dev_err(intf->usb_dev, "no iso interface");
+
+	return false;
+}
+
 static int cxusb_probe(struct usb_interface *intf,
 		       const struct usb_device_id *id)
 {
+	struct dvb_usb_device *dvbdev;
+	int ret;
+
+	/* Medion 95700 */
 	if (0 == dvb_usb_device_init(intf, &cxusb_medion_properties,
-				     THIS_MODULE, NULL, adapter_nr) ||
-	    0 == dvb_usb_device_init(intf, &cxusb_bluebird_lgh064f_properties,
+				     THIS_MODULE, &dvbdev, adapter_nr)) {
+		if (!cxusb_medion_check_intf(intf)) {
+			ret = -ENODEV;
+			goto ret_uninit;
+		}
+
+		_cxusb_power_ctrl(dvbdev, 1);
+		ret = cxusb_medion_set_mode(dvbdev, false);
+		if (ret)
+			goto ret_uninit;
+
+		ret = cxusb_medion_register_analog(dvbdev);
+
+		cxusb_medion_set_mode(dvbdev, true);
+		_cxusb_power_ctrl(dvbdev, 0);
+
+		if (ret != 0)
+			goto ret_uninit;
+
+		/* release device from INIT mode to normal operation */
+		cxusb_medion_put(dvbdev);
+
+		return 0;
+	} else if (0 == dvb_usb_device_init(intf, &cxusb_bluebird_lgh064f_properties,
 				     THIS_MODULE, NULL, adapter_nr) ||
 	    0 == dvb_usb_device_init(intf, &cxusb_bluebird_dee1601_properties,
 				     THIS_MODULE, NULL, adapter_nr) ||
@@ -1362,6 +1725,11 @@ static int cxusb_probe(struct usb_interface *intf,
 		return 0;
 
 	return -EINVAL;
+
+ret_uninit:
+	dvb_usb_device_exit(intf);
+
+	return ret;
 }
 
 static void cxusb_disconnect(struct usb_interface *intf)
@@ -1370,6 +1738,9 @@ static void cxusb_disconnect(struct usb_interface *intf)
 	struct cxusb_state *st = d->priv;
 	struct i2c_client *client;
 
+	if (d->props.devices[0].warm_ids[0] == &cxusb_table[MEDION_MD95700])
+		cxusb_medion_unregister_analog(d);
+
 	/* remove I2C client for tuner */
 	client = st->i2c_client_tuner;
 	if (client) {
@@ -1387,31 +1758,6 @@ static void cxusb_disconnect(struct usb_interface *intf)
 	dvb_usb_device_exit(intf);
 }
 
-enum cxusb_table_index {
-	MEDION_MD95700,
-	DVICO_BLUEBIRD_LG064F_COLD,
-	DVICO_BLUEBIRD_LG064F_WARM,
-	DVICO_BLUEBIRD_DUAL_1_COLD,
-	DVICO_BLUEBIRD_DUAL_1_WARM,
-	DVICO_BLUEBIRD_LGZ201_COLD,
-	DVICO_BLUEBIRD_LGZ201_WARM,
-	DVICO_BLUEBIRD_TH7579_COLD,
-	DVICO_BLUEBIRD_TH7579_WARM,
-	DIGITALNOW_BLUEBIRD_DUAL_1_COLD,
-	DIGITALNOW_BLUEBIRD_DUAL_1_WARM,
-	DVICO_BLUEBIRD_DUAL_2_COLD,
-	DVICO_BLUEBIRD_DUAL_2_WARM,
-	DVICO_BLUEBIRD_DUAL_4,
-	DVICO_BLUEBIRD_DVB_T_NANO_2,
-	DVICO_BLUEBIRD_DVB_T_NANO_2_NFW_WARM,
-	AVERMEDIA_VOLAR_A868R,
-	DVICO_BLUEBIRD_DUAL_4_REV_2,
-	CONEXANT_D680_DMB,
-	MYGICA_D689,
-	MYGICA_T230,
-	NR__cxusb_table_index
-};
-
 static struct usb_device_id cxusb_table[NR__cxusb_table_index + 1] = {
 	[MEDION_MD95700] = {
 		USB_DEVICE(USB_VID_MEDION, USB_PID_MEDION_MD95700)
@@ -1485,13 +1831,16 @@ static struct dvb_usb_device_properties cxusb_medion_properties = {
 
 	.usb_ctrl = CYPRESS_FX2,
 
-	.size_of_priv     = sizeof(struct cxusb_state),
+	.size_of_priv     = sizeof(struct cxusb_medion_dev),
+	.priv_init        = cxusb_medion_priv_init,
+	.priv_destroy     = cxusb_medion_priv_destroy,
 
 	.num_adapters = 1,
 	.adapter = {
 		{
 		.num_frontends = 1,
 		.fe = {{
+			.caps = DVB_USB_ADAP_STREAMING_CTRL_NO_URB,
 			.streaming_ctrl   = cxusb_streaming_ctrl,
 			.frontend_attach  = cxusb_cx22702_frontend_attach,
 			.tuner_attach     = cxusb_fmd1216me_tuner_attach,
@@ -2198,6 +2547,7 @@ module_usb_driver(cxusb_driver);
 MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
 MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>");
 MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>");
+MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>");
 MODULE_DESCRIPTION("Driver for Conexant USB2.0 hybrid reference design");
 MODULE_VERSION("1.0-alpha");
 MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/cxusb.h b/drivers/media/usb/dvb-usb/cxusb.h
index 88f9b9804b25..f586d61a7bf8 100644
--- a/drivers/media/usb/dvb-usb/cxusb.h
+++ b/drivers/media/usb/dvb-usb/cxusb.h
@@ -2,6 +2,9 @@
 #ifndef _DVB_USB_CXUSB_H_
 #define _DVB_USB_CXUSB_H_
 
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+
 #define DVB_USB_LOG_PREFIX "cxusb"
 #include "dvb-usb.h"
 
@@ -34,6 +37,7 @@
 
 struct cxusb_state {
 	u8 gpio_write_state[3];
+	bool gpio_write_refresh[3];
 	struct i2c_client *i2c_client_demod;
 	struct i2c_client *i2c_client_tuner;
 
@@ -45,4 +49,48 @@ struct cxusb_state {
 		enum fe_status *status);
 };
 
+enum cxusb_open_type {
+	CXUSB_OPEN_INIT, CXUSB_OPEN_NONE,
+	CXUSB_OPEN_ANALOG, CXUSB_OPEN_DIGITAL
+};
+
+struct cxusb_medion_dev {
+	/* has to be the first one */
+	struct cxusb_state state;
+
+	struct dvb_usb_device *dvbdev;
+
+	enum cxusb_open_type open_type;
+	unsigned int open_ctr;
+	struct mutex open_lock;
+};
+
+/* defines for "debug" module parameter */
+#define CXUSB_DBG_RC BIT(0)
+#define CXUSB_DBG_I2C BIT(1)
+#define CXUSB_DBG_MISC BIT(2)
+
+extern int dvb_usb_cxusb_debug;
+
+int cxusb_ctrl_msg(struct dvb_usb_device *d,
+		   u8 cmd, const u8 *wbuf, int wlen, u8 *rbuf, int rlen);
+
+static inline int cxusb_medion_analog_init(struct dvb_usb_device *dvbdev)
+{
+	return -EINVAL;
+}
+
+static inline int cxusb_medion_register_analog(struct dvb_usb_device *dvbdev)
+{
+	return 0;
+}
+
+static inline void cxusb_medion_unregister_analog(struct dvb_usb_device *dvbdev)
+{
+}
+
+int cxusb_medion_get(struct dvb_usb_device *dvbdev,
+		     enum cxusb_open_type open_type);
+void cxusb_medion_put(struct dvb_usb_device *dvbdev);
+
 #endif
diff --git a/drivers/media/usb/dvb-usb/dvb-usb-dvb.c b/drivers/media/usb/dvb-usb/dvb-usb-dvb.c
index 8056053c9ab0..01987ec5e0c5 100644
--- a/drivers/media/usb/dvb-usb/dvb-usb-dvb.c
+++ b/drivers/media/usb/dvb-usb/dvb-usb-dvb.c
@@ -15,6 +15,7 @@ static int dvb_usb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff)
 {
 	struct dvb_usb_adapter *adap = dvbdmxfeed->demux->priv;
 	int newfeedcount, ret;
+	bool streaming_ctrl_no_urb;
 
 	if (adap == NULL)
 		return -ENODEV;
@@ -24,12 +25,16 @@ static int dvb_usb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff)
 		return -EINVAL;
 	}
 
+	streaming_ctrl_no_urb = adap->props.fe[adap->active_fe].caps &
+		DVB_USB_ADAP_STREAMING_CTRL_NO_URB;
 	newfeedcount = adap->feedcount + (onoff ? 1 : -1);
 
 	/* stop feed before setting a new pid if there will be no pid anymore */
 	if (newfeedcount == 0) {
 		deb_ts("stop feeding\n");
-		usb_urb_kill(&adap->fe_adap[adap->active_fe].stream);
+
+		if (streaming_ctrl_no_urb)
+			usb_urb_kill(&adap->fe_adap[adap->active_fe].stream);
 
 		if (adap->props.fe[adap->active_fe].streaming_ctrl != NULL) {
 			ret = adap->props.fe[adap->active_fe].streaming_ctrl(adap, 0);
@@ -38,6 +43,9 @@ static int dvb_usb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff)
 				return ret;
 			}
 		}
+
+		if (!streaming_ctrl_no_urb)
+			usb_urb_kill(&adap->fe_adap[adap->active_fe].stream);
 	}
 
 	adap->feedcount = newfeedcount;
@@ -56,8 +64,10 @@ static int dvb_usb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff)
 	 * for reception.
 	 */
 	if (adap->feedcount == onoff && adap->feedcount > 0) {
-		deb_ts("submitting all URBs\n");
-		usb_urb_submit(&adap->fe_adap[adap->active_fe].stream);
+		if (!streaming_ctrl_no_urb) {
+			deb_ts("submitting all URBs early\n");
+			usb_urb_submit(&adap->fe_adap[adap->active_fe].stream);
+		}
 
 		deb_ts("controlling pid parser\n");
 		if (adap->props.fe[adap->active_fe].caps & DVB_USB_ADAP_HAS_PID_FILTER &&
@@ -80,6 +90,10 @@ static int dvb_usb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff)
 			}
 		}
 
+		if (streaming_ctrl_no_urb) {
+			deb_ts("submitting all URBs late\n");
+			usb_urb_submit(&adap->fe_adap[adap->active_fe].stream);
+		}
 	}
 	return 0;
 }
diff --git a/drivers/media/usb/dvb-usb/dvb-usb-init.c b/drivers/media/usb/dvb-usb/dvb-usb-init.c
index 40ca4eafb137..dddf4eea8934 100644
--- a/drivers/media/usb/dvb-usb/dvb-usb-init.c
+++ b/drivers/media/usb/dvb-usb/dvb-usb-init.c
@@ -133,6 +133,10 @@ static int dvb_usb_exit(struct dvb_usb_device *d)
 	dvb_usb_i2c_exit(d);
 	deb_info("state should be zero now: %x\n", d->state);
 	d->state = DVB_USB_STATE_INIT;
+
+	if (d->priv != NULL && d->props.priv_destroy != NULL)
+		d->props.priv_destroy(d);
+
 	kfree(d->priv);
 	kfree(d);
 	return 0;
@@ -154,6 +158,15 @@ static int dvb_usb_init(struct dvb_usb_device *d, short *adapter_nums)
 			err("no memory for priv in 'struct dvb_usb_device'");
 			return -ENOMEM;
 		}
+
+		if (d->props.priv_init != NULL) {
+			ret = d->props.priv_init(d);
+			if (ret != 0) {
+				kfree(d->priv);
+				d->priv = NULL;
+				return ret;
+			}
+		}
 	}
 
 	/* check the capabilities and set appropriate variables */
diff --git a/drivers/media/usb/dvb-usb/dvb-usb.h b/drivers/media/usb/dvb-usb/dvb-usb.h
index 317ed6a82d19..b2ae6849f138 100644
--- a/drivers/media/usb/dvb-usb/dvb-usb.h
+++ b/drivers/media/usb/dvb-usb/dvb-usb.h
@@ -143,6 +143,7 @@ struct dvb_usb_adapter_fe_properties {
 #define DVB_USB_ADAP_NEED_PID_FILTERING           0x04
 #define DVB_USB_ADAP_RECEIVES_204_BYTE_TS         0x08
 #define DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD         0x10
+#define DVB_USB_ADAP_STREAMING_CTRL_NO_URB        0x20
 	int caps;
 	int pid_filter_count;
 
@@ -234,6 +235,11 @@ enum dvb_usb_mode {
  *
  * @size_of_priv: how many bytes shall be allocated for the private field
  *  of struct dvb_usb_device.
+ * @priv_init: optional callback to initialize the variable that private field
+ * of struct dvb_usb_device has pointer to just after it had been allocated and
+ * zeroed.
+ * @priv_destroy: just like priv_init, only called before deallocating
+ * the memory pointed by private field of struct dvb_usb_device.
  *
  * @power_ctrl: called to enable/disable power of the device.
  * @read_mac_address: called to read the MAC address of the device.
@@ -275,6 +281,8 @@ struct dvb_usb_device_properties {
 	int        no_reconnect;
 
 	int size_of_priv;
+	int (*priv_init)(struct dvb_usb_device *);
+	void (*priv_destroy)(struct dvb_usb_device *);
 
 	int num_adapters;
 	struct dvb_usb_adapter_properties adapter[MAX_NO_OF_ADAPTER_PER_DEVICE];

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v7 6/6] [media] cxusb: add analog mode support for Medion MD95700
  2018-07-02 21:23 [PATCH v7 0/6] [media] Add analog mode support for Medion MD95700 Maciej S. Szmigiero
                   ` (4 preceding siblings ...)
  2018-07-02 21:23 ` [PATCH v7 5/6] [media] cxusb: implement Medion MD95700 digital / analog coexistence Maciej S. Szmigiero
@ 2018-07-02 21:23 ` Maciej S. Szmigiero
  2018-07-04  9:33   ` Hans Verkuil
  2018-07-04  9:33 ` [PATCH v7 0/6] [media] Add " Hans Verkuil
  6 siblings, 1 reply; 14+ messages in thread
From: Maciej S. Szmigiero @ 2018-07-02 21:23 UTC (permalink / raw)
  To: Michael Krufky, Mauro Carvalho Chehab
  Cc: Andy Walls, linux-kernel, linux-media, Hans Verkuil

This patch adds support for analog part of Medion 95700 in the cxusb
driver.

What works:
* Video capture at various sizes with sequential fields,
* Input switching (TV Tuner, Composite, S-Video),
* TV and radio tuning,
* Video standard switching and auto detection,
* Radio mode switching (stereo / mono),
* Unplugging while capturing,
* DVB / analog coexistence,
* Raw BT.656 stream support.

What does not work yet:
* Audio,
* VBI,
* Picture controls.

Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
 drivers/media/usb/dvb-usb/Kconfig        |   16 +-
 drivers/media/usb/dvb-usb/Makefile       |    3 +
 drivers/media/usb/dvb-usb/cxusb-analog.c | 1914 ++++++++++++++++++++++
 drivers/media/usb/dvb-usb/cxusb.c        |    2 -
 drivers/media/usb/dvb-usb/cxusb.h        |  106 ++
 5 files changed, 2037 insertions(+), 4 deletions(-)
 create mode 100644 drivers/media/usb/dvb-usb/cxusb-analog.c

diff --git a/drivers/media/usb/dvb-usb/Kconfig b/drivers/media/usb/dvb-usb/Kconfig
index b8a1c62a0682..010d00ddeb05 100644
--- a/drivers/media/usb/dvb-usb/Kconfig
+++ b/drivers/media/usb/dvb-usb/Kconfig
@@ -138,12 +138,24 @@ config DVB_USB_CXUSB
 	select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Say Y here to support the Conexant USB2.0 hybrid reference design.
-	  Currently, only DVB and ATSC modes are supported, analog mode
-	  shall be added in the future. Devices that require this module:
+	  DVB and ATSC modes are supported, for a basic analog mode support
+	  see the next option ("Analog support for the Conexant USB2.0 hybrid
+	  reference design").
+	  Devices that require this module:
 
 	  Medion MD95700 hybrid USB2.0 device.
 	  DViCO FusionHDTV (Bluebird) USB2.0 devices
 
+config DVB_USB_CXUSB_ANALOG
+	bool "Analog support for the Conexant USB2.0 hybrid reference design"
+	depends on DVB_USB_CXUSB && VIDEO_V4L2
+	select VIDEO_CX25840
+	select VIDEOBUF2_VMALLOC
+	help
+	  Say Y here to enable basic analog mode support for the Conexant
+	  USB2.0 hybrid reference design.
+	  Currently this mode is supported only on a Medion MD95700 device.
+
 config DVB_USB_M920X
 	tristate "Uli m920x DVB-T USB2.0 support"
 	depends on DVB_USB
diff --git a/drivers/media/usb/dvb-usb/Makefile b/drivers/media/usb/dvb-usb/Makefile
index 9ad2618408ef..e47bcadcfc3d 100644
--- a/drivers/media/usb/dvb-usb/Makefile
+++ b/drivers/media/usb/dvb-usb/Makefile
@@ -42,6 +42,9 @@ dvb-usb-digitv-objs := digitv.o
 obj-$(CONFIG_DVB_USB_DIGITV) += dvb-usb-digitv.o
 
 dvb-usb-cxusb-objs := cxusb.o
+ifeq ($(CONFIG_DVB_USB_CXUSB_ANALOG),y)
+dvb-usb-cxusb-objs += cxusb-analog.o
+endif
 obj-$(CONFIG_DVB_USB_CXUSB) += dvb-usb-cxusb.o
 
 dvb-usb-ttusb2-objs := ttusb2.o
diff --git a/drivers/media/usb/dvb-usb/cxusb-analog.c b/drivers/media/usb/dvb-usb/cxusb-analog.c
new file mode 100644
index 000000000000..969d82b24f41
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/cxusb-analog.c
@@ -0,0 +1,1914 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// DVB USB compliant linux driver for Conexant USB reference design -
+// (analog part).
+//
+// Copyright (C) 2011, 2017 Maciej S. Szmigiero (mail@maciej.szmigiero.name)
+//
+// TODO:
+//  * audio support,
+//  * finish radio support (requires audio of course),
+//  * VBI support,
+//  * controls support
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timekeeping.h>
+#include <linux/vmalloc.h>
+#include <media/drv-intf/cx25840.h>
+#include <media/tuner.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-subdev.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "cxusb.h"
+
+static int cxusb_medion_v_queue_setup(struct vb2_queue *q,
+				      unsigned int *num_buffers,
+				      unsigned int *num_planes,
+				      unsigned int sizes[],
+				      struct device *alloc_devs[])
+{
+	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	unsigned int size = cxdev->raw_mode ?
+		CXUSB_VIDEO_MAX_FRAME_SIZE :
+		cxdev->width * cxdev->height * 2;
+
+	if (*num_planes > 0) {
+		if (*num_planes != 1)
+			return -EINVAL;
+
+		if (sizes[0] < size)
+			return -EINVAL;
+	} else {
+		*num_planes = 1;
+		sizes[0] = size;
+	}
+
+	if (q->num_buffers + *num_buffers < 6)
+		*num_buffers = 6 - q->num_buffers;
+
+	return 0;
+}
+
+static int cxusb_medion_v_buf_init(struct vb2_buffer *vb)
+{
+	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(vb->vb2_queue);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	cxusb_vprintk(dvbdev, OPS, "buffer init\n");
+
+	if (cxdev->raw_mode) {
+		if (vb2_plane_size(vb, 0) < CXUSB_VIDEO_MAX_FRAME_SIZE)
+			return -ENOMEM;
+	} else {
+		if (vb2_plane_size(vb, 0) < cxdev->width * cxdev->height * 2)
+			return -ENOMEM;
+	}
+
+	cxusb_vprintk(dvbdev, OPS, "buffer OK\n");
+
+	return 0;
+}
+
+static void cxusb_auxbuf_init(struct cxusb_medion_auxbuf *auxbuf,
+			      u8 *buf, unsigned int len)
+{
+	auxbuf->buf = buf;
+	auxbuf->len = len;
+	auxbuf->paylen = 0;
+}
+
+static void cxusb_auxbuf_head_trim(struct dvb_usb_device *dvbdev,
+				   struct cxusb_medion_auxbuf *auxbuf,
+				   unsigned int pos)
+{
+	if (pos == 0)
+		return;
+
+	if (WARN_ON(pos > auxbuf->paylen))
+		return;
+
+	cxusb_vprintk(dvbdev, AUXB,
+		      "trimming auxbuf len by %u to %u\n",
+		      pos, auxbuf->paylen - pos);
+
+	memmove(auxbuf->buf, auxbuf->buf + pos, auxbuf->paylen - pos);
+	auxbuf->paylen -= pos;
+}
+
+static unsigned int cxusb_auxbuf_paylen(struct cxusb_medion_auxbuf *auxbuf)
+{
+	return auxbuf->paylen;
+}
+
+static bool cxusb_auxbuf_make_space(struct dvb_usb_device *dvbdev,
+				    struct cxusb_medion_auxbuf *auxbuf,
+				    unsigned int howmuch)
+{
+	unsigned int freespace;
+
+	if (WARN_ON(howmuch >= auxbuf->len))
+		howmuch = auxbuf->len - 1;
+
+	freespace = auxbuf->len - cxusb_auxbuf_paylen(auxbuf);
+
+	cxusb_vprintk(dvbdev, AUXB, "freespace is %u\n", freespace);
+
+	if (freespace >= howmuch)
+		return true;
+
+	howmuch -= freespace;
+
+	cxusb_vprintk(dvbdev, AUXB, "will overwrite %u bytes of buffer\n",
+		      howmuch);
+
+	cxusb_auxbuf_head_trim(dvbdev, auxbuf, howmuch);
+
+	return false;
+}
+
+/* returns false if some data was overwritten */
+static bool cxusb_auxbuf_append_urb(struct dvb_usb_device *dvbdev,
+				    struct cxusb_medion_auxbuf *auxbuf,
+				    struct urb *urb)
+{
+	unsigned long len = 0;
+	int i;
+	bool ret;
+
+	for (i = 0; i < urb->number_of_packets; i++)
+		len += urb->iso_frame_desc[i].actual_length;
+
+	ret = cxusb_auxbuf_make_space(dvbdev, auxbuf, len);
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		unsigned int to_copy;
+
+		to_copy = urb->iso_frame_desc[i].actual_length;
+
+		memcpy(auxbuf->buf + auxbuf->paylen, urb->transfer_buffer +
+		       urb->iso_frame_desc[i].offset, to_copy);
+
+		auxbuf->paylen += to_copy;
+	}
+
+	return ret;
+}
+
+static bool cxusb_auxbuf_copy(struct cxusb_medion_auxbuf *auxbuf,
+			      unsigned int pos, unsigned char *dest,
+			      unsigned int len)
+{
+	if (pos + len > auxbuf->paylen)
+		return false;
+
+	memcpy(dest, auxbuf->buf + pos, len);
+
+	return true;
+}
+
+static unsigned int cxusb_auxbuf_advance(struct cxusb_medion_auxbuf *auxbuf,
+					 unsigned int pos,
+					 unsigned int increment)
+{
+	return pos + increment;
+}
+
+static unsigned int cxusb_auxbuf_begin(struct cxusb_medion_auxbuf *auxbuf)
+{
+	return 0;
+}
+
+static bool cxusb_auxbuf_isend(struct cxusb_medion_auxbuf *auxbuf,
+			       unsigned int pos)
+{
+	return pos >= auxbuf->paylen;
+}
+
+static bool cxusb_medion_cf_refc_fld_chg(struct dvb_usb_device *dvbdev,
+					 struct cxusb_bt656_params *bt656,
+					 bool firstfield,
+					 unsigned int maxlines,
+					 unsigned int maxlinesamples,
+					 unsigned char buf[4])
+{
+	bool firstfield_code = (buf[3] & CXUSB_BT656_FIELD_MASK) ==
+		CXUSB_BT656_FIELD_1;
+	unsigned int remlines;
+
+	if (bt656->line == 0 || firstfield == firstfield_code)
+		return false;
+
+	if (bt656->fmode == LINE_SAMPLES) {
+		unsigned int remsamples = maxlinesamples -
+			bt656->linesamples;
+
+		cxusb_vprintk(dvbdev, BT656,
+			      "field %c after line %u field change\n",
+			      firstfield ? '1' : '2', bt656->line);
+
+		if (bt656->buf != NULL && remsamples > 0) {
+			memset(bt656->buf, 0, remsamples);
+			bt656->buf += remsamples;
+
+			cxusb_vprintk(dvbdev, BT656,
+				      "field %c line %u %u samples still remaining (of %u)\n",
+				      firstfield ? '1' : '2',
+				      bt656->line, remsamples,
+				      maxlinesamples);
+		}
+
+		bt656->line++;
+	}
+
+	remlines = maxlines - bt656->line;
+	if (bt656->buf != NULL && remlines > 0) {
+		memset(bt656->buf, 0, remlines * maxlinesamples);
+		bt656->buf += remlines * maxlinesamples;
+
+		cxusb_vprintk(dvbdev, BT656,
+			      "field %c %u lines still remaining (of %u)\n",
+			      firstfield ? '1' : '2', remlines,
+			      maxlines);
+	}
+
+	return true;
+}
+
+static bool cxusb_medion_cf_refc_start_sch(struct dvb_usb_device *dvbdev,
+					   struct cxusb_medion_auxbuf *auxbuf,
+					   struct cxusb_bt656_params *bt656,
+					   bool firstfield,
+					   unsigned char buf[4])
+{
+	bool firstfield_code = (buf[3] & CXUSB_BT656_FIELD_MASK) ==
+		CXUSB_BT656_FIELD_1;
+	bool sav_code = (buf[3] & CXUSB_BT656_SEAV_MASK) ==
+		CXUSB_BT656_SEAV_SAV;
+	bool vbi_code = (buf[3] & CXUSB_BT656_VBI_MASK) ==
+		CXUSB_BT656_VBI_ON;
+
+	if (bt656->fmode != START_SEARCH)
+		return false;
+
+	if (sav_code && firstfield == firstfield_code) {
+		if (!vbi_code) {
+			cxusb_vprintk(dvbdev, BT656, "line start @ pos %x\n",
+				      bt656->pos);
+
+			bt656->linesamples = 0;
+			bt656->fmode = LINE_SAMPLES;
+		} else {
+			cxusb_vprintk(dvbdev, BT656, "VBI start @ pos %x\n",
+				      bt656->pos);
+
+			bt656->fmode = VBI_SAMPLES;
+		}
+	}
+
+	bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 4);
+
+	return true;
+}
+
+static bool cxusb_medion_cf_refc_line_smpl(struct dvb_usb_device *dvbdev,
+					   struct cxusb_bt656_params *bt656,
+					   bool firstfield,
+					   unsigned int maxlinesamples,
+					   unsigned char buf[4])
+{
+	bool sav_code = (buf[3] & CXUSB_BT656_SEAV_MASK) ==
+		CXUSB_BT656_SEAV_SAV;
+	unsigned int remsamples;
+
+	if (bt656->fmode != LINE_SAMPLES)
+		return false;
+
+	if (sav_code)
+		cxusb_vprintk(dvbdev, BT656,
+			      "SAV in line samples @ line %u, pos %x\n",
+			      bt656->line, bt656->pos);
+
+	remsamples = maxlinesamples - bt656->linesamples;
+	if (bt656->buf != NULL && remsamples > 0) {
+		memset(bt656->buf, 0, remsamples);
+		bt656->buf += remsamples;
+
+		cxusb_vprintk(dvbdev, BT656,
+			      "field %c line %u %u samples still remaining (of %u)\n",
+			      firstfield ? '1' : '2', bt656->line, remsamples,
+			      maxlinesamples);
+	}
+
+	bt656->fmode = START_SEARCH;
+	bt656->line++;
+
+	return true;
+}
+
+static bool cxusb_medion_cf_refc_vbi_smpl(struct dvb_usb_device *dvbdev,
+					  struct cxusb_bt656_params *bt656,
+					  unsigned char buf[4])
+{
+	bool sav_code = (buf[3] & CXUSB_BT656_SEAV_MASK) ==
+		CXUSB_BT656_SEAV_SAV;
+
+	if (bt656->fmode != VBI_SAMPLES)
+		return false;
+
+	if (sav_code)
+		cxusb_vprintk(dvbdev, BT656, "SAV in VBI samples @ pos %x\n",
+			      bt656->pos);
+
+	bt656->fmode = START_SEARCH;
+
+	return true;
+}
+
+static void cxusb_medion_cf_ref_code(struct dvb_usb_device *dvbdev,
+				     struct cxusb_medion_auxbuf *auxbuf,
+				     struct cxusb_bt656_params *bt656,
+				     bool firstfield,
+				     unsigned int maxlines,
+				     unsigned int maxlinesamples,
+				     unsigned char buf[4])
+{
+	if (cxusb_medion_cf_refc_start_sch(dvbdev, auxbuf, bt656, firstfield,
+					   buf))
+		return;
+
+	if (cxusb_medion_cf_refc_line_smpl(dvbdev, bt656, firstfield,
+					   maxlinesamples, buf))
+		return;
+
+	if (cxusb_medion_cf_refc_vbi_smpl(dvbdev, bt656, buf))
+		return;
+
+	bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 4);
+}
+
+static bool cxusb_medion_cs_start_sch(struct dvb_usb_device *dvbdev,
+				      struct cxusb_medion_auxbuf *auxbuf,
+				      struct cxusb_bt656_params *bt656,
+				      unsigned int maxlinesamples)
+{
+	unsigned char buf[64];
+	unsigned int idx;
+	unsigned int tocheck = clamp_t(size_t, maxlinesamples / 4, 3,
+				       sizeof(buf));
+
+	if (bt656->fmode != START_SEARCH || bt656->line == 0)
+		return false;
+
+	if (!cxusb_auxbuf_copy(auxbuf, bt656->pos + 1, buf, tocheck)) {
+		bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 1);
+		return true;
+	}
+
+	for (idx = 0; idx <= tocheck - 3; idx++)
+		if (memcmp(buf + idx, CXUSB_BT656_PREAMBLE, 3) == 0)
+			break;
+
+	if (idx <= tocheck - 3) {
+		bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 1);
+		return true;
+	}
+
+	cxusb_vprintk(dvbdev, BT656, "line %u early start, pos %x\n",
+		      bt656->line, bt656->pos);
+
+	bt656->linesamples = 0;
+	bt656->fmode = LINE_SAMPLES;
+
+	return true;
+}
+
+static bool cxusb_medion_cs_line_smpl(struct dvb_usb_device *dvbdev,
+				      struct cxusb_medion_auxbuf *auxbuf,
+				      struct cxusb_bt656_params *bt656,
+				      unsigned int maxlinesamples,
+				      unsigned char val)
+{
+	if (bt656->fmode != LINE_SAMPLES)
+		return false;
+
+	if (bt656->buf != NULL)
+		*(bt656->buf++) = val;
+
+	bt656->linesamples++;
+	bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 1);
+
+	if (bt656->linesamples >= maxlinesamples) {
+		bt656->fmode = START_SEARCH;
+		bt656->line++;
+	}
+
+	return true;
+}
+
+static void cxusb_medion_copy_samples(struct dvb_usb_device *dvbdev,
+				      struct cxusb_medion_auxbuf *auxbuf,
+				      struct cxusb_bt656_params *bt656,
+				      unsigned int maxlinesamples,
+				      unsigned char val)
+{
+	if (cxusb_medion_cs_start_sch(dvbdev, auxbuf, bt656, maxlinesamples))
+		return;
+
+	if (cxusb_medion_cs_line_smpl(dvbdev, auxbuf, bt656, maxlinesamples,
+				      val))
+		return;
+
+	/* TODO: copy VBI samples */
+	bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 1);
+}
+
+static bool cxusb_medion_copy_field(struct dvb_usb_device *dvbdev,
+				    struct cxusb_medion_auxbuf *auxbuf,
+				    struct cxusb_bt656_params *bt656,
+				    bool firstfield,
+				    unsigned int maxlines,
+				    unsigned int maxlinesmpls)
+{
+	while (bt656->line < maxlines &&
+	       !cxusb_auxbuf_isend(auxbuf, bt656->pos)) {
+		unsigned char val;
+
+		if (!cxusb_auxbuf_copy(auxbuf, bt656->pos, &val, 1))
+			return false;
+
+		if (val == CXUSB_BT656_PREAMBLE[0]) {
+			unsigned char buf[4];
+
+			buf[0] = val;
+			if (!cxusb_auxbuf_copy(auxbuf, bt656->pos + 1,
+					       buf + 1, 3))
+				return false;
+
+			if (buf[1] == CXUSB_BT656_PREAMBLE[1] &&
+			    buf[2] == CXUSB_BT656_PREAMBLE[2]) {
+				if (cxusb_medion_cf_refc_fld_chg(dvbdev,
+								 bt656,
+								 firstfield,
+								 maxlines,
+								 maxlinesmpls,
+								 buf))
+					return true;
+
+				cxusb_medion_cf_ref_code(dvbdev, auxbuf,
+							 bt656, firstfield,
+							 maxlines,
+							 maxlinesmpls, buf);
+
+				continue;
+			}
+		}
+
+		cxusb_medion_copy_samples(dvbdev, auxbuf, bt656, maxlinesmpls,
+					  val);
+	}
+
+	if (bt656->line < maxlines) {
+		cxusb_vprintk(dvbdev, BT656,
+			      "end of buffer pos = %u, line = %u\n",
+			      bt656->pos, bt656->line);
+		return false;
+	}
+
+	return true;
+}
+
+static void cxusb_medion_v_process_urb_raw_mode(struct cxusb_medion_dev *cxdev,
+						struct urb *urb)
+{
+	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
+	u8 *buf;
+	struct cxusb_medion_vbuffer *vbuf;
+	int i;
+	unsigned long len = 0;
+
+	if (list_empty(&cxdev->buflist)) {
+		dev_warn(&dvbdev->udev->dev, "no free buffers\n");
+		return;
+	}
+
+	vbuf = list_first_entry(&cxdev->buflist, struct cxusb_medion_vbuffer,
+				list);
+	list_del(&vbuf->list);
+
+	vbuf->vb2.timestamp = ktime_get_ns();
+
+	buf = vb2_plane_vaddr(&vbuf->vb2, 0);
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		memcpy(buf, urb->transfer_buffer +
+		       urb->iso_frame_desc[i].offset,
+		       urb->iso_frame_desc[i].actual_length);
+
+		buf += urb->iso_frame_desc[i].actual_length;
+		len += urb->iso_frame_desc[i].actual_length;
+	}
+
+	vb2_set_plane_payload(&vbuf->vb2, 0, len);
+
+	vb2_buffer_done(&vbuf->vb2, VB2_BUF_STATE_DONE);
+}
+
+static void cxusb_medion_v_process_urb(struct cxusb_medion_dev *cxdev,
+				       struct urb *urb)
+{
+	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
+	struct cxusb_bt656_params *bt656 = &cxdev->bt656;
+	bool reset;
+
+	cxusb_vprintk(dvbdev, URB, "appending urb\n");
+
+	/*
+	 * append new data to circ. buffer
+	 * overwrite old data if necessary
+	 */
+	reset = !cxusb_auxbuf_append_urb(dvbdev, &cxdev->auxbuf, urb);
+
+	/*
+	 * if this is a new frame
+	 * fetch a buffer from list
+	 */
+	if (bt656->mode == NEW_FRAME) {
+		if (!list_empty(&cxdev->buflist)) {
+			cxdev->vbuf =
+				list_first_entry(&cxdev->buflist,
+						 struct cxusb_medion_vbuffer,
+						 list);
+			list_del(&cxdev->vbuf->list);
+
+			cxdev->vbuf->vb2.timestamp = ktime_get_ns();
+		} else
+			dev_warn(&dvbdev->udev->dev, "no free buffers\n");
+	}
+
+	if (bt656->mode == NEW_FRAME || reset) {
+		bt656->pos = cxusb_auxbuf_begin(&cxdev->auxbuf);
+		bt656->mode = FIRST_FIELD;
+		bt656->fmode = START_SEARCH;
+		bt656->line = 0;
+
+		if (cxdev->vbuf != NULL)
+			bt656->buf = vb2_plane_vaddr(&cxdev->vbuf->vb2, 0);
+	}
+
+	cxusb_vprintk(dvbdev, URB, "auxbuf payload len %u",
+		      cxusb_auxbuf_paylen(&cxdev->auxbuf));
+
+	if (bt656->mode == FIRST_FIELD) {
+		cxusb_vprintk(dvbdev, URB, "copying field 1\n");
+
+		if (!cxusb_medion_copy_field(dvbdev, &cxdev->auxbuf, bt656,
+					     true, cxdev->height / 2,
+					     cxdev->width * 2))
+			return;
+
+		/*
+		 * do not trim buffer there in case
+		 * we need to reset search later
+		 */
+		bt656->mode = SECOND_FIELD;
+		bt656->fmode = START_SEARCH;
+		bt656->line = 0;
+	}
+
+	if (bt656->mode == SECOND_FIELD) {
+		cxusb_vprintk(dvbdev, URB, "copying field 2\n");
+
+		if (!cxusb_medion_copy_field(dvbdev, &cxdev->auxbuf, bt656,
+					     false, cxdev->height / 2,
+					     cxdev->width * 2))
+			return;
+
+		cxusb_auxbuf_head_trim(dvbdev, &cxdev->auxbuf, bt656->pos);
+
+		bt656->mode = NEW_FRAME;
+
+		if (cxdev->vbuf != NULL) {
+			vb2_set_plane_payload(&cxdev->vbuf->vb2, 0,
+					      cxdev->width * cxdev->height * 2);
+
+			vb2_buffer_done(&cxdev->vbuf->vb2, VB2_BUF_STATE_DONE);
+
+			cxdev->vbuf = NULL;
+			cxdev->bt656.buf = NULL;
+
+			cxusb_vprintk(dvbdev, URB, "frame done\n");
+		} else
+			cxusb_vprintk(dvbdev, URB, "frame skipped\n");
+	}
+}
+
+static void cxusb_medion_v_complete_work(struct work_struct *work)
+{
+	struct cxusb_medion_dev *cxdev = container_of(work,
+						      struct cxusb_medion_dev,
+						      urbwork);
+	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
+	struct urb *urb;
+	int ret;
+	unsigned int i, urbn;
+
+	mutex_lock(cxdev->videodev->lock);
+
+	cxusb_vprintk(dvbdev, URB, "worker called, streaming = %d\n",
+		      (int)cxdev->streaming);
+
+	if (!cxdev->streaming)
+		goto unlock;
+
+	urbn = cxdev->nexturb;
+	if (test_bit(urbn, &cxdev->urbcomplete)) {
+		urb = cxdev->streamurbs[urbn];
+		clear_bit(urbn, &cxdev->urbcomplete);
+
+		cxdev->nexturb++;
+		cxdev->nexturb %= CXUSB_VIDEO_URBS;
+	} else {
+		for (i = 0, urbn++; i < CXUSB_VIDEO_URBS - 1; i++, urbn++) {
+			urbn %= CXUSB_VIDEO_URBS;
+			if (test_bit(urbn, &cxdev->urbcomplete)) {
+				urb = cxdev->streamurbs[urbn];
+				clear_bit(urbn, &cxdev->urbcomplete);
+				break;
+			}
+		}
+
+		if (i >= CXUSB_VIDEO_URBS - 1) {
+			cxusb_vprintk(dvbdev, URB,
+				      "URB worker called but no URB ready\n");
+			goto unlock;
+		}
+
+		cxusb_vprintk(dvbdev, URB,
+			      "out-of-order URB: expected %u but %u is ready\n",
+			      cxdev->nexturb, urbn);
+
+		cxdev->nexturb = urbn + 1;
+		cxdev->nexturb %= CXUSB_VIDEO_URBS;
+	}
+
+	cxusb_vprintk(dvbdev, URB, "URB %u status = %d\n", urbn, urb->status);
+
+	if (urb->status == 0 || urb->status == -EXDEV) {
+		int i;
+		unsigned long len = 0;
+
+		for (i = 0; i < urb->number_of_packets; i++)
+			len += urb->iso_frame_desc[i].actual_length;
+
+		cxusb_vprintk(dvbdev, URB, "URB %u data len = %lu\n",
+			      urbn, len);
+
+		if (len > 0) {
+			if (cxdev->raw_mode)
+				cxusb_medion_v_process_urb_raw_mode(cxdev, urb);
+			else
+				cxusb_medion_v_process_urb(cxdev, urb);
+		}
+	}
+
+	cxusb_vprintk(dvbdev, URB, "URB %u submit\n", urbn);
+
+	ret = usb_submit_urb(urb, GFP_KERNEL);
+	if (ret != 0)
+		dev_err(&dvbdev->udev->dev,
+			"unable to submit URB (%d), you'll have to restart streaming\n",
+			ret);
+
+	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
+		if (test_bit(i, &cxdev->urbcomplete)) {
+			schedule_work(&cxdev->urbwork);
+			break;
+		}
+
+unlock:
+	mutex_unlock(cxdev->videodev->lock);
+}
+
+static void cxusb_medion_v_complete(struct urb *u)
+{
+	struct dvb_usb_device *dvbdev = u->context;
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	unsigned int i;
+
+	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
+		if (cxdev->streamurbs[i] == u)
+			break;
+
+	if (i >= CXUSB_VIDEO_URBS) {
+		dev_err(&dvbdev->udev->dev,
+			"complete on unknown URB\n");
+		return;
+	}
+
+	cxusb_vprintk(dvbdev, URB, "URB %d complete\n", i);
+
+	set_bit(i, &cxdev->urbcomplete);
+	schedule_work(&cxdev->urbwork);
+}
+
+static bool cxusb_medion_stream_busy(struct cxusb_medion_dev *cxdev)
+{
+	int i;
+
+	if (cxdev->streaming)
+		return true;
+
+	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
+		/*
+		 * not streaming but URB is still active -
+		 * stream is being stopped
+		 */
+		if (cxdev->streamurbs[i] != NULL)
+			return true;
+
+	return false;
+}
+
+static void cxusb_medion_return_buffers(struct cxusb_medion_dev *cxdev,
+					bool requeue)
+{
+	struct cxusb_medion_vbuffer *vbuf, *vbuf_tmp;
+
+	list_for_each_entry_safe(vbuf, vbuf_tmp, &cxdev->buflist,
+				 list) {
+		list_del(&vbuf->list);
+		vb2_buffer_done(&vbuf->vb2, requeue ? VB2_BUF_STATE_QUEUED :
+				VB2_BUF_STATE_ERROR);
+	}
+
+	if (cxdev->vbuf != NULL) {
+		vb2_buffer_done(&cxdev->vbuf->vb2, requeue ?
+				VB2_BUF_STATE_QUEUED :
+				VB2_BUF_STATE_ERROR);
+
+		cxdev->vbuf = NULL;
+		cxdev->bt656.buf = NULL;
+	}
+}
+
+static int cxusb_medion_v_start_streaming(struct vb2_queue *q,
+					  unsigned int count)
+{
+	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	u8 streamon_params[2] = { 0x03, 0x00 };
+	int npackets, i;
+	int ret;
+
+	cxusb_vprintk(dvbdev, OPS, "should start streaming\n");
+
+	/* already streaming */
+	if (cxdev->streaming)
+		return 0;
+
+	if (cxusb_medion_stream_busy(cxdev)) {
+		ret = -EBUSY;
+		goto ret_retbufs;
+	}
+
+	ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 1);
+	if (ret != 0) {
+		dev_err(&dvbdev->udev->dev,
+			"unable to start stream (%d)\n", ret);
+		goto ret_retbufs;
+	}
+
+	ret = cxusb_ctrl_msg(dvbdev, CMD_STREAMING_ON, streamon_params, 2,
+			     NULL, 0);
+	if (ret != 0) {
+		dev_err(&dvbdev->udev->dev,
+			"unable to start streaming (%d)\n", ret);
+		goto ret_unstream_cx;
+	}
+
+	if (cxdev->raw_mode)
+		npackets = CXUSB_VIDEO_MAX_FRAME_PKTS;
+	else {
+		u8 *buf;
+		unsigned int urblen, auxbuflen;
+
+		/* has to be less than full frame size */
+		urblen = (cxdev->width * 2 + 4 + 4) * cxdev->height;
+		npackets = urblen / CXUSB_VIDEO_PKT_SIZE;
+		urblen = npackets * CXUSB_VIDEO_PKT_SIZE;
+
+		auxbuflen = (cxdev->width * 2 + 4 + 4) *
+			(cxdev->height + 50 /* VBI lines */) + urblen;
+
+		buf = vmalloc(auxbuflen);
+		if (buf == NULL) {
+			ret = -ENOMEM;
+			goto ret_unstream_md;
+		}
+
+		cxusb_auxbuf_init(&cxdev->auxbuf, buf, auxbuflen);
+	}
+
+	for (i = 0; i < CXUSB_VIDEO_URBS; i++) {
+		int framen;
+		u8 *streambuf;
+		struct urb *surb;
+
+		streambuf = kmalloc(npackets * CXUSB_VIDEO_PKT_SIZE,
+				    GFP_KERNEL);
+		if (streambuf == NULL) {
+			if (i == 0) {
+				ret = -ENOMEM;
+				goto ret_freeab;
+			} else
+				break;
+		}
+
+		surb = usb_alloc_urb(npackets, GFP_KERNEL);
+		if (surb == NULL) {
+			kfree(streambuf);
+			ret = -ENOMEM;
+			goto ret_freeu;
+		}
+
+		cxdev->streamurbs[i] = surb;
+		surb->dev = dvbdev->udev;
+		surb->context = dvbdev;
+		surb->pipe = usb_rcvisocpipe(dvbdev->udev, 2);
+
+		surb->interval = 1;
+		surb->transfer_flags = URB_ISO_ASAP;
+
+		surb->transfer_buffer = streambuf;
+
+		surb->complete = cxusb_medion_v_complete;
+		surb->number_of_packets = npackets;
+		surb->transfer_buffer_length = npackets * CXUSB_VIDEO_PKT_SIZE;
+
+		for (framen = 0; framen < npackets; framen++) {
+			surb->iso_frame_desc[framen].offset =
+				CXUSB_VIDEO_PKT_SIZE * framen;
+
+			surb->iso_frame_desc[framen].length =
+				CXUSB_VIDEO_PKT_SIZE;
+		}
+	}
+
+	cxdev->urbcomplete = 0;
+	cxdev->nexturb = 0;
+	cxdev->vbuf = NULL;
+	cxdev->bt656.mode = NEW_FRAME;
+	cxdev->bt656.buf = NULL;
+
+	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
+		if (cxdev->streamurbs[i] != NULL) {
+			ret = usb_submit_urb(cxdev->streamurbs[i],
+					GFP_KERNEL);
+			if (ret != 0)
+				dev_err(&dvbdev->udev->dev,
+					"URB %d submission failed (%d)\n", i,
+					ret);
+		}
+
+	cxdev->streaming = true;
+
+	return 0;
+
+ret_freeu:
+	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
+		if (cxdev->streamurbs[i] != NULL) {
+			kfree(cxdev->streamurbs[i]->transfer_buffer);
+			usb_free_urb(cxdev->streamurbs[i]);
+			cxdev->streamurbs[i] = NULL;
+		}
+
+ret_freeab:
+	if (!cxdev->raw_mode)
+		vfree(cxdev->auxbuf.buf);
+
+ret_unstream_md:
+	cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
+
+ret_unstream_cx:
+	v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
+
+ret_retbufs:
+	cxusb_medion_return_buffers(cxdev, true);
+
+	return ret;
+}
+
+static void cxusb_medion_v_stop_streaming(struct vb2_queue *q)
+{
+	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	int i, ret;
+
+	cxusb_vprintk(dvbdev, OPS, "should stop streaming\n");
+
+	if (!cxdev->streaming)
+		return;
+
+	cxdev->streaming = false;
+
+	cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
+
+	ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
+	if (ret != 0)
+		dev_err(&dvbdev->udev->dev, "unable to stop stream (%d)\n",
+			ret);
+
+	/* let URB completion run */
+	mutex_unlock(cxdev->videodev->lock);
+
+	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
+		if (cxdev->streamurbs[i] != NULL)
+			usb_kill_urb(cxdev->streamurbs[i]);
+
+	flush_work(&cxdev->urbwork);
+
+	mutex_lock(cxdev->videodev->lock);
+
+	/* free transfer buffer and URB */
+	if (!cxdev->raw_mode)
+		vfree(cxdev->auxbuf.buf);
+
+	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
+		if (cxdev->streamurbs[i] != NULL) {
+			kfree(cxdev->streamurbs[i]->transfer_buffer);
+			usb_free_urb(cxdev->streamurbs[i]);
+			cxdev->streamurbs[i] = NULL;
+		}
+
+	cxusb_medion_return_buffers(cxdev, false);
+}
+
+static void cxusub_medion_v_buf_queue(struct vb2_buffer *vb)
+{
+	struct cxusb_medion_vbuffer *vbuf =
+		container_of(vb, struct cxusb_medion_vbuffer, vb2);
+	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(vb->vb2_queue);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	/* cxusb_vprintk(dvbdev, OPS, "mmmm.. fresh buffer...\n"); */
+
+	list_add_tail(&vbuf->list, &cxdev->buflist);
+}
+
+static void cxusub_medion_v_wait_prepare(struct vb2_queue *q)
+{
+	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	mutex_unlock(cxdev->videodev->lock);
+}
+
+static void cxusub_medion_v_wait_finish(struct vb2_queue *q)
+{
+	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	mutex_lock(cxdev->videodev->lock);
+}
+
+static const struct vb2_ops cxdev_video_qops = {
+	.queue_setup = cxusb_medion_v_queue_setup,
+	.buf_init = cxusb_medion_v_buf_init,
+	.start_streaming = cxusb_medion_v_start_streaming,
+	.stop_streaming = cxusb_medion_v_stop_streaming,
+	.buf_queue = cxusub_medion_v_buf_queue,
+	.wait_prepare = cxusub_medion_v_wait_prepare,
+	.wait_finish = cxusub_medion_v_wait_finish
+};
+
+static int cxusb_medion_v_querycap(struct file *file, void *fh,
+				   struct v4l2_capability *cap)
+{
+	const __u32 videocaps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER |
+		V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+	const __u32 radiocaps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct video_device *vdev = video_devdata(file);
+
+	strncpy(cap->driver, dvbdev->udev->dev.driver->name,
+		sizeof(cap->driver) - 1);
+	strcpy(cap->card, "Medion 95700");
+	usb_make_path(dvbdev->udev, cap->bus_info, sizeof(cap->bus_info));
+
+	if (vdev->vfl_type == VFL_TYPE_GRABBER)
+		cap->device_caps = videocaps;
+	else
+		cap->device_caps = radiocaps;
+
+	cap->capabilities = videocaps | radiocaps | V4L2_CAP_DEVICE_CAPS;
+
+	memset(cap->reserved, 0, sizeof(cap->reserved));
+
+	return 0;
+}
+
+static int cxusb_medion_v_enum_fmt_vid_cap(struct file *file, void *fh,
+					   struct v4l2_fmtdesc *f)
+{
+	if (f->index != 0)
+		return -EINVAL;
+
+	f->flags = 0;
+	strcpy(f->description, "YUV 4:2:2");
+	f->pixelformat = V4L2_PIX_FMT_UYVY;
+	memset(f->reserved, 0, sizeof(f->reserved));
+
+	return 0;
+}
+
+static int cxusb_medion_g_fmt_vid_cap(struct file *file, void *fh,
+				      struct v4l2_format *f)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	f->fmt.pix.width = cxdev->width;
+	f->fmt.pix.height = cxdev->height;
+	f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
+	f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
+	f->fmt.pix.bytesperline = cxdev->raw_mode ? 0 : cxdev->width * 2;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	f->fmt.pix.sizeimage =
+		cxdev->raw_mode ? CXUSB_VIDEO_MAX_FRAME_SIZE :
+		f->fmt.pix.bytesperline * f->fmt.pix.height;
+	f->fmt.pix.priv = 0;
+
+	return 0;
+}
+
+static int cxusb_medion_g_parm(struct file *file, void *fh,
+			       struct v4l2_streamparm *param)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	memset(&param->parm.capture, 0, sizeof(param->parm.capture));
+
+	if (cxdev->raw_mode)
+		param->parm.capture.extendedmode |=
+			CXUSB_EXTENDEDMODE_CAPTURE_RAW;
+
+	param->parm.capture.readbuffers = 1;
+
+	return 0;
+}
+
+static int cxusb_medion_s_parm(struct file *file, void *fh,
+			       struct v4l2_streamparm *param)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	bool want_raw;
+
+	if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	want_raw = param->parm.capture.extendedmode &
+		CXUSB_EXTENDEDMODE_CAPTURE_RAW;
+
+	if (want_raw != cxdev->raw_mode) {
+		if (cxusb_medion_stream_busy(cxdev) ||
+		    vb2_is_busy(&cxdev->videoqueue))
+			return -EBUSY;
+
+		cxdev->raw_mode = want_raw;
+	}
+
+	param->parm.capture.readbuffers = 1;
+
+	return 0;
+}
+
+static int cxusb_medion_try_s_fmt_vid_cap(struct file *file,
+					  struct v4l2_format *f,
+					  bool isset)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	struct v4l2_subdev_format subfmt;
+	int ret;
+
+	if (isset && (cxusb_medion_stream_busy(cxdev) ||
+		      vb2_is_busy(&cxdev->videoqueue)))
+		return -EBUSY;
+
+	memset(&subfmt, 0, sizeof(subfmt));
+	subfmt.which = isset ? V4L2_SUBDEV_FORMAT_ACTIVE :
+		V4L2_SUBDEV_FORMAT_TRY;
+	subfmt.format.width = f->fmt.pix.width & ~1;
+	subfmt.format.height = f->fmt.pix.height & ~1;
+	subfmt.format.code = MEDIA_BUS_FMT_FIXED;
+	subfmt.format.field = V4L2_FIELD_SEQ_TB;
+	subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt);
+	if (ret != 0) {
+		if (ret != -ERANGE)
+			return ret;
+
+		/* try some common formats */
+		subfmt.format.width = 720;
+		subfmt.format.height = 576;
+		ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL,
+				       &subfmt);
+		if (ret != 0) {
+			if (ret != -ERANGE)
+				return ret;
+
+			subfmt.format.width = 640;
+			subfmt.format.height = 480;
+			ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt,
+					       NULL, &subfmt);
+			if (ret != 0)
+				return ret;
+		}
+	}
+
+	f->fmt.pix.width = subfmt.format.width;
+	f->fmt.pix.height = subfmt.format.height;
+	f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
+	f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
+	f->fmt.pix.bytesperline = cxdev->raw_mode ? 0 : f->fmt.pix.width * 2;
+	f->fmt.pix.sizeimage =
+		cxdev->raw_mode ? CXUSB_VIDEO_MAX_FRAME_SIZE :
+		f->fmt.pix.bytesperline * f->fmt.pix.height;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	f->fmt.pix.priv = 0;
+
+	if (isset) {
+		cxdev->width = f->fmt.pix.width;
+		cxdev->height = f->fmt.pix.height;
+	}
+
+	return 0;
+}
+
+static int cxusb_medion_try_fmt_vid_cap(struct file *file, void *fh,
+					struct v4l2_format *f)
+{
+	return cxusb_medion_try_s_fmt_vid_cap(file, f, false);
+}
+
+static int cxusb_medion_s_fmt_vid_cap(struct file *file, void *fh,
+				      struct v4l2_format *f)
+{
+	return cxusb_medion_try_s_fmt_vid_cap(file, f, true);
+}
+
+static const struct {
+	struct v4l2_input input;
+	u32 inputcfg;
+} cxusb_medion_inputs[] = {
+	{ .input = { .name = "TV tuner", .type = V4L2_INPUT_TYPE_TUNER,
+		     .tuner = 0, .std = V4L2_STD_PAL },
+	  .inputcfg = CX25840_COMPOSITE2, },
+
+	{  .input = { .name = "Composite", .type = V4L2_INPUT_TYPE_CAMERA,
+		     .std = V4L2_STD_ALL },
+	   .inputcfg = CX25840_COMPOSITE1, },
+
+	{  .input = { .name = "S-Video", .type = V4L2_INPUT_TYPE_CAMERA,
+		      .std = V4L2_STD_ALL },
+	   .inputcfg = CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }
+};
+
+#define CXUSB_INPUT_CNT ARRAY_SIZE(cxusb_medion_inputs)
+
+static int cxusb_medion_enum_input(struct file *file, void *fh,
+				   struct v4l2_input *inp)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	u32 index = inp->index;
+
+	if (index >= CXUSB_INPUT_CNT)
+		return -EINVAL;
+
+	*inp = cxusb_medion_inputs[index].input;
+	inp->index = index;
+	inp->capabilities |= V4L2_IN_CAP_STD;
+
+	if (index == cxdev->input) {
+		int ret;
+		u32 status = 0;
+
+		ret = v4l2_subdev_call(cxdev->cx25840, video, g_input_status,
+				       &status);
+		if (ret != 0)
+			dev_warn(&dvbdev->udev->dev,
+				 "cx25840 input status query failed (%d)\n",
+				 ret);
+		else
+			inp->status = status;
+	}
+
+	return 0;
+}
+
+static int cxusb_medion_g_input(struct file *file, void *fh,
+				unsigned int *i)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	*i = cxdev->input;
+
+	return 0;
+}
+
+static int cxusb_medion_s_input(struct file *file, void *fh,
+				unsigned int i)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	int ret;
+
+	if (i >= CXUSB_INPUT_CNT)
+		return -EINVAL;
+
+	ret = v4l2_subdev_call(cxdev->cx25840, video, s_routing,
+			       cxusb_medion_inputs[i].inputcfg, 0, 0);
+	if (ret != 0)
+		return ret;
+
+	cxdev->input = i;
+
+	return 0;
+}
+
+static int cxusb_medion_g_tuner(struct file *file, void *fh,
+				struct v4l2_tuner *tuner)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	struct video_device *vdev = video_devdata(file);
+	int ret;
+
+	if (tuner->index != 0)
+		return -EINVAL;
+
+	if (vdev->vfl_type == VFL_TYPE_GRABBER)
+		tuner->type = V4L2_TUNER_ANALOG_TV;
+	else
+		tuner->type = V4L2_TUNER_RADIO;
+
+	tuner->capability = 0;
+	tuner->afc = 0;
+
+	/*
+	 * fills:
+	 * always: capability (static), rangelow (static), rangehigh (static)
+	 * radio mode: afc (may fail silently), rxsubchans (static), audmode
+	 */
+	ret = v4l2_subdev_call(cxdev->tda9887, tuner, g_tuner, tuner);
+	if (ret != 0)
+		return ret;
+
+	/*
+	 * fills:
+	 * always: capability (static), rangelow (static), rangehigh (static)
+	 * radio mode: rxsubchans (always stereo), audmode,
+	 * signal (might be wrong)
+	 */
+	ret = v4l2_subdev_call(cxdev->tuner, tuner, g_tuner, tuner);
+	if (ret != 0)
+		return ret;
+
+	tuner->signal = 0;
+
+	/*
+	 * fills: TV mode: capability, rxsubchans, audmode, signal
+	 */
+	ret = v4l2_subdev_call(cxdev->cx25840, tuner, g_tuner, tuner);
+	if (ret != 0)
+		return ret;
+
+	if (vdev->vfl_type == VFL_TYPE_GRABBER)
+		strcpy(tuner->name, "TV tuner");
+	else
+		strcpy(tuner->name, "Radio tuner");
+
+	memset(tuner->reserved, 0, sizeof(tuner->reserved));
+
+	return 0;
+}
+
+static int cxusb_medion_s_tuner(struct file *file, void *fh,
+				const struct v4l2_tuner *tuner)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	struct video_device *vdev = video_devdata(file);
+	int ret;
+
+	if (tuner->index != 0)
+		return -EINVAL;
+
+	ret = v4l2_subdev_call(cxdev->tda9887, tuner, s_tuner, tuner);
+	if (ret != 0)
+		return ret;
+
+	ret = v4l2_subdev_call(cxdev->tuner, tuner, s_tuner, tuner);
+	if (ret != 0)
+		return ret;
+
+	/*
+	 * make sure that cx25840 is in a correct TV / radio mode,
+	 * since calls above may have changed it for tuner / IF demod
+	 */
+	if (vdev->vfl_type == VFL_TYPE_GRABBER)
+		v4l2_subdev_call(cxdev->cx25840, video, s_std, cxdev->norm);
+	else
+		v4l2_subdev_call(cxdev->cx25840, tuner, s_radio);
+
+	return v4l2_subdev_call(cxdev->cx25840, tuner, s_tuner, tuner);
+}
+
+static int cxusb_medion_g_frequency(struct file *file, void *fh,
+				    struct v4l2_frequency *freq)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	if (freq->tuner != 0)
+		return -EINVAL;
+
+	return v4l2_subdev_call(cxdev->tuner, tuner, g_frequency, freq);
+}
+
+static int cxusb_medion_s_frequency(struct file *file, void *fh,
+				    const struct v4l2_frequency *freq)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	struct video_device *vdev = video_devdata(file);
+	int ret;
+
+	if (freq->tuner != 0)
+		return -EINVAL;
+
+	ret = v4l2_subdev_call(cxdev->tda9887, tuner, s_frequency, freq);
+	if (ret != 0)
+		return ret;
+
+	ret = v4l2_subdev_call(cxdev->tuner, tuner, s_frequency, freq);
+	if (ret != 0)
+		return ret;
+
+	/*
+	 * make sure that cx25840 is in a correct TV / radio mode,
+	 * since calls above may have changed it for tuner / IF demod
+	 */
+	if (vdev->vfl_type == VFL_TYPE_GRABBER)
+		v4l2_subdev_call(cxdev->cx25840, video, s_std, cxdev->norm);
+	else
+		v4l2_subdev_call(cxdev->cx25840, tuner, s_radio);
+
+	return v4l2_subdev_call(cxdev->cx25840, tuner, s_frequency, freq);
+}
+
+static int cxusb_medion_g_std(struct file *file, void *fh,
+			      v4l2_std_id *norm)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	int ret;
+
+	ret = v4l2_subdev_call(cxdev->cx25840, video, g_std, norm);
+	if (ret != 0) {
+		cxusb_vprintk(dvbdev, OPS, "cannot get standard for input %u\n",
+			      (unsigned int)cxdev->input);
+
+		return ret;
+	}
+
+	cxusb_vprintk(dvbdev, OPS,
+		      "current standard for input %u is %lx\n",
+		      (unsigned int)cxdev->input,
+		      (unsigned long)*norm);
+
+	if (cxdev->input == 0)
+		/*
+		 * make sure we don't have improper std bits set
+		 * for TV tuner (could happen when no signal was
+		 * present yet after reset)
+		 */
+		*norm &= V4L2_STD_PAL;
+
+	if (*norm == V4L2_STD_UNKNOWN)
+		return -ENODATA;
+
+	return 0;
+}
+
+static int cxusb_medion_s_std(struct file *file, void *fh,
+			      v4l2_std_id norm)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	int ret;
+
+	cxusb_vprintk(dvbdev, OPS,
+		      "trying to set standard for input %u to %lx\n",
+		      (unsigned int)cxdev->input,
+		      (unsigned long)norm);
+
+	/* on composite or S-Video any std is acceptable */
+	if (cxdev->input != 0) {
+		ret = v4l2_subdev_call(cxdev->cx25840, video, s_std, norm);
+		if (ret)
+			return ret;
+
+		goto ret_savenorm;
+	}
+
+	/* TV tuner is only able to demodulate PAL */
+	if ((norm & ~V4L2_STD_PAL) != 0)
+		return -EINVAL;
+
+	/* no autodetection support */
+	if (norm == 0)
+		return -EINVAL;
+
+	ret = v4l2_subdev_call(cxdev->tda9887, video, s_std, norm);
+	if (ret != 0) {
+		dev_err(&dvbdev->udev->dev,
+			"tda9887 norm setup failed (%d)\n",
+			ret);
+		return ret;
+	}
+
+	ret = v4l2_subdev_call(cxdev->tuner, video, s_std, norm);
+	if (ret != 0) {
+		dev_err(&dvbdev->udev->dev,
+			"tuner norm setup failed (%d)\n",
+			ret);
+		return ret;
+	}
+
+	ret = v4l2_subdev_call(cxdev->cx25840, video, s_std, norm);
+	if (ret != 0) {
+		dev_err(&dvbdev->udev->dev,
+			"cx25840 norm setup failed (%d)\n",
+			ret);
+		return ret;
+	}
+
+ret_savenorm:
+	cxdev->norm = norm;
+
+	return 0;
+}
+
+static int cxusb_medion_log_status(struct file *file, void *fh)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(file);
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	v4l2_device_call_all(&cxdev->v4l2dev, 0, core, log_status);
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops cxusb_video_ioctl = {
+	.vidioc_querycap = cxusb_medion_v_querycap,
+	.vidioc_enum_fmt_vid_cap = cxusb_medion_v_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap = cxusb_medion_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap = cxusb_medion_s_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap = cxusb_medion_try_fmt_vid_cap,
+	.vidioc_enum_input = cxusb_medion_enum_input,
+	.vidioc_g_input = cxusb_medion_g_input,
+	.vidioc_s_input = cxusb_medion_s_input,
+	.vidioc_g_parm = cxusb_medion_g_parm,
+	.vidioc_s_parm = cxusb_medion_s_parm,
+	.vidioc_g_tuner = cxusb_medion_g_tuner,
+	.vidioc_s_tuner = cxusb_medion_s_tuner,
+	.vidioc_g_frequency = cxusb_medion_g_frequency,
+	.vidioc_s_frequency = cxusb_medion_s_frequency,
+	.vidioc_g_std = cxusb_medion_g_std,
+	.vidioc_s_std = cxusb_medion_s_std,
+	.vidioc_log_status = cxusb_medion_log_status,
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff
+};
+
+static const struct v4l2_ioctl_ops cxusb_radio_ioctl = {
+	.vidioc_querycap = cxusb_medion_v_querycap,
+	.vidioc_g_tuner = cxusb_medion_g_tuner,
+	.vidioc_s_tuner = cxusb_medion_s_tuner,
+	.vidioc_g_frequency = cxusb_medion_g_frequency,
+	.vidioc_s_frequency = cxusb_medion_s_frequency,
+	.vidioc_log_status = cxusb_medion_log_status
+};
+
+/*
+ * in principle, this should be const, but s_io_pin_config is declared
+ * to take non-const, and gcc complains
+ */
+static struct v4l2_subdev_io_pin_config cxusub_medion_pin_config[] = {
+	{ .pin = CX25840_PIN_DVALID_PRGM0, .function = CX25840_PAD_DEFAULT,
+	  .strength = CX25840_PIN_DRIVE_MEDIUM },
+	{ .pin = CX25840_PIN_PLL_CLK_PRGM7, .function = CX25840_PAD_AUX_PLL },
+	{ .pin = CX25840_PIN_HRESET_PRGM2, .function = CX25840_PAD_ACTIVE,
+	  .strength = CX25840_PIN_DRIVE_MEDIUM }
+};
+
+int cxusb_medion_analog_init(struct dvb_usb_device *dvbdev)
+{
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	u8 tuner_analog_msg_data[] = { 0x9c, 0x60, 0x85, 0x54 };
+	struct i2c_msg tuner_analog_msg = { .addr = 0x61, .flags = 0,
+					    .buf = tuner_analog_msg_data,
+					    .len =
+					    sizeof(tuner_analog_msg_data) };
+	struct v4l2_subdev_format subfmt;
+	int ret;
+
+	/* switch tuner to analog mode so IF demod will become accessible */
+	ret = i2c_transfer(&dvbdev->i2c_adap, &tuner_analog_msg, 1);
+	if (ret != 1)
+		dev_warn(&dvbdev->udev->dev,
+			 "tuner analog switch failed (%d)\n", ret);
+
+	ret = v4l2_subdev_call(cxdev->cx25840, core, load_fw);
+	if (ret != 0)
+		dev_warn(&dvbdev->udev->dev,
+			 "cx25840 fw load failed (%d)\n", ret);
+
+	ret = v4l2_subdev_call(cxdev->cx25840, video, s_routing,
+			       CX25840_COMPOSITE1, 0,
+			       CX25840_VCONFIG_FMT_BT656 |
+			       CX25840_VCONFIG_RES_8BIT |
+			       CX25840_VCONFIG_VBIRAW_DISABLED |
+			       CX25840_VCONFIG_ANCDATA_DISABLED |
+			       CX25840_VCONFIG_ACTIVE_COMPOSITE |
+			       CX25840_VCONFIG_VALID_ANDACTIVE |
+			       CX25840_VCONFIG_HRESETW_NORMAL |
+			       CX25840_VCONFIG_CLKGATE_NONE |
+			       CX25840_VCONFIG_DCMODE_DWORDS);
+	if (ret != 0)
+		dev_warn(&dvbdev->udev->dev,
+			 "cx25840 mode set failed (%d)\n", ret);
+
+	/* composite */
+	cxdev->input = 1;
+	cxdev->norm = 0;
+
+	/* TODO: setup audio samples insertion */
+
+	ret = v4l2_subdev_call(cxdev->cx25840, core, s_io_pin_config,
+			       sizeof(cxusub_medion_pin_config) /
+			       sizeof(cxusub_medion_pin_config[0]),
+			       cxusub_medion_pin_config);
+	if (ret != 0)
+		dev_warn(&dvbdev->udev->dev,
+			"cx25840 pin config failed (%d)\n", ret);
+
+	/* make sure that we aren't in radio mode */
+	v4l2_subdev_call(cxdev->tda9887, video, s_std, V4L2_STD_PAL);
+	v4l2_subdev_call(cxdev->tuner, video, s_std, V4L2_STD_PAL);
+	v4l2_subdev_call(cxdev->cx25840, video, s_std, cxdev->norm);
+
+	memset(&subfmt, 0, sizeof(subfmt));
+	subfmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+	subfmt.format.width = cxdev->width;
+	subfmt.format.height = cxdev->height;
+	subfmt.format.code = MEDIA_BUS_FMT_FIXED;
+	subfmt.format.field = V4L2_FIELD_SEQ_TB;
+	subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt);
+	if (ret != 0) {
+		subfmt.format.width = 640;
+		subfmt.format.height = 480;
+		ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL,
+				       &subfmt);
+		if (ret != 0)
+			dev_warn(&dvbdev->udev->dev,
+				 "cx25840 format set failed (%d)\n", ret);
+	}
+
+	if (ret == 0) {
+		cxdev->width = subfmt.format.width;
+		cxdev->height = subfmt.format.height;
+	}
+
+	return 0;
+}
+
+static int cxusb_videoradio_open(struct file *f)
+{
+	struct dvb_usb_device *dvbdev = video_drvdata(f);
+	int ret;
+
+	/*
+	 * no locking needed since this call only modifies analog
+	 * state if there are no other analog handles currenly
+	 * opened so ops done via them cannot create a conflict
+	 */
+	ret = cxusb_medion_get(dvbdev, CXUSB_OPEN_ANALOG);
+	if (ret != 0)
+		return ret;
+
+	ret = v4l2_fh_open(f);
+	if (ret != 0)
+		goto ret_release;
+
+	cxusb_vprintk(dvbdev, OPS, "got open\n");
+
+	return 0;
+
+ret_release:
+	cxusb_medion_put(dvbdev);
+
+	return ret;
+}
+
+static int cxusb_videoradio_release(struct file *f)
+{
+	struct video_device *vdev = video_devdata(f);
+	struct dvb_usb_device *dvbdev = video_drvdata(f);
+	int ret;
+
+	cxusb_vprintk(dvbdev, OPS, "got release\n");
+
+	if (vdev->vfl_type == VFL_TYPE_GRABBER)
+		ret = vb2_fop_release(f);
+	else
+		ret = v4l2_fh_release(f);
+
+	cxusb_medion_put(dvbdev);
+
+	return ret;
+}
+
+static const struct v4l2_file_operations cxusb_video_fops = {
+	.owner = THIS_MODULE,
+	.read = vb2_fop_read,
+	.poll = vb2_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = vb2_fop_mmap,
+	.open = cxusb_videoradio_open,
+	.release = cxusb_videoradio_release
+};
+
+static const struct v4l2_file_operations cxusb_radio_fops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = video_ioctl2,
+	.open = cxusb_videoradio_open,
+	.release = cxusb_videoradio_release
+};
+
+static void cxusb_medion_v4l2_release(struct v4l2_device *v4l2_dev)
+{
+	struct cxusb_medion_dev *cxdev =
+		container_of(v4l2_dev, struct cxusb_medion_dev, v4l2dev);
+	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
+
+	cxusb_vprintk(dvbdev, OPS, "v4l2 device release\n");
+
+	v4l2_device_unregister(&cxdev->v4l2dev);
+
+	mutex_destroy(&cxdev->dev_lock);
+
+	while (completion_done(&cxdev->v4l2_release))
+		schedule();
+
+	complete(&cxdev->v4l2_release);
+}
+
+static void cxusb_medion_videodev_release(struct video_device *vdev)
+{
+	struct dvb_usb_device *dvbdev = video_get_drvdata(vdev);
+
+	cxusb_vprintk(dvbdev, OPS, "video device release\n");
+
+	vb2_queue_release(vdev->queue);
+
+	video_device_release(vdev);
+}
+
+static int cxusb_medion_register_analog_video(struct dvb_usb_device *dvbdev)
+{
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	int ret;
+
+	cxdev->videoqueue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	cxdev->videoqueue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+	cxdev->videoqueue.ops = &cxdev_video_qops;
+	cxdev->videoqueue.mem_ops = &vb2_vmalloc_memops;
+	cxdev->videoqueue.drv_priv = dvbdev;
+	cxdev->videoqueue.buf_struct_size =
+		sizeof(struct cxusb_medion_vbuffer);
+	cxdev->videoqueue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+
+	ret = vb2_queue_init(&cxdev->videoqueue);
+	if (ret) {
+		dev_err(&dvbdev->udev->dev,
+			"video queue init failed, ret = %d\n", ret);
+		return ret;
+	}
+
+	cxdev->videodev = video_device_alloc();
+	if (cxdev->videodev == NULL) {
+		dev_err(&dvbdev->udev->dev, "video device alloc failed\n");
+		ret = -ENOMEM;
+		goto ret_qrelease;
+	}
+
+	cxdev->videodev->fops = &cxusb_video_fops;
+	cxdev->videodev->v4l2_dev = &cxdev->v4l2dev;
+	cxdev->videodev->queue = &cxdev->videoqueue;
+	strcpy(cxdev->videodev->name, "cxusb");
+	cxdev->videodev->vfl_dir = VFL_DIR_RX;
+	cxdev->videodev->ioctl_ops = &cxusb_video_ioctl;
+	cxdev->videodev->tvnorms = V4L2_STD_ALL;
+	cxdev->videodev->release = cxusb_medion_videodev_release;
+	cxdev->videodev->lock = &cxdev->dev_lock;
+	video_set_drvdata(cxdev->videodev, dvbdev);
+
+	ret = video_register_device(cxdev->videodev, VFL_TYPE_GRABBER, -1);
+	if (ret) {
+		dev_err(&dvbdev->udev->dev,
+			"video device register failed, ret = %d\n", ret);
+		goto ret_vrelease;
+	}
+
+	return 0;
+
+ret_vrelease:
+	video_device_release(cxdev->videodev);
+
+ret_qrelease:
+	vb2_queue_release(&cxdev->videoqueue);
+
+	return ret;
+}
+
+static int cxusb_medion_register_analog_radio(struct dvb_usb_device *dvbdev)
+{
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	int ret;
+
+	cxdev->radiodev = video_device_alloc();
+	if (cxdev->radiodev == NULL) {
+		dev_err(&dvbdev->udev->dev, "radio device alloc failed\n");
+		return -ENOMEM;
+	}
+
+	cxdev->radiodev->fops = &cxusb_radio_fops;
+	cxdev->radiodev->v4l2_dev = &cxdev->v4l2dev;
+	strcpy(cxdev->radiodev->name, "cxusb");
+	cxdev->radiodev->vfl_dir = VFL_DIR_RX;
+	cxdev->radiodev->ioctl_ops = &cxusb_radio_ioctl;
+	cxdev->radiodev->release = video_device_release;
+	cxdev->radiodev->lock = &cxdev->dev_lock;
+	video_set_drvdata(cxdev->radiodev, dvbdev);
+
+	ret = video_register_device(cxdev->radiodev, VFL_TYPE_RADIO, -1);
+	if (ret) {
+		dev_err(&dvbdev->udev->dev,
+			"radio device register failed, ret = %d\n", ret);
+		video_device_release(cxdev->radiodev);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int cxusb_medion_register_analog_subdevs(struct dvb_usb_device *dvbdev)
+{
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	struct tuner_setup tun_setup;
+	struct i2c_board_info cx25840_board;
+	struct cx25840_platform_data cx25840_platform;
+
+	/* attach capture chip */
+	memset(&cx25840_platform, 0, sizeof(cx25840_platform));
+	cx25840_platform.generic_mode = 1;
+
+	memset(&cx25840_board, 0, sizeof(cx25840_board));
+	strcpy(cx25840_board.type, "cx25840");
+	cx25840_board.addr = 0x44;
+	cx25840_board.platform_data = &cx25840_platform;
+
+	cxdev->cx25840 = v4l2_i2c_new_subdev_board(&cxdev->v4l2dev,
+						   &dvbdev->i2c_adap,
+						   &cx25840_board, NULL);
+	if (cxdev->cx25840 == NULL) {
+		dev_err(&dvbdev->udev->dev, "cx25840 not found\n");
+		return -ENODEV;
+	}
+
+	/* attach analog tuner */
+	cxdev->tuner = v4l2_i2c_new_subdev(&cxdev->v4l2dev,
+					   &dvbdev->i2c_adap,
+					   "tuner", 0x61, NULL);
+	if (cxdev->tuner == NULL) {
+		dev_err(&dvbdev->udev->dev, "tuner not found\n");
+		return -ENODEV;
+	}
+
+	/* configure it */
+	memset(&tun_setup, 0, sizeof(tun_setup));
+	tun_setup.addr = 0x61;
+	tun_setup.type = TUNER_PHILIPS_FMD1216ME_MK3;
+	tun_setup.mode_mask = T_RADIO | T_ANALOG_TV;
+	v4l2_subdev_call(cxdev->tuner, tuner, s_type_addr, &tun_setup);
+
+	/* attach IF demod */
+	cxdev->tda9887 = v4l2_i2c_new_subdev(&cxdev->v4l2dev,
+					     &dvbdev->i2c_adap,
+					     "tuner", 0x43, NULL);
+	if (cxdev->tda9887 == NULL) {
+		dev_err(&dvbdev->udev->dev, "tda9887 not found\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+int cxusb_medion_register_analog(struct dvb_usb_device *dvbdev)
+{
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+	int ret;
+
+	mutex_init(&cxdev->dev_lock);
+
+	init_completion(&cxdev->v4l2_release);
+
+	cxdev->v4l2dev.release = cxusb_medion_v4l2_release;
+
+	ret = v4l2_device_register(&dvbdev->udev->dev, &cxdev->v4l2dev);
+	if (ret != 0) {
+		dev_err(&dvbdev->udev->dev,
+			"V4L2 device registration failed, ret = %d\n", ret);
+		mutex_destroy(&cxdev->dev_lock);
+		return ret;
+	}
+
+	ret = cxusb_medion_register_analog_subdevs(dvbdev);
+	if (ret)
+		goto ret_unregister;
+
+	INIT_WORK(&cxdev->urbwork, cxusb_medion_v_complete_work);
+	INIT_LIST_HEAD(&cxdev->buflist);
+
+	cxdev->width = 320;
+	cxdev->height = 240;
+
+	ret = cxusb_medion_register_analog_video(dvbdev);
+	if (ret)
+		goto ret_unregister;
+
+	ret = cxusb_medion_register_analog_radio(dvbdev);
+	if (ret)
+		goto ret_vunreg;
+
+	return 0;
+
+ret_vunreg:
+	video_unregister_device(cxdev->videodev);
+
+ret_unregister:
+	v4l2_device_put(&cxdev->v4l2dev);
+	wait_for_completion(&cxdev->v4l2_release);
+
+	return ret;
+}
+
+void cxusb_medion_unregister_analog(struct dvb_usb_device *dvbdev)
+{
+	struct cxusb_medion_dev *cxdev = dvbdev->priv;
+
+	cxusb_vprintk(dvbdev, OPS, "unregistering analog\n");
+
+	video_unregister_device(cxdev->radiodev);
+	video_unregister_device(cxdev->videodev);
+
+	v4l2_device_put(&cxdev->v4l2dev);
+	wait_for_completion(&cxdev->v4l2_release);
+
+	cxusb_vprintk(dvbdev, OPS, "analog unregistered\n");
+}
diff --git a/drivers/media/usb/dvb-usb/cxusb.c b/drivers/media/usb/dvb-usb/cxusb.c
index e0d390789b58..7498a2820178 100644
--- a/drivers/media/usb/dvb-usb/cxusb.c
+++ b/drivers/media/usb/dvb-usb/cxusb.c
@@ -11,7 +11,6 @@
  * design, so it can be reused for the "analogue-only" device (if it will
  * appear at all).
  *
- * TODO: Use the cx25840-driver for the analogue part
  *
  * Copyright (C) 2005 Patrick Boettcher (patrick.boettcher@posteo.de)
  * Copyright (C) 2006 Michael Krufky (mkrufky@linuxtv.org)
@@ -2549,5 +2548,4 @@ MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>");
 MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>");
 MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>");
 MODULE_DESCRIPTION("Driver for Conexant USB2.0 hybrid reference design");
-MODULE_VERSION("1.0-alpha");
 MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/cxusb.h b/drivers/media/usb/dvb-usb/cxusb.h
index f586d61a7bf8..eb5ddda5962b 100644
--- a/drivers/media/usb/dvb-usb/cxusb.h
+++ b/drivers/media/usb/dvb-usb/cxusb.h
@@ -2,12 +2,27 @@
 #ifndef _DVB_USB_CXUSB_H_
 #define _DVB_USB_CXUSB_H_
 
+#include <linux/completion.h>
 #include <linux/i2c.h>
+#include <linux/list.h>
 #include <linux/mutex.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-core.h>
 
 #define DVB_USB_LOG_PREFIX "cxusb"
 #include "dvb-usb.h"
 
+#define CXUSB_VIDEO_URBS (5)
+
+#define CXUSB_VIDEO_PKT_SIZE 3030
+#define CXUSB_VIDEO_MAX_FRAME_PKTS 346
+#define CXUSB_VIDEO_MAX_FRAME_SIZE (CXUSB_VIDEO_MAX_FRAME_PKTS * \
+					CXUSB_VIDEO_PKT_SIZE)
+
 /* usb commands - some of it are guesses, don't have a reference yet */
 #define CMD_BLUEBIRD_GPIO_RW 0x05
 
@@ -32,6 +47,20 @@
 #define CMD_ANALOG        0x50
 #define CMD_DIGITAL       0x51
 
+#define CXUSB_BT656_PREAMBLE ((const u8 *)"\xff\x00\x00")
+
+#define CXUSB_BT656_FIELD_MASK BIT(6)
+#define CXUSB_BT656_FIELD_1 0
+#define CXUSB_BT656_FIELD_2 BIT(6)
+
+#define CXUSB_BT656_VBI_MASK BIT(5)
+#define CXUSB_BT656_VBI_ON BIT(5)
+#define CXUSB_BT656_VBI_OFF 0
+
+#define CXUSB_BT656_SEAV_MASK BIT(4)
+#define CXUSB_BT656_SEAV_EAV BIT(4)
+#define CXUSB_BT656_SEAV_SAV 0
+
 /* Max transfer size done by I2C transfer functions */
 #define MAX_XFER_SIZE  80
 
@@ -54,6 +83,29 @@ enum cxusb_open_type {
 	CXUSB_OPEN_ANALOG, CXUSB_OPEN_DIGITAL
 };
 
+struct cxusb_medion_auxbuf {
+	u8 *buf;
+	unsigned int len;
+	unsigned int paylen;
+};
+
+enum cxusb_bt656_mode {
+	NEW_FRAME, FIRST_FIELD, SECOND_FIELD
+};
+
+enum cxusb_bt656_fmode {
+	START_SEARCH, LINE_SAMPLES, VBI_SAMPLES
+};
+
+struct cxusb_bt656_params {
+	enum cxusb_bt656_mode mode;
+	enum cxusb_bt656_fmode fmode;
+	unsigned int pos;
+	unsigned int line;
+	unsigned int linesamples;
+	u8 *buf;
+};
+
 struct cxusb_medion_dev {
 	/* has to be the first one */
 	struct cxusb_state state;
@@ -63,18 +115,71 @@ struct cxusb_medion_dev {
 	enum cxusb_open_type open_type;
 	unsigned int open_ctr;
 	struct mutex open_lock;
+
+#ifdef CONFIG_DVB_USB_CXUSB_ANALOG
+	struct v4l2_device v4l2dev;
+	struct v4l2_subdev *cx25840;
+	struct v4l2_subdev *tuner;
+	struct v4l2_subdev *tda9887;
+	struct video_device *videodev, *radiodev;
+	struct mutex dev_lock;
+
+	struct vb2_queue videoqueue;
+	u32 input;
+	bool streaming;
+	u32 width, height;
+	bool raw_mode;
+	struct cxusb_medion_auxbuf auxbuf;
+	v4l2_std_id norm;
+
+	struct urb *streamurbs[CXUSB_VIDEO_URBS];
+	unsigned long urbcomplete;
+	struct work_struct urbwork;
+	unsigned int nexturb;
+
+	struct cxusb_bt656_params bt656;
+	struct cxusb_medion_vbuffer *vbuf;
+
+	struct list_head buflist;
+
+	struct completion v4l2_release;
+#endif
 };
 
+struct cxusb_medion_vbuffer {
+	struct vb2_buffer vb2;
+	struct list_head list;
+};
+
+/* Capture streaming parameters extendedmode field flags */
+#define CXUSB_EXTENDEDMODE_CAPTURE_RAW 1
+
 /* defines for "debug" module parameter */
 #define CXUSB_DBG_RC BIT(0)
 #define CXUSB_DBG_I2C BIT(1)
 #define CXUSB_DBG_MISC BIT(2)
+#define CXUSB_DBG_BT656 BIT(3)
+#define CXUSB_DBG_URB BIT(4)
+#define CXUSB_DBG_OPS BIT(5)
+#define CXUSB_DBG_AUXB BIT(6)
 
 extern int dvb_usb_cxusb_debug;
 
+#define cxusb_vprintk(dvbdev, lvl, ...) do {				\
+		struct cxusb_medion_dev *_cxdev = (dvbdev)->priv;	\
+		if (dvb_usb_cxusb_debug & CXUSB_DBG_##lvl)		\
+			v4l2_printk(KERN_DEBUG,			\
+				    &_cxdev->v4l2dev, __VA_ARGS__);	\
+	} while (0)
+
 int cxusb_ctrl_msg(struct dvb_usb_device *d,
 		   u8 cmd, const u8 *wbuf, int wlen, u8 *rbuf, int rlen);
 
+#ifdef CONFIG_DVB_USB_CXUSB_ANALOG
+int cxusb_medion_analog_init(struct dvb_usb_device *dvbdev);
+int cxusb_medion_register_analog(struct dvb_usb_device *dvbdev);
+void cxusb_medion_unregister_analog(struct dvb_usb_device *dvbdev);
+#else
 static inline int cxusb_medion_analog_init(struct dvb_usb_device *dvbdev)
 {
 	return -EINVAL;
@@ -88,6 +193,7 @@ static inline int cxusb_medion_register_analog(struct dvb_usb_device *dvbdev)
 static inline void cxusb_medion_unregister_analog(struct dvb_usb_device *dvbdev)
 {
 }
+#endif
 
 int cxusb_medion_get(struct dvb_usb_device *dvbdev,
 		     enum cxusb_open_type open_type);

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v7 3/6] cx25840: add pin to pad mapping and output format configuration
  2018-07-02 21:23 ` [PATCH v7 3/6] cx25840: add pin to pad mapping and output format configuration Maciej S. Szmigiero
@ 2018-07-04  9:05   ` Hans Verkuil
  2018-07-10 21:47     ` Maciej S. Szmigiero
  0 siblings, 1 reply; 14+ messages in thread
From: Hans Verkuil @ 2018-07-04  9:05 UTC (permalink / raw)
  To: Maciej S. Szmigiero, Michael Krufky, Mauro Carvalho Chehab
  Cc: Andy Walls, linux-kernel, linux-media

On 02/07/18 23:23, Maciej S. Szmigiero wrote:
> This commit adds pin to pad mapping and output format configuration support
> in CX2584x-series chips to cx25840 driver.
> 
> This functionality is then used to allow disabling ivtv-specific hacks
> (called a "generic mode"), so cx25840 driver can be used for other devices
> not needing them without risking compatibility problems.
> 
> Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
> ---
>  drivers/media/i2c/cx25840/cx25840-core.c | 396 ++++++++++++++++++++++-
>  drivers/media/i2c/cx25840/cx25840-core.h |  13 +
>  drivers/media/i2c/cx25840/cx25840-vbi.c  |   3 +
>  include/media/drv-intf/cx25840.h         |  74 ++++-
>  4 files changed, 484 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c
> index b168bf3635b6..0643c3b44a2b 100644
> --- a/drivers/media/i2c/cx25840/cx25840-core.c
> +++ b/drivers/media/i2c/cx25840/cx25840-core.c
> @@ -21,6 +21,9 @@
>   * CX23888 DIF support for the HVR1850
>   * Copyright (C) 2011 Steven Toth <stoth@kernellabs.com>
>   *
> + * CX2584x pin to pad mapping and output format configuration support are
> + * Copyright (C) 2011 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
> + *
>   * 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
> @@ -316,6 +319,260 @@ static int cx23885_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
>  	return 0;
>  }
>  
> +static u8 cx25840_function_to_pad(struct i2c_client *client, u8 function)
> +{
> +	switch (function) {
> +	case CX25840_PAD_ACTIVE:
> +		return 1;
> +
> +	case CX25840_PAD_VACTIVE:
> +		return 2;
> +
> +	case CX25840_PAD_CBFLAG:
> +		return 3;
> +
> +	case CX25840_PAD_VID_DATA_EXT0:
> +		return 4;
> +
> +	case CX25840_PAD_VID_DATA_EXT1:
> +		return 5;
> +
> +	case CX25840_PAD_GPO0:
> +		return 6;
> +
> +	case CX25840_PAD_GPO1:
> +		return 7;
> +
> +	case CX25840_PAD_GPO2:
> +		return 8;
> +
> +	case CX25840_PAD_GPO3:
> +		return 9;
> +
> +	case CX25840_PAD_IRQ_N:
> +		return 10;
> +
> +	case CX25840_PAD_AC_SYNC:
> +		return 11;
> +
> +	case CX25840_PAD_AC_SDOUT:
> +		return 12;
> +
> +	case CX25840_PAD_PLL_CLK:
> +		return 13;
> +
> +	case CX25840_PAD_VRESET:
> +		return 14;
> +
> +	default:
> +		if (function != CX25840_PAD_DEFAULT)
> +			v4l_err(client,
> +				"invalid function %u, assuming default\n",
> +				(unsigned int)function);
> +		return 0;
> +	}

Unless I am mistaken this function boils down to:

static u8 cx25840_function_to_pad(struct i2c_client *client, u8 function)
{
	return function > CX25840_PAD_VRESET ? 0 : function;
}

> +}
> +
> +static void cx25840_set_invert(u8 *pinctrl3, u8 *voutctrl4, u8 function,
> +			       u8 pin, bool invert)
> +{
> +	switch (function) {
> +	case CX25840_PAD_IRQ_N:
> +		if (invert)
> +			*pinctrl3 &= ~2;
> +		else
> +			*pinctrl3 |= 2;
> +		break;
> +
> +	case CX25840_PAD_ACTIVE:
> +		if (invert)
> +			*voutctrl4 |= BIT(2);
> +		else
> +			*voutctrl4 &= ~BIT(2);
> +		break;
> +
> +	case CX25840_PAD_VACTIVE:
> +		if (invert)
> +			*voutctrl4 |= BIT(5);
> +		else
> +			*voutctrl4 &= ~BIT(5);
> +		break;
> +
> +	case CX25840_PAD_CBFLAG:
> +		if (invert)
> +			*voutctrl4 |= BIT(4);
> +		else
> +			*voutctrl4 &= ~BIT(4);
> +		break;
> +
> +	case CX25840_PAD_VRESET:
> +		if (invert)
> +			*voutctrl4 |= BIT(0);
> +		else
> +			*voutctrl4 &= ~BIT(0);
> +		break;
> +	}
> +
> +	if (function != CX25840_PAD_DEFAULT)
> +		return;
> +
> +	switch (pin) {
> +	case CX25840_PIN_DVALID_PRGM0:
> +		if (invert)
> +			*voutctrl4 |= BIT(6);
> +		else
> +			*voutctrl4 &= ~BIT(6);
> +		break;
> +
> +	case CX25840_PIN_HRESET_PRGM2:
> +		if (invert)
> +			*voutctrl4 |= BIT(1);
> +		else
> +			*voutctrl4 &= ~BIT(1);
> +		break;
> +	}
> +}
> +
> +static int cx25840_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
> +				   struct v4l2_subdev_io_pin_config *p)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> +	unsigned int i;
> +	u8 pinctrl[6], pinconf[10], voutctrl4;
> +
> +	for (i = 0; i < 6; i++)
> +		pinctrl[i] = cx25840_read(client, 0x114 + i);
> +
> +	for (i = 0; i < 10; i++)
> +		pinconf[i] = cx25840_read(client, 0x11c + i);
> +
> +	voutctrl4 = cx25840_read(client, 0x407);
> +
> +	for (i = 0; i < n; i++) {
> +		u8 strength = p[i].strength;
> +
> +		if (strength != CX25840_PIN_DRIVE_SLOW &&
> +		    strength != CX25840_PIN_DRIVE_MEDIUM &&
> +		    strength != CX25840_PIN_DRIVE_FAST) {
> +
> +			v4l_err(client,
> +				"invalid drive speed for pin %u (%u), assuming fast\n",
> +				(unsigned int)p[i].pin,
> +				(unsigned int)strength);
> +
> +			strength = CX25840_PIN_DRIVE_FAST;
> +		}
> +
> +		switch (p[i].pin) {
> +		case CX25840_PIN_DVALID_PRGM0:
> +			if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE))
> +				pinctrl[0] &= ~BIT(6);
> +			else
> +				pinctrl[0] |= BIT(6);
> +
> +			pinconf[3] &= 0xf0;
> +			pinconf[3] |= cx25840_function_to_pad(client,
> +							      p[i].function);
> +
> +			cx25840_set_invert(&pinctrl[3], &voutctrl4,
> +					   p[i].function,
> +					   CX25840_PIN_DVALID_PRGM0,
> +					   p[i].flags &
> +					   BIT(V4L2_SUBDEV_IO_PIN_ACTIVE_LOW));
> +
> +			pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */
> +			switch (strength) {
> +			case CX25840_PIN_DRIVE_SLOW:
> +				pinctrl[4] |= 1 << 2;
> +				break;
> +
> +			case CX25840_PIN_DRIVE_FAST:
> +				pinctrl[4] |= 2 << 2;
> +				break;
> +			}
> +
> +			break;
> +
> +		case CX25840_PIN_HRESET_PRGM2:
> +			if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE))
> +				pinctrl[1] &= ~BIT(0);
> +			else
> +				pinctrl[1] |= BIT(0);
> +
> +			pinconf[4] &= 0xf0;
> +			pinconf[4] |= cx25840_function_to_pad(client,
> +							      p[i].function);
> +
> +			cx25840_set_invert(&pinctrl[3], &voutctrl4,
> +					   p[i].function,
> +					   CX25840_PIN_HRESET_PRGM2,
> +					   p[i].flags &
> +					   BIT(V4L2_SUBDEV_IO_PIN_ACTIVE_LOW));
> +
> +			pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */
> +			switch (strength) {
> +			case CX25840_PIN_DRIVE_SLOW:
> +				pinctrl[4] |= 1 << 2;
> +				break;
> +
> +			case CX25840_PIN_DRIVE_FAST:
> +				pinctrl[4] |= 2 << 2;
> +				break;
> +			}
> +
> +			break;
> +
> +		case CX25840_PIN_PLL_CLK_PRGM7:
> +			if (p[i].flags & BIT(V4L2_SUBDEV_IO_PIN_DISABLE))
> +				pinctrl[2] &= ~BIT(2);
> +			else
> +				pinctrl[2] |= BIT(2);
> +
> +			switch (p[i].function) {
> +			case CX25840_PAD_XTI_X5_DLL:
> +				pinconf[6] = 0;
> +				break;
> +
> +			case CX25840_PAD_AUX_PLL:
> +				pinconf[6] = 1;
> +				break;
> +
> +			case CX25840_PAD_VID_PLL:
> +				pinconf[6] = 5;
> +				break;
> +
> +			case CX25840_PAD_XTI:
> +				pinconf[6] = 2;
> +				break;
> +
> +			default:
> +				pinconf[6] = 3;
> +				pinconf[6] |=
> +					cx25840_function_to_pad(client,
> +								p[i].function)
> +					<< 4;
> +			}
> +
> +			break;
> +
> +		default:
> +			v4l_err(client, "invalid or unsupported pin %u\n",
> +				(unsigned int)p[i].pin);
> +			break;
> +		}
> +	}
> +
> +	cx25840_write(client, 0x407, voutctrl4);
> +
> +	for (i = 0; i < 6; i++)
> +		cx25840_write(client, 0x114 + i, pinctrl[i]);
> +
> +	for (i = 0; i < 10; i++)
> +		cx25840_write(client, 0x11c + i, pinconf[i]);
> +
> +	return 0;
> +}
> +
>  static int common_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
>  				      struct v4l2_subdev_io_pin_config *pincfg)
>  {
> @@ -323,6 +580,8 @@ static int common_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
>  
>  	if (is_cx2388x(state))
>  		return cx23885_s_io_pin_config(sd, n, pincfg);
> +	else if (is_cx2584x(state))
> +		return cx25840_s_io_pin_config(sd, n, pincfg);
>  	return 0;
>  }
>  
> @@ -455,6 +714,22 @@ static void cx25840_initialize(struct i2c_client *client)
>  	/* (re)set input */
>  	set_input(client, state->vid_input, state->aud_input);
>  
> +	if (state->generic_mode) {
> +		/* set datasheet video output defaults */
> +		cx25840_vconfig(client, CX25840_VCONFIG_FMT_BT656 |
> +				CX25840_VCONFIG_RES_8BIT |
> +				CX25840_VCONFIG_VBIRAW_DISABLED |
> +				CX25840_VCONFIG_ANCDATA_ENABLED |
> +				CX25840_VCONFIG_TASKBIT_ONE |
> +				CX25840_VCONFIG_ACTIVE_HORIZONTAL |
> +				CX25840_VCONFIG_VALID_NORMAL |
> +				CX25840_VCONFIG_HRESETW_NORMAL |
> +				CX25840_VCONFIG_CLKGATE_NONE |
> +				CX25840_VCONFIG_DCMODE_DWORDS |
> +				CX25840_VCONFIG_IDID0S_NORMAL |
> +				CX25840_VCONFIG_VIPCLAMP_DISABLED);
> +	}
> +
>  	/* start microcontroller */
>  	cx25840_and_or(client, 0x803, ~0x10, 0x10);
>  }
> @@ -1403,7 +1678,9 @@ static int cx25840_set_fmt(struct v4l2_subdev *sd,
>  		Hsrc |= (cx25840_read(client, 0x471) & 0xf0) >> 4;
>  	}
>  
> -	Vlines = fmt->height + (is_50Hz ? 4 : 7);
> +	Vlines = fmt->height;
> +	if (!state->generic_mode)
> +		Vlines += is_50Hz ? 4 : 7;
>  
>  	/*
>  	 * We keep 1 margin for the Vsrc < Vlines check since the
> @@ -1647,6 +1924,119 @@ static void log_audio_status(struct i2c_client *client)
>  	}
>  }
>  
> +#define CX25840_VCONFIG_OPTION(state, cfg_in, opt_msk)			\
> +	do {								\
> +		if ((cfg_in) & (opt_msk)) {				\
> +			(state)->vid_config &= ~(opt_msk);		\
> +			(state)->vid_config |= (cfg_in) & (opt_msk);	\
> +		}							\
> +	} while (0)
> +
> +#define CX25840_VCONFIG_SET_BIT(state, opt_msk, voc, idx, bit, oneval)	\
> +	do {								\
> +		if ((state)->vid_config & (opt_msk)) {			\
> +			if (((state)->vid_config & (opt_msk)) ==	\
> +			    (oneval))					\
> +				(voc)[idx] |= BIT(bit);		\
> +			else						\
> +				(voc)[idx] &= ~BIT(bit);		\
> +		}							\
> +	} while (0)
> +
> +int cx25840_vconfig(struct i2c_client *client, u32 cfg_in)
> +{
> +	struct cx25840_state *state = to_state(i2c_get_clientdata(client));
> +	u8 voutctrl[3];
> +	unsigned int i;
> +
> +	/* apply incoming options to the current state */
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_FMT_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_RES_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VBIRAW_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ANCDATA_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_TASKBIT_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ACTIVE_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VALID_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_HRESETW_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_CLKGATE_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_DCMODE_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_IDID0S_MASK);
> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VIPCLAMP_MASK);

This appears to be a very complex way of saying:

	state->vid_config = cfg_in;

> +
> +	for (i = 0; i < 3; i++)
> +		voutctrl[i] = cx25840_read(client, 0x404 + i);
> +
> +	/* apply state to hardware regs */
> +	if (state->vid_config & CX25840_VCONFIG_FMT_MASK)
> +		voutctrl[0] &= ~3;
> +	switch (state->vid_config & CX25840_VCONFIG_FMT_MASK) {
> +	case CX25840_VCONFIG_FMT_BT656:
> +		voutctrl[0] |= 1;
> +		break;
> +
> +	case CX25840_VCONFIG_FMT_VIP11:
> +		voutctrl[0] |= 2;
> +		break;
> +
> +	case CX25840_VCONFIG_FMT_VIP2:
> +		voutctrl[0] |= 3;
> +		break;
> +
> +	case CX25840_VCONFIG_FMT_BT601:
> +		/* zero */
> +	default:
> +		break;
> +	}
> +
> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_RES_MASK, voutctrl,
> +				0, 2, CX25840_VCONFIG_RES_10BIT);
> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VBIRAW_MASK, voutctrl,
> +				0, 3, CX25840_VCONFIG_VBIRAW_ENABLED);
> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ANCDATA_MASK, voutctrl,
> +				0, 4, CX25840_VCONFIG_ANCDATA_ENABLED);
> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_TASKBIT_MASK, voutctrl,
> +				0, 5, CX25840_VCONFIG_TASKBIT_ONE);
> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ACTIVE_MASK, voutctrl,
> +				1, 2, CX25840_VCONFIG_ACTIVE_HORIZONTAL);
> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VALID_MASK, voutctrl,
> +				1, 3, CX25840_VCONFIG_VALID_ANDACTIVE);
> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_HRESETW_MASK, voutctrl,
> +				1, 4, CX25840_VCONFIG_HRESETW_PIXCLK);
> +
> +	if (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK)
> +		voutctrl[1] &= ~(3 << 6);
> +	switch (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK) {
> +	case CX25840_VCONFIG_CLKGATE_VALID:
> +		voutctrl[1] |= 2;
> +		break;
> +
> +	case CX25840_VCONFIG_CLKGATE_VALIDACTIVE:
> +		voutctrl[1] |= 3;
> +		break;
> +
> +	case CX25840_VCONFIG_CLKGATE_NONE:
> +		/* zero */
> +	default:
> +		break;
> +	}
> +
> +
> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_DCMODE_MASK, voutctrl,
> +				2, 0, CX25840_VCONFIG_DCMODE_BYTES);
> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_IDID0S_MASK, voutctrl,
> +				2, 1, CX25840_VCONFIG_IDID0S_LINECNT);
> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VIPCLAMP_MASK, voutctrl,
> +				2, 4, CX25840_VCONFIG_VIPCLAMP_ENABLED);
> +
> +	for (i = 0; i < 3; i++)
> +		cx25840_write(client, 0x404 + i, voutctrl[i]);
> +
> +	return 0;
> +}
> +
> +#undef CX25840_VCONFIG_SET_BIT
> +#undef CX25840_VCONFIG_OPTION

Why #undef? You would normally never do that.

> +
>  /* ----------------------------------------------------------------------- */
>  
>  /* This load_fw operation must be called to load the driver's firmware.
> @@ -1836,6 +2226,9 @@ static int cx25840_s_video_routing(struct v4l2_subdev *sd,
>  	if (is_cx23888(state))
>  		cx23888_std_setup(client);
>  
> +	if (is_cx2584x(state) && state->generic_mode)
> +		cx25840_vconfig(client, config);
> +

You do the vconfig configuration when the video routing changes. But isn't this
configuration a one-time thing? E.g. something you do only when initializing the
board?

At least in the cxusb code the cfg_in value is constant and not dependent on what
input is chosen.

If this is true, then you should add the core init op instead. And as a bonus you
can turn on generic_mode if the init op is called instead of having to add it
to the platform data.

>  	return set_input(client, input, state->aud_input);
>  }
>  
> @@ -5335,6 +5728,7 @@ static int cx25840_probe(struct i2c_client *client,
>  		struct cx25840_platform_data *pdata = client->dev.platform_data;
>  
>  		state->pvr150_workaround = pdata->pvr150_workaround;
> +		state->generic_mode = pdata->generic_mode;
>  	}
>  
>  	cx25840_ir_probe(sd);
> diff --git a/drivers/media/i2c/cx25840/cx25840-core.h b/drivers/media/i2c/cx25840/cx25840-core.h
> index c323b1af1f83..e5f52b8d6d9c 100644
> --- a/drivers/media/i2c/cx25840/cx25840-core.h
> +++ b/drivers/media/i2c/cx25840/cx25840-core.h
> @@ -54,10 +54,12 @@ enum cx25840_media_pads {
>   * @mute:		audio mute V4L2 control (non-cx2583x devices only)
>   * @pvr150_workaround:	whether we enable workaround for Hauppauge PVR150
>   *			hardware bug (audio dropping out)
> + * @generic_mode:	whether we disable ivtv-specific hacks
>   * @radio:		set if we are currently in the radio mode, otherwise
>   *			the current mode is non-radio (that is, video)
>   * @std:		currently set video standard
>   * @vid_input:		currently set video input
> + * @vid_config:	currently set video output configuration
>   * @aud_input:		currently set audio input
>   * @audclk_freq:	currently set audio sample rate
>   * @audmode:		currently set audio mode (when in non-radio mode)
> @@ -84,9 +86,11 @@ struct cx25840_state {
>  		struct v4l2_ctrl *mute;
>  	};
>  	int pvr150_workaround;
> +	int generic_mode;

Should be bool.

>  	int radio;
>  	v4l2_std_id std;
>  	enum cx25840_video_input vid_input;
> +	u32 vid_config;
>  	enum cx25840_audio_input aud_input;
>  	u32 audclk_freq;
>  	int audmode;
> @@ -119,6 +123,14 @@ static inline bool is_cx2583x(struct cx25840_state *state)
>  	       state->id == CX25837;
>  }
>  
> +static inline bool is_cx2584x(struct cx25840_state *state)
> +{
> +	return state->id == CX25840 ||
> +	       state->id == CX25841 ||
> +	       state->id == CX25842 ||
> +	       state->id == CX25843;
> +}
> +
>  static inline bool is_cx231xx(struct cx25840_state *state)
>  {
>  	return state->id == CX2310X_AV;
> @@ -156,6 +168,7 @@ int cx25840_and_or(struct i2c_client *client, u16 addr, unsigned mask, u8 value)
>  int cx25840_and_or4(struct i2c_client *client, u16 addr, u32 and_mask,
>  		    u32 or_value);
>  void cx25840_std_setup(struct i2c_client *client);
> +int cx25840_vconfig(struct i2c_client *client, u32 cfg_in);
>  
>  /* ----------------------------------------------------------------------- */
>  /* cx25850-firmware.c                                                      */
> diff --git a/drivers/media/i2c/cx25840/cx25840-vbi.c b/drivers/media/i2c/cx25840/cx25840-vbi.c
> index 8c99a79fb726..23b7c1fb28ab 100644
> --- a/drivers/media/i2c/cx25840/cx25840-vbi.c
> +++ b/drivers/media/i2c/cx25840/cx25840-vbi.c
> @@ -95,6 +95,7 @@ int cx25840_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *
>  	memset(svbi->service_lines, 0, sizeof(svbi->service_lines));
>  	svbi->service_set = 0;
>  	/* we're done if raw VBI is active */
> +	/* this will have to be changed for generic_mode VBI */

Prefix this comment with 'TODO:'. Ditto for the similar comments below.

>  	if ((cx25840_read(client, 0x404) & 0x10) == 0)
>  		return 0;
>  
> @@ -137,6 +138,7 @@ int cx25840_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt)
>  		cx25840_write(client, 0x54f, vbi_offset);
>  	else
>  		cx25840_write(client, 0x47f, vbi_offset);
> +	/* this will have to be changed for generic_mode VBI */
>  	cx25840_write(client, 0x404, 0x2e);
>  	return 0;
>  }
> @@ -157,6 +159,7 @@ int cx25840_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *
>  	cx25840_std_setup(client);
>  
>  	/* Sliced VBI */
> +	/* this will have to be changed for generic_mode VBI */
>  	cx25840_write(client, 0x404, 0x32);	/* Ancillary data */
>  	cx25840_write(client, 0x406, 0x13);
>  	if (is_cx23888(state))
> diff --git a/include/media/drv-intf/cx25840.h b/include/media/drv-intf/cx25840.h
> index 783c5bdd63eb..dc104b4f09eb 100644
> --- a/include/media/drv-intf/cx25840.h
> +++ b/include/media/drv-intf/cx25840.h
> @@ -88,6 +88,70 @@ enum cx25840_video_input {
>  	CX25840_DIF_ON = 0x80000400,
>  };
>  
> +/* arguments to video s_routing config param */
> +#define CX25840_VCONFIG_FMT_SHIFT 0
> +#define CX25840_VCONFIG_FMT_MASK GENMASK(2, 0)
> +#define CX25840_VCONFIG_FMT_BT601 BIT(0)
> +#define CX25840_VCONFIG_FMT_BT656 BIT(1)
> +#define CX25840_VCONFIG_FMT_VIP11 GENMASK(1, 0)
> +#define CX25840_VCONFIG_FMT_VIP2 BIT(2)
> +
> +#define CX25840_VCONFIG_RES_SHIFT 3
> +#define CX25840_VCONFIG_RES_MASK GENMASK(4, 3)
> +#define CX25840_VCONFIG_RES_8BIT BIT(3)
> +#define CX25840_VCONFIG_RES_10BIT BIT(4)
> +
> +#define CX25840_VCONFIG_VBIRAW_SHIFT 5
> +#define CX25840_VCONFIG_VBIRAW_MASK GENMASK(6, 5)
> +#define CX25840_VCONFIG_VBIRAW_DISABLED BIT(5)
> +#define CX25840_VCONFIG_VBIRAW_ENABLED BIT(6)
> +
> +#define CX25840_VCONFIG_ANCDATA_SHIFT 7
> +#define CX25840_VCONFIG_ANCDATA_MASK GENMASK(8, 7)
> +#define CX25840_VCONFIG_ANCDATA_DISABLED BIT(7)
> +#define CX25840_VCONFIG_ANCDATA_ENABLED BIT(8)
> +
> +#define CX25840_VCONFIG_TASKBIT_SHIFT 9
> +#define CX25840_VCONFIG_TASKBIT_MASK GENMASK(10, 9)
> +#define CX25840_VCONFIG_TASKBIT_ZERO BIT(9)
> +#define CX25840_VCONFIG_TASKBIT_ONE BIT(10)
> +
> +#define CX25840_VCONFIG_ACTIVE_SHIFT 11
> +#define CX25840_VCONFIG_ACTIVE_MASK GENMASK(12, 11)
> +#define CX25840_VCONFIG_ACTIVE_COMPOSITE BIT(11)
> +#define CX25840_VCONFIG_ACTIVE_HORIZONTAL BIT(12)
> +
> +#define CX25840_VCONFIG_VALID_SHIFT 13
> +#define CX25840_VCONFIG_VALID_MASK GENMASK(14, 13)
> +#define CX25840_VCONFIG_VALID_NORMAL BIT(13)
> +#define CX25840_VCONFIG_VALID_ANDACTIVE BIT(14)
> +
> +#define CX25840_VCONFIG_HRESETW_SHIFT 15
> +#define CX25840_VCONFIG_HRESETW_MASK GENMASK(16, 15)
> +#define CX25840_VCONFIG_HRESETW_NORMAL BIT(15)
> +#define CX25840_VCONFIG_HRESETW_PIXCLK BIT(16)
> +
> +#define CX25840_VCONFIG_CLKGATE_SHIFT 17
> +#define CX25840_VCONFIG_CLKGATE_MASK GENMASK(18, 17)
> +#define CX25840_VCONFIG_CLKGATE_NONE BIT(17)
> +#define CX25840_VCONFIG_CLKGATE_VALID BIT(18)
> +#define CX25840_VCONFIG_CLKGATE_VALIDACTIVE GENMASK(18, 17)
> +
> +#define CX25840_VCONFIG_DCMODE_SHIFT 19
> +#define CX25840_VCONFIG_DCMODE_MASK GENMASK(20, 19)
> +#define CX25840_VCONFIG_DCMODE_DWORDS BIT(19)
> +#define CX25840_VCONFIG_DCMODE_BYTES BIT(20)
> +
> +#define CX25840_VCONFIG_IDID0S_SHIFT 21
> +#define CX25840_VCONFIG_IDID0S_MASK GENMASK(22, 21)
> +#define CX25840_VCONFIG_IDID0S_NORMAL BIT(21)
> +#define CX25840_VCONFIG_IDID0S_LINECNT BIT(22)
> +
> +#define CX25840_VCONFIG_VIPCLAMP_SHIFT 23
> +#define CX25840_VCONFIG_VIPCLAMP_MASK GENMASK(24, 23)
> +#define CX25840_VCONFIG_VIPCLAMP_ENABLED BIT(23)
> +#define CX25840_VCONFIG_VIPCLAMP_DISABLED BIT(24)
> +
>  enum cx25840_audio_input {
>  	/* Audio inputs: serial or In4-In8 */
>  	CX25840_AUDIO_SERIAL,
> @@ -180,9 +244,17 @@ enum cx23885_io_pad {
>     audio autodetect fails on some channels for these models and the workaround
>     is to select the audio standard explicitly. Many thanks to Hauppauge for
>     providing this information.
> -   This platform data only needs to be supplied by the ivtv driver. */
> +   This platform data only needs to be supplied by the ivtv driver.

This statement is no longer true since cxusb will use it as well.

> +
> +   generic_mode disables some of the ivtv-related hacks in this driver,
> +   enables setting video output config and sets it according to datasheet
> +   defaults on initialization.
> +   This flag is to be used for example with USB video capture devices
> +   using this chip.
> +*/
>  struct cx25840_platform_data {
>  	int pvr150_workaround;
> +	int generic_mode;

This can be a bool.

>  };
>  
>  #endif
> 

Regards,

	Hans

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v7 6/6] [media] cxusb: add analog mode support for Medion MD95700
  2018-07-02 21:23 ` [PATCH v7 6/6] [media] cxusb: add analog mode support for Medion MD95700 Maciej S. Szmigiero
@ 2018-07-04  9:33   ` Hans Verkuil
  2018-07-10 21:48     ` Maciej S. Szmigiero
  0 siblings, 1 reply; 14+ messages in thread
From: Hans Verkuil @ 2018-07-04  9:33 UTC (permalink / raw)
  To: Maciej S. Szmigiero, Michael Krufky, Mauro Carvalho Chehab
  Cc: Andy Walls, linux-kernel, linux-media

Hi Maciej,

On 02/07/18 23:23, Maciej S. Szmigiero wrote:
> This patch adds support for analog part of Medion 95700 in the cxusb
> driver.
> 
> What works:
> * Video capture at various sizes with sequential fields,
> * Input switching (TV Tuner, Composite, S-Video),
> * TV and radio tuning,
> * Video standard switching and auto detection,
> * Radio mode switching (stereo / mono),
> * Unplugging while capturing,
> * DVB / analog coexistence,
> * Raw BT.656 stream support.
> 
> What does not work yet:
> * Audio,
> * VBI,
> * Picture controls.
> 
> Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
> ---
>  drivers/media/usb/dvb-usb/Kconfig        |   16 +-
>  drivers/media/usb/dvb-usb/Makefile       |    3 +
>  drivers/media/usb/dvb-usb/cxusb-analog.c | 1914 ++++++++++++++++++++++
>  drivers/media/usb/dvb-usb/cxusb.c        |    2 -
>  drivers/media/usb/dvb-usb/cxusb.h        |  106 ++
>  5 files changed, 2037 insertions(+), 4 deletions(-)
>  create mode 100644 drivers/media/usb/dvb-usb/cxusb-analog.c
> 
> diff --git a/drivers/media/usb/dvb-usb/Kconfig b/drivers/media/usb/dvb-usb/Kconfig
> index b8a1c62a0682..010d00ddeb05 100644
> --- a/drivers/media/usb/dvb-usb/Kconfig
> +++ b/drivers/media/usb/dvb-usb/Kconfig
> @@ -138,12 +138,24 @@ config DVB_USB_CXUSB
>  	select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT
>  	help
>  	  Say Y here to support the Conexant USB2.0 hybrid reference design.
> -	  Currently, only DVB and ATSC modes are supported, analog mode
> -	  shall be added in the future. Devices that require this module:
> +	  DVB and ATSC modes are supported, for a basic analog mode support
> +	  see the next option ("Analog support for the Conexant USB2.0 hybrid
> +	  reference design").
> +	  Devices that require this module:
>  
>  	  Medion MD95700 hybrid USB2.0 device.
>  	  DViCO FusionHDTV (Bluebird) USB2.0 devices
>  
> +config DVB_USB_CXUSB_ANALOG
> +	bool "Analog support for the Conexant USB2.0 hybrid reference design"
> +	depends on DVB_USB_CXUSB && VIDEO_V4L2
> +	select VIDEO_CX25840
> +	select VIDEOBUF2_VMALLOC
> +	help
> +	  Say Y here to enable basic analog mode support for the Conexant
> +	  USB2.0 hybrid reference design.
> +	  Currently this mode is supported only on a Medion MD95700 device.
> +
>  config DVB_USB_M920X
>  	tristate "Uli m920x DVB-T USB2.0 support"
>  	depends on DVB_USB
> diff --git a/drivers/media/usb/dvb-usb/Makefile b/drivers/media/usb/dvb-usb/Makefile
> index 9ad2618408ef..e47bcadcfc3d 100644
> --- a/drivers/media/usb/dvb-usb/Makefile
> +++ b/drivers/media/usb/dvb-usb/Makefile
> @@ -42,6 +42,9 @@ dvb-usb-digitv-objs := digitv.o
>  obj-$(CONFIG_DVB_USB_DIGITV) += dvb-usb-digitv.o
>  
>  dvb-usb-cxusb-objs := cxusb.o
> +ifeq ($(CONFIG_DVB_USB_CXUSB_ANALOG),y)
> +dvb-usb-cxusb-objs += cxusb-analog.o
> +endif
>  obj-$(CONFIG_DVB_USB_CXUSB) += dvb-usb-cxusb.o
>  
>  dvb-usb-ttusb2-objs := ttusb2.o
> diff --git a/drivers/media/usb/dvb-usb/cxusb-analog.c b/drivers/media/usb/dvb-usb/cxusb-analog.c
> new file mode 100644
> index 000000000000..969d82b24f41
> --- /dev/null
> +++ b/drivers/media/usb/dvb-usb/cxusb-analog.c
> @@ -0,0 +1,1914 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +//
> +// DVB USB compliant linux driver for Conexant USB reference design -
> +// (analog part).
> +//
> +// Copyright (C) 2011, 2017 Maciej S. Szmigiero (mail@maciej.szmigiero.name)
> +//
> +// TODO:
> +//  * audio support,
> +//  * finish radio support (requires audio of course),
> +//  * VBI support,
> +//  * controls support
> +
> +#include <linux/bitops.h>
> +#include <linux/device.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <linux/timekeeping.h>
> +#include <linux/vmalloc.h>
> +#include <media/drv-intf/cx25840.h>
> +#include <media/tuner.h>
> +#include <media/v4l2-fh.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-v4l2.h>
> +#include <media/videobuf2-vmalloc.h>
> +
> +#include "cxusb.h"
> +
> +static int cxusb_medion_v_queue_setup(struct vb2_queue *q,
> +				      unsigned int *num_buffers,
> +				      unsigned int *num_planes,
> +				      unsigned int sizes[],
> +				      struct device *alloc_devs[])
> +{
> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	unsigned int size = cxdev->raw_mode ?
> +		CXUSB_VIDEO_MAX_FRAME_SIZE :
> +		cxdev->width * cxdev->height * 2;
> +
> +	if (*num_planes > 0) {
> +		if (*num_planes != 1)
> +			return -EINVAL;
> +
> +		if (sizes[0] < size)
> +			return -EINVAL;
> +	} else {
> +		*num_planes = 1;
> +		sizes[0] = size;
> +	}
> +
> +	if (q->num_buffers + *num_buffers < 6)
> +		*num_buffers = 6 - q->num_buffers;

Huh? These two lines should be removed. I'm not sure what the purpose is.

> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_v_buf_init(struct vb2_buffer *vb)
> +{
> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(vb->vb2_queue);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +
> +	cxusb_vprintk(dvbdev, OPS, "buffer init\n");
> +
> +	if (cxdev->raw_mode) {
> +		if (vb2_plane_size(vb, 0) < CXUSB_VIDEO_MAX_FRAME_SIZE)
> +			return -ENOMEM;
> +	} else {
> +		if (vb2_plane_size(vb, 0) < cxdev->width * cxdev->height * 2)
> +			return -ENOMEM;
> +	}
> +
> +	cxusb_vprintk(dvbdev, OPS, "buffer OK\n");
> +
> +	return 0;
> +}
> +
> +static void cxusb_auxbuf_init(struct cxusb_medion_auxbuf *auxbuf,
> +			      u8 *buf, unsigned int len)
> +{
> +	auxbuf->buf = buf;
> +	auxbuf->len = len;
> +	auxbuf->paylen = 0;
> +}
> +
> +static void cxusb_auxbuf_head_trim(struct dvb_usb_device *dvbdev,
> +				   struct cxusb_medion_auxbuf *auxbuf,
> +				   unsigned int pos)
> +{
> +	if (pos == 0)
> +		return;
> +
> +	if (WARN_ON(pos > auxbuf->paylen))
> +		return;
> +
> +	cxusb_vprintk(dvbdev, AUXB,
> +		      "trimming auxbuf len by %u to %u\n",
> +		      pos, auxbuf->paylen - pos);
> +
> +	memmove(auxbuf->buf, auxbuf->buf + pos, auxbuf->paylen - pos);
> +	auxbuf->paylen -= pos;
> +}
> +
> +static unsigned int cxusb_auxbuf_paylen(struct cxusb_medion_auxbuf *auxbuf)
> +{
> +	return auxbuf->paylen;
> +}
> +
> +static bool cxusb_auxbuf_make_space(struct dvb_usb_device *dvbdev,
> +				    struct cxusb_medion_auxbuf *auxbuf,
> +				    unsigned int howmuch)
> +{
> +	unsigned int freespace;
> +
> +	if (WARN_ON(howmuch >= auxbuf->len))
> +		howmuch = auxbuf->len - 1;
> +
> +	freespace = auxbuf->len - cxusb_auxbuf_paylen(auxbuf);
> +
> +	cxusb_vprintk(dvbdev, AUXB, "freespace is %u\n", freespace);
> +
> +	if (freespace >= howmuch)
> +		return true;
> +
> +	howmuch -= freespace;
> +
> +	cxusb_vprintk(dvbdev, AUXB, "will overwrite %u bytes of buffer\n",
> +		      howmuch);
> +
> +	cxusb_auxbuf_head_trim(dvbdev, auxbuf, howmuch);
> +
> +	return false;
> +}
> +
> +/* returns false if some data was overwritten */
> +static bool cxusb_auxbuf_append_urb(struct dvb_usb_device *dvbdev,
> +				    struct cxusb_medion_auxbuf *auxbuf,
> +				    struct urb *urb)
> +{
> +	unsigned long len = 0;
> +	int i;
> +	bool ret;
> +
> +	for (i = 0; i < urb->number_of_packets; i++)
> +		len += urb->iso_frame_desc[i].actual_length;
> +
> +	ret = cxusb_auxbuf_make_space(dvbdev, auxbuf, len);
> +
> +	for (i = 0; i < urb->number_of_packets; i++) {
> +		unsigned int to_copy;
> +
> +		to_copy = urb->iso_frame_desc[i].actual_length;
> +
> +		memcpy(auxbuf->buf + auxbuf->paylen, urb->transfer_buffer +
> +		       urb->iso_frame_desc[i].offset, to_copy);
> +
> +		auxbuf->paylen += to_copy;
> +	}
> +
> +	return ret;
> +}
> +
> +static bool cxusb_auxbuf_copy(struct cxusb_medion_auxbuf *auxbuf,
> +			      unsigned int pos, unsigned char *dest,
> +			      unsigned int len)
> +{
> +	if (pos + len > auxbuf->paylen)
> +		return false;
> +
> +	memcpy(dest, auxbuf->buf + pos, len);
> +
> +	return true;
> +}
> +
> +static unsigned int cxusb_auxbuf_advance(struct cxusb_medion_auxbuf *auxbuf,
> +					 unsigned int pos,
> +					 unsigned int increment)
> +{
> +	return pos + increment;
> +}
> +
> +static unsigned int cxusb_auxbuf_begin(struct cxusb_medion_auxbuf *auxbuf)
> +{
> +	return 0;
> +}
> +
> +static bool cxusb_auxbuf_isend(struct cxusb_medion_auxbuf *auxbuf,
> +			       unsigned int pos)
> +{
> +	return pos >= auxbuf->paylen;
> +}

These three functions seem pointless to me.

> +
> +static bool cxusb_medion_cf_refc_fld_chg(struct dvb_usb_device *dvbdev,
> +					 struct cxusb_bt656_params *bt656,
> +					 bool firstfield,
> +					 unsigned int maxlines,
> +					 unsigned int maxlinesamples,
> +					 unsigned char buf[4])
> +{
> +	bool firstfield_code = (buf[3] & CXUSB_BT656_FIELD_MASK) ==
> +		CXUSB_BT656_FIELD_1;
> +	unsigned int remlines;
> +
> +	if (bt656->line == 0 || firstfield == firstfield_code)
> +		return false;
> +
> +	if (bt656->fmode == LINE_SAMPLES) {
> +		unsigned int remsamples = maxlinesamples -
> +			bt656->linesamples;
> +
> +		cxusb_vprintk(dvbdev, BT656,
> +			      "field %c after line %u field change\n",
> +			      firstfield ? '1' : '2', bt656->line);
> +
> +		if (bt656->buf != NULL && remsamples > 0) {
> +			memset(bt656->buf, 0, remsamples);
> +			bt656->buf += remsamples;
> +
> +			cxusb_vprintk(dvbdev, BT656,
> +				      "field %c line %u %u samples still remaining (of %u)\n",
> +				      firstfield ? '1' : '2',
> +				      bt656->line, remsamples,
> +				      maxlinesamples);
> +		}
> +
> +		bt656->line++;
> +	}
> +
> +	remlines = maxlines - bt656->line;
> +	if (bt656->buf != NULL && remlines > 0) {
> +		memset(bt656->buf, 0, remlines * maxlinesamples);
> +		bt656->buf += remlines * maxlinesamples;
> +
> +		cxusb_vprintk(dvbdev, BT656,
> +			      "field %c %u lines still remaining (of %u)\n",
> +			      firstfield ? '1' : '2', remlines,
> +			      maxlines);
> +	}
> +
> +	return true;
> +}
> +
> +static bool cxusb_medion_cf_refc_start_sch(struct dvb_usb_device *dvbdev,
> +					   struct cxusb_medion_auxbuf *auxbuf,
> +					   struct cxusb_bt656_params *bt656,
> +					   bool firstfield,
> +					   unsigned char buf[4])
> +{
> +	bool firstfield_code = (buf[3] & CXUSB_BT656_FIELD_MASK) ==
> +		CXUSB_BT656_FIELD_1;
> +	bool sav_code = (buf[3] & CXUSB_BT656_SEAV_MASK) ==
> +		CXUSB_BT656_SEAV_SAV;
> +	bool vbi_code = (buf[3] & CXUSB_BT656_VBI_MASK) ==
> +		CXUSB_BT656_VBI_ON;
> +
> +	if (bt656->fmode != START_SEARCH)
> +		return false;
> +
> +	if (sav_code && firstfield == firstfield_code) {
> +		if (!vbi_code) {
> +			cxusb_vprintk(dvbdev, BT656, "line start @ pos %x\n",
> +				      bt656->pos);
> +
> +			bt656->linesamples = 0;
> +			bt656->fmode = LINE_SAMPLES;
> +		} else {
> +			cxusb_vprintk(dvbdev, BT656, "VBI start @ pos %x\n",
> +				      bt656->pos);
> +
> +			bt656->fmode = VBI_SAMPLES;
> +		}
> +	}
> +
> +	bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 4);
> +
> +	return true;
> +}
> +
> +static bool cxusb_medion_cf_refc_line_smpl(struct dvb_usb_device *dvbdev,
> +					   struct cxusb_bt656_params *bt656,
> +					   bool firstfield,
> +					   unsigned int maxlinesamples,
> +					   unsigned char buf[4])
> +{
> +	bool sav_code = (buf[3] & CXUSB_BT656_SEAV_MASK) ==
> +		CXUSB_BT656_SEAV_SAV;
> +	unsigned int remsamples;
> +
> +	if (bt656->fmode != LINE_SAMPLES)
> +		return false;
> +
> +	if (sav_code)
> +		cxusb_vprintk(dvbdev, BT656,
> +			      "SAV in line samples @ line %u, pos %x\n",
> +			      bt656->line, bt656->pos);
> +
> +	remsamples = maxlinesamples - bt656->linesamples;
> +	if (bt656->buf != NULL && remsamples > 0) {
> +		memset(bt656->buf, 0, remsamples);
> +		bt656->buf += remsamples;
> +
> +		cxusb_vprintk(dvbdev, BT656,
> +			      "field %c line %u %u samples still remaining (of %u)\n",
> +			      firstfield ? '1' : '2', bt656->line, remsamples,
> +			      maxlinesamples);
> +	}
> +
> +	bt656->fmode = START_SEARCH;
> +	bt656->line++;
> +
> +	return true;
> +}
> +
> +static bool cxusb_medion_cf_refc_vbi_smpl(struct dvb_usb_device *dvbdev,
> +					  struct cxusb_bt656_params *bt656,
> +					  unsigned char buf[4])
> +{
> +	bool sav_code = (buf[3] & CXUSB_BT656_SEAV_MASK) ==
> +		CXUSB_BT656_SEAV_SAV;
> +
> +	if (bt656->fmode != VBI_SAMPLES)
> +		return false;
> +
> +	if (sav_code)
> +		cxusb_vprintk(dvbdev, BT656, "SAV in VBI samples @ pos %x\n",
> +			      bt656->pos);
> +
> +	bt656->fmode = START_SEARCH;
> +
> +	return true;
> +}
> +
> +static void cxusb_medion_cf_ref_code(struct dvb_usb_device *dvbdev,
> +				     struct cxusb_medion_auxbuf *auxbuf,
> +				     struct cxusb_bt656_params *bt656,
> +				     bool firstfield,
> +				     unsigned int maxlines,
> +				     unsigned int maxlinesamples,
> +				     unsigned char buf[4])
> +{
> +	if (cxusb_medion_cf_refc_start_sch(dvbdev, auxbuf, bt656, firstfield,
> +					   buf))
> +		return;
> +
> +	if (cxusb_medion_cf_refc_line_smpl(dvbdev, bt656, firstfield,
> +					   maxlinesamples, buf))
> +		return;
> +
> +	if (cxusb_medion_cf_refc_vbi_smpl(dvbdev, bt656, buf))
> +		return;
> +
> +	bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 4);
> +}
> +
> +static bool cxusb_medion_cs_start_sch(struct dvb_usb_device *dvbdev,
> +				      struct cxusb_medion_auxbuf *auxbuf,
> +				      struct cxusb_bt656_params *bt656,
> +				      unsigned int maxlinesamples)
> +{
> +	unsigned char buf[64];
> +	unsigned int idx;
> +	unsigned int tocheck = clamp_t(size_t, maxlinesamples / 4, 3,
> +				       sizeof(buf));
> +
> +	if (bt656->fmode != START_SEARCH || bt656->line == 0)
> +		return false;
> +
> +	if (!cxusb_auxbuf_copy(auxbuf, bt656->pos + 1, buf, tocheck)) {
> +		bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 1);
> +		return true;
> +	}
> +
> +	for (idx = 0; idx <= tocheck - 3; idx++)
> +		if (memcmp(buf + idx, CXUSB_BT656_PREAMBLE, 3) == 0)
> +			break;
> +
> +	if (idx <= tocheck - 3) {
> +		bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 1);
> +		return true;
> +	}
> +
> +	cxusb_vprintk(dvbdev, BT656, "line %u early start, pos %x\n",
> +		      bt656->line, bt656->pos);
> +
> +	bt656->linesamples = 0;
> +	bt656->fmode = LINE_SAMPLES;
> +
> +	return true;
> +}
> +
> +static bool cxusb_medion_cs_line_smpl(struct dvb_usb_device *dvbdev,
> +				      struct cxusb_medion_auxbuf *auxbuf,
> +				      struct cxusb_bt656_params *bt656,
> +				      unsigned int maxlinesamples,
> +				      unsigned char val)
> +{
> +	if (bt656->fmode != LINE_SAMPLES)
> +		return false;
> +
> +	if (bt656->buf != NULL)
> +		*(bt656->buf++) = val;
> +
> +	bt656->linesamples++;
> +	bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 1);
> +
> +	if (bt656->linesamples >= maxlinesamples) {
> +		bt656->fmode = START_SEARCH;
> +		bt656->line++;
> +	}
> +
> +	return true;
> +}
> +
> +static void cxusb_medion_copy_samples(struct dvb_usb_device *dvbdev,
> +				      struct cxusb_medion_auxbuf *auxbuf,
> +				      struct cxusb_bt656_params *bt656,
> +				      unsigned int maxlinesamples,
> +				      unsigned char val)
> +{
> +	if (cxusb_medion_cs_start_sch(dvbdev, auxbuf, bt656, maxlinesamples))
> +		return;
> +
> +	if (cxusb_medion_cs_line_smpl(dvbdev, auxbuf, bt656, maxlinesamples,
> +				      val))
> +		return;
> +
> +	/* TODO: copy VBI samples */
> +	bt656->pos = cxusb_auxbuf_advance(auxbuf, bt656->pos, 1);
> +}
> +
> +static bool cxusb_medion_copy_field(struct dvb_usb_device *dvbdev,
> +				    struct cxusb_medion_auxbuf *auxbuf,
> +				    struct cxusb_bt656_params *bt656,
> +				    bool firstfield,
> +				    unsigned int maxlines,
> +				    unsigned int maxlinesmpls)
> +{
> +	while (bt656->line < maxlines &&
> +	       !cxusb_auxbuf_isend(auxbuf, bt656->pos)) {
> +		unsigned char val;
> +
> +		if (!cxusb_auxbuf_copy(auxbuf, bt656->pos, &val, 1))
> +			return false;
> +
> +		if (val == CXUSB_BT656_PREAMBLE[0]) {
> +			unsigned char buf[4];
> +
> +			buf[0] = val;
> +			if (!cxusb_auxbuf_copy(auxbuf, bt656->pos + 1,
> +					       buf + 1, 3))
> +				return false;
> +
> +			if (buf[1] == CXUSB_BT656_PREAMBLE[1] &&
> +			    buf[2] == CXUSB_BT656_PREAMBLE[2]) {
> +				if (cxusb_medion_cf_refc_fld_chg(dvbdev,
> +								 bt656,
> +								 firstfield,
> +								 maxlines,
> +								 maxlinesmpls,
> +								 buf))
> +					return true;
> +
> +				cxusb_medion_cf_ref_code(dvbdev, auxbuf,
> +							 bt656, firstfield,
> +							 maxlines,
> +							 maxlinesmpls, buf);
> +
> +				continue;
> +			}
> +		}
> +
> +		cxusb_medion_copy_samples(dvbdev, auxbuf, bt656, maxlinesmpls,
> +					  val);
> +	}
> +
> +	if (bt656->line < maxlines) {
> +		cxusb_vprintk(dvbdev, BT656,
> +			      "end of buffer pos = %u, line = %u\n",
> +			      bt656->pos, bt656->line);
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +static void cxusb_medion_v_process_urb_raw_mode(struct cxusb_medion_dev *cxdev,
> +						struct urb *urb)
> +{
> +	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
> +	u8 *buf;
> +	struct cxusb_medion_vbuffer *vbuf;
> +	int i;
> +	unsigned long len = 0;
> +
> +	if (list_empty(&cxdev->buflist)) {
> +		dev_warn(&dvbdev->udev->dev, "no free buffers\n");
> +		return;
> +	}
> +
> +	vbuf = list_first_entry(&cxdev->buflist, struct cxusb_medion_vbuffer,
> +				list);
> +	list_del(&vbuf->list);
> +
> +	vbuf->vb2.timestamp = ktime_get_ns();
> +
> +	buf = vb2_plane_vaddr(&vbuf->vb2, 0);
> +
> +	for (i = 0; i < urb->number_of_packets; i++) {
> +		memcpy(buf, urb->transfer_buffer +
> +		       urb->iso_frame_desc[i].offset,
> +		       urb->iso_frame_desc[i].actual_length);
> +
> +		buf += urb->iso_frame_desc[i].actual_length;
> +		len += urb->iso_frame_desc[i].actual_length;
> +	}
> +
> +	vb2_set_plane_payload(&vbuf->vb2, 0, len);
> +
> +	vb2_buffer_done(&vbuf->vb2, VB2_BUF_STATE_DONE);

The frame sequence counter in vb2_v4l2_buffer does not seem to be incremented.

Did you test this driver with v4l2-compliance? It will detect such errors.

> +}
> +
> +static void cxusb_medion_v_process_urb(struct cxusb_medion_dev *cxdev,
> +				       struct urb *urb)
> +{
> +	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
> +	struct cxusb_bt656_params *bt656 = &cxdev->bt656;
> +	bool reset;
> +
> +	cxusb_vprintk(dvbdev, URB, "appending urb\n");
> +
> +	/*
> +	 * append new data to circ. buffer
> +	 * overwrite old data if necessary
> +	 */
> +	reset = !cxusb_auxbuf_append_urb(dvbdev, &cxdev->auxbuf, urb);
> +
> +	/*
> +	 * if this is a new frame
> +	 * fetch a buffer from list
> +	 */
> +	if (bt656->mode == NEW_FRAME) {
> +		if (!list_empty(&cxdev->buflist)) {
> +			cxdev->vbuf =
> +				list_first_entry(&cxdev->buflist,
> +						 struct cxusb_medion_vbuffer,
> +						 list);
> +			list_del(&cxdev->vbuf->list);
> +
> +			cxdev->vbuf->vb2.timestamp = ktime_get_ns();
> +		} else
> +			dev_warn(&dvbdev->udev->dev, "no free buffers\n");
> +	}
> +
> +	if (bt656->mode == NEW_FRAME || reset) {
> +		bt656->pos = cxusb_auxbuf_begin(&cxdev->auxbuf);
> +		bt656->mode = FIRST_FIELD;
> +		bt656->fmode = START_SEARCH;
> +		bt656->line = 0;
> +
> +		if (cxdev->vbuf != NULL)
> +			bt656->buf = vb2_plane_vaddr(&cxdev->vbuf->vb2, 0);
> +	}
> +
> +	cxusb_vprintk(dvbdev, URB, "auxbuf payload len %u",
> +		      cxusb_auxbuf_paylen(&cxdev->auxbuf));
> +
> +	if (bt656->mode == FIRST_FIELD) {
> +		cxusb_vprintk(dvbdev, URB, "copying field 1\n");
> +
> +		if (!cxusb_medion_copy_field(dvbdev, &cxdev->auxbuf, bt656,
> +					     true, cxdev->height / 2,
> +					     cxdev->width * 2))
> +			return;
> +
> +		/*
> +		 * do not trim buffer there in case
> +		 * we need to reset search later
> +		 */
> +		bt656->mode = SECOND_FIELD;
> +		bt656->fmode = START_SEARCH;
> +		bt656->line = 0;
> +	}
> +
> +	if (bt656->mode == SECOND_FIELD) {
> +		cxusb_vprintk(dvbdev, URB, "copying field 2\n");
> +
> +		if (!cxusb_medion_copy_field(dvbdev, &cxdev->auxbuf, bt656,
> +					     false, cxdev->height / 2,
> +					     cxdev->width * 2))
> +			return;
> +
> +		cxusb_auxbuf_head_trim(dvbdev, &cxdev->auxbuf, bt656->pos);
> +
> +		bt656->mode = NEW_FRAME;
> +
> +		if (cxdev->vbuf != NULL) {
> +			vb2_set_plane_payload(&cxdev->vbuf->vb2, 0,
> +					      cxdev->width * cxdev->height * 2);
> +
> +			vb2_buffer_done(&cxdev->vbuf->vb2, VB2_BUF_STATE_DONE);
> +
> +			cxdev->vbuf = NULL;
> +			cxdev->bt656.buf = NULL;
> +
> +			cxusb_vprintk(dvbdev, URB, "frame done\n");
> +		} else
> +			cxusb_vprintk(dvbdev, URB, "frame skipped\n");
> +	}
> +}
> +
> +static void cxusb_medion_v_complete_work(struct work_struct *work)
> +{
> +	struct cxusb_medion_dev *cxdev = container_of(work,
> +						      struct cxusb_medion_dev,
> +						      urbwork);
> +	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
> +	struct urb *urb;
> +	int ret;
> +	unsigned int i, urbn;
> +
> +	mutex_lock(cxdev->videodev->lock);
> +
> +	cxusb_vprintk(dvbdev, URB, "worker called, streaming = %d\n",
> +		      (int)cxdev->streaming);
> +
> +	if (!cxdev->streaming)
> +		goto unlock;
> +
> +	urbn = cxdev->nexturb;
> +	if (test_bit(urbn, &cxdev->urbcomplete)) {
> +		urb = cxdev->streamurbs[urbn];
> +		clear_bit(urbn, &cxdev->urbcomplete);
> +
> +		cxdev->nexturb++;
> +		cxdev->nexturb %= CXUSB_VIDEO_URBS;
> +	} else {
> +		for (i = 0, urbn++; i < CXUSB_VIDEO_URBS - 1; i++, urbn++) {
> +			urbn %= CXUSB_VIDEO_URBS;
> +			if (test_bit(urbn, &cxdev->urbcomplete)) {
> +				urb = cxdev->streamurbs[urbn];
> +				clear_bit(urbn, &cxdev->urbcomplete);
> +				break;
> +			}
> +		}
> +
> +		if (i >= CXUSB_VIDEO_URBS - 1) {
> +			cxusb_vprintk(dvbdev, URB,
> +				      "URB worker called but no URB ready\n");
> +			goto unlock;
> +		}
> +
> +		cxusb_vprintk(dvbdev, URB,
> +			      "out-of-order URB: expected %u but %u is ready\n",
> +			      cxdev->nexturb, urbn);
> +
> +		cxdev->nexturb = urbn + 1;
> +		cxdev->nexturb %= CXUSB_VIDEO_URBS;
> +	}
> +
> +	cxusb_vprintk(dvbdev, URB, "URB %u status = %d\n", urbn, urb->status);
> +
> +	if (urb->status == 0 || urb->status == -EXDEV) {
> +		int i;
> +		unsigned long len = 0;
> +
> +		for (i = 0; i < urb->number_of_packets; i++)
> +			len += urb->iso_frame_desc[i].actual_length;
> +
> +		cxusb_vprintk(dvbdev, URB, "URB %u data len = %lu\n",
> +			      urbn, len);
> +
> +		if (len > 0) {
> +			if (cxdev->raw_mode)
> +				cxusb_medion_v_process_urb_raw_mode(cxdev, urb);
> +			else
> +				cxusb_medion_v_process_urb(cxdev, urb);
> +		}
> +	}
> +
> +	cxusb_vprintk(dvbdev, URB, "URB %u submit\n", urbn);
> +
> +	ret = usb_submit_urb(urb, GFP_KERNEL);
> +	if (ret != 0)
> +		dev_err(&dvbdev->udev->dev,
> +			"unable to submit URB (%d), you'll have to restart streaming\n",
> +			ret);
> +
> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
> +		if (test_bit(i, &cxdev->urbcomplete)) {
> +			schedule_work(&cxdev->urbwork);
> +			break;
> +		}
> +
> +unlock:
> +	mutex_unlock(cxdev->videodev->lock);
> +}
> +
> +static void cxusb_medion_v_complete(struct urb *u)
> +{
> +	struct dvb_usb_device *dvbdev = u->context;
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	unsigned int i;
> +
> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
> +		if (cxdev->streamurbs[i] == u)
> +			break;
> +
> +	if (i >= CXUSB_VIDEO_URBS) {
> +		dev_err(&dvbdev->udev->dev,
> +			"complete on unknown URB\n");
> +		return;
> +	}
> +
> +	cxusb_vprintk(dvbdev, URB, "URB %d complete\n", i);
> +
> +	set_bit(i, &cxdev->urbcomplete);
> +	schedule_work(&cxdev->urbwork);
> +}
> +
> +static bool cxusb_medion_stream_busy(struct cxusb_medion_dev *cxdev)
> +{
> +	int i;
> +
> +	if (cxdev->streaming)
> +		return true;
> +
> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
> +		/*
> +		 * not streaming but URB is still active -
> +		 * stream is being stopped
> +		 */
> +		if (cxdev->streamurbs[i] != NULL)
> +			return true;
> +
> +	return false;
> +}
> +
> +static void cxusb_medion_return_buffers(struct cxusb_medion_dev *cxdev,
> +					bool requeue)
> +{
> +	struct cxusb_medion_vbuffer *vbuf, *vbuf_tmp;
> +
> +	list_for_each_entry_safe(vbuf, vbuf_tmp, &cxdev->buflist,
> +				 list) {
> +		list_del(&vbuf->list);
> +		vb2_buffer_done(&vbuf->vb2, requeue ? VB2_BUF_STATE_QUEUED :
> +				VB2_BUF_STATE_ERROR);
> +	}
> +
> +	if (cxdev->vbuf != NULL) {
> +		vb2_buffer_done(&cxdev->vbuf->vb2, requeue ?
> +				VB2_BUF_STATE_QUEUED :
> +				VB2_BUF_STATE_ERROR);
> +
> +		cxdev->vbuf = NULL;
> +		cxdev->bt656.buf = NULL;
> +	}
> +}
> +
> +static int cxusb_medion_v_start_streaming(struct vb2_queue *q,
> +					  unsigned int count)
> +{
> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	u8 streamon_params[2] = { 0x03, 0x00 };
> +	int npackets, i;
> +	int ret;
> +
> +	cxusb_vprintk(dvbdev, OPS, "should start streaming\n");
> +
> +	/* already streaming */
> +	if (cxdev->streaming)
> +		return 0;

Unnecessary check, this can't happen.

> +
> +	if (cxusb_medion_stream_busy(cxdev)) {
> +		ret = -EBUSY;
> +		goto ret_retbufs;
> +	}
> +
> +	ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 1);
> +	if (ret != 0) {
> +		dev_err(&dvbdev->udev->dev,
> +			"unable to start stream (%d)\n", ret);
> +		goto ret_retbufs;
> +	}
> +
> +	ret = cxusb_ctrl_msg(dvbdev, CMD_STREAMING_ON, streamon_params, 2,
> +			     NULL, 0);
> +	if (ret != 0) {
> +		dev_err(&dvbdev->udev->dev,
> +			"unable to start streaming (%d)\n", ret);
> +		goto ret_unstream_cx;
> +	}
> +
> +	if (cxdev->raw_mode)
> +		npackets = CXUSB_VIDEO_MAX_FRAME_PKTS;
> +	else {
> +		u8 *buf;
> +		unsigned int urblen, auxbuflen;
> +
> +		/* has to be less than full frame size */
> +		urblen = (cxdev->width * 2 + 4 + 4) * cxdev->height;
> +		npackets = urblen / CXUSB_VIDEO_PKT_SIZE;
> +		urblen = npackets * CXUSB_VIDEO_PKT_SIZE;
> +
> +		auxbuflen = (cxdev->width * 2 + 4 + 4) *
> +			(cxdev->height + 50 /* VBI lines */) + urblen;
> +
> +		buf = vmalloc(auxbuflen);
> +		if (buf == NULL) {
> +			ret = -ENOMEM;
> +			goto ret_unstream_md;
> +		}
> +
> +		cxusb_auxbuf_init(&cxdev->auxbuf, buf, auxbuflen);
> +	}
> +
> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++) {
> +		int framen;
> +		u8 *streambuf;
> +		struct urb *surb;
> +
> +		streambuf = kmalloc(npackets * CXUSB_VIDEO_PKT_SIZE,
> +				    GFP_KERNEL);
> +		if (streambuf == NULL) {
> +			if (i == 0) {
> +				ret = -ENOMEM;
> +				goto ret_freeab;
> +			} else
> +				break;
> +		}
> +
> +		surb = usb_alloc_urb(npackets, GFP_KERNEL);
> +		if (surb == NULL) {
> +			kfree(streambuf);
> +			ret = -ENOMEM;
> +			goto ret_freeu;
> +		}
> +
> +		cxdev->streamurbs[i] = surb;
> +		surb->dev = dvbdev->udev;
> +		surb->context = dvbdev;
> +		surb->pipe = usb_rcvisocpipe(dvbdev->udev, 2);
> +
> +		surb->interval = 1;
> +		surb->transfer_flags = URB_ISO_ASAP;
> +
> +		surb->transfer_buffer = streambuf;
> +
> +		surb->complete = cxusb_medion_v_complete;
> +		surb->number_of_packets = npackets;
> +		surb->transfer_buffer_length = npackets * CXUSB_VIDEO_PKT_SIZE;
> +
> +		for (framen = 0; framen < npackets; framen++) {
> +			surb->iso_frame_desc[framen].offset =
> +				CXUSB_VIDEO_PKT_SIZE * framen;
> +
> +			surb->iso_frame_desc[framen].length =
> +				CXUSB_VIDEO_PKT_SIZE;
> +		}
> +	}
> +
> +	cxdev->urbcomplete = 0;
> +	cxdev->nexturb = 0;
> +	cxdev->vbuf = NULL;
> +	cxdev->bt656.mode = NEW_FRAME;
> +	cxdev->bt656.buf = NULL;
> +
> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
> +		if (cxdev->streamurbs[i] != NULL) {
> +			ret = usb_submit_urb(cxdev->streamurbs[i],
> +					GFP_KERNEL);
> +			if (ret != 0)
> +				dev_err(&dvbdev->udev->dev,
> +					"URB %d submission failed (%d)\n", i,
> +					ret);
> +		}
> +
> +	cxdev->streaming = true;

No need to keep track of the streaming state. You can use vb2_start_streaming_called()
instead since vb2 already keeps track of this.

> +
> +	return 0;
> +
> +ret_freeu:
> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
> +		if (cxdev->streamurbs[i] != NULL) {
> +			kfree(cxdev->streamurbs[i]->transfer_buffer);
> +			usb_free_urb(cxdev->streamurbs[i]);
> +			cxdev->streamurbs[i] = NULL;
> +		}
> +
> +ret_freeab:
> +	if (!cxdev->raw_mode)
> +		vfree(cxdev->auxbuf.buf);
> +
> +ret_unstream_md:
> +	cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
> +
> +ret_unstream_cx:
> +	v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
> +
> +ret_retbufs:
> +	cxusb_medion_return_buffers(cxdev, true);
> +
> +	return ret;
> +}
> +
> +static void cxusb_medion_v_stop_streaming(struct vb2_queue *q)
> +{
> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	int i, ret;
> +
> +	cxusb_vprintk(dvbdev, OPS, "should stop streaming\n");
> +
> +	if (!cxdev->streaming)
> +		return;
> +
> +	cxdev->streaming = false;
> +
> +	cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
> +
> +	ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
> +	if (ret != 0)
> +		dev_err(&dvbdev->udev->dev, "unable to stop stream (%d)\n",
> +			ret);
> +
> +	/* let URB completion run */
> +	mutex_unlock(cxdev->videodev->lock);
> +
> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
> +		if (cxdev->streamurbs[i] != NULL)
> +			usb_kill_urb(cxdev->streamurbs[i]);
> +
> +	flush_work(&cxdev->urbwork);
> +
> +	mutex_lock(cxdev->videodev->lock);
> +
> +	/* free transfer buffer and URB */
> +	if (!cxdev->raw_mode)
> +		vfree(cxdev->auxbuf.buf);
> +
> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
> +		if (cxdev->streamurbs[i] != NULL) {
> +			kfree(cxdev->streamurbs[i]->transfer_buffer);
> +			usb_free_urb(cxdev->streamurbs[i]);
> +			cxdev->streamurbs[i] = NULL;
> +		}
> +
> +	cxusb_medion_return_buffers(cxdev, false);
> +}
> +
> +static void cxusub_medion_v_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct cxusb_medion_vbuffer *vbuf =
> +		container_of(vb, struct cxusb_medion_vbuffer, vb2);
> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(vb->vb2_queue);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +
> +	/* cxusb_vprintk(dvbdev, OPS, "mmmm.. fresh buffer...\n"); */
> +
> +	list_add_tail(&vbuf->list, &cxdev->buflist);

I would expect a spinlock here to protect the list.

> +}
> +
> +static void cxusub_medion_v_wait_prepare(struct vb2_queue *q)
> +{
> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +
> +	mutex_unlock(cxdev->videodev->lock);
> +}
> +
> +static void cxusub_medion_v_wait_finish(struct vb2_queue *q)
> +{
> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +
> +	mutex_lock(cxdev->videodev->lock);
> +}
> +
> +static const struct vb2_ops cxdev_video_qops = {
> +	.queue_setup = cxusb_medion_v_queue_setup,
> +	.buf_init = cxusb_medion_v_buf_init,
> +	.start_streaming = cxusb_medion_v_start_streaming,
> +	.stop_streaming = cxusb_medion_v_stop_streaming,
> +	.buf_queue = cxusub_medion_v_buf_queue,
> +	.wait_prepare = cxusub_medion_v_wait_prepare,
> +	.wait_finish = cxusub_medion_v_wait_finish

Use vb2_ops_wait_prepare/finish helpers instead.

Set the lock field in struct vb2_queue to the mutex that these helpers
require (cxdev->videodev->lock).

> +};
> +
> +static int cxusb_medion_v_querycap(struct file *file, void *fh,
> +				   struct v4l2_capability *cap)
> +{
> +	const __u32 videocaps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER |
> +		V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
> +	const __u32 radiocaps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct video_device *vdev = video_devdata(file);
> +
> +	strncpy(cap->driver, dvbdev->udev->dev.driver->name,
> +		sizeof(cap->driver) - 1);
> +	strcpy(cap->card, "Medion 95700");
> +	usb_make_path(dvbdev->udev, cap->bus_info, sizeof(cap->bus_info));
> +
> +	if (vdev->vfl_type == VFL_TYPE_GRABBER)
> +		cap->device_caps = videocaps;
> +	else
> +		cap->device_caps = radiocaps;

Set the device_caps in struct video_device during probe.

> +
> +	cap->capabilities = videocaps | radiocaps | V4L2_CAP_DEVICE_CAPS;
> +
> +	memset(cap->reserved, 0, sizeof(cap->reserved));

Drop the memset, this is done for you by the v4l2 core.

> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_v_enum_fmt_vid_cap(struct file *file, void *fh,
> +					   struct v4l2_fmtdesc *f)
> +{
> +	if (f->index != 0)
> +		return -EINVAL;
> +
> +	f->flags = 0;
> +	strcpy(f->description, "YUV 4:2:2");

Don't set the name, the core will fill this in.

> +	f->pixelformat = V4L2_PIX_FMT_UYVY;
> +	memset(f->reserved, 0, sizeof(f->reserved));

Drop the memset.

> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_g_fmt_vid_cap(struct file *file, void *fh,
> +				      struct v4l2_format *f)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +
> +	f->fmt.pix.width = cxdev->width;
> +	f->fmt.pix.height = cxdev->height;
> +	f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
> +	f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
> +	f->fmt.pix.bytesperline = cxdev->raw_mode ? 0 : cxdev->width * 2;

Dunno what raw_mode is, but it looks suspicous.

> +	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
> +	f->fmt.pix.sizeimage =
> +		cxdev->raw_mode ? CXUSB_VIDEO_MAX_FRAME_SIZE :
> +		f->fmt.pix.bytesperline * f->fmt.pix.height;
> +	f->fmt.pix.priv = 0;
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_g_parm(struct file *file, void *fh,
> +			       struct v4l2_streamparm *param)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +
> +	if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> +		return -EINVAL;
> +
> +	memset(&param->parm.capture, 0, sizeof(param->parm.capture));

No need, it's done for you.

> +
> +	if (cxdev->raw_mode)
> +		param->parm.capture.extendedmode |=
> +			CXUSB_EXTENDEDMODE_CAPTURE_RAW;
> +
> +	param->parm.capture.readbuffers = 1;
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_s_parm(struct file *file, void *fh,
> +			       struct v4l2_streamparm *param)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	bool want_raw;
> +
> +	if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> +		return -EINVAL;
> +
> +	want_raw = param->parm.capture.extendedmode &
> +		CXUSB_EXTENDEDMODE_CAPTURE_RAW;
> +
> +	if (want_raw != cxdev->raw_mode) {
> +		if (cxusb_medion_stream_busy(cxdev) ||
> +		    vb2_is_busy(&cxdev->videoqueue))
> +			return -EBUSY;
> +
> +		cxdev->raw_mode = want_raw;
> +	}
> +
> +	param->parm.capture.readbuffers = 1;
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_try_s_fmt_vid_cap(struct file *file,
> +					  struct v4l2_format *f,
> +					  bool isset)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	struct v4l2_subdev_format subfmt;
> +	int ret;
> +
> +	if (isset && (cxusb_medion_stream_busy(cxdev) ||
> +		      vb2_is_busy(&cxdev->videoqueue)))
> +		return -EBUSY;
> +
> +	memset(&subfmt, 0, sizeof(subfmt));
> +	subfmt.which = isset ? V4L2_SUBDEV_FORMAT_ACTIVE :
> +		V4L2_SUBDEV_FORMAT_TRY;
> +	subfmt.format.width = f->fmt.pix.width & ~1;
> +	subfmt.format.height = f->fmt.pix.height & ~1;
> +	subfmt.format.code = MEDIA_BUS_FMT_FIXED;
> +	subfmt.format.field = V4L2_FIELD_SEQ_TB;
> +	subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M;
> +
> +	ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt);
> +	if (ret != 0) {
> +		if (ret != -ERANGE)
> +			return ret;
> +
> +		/* try some common formats */
> +		subfmt.format.width = 720;
> +		subfmt.format.height = 576;
> +		ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL,
> +				       &subfmt);
> +		if (ret != 0) {
> +			if (ret != -ERANGE)
> +				return ret;
> +
> +			subfmt.format.width = 640;
> +			subfmt.format.height = 480;
> +			ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt,
> +					       NULL, &subfmt);
> +			if (ret != 0)
> +				return ret;
> +		}
> +	}
> +
> +	f->fmt.pix.width = subfmt.format.width;
> +	f->fmt.pix.height = subfmt.format.height;
> +	f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
> +	f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
> +	f->fmt.pix.bytesperline = cxdev->raw_mode ? 0 : f->fmt.pix.width * 2;
> +	f->fmt.pix.sizeimage =
> +		cxdev->raw_mode ? CXUSB_VIDEO_MAX_FRAME_SIZE :
> +		f->fmt.pix.bytesperline * f->fmt.pix.height;
> +	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
> +	f->fmt.pix.priv = 0;
> +
> +	if (isset) {
> +		cxdev->width = f->fmt.pix.width;
> +		cxdev->height = f->fmt.pix.height;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_try_fmt_vid_cap(struct file *file, void *fh,
> +					struct v4l2_format *f)
> +{
> +	return cxusb_medion_try_s_fmt_vid_cap(file, f, false);
> +}
> +
> +static int cxusb_medion_s_fmt_vid_cap(struct file *file, void *fh,
> +				      struct v4l2_format *f)
> +{
> +	return cxusb_medion_try_s_fmt_vid_cap(file, f, true);
> +}
> +
> +static const struct {
> +	struct v4l2_input input;
> +	u32 inputcfg;
> +} cxusb_medion_inputs[] = {
> +	{ .input = { .name = "TV tuner", .type = V4L2_INPUT_TYPE_TUNER,
> +		     .tuner = 0, .std = V4L2_STD_PAL },
> +	  .inputcfg = CX25840_COMPOSITE2, },
> +
> +	{  .input = { .name = "Composite", .type = V4L2_INPUT_TYPE_CAMERA,
> +		     .std = V4L2_STD_ALL },
> +	   .inputcfg = CX25840_COMPOSITE1, },
> +
> +	{  .input = { .name = "S-Video", .type = V4L2_INPUT_TYPE_CAMERA,
> +		      .std = V4L2_STD_ALL },
> +	   .inputcfg = CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }
> +};
> +
> +#define CXUSB_INPUT_CNT ARRAY_SIZE(cxusb_medion_inputs)
> +
> +static int cxusb_medion_enum_input(struct file *file, void *fh,
> +				   struct v4l2_input *inp)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	u32 index = inp->index;
> +
> +	if (index >= CXUSB_INPUT_CNT)
> +		return -EINVAL;
> +
> +	*inp = cxusb_medion_inputs[index].input;
> +	inp->index = index;
> +	inp->capabilities |= V4L2_IN_CAP_STD;
> +
> +	if (index == cxdev->input) {
> +		int ret;
> +		u32 status = 0;
> +
> +		ret = v4l2_subdev_call(cxdev->cx25840, video, g_input_status,
> +				       &status);
> +		if (ret != 0)
> +			dev_warn(&dvbdev->udev->dev,
> +				 "cx25840 input status query failed (%d)\n",
> +				 ret);
> +		else
> +			inp->status = status;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_g_input(struct file *file, void *fh,
> +				unsigned int *i)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +
> +	*i = cxdev->input;
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_s_input(struct file *file, void *fh,
> +				unsigned int i)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	int ret;
> +
> +	if (i >= CXUSB_INPUT_CNT)
> +		return -EINVAL;
> +
> +	ret = v4l2_subdev_call(cxdev->cx25840, video, s_routing,
> +			       cxusb_medion_inputs[i].inputcfg, 0, 0);
> +	if (ret != 0)
> +		return ret;
> +
> +	cxdev->input = i;
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_g_tuner(struct file *file, void *fh,
> +				struct v4l2_tuner *tuner)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	struct video_device *vdev = video_devdata(file);
> +	int ret;
> +
> +	if (tuner->index != 0)
> +		return -EINVAL;
> +
> +	if (vdev->vfl_type == VFL_TYPE_GRABBER)
> +		tuner->type = V4L2_TUNER_ANALOG_TV;
> +	else
> +		tuner->type = V4L2_TUNER_RADIO;
> +
> +	tuner->capability = 0;
> +	tuner->afc = 0;
> +
> +	/*
> +	 * fills:
> +	 * always: capability (static), rangelow (static), rangehigh (static)
> +	 * radio mode: afc (may fail silently), rxsubchans (static), audmode
> +	 */
> +	ret = v4l2_subdev_call(cxdev->tda9887, tuner, g_tuner, tuner);
> +	if (ret != 0)
> +		return ret;
> +
> +	/*
> +	 * fills:
> +	 * always: capability (static), rangelow (static), rangehigh (static)
> +	 * radio mode: rxsubchans (always stereo), audmode,
> +	 * signal (might be wrong)
> +	 */
> +	ret = v4l2_subdev_call(cxdev->tuner, tuner, g_tuner, tuner);
> +	if (ret != 0)
> +		return ret;
> +
> +	tuner->signal = 0;
> +
> +	/*
> +	 * fills: TV mode: capability, rxsubchans, audmode, signal
> +	 */
> +	ret = v4l2_subdev_call(cxdev->cx25840, tuner, g_tuner, tuner);
> +	if (ret != 0)
> +		return ret;
> +
> +	if (vdev->vfl_type == VFL_TYPE_GRABBER)
> +		strcpy(tuner->name, "TV tuner");
> +	else
> +		strcpy(tuner->name, "Radio tuner");
> +
> +	memset(tuner->reserved, 0, sizeof(tuner->reserved));
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_s_tuner(struct file *file, void *fh,
> +				const struct v4l2_tuner *tuner)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	struct video_device *vdev = video_devdata(file);
> +	int ret;
> +
> +	if (tuner->index != 0)
> +		return -EINVAL;
> +
> +	ret = v4l2_subdev_call(cxdev->tda9887, tuner, s_tuner, tuner);
> +	if (ret != 0)
> +		return ret;
> +
> +	ret = v4l2_subdev_call(cxdev->tuner, tuner, s_tuner, tuner);
> +	if (ret != 0)
> +		return ret;
> +
> +	/*
> +	 * make sure that cx25840 is in a correct TV / radio mode,
> +	 * since calls above may have changed it for tuner / IF demod
> +	 */
> +	if (vdev->vfl_type == VFL_TYPE_GRABBER)
> +		v4l2_subdev_call(cxdev->cx25840, video, s_std, cxdev->norm);
> +	else
> +		v4l2_subdev_call(cxdev->cx25840, tuner, s_radio);
> +
> +	return v4l2_subdev_call(cxdev->cx25840, tuner, s_tuner, tuner);
> +}
> +
> +static int cxusb_medion_g_frequency(struct file *file, void *fh,
> +				    struct v4l2_frequency *freq)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +
> +	if (freq->tuner != 0)
> +		return -EINVAL;
> +
> +	return v4l2_subdev_call(cxdev->tuner, tuner, g_frequency, freq);
> +}
> +
> +static int cxusb_medion_s_frequency(struct file *file, void *fh,
> +				    const struct v4l2_frequency *freq)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	struct video_device *vdev = video_devdata(file);
> +	int ret;
> +
> +	if (freq->tuner != 0)
> +		return -EINVAL;
> +
> +	ret = v4l2_subdev_call(cxdev->tda9887, tuner, s_frequency, freq);
> +	if (ret != 0)
> +		return ret;
> +
> +	ret = v4l2_subdev_call(cxdev->tuner, tuner, s_frequency, freq);
> +	if (ret != 0)
> +		return ret;
> +
> +	/*
> +	 * make sure that cx25840 is in a correct TV / radio mode,
> +	 * since calls above may have changed it for tuner / IF demod
> +	 */
> +	if (vdev->vfl_type == VFL_TYPE_GRABBER)
> +		v4l2_subdev_call(cxdev->cx25840, video, s_std, cxdev->norm);
> +	else
> +		v4l2_subdev_call(cxdev->cx25840, tuner, s_radio);
> +
> +	return v4l2_subdev_call(cxdev->cx25840, tuner, s_frequency, freq);
> +}
> +
> +static int cxusb_medion_g_std(struct file *file, void *fh,
> +			      v4l2_std_id *norm)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	int ret;
> +
> +	ret = v4l2_subdev_call(cxdev->cx25840, video, g_std, norm);
> +	if (ret != 0) {
> +		cxusb_vprintk(dvbdev, OPS, "cannot get standard for input %u\n",
> +			      (unsigned int)cxdev->input);
> +
> +		return ret;
> +	}
> +
> +	cxusb_vprintk(dvbdev, OPS,
> +		      "current standard for input %u is %lx\n",
> +		      (unsigned int)cxdev->input,
> +		      (unsigned long)*norm);
> +
> +	if (cxdev->input == 0)
> +		/*
> +		 * make sure we don't have improper std bits set
> +		 * for TV tuner (could happen when no signal was
> +		 * present yet after reset)
> +		 */
> +		*norm &= V4L2_STD_PAL;
> +
> +	if (*norm == V4L2_STD_UNKNOWN)
> +		return -ENODATA;
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_s_std(struct file *file, void *fh,
> +			      v4l2_std_id norm)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	int ret;
> +
> +	cxusb_vprintk(dvbdev, OPS,
> +		      "trying to set standard for input %u to %lx\n",
> +		      (unsigned int)cxdev->input,
> +		      (unsigned long)norm);
> +
> +	/* on composite or S-Video any std is acceptable */
> +	if (cxdev->input != 0) {
> +		ret = v4l2_subdev_call(cxdev->cx25840, video, s_std, norm);
> +		if (ret)
> +			return ret;
> +
> +		goto ret_savenorm;
> +	}
> +
> +	/* TV tuner is only able to demodulate PAL */
> +	if ((norm & ~V4L2_STD_PAL) != 0)
> +		return -EINVAL;
> +
> +	/* no autodetection support */
> +	if (norm == 0)
> +		return -EINVAL;
> +
> +	ret = v4l2_subdev_call(cxdev->tda9887, video, s_std, norm);
> +	if (ret != 0) {
> +		dev_err(&dvbdev->udev->dev,
> +			"tda9887 norm setup failed (%d)\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	ret = v4l2_subdev_call(cxdev->tuner, video, s_std, norm);
> +	if (ret != 0) {
> +		dev_err(&dvbdev->udev->dev,
> +			"tuner norm setup failed (%d)\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	ret = v4l2_subdev_call(cxdev->cx25840, video, s_std, norm);
> +	if (ret != 0) {
> +		dev_err(&dvbdev->udev->dev,
> +			"cx25840 norm setup failed (%d)\n",
> +			ret);
> +		return ret;
> +	}
> +
> +ret_savenorm:
> +	cxdev->norm = norm;
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_log_status(struct file *file, void *fh)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +
> +	v4l2_device_call_all(&cxdev->v4l2dev, 0, core, log_status);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops cxusb_video_ioctl = {
> +	.vidioc_querycap = cxusb_medion_v_querycap,
> +	.vidioc_enum_fmt_vid_cap = cxusb_medion_v_enum_fmt_vid_cap,
> +	.vidioc_g_fmt_vid_cap = cxusb_medion_g_fmt_vid_cap,
> +	.vidioc_s_fmt_vid_cap = cxusb_medion_s_fmt_vid_cap,
> +	.vidioc_try_fmt_vid_cap = cxusb_medion_try_fmt_vid_cap,
> +	.vidioc_enum_input = cxusb_medion_enum_input,
> +	.vidioc_g_input = cxusb_medion_g_input,
> +	.vidioc_s_input = cxusb_medion_s_input,
> +	.vidioc_g_parm = cxusb_medion_g_parm,
> +	.vidioc_s_parm = cxusb_medion_s_parm,
> +	.vidioc_g_tuner = cxusb_medion_g_tuner,
> +	.vidioc_s_tuner = cxusb_medion_s_tuner,
> +	.vidioc_g_frequency = cxusb_medion_g_frequency,
> +	.vidioc_s_frequency = cxusb_medion_s_frequency,
> +	.vidioc_g_std = cxusb_medion_g_std,
> +	.vidioc_s_std = cxusb_medion_s_std,
> +	.vidioc_log_status = cxusb_medion_log_status,
> +	.vidioc_reqbufs = vb2_ioctl_reqbufs,
> +	.vidioc_querybuf = vb2_ioctl_querybuf,
> +	.vidioc_qbuf = vb2_ioctl_qbuf,
> +	.vidioc_dqbuf = vb2_ioctl_dqbuf,
> +	.vidioc_create_bufs = vb2_ioctl_create_bufs,
> +	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> +	.vidioc_streamon = vb2_ioctl_streamon,
> +	.vidioc_streamoff = vb2_ioctl_streamoff
> +};
> +
> +static const struct v4l2_ioctl_ops cxusb_radio_ioctl = {
> +	.vidioc_querycap = cxusb_medion_v_querycap,
> +	.vidioc_g_tuner = cxusb_medion_g_tuner,
> +	.vidioc_s_tuner = cxusb_medion_s_tuner,
> +	.vidioc_g_frequency = cxusb_medion_g_frequency,
> +	.vidioc_s_frequency = cxusb_medion_s_frequency,
> +	.vidioc_log_status = cxusb_medion_log_status
> +};
> +
> +/*
> + * in principle, this should be const, but s_io_pin_config is declared
> + * to take non-const, and gcc complains
> + */
> +static struct v4l2_subdev_io_pin_config cxusub_medion_pin_config[] = {
> +	{ .pin = CX25840_PIN_DVALID_PRGM0, .function = CX25840_PAD_DEFAULT,
> +	  .strength = CX25840_PIN_DRIVE_MEDIUM },
> +	{ .pin = CX25840_PIN_PLL_CLK_PRGM7, .function = CX25840_PAD_AUX_PLL },
> +	{ .pin = CX25840_PIN_HRESET_PRGM2, .function = CX25840_PAD_ACTIVE,
> +	  .strength = CX25840_PIN_DRIVE_MEDIUM }
> +};
> +
> +int cxusb_medion_analog_init(struct dvb_usb_device *dvbdev)
> +{
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	u8 tuner_analog_msg_data[] = { 0x9c, 0x60, 0x85, 0x54 };
> +	struct i2c_msg tuner_analog_msg = { .addr = 0x61, .flags = 0,
> +					    .buf = tuner_analog_msg_data,
> +					    .len =
> +					    sizeof(tuner_analog_msg_data) };
> +	struct v4l2_subdev_format subfmt;
> +	int ret;
> +
> +	/* switch tuner to analog mode so IF demod will become accessible */
> +	ret = i2c_transfer(&dvbdev->i2c_adap, &tuner_analog_msg, 1);
> +	if (ret != 1)
> +		dev_warn(&dvbdev->udev->dev,
> +			 "tuner analog switch failed (%d)\n", ret);
> +
> +	ret = v4l2_subdev_call(cxdev->cx25840, core, load_fw);
> +	if (ret != 0)
> +		dev_warn(&dvbdev->udev->dev,
> +			 "cx25840 fw load failed (%d)\n", ret);
> +
> +	ret = v4l2_subdev_call(cxdev->cx25840, video, s_routing,
> +			       CX25840_COMPOSITE1, 0,
> +			       CX25840_VCONFIG_FMT_BT656 |
> +			       CX25840_VCONFIG_RES_8BIT |
> +			       CX25840_VCONFIG_VBIRAW_DISABLED |
> +			       CX25840_VCONFIG_ANCDATA_DISABLED |
> +			       CX25840_VCONFIG_ACTIVE_COMPOSITE |
> +			       CX25840_VCONFIG_VALID_ANDACTIVE |
> +			       CX25840_VCONFIG_HRESETW_NORMAL |
> +			       CX25840_VCONFIG_CLKGATE_NONE |
> +			       CX25840_VCONFIG_DCMODE_DWORDS);
> +	if (ret != 0)
> +		dev_warn(&dvbdev->udev->dev,
> +			 "cx25840 mode set failed (%d)\n", ret);
> +
> +	/* composite */
> +	cxdev->input = 1;
> +	cxdev->norm = 0;
> +
> +	/* TODO: setup audio samples insertion */
> +
> +	ret = v4l2_subdev_call(cxdev->cx25840, core, s_io_pin_config,
> +			       sizeof(cxusub_medion_pin_config) /
> +			       sizeof(cxusub_medion_pin_config[0]),
> +			       cxusub_medion_pin_config);
> +	if (ret != 0)
> +		dev_warn(&dvbdev->udev->dev,
> +			"cx25840 pin config failed (%d)\n", ret);
> +
> +	/* make sure that we aren't in radio mode */
> +	v4l2_subdev_call(cxdev->tda9887, video, s_std, V4L2_STD_PAL);
> +	v4l2_subdev_call(cxdev->tuner, video, s_std, V4L2_STD_PAL);
> +	v4l2_subdev_call(cxdev->cx25840, video, s_std, cxdev->norm);
> +
> +	memset(&subfmt, 0, sizeof(subfmt));
> +	subfmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> +	subfmt.format.width = cxdev->width;
> +	subfmt.format.height = cxdev->height;
> +	subfmt.format.code = MEDIA_BUS_FMT_FIXED;
> +	subfmt.format.field = V4L2_FIELD_SEQ_TB;
> +	subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M;
> +
> +	ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt);
> +	if (ret != 0) {
> +		subfmt.format.width = 640;
> +		subfmt.format.height = 480;
> +		ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL,
> +				       &subfmt);
> +		if (ret != 0)
> +			dev_warn(&dvbdev->udev->dev,
> +				 "cx25840 format set failed (%d)\n", ret);
> +	}
> +
> +	if (ret == 0) {
> +		cxdev->width = subfmt.format.width;
> +		cxdev->height = subfmt.format.height;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cxusb_videoradio_open(struct file *f)
> +{
> +	struct dvb_usb_device *dvbdev = video_drvdata(f);
> +	int ret;
> +
> +	/*
> +	 * no locking needed since this call only modifies analog
> +	 * state if there are no other analog handles currenly
> +	 * opened so ops done via them cannot create a conflict
> +	 */
> +	ret = cxusb_medion_get(dvbdev, CXUSB_OPEN_ANALOG);
> +	if (ret != 0)
> +		return ret;
> +
> +	ret = v4l2_fh_open(f);
> +	if (ret != 0)
> +		goto ret_release;
> +
> +	cxusb_vprintk(dvbdev, OPS, "got open\n");
> +
> +	return 0;
> +
> +ret_release:
> +	cxusb_medion_put(dvbdev);
> +
> +	return ret;
> +}
> +
> +static int cxusb_videoradio_release(struct file *f)
> +{
> +	struct video_device *vdev = video_devdata(f);
> +	struct dvb_usb_device *dvbdev = video_drvdata(f);
> +	int ret;
> +
> +	cxusb_vprintk(dvbdev, OPS, "got release\n");
> +
> +	if (vdev->vfl_type == VFL_TYPE_GRABBER)
> +		ret = vb2_fop_release(f);
> +	else
> +		ret = v4l2_fh_release(f);
> +
> +	cxusb_medion_put(dvbdev);
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_file_operations cxusb_video_fops = {
> +	.owner = THIS_MODULE,
> +	.read = vb2_fop_read,
> +	.poll = vb2_fop_poll,
> +	.unlocked_ioctl = video_ioctl2,
> +	.mmap = vb2_fop_mmap,
> +	.open = cxusb_videoradio_open,
> +	.release = cxusb_videoradio_release
> +};
> +
> +static const struct v4l2_file_operations cxusb_radio_fops = {
> +	.owner = THIS_MODULE,
> +	.unlocked_ioctl = video_ioctl2,
> +	.open = cxusb_videoradio_open,
> +	.release = cxusb_videoradio_release
> +};
> +
> +static void cxusb_medion_v4l2_release(struct v4l2_device *v4l2_dev)
> +{
> +	struct cxusb_medion_dev *cxdev =
> +		container_of(v4l2_dev, struct cxusb_medion_dev, v4l2dev);
> +	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
> +
> +	cxusb_vprintk(dvbdev, OPS, "v4l2 device release\n");
> +
> +	v4l2_device_unregister(&cxdev->v4l2dev);
> +
> +	mutex_destroy(&cxdev->dev_lock);
> +
> +	while (completion_done(&cxdev->v4l2_release))
> +		schedule();
> +
> +	complete(&cxdev->v4l2_release);
> +}
> +
> +static void cxusb_medion_videodev_release(struct video_device *vdev)
> +{
> +	struct dvb_usb_device *dvbdev = video_get_drvdata(vdev);
> +
> +	cxusb_vprintk(dvbdev, OPS, "video device release\n");
> +
> +	vb2_queue_release(vdev->queue);
> +
> +	video_device_release(vdev);
> +}
> +
> +static int cxusb_medion_register_analog_video(struct dvb_usb_device *dvbdev)
> +{
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	int ret;
> +
> +	cxdev->videoqueue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +	cxdev->videoqueue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
> +	cxdev->videoqueue.ops = &cxdev_video_qops;
> +	cxdev->videoqueue.mem_ops = &vb2_vmalloc_memops;
> +	cxdev->videoqueue.drv_priv = dvbdev;
> +	cxdev->videoqueue.buf_struct_size =
> +		sizeof(struct cxusb_medion_vbuffer);
> +	cxdev->videoqueue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +
> +	ret = vb2_queue_init(&cxdev->videoqueue);
> +	if (ret) {
> +		dev_err(&dvbdev->udev->dev,
> +			"video queue init failed, ret = %d\n", ret);
> +		return ret;
> +	}
> +
> +	cxdev->videodev = video_device_alloc();
> +	if (cxdev->videodev == NULL) {
> +		dev_err(&dvbdev->udev->dev, "video device alloc failed\n");
> +		ret = -ENOMEM;
> +		goto ret_qrelease;
> +	}
> +
> +	cxdev->videodev->fops = &cxusb_video_fops;
> +	cxdev->videodev->v4l2_dev = &cxdev->v4l2dev;
> +	cxdev->videodev->queue = &cxdev->videoqueue;
> +	strcpy(cxdev->videodev->name, "cxusb");
> +	cxdev->videodev->vfl_dir = VFL_DIR_RX;
> +	cxdev->videodev->ioctl_ops = &cxusb_video_ioctl;
> +	cxdev->videodev->tvnorms = V4L2_STD_ALL;
> +	cxdev->videodev->release = cxusb_medion_videodev_release;
> +	cxdev->videodev->lock = &cxdev->dev_lock;
> +	video_set_drvdata(cxdev->videodev, dvbdev);
> +
> +	ret = video_register_device(cxdev->videodev, VFL_TYPE_GRABBER, -1);
> +	if (ret) {
> +		dev_err(&dvbdev->udev->dev,
> +			"video device register failed, ret = %d\n", ret);
> +		goto ret_vrelease;
> +	}
> +
> +	return 0;
> +
> +ret_vrelease:
> +	video_device_release(cxdev->videodev);
> +
> +ret_qrelease:
> +	vb2_queue_release(&cxdev->videoqueue);
> +
> +	return ret;
> +}
> +
> +static int cxusb_medion_register_analog_radio(struct dvb_usb_device *dvbdev)
> +{
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	int ret;
> +
> +	cxdev->radiodev = video_device_alloc();
> +	if (cxdev->radiodev == NULL) {
> +		dev_err(&dvbdev->udev->dev, "radio device alloc failed\n");
> +		return -ENOMEM;
> +	}
> +
> +	cxdev->radiodev->fops = &cxusb_radio_fops;
> +	cxdev->radiodev->v4l2_dev = &cxdev->v4l2dev;
> +	strcpy(cxdev->radiodev->name, "cxusb");
> +	cxdev->radiodev->vfl_dir = VFL_DIR_RX;
> +	cxdev->radiodev->ioctl_ops = &cxusb_radio_ioctl;
> +	cxdev->radiodev->release = video_device_release;
> +	cxdev->radiodev->lock = &cxdev->dev_lock;
> +	video_set_drvdata(cxdev->radiodev, dvbdev);
> +
> +	ret = video_register_device(cxdev->radiodev, VFL_TYPE_RADIO, -1);
> +	if (ret) {
> +		dev_err(&dvbdev->udev->dev,
> +			"radio device register failed, ret = %d\n", ret);
> +		video_device_release(cxdev->radiodev);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cxusb_medion_register_analog_subdevs(struct dvb_usb_device *dvbdev)
> +{
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	struct tuner_setup tun_setup;
> +	struct i2c_board_info cx25840_board;
> +	struct cx25840_platform_data cx25840_platform;
> +
> +	/* attach capture chip */
> +	memset(&cx25840_platform, 0, sizeof(cx25840_platform));
> +	cx25840_platform.generic_mode = 1;
> +
> +	memset(&cx25840_board, 0, sizeof(cx25840_board));
> +	strcpy(cx25840_board.type, "cx25840");
> +	cx25840_board.addr = 0x44;
> +	cx25840_board.platform_data = &cx25840_platform;
> +
> +	cxdev->cx25840 = v4l2_i2c_new_subdev_board(&cxdev->v4l2dev,
> +						   &dvbdev->i2c_adap,
> +						   &cx25840_board, NULL);
> +	if (cxdev->cx25840 == NULL) {
> +		dev_err(&dvbdev->udev->dev, "cx25840 not found\n");
> +		return -ENODEV;
> +	}
> +
> +	/* attach analog tuner */
> +	cxdev->tuner = v4l2_i2c_new_subdev(&cxdev->v4l2dev,
> +					   &dvbdev->i2c_adap,
> +					   "tuner", 0x61, NULL);
> +	if (cxdev->tuner == NULL) {
> +		dev_err(&dvbdev->udev->dev, "tuner not found\n");
> +		return -ENODEV;
> +	}
> +
> +	/* configure it */
> +	memset(&tun_setup, 0, sizeof(tun_setup));
> +	tun_setup.addr = 0x61;
> +	tun_setup.type = TUNER_PHILIPS_FMD1216ME_MK3;
> +	tun_setup.mode_mask = T_RADIO | T_ANALOG_TV;
> +	v4l2_subdev_call(cxdev->tuner, tuner, s_type_addr, &tun_setup);
> +
> +	/* attach IF demod */
> +	cxdev->tda9887 = v4l2_i2c_new_subdev(&cxdev->v4l2dev,
> +					     &dvbdev->i2c_adap,
> +					     "tuner", 0x43, NULL);
> +	if (cxdev->tda9887 == NULL) {
> +		dev_err(&dvbdev->udev->dev, "tda9887 not found\n");
> +		return -ENODEV;
> +	}
> +
> +	return 0;
> +}
> +
> +int cxusb_medion_register_analog(struct dvb_usb_device *dvbdev)
> +{
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +	int ret;
> +
> +	mutex_init(&cxdev->dev_lock);
> +
> +	init_completion(&cxdev->v4l2_release);
> +
> +	cxdev->v4l2dev.release = cxusb_medion_v4l2_release;
> +
> +	ret = v4l2_device_register(&dvbdev->udev->dev, &cxdev->v4l2dev);
> +	if (ret != 0) {
> +		dev_err(&dvbdev->udev->dev,
> +			"V4L2 device registration failed, ret = %d\n", ret);
> +		mutex_destroy(&cxdev->dev_lock);
> +		return ret;
> +	}
> +
> +	ret = cxusb_medion_register_analog_subdevs(dvbdev);
> +	if (ret)
> +		goto ret_unregister;
> +
> +	INIT_WORK(&cxdev->urbwork, cxusb_medion_v_complete_work);
> +	INIT_LIST_HEAD(&cxdev->buflist);
> +
> +	cxdev->width = 320;
> +	cxdev->height = 240;
> +
> +	ret = cxusb_medion_register_analog_video(dvbdev);
> +	if (ret)
> +		goto ret_unregister;
> +
> +	ret = cxusb_medion_register_analog_radio(dvbdev);
> +	if (ret)
> +		goto ret_vunreg;
> +
> +	return 0;
> +
> +ret_vunreg:
> +	video_unregister_device(cxdev->videodev);
> +
> +ret_unregister:
> +	v4l2_device_put(&cxdev->v4l2dev);
> +	wait_for_completion(&cxdev->v4l2_release);
> +
> +	return ret;
> +}
> +
> +void cxusb_medion_unregister_analog(struct dvb_usb_device *dvbdev)
> +{
> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
> +
> +	cxusb_vprintk(dvbdev, OPS, "unregistering analog\n");
> +
> +	video_unregister_device(cxdev->radiodev);
> +	video_unregister_device(cxdev->videodev);
> +
> +	v4l2_device_put(&cxdev->v4l2dev);
> +	wait_for_completion(&cxdev->v4l2_release);
> +
> +	cxusb_vprintk(dvbdev, OPS, "analog unregistered\n");
> +}
> diff --git a/drivers/media/usb/dvb-usb/cxusb.c b/drivers/media/usb/dvb-usb/cxusb.c
> index e0d390789b58..7498a2820178 100644
> --- a/drivers/media/usb/dvb-usb/cxusb.c
> +++ b/drivers/media/usb/dvb-usb/cxusb.c
> @@ -11,7 +11,6 @@
>   * design, so it can be reused for the "analogue-only" device (if it will
>   * appear at all).
>   *
> - * TODO: Use the cx25840-driver for the analogue part
>   *
>   * Copyright (C) 2005 Patrick Boettcher (patrick.boettcher@posteo.de)
>   * Copyright (C) 2006 Michael Krufky (mkrufky@linuxtv.org)
> @@ -2549,5 +2548,4 @@ MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>");
>  MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>");
>  MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>");
>  MODULE_DESCRIPTION("Driver for Conexant USB2.0 hybrid reference design");
> -MODULE_VERSION("1.0-alpha");
>  MODULE_LICENSE("GPL");
> diff --git a/drivers/media/usb/dvb-usb/cxusb.h b/drivers/media/usb/dvb-usb/cxusb.h
> index f586d61a7bf8..eb5ddda5962b 100644
> --- a/drivers/media/usb/dvb-usb/cxusb.h
> +++ b/drivers/media/usb/dvb-usb/cxusb.h
> @@ -2,12 +2,27 @@
>  #ifndef _DVB_USB_CXUSB_H_
>  #define _DVB_USB_CXUSB_H_
>  
> +#include <linux/completion.h>
>  #include <linux/i2c.h>
> +#include <linux/list.h>
>  #include <linux/mutex.h>
> +#include <linux/usb.h>
> +#include <linux/workqueue.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-device.h>
> +#include <media/videobuf2-core.h>
>  
>  #define DVB_USB_LOG_PREFIX "cxusb"
>  #include "dvb-usb.h"
>  
> +#define CXUSB_VIDEO_URBS (5)
> +
> +#define CXUSB_VIDEO_PKT_SIZE 3030
> +#define CXUSB_VIDEO_MAX_FRAME_PKTS 346
> +#define CXUSB_VIDEO_MAX_FRAME_SIZE (CXUSB_VIDEO_MAX_FRAME_PKTS * \
> +					CXUSB_VIDEO_PKT_SIZE)
> +
>  /* usb commands - some of it are guesses, don't have a reference yet */
>  #define CMD_BLUEBIRD_GPIO_RW 0x05
>  
> @@ -32,6 +47,20 @@
>  #define CMD_ANALOG        0x50
>  #define CMD_DIGITAL       0x51
>  
> +#define CXUSB_BT656_PREAMBLE ((const u8 *)"\xff\x00\x00")
> +
> +#define CXUSB_BT656_FIELD_MASK BIT(6)
> +#define CXUSB_BT656_FIELD_1 0
> +#define CXUSB_BT656_FIELD_2 BIT(6)
> +
> +#define CXUSB_BT656_VBI_MASK BIT(5)
> +#define CXUSB_BT656_VBI_ON BIT(5)
> +#define CXUSB_BT656_VBI_OFF 0
> +
> +#define CXUSB_BT656_SEAV_MASK BIT(4)
> +#define CXUSB_BT656_SEAV_EAV BIT(4)
> +#define CXUSB_BT656_SEAV_SAV 0
> +
>  /* Max transfer size done by I2C transfer functions */
>  #define MAX_XFER_SIZE  80
>  
> @@ -54,6 +83,29 @@ enum cxusb_open_type {
>  	CXUSB_OPEN_ANALOG, CXUSB_OPEN_DIGITAL
>  };
>  
> +struct cxusb_medion_auxbuf {
> +	u8 *buf;
> +	unsigned int len;
> +	unsigned int paylen;
> +};
> +
> +enum cxusb_bt656_mode {
> +	NEW_FRAME, FIRST_FIELD, SECOND_FIELD
> +};
> +
> +enum cxusb_bt656_fmode {
> +	START_SEARCH, LINE_SAMPLES, VBI_SAMPLES
> +};
> +
> +struct cxusb_bt656_params {
> +	enum cxusb_bt656_mode mode;
> +	enum cxusb_bt656_fmode fmode;
> +	unsigned int pos;
> +	unsigned int line;
> +	unsigned int linesamples;
> +	u8 *buf;
> +};
> +
>  struct cxusb_medion_dev {
>  	/* has to be the first one */
>  	struct cxusb_state state;
> @@ -63,18 +115,71 @@ struct cxusb_medion_dev {
>  	enum cxusb_open_type open_type;
>  	unsigned int open_ctr;
>  	struct mutex open_lock;
> +
> +#ifdef CONFIG_DVB_USB_CXUSB_ANALOG
> +	struct v4l2_device v4l2dev;
> +	struct v4l2_subdev *cx25840;
> +	struct v4l2_subdev *tuner;
> +	struct v4l2_subdev *tda9887;
> +	struct video_device *videodev, *radiodev;
> +	struct mutex dev_lock;
> +
> +	struct vb2_queue videoqueue;
> +	u32 input;
> +	bool streaming;
> +	u32 width, height;
> +	bool raw_mode;
> +	struct cxusb_medion_auxbuf auxbuf;
> +	v4l2_std_id norm;
> +
> +	struct urb *streamurbs[CXUSB_VIDEO_URBS];
> +	unsigned long urbcomplete;
> +	struct work_struct urbwork;
> +	unsigned int nexturb;
> +
> +	struct cxusb_bt656_params bt656;
> +	struct cxusb_medion_vbuffer *vbuf;
> +
> +	struct list_head buflist;
> +
> +	struct completion v4l2_release;
> +#endif
>  };
>  
> +struct cxusb_medion_vbuffer {
> +	struct vb2_buffer vb2;
> +	struct list_head list;
> +};
> +
> +/* Capture streaming parameters extendedmode field flags */
> +#define CXUSB_EXTENDEDMODE_CAPTURE_RAW 1
> +
>  /* defines for "debug" module parameter */
>  #define CXUSB_DBG_RC BIT(0)
>  #define CXUSB_DBG_I2C BIT(1)
>  #define CXUSB_DBG_MISC BIT(2)
> +#define CXUSB_DBG_BT656 BIT(3)
> +#define CXUSB_DBG_URB BIT(4)
> +#define CXUSB_DBG_OPS BIT(5)
> +#define CXUSB_DBG_AUXB BIT(6)
>  
>  extern int dvb_usb_cxusb_debug;
>  
> +#define cxusb_vprintk(dvbdev, lvl, ...) do {				\
> +		struct cxusb_medion_dev *_cxdev = (dvbdev)->priv;	\
> +		if (dvb_usb_cxusb_debug & CXUSB_DBG_##lvl)		\
> +			v4l2_printk(KERN_DEBUG,			\
> +				    &_cxdev->v4l2dev, __VA_ARGS__);	\
> +	} while (0)
> +
>  int cxusb_ctrl_msg(struct dvb_usb_device *d,
>  		   u8 cmd, const u8 *wbuf, int wlen, u8 *rbuf, int rlen);
>  
> +#ifdef CONFIG_DVB_USB_CXUSB_ANALOG
> +int cxusb_medion_analog_init(struct dvb_usb_device *dvbdev);
> +int cxusb_medion_register_analog(struct dvb_usb_device *dvbdev);
> +void cxusb_medion_unregister_analog(struct dvb_usb_device *dvbdev);
> +#else
>  static inline int cxusb_medion_analog_init(struct dvb_usb_device *dvbdev)
>  {
>  	return -EINVAL;
> @@ -88,6 +193,7 @@ static inline int cxusb_medion_register_analog(struct dvb_usb_device *dvbdev)
>  static inline void cxusb_medion_unregister_analog(struct dvb_usb_device *dvbdev)
>  {
>  }
> +#endif
>  
>  int cxusb_medion_get(struct dvb_usb_device *dvbdev,
>  		     enum cxusb_open_type open_type);
> 

I stopped reviewing halfway because I was wondering whether this is the right approach.

Most of the analog support is not actually tied to the medion but is fairly generic.
This is how other drivers do that as well: the implementation is generic and the board
specific bits are implemented via card structures.

They also make use of v4l2_device_call_all() instead of calling each subdev directly,
this means that the board-specific code loads the correct subdevs, but the generic
code doesn't need to know (in general) what those are, it just calls all of them.

Regards,

	Hans

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v7 0/6] [media] Add analog mode support for Medion MD95700
  2018-07-02 21:23 [PATCH v7 0/6] [media] Add analog mode support for Medion MD95700 Maciej S. Szmigiero
                   ` (5 preceding siblings ...)
  2018-07-02 21:23 ` [PATCH v7 6/6] [media] cxusb: add analog mode support for Medion MD95700 Maciej S. Szmigiero
@ 2018-07-04  9:33 ` Hans Verkuil
  6 siblings, 0 replies; 14+ messages in thread
From: Hans Verkuil @ 2018-07-04  9:33 UTC (permalink / raw)
  To: Maciej S. Szmigiero, Michael Krufky, Mauro Carvalho Chehab
  Cc: Andy Walls, linux-kernel, linux-media

Hi Maciej,

First my apologies for the long delay in reviewing, I'll try to do better
next time.

Anyway, I'll take patches 1, 2 and 4 now. I commented on patch 3, that definitely
can be improved. Same for patch 6: that can be made a lot more generic.

Regards,

	Hans

On 02/07/18 23:23, Maciej S. Szmigiero wrote:
> This series adds support for analog part of Medion 95700 in the cxusb
> driver.
> 
> What works:
> * Video capture at various sizes with sequential fields,
> * Input switching (TV Tuner, Composite, S-Video),
> * TV and radio tuning,
> * Video standard switching and auto detection,
> * Radio mode switching (stereo / mono),
> * Unplugging while capturing,
> * DVB / analog coexistence,
> * Raw BT.656 stream support.
> 
> What does not work yet:
> * Audio,
> * VBI,
> * Picture controls.
> 
> This series (as a one patch) was submitted for inclusion few years ago,
> then waited few months in a patch queue.
> Unfortunately, by the time it was supposed to be merged there
> were enough changes in media that it was no longer mergeable.
> 
> I thought at that time that I will be able to rebase and retest it soon
> but unfortunately up till now I was never able to find enough time to do
> so.
> Also, with the passing of time the implementation diverged more and
> more from the current kernel code, necessitating even more reworking.
> 
> That last iteration can be found here:
> https://patchwork.linuxtv.org/patch/8048/
> 
> Since that version there had been the following changes:
> * Adaptation to changes in V4L2 / DVB core,
> 
> * Radio device was added, with a possibility to tune to a FM radio
> station and switch between stereo and mono modes (tested by taping
> audio signal directly at tuner output pin),
> 
> * DVB / analog coexistence was improved - resolved a few cases where
> DVB core would switch off power or reset the tuner when the device
> was still being used but in the analog mode,
> 
> * Fixed issues reported by v4l2-compliance,
> 
> * Switching to raw BT.656 mode is now done by a custom streaming
> parameter set via VIDIOC_S_PARM ioctl instead of using a
> V4L2_BUF_TYPE_PRIVATE buffer (which was removed from V4L2),
> 
> * General small code cleanups (like using BIT() or ARRAY_SIZE() macros
> instead of open coding them, code formatting improvements, etc.).
> 
> Changes from v1:
> * Only support configuration of cx25840 pins that the cxusb driver is
> actually using so there is no need for an ugly CX25840_PIN() macro,
> 
> * Split cxusb changes into two patches: first one implementing
> digital / analog coexistence in this driver, second one adding the
> actual implementation of the analog mode,
> 
> * Fix a warning reported by kbuild test robot.
> 
> Changes from v2:
> * Split out ivtv cx25840 platform data zero-initialization to a separate
> commit,
> 
> * Add kernel-doc description of struct cx25840_state,
> 
> * Make sure that all variables used in CX25840_VCONFIG_OPTION() and
> CX25840_VCONFIG_SET_BIT() macros are their explicit parameters,
> 
> * Split out some code from cxusb_medion_copy_field() and
> cxusb_medion_v_complete_work() functions to separate ones to increase
> their readability,
> 
> * Generate masks using GENMASK() and BIT() macros in cx25840.h and
> cxusb.h.
> 
> Changes from v3:
> Add SPDX tag to a newly added "cxusb-analog.c" file.
> 
> Changes from v4:
> * Make analog support conditional on a new DVB_USB_CXUSB_ANALOG Kconfig
> option,
> 
> * Use '//' comments in the header of a newly added "cxusb-analog.c"
> file,
> 
> * Don't print errors on memory allocation failures,
> 
> * Get rid of the driver MODULE_VERSION(),
> 
> * Small formating fix of a one line.
> 
> Changes from v5:
> Rebase onto current media_tree/master.
> 
> Changes from v6:
> Adapt to commit 4eb2f55728abbe changing V4L2_SUBDEV_IO_PIN_* from bit
> masks to bit numbers.
> Thanks to Dan Carpenter's kbuild bot for catching this.
> 
>  drivers/media/i2c/cx25840/cx25840-core.c |  396 ++++-
>  drivers/media/i2c/cx25840/cx25840-core.h |   46 +-
>  drivers/media/i2c/cx25840/cx25840-vbi.c  |    3 +
>  drivers/media/pci/ivtv/ivtv-i2c.c        |    1 +
>  drivers/media/tuners/tuner-simple.c      |    5 +-
>  drivers/media/usb/dvb-usb/Kconfig        |   16 +-
>  drivers/media/usb/dvb-usb/Makefile       |    3 +
>  drivers/media/usb/dvb-usb/cxusb-analog.c | 1914 ++++++++++++++++++++++
>  drivers/media/usb/dvb-usb/cxusb.c        |  452 ++++-
>  drivers/media/usb/dvb-usb/cxusb.h        |  154 ++
>  drivers/media/usb/dvb-usb/dvb-usb-dvb.c  |   20 +-
>  drivers/media/usb/dvb-usb/dvb-usb-init.c |   13 +
>  drivers/media/usb/dvb-usb/dvb-usb.h      |    8 +
>  include/media/drv-intf/cx25840.h         |   74 +-
>  14 files changed, 3042 insertions(+), 63 deletions(-)
>  create mode 100644 drivers/media/usb/dvb-usb/cxusb-analog.c
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v7 3/6] cx25840: add pin to pad mapping and output format configuration
  2018-07-04  9:05   ` Hans Verkuil
@ 2018-07-10 21:47     ` Maciej S. Szmigiero
  2018-07-17 12:29       ` Hans Verkuil
  0 siblings, 1 reply; 14+ messages in thread
From: Maciej S. Szmigiero @ 2018-07-10 21:47 UTC (permalink / raw)
  To: Hans Verkuil
  Cc: Michael Krufky, Mauro Carvalho Chehab, Andy Walls, linux-kernel,
	linux-media

Hi Hans,

On 04.07.2018 11:05, Hans Verkuil wrote:
> On 02/07/18 23:23, Maciej S. Szmigiero wrote:
(..)
>> @@ -316,6 +319,260 @@ static int cx23885_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
>>  	return 0;
>>  }
>>  
>> +static u8 cx25840_function_to_pad(struct i2c_client *client, u8 function)
>> +{
>> +	switch (function) {
>> +	case CX25840_PAD_ACTIVE:
>> +		return 1;
>> +
>> +	case CX25840_PAD_VACTIVE:
>> +		return 2;
>> +
>> +	case CX25840_PAD_CBFLAG:
>> +		return 3;
>> +
>> +	case CX25840_PAD_VID_DATA_EXT0:
>> +		return 4;
>> +
>> +	case CX25840_PAD_VID_DATA_EXT1:
>> +		return 5;
>> +
>> +	case CX25840_PAD_GPO0:
>> +		return 6;
>> +
>> +	case CX25840_PAD_GPO1:
>> +		return 7;
>> +
>> +	case CX25840_PAD_GPO2:
>> +		return 8;
>> +
>> +	case CX25840_PAD_GPO3:
>> +		return 9;
>> +
>> +	case CX25840_PAD_IRQ_N:
>> +		return 10;
>> +
>> +	case CX25840_PAD_AC_SYNC:
>> +		return 11;
>> +
>> +	case CX25840_PAD_AC_SDOUT:
>> +		return 12;
>> +
>> +	case CX25840_PAD_PLL_CLK:
>> +		return 13;
>> +
>> +	case CX25840_PAD_VRESET:
>> +		return 14;
>> +
>> +	default:
>> +		if (function != CX25840_PAD_DEFAULT)
>> +			v4l_err(client,
>> +				"invalid function %u, assuming default\n",
>> +				(unsigned int)function);
>> +		return 0;
>> +	}
> 
> Unless I am mistaken this function boils down to:
> 
> static u8 cx25840_function_to_pad(struct i2c_client *client, u8 function)
> {
> 	return function > CX25840_PAD_VRESET ? 0 : function;
> }

Yes, you are right these functions are equivalent (sans a warning when
a caller passes an invalid function).

However, these values (CX25840_PAD_*) were meant to be driver-internal.
If we use them also as register value constants (which is what
cx25840_function_to_pad() is supposed to return) then we'll need to add
a comment to their enum cx25840_io_pad so nobody shuffles them or changes
their values by mistake.

>> @@ -1647,6 +1924,119 @@ static void log_audio_status(struct i2c_client *client)
>>  	}
>>  }
>>  
>> +#define CX25840_VCONFIG_OPTION(state, cfg_in, opt_msk)			\
>> +	do {								\
>> +		if ((cfg_in) & (opt_msk)) {				\
>> +			(state)->vid_config &= ~(opt_msk);		\
>> +			(state)->vid_config |= (cfg_in) & (opt_msk);	\
>> +		}							\
>> +	} while (0)
>> +
>> +#define CX25840_VCONFIG_SET_BIT(state, opt_msk, voc, idx, bit, oneval)	\
>> +	do {								\
>> +		if ((state)->vid_config & (opt_msk)) {			\
>> +			if (((state)->vid_config & (opt_msk)) ==	\
>> +			    (oneval))					\
>> +				(voc)[idx] |= BIT(bit);		\
>> +			else						\
>> +				(voc)[idx] &= ~BIT(bit);		\
>> +		}							\
>> +	} while (0)
>> +
>> +int cx25840_vconfig(struct i2c_client *client, u32 cfg_in)
>> +{
>> +	struct cx25840_state *state = to_state(i2c_get_clientdata(client));
>> +	u8 voutctrl[3];
>> +	unsigned int i;
>> +
>> +	/* apply incoming options to the current state */
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_FMT_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_RES_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VBIRAW_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ANCDATA_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_TASKBIT_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ACTIVE_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VALID_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_HRESETW_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_CLKGATE_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_DCMODE_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_IDID0S_MASK);
>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VIPCLAMP_MASK);
> 
> This appears to be a very complex way of saying:
> 
> 	state->vid_config = cfg_in;

This is supposed to change in vid_config only these options that are set
in the incoming cfg_in, leaving the rest as-is.

If the code simply assigns cfg_in to vid_config it will also clear all the
existing options.

> 
>> +
>> +	for (i = 0; i < 3; i++)
>> +		voutctrl[i] = cx25840_read(client, 0x404 + i);
>> +
>> +	/* apply state to hardware regs */
>> +	if (state->vid_config & CX25840_VCONFIG_FMT_MASK)
>> +		voutctrl[0] &= ~3;
>> +	switch (state->vid_config & CX25840_VCONFIG_FMT_MASK) {
>> +	case CX25840_VCONFIG_FMT_BT656:
>> +		voutctrl[0] |= 1;
>> +		break;
>> +
>> +	case CX25840_VCONFIG_FMT_VIP11:
>> +		voutctrl[0] |= 2;
>> +		break;
>> +
>> +	case CX25840_VCONFIG_FMT_VIP2:
>> +		voutctrl[0] |= 3;
>> +		break;
>> +
>> +	case CX25840_VCONFIG_FMT_BT601:
>> +		/* zero */
>> +	default:
>> +		break;
>> +	}
>> +
>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_RES_MASK, voutctrl,
>> +				0, 2, CX25840_VCONFIG_RES_10BIT);
>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VBIRAW_MASK, voutctrl,
>> +				0, 3, CX25840_VCONFIG_VBIRAW_ENABLED);
>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ANCDATA_MASK, voutctrl,
>> +				0, 4, CX25840_VCONFIG_ANCDATA_ENABLED);
>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_TASKBIT_MASK, voutctrl,
>> +				0, 5, CX25840_VCONFIG_TASKBIT_ONE);
>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ACTIVE_MASK, voutctrl,
>> +				1, 2, CX25840_VCONFIG_ACTIVE_HORIZONTAL);
>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VALID_MASK, voutctrl,
>> +				1, 3, CX25840_VCONFIG_VALID_ANDACTIVE);
>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_HRESETW_MASK, voutctrl,
>> +				1, 4, CX25840_VCONFIG_HRESETW_PIXCLK);
>> +
>> +	if (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK)
>> +		voutctrl[1] &= ~(3 << 6);
>> +	switch (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK) {
>> +	case CX25840_VCONFIG_CLKGATE_VALID:
>> +		voutctrl[1] |= 2;
>> +		break;
>> +
>> +	case CX25840_VCONFIG_CLKGATE_VALIDACTIVE:
>> +		voutctrl[1] |= 3;
>> +		break;
>> +
>> +	case CX25840_VCONFIG_CLKGATE_NONE:
>> +		/* zero */
>> +	default:
>> +		break;
>> +	}
>> +
>> +
>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_DCMODE_MASK, voutctrl,
>> +				2, 0, CX25840_VCONFIG_DCMODE_BYTES);
>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_IDID0S_MASK, voutctrl,
>> +				2, 1, CX25840_VCONFIG_IDID0S_LINECNT);
>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VIPCLAMP_MASK, voutctrl,
>> +				2, 4, CX25840_VCONFIG_VIPCLAMP_ENABLED);
>> +
>> +	for (i = 0; i < 3; i++)
>> +		cx25840_write(client, 0x404 + i, voutctrl[i]);
>> +
>> +	return 0;
>> +}
>> +
>> +#undef CX25840_VCONFIG_SET_BIT
>> +#undef CX25840_VCONFIG_OPTION
> 
> Why #undef? You would normally never do that.

The idea here is to catch (unintended) other users of these macros, other
than cx25840_vconfig() and to not pollute the macro namespace.
But these #undefs can be removed if they are against the coding style.

> 
>> +
>>  /* ----------------------------------------------------------------------- */
>>  
>>  /* This load_fw operation must be called to load the driver's firmware.
>> @@ -1836,6 +2226,9 @@ static int cx25840_s_video_routing(struct v4l2_subdev *sd,
>>  	if (is_cx23888(state))
>>  		cx23888_std_setup(client);
>>  
>> +	if (is_cx2584x(state) && state->generic_mode)
>> +		cx25840_vconfig(client, config);
>> +
> 
> You do the vconfig configuration when the video routing changes. But isn't this
> configuration a one-time thing? E.g. something you do only when initializing the
> board?
> 
> At least in the cxusb code the cfg_in value is constant and not dependent on what
> input is chosen.
> 
> If this is true, then you should add the core init op instead. And as a bonus you
> can turn on generic_mode if the init op is called instead of having to add it
> to the platform data.
> 

The problem here is that the Medion MD95700 has two modes:
digital (DVB-T) and analog.
When it is operating in the digital mode the device analog components
(including the cx25840 chip) have their power cut by the hardware.

This means that the cx25840 has to be fully reinitialized (firmware
loaded, format set, etc.) every time the user opens the Medion's v4l2
video or radio device node if the device has previously operated in the
DVB-T mode.
Mode switching is supported transparently by the driver without needing
to reload the module or reconnect the device.

> 
> Regards,
> 
> 	Hans
> 

Thanks and best regards,
Maciej

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v7 6/6] [media] cxusb: add analog mode support for Medion MD95700
  2018-07-04  9:33   ` Hans Verkuil
@ 2018-07-10 21:48     ` Maciej S. Szmigiero
  2018-07-17 12:56       ` Hans Verkuil
  0 siblings, 1 reply; 14+ messages in thread
From: Maciej S. Szmigiero @ 2018-07-10 21:48 UTC (permalink / raw)
  To: Hans Verkuil
  Cc: Michael Krufky, Mauro Carvalho Chehab, Andy Walls, linux-kernel,
	linux-media

Hi Hans,

On 04.07.2018 11:33, Hans Verkuil wrote:
> Hi Maciej,
> 
> On 02/07/18 23:23, Maciej S. Szmigiero wrote:
(..)
>> +static int cxusb_medion_v_queue_setup(struct vb2_queue *q,
>> +				      unsigned int *num_buffers,
>> +				      unsigned int *num_planes,
>> +				      unsigned int sizes[],
>> +				      struct device *alloc_devs[])
>> +{
>> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
>> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
>> +	unsigned int size = cxdev->raw_mode ?
>> +		CXUSB_VIDEO_MAX_FRAME_SIZE :
>> +		cxdev->width * cxdev->height * 2;
>> +
>> +	if (*num_planes > 0) {
>> +		if (*num_planes != 1)
>> +			return -EINVAL;
>> +
>> +		if (sizes[0] < size)
>> +			return -EINVAL;
>> +	} else {
>> +		*num_planes = 1;
>> +		sizes[0] = size;
>> +	}
>> +
>> +	if (q->num_buffers + *num_buffers < 6)
>> +		*num_buffers = 6 - q->num_buffers;
> 
> Huh? These two lines should be removed. I'm not sure what the purpose is.

This is to request that the vb2 framework keeps at least 6 buffers in the
queue.

There is a similar code in usb/usbtv/usbtv-video.c:
> if (vq->num_buffers + *nbuffers < 2)
>                 *nbuffers = 2 - vq->num_buffers;

And in usb/hackrf/hackrf.c and usb/airspy/airspy.c:
> /* Need at least 8 buffers */
> if (vq->num_buffers + *nbuffers < 8)
> 	*nbuffers = 8 - vq->num_buffers;

In addition to this, many drivers (like stk1160, s2255, pwc, msi2500,
go7007, ...) always enforce some minimum *num_buffers count, regardless
of the number of buffers currently in the queue.

>> +static void cxusb_auxbuf_init(struct cxusb_medion_auxbuf *auxbuf,
>> +			      u8 *buf, unsigned int len)
>> +{
>> +	auxbuf->buf = buf;
>> +	auxbuf->len = len;
>> +	auxbuf->paylen = 0;
>> +}
>> +
>> +static void cxusb_auxbuf_head_trim(struct dvb_usb_device *dvbdev,
>> +				   struct cxusb_medion_auxbuf *auxbuf,
>> +				   unsigned int pos)
>> +{
>> +	if (pos == 0)
>> +		return;
>> +
>> +	if (WARN_ON(pos > auxbuf->paylen))
>> +		return;
>> +
>> +	cxusb_vprintk(dvbdev, AUXB,
>> +		      "trimming auxbuf len by %u to %u\n",
>> +		      pos, auxbuf->paylen - pos);
>> +
>> +	memmove(auxbuf->buf, auxbuf->buf + pos, auxbuf->paylen - pos);
>> +	auxbuf->paylen -= pos;
>> +}
>> +
>> +static unsigned int cxusb_auxbuf_paylen(struct cxusb_medion_auxbuf *auxbuf)
>> +{
>> +	return auxbuf->paylen;
>> +}
>> +
>> +static bool cxusb_auxbuf_make_space(struct dvb_usb_device *dvbdev,
>> +				    struct cxusb_medion_auxbuf *auxbuf,
>> +				    unsigned int howmuch)
>> +{
>> +	unsigned int freespace;
>> +
>> +	if (WARN_ON(howmuch >= auxbuf->len))
>> +		howmuch = auxbuf->len - 1;
>> +
>> +	freespace = auxbuf->len - cxusb_auxbuf_paylen(auxbuf);
>> +
>> +	cxusb_vprintk(dvbdev, AUXB, "freespace is %u\n", freespace);
>> +
>> +	if (freespace >= howmuch)
>> +		return true;
>> +
>> +	howmuch -= freespace;
>> +
>> +	cxusb_vprintk(dvbdev, AUXB, "will overwrite %u bytes of buffer\n",
>> +		      howmuch);
>> +
>> +	cxusb_auxbuf_head_trim(dvbdev, auxbuf, howmuch);
>> +
>> +	return false;
>> +}
>> +
>> +/* returns false if some data was overwritten */
>> +static bool cxusb_auxbuf_append_urb(struct dvb_usb_device *dvbdev,
>> +				    struct cxusb_medion_auxbuf *auxbuf,
>> +				    struct urb *urb)
>> +{
>> +	unsigned long len = 0;
>> +	int i;
>> +	bool ret;
>> +
>> +	for (i = 0; i < urb->number_of_packets; i++)
>> +		len += urb->iso_frame_desc[i].actual_length;
>> +
>> +	ret = cxusb_auxbuf_make_space(dvbdev, auxbuf, len);
>> +
>> +	for (i = 0; i < urb->number_of_packets; i++) {
>> +		unsigned int to_copy;
>> +
>> +		to_copy = urb->iso_frame_desc[i].actual_length;
>> +
>> +		memcpy(auxbuf->buf + auxbuf->paylen, urb->transfer_buffer +
>> +		       urb->iso_frame_desc[i].offset, to_copy);
>> +
>> +		auxbuf->paylen += to_copy;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static bool cxusb_auxbuf_copy(struct cxusb_medion_auxbuf *auxbuf,
>> +			      unsigned int pos, unsigned char *dest,
>> +			      unsigned int len)
>> +{
>> +	if (pos + len > auxbuf->paylen)
>> +		return false;
>> +
>> +	memcpy(dest, auxbuf->buf + pos, len);
>> +
>> +	return true;
>> +}
>> +
>> +static unsigned int cxusb_auxbuf_advance(struct cxusb_medion_auxbuf *auxbuf,
>> +					 unsigned int pos,
>> +					 unsigned int increment)
>> +{
>> +	return pos + increment;
>> +}
>> +
>> +static unsigned int cxusb_auxbuf_begin(struct cxusb_medion_auxbuf *auxbuf)
>> +{
>> +	return 0;
>> +}
>> +
>> +static bool cxusb_auxbuf_isend(struct cxusb_medion_auxbuf *auxbuf,
>> +			       unsigned int pos)
>> +{
>> +	return pos >= auxbuf->paylen;
>> +}
> 
> These three functions seem pointless to me.

I will remove cxusb_auxbuf_begin().

cxusb_auxbuf_advance() is called 6 times in the code while
cxusb_auxbuf_isend() keeps the buffer implementation opaque for users so
I think they make the code a bit nicer.

>> +static void cxusb_medion_v_process_urb_raw_mode(struct cxusb_medion_dev *cxdev,
>> +						struct urb *urb)
>> +{
>> +	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
>> +	u8 *buf;
>> +	struct cxusb_medion_vbuffer *vbuf;
>> +	int i;
>> +	unsigned long len = 0;
>> +
>> +	if (list_empty(&cxdev->buflist)) {
>> +		dev_warn(&dvbdev->udev->dev, "no free buffers\n");
>> +		return;
>> +	}
>> +
>> +	vbuf = list_first_entry(&cxdev->buflist, struct cxusb_medion_vbuffer,
>> +				list);
>> +	list_del(&vbuf->list);
>> +
>> +	vbuf->vb2.timestamp = ktime_get_ns();
>> +
>> +	buf = vb2_plane_vaddr(&vbuf->vb2, 0);
>> +
>> +	for (i = 0; i < urb->number_of_packets; i++) {
>> +		memcpy(buf, urb->transfer_buffer +
>> +		       urb->iso_frame_desc[i].offset,
>> +		       urb->iso_frame_desc[i].actual_length);
>> +
>> +		buf += urb->iso_frame_desc[i].actual_length;
>> +		len += urb->iso_frame_desc[i].actual_length;
>> +	}
>> +
>> +	vb2_set_plane_payload(&vbuf->vb2, 0, len);
>> +
>> +	vb2_buffer_done(&vbuf->vb2, VB2_BUF_STATE_DONE);
> 
> The frame sequence counter in vb2_v4l2_buffer does not seem to be incremented.
> 
> Did you test this driver with v4l2-compliance? It will detect such errors.

Hmm, I did test this driver with v4l2-compliance last year and don't
remember seeing any error about the frame sequence counter.

When I'll be doing a respin I will check this again and see whether
v4l2-compliance reports anything (it seems it should).

>> +static int cxusb_medion_v_start_streaming(struct vb2_queue *q,
>> +					  unsigned int count)
>> +{
>> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
>> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
>> +	u8 streamon_params[2] = { 0x03, 0x00 };
>> +	int npackets, i;
>> +	int ret;
>> +
>> +	cxusb_vprintk(dvbdev, OPS, "should start streaming\n");
>> +
>> +	/* already streaming */
>> +	if (cxdev->streaming)
>> +		return 0;
> 
> Unnecessary check, this can't happen.
> 
>> +
>> +	if (cxusb_medion_stream_busy(cxdev)) {
>> +		ret = -EBUSY;
>> +		goto ret_retbufs;
>> +	}
>> +
>> +	ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 1);
>> +	if (ret != 0) {
>> +		dev_err(&dvbdev->udev->dev,
>> +			"unable to start stream (%d)\n", ret);
>> +		goto ret_retbufs;
>> +	}
>> +
>> +	ret = cxusb_ctrl_msg(dvbdev, CMD_STREAMING_ON, streamon_params, 2,
>> +			     NULL, 0);
>> +	if (ret != 0) {
>> +		dev_err(&dvbdev->udev->dev,
>> +			"unable to start streaming (%d)\n", ret);
>> +		goto ret_unstream_cx;
>> +	}
>> +
>> +	if (cxdev->raw_mode)
>> +		npackets = CXUSB_VIDEO_MAX_FRAME_PKTS;
>> +	else {
>> +		u8 *buf;
>> +		unsigned int urblen, auxbuflen;
>> +
>> +		/* has to be less than full frame size */
>> +		urblen = (cxdev->width * 2 + 4 + 4) * cxdev->height;
>> +		npackets = urblen / CXUSB_VIDEO_PKT_SIZE;
>> +		urblen = npackets * CXUSB_VIDEO_PKT_SIZE;
>> +
>> +		auxbuflen = (cxdev->width * 2 + 4 + 4) *
>> +			(cxdev->height + 50 /* VBI lines */) + urblen;
>> +
>> +		buf = vmalloc(auxbuflen);
>> +		if (buf == NULL) {
>> +			ret = -ENOMEM;
>> +			goto ret_unstream_md;
>> +		}
>> +
>> +		cxusb_auxbuf_init(&cxdev->auxbuf, buf, auxbuflen);
>> +	}
>> +
>> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++) {
>> +		int framen;
>> +		u8 *streambuf;
>> +		struct urb *surb;
>> +
>> +		streambuf = kmalloc(npackets * CXUSB_VIDEO_PKT_SIZE,
>> +				    GFP_KERNEL);
>> +		if (streambuf == NULL) {
>> +			if (i == 0) {
>> +				ret = -ENOMEM;
>> +				goto ret_freeab;
>> +			} else
>> +				break;
>> +		}
>> +
>> +		surb = usb_alloc_urb(npackets, GFP_KERNEL);
>> +		if (surb == NULL) {
>> +			kfree(streambuf);
>> +			ret = -ENOMEM;
>> +			goto ret_freeu;
>> +		}
>> +
>> +		cxdev->streamurbs[i] = surb;
>> +		surb->dev = dvbdev->udev;
>> +		surb->context = dvbdev;
>> +		surb->pipe = usb_rcvisocpipe(dvbdev->udev, 2);
>> +
>> +		surb->interval = 1;
>> +		surb->transfer_flags = URB_ISO_ASAP;
>> +
>> +		surb->transfer_buffer = streambuf;
>> +
>> +		surb->complete = cxusb_medion_v_complete;
>> +		surb->number_of_packets = npackets;
>> +		surb->transfer_buffer_length = npackets * CXUSB_VIDEO_PKT_SIZE;
>> +
>> +		for (framen = 0; framen < npackets; framen++) {
>> +			surb->iso_frame_desc[framen].offset =
>> +				CXUSB_VIDEO_PKT_SIZE * framen;
>> +
>> +			surb->iso_frame_desc[framen].length =
>> +				CXUSB_VIDEO_PKT_SIZE;
>> +		}
>> +	}
>> +
>> +	cxdev->urbcomplete = 0;
>> +	cxdev->nexturb = 0;
>> +	cxdev->vbuf = NULL;
>> +	cxdev->bt656.mode = NEW_FRAME;
>> +	cxdev->bt656.buf = NULL;
>> +
>> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>> +		if (cxdev->streamurbs[i] != NULL) {
>> +			ret = usb_submit_urb(cxdev->streamurbs[i],
>> +					GFP_KERNEL);
>> +			if (ret != 0)
>> +				dev_err(&dvbdev->udev->dev,
>> +					"URB %d submission failed (%d)\n", i,
>> +					ret);
>> +		}
>> +
>> +	cxdev->streaming = true;
> 
> No need to keep track of the streaming state. You can use vb2_start_streaming_called()
> instead since vb2 already keeps track of this.

The driver still needs some flag to tell its workqueue task to not
process and resubmit URBs when the stream is being stopped.

Otherwise, as the device lock gets released to flush the workqueue task
there will be a time window where this task could still resubmit URBs
(not knowing that we are stopping the stream).
And we need to have the URBs killed before we flush the workqueue since
their completion routine can schedule the workqueue task.

There seems to be no vb2 specific flag for this:
q->start_streaming_called is zeroed only after stop_streaming callback
has already returned.
And we need to have URBs killed and the workqueue task flushed before
we return from stop_streaming callback so we can clean up their
resources in this callback.

>> +
>> +	return 0;
>> +
>> +ret_freeu:
>> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>> +		if (cxdev->streamurbs[i] != NULL) {
>> +			kfree(cxdev->streamurbs[i]->transfer_buffer);
>> +			usb_free_urb(cxdev->streamurbs[i]);
>> +			cxdev->streamurbs[i] = NULL;
>> +		}
>> +
>> +ret_freeab:
>> +	if (!cxdev->raw_mode)
>> +		vfree(cxdev->auxbuf.buf);
>> +
>> +ret_unstream_md:
>> +	cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
>> +
>> +ret_unstream_cx:
>> +	v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
>> +
>> +ret_retbufs:
>> +	cxusb_medion_return_buffers(cxdev, true);
>> +
>> +	return ret;
>> +}
>> +
>> +static void cxusb_medion_v_stop_streaming(struct vb2_queue *q)
>> +{
>> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
>> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
>> +	int i, ret;
>> +
>> +	cxusb_vprintk(dvbdev, OPS, "should stop streaming\n");
>> +
>> +	if (!cxdev->streaming)
>> +		return;
>> +
>> +	cxdev->streaming = false;
>> +
>> +	cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
>> +
>> +	ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
>> +	if (ret != 0)
>> +		dev_err(&dvbdev->udev->dev, "unable to stop stream (%d)\n",
>> +			ret);
>> +
>> +	/* let URB completion run */
>> +	mutex_unlock(cxdev->videodev->lock);
>> +
>> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>> +		if (cxdev->streamurbs[i] != NULL)
>> +			usb_kill_urb(cxdev->streamurbs[i]);
>> +
>> +	flush_work(&cxdev->urbwork);
>> +
>> +	mutex_lock(cxdev->videodev->lock);
>> +
>> +	/* free transfer buffer and URB */
>> +	if (!cxdev->raw_mode)
>> +		vfree(cxdev->auxbuf.buf);
>> +
>> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>> +		if (cxdev->streamurbs[i] != NULL) {
>> +			kfree(cxdev->streamurbs[i]->transfer_buffer);
>> +			usb_free_urb(cxdev->streamurbs[i]);
>> +			cxdev->streamurbs[i] = NULL;
>> +		}
>> +
>> +	cxusb_medion_return_buffers(cxdev, false);
>> +}
>> +
>> +static void cxusub_medion_v_buf_queue(struct vb2_buffer *vb)
>> +{
>> +	struct cxusb_medion_vbuffer *vbuf =
>> +		container_of(vb, struct cxusb_medion_vbuffer, vb2);
>> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(vb->vb2_queue);
>> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
>> +
>> +	/* cxusb_vprintk(dvbdev, OPS, "mmmm.. fresh buffer...\n"); */
>> +
>> +	list_add_tail(&vbuf->list, &cxdev->buflist);
> 
> I would expect a spinlock here to protect the list.

All buffer list accesses are done either while holding main 
lock or in vb2 callbacks (which accoring to
https://lwn.net/Articles/447435/ can assume that the lock has been
taken).
 
>> +static int cxusb_medion_g_fmt_vid_cap(struct file *file, void *fh,
>> +				      struct v4l2_format *f)
>> +{
>> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
>> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
>> +
>> +	f->fmt.pix.width = cxdev->width;
>> +	f->fmt.pix.height = cxdev->height;
>> +	f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
>> +	f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
>> +	f->fmt.pix.bytesperline = cxdev->raw_mode ? 0 : cxdev->width * 2;
> 
> Dunno what raw_mode is, but it looks suspicous.

It is simply an ability to capture a raw BT.656 stream as received by
the driver from the device.
This functionality is very useful for debugging.
> I stopped reviewing halfway because I was wondering whether this is the right approach.
> 
> Most of the analog support is not actually tied to the medion but is fairly generic.
> This is how other drivers do that as well: the implementation is generic and the board
> specific bits are implemented via card structures.
> 
> They also make use of v4l2_device_call_all() instead of calling each subdev directly,
> this means that the board-specific code loads the correct subdevs, but the generic
> code doesn't need to know (in general) what those are, it just calls all of them.

There are only three subdevices here: a RF tuner, TDA9887 demod and
cx25840 chip.
Most of the subdev calls are for c25840 only, some (like g_tuner) should
be done in a proper order so returned details aren't overwritten by less
specific data being returned from the next subdevice.
The RF tuner and the demod are target of just a few of the subdev calls.

v4l2_device_call_all() ignores any errors, which makes for example trying
a video format hard (and which format will be accepted by the cx25840
depends on the currently set broadcast standard and parameters of the
last signal that was received), while V4L2 docs say that
VIDIOC_{S,TRY}_FMT ioctls "should not return an error code unless the
type field is invalid", that is, they should not return an error for
invalid or unsupported image widths or heights.
They should instead return something sensible for these image parameters
which requires a feedback from the cx25840 subdev which one it will
finally accept.

With respect to making the code more generic: considering this is a
13-year old hardware I think it is fairly unlikely that there are going
to be devices with similar internal design released in the future.
And in the unlikely case that this indeed happens then the code can
always be refactored into some more generic framework.

This driver has been alredy nearly merged back in 2012 (even without last
year cleanups), just I wasn't able to find time to rebase it then:
https://www.mail-archive.com/linux-media@vger.kernel.org/msg50449.html
so it isn't something completely new.

> Regards,
> 
> 	Hans
> 

Thanks and best regards,
Maciej

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v7 3/6] cx25840: add pin to pad mapping and output format configuration
  2018-07-10 21:47     ` Maciej S. Szmigiero
@ 2018-07-17 12:29       ` Hans Verkuil
  0 siblings, 0 replies; 14+ messages in thread
From: Hans Verkuil @ 2018-07-17 12:29 UTC (permalink / raw)
  To: Maciej S. Szmigiero
  Cc: Michael Krufky, Mauro Carvalho Chehab, Andy Walls, linux-kernel,
	linux-media

On 10/07/18 23:47, Maciej S. Szmigiero wrote:
> Hi Hans,
> 
> On 04.07.2018 11:05, Hans Verkuil wrote:
>> On 02/07/18 23:23, Maciej S. Szmigiero wrote:
> (..)
>>> @@ -316,6 +319,260 @@ static int cx23885_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
>>>  	return 0;
>>>  }
>>>  
>>> +static u8 cx25840_function_to_pad(struct i2c_client *client, u8 function)
>>> +{
>>> +	switch (function) {
>>> +	case CX25840_PAD_ACTIVE:
>>> +		return 1;
>>> +
>>> +	case CX25840_PAD_VACTIVE:
>>> +		return 2;
>>> +
>>> +	case CX25840_PAD_CBFLAG:
>>> +		return 3;
>>> +
>>> +	case CX25840_PAD_VID_DATA_EXT0:
>>> +		return 4;
>>> +
>>> +	case CX25840_PAD_VID_DATA_EXT1:
>>> +		return 5;
>>> +
>>> +	case CX25840_PAD_GPO0:
>>> +		return 6;
>>> +
>>> +	case CX25840_PAD_GPO1:
>>> +		return 7;
>>> +
>>> +	case CX25840_PAD_GPO2:
>>> +		return 8;
>>> +
>>> +	case CX25840_PAD_GPO3:
>>> +		return 9;
>>> +
>>> +	case CX25840_PAD_IRQ_N:
>>> +		return 10;
>>> +
>>> +	case CX25840_PAD_AC_SYNC:
>>> +		return 11;
>>> +
>>> +	case CX25840_PAD_AC_SDOUT:
>>> +		return 12;
>>> +
>>> +	case CX25840_PAD_PLL_CLK:
>>> +		return 13;
>>> +
>>> +	case CX25840_PAD_VRESET:
>>> +		return 14;
>>> +
>>> +	default:
>>> +		if (function != CX25840_PAD_DEFAULT)
>>> +			v4l_err(client,
>>> +				"invalid function %u, assuming default\n",
>>> +				(unsigned int)function);
>>> +		return 0;
>>> +	}
>>
>> Unless I am mistaken this function boils down to:
>>
>> static u8 cx25840_function_to_pad(struct i2c_client *client, u8 function)
>> {
>> 	return function > CX25840_PAD_VRESET ? 0 : function;
>> }
> 
> Yes, you are right these functions are equivalent (sans a warning when
> a caller passes an invalid function).
> 
> However, these values (CX25840_PAD_*) were meant to be driver-internal.
> If we use them also as register value constants (which is what
> cx25840_function_to_pad() is supposed to return) then we'll need to add
> a comment to their enum cx25840_io_pad so nobody shuffles them or changes
> their values by mistake.

Right. Just add a comment and keep it simple.

> 
>>> @@ -1647,6 +1924,119 @@ static void log_audio_status(struct i2c_client *client)
>>>  	}
>>>  }
>>>  
>>> +#define CX25840_VCONFIG_OPTION(state, cfg_in, opt_msk)			\
>>> +	do {								\
>>> +		if ((cfg_in) & (opt_msk)) {				\
>>> +			(state)->vid_config &= ~(opt_msk);		\
>>> +			(state)->vid_config |= (cfg_in) & (opt_msk);	\
>>> +		}							\
>>> +	} while (0)
>>> +
>>> +#define CX25840_VCONFIG_SET_BIT(state, opt_msk, voc, idx, bit, oneval)	\
>>> +	do {								\
>>> +		if ((state)->vid_config & (opt_msk)) {			\
>>> +			if (((state)->vid_config & (opt_msk)) ==	\
>>> +			    (oneval))					\
>>> +				(voc)[idx] |= BIT(bit);		\
>>> +			else						\
>>> +				(voc)[idx] &= ~BIT(bit);		\
>>> +		}							\
>>> +	} while (0)
>>> +
>>> +int cx25840_vconfig(struct i2c_client *client, u32 cfg_in)
>>> +{
>>> +	struct cx25840_state *state = to_state(i2c_get_clientdata(client));
>>> +	u8 voutctrl[3];
>>> +	unsigned int i;
>>> +
>>> +	/* apply incoming options to the current state */
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_FMT_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_RES_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VBIRAW_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ANCDATA_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_TASKBIT_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_ACTIVE_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VALID_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_HRESETW_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_CLKGATE_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_DCMODE_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_IDID0S_MASK);
>>> +	CX25840_VCONFIG_OPTION(state, cfg_in, CX25840_VCONFIG_VIPCLAMP_MASK);
>>
>> This appears to be a very complex way of saying:
>>
>> 	state->vid_config = cfg_in;
> 
> This is supposed to change in vid_config only these options that are set
> in the incoming cfg_in, leaving the rest as-is.
> 
> If the code simply assigns cfg_in to vid_config it will also clear all the
> existing options.

Ah yes, but see my reply below at the end.

> 
>>
>>> +
>>> +	for (i = 0; i < 3; i++)
>>> +		voutctrl[i] = cx25840_read(client, 0x404 + i);
>>> +
>>> +	/* apply state to hardware regs */
>>> +	if (state->vid_config & CX25840_VCONFIG_FMT_MASK)
>>> +		voutctrl[0] &= ~3;
>>> +	switch (state->vid_config & CX25840_VCONFIG_FMT_MASK) {
>>> +	case CX25840_VCONFIG_FMT_BT656:
>>> +		voutctrl[0] |= 1;
>>> +		break;
>>> +
>>> +	case CX25840_VCONFIG_FMT_VIP11:
>>> +		voutctrl[0] |= 2;
>>> +		break;
>>> +
>>> +	case CX25840_VCONFIG_FMT_VIP2:
>>> +		voutctrl[0] |= 3;
>>> +		break;
>>> +
>>> +	case CX25840_VCONFIG_FMT_BT601:
>>> +		/* zero */
>>> +	default:
>>> +		break;
>>> +	}
>>> +
>>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_RES_MASK, voutctrl,
>>> +				0, 2, CX25840_VCONFIG_RES_10BIT);
>>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VBIRAW_MASK, voutctrl,
>>> +				0, 3, CX25840_VCONFIG_VBIRAW_ENABLED);
>>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ANCDATA_MASK, voutctrl,
>>> +				0, 4, CX25840_VCONFIG_ANCDATA_ENABLED);
>>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_TASKBIT_MASK, voutctrl,
>>> +				0, 5, CX25840_VCONFIG_TASKBIT_ONE);
>>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_ACTIVE_MASK, voutctrl,
>>> +				1, 2, CX25840_VCONFIG_ACTIVE_HORIZONTAL);
>>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VALID_MASK, voutctrl,
>>> +				1, 3, CX25840_VCONFIG_VALID_ANDACTIVE);
>>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_HRESETW_MASK, voutctrl,
>>> +				1, 4, CX25840_VCONFIG_HRESETW_PIXCLK);
>>> +
>>> +	if (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK)
>>> +		voutctrl[1] &= ~(3 << 6);
>>> +	switch (state->vid_config & CX25840_VCONFIG_CLKGATE_MASK) {
>>> +	case CX25840_VCONFIG_CLKGATE_VALID:
>>> +		voutctrl[1] |= 2;
>>> +		break;
>>> +
>>> +	case CX25840_VCONFIG_CLKGATE_VALIDACTIVE:
>>> +		voutctrl[1] |= 3;
>>> +		break;
>>> +
>>> +	case CX25840_VCONFIG_CLKGATE_NONE:
>>> +		/* zero */
>>> +	default:
>>> +		break;
>>> +	}
>>> +
>>> +
>>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_DCMODE_MASK, voutctrl,
>>> +				2, 0, CX25840_VCONFIG_DCMODE_BYTES);
>>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_IDID0S_MASK, voutctrl,
>>> +				2, 1, CX25840_VCONFIG_IDID0S_LINECNT);
>>> +	CX25840_VCONFIG_SET_BIT(state, CX25840_VCONFIG_VIPCLAMP_MASK, voutctrl,
>>> +				2, 4, CX25840_VCONFIG_VIPCLAMP_ENABLED);
>>> +
>>> +	for (i = 0; i < 3; i++)
>>> +		cx25840_write(client, 0x404 + i, voutctrl[i]);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +#undef CX25840_VCONFIG_SET_BIT
>>> +#undef CX25840_VCONFIG_OPTION
>>
>> Why #undef? You would normally never do that.
> 
> The idea here is to catch (unintended) other users of these macros, other
> than cx25840_vconfig() and to not pollute the macro namespace.
> But these #undefs can be removed if they are against the coding style.

Please remove. You do not normally use #undef unless you are redefining a
macro.

> 
>>
>>> +
>>>  /* ----------------------------------------------------------------------- */
>>>  
>>>  /* This load_fw operation must be called to load the driver's firmware.
>>> @@ -1836,6 +2226,9 @@ static int cx25840_s_video_routing(struct v4l2_subdev *sd,
>>>  	if (is_cx23888(state))
>>>  		cx23888_std_setup(client);
>>>  
>>> +	if (is_cx2584x(state) && state->generic_mode)
>>> +		cx25840_vconfig(client, config);
>>> +
>>
>> You do the vconfig configuration when the video routing changes. But isn't this
>> configuration a one-time thing? E.g. something you do only when initializing the
>> board?
>>
>> At least in the cxusb code the cfg_in value is constant and not dependent on what
>> input is chosen.
>>
>> If this is true, then you should add the core init op instead. And as a bonus you
>> can turn on generic_mode if the init op is called instead of having to add it
>> to the platform data.
>>
> 
> The problem here is that the Medion MD95700 has two modes:
> digital (DVB-T) and analog.
> When it is operating in the digital mode the device analog components
> (including the cx25840 chip) have their power cut by the hardware.
> 
> This means that the cx25840 has to be fully reinitialized (firmware
> loaded, format set, etc.) every time the user opens the Medion's v4l2
> video or radio device node if the device has previously operated in the
> DVB-T mode.
> Mode switching is supported transparently by the driver without needing
> to reload the module or reconnect the device.

So? You call the core init op to initialize vconfig (just pass in all the
bits to simplify things), and whenever s_video_routing it called the driver
will just call cx25840_vconfig() with the vconfig value set by the core init op.

As far as I can see all you want to do is to specify a specific vconfig value
that should be applied whenever s_video_routing is called.

So in cxusb_medion_analog_init() you call the core init op, then the
video s_routing op to switch to COMPOSITE1.

I like that much better since vconfig doesn't change when you switch inputs.
It's only needed when you initialize the analog part.

Regards,

	Hans

> 
>>
>> Regards,
>>
>> 	Hans
>>
> 
> Thanks and best regards,
> Maciej
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v7 6/6] [media] cxusb: add analog mode support for Medion MD95700
  2018-07-10 21:48     ` Maciej S. Szmigiero
@ 2018-07-17 12:56       ` Hans Verkuil
  0 siblings, 0 replies; 14+ messages in thread
From: Hans Verkuil @ 2018-07-17 12:56 UTC (permalink / raw)
  To: Maciej S. Szmigiero
  Cc: Michael Krufky, Mauro Carvalho Chehab, Andy Walls, linux-kernel,
	linux-media

On 10/07/18 23:48, Maciej S. Szmigiero wrote:
> Hi Hans,
> 
> On 04.07.2018 11:33, Hans Verkuil wrote:
>> Hi Maciej,
>>
>> On 02/07/18 23:23, Maciej S. Szmigiero wrote:
> (..)
>>> +static int cxusb_medion_v_queue_setup(struct vb2_queue *q,
>>> +				      unsigned int *num_buffers,
>>> +				      unsigned int *num_planes,
>>> +				      unsigned int sizes[],
>>> +				      struct device *alloc_devs[])
>>> +{
>>> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
>>> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> +	unsigned int size = cxdev->raw_mode ?
>>> +		CXUSB_VIDEO_MAX_FRAME_SIZE :
>>> +		cxdev->width * cxdev->height * 2;
>>> +
>>> +	if (*num_planes > 0) {
>>> +		if (*num_planes != 1)
>>> +			return -EINVAL;
>>> +
>>> +		if (sizes[0] < size)
>>> +			return -EINVAL;
>>> +	} else {
>>> +		*num_planes = 1;
>>> +		sizes[0] = size;
>>> +	}
>>> +
>>> +	if (q->num_buffers + *num_buffers < 6)
>>> +		*num_buffers = 6 - q->num_buffers;
>>
>> Huh? These two lines should be removed. I'm not sure what the purpose is.
> 
> This is to request that the vb2 framework keeps at least 6 buffers in the
> queue.
> 
> There is a similar code in usb/usbtv/usbtv-video.c:
>> if (vq->num_buffers + *nbuffers < 2)
>>                 *nbuffers = 2 - vq->num_buffers;
> 
> And in usb/hackrf/hackrf.c and usb/airspy/airspy.c:
>> /* Need at least 8 buffers */
>> if (vq->num_buffers + *nbuffers < 8)
>> 	*nbuffers = 8 - vq->num_buffers;
> 
> In addition to this, many drivers (like stk1160, s2255, pwc, msi2500,
> go7007, ...) always enforce some minimum *num_buffers count, regardless
> of the number of buffers currently in the queue.

Yeah, that's old code. These days you set the min_buffers_needed field of
struct vb2_queue and vb2 will take care of this itself.

Please set that field instead.

> 
>>> +static void cxusb_auxbuf_init(struct cxusb_medion_auxbuf *auxbuf,
>>> +			      u8 *buf, unsigned int len)
>>> +{
>>> +	auxbuf->buf = buf;
>>> +	auxbuf->len = len;
>>> +	auxbuf->paylen = 0;
>>> +}
>>> +
>>> +static void cxusb_auxbuf_head_trim(struct dvb_usb_device *dvbdev,
>>> +				   struct cxusb_medion_auxbuf *auxbuf,
>>> +				   unsigned int pos)
>>> +{
>>> +	if (pos == 0)
>>> +		return;
>>> +
>>> +	if (WARN_ON(pos > auxbuf->paylen))
>>> +		return;
>>> +
>>> +	cxusb_vprintk(dvbdev, AUXB,
>>> +		      "trimming auxbuf len by %u to %u\n",
>>> +		      pos, auxbuf->paylen - pos);
>>> +
>>> +	memmove(auxbuf->buf, auxbuf->buf + pos, auxbuf->paylen - pos);
>>> +	auxbuf->paylen -= pos;
>>> +}
>>> +
>>> +static unsigned int cxusb_auxbuf_paylen(struct cxusb_medion_auxbuf *auxbuf)
>>> +{
>>> +	return auxbuf->paylen;
>>> +}
>>> +
>>> +static bool cxusb_auxbuf_make_space(struct dvb_usb_device *dvbdev,
>>> +				    struct cxusb_medion_auxbuf *auxbuf,
>>> +				    unsigned int howmuch)
>>> +{
>>> +	unsigned int freespace;
>>> +
>>> +	if (WARN_ON(howmuch >= auxbuf->len))
>>> +		howmuch = auxbuf->len - 1;
>>> +
>>> +	freespace = auxbuf->len - cxusb_auxbuf_paylen(auxbuf);
>>> +
>>> +	cxusb_vprintk(dvbdev, AUXB, "freespace is %u\n", freespace);
>>> +
>>> +	if (freespace >= howmuch)
>>> +		return true;
>>> +
>>> +	howmuch -= freespace;
>>> +
>>> +	cxusb_vprintk(dvbdev, AUXB, "will overwrite %u bytes of buffer\n",
>>> +		      howmuch);
>>> +
>>> +	cxusb_auxbuf_head_trim(dvbdev, auxbuf, howmuch);
>>> +
>>> +	return false;
>>> +}
>>> +
>>> +/* returns false if some data was overwritten */
>>> +static bool cxusb_auxbuf_append_urb(struct dvb_usb_device *dvbdev,
>>> +				    struct cxusb_medion_auxbuf *auxbuf,
>>> +				    struct urb *urb)
>>> +{
>>> +	unsigned long len = 0;
>>> +	int i;
>>> +	bool ret;
>>> +
>>> +	for (i = 0; i < urb->number_of_packets; i++)
>>> +		len += urb->iso_frame_desc[i].actual_length;
>>> +
>>> +	ret = cxusb_auxbuf_make_space(dvbdev, auxbuf, len);
>>> +
>>> +	for (i = 0; i < urb->number_of_packets; i++) {
>>> +		unsigned int to_copy;
>>> +
>>> +		to_copy = urb->iso_frame_desc[i].actual_length;
>>> +
>>> +		memcpy(auxbuf->buf + auxbuf->paylen, urb->transfer_buffer +
>>> +		       urb->iso_frame_desc[i].offset, to_copy);
>>> +
>>> +		auxbuf->paylen += to_copy;
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static bool cxusb_auxbuf_copy(struct cxusb_medion_auxbuf *auxbuf,
>>> +			      unsigned int pos, unsigned char *dest,
>>> +			      unsigned int len)
>>> +{
>>> +	if (pos + len > auxbuf->paylen)
>>> +		return false;
>>> +
>>> +	memcpy(dest, auxbuf->buf + pos, len);
>>> +
>>> +	return true;
>>> +}
>>> +
>>> +static unsigned int cxusb_auxbuf_advance(struct cxusb_medion_auxbuf *auxbuf,
>>> +					 unsigned int pos,
>>> +					 unsigned int increment)
>>> +{
>>> +	return pos + increment;
>>> +}
>>> +
>>> +static unsigned int cxusb_auxbuf_begin(struct cxusb_medion_auxbuf *auxbuf)
>>> +{
>>> +	return 0;
>>> +}
>>> +
>>> +static bool cxusb_auxbuf_isend(struct cxusb_medion_auxbuf *auxbuf,
>>> +			       unsigned int pos)
>>> +{
>>> +	return pos >= auxbuf->paylen;
>>> +}
>>
>> These three functions seem pointless to me.
> 
> I will remove cxusb_auxbuf_begin().
> 
> cxusb_auxbuf_advance() is called 6 times in the code while
> cxusb_auxbuf_isend() keeps the buffer implementation opaque for users so
> I think they make the code a bit nicer.

Why would you want to keep these one-liners opaque? It doesn't help me
understand the code. Please don't do this.

> 
>>> +static void cxusb_medion_v_process_urb_raw_mode(struct cxusb_medion_dev *cxdev,
>>> +						struct urb *urb)
>>> +{
>>> +	struct dvb_usb_device *dvbdev = cxdev->dvbdev;
>>> +	u8 *buf;
>>> +	struct cxusb_medion_vbuffer *vbuf;
>>> +	int i;
>>> +	unsigned long len = 0;
>>> +
>>> +	if (list_empty(&cxdev->buflist)) {
>>> +		dev_warn(&dvbdev->udev->dev, "no free buffers\n");
>>> +		return;
>>> +	}
>>> +
>>> +	vbuf = list_first_entry(&cxdev->buflist, struct cxusb_medion_vbuffer,
>>> +				list);
>>> +	list_del(&vbuf->list);
>>> +
>>> +	vbuf->vb2.timestamp = ktime_get_ns();
>>> +
>>> +	buf = vb2_plane_vaddr(&vbuf->vb2, 0);
>>> +
>>> +	for (i = 0; i < urb->number_of_packets; i++) {
>>> +		memcpy(buf, urb->transfer_buffer +
>>> +		       urb->iso_frame_desc[i].offset,
>>> +		       urb->iso_frame_desc[i].actual_length);
>>> +
>>> +		buf += urb->iso_frame_desc[i].actual_length;
>>> +		len += urb->iso_frame_desc[i].actual_length;
>>> +	}
>>> +
>>> +	vb2_set_plane_payload(&vbuf->vb2, 0, len);
>>> +
>>> +	vb2_buffer_done(&vbuf->vb2, VB2_BUF_STATE_DONE);
>>
>> The frame sequence counter in vb2_v4l2_buffer does not seem to be incremented.
>>
>> Did you test this driver with v4l2-compliance? It will detect such errors.
> 
> Hmm, I did test this driver with v4l2-compliance last year and don't
> remember seeing any error about the frame sequence counter.
> 
> When I'll be doing a respin I will check this again and see whether
> v4l2-compliance reports anything (it seems it should).
> 
>>> +static int cxusb_medion_v_start_streaming(struct vb2_queue *q,
>>> +					  unsigned int count)
>>> +{
>>> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
>>> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> +	u8 streamon_params[2] = { 0x03, 0x00 };
>>> +	int npackets, i;
>>> +	int ret;
>>> +
>>> +	cxusb_vprintk(dvbdev, OPS, "should start streaming\n");
>>> +
>>> +	/* already streaming */
>>> +	if (cxdev->streaming)
>>> +		return 0;
>>
>> Unnecessary check, this can't happen.
>>
>>> +
>>> +	if (cxusb_medion_stream_busy(cxdev)) {
>>> +		ret = -EBUSY;
>>> +		goto ret_retbufs;
>>> +	}
>>> +
>>> +	ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 1);
>>> +	if (ret != 0) {
>>> +		dev_err(&dvbdev->udev->dev,
>>> +			"unable to start stream (%d)\n", ret);
>>> +		goto ret_retbufs;
>>> +	}
>>> +
>>> +	ret = cxusb_ctrl_msg(dvbdev, CMD_STREAMING_ON, streamon_params, 2,
>>> +			     NULL, 0);
>>> +	if (ret != 0) {
>>> +		dev_err(&dvbdev->udev->dev,
>>> +			"unable to start streaming (%d)\n", ret);
>>> +		goto ret_unstream_cx;
>>> +	}
>>> +
>>> +	if (cxdev->raw_mode)
>>> +		npackets = CXUSB_VIDEO_MAX_FRAME_PKTS;
>>> +	else {
>>> +		u8 *buf;
>>> +		unsigned int urblen, auxbuflen;
>>> +
>>> +		/* has to be less than full frame size */
>>> +		urblen = (cxdev->width * 2 + 4 + 4) * cxdev->height;
>>> +		npackets = urblen / CXUSB_VIDEO_PKT_SIZE;
>>> +		urblen = npackets * CXUSB_VIDEO_PKT_SIZE;
>>> +
>>> +		auxbuflen = (cxdev->width * 2 + 4 + 4) *
>>> +			(cxdev->height + 50 /* VBI lines */) + urblen;
>>> +
>>> +		buf = vmalloc(auxbuflen);
>>> +		if (buf == NULL) {
>>> +			ret = -ENOMEM;
>>> +			goto ret_unstream_md;
>>> +		}
>>> +
>>> +		cxusb_auxbuf_init(&cxdev->auxbuf, buf, auxbuflen);
>>> +	}
>>> +
>>> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++) {
>>> +		int framen;
>>> +		u8 *streambuf;
>>> +		struct urb *surb;
>>> +
>>> +		streambuf = kmalloc(npackets * CXUSB_VIDEO_PKT_SIZE,
>>> +				    GFP_KERNEL);
>>> +		if (streambuf == NULL) {
>>> +			if (i == 0) {
>>> +				ret = -ENOMEM;
>>> +				goto ret_freeab;
>>> +			} else
>>> +				break;
>>> +		}
>>> +
>>> +		surb = usb_alloc_urb(npackets, GFP_KERNEL);
>>> +		if (surb == NULL) {
>>> +			kfree(streambuf);
>>> +			ret = -ENOMEM;
>>> +			goto ret_freeu;
>>> +		}
>>> +
>>> +		cxdev->streamurbs[i] = surb;
>>> +		surb->dev = dvbdev->udev;
>>> +		surb->context = dvbdev;
>>> +		surb->pipe = usb_rcvisocpipe(dvbdev->udev, 2);
>>> +
>>> +		surb->interval = 1;
>>> +		surb->transfer_flags = URB_ISO_ASAP;
>>> +
>>> +		surb->transfer_buffer = streambuf;
>>> +
>>> +		surb->complete = cxusb_medion_v_complete;
>>> +		surb->number_of_packets = npackets;
>>> +		surb->transfer_buffer_length = npackets * CXUSB_VIDEO_PKT_SIZE;
>>> +
>>> +		for (framen = 0; framen < npackets; framen++) {
>>> +			surb->iso_frame_desc[framen].offset =
>>> +				CXUSB_VIDEO_PKT_SIZE * framen;
>>> +
>>> +			surb->iso_frame_desc[framen].length =
>>> +				CXUSB_VIDEO_PKT_SIZE;
>>> +		}
>>> +	}
>>> +
>>> +	cxdev->urbcomplete = 0;
>>> +	cxdev->nexturb = 0;
>>> +	cxdev->vbuf = NULL;
>>> +	cxdev->bt656.mode = NEW_FRAME;
>>> +	cxdev->bt656.buf = NULL;
>>> +
>>> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>>> +		if (cxdev->streamurbs[i] != NULL) {
>>> +			ret = usb_submit_urb(cxdev->streamurbs[i],
>>> +					GFP_KERNEL);
>>> +			if (ret != 0)
>>> +				dev_err(&dvbdev->udev->dev,
>>> +					"URB %d submission failed (%d)\n", i,
>>> +					ret);
>>> +		}
>>> +
>>> +	cxdev->streaming = true;
>>
>> No need to keep track of the streaming state. You can use vb2_start_streaming_called()
>> instead since vb2 already keeps track of this.
> 
> The driver still needs some flag to tell its workqueue task to not
> process and resubmit URBs when the stream is being stopped.
> 
> Otherwise, as the device lock gets released to flush the workqueue task
> there will be a time window where this task could still resubmit URBs
> (not knowing that we are stopping the stream).
> And we need to have the URBs killed before we flush the workqueue since
> their completion routine can schedule the workqueue task.
> 
> There seems to be no vb2 specific flag for this:
> q->start_streaming_called is zeroed only after stop_streaming callback
> has already returned.
> And we need to have URBs killed and the workqueue task flushed before
> we return from stop_streaming callback so we can clean up their
> resources in this callback.

Ah, it's used in cxusb_medion_v_complete_work. I think it would be better
to set a 'stop_streaming' flag that cxusb_medion_v_complete_work() looks
at, rather than this 'streaming' flag.

There should not be any URBs in flight before start_streaming is called, and
there should not be any URBs in flight when stop_streaming exists.

cxusb_medion_stream_busy() is very dubious as well. It should never return
true when you call it in start_streaming, and in the other cases it is called
it adds nothing to vb2_is_busy().

> 
>>> +
>>> +	return 0;
>>> +
>>> +ret_freeu:
>>> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>>> +		if (cxdev->streamurbs[i] != NULL) {
>>> +			kfree(cxdev->streamurbs[i]->transfer_buffer);
>>> +			usb_free_urb(cxdev->streamurbs[i]);
>>> +			cxdev->streamurbs[i] = NULL;
>>> +		}
>>> +
>>> +ret_freeab:
>>> +	if (!cxdev->raw_mode)
>>> +		vfree(cxdev->auxbuf.buf);
>>> +
>>> +ret_unstream_md:
>>> +	cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
>>> +
>>> +ret_unstream_cx:
>>> +	v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
>>> +
>>> +ret_retbufs:
>>> +	cxusb_medion_return_buffers(cxdev, true);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static void cxusb_medion_v_stop_streaming(struct vb2_queue *q)
>>> +{
>>> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
>>> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> +	int i, ret;
>>> +
>>> +	cxusb_vprintk(dvbdev, OPS, "should stop streaming\n");
>>> +
>>> +	if (!cxdev->streaming)
>>> +		return;
>>> +
>>> +	cxdev->streaming = false;
>>> +
>>> +	cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
>>> +
>>> +	ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
>>> +	if (ret != 0)
>>> +		dev_err(&dvbdev->udev->dev, "unable to stop stream (%d)\n",
>>> +			ret);
>>> +
>>> +	/* let URB completion run */
>>> +	mutex_unlock(cxdev->videodev->lock);
>>> +
>>> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>>> +		if (cxdev->streamurbs[i] != NULL)
>>> +			usb_kill_urb(cxdev->streamurbs[i]);
>>> +
>>> +	flush_work(&cxdev->urbwork);
>>> +
>>> +	mutex_lock(cxdev->videodev->lock);
>>> +
>>> +	/* free transfer buffer and URB */
>>> +	if (!cxdev->raw_mode)
>>> +		vfree(cxdev->auxbuf.buf);
>>> +
>>> +	for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>>> +		if (cxdev->streamurbs[i] != NULL) {
>>> +			kfree(cxdev->streamurbs[i]->transfer_buffer);
>>> +			usb_free_urb(cxdev->streamurbs[i]);
>>> +			cxdev->streamurbs[i] = NULL;
>>> +		}
>>> +
>>> +	cxusb_medion_return_buffers(cxdev, false);
>>> +}
>>> +
>>> +static void cxusub_medion_v_buf_queue(struct vb2_buffer *vb)
>>> +{
>>> +	struct cxusb_medion_vbuffer *vbuf =
>>> +		container_of(vb, struct cxusb_medion_vbuffer, vb2);
>>> +	struct dvb_usb_device *dvbdev = vb2_get_drv_priv(vb->vb2_queue);
>>> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> +
>>> +	/* cxusb_vprintk(dvbdev, OPS, "mmmm.. fresh buffer...\n"); */
>>> +
>>> +	list_add_tail(&vbuf->list, &cxdev->buflist);
>>
>> I would expect a spinlock here to protect the list.
> 
> All buffer list accesses are done either while holding main 
> lock or in vb2 callbacks (which accoring to
> https://lwn.net/Articles/447435/ can assume that the lock has been
> taken).

Ah, nothing runs in interrupt context in this driver. Got it.

>  
>>> +static int cxusb_medion_g_fmt_vid_cap(struct file *file, void *fh,
>>> +				      struct v4l2_format *f)
>>> +{
>>> +	struct dvb_usb_device *dvbdev = video_drvdata(file);
>>> +	struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> +
>>> +	f->fmt.pix.width = cxdev->width;
>>> +	f->fmt.pix.height = cxdev->height;
>>> +	f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
>>> +	f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
>>> +	f->fmt.pix.bytesperline = cxdev->raw_mode ? 0 : cxdev->width * 2;
>>
>> Dunno what raw_mode is, but it looks suspicous.
> 
> It is simply an ability to capture a raw BT.656 stream as received by
> the driver from the device.
> This functionality is very useful for debugging.

Please drop raw_mode support. Feel free to keep it as a separate patch for
your own debugging, but this isn't the way it should be done if you want
to get this mainlined. Since it is only used for debugging, I don't think
it is worth the effort.

>> I stopped reviewing halfway because I was wondering whether this is the right approach.
>>
>> Most of the analog support is not actually tied to the medion but is fairly generic.
>> This is how other drivers do that as well: the implementation is generic and the board
>> specific bits are implemented via card structures.
>>
>> They also make use of v4l2_device_call_all() instead of calling each subdev directly,
>> this means that the board-specific code loads the correct subdevs, but the generic
>> code doesn't need to know (in general) what those are, it just calls all of them.
> 
> There are only three subdevices here: a RF tuner, TDA9887 demod and
> cx25840 chip.
> Most of the subdev calls are for c25840 only, some (like g_tuner) should
> be done in a proper order so returned details aren't overwritten by less
> specific data being returned from the next subdevice.
> The RF tuner and the demod are target of just a few of the subdev calls.
> 
> v4l2_device_call_all() ignores any errors, which makes for example trying

If you need to intercept errors, then you can use v4l2_device_call_until_err().

> a video format hard (and which format will be accepted by the cx25840
> depends on the currently set broadcast standard and parameters of the
> last signal that was received), while V4L2 docs say that
> VIDIOC_{S,TRY}_FMT ioctls "should not return an error code unless the
> type field is invalid", that is, they should not return an error for
> invalid or unsupported image widths or heights.
> They should instead return something sensible for these image parameters
> which requires a feedback from the cx25840 subdev which one it will
> finally accept.
> 
> With respect to making the code more generic: considering this is a
> 13-year old hardware I think it is fairly unlikely that there are going
> to be devices with similar internal design released in the future.
> And in the unlikely case that this indeed happens then the code can
> always be refactored into some more generic framework.

That's a fair point.

I would like to see some comments that explain that if we ever need to
support other analog hardware, then this code should be made generic.

Regards,

	Hans

> 
> This driver has been alredy nearly merged back in 2012 (even without last
> year cleanups), just I wasn't able to find time to rebase it then:
> https://www.mail-archive.com/linux-media@vger.kernel.org/msg50449.html
> so it isn't something completely new.
> 
>> Regards,
>>
>> 	Hans
>>
> 
> Thanks and best regards,
> Maciej
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2018-07-17 12:56 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-07-02 21:23 [PATCH v7 0/6] [media] Add analog mode support for Medion MD95700 Maciej S. Szmigiero
2018-07-02 21:23 ` [PATCH v7 1/6] ivtv: zero-initialize cx25840 platform data Maciej S. Szmigiero
2018-07-02 21:23 ` [PATCH v7 2/6] cx25840: add kernel-doc description of struct cx25840_state Maciej S. Szmigiero
2018-07-02 21:23 ` [PATCH v7 3/6] cx25840: add pin to pad mapping and output format configuration Maciej S. Szmigiero
2018-07-04  9:05   ` Hans Verkuil
2018-07-10 21:47     ` Maciej S. Szmigiero
2018-07-17 12:29       ` Hans Verkuil
2018-07-02 21:23 ` [PATCH v7 4/6] tuner-simple: allow setting mono radio mode Maciej S. Szmigiero
2018-07-02 21:23 ` [PATCH v7 5/6] [media] cxusb: implement Medion MD95700 digital / analog coexistence Maciej S. Szmigiero
2018-07-02 21:23 ` [PATCH v7 6/6] [media] cxusb: add analog mode support for Medion MD95700 Maciej S. Szmigiero
2018-07-04  9:33   ` Hans Verkuil
2018-07-10 21:48     ` Maciej S. Szmigiero
2018-07-17 12:56       ` Hans Verkuil
2018-07-04  9:33 ` [PATCH v7 0/6] [media] Add " Hans Verkuil

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).