2019-04-29 16:19:42

by Maciej S. Szmigiero

[permalink] [raw]
Subject: [PATCH v12 0/8] [media] Add analog mode support for Medion MD95700

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.

Changes from v7:
* Simplify cx25840_function_to_pad() assuming that the values of
individual members of I/O pad enum will always match the actual
chip register values,

* Add a comment that the above is now a requirement to the I/O pad enum,

* Don't #undef macros in the cx25840 patch,

* Change "generic_mode" variable type in struct cx25840_state to bool,

* Prefix todo-style comments added by the cx25840 patch with "TODO:",

* Add a core init op to the cx25840 driver and use it to turn the
generic_mode on instead of doing it via the platform data,

* Use the above core init op to also provide a vconfig configuration
to the cx25840 driver so the cxusb driver does not have to pass this
(constant) configuration there on every video input change,

* Remove cxusb_auxbuf_advance(), cxusb_auxbuf_begin() and
cxusb_auxbuf_isend(),

* Add a missing frame sequence counter support,

* Test the driver with v4l2-compliance with streaming tests enabled,

* Set device_caps in struct video_device during probe instead of returning
it manually in vidioc_querycap handler,

* Don't set the format name in vidioc_enum_fmt_vid_cap handler,

* Drop various memset() zeroing in ioctl handlers, as suggested during
last review,

* Enforce minimum buffer count via in_buffers_needed field of
struct vb2_queue instead of doing it in queue_setup callback,

* Rewrite stream starting / stopping / configuration mechanism using a
'stop_streaming' flag, remove cxusb_medion_stream_busy(),

* Set the lock field in struct vb2_queue so vb2_ops_wait_prepare() /
vb2_ops_wait_finish() helpers can be utilized,

* Split out raw (unprocessed) BT.656 stream capturing support to a
separate patch, adapt it to a recent change introduced by
commit 8a7c5594c020 ("v4l2-ioctl: clear fields in s_parm") that forced
extendedmode set by VIDIOC_S_PARM to zero,

* Add a comment that if we ever need to support analog mode in similar
hardware then this code should be made generic,

* Remove a requirement that URB length has to be less than full frame
size,

* Limit URB length to avoid very high order memory allocations,

* Remove out-of-order incoming URB support: this can't really happen,

* Small cleanups here and there.

Changes from v8:
Rebase onto current media_tree/master.

Changes from v9:
* Elaborate generic mode comments in cx25840 and cxusb drivers,
add datasheet references to cx25840 chip video output settings,

* Describe the new DVB_USB_ADAP_STREAMING_CTRL_NO_URB flag,

* Drop likely and unlikely annotations in cxusb driver from the patches,

* Exclusively use strscpy (and not strncpy, strcpy, etc.) in the patches,

* Drop zeroing of various v4l fields which should be zeroed by the
core v4l code,

* Adapt VIDIOC_S_FMT handler so the video format can't be changed if
buffers are allocated,

* Add VB2_DMABUF to the list of supported I/O modes.

Changes from v10:
* Add a commit cleaning up cx25840_load_fw() and its comments
(no functional change intended),

* Add a commit renaming cx25840 g_std operation to querystd operation
since this is what this handler does,

* Add a commit that clamps out-of-range picture sizes in cx25840
set_fmt subdev operation so it conforms better to the V4L2 API,

* cx25840 driver generic mode (enabled by calling its init op) is currently
only implemented for cx2584x-family chips.
Add a relevant comment and a warning to catch unintentional uses,

* Program cx25840 driver video timings in the generic mode to the same
values that the chip autoconfig would set.

While improving the set_fmt subdev operation it became obvious that
the video timings that cx25840 driver was setting resulted in a strange
BT.656 PAL stream that had two active video areas per field (one with
just a single line) and 580 active lines total.
An output format with 580 active lines total meant that the most common
PAL capture line count of 576 needed a non-identity scaling factor.

This is the reason why in the generic mode the driver now uses the same
values that the chip autoconfig would set (read from the chip).
These give a nice, BT.656-compliant PAL stream with 576 lines and one
active video area per field.

cxusb raw mode, as provided by the last commit, is very useful for
diagnosing these issues as it gives access to a raw BT.656 stream
from the cx25840 chip where one can see line count for each field, see
where VBI lines lie, which lines actually contain samples (as it is
possible to have some active video lines without any samples at all and
some VBI lines with normal line samples - cx25840 can move a VBI marker
independently of a "start capturing samples" marker and, in fact, can have
a slightly different VBI / active video divider between the two fields in
a frame),

* Remove the dvb-usb framework DVB_USB_ADAP_STREAMING_CTRL_NO_URB flag
and always assume its behaviour (always make sure that data URBs are
not active before calling a driver streaming_ctrl callback),

* Initialize cxusb driver to PAL mode instead of trying to autodetect
the standard,

* Change videodev->tvnorms and the currently set standard when switching
inputs so VIDIOC_ENUMSTD and VIDIOC_G_STD correctly return a limited
standard set for TV tuner input,

* Implement VIDIOC_QUERYSTD operation,

* Make sure to return bottom-top field order for 525-line signal
captures.

Changes from v11:
Add g_std operation to cx25840 driver and use it instead of querystd
operation when determining field order in cxusb driver.

drivers/media/i2c/cx25840/cx25840-core.c | 554 +++++-
drivers/media/i2c/cx25840/cx25840-core.h | 15 +
drivers/media/i2c/cx25840/cx25840-vbi.c | 4 +
drivers/media/usb/dvb-usb/Kconfig | 16 +-
drivers/media/usb/dvb-usb/Makefile | 3 +
drivers/media/usb/dvb-usb/cxusb-analog.c | 1974 ++++++++++++++++++++++
drivers/media/usb/dvb-usb/cxusb.c | 451 ++++-
drivers/media/usb/dvb-usb/cxusb.h | 158 ++
drivers/media/usb/dvb-usb/dvb-usb-dvb.c | 5 +-
drivers/media/usb/dvb-usb/dvb-usb-init.c | 13 +
drivers/media/usb/dvb-usb/dvb-usb.h | 10 +
drivers/media/v4l2-core/v4l2-ioctl.c | 3 +-
include/media/drv-intf/cx25840.h | 105 +-
13 files changed, 3176 insertions(+), 135 deletions(-)
create mode 100644 drivers/media/usb/dvb-usb/cxusb-analog.c


2019-04-29 16:19:49

by Maciej S. Szmigiero

[permalink] [raw]
Subject: [PATCH v12 8/8] [media] cxusb: add raw mode support for Medion MD95700

This adds raw (unprocessed) BT.656 stream capturing support for the analog
part of Medion 95700.
It can be enabled by setting CXUSB_EXTENDEDMODE_CAPTURE_RAW flag in
parm.capture.extendedmode passed to VIDIOC_S_PARM.

Signed-off-by: Maciej S. Szmigiero <[email protected]>
---
drivers/media/usb/dvb-usb/cxusb-analog.c | 190 +++++++++++++++++++----
drivers/media/usb/dvb-usb/cxusb.h | 4 +
drivers/media/v4l2-core/v4l2-ioctl.c | 3 +-
3 files changed, 163 insertions(+), 34 deletions(-)

diff --git a/drivers/media/usb/dvb-usb/cxusb-analog.c b/drivers/media/usb/dvb-usb/cxusb-analog.c
index 395c257aa237..aed491481c51 100644
--- a/drivers/media/usb/dvb-usb/cxusb-analog.c
+++ b/drivers/media/usb/dvb-usb/cxusb-analog.c
@@ -44,7 +44,9 @@ static int cxusb_medion_v_queue_setup(struct vb2_queue *q,
{
struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
struct cxusb_medion_dev *cxdev = dvbdev->priv;
- unsigned int size = cxdev->width * cxdev->height * 2;
+ unsigned int size = cxdev->raw_mode ?
+ CXUSB_VIDEO_MAX_FRAME_SIZE :
+ cxdev->width * cxdev->height * 2;

if (*num_planes > 0) {
if (*num_planes != 1)
@@ -67,8 +69,13 @@ static int cxusb_medion_v_buf_init(struct vb2_buffer *vb)

cxusb_vprintk(dvbdev, OPS, "buffer init\n");

- if (vb2_plane_size(vb, 0) < cxdev->width * cxdev->height * 2)
- return -ENOMEM;
+ 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");

@@ -442,6 +449,45 @@ static bool cxusb_medion_copy_field(struct dvb_usb_device *dvbdev,
return true;
}

+static void cxusb_medion_v_process_urb_raw(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;
+
+ if (list_empty(&cxdev->buflist)) {
+ dev_warn(&dvbdev->udev->dev, "no free buffers\n");
+ cxdev->vbuf_sequence++;
+ return;
+ }
+
+ vbuf = list_first_entry(&cxdev->buflist, struct cxusb_medion_vbuffer,
+ list);
+ list_del(&vbuf->list);
+
+ vbuf->vb2.field = V4L2_FIELD_NONE;
+ vbuf->vb2.sequence = cxdev->vbuf_sequence++;
+ vbuf->vb2.vb2_buf.timestamp = ktime_get_ns();
+
+ buf = vb2_plane_vaddr(&vbuf->vb2.vb2_buf, 0);
+
+ for (i = 0, len = 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.vb2_buf, 0, len);
+
+ vb2_buffer_done(&vbuf->vb2.vb2_buf, VB2_BUF_STATE_DONE);
+}
+
static bool cxusb_medion_v_process_auxbuf(struct cxusb_medion_dev *cxdev,
bool reset)
{
@@ -566,22 +612,26 @@ static bool cxusb_medion_v_complete_handle_urb(struct cxusb_medion_dev *cxdev,
len);

if (len > 0) {
- cxusb_vprintk(dvbdev, URB, "appending URB\n");
-
- /*
- * append new data to auxbuf while
- * overwriting old data if necessary
- *
- * if any overwrite happens then we can no
- * longer rely on consistency of the whole
- * data so let's start again the current
- * auxbuf frame assembling process from
- * the beginning
- */
- *auxbuf_reset =
- !cxusb_auxbuf_append_urb(dvbdev,
- &cxdev->auxbuf,
- urb);
+ if (cxdev->raw_mode)
+ cxusb_medion_v_process_urb_raw(cxdev, urb);
+ else {
+ cxusb_vprintk(dvbdev, URB, "appending URB\n");
+
+ /*
+ * append new data to auxbuf while
+ * overwriting old data if necessary
+ *
+ * if any overwrite happens then we can no
+ * longer rely on consistency of the whole
+ * data so let's start again the current
+ * auxbuf frame assembling process from
+ * the beginning
+ */
+ *auxbuf_reset =
+ !cxusb_auxbuf_append_urb(dvbdev,
+ &cxdev->auxbuf,
+ urb);
+ }
}
}

@@ -616,7 +666,8 @@ static void cxusb_medion_v_complete_work(struct work_struct *work)

reschedule = cxusb_medion_v_complete_handle_urb(cxdev, &auxbuf_reset);

- if (cxusb_medion_v_process_auxbuf(cxdev, auxbuf_reset))
+ if (!cxdev->raw_mode && cxusb_medion_v_process_auxbuf(cxdev,
+ auxbuf_reset))
/* reschedule us until auxbuf no longer can produce any frame */
reschedule = true;

@@ -805,9 +856,13 @@ static int cxusb_medion_v_start_streaming(struct vb2_queue *q,
goto ret_unstream_cx;
}

- ret = cxusb_medion_v_ss_auxbuf_alloc(cxdev, &npackets);
- if (ret != 0)
- goto ret_unstream_md;
+ if (cxdev->raw_mode)
+ npackets = CXUSB_VIDEO_MAX_FRAME_PKTS;
+ else {
+ ret = cxusb_medion_v_ss_auxbuf_alloc(cxdev, &npackets);
+ if (ret != 0)
+ goto ret_unstream_md;
+ }

for (i = 0; i < CXUSB_VIDEO_URBS; i++) {
int framen;
@@ -863,9 +918,11 @@ static int cxusb_medion_v_start_streaming(struct vb2_queue *q,
cxdev->nexturb = 0;
cxdev->vbuf_sequence = 0;

- cxdev->vbuf = NULL;
- cxdev->bt656.mode = NEW_FRAME;
- cxdev->bt656.buf = NULL;
+ if (!cxdev->raw_mode) {
+ 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) {
@@ -883,7 +940,8 @@ static int cxusb_medion_v_start_streaming(struct vb2_queue *q,
cxusb_medion_urbs_free(cxdev);

ret_freeab:
- vfree(cxdev->auxbuf.buf);
+ if (!cxdev->raw_mode)
+ vfree(cxdev->auxbuf.buf);

ret_unstream_md:
cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
@@ -930,7 +988,8 @@ static void cxusb_medion_v_stop_streaming(struct vb2_queue *q)
mutex_lock(cxdev->videodev->lock);

/* free transfer buffer and URB */
- vfree(cxdev->auxbuf.buf);
+ if (!cxdev->raw_mode)
+ vfree(cxdev->auxbuf.buf);

cxusb_medion_urbs_free(cxdev);

@@ -1003,9 +1062,11 @@ static int cxusb_medion_g_fmt_vid_cap(struct file *file, void *fh,
f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
f->fmt.pix.field = vb2_start_streaming_called(&cxdev->videoqueue) ?
cxdev->field_order : cxusb_medion_field_order(cxdev);
- f->fmt.pix.bytesperline = cxdev->width * 2;
+ f->fmt.pix.bytesperline = cxdev->raw_mode ? 0 : cxdev->width * 2;
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
- f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
+ f->fmt.pix.sizeimage =
+ cxdev->raw_mode ? CXUSB_VIDEO_MAX_FRAME_SIZE :
+ f->fmt.pix.bytesperline * f->fmt.pix.height;

return 0;
}
@@ -1043,8 +1104,10 @@ static int cxusb_medion_try_s_fmt_vid_cap(struct file *file,
f->fmt.pix.height = subfmt.format.height;
f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
f->fmt.pix.field = field;
- f->fmt.pix.bytesperline = f->fmt.pix.width * 2;
- f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
+ 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;

if (isset) {
@@ -1400,6 +1463,67 @@ static int cxusb_medion_querystd(struct file *file, void *fh,
return 0;
}

+static int cxusb_medion_g_s_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *param)
+{
+ v4l2_std_id std;
+
+ if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ param->parm.capture.readbuffers = 2;
+
+ if (cxusb_medion_g_std(file, fh, &std) == 0)
+ v4l2_video_std_frame_period(std,
+ &param->parm.capture.timeperframe);
+
+ 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;
+ int ret;
+
+ ret = cxusb_medion_g_s_parm(file, fh, param);
+ if (ret != 0)
+ return ret;
+
+ if (cxdev->raw_mode)
+ param->parm.capture.extendedmode |=
+ CXUSB_EXTENDEDMODE_CAPTURE_RAW;
+
+ 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;
+ int ret;
+ bool want_raw;
+
+ ret = cxusb_medion_g_s_parm(file, fh, param);
+ if (ret != 0)
+ return ret;
+
+ want_raw = param->parm.capture.extendedmode &
+ CXUSB_EXTENDEDMODE_CAPTURE_RAW;
+
+ if (want_raw != cxdev->raw_mode) {
+ if (vb2_start_streaming_called(&cxdev->videoqueue) ||
+ cxdev->stop_streaming)
+ return -EBUSY;
+
+ cxdev->raw_mode = want_raw;
+ }
+
+ return 0;
+}
+
static int cxusb_medion_log_status(struct file *file, void *fh)
{
struct dvb_usb_device *dvbdev = video_drvdata(file);
@@ -1419,6 +1543,8 @@ static const struct v4l2_ioctl_ops cxusb_video_ioctl = {
.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,
diff --git a/drivers/media/usb/dvb-usb/cxusb.h b/drivers/media/usb/dvb-usb/cxusb.h
index 9636f9575e58..35e72f571a2c 100644
--- a/drivers/media/usb/dvb-usb/cxusb.h
+++ b/drivers/media/usb/dvb-usb/cxusb.h
@@ -131,6 +131,7 @@ struct cxusb_medion_dev {
bool stop_streaming;
u32 width, height;
u32 field_order;
+ bool raw_mode;
struct cxusb_medion_auxbuf auxbuf;
v4l2_std_id norm;

@@ -154,6 +155,9 @@ struct cxusb_medion_vbuffer {
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)
diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index ac87c3e37280..0b9ca5acdd35 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -2023,7 +2023,7 @@ static int v4l_s_parm(const struct v4l2_ioctl_ops *ops,
if (ret)
return ret;

- /* Note: extendedmode is never used in drivers */
+ /* Note: extendedmode is never used in output drivers */
if (V4L2_TYPE_IS_OUTPUT(p->type)) {
memset(p->parm.output.reserved, 0,
sizeof(p->parm.output.reserved));
@@ -2032,7 +2032,6 @@ static int v4l_s_parm(const struct v4l2_ioctl_ops *ops,
} else {
memset(p->parm.capture.reserved, 0,
sizeof(p->parm.capture.reserved));
- p->parm.capture.extendedmode = 0;
p->parm.capture.capturemode &= V4L2_MODE_HIGHQUALITY;
}
return ops->vidioc_s_parm(file, fh, p);

2019-04-29 16:19:50

by Maciej S. Szmigiero

[permalink] [raw]
Subject: [PATCH v12 2/8] cx25840: g_std operation really implements querystd operation

cx25840 driver g_std operation queries the currently detected video signal,
however this is what querystd operation should do, so let's rename the
handler.

None of the existing cx25840 driver users ever called the g_std operation,
one of them calls querystd on each of its subdevs but then the result is
only used to implement VIDIOC_QUERYSTD (as it should).

Signed-off-by: Maciej S. Szmigiero <[email protected]>
---
drivers/media/i2c/cx25840/cx25840-core.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c
index 0bf30222cf93..2bcaf239b0d2 100644
--- a/drivers/media/i2c/cx25840/cx25840-core.c
+++ b/drivers/media/i2c/cx25840/cx25840-core.c
@@ -1772,7 +1772,7 @@ static int cx25840_s_stream(struct v4l2_subdev *sd, int enable)
}

/* Query the current detected video format */
-static int cx25840_g_std(struct v4l2_subdev *sd, v4l2_std_id *std)
+static int cx25840_querystd(struct v4l2_subdev *sd, v4l2_std_id *std)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);

@@ -1800,8 +1800,9 @@ static int cx25840_g_std(struct v4l2_subdev *sd, v4l2_std_id *std)
u32 fmt = (cx25840_read4(client, 0x40c) >> 8) & 0xf;
*std = stds[ fmt ];

- v4l_dbg(1, cx25840_debug, client, "g_std fmt = %x, v4l2_std_id = 0x%x\n",
- fmt, (unsigned int)stds[ fmt ]);
+ v4l_dbg(1, cx25840_debug, client,
+ "querystd fmt = %x, v4l2_std_id = 0x%x\n",
+ fmt, (unsigned int)stds[fmt]);

return 0;
}
@@ -5081,7 +5082,7 @@ static const struct v4l2_subdev_audio_ops cx25840_audio_ops = {

static const struct v4l2_subdev_video_ops cx25840_video_ops = {
.s_std = cx25840_s_std,
- .g_std = cx25840_g_std,
+ .querystd = cx25840_querystd,
.s_routing = cx25840_s_video_routing,
.s_stream = cx25840_s_stream,
.g_input_status = cx25840_g_input_status,

2019-04-29 16:20:00

by Maciej S. Szmigiero

[permalink] [raw]
Subject: [PATCH v12 7/8] [media] cxusb: add analog mode support for Medion MD95700

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.

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

Signed-off-by: Maciej S. Szmigiero <[email protected]>
---
drivers/media/usb/dvb-usb/Kconfig | 16 +-
drivers/media/usb/dvb-usb/Makefile | 3 +
drivers/media/usb/dvb-usb/cxusb-analog.c | 1848 ++++++++++++++++++++++
drivers/media/usb/dvb-usb/cxusb.c | 2 -
drivers/media/usb/dvb-usb/cxusb.h | 106 ++
5 files changed, 1971 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 513df955eaa3..8af8b920d7e4 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 407d90ca8be0..28e4806a87cd 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..395c257aa237
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/cxusb-analog.c
@@ -0,0 +1,1848 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// DVB USB compliant linux driver for Conexant USB reference design -
+// (analog part).
+//
+// Copyright (C) 2011, 2017, 2018
+// Maciej S. Szmigiero ([email protected])
+//
+// In case there are new analog / DVB-T hybrid devices released in the market
+// using the same general design as Medion MD95700: a CX25840 video decoder
+// outputting a BT.656 stream to a USB bridge chip which then forwards it to
+// the host in isochronous USB packets this code should be made generic, with
+// board specific bits implemented via separate card structures.
+//
+// This is, however, unlikely as the Medion model was released
+// years ago (in 2005).
+//
+// 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-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->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;
+ }
+
+ 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 (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 dvb_usb_device *dvbdev,
+ struct cxusb_medion_auxbuf *auxbuf,
+ u8 *buf, unsigned int len)
+{
+ cxusb_vprintk(dvbdev, AUXB, "initializing auxbuf of len %u\n", 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;
+ int i;
+ bool ret;
+
+ for (i = 0, len = 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 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 void cxusb_medion_cf_refc_start_sch(struct dvb_usb_device *dvbdev,
+ 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 (!sav_code || firstfield != firstfield_code)
+ return;
+
+ if (!vbi_code) {
+ cxusb_vprintk(dvbdev, BT656, "line start @ pos %u\n",
+ bt656->pos);
+
+ bt656->linesamples = 0;
+ bt656->fmode = LINE_SAMPLES;
+ } else {
+ cxusb_vprintk(dvbdev, BT656, "VBI start @ pos %u\n",
+ bt656->pos);
+
+ bt656->fmode = VBI_SAMPLES;
+ }
+}
+
+static void 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 (sav_code)
+ cxusb_vprintk(dvbdev, BT656,
+ "SAV in line samples @ line %u, pos %u\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++;
+}
+
+static void 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 (sav_code)
+ cxusb_vprintk(dvbdev, BT656, "SAV in VBI samples @ pos %u\n",
+ bt656->pos);
+
+ bt656->fmode = START_SEARCH;
+}
+
+/* returns whether the whole 4-byte code should be skipped in the buffer */
+static bool cxusb_medion_cf_ref_code(struct dvb_usb_device *dvbdev,
+ struct cxusb_bt656_params *bt656,
+ bool firstfield,
+ unsigned int maxlines,
+ unsigned int maxlinesamples,
+ unsigned char buf[4])
+{
+ if (bt656->fmode == START_SEARCH)
+ cxusb_medion_cf_refc_start_sch(dvbdev, bt656, firstfield, buf);
+ else if (bt656->fmode == LINE_SAMPLES) {
+ cxusb_medion_cf_refc_line_smpl(dvbdev, bt656, firstfield,
+ maxlinesamples, buf);
+ return false;
+ } else if (bt656->fmode == VBI_SAMPLES) {
+ cxusb_medion_cf_refc_vbi_smpl(dvbdev, bt656, buf);
+ return false;
+ }
+
+ return true;
+}
+
+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 (!cxusb_auxbuf_copy(auxbuf, bt656->pos + 1, buf, tocheck))
+ return false;
+
+ for (idx = 0; idx <= tocheck - 3; idx++)
+ if (memcmp(buf + idx, CXUSB_BT656_PREAMBLE, 3) == 0) {
+ bt656->pos += (1 + idx);
+ return true;
+ }
+
+ cxusb_vprintk(dvbdev, BT656, "line %u early start, pos %u\n",
+ bt656->line, bt656->pos);
+
+ bt656->linesamples = 0;
+ bt656->fmode = LINE_SAMPLES;
+
+ return true;
+}
+
+static void cxusb_medion_cs_line_smpl(struct cxusb_bt656_params *bt656,
+ unsigned int maxlinesamples,
+ unsigned char val)
+{
+ if (bt656->buf != NULL)
+ *(bt656->buf++) = val;
+
+ bt656->linesamples++;
+ bt656->pos++;
+
+ if (bt656->linesamples >= maxlinesamples) {
+ bt656->fmode = START_SEARCH;
+ bt656->line++;
+ }
+}
+
+static bool 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 (bt656->fmode == START_SEARCH && bt656->line > 0)
+ return cxusb_medion_cs_start_sch(dvbdev, auxbuf, bt656,
+ maxlinesamples);
+ else if (bt656->fmode == LINE_SAMPLES)
+ cxusb_medion_cs_line_smpl(bt656, maxlinesamples, val);
+ else /* TODO: copy VBI samples */
+ bt656->pos++;
+
+ return true;
+}
+
+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) {
+ unsigned char val;
+
+ if (!cxusb_auxbuf_copy(auxbuf, bt656->pos, &val, 1))
+ break;
+
+ if (val == CXUSB_BT656_PREAMBLE[0]) {
+ unsigned char buf[4];
+
+ buf[0] = val;
+ if (!cxusb_auxbuf_copy(auxbuf, bt656->pos + 1,
+ buf + 1, 3))
+ break;
+
+ if (buf[1] == CXUSB_BT656_PREAMBLE[1] &&
+ buf[2] == CXUSB_BT656_PREAMBLE[2]) {
+ /*
+ * is this a field change?
+ * if so, terminate copying the current field
+ */
+ if (cxusb_medion_cf_refc_fld_chg(dvbdev,
+ bt656,
+ firstfield,
+ maxlines,
+ maxlinesmpls,
+ buf))
+ return true;
+
+ if (cxusb_medion_cf_ref_code(dvbdev, bt656,
+ firstfield,
+ maxlines,
+ maxlinesmpls,
+ buf))
+ bt656->pos += 4;
+
+ continue;
+ }
+ }
+
+ if (!cxusb_medion_copy_samples(dvbdev, auxbuf, bt656,
+ maxlinesmpls, val))
+ break;
+ }
+
+ 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 bool cxusb_medion_v_process_auxbuf(struct cxusb_medion_dev *cxdev,
+ bool reset)
+{
+ struct dvb_usb_device *dvbdev = cxdev->dvbdev;
+ struct cxusb_bt656_params *bt656 = &cxdev->bt656;
+
+ /*
+ * 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);
+ } else
+ dev_warn(&dvbdev->udev->dev, "no free buffers\n");
+ }
+
+ if (bt656->mode == NEW_FRAME || reset) {
+ cxusb_vprintk(dvbdev, URB, "will copy field 1\n");
+ bt656->pos = 0;
+ bt656->mode = FIRST_FIELD;
+ bt656->fmode = START_SEARCH;
+ bt656->line = 0;
+
+ if (cxdev->vbuf != NULL) {
+ cxdev->vbuf->vb2.vb2_buf.timestamp = ktime_get_ns();
+ bt656->buf = vb2_plane_vaddr(&cxdev->vbuf->vb2.vb2_buf,
+ 0);
+ }
+ }
+
+ /* cxusb_vprintk(dvbdev, URB, "auxbuf payload len %u",
+ cxusb_auxbuf_paylen(&cxdev->auxbuf)); */
+
+ if (bt656->mode == FIRST_FIELD) {
+ if (!cxusb_medion_copy_field(dvbdev, &cxdev->auxbuf, bt656,
+ true, cxdev->height / 2,
+ cxdev->width * 2))
+ return false;
+
+ /*
+ * do not trim buffer there in case
+ * we need to reset the search later
+ */
+
+ cxusb_vprintk(dvbdev, URB, "will copy field 2\n");
+ bt656->mode = SECOND_FIELD;
+ bt656->fmode = START_SEARCH;
+ bt656->line = 0;
+ }
+
+ if (bt656->mode == SECOND_FIELD) {
+ if (!cxusb_medion_copy_field(dvbdev, &cxdev->auxbuf, bt656,
+ false, cxdev->height / 2,
+ cxdev->width * 2))
+ return false;
+
+ cxusb_auxbuf_head_trim(dvbdev, &cxdev->auxbuf, bt656->pos);
+
+ bt656->mode = NEW_FRAME;
+
+ if (cxdev->vbuf != NULL) {
+ vb2_set_plane_payload(&cxdev->vbuf->vb2.vb2_buf, 0,
+ cxdev->width * cxdev->height * 2);
+
+ cxdev->vbuf->vb2.field = cxdev->field_order;
+ cxdev->vbuf->vb2.sequence = cxdev->vbuf_sequence++;
+
+ vb2_buffer_done(&cxdev->vbuf->vb2.vb2_buf,
+ 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");
+ cxdev->vbuf_sequence++;
+ }
+ }
+
+ return true;
+}
+
+static bool cxusb_medion_v_complete_handle_urb(struct cxusb_medion_dev *cxdev,
+ bool *auxbuf_reset)
+{
+ struct dvb_usb_device *dvbdev = cxdev->dvbdev;
+ unsigned int urbn;
+ struct urb *urb;
+ int ret;
+
+ *auxbuf_reset = false;
+
+ urbn = cxdev->nexturb;
+ if (!test_bit(urbn, &cxdev->urbcomplete))
+ return false;
+
+ clear_bit(urbn, &cxdev->urbcomplete);
+
+ do {
+ cxdev->nexturb++;
+ cxdev->nexturb %= CXUSB_VIDEO_URBS;
+ urb = cxdev->streamurbs[cxdev->nexturb];
+ } while (urb == NULL);
+
+ urb = cxdev->streamurbs[urbn];
+ cxusb_vprintk(dvbdev, URB, "URB %u status = %d\n", urbn, urb->status);
+
+ if (urb->status == 0 || urb->status == -EXDEV) {
+ int i;
+ unsigned long len;
+
+ for (i = 0, len = 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) {
+ cxusb_vprintk(dvbdev, URB, "appending URB\n");
+
+ /*
+ * append new data to auxbuf while
+ * overwriting old data if necessary
+ *
+ * if any overwrite happens then we can no
+ * longer rely on consistency of the whole
+ * data so let's start again the current
+ * auxbuf frame assembling process from
+ * the beginning
+ */
+ *auxbuf_reset =
+ !cxusb_auxbuf_append_urb(dvbdev,
+ &cxdev->auxbuf,
+ urb);
+ }
+ }
+
+ cxusb_vprintk(dvbdev, URB, "URB %u resubmit\n", urbn);
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret != 0)
+ dev_err(&dvbdev->udev->dev,
+ "unable to resubmit URB %u (%d), you'll have to restart streaming\n",
+ urbn, ret);
+
+ /* next URB is complete already? reschedule us then to handle it */
+ return test_bit(cxdev->nexturb, &cxdev->urbcomplete);
+}
+
+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;
+ bool auxbuf_reset;
+ bool reschedule;
+
+ mutex_lock(cxdev->videodev->lock);
+
+ cxusb_vprintk(dvbdev, URB, "worker called, stop_streaming = %d\n",
+ (int)cxdev->stop_streaming);
+
+ if (cxdev->stop_streaming)
+ goto unlock;
+
+ reschedule = cxusb_medion_v_complete_handle_urb(cxdev, &auxbuf_reset);
+
+ if (cxusb_medion_v_process_auxbuf(cxdev, auxbuf_reset))
+ /* reschedule us until auxbuf no longer can produce any frame */
+ reschedule = true;
+
+ if (reschedule) {
+ cxusb_vprintk(dvbdev, URB, "rescheduling worker\n");
+ schedule_work(&cxdev->urbwork);
+ }
+
+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 %u complete\n", i);
+
+ set_bit(i, &cxdev->urbcomplete);
+ schedule_work(&cxdev->urbwork);
+}
+
+static void cxusb_medion_urbs_free(struct cxusb_medion_dev *cxdev)
+{
+ unsigned int i;
+
+ 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;
+ }
+}
+
+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.vb2_buf,
+ requeue ? VB2_BUF_STATE_QUEUED :
+ VB2_BUF_STATE_ERROR);
+ }
+
+ if (cxdev->vbuf != NULL) {
+ vb2_buffer_done(&cxdev->vbuf->vb2.vb2_buf,
+ requeue ? VB2_BUF_STATE_QUEUED :
+ VB2_BUF_STATE_ERROR);
+
+ cxdev->vbuf = NULL;
+ cxdev->bt656.buf = NULL;
+ }
+}
+
+static int cxusb_medion_v_ss_auxbuf_alloc(struct cxusb_medion_dev *cxdev,
+ int *npackets)
+{
+ struct dvb_usb_device *dvbdev = cxdev->dvbdev;
+ u8 *buf;
+ unsigned int framelen, urblen, auxbuflen;
+
+ framelen = (cxdev->width * 2 + 4 + 4) *
+ (cxdev->height + 50 /* VBI lines */);
+
+ /*
+ * try to fit a whole frame into each URB, as long as doing so
+ * does not require very high order memory allocations
+ */
+ BUILD_BUG_ON(CXUSB_VIDEO_URB_MAX_SIZE / CXUSB_VIDEO_PKT_SIZE >
+ CXUSB_VIDEO_MAX_FRAME_PKTS);
+ *npackets = min_t(int, (framelen + CXUSB_VIDEO_PKT_SIZE - 1) /
+ CXUSB_VIDEO_PKT_SIZE,
+ CXUSB_VIDEO_URB_MAX_SIZE / CXUSB_VIDEO_PKT_SIZE);
+ urblen = *npackets * CXUSB_VIDEO_PKT_SIZE;
+
+ cxusb_vprintk(dvbdev, URB,
+ "each URB will have %d packets for total of %u bytes (%u x %u @ %u)\n",
+ *npackets, urblen, (unsigned int)cxdev->width,
+ (unsigned int)cxdev->height, framelen);
+
+ auxbuflen = framelen + urblen;
+
+ buf = vmalloc(auxbuflen);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ cxusb_auxbuf_init(dvbdev, &cxdev->auxbuf, buf, auxbuflen);
+
+ return 0;
+}
+
+static u32 cxusb_medion_norm2field_order(v4l2_std_id norm)
+{
+ bool is625 = norm & V4L2_STD_625_50;
+ bool is525 = norm & V4L2_STD_525_60;
+
+ if (!is625 && !is525)
+ return V4L2_FIELD_NONE;
+
+ if (is625 && is525)
+ return V4L2_FIELD_NONE;
+
+ if (is625)
+ return V4L2_FIELD_SEQ_TB;
+ else /* is525 */
+ return V4L2_FIELD_SEQ_BT;
+}
+
+static u32 cxusb_medion_field_order(struct cxusb_medion_dev *cxdev)
+{
+ struct dvb_usb_device *dvbdev = cxdev->dvbdev;
+ u32 field;
+ int ret;
+ v4l2_std_id norm;
+
+ /* TV tuner is PAL-only so it is always TB */
+ if (cxdev->input == 0)
+ return V4L2_FIELD_SEQ_TB;
+
+ field = cxusb_medion_norm2field_order(cxdev->norm);
+ if (field != V4L2_FIELD_NONE)
+ return field;
+
+ ret = v4l2_subdev_call(cxdev->cx25840, video, g_std, &norm);
+ if (ret != 0)
+ cxusb_vprintk(dvbdev, OPS,
+ "cannot get current standard for input %u\n",
+ (unsigned int)cxdev->input);
+ else {
+ field = cxusb_medion_norm2field_order(norm);
+ if (field != V4L2_FIELD_NONE)
+ return field;
+ }
+
+ dev_warn(&dvbdev->udev->dev,
+ "cannot determine field order for the current standard setup and received signal, using TB\n");
+ return V4L2_FIELD_SEQ_TB;
+}
+
+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");
+
+ if (cxdev->stop_streaming) {
+ /* stream is being stopped */
+ ret = -EBUSY;
+ goto ret_retbufs;
+ }
+
+ cxdev->field_order = cxusb_medion_field_order(cxdev);
+
+ 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;
+ }
+
+ ret = cxusb_medion_v_ss_auxbuf_alloc(cxdev, &npackets);
+ if (ret != 0)
+ goto ret_unstream_md;
+
+ for (i = 0; i < CXUSB_VIDEO_URBS; i++) {
+ int framen;
+ u8 *streambuf;
+ struct urb *surb;
+
+ /*
+ * TODO: change this to an array of single pages to avoid
+ * doing a large continuous allocation when (if)
+ * s-g isochronous USB transfers are supported
+ */
+ streambuf = kmalloc(npackets * CXUSB_VIDEO_PKT_SIZE,
+ GFP_KERNEL);
+ if (streambuf == NULL) {
+ if (i < 2) {
+ 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_sequence = 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);
+ }
+
+ return 0;
+
+ret_freeu:
+ cxusb_medion_urbs_free(cxdev);
+
+ret_freeab:
+ 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 ret;
+ unsigned int i;
+
+ cxusb_vprintk(dvbdev, OPS, "should stop streaming\n");
+
+ if (WARN_ON(cxdev->stop_streaming))
+ return;
+
+ cxdev->stop_streaming = true;
+
+ 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 */
+ vfree(cxdev->auxbuf.buf);
+
+ cxusb_medion_urbs_free(cxdev);
+
+ cxusb_medion_return_buffers(cxdev, false);
+
+ cxdev->stop_streaming = false;
+}
+
+static void cxusub_medion_v_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *v4l2buf = to_vb2_v4l2_buffer(vb);
+ struct cxusb_medion_vbuffer *vbuf =
+ container_of(v4l2buf, 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.. a fresh buffer...\n"); */
+
+ list_add_tail(&vbuf->list, &cxdev->buflist);
+}
+
+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 = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish
+};
+
+static const __u32 videocaps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER |
+ V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+static const __u32 radiocaps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+
+static int cxusb_medion_v_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct dvb_usb_device *dvbdev = video_drvdata(file);
+
+ strscpy(cap->driver, dvbdev->udev->dev.driver->name,
+ sizeof(cap->driver));
+ strscpy(cap->card, "Medion 95700", sizeof(cap->card));
+ usb_make_path(dvbdev->udev, cap->bus_info, sizeof(cap->bus_info));
+
+ cap->capabilities = videocaps | radiocaps | V4L2_CAP_DEVICE_CAPS;
+
+ 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->pixelformat = V4L2_PIX_FMT_UYVY;
+
+ 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 = vb2_start_streaming_called(&cxdev->videoqueue) ?
+ cxdev->field_order : cxusb_medion_field_order(cxdev);
+ f->fmt.pix.bytesperline = cxdev->width * 2;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+ f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
+
+ 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;
+ u32 field;
+ int ret;
+
+ if (isset && vb2_is_busy(&cxdev->videoqueue))
+ return -EBUSY;
+
+ field = vb2_start_streaming_called(&cxdev->videoqueue) ?
+ cxdev->field_order : cxusb_medion_field_order(cxdev);
+
+ 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 = field;
+ subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ 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 = field;
+ f->fmt.pix.bytesperline = f->fmt.pix.width * 2;
+ f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ 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_set_norm(struct cxusb_medion_dev *cxdev,
+ v4l2_std_id norm)
+{
+ struct dvb_usb_device *dvbdev = cxdev->dvbdev;
+ int ret;
+
+ cxusb_vprintk(dvbdev, OPS,
+ "trying to set standard for input %u to %lx\n",
+ (unsigned int)cxdev->input,
+ (unsigned long)norm);
+
+ /* no autodetection support */
+ if (norm == V4L2_STD_UNKNOWN)
+ return -EINVAL;
+
+ /* 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;
+
+ 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_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;
+ v4l2_std_id norm;
+
+ 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;
+ cxdev->videodev->tvnorms = cxusb_medion_inputs[i].input.std;
+
+ norm = cxdev->norm & cxusb_medion_inputs[i].input.std;
+ if (norm == 0)
+ norm = cxusb_medion_inputs[i].input.std;
+
+ cxusb_medion_set_norm(cxdev, norm);
+
+ 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)
+ strscpy(tuner->name, "TV tuner", sizeof(tuner->name));
+ else
+ strscpy(tuner->name, "Radio tuner", sizeof(tuner->name));
+
+ 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;
+
+ *norm = cxdev->norm;
+
+ 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;
+
+ return cxusb_medion_set_norm(cxdev, norm);
+}
+
+static int cxusb_medion_querystd(struct file *file, void *fh,
+ v4l2_std_id *norm)
+{
+ struct dvb_usb_device *dvbdev = video_drvdata(file);
+ struct cxusb_medion_dev *cxdev = dvbdev->priv;
+ v4l2_std_id norm_mask;
+ int ret;
+
+ /*
+ * make sure we don't have improper std bits set for the TV tuner
+ * (could happen when no signal was present yet after reset)
+ */
+ if (cxdev->input == 0)
+ norm_mask = V4L2_STD_PAL;
+ else
+ norm_mask = V4L2_STD_ALL;
+
+ ret = v4l2_subdev_call(cxdev->cx25840, video, querystd, norm);
+ if (ret != 0) {
+ cxusb_vprintk(dvbdev, OPS,
+ "cannot get detected standard for input %u\n",
+ (unsigned int)cxdev->input);
+ return ret;
+ }
+
+ cxusb_vprintk(dvbdev, OPS, "input %u detected standard is %lx\n",
+ (unsigned int)cxdev->input, (unsigned long)*norm);
+ *norm &= norm_mask;
+
+ 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_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_querystd = cxusb_medion_querystd,
+ .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);
+
+ /*
+ * cx25840 might have lost power during mode switching so we need
+ * to set it again
+ */
+ ret = v4l2_subdev_call(cxdev->cx25840, core, reset, 0);
+ if (ret != 0)
+ dev_warn(&dvbdev->udev->dev,
+ "cx25840 reset failed (%d)\n", ret);
+
+ ret = v4l2_subdev_call(cxdev->cx25840, video, s_routing,
+ CX25840_COMPOSITE1, 0, 0);
+ if (ret != 0)
+ dev_warn(&dvbdev->udev->dev,
+ "cx25840 initial input setting failed (%d)\n", ret);
+
+ /* composite */
+ cxdev->input = 1;
+ cxdev->videodev->tvnorms = V4L2_STD_ALL;
+ cxdev->norm = V4L2_STD_PAL;
+
+ /* 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, cxdev->norm);
+ v4l2_subdev_call(cxdev->tuner, video, s_std, cxdev->norm);
+ 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)
+ 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 |
+ VB2_DMABUF;
+ 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;
+ cxdev->videoqueue.min_buffers_needed = 6;
+ cxdev->videoqueue.lock = &cxdev->dev_lock;
+
+ 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->device_caps = videocaps;
+ cxdev->videodev->fops = &cxusb_video_fops;
+ cxdev->videodev->v4l2_dev = &cxdev->v4l2dev;
+ cxdev->videodev->queue = &cxdev->videoqueue;
+ strscpy(cxdev->videodev->name, "cxusb", sizeof(cxdev->videodev->name));
+ 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->device_caps = radiocaps;
+ cxdev->radiodev->fops = &cxusb_radio_fops;
+ cxdev->radiodev->v4l2_dev = &cxdev->v4l2dev;
+ strscpy(cxdev->radiodev->name, "cxusb", sizeof(cxdev->radiodev->name));
+ 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;
+ int ret;
+ struct tuner_setup tun_setup;
+
+ /* attach cx25840 capture chip */
+ cxdev->cx25840 = v4l2_i2c_new_subdev(&cxdev->v4l2dev,
+ &dvbdev->i2c_adap,
+ "cx25840", 0x44, NULL);
+ if (cxdev->cx25840 == NULL) {
+ dev_err(&dvbdev->udev->dev, "cx25840 not found\n");
+ return -ENODEV;
+ }
+
+ /*
+ * Initialize cx25840 chip by calling its subdevice init core op.
+ *
+ * This switches it into the generic mode that disables some of
+ * ivtv-related hacks in the cx25840 driver while allowing setting
+ * of the chip video output configuration (passed in the call below
+ * as the last argument).
+ */
+ ret = v4l2_subdev_call(cxdev->cx25840, core, init,
+ 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_err(&dvbdev->udev->dev,
+ "cx25840 init failed (%d)\n", ret);
+ return ret;
+ }
+
+ /* 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 68b0543abbdc..8b754ea069bc 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 ([email protected])
* Copyright (C) 2006 Michael Krufky ([email protected])
@@ -2548,5 +2547,4 @@ MODULE_AUTHOR("Michael Krufky <[email protected]>");
MODULE_AUTHOR("Chris Pascoe <[email protected]>");
MODULE_AUTHOR("Maciej S. Szmigiero <[email protected]>");
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..9636f9575e58 100644
--- a/drivers/media/usb/dvb-usb/cxusb.h
+++ b/drivers/media/usb/dvb-usb/cxusb.h
@@ -2,12 +2,29 @@
#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>
+#include <media/videobuf2-v4l2.h>

#define DVB_USB_LOG_PREFIX "cxusb"
#include "dvb-usb.h"

+#define CXUSB_VIDEO_URBS (5)
+#define CXUSB_VIDEO_URB_MAX_SIZE (512 * 1024)
+
+#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 +49,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 +85,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 +117,69 @@ 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 stop_streaming;
+ u32 width, height;
+ u32 field_order;
+ 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;
+ __u32 vbuf_sequence;
+
+ struct list_head buflist;
+
+ struct completion v4l2_release;
+#endif
+};
+
+struct cxusb_medion_vbuffer {
+ struct vb2_v4l2_buffer vb2;
+ struct list_head list;
};

/* 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);

2020-06-03 14:36:22

by Lukas Straub

[permalink] [raw]
Subject: Re: [PATCH v12 0/8] [media] Add analog mode support for Medion MD95700

On Mon, 29 Apr 2019 18:16:51 +0200
"Maciej S. Szmigiero" <[email protected]> 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.
>
> Changes from v7:
> * Simplify cx25840_function_to_pad() assuming that the values of
> individual members of I/O pad enum will always match the actual
> chip register values,
>
> * Add a comment that the above is now a requirement to the I/O pad enum,
>
> * Don't #undef macros in the cx25840 patch,
>
> * Change "generic_mode" variable type in struct cx25840_state to bool,
>
> * Prefix todo-style comments added by the cx25840 patch with "TODO:",
>
> * Add a core init op to the cx25840 driver and use it to turn the
> generic_mode on instead of doing it via the platform data,
>
> * Use the above core init op to also provide a vconfig configuration
> to the cx25840 driver so the cxusb driver does not have to pass this
> (constant) configuration there on every video input change,
>
> * Remove cxusb_auxbuf_advance(), cxusb_auxbuf_begin() and
> cxusb_auxbuf_isend(),
>
> * Add a missing frame sequence counter support,
>
> * Test the driver with v4l2-compliance with streaming tests enabled,
>
> * Set device_caps in struct video_device during probe instead of returning
> it manually in vidioc_querycap handler,
>
> * Don't set the format name in vidioc_enum_fmt_vid_cap handler,
>
> * Drop various memset() zeroing in ioctl handlers, as suggested during
> last review,
>
> * Enforce minimum buffer count via in_buffers_needed field of
> struct vb2_queue instead of doing it in queue_setup callback,
>
> * Rewrite stream starting / stopping / configuration mechanism using a
> 'stop_streaming' flag, remove cxusb_medion_stream_busy(),
>
> * Set the lock field in struct vb2_queue so vb2_ops_wait_prepare() /
> vb2_ops_wait_finish() helpers can be utilized,
>
> * Split out raw (unprocessed) BT.656 stream capturing support to a
> separate patch, adapt it to a recent change introduced by
> commit 8a7c5594c020 ("v4l2-ioctl: clear fields in s_parm") that forced
> extendedmode set by VIDIOC_S_PARM to zero,
>
> * Add a comment that if we ever need to support analog mode in similar
> hardware then this code should be made generic,
>
> * Remove a requirement that URB length has to be less than full frame
> size,
>
> * Limit URB length to avoid very high order memory allocations,
>
> * Remove out-of-order incoming URB support: this can't really happen,
>
> * Small cleanups here and there.
>
> Changes from v8:
> Rebase onto current media_tree/master.
>
> Changes from v9:
> * Elaborate generic mode comments in cx25840 and cxusb drivers,
> add datasheet references to cx25840 chip video output settings,
>
> * Describe the new DVB_USB_ADAP_STREAMING_CTRL_NO_URB flag,
>
> * Drop likely and unlikely annotations in cxusb driver from the patches,
>
> * Exclusively use strscpy (and not strncpy, strcpy, etc.) in the patches,
>
> * Drop zeroing of various v4l fields which should be zeroed by the
> core v4l code,
>
> * Adapt VIDIOC_S_FMT handler so the video format can't be changed if
> buffers are allocated,
>
> * Add VB2_DMABUF to the list of supported I/O modes.
>
> Changes from v10:
> * Add a commit cleaning up cx25840_load_fw() and its comments
> (no functional change intended),
>
> * Add a commit renaming cx25840 g_std operation to querystd operation
> since this is what this handler does,
>
> * Add a commit that clamps out-of-range picture sizes in cx25840
> set_fmt subdev operation so it conforms better to the V4L2 API,
>
> * cx25840 driver generic mode (enabled by calling its init op) is currently
> only implemented for cx2584x-family chips.
> Add a relevant comment and a warning to catch unintentional uses,
>
> * Program cx25840 driver video timings in the generic mode to the same
> values that the chip autoconfig would set.
>
> While improving the set_fmt subdev operation it became obvious that
> the video timings that cx25840 driver was setting resulted in a strange
> BT.656 PAL stream that had two active video areas per field (one with
> just a single line) and 580 active lines total.
> An output format with 580 active lines total meant that the most common
> PAL capture line count of 576 needed a non-identity scaling factor.
>
> This is the reason why in the generic mode the driver now uses the same
> values that the chip autoconfig would set (read from the chip).
> These give a nice, BT.656-compliant PAL stream with 576 lines and one
> active video area per field.
>
> cxusb raw mode, as provided by the last commit, is very useful for
> diagnosing these issues as it gives access to a raw BT.656 stream
> from the cx25840 chip where one can see line count for each field, see
> where VBI lines lie, which lines actually contain samples (as it is
> possible to have some active video lines without any samples at all and
> some VBI lines with normal line samples - cx25840 can move a VBI marker
> independently of a "start capturing samples" marker and, in fact, can have
> a slightly different VBI / active video divider between the two fields in
> a frame),
>
> * Remove the dvb-usb framework DVB_USB_ADAP_STREAMING_CTRL_NO_URB flag
> and always assume its behaviour (always make sure that data URBs are
> not active before calling a driver streaming_ctrl callback),
>
> * Initialize cxusb driver to PAL mode instead of trying to autodetect
> the standard,
>
> * Change videodev->tvnorms and the currently set standard when switching
> inputs so VIDIOC_ENUMSTD and VIDIOC_G_STD correctly return a limited
> standard set for TV tuner input,
>
> * Implement VIDIOC_QUERYSTD operation,
>
> * Make sure to return bottom-top field order for 525-line signal
> captures.
>
> Changes from v11:
> Add g_std operation to cx25840 driver and use it instead of querystd
> operation when determining field order in cxusb driver.
>
> drivers/media/i2c/cx25840/cx25840-core.c | 554 +++++-
> drivers/media/i2c/cx25840/cx25840-core.h | 15 +
> drivers/media/i2c/cx25840/cx25840-vbi.c | 4 +
> drivers/media/usb/dvb-usb/Kconfig | 16 +-
> drivers/media/usb/dvb-usb/Makefile | 3 +
> drivers/media/usb/dvb-usb/cxusb-analog.c | 1974 ++++++++++++++++++++++
> drivers/media/usb/dvb-usb/cxusb.c | 451 ++++-
> drivers/media/usb/dvb-usb/cxusb.h | 158 ++
> drivers/media/usb/dvb-usb/dvb-usb-dvb.c | 5 +-
> drivers/media/usb/dvb-usb/dvb-usb-init.c | 13 +
> drivers/media/usb/dvb-usb/dvb-usb.h | 10 +
> drivers/media/v4l2-core/v4l2-ioctl.c | 3 +-
> include/media/drv-intf/cx25840.h | 105 +-
> 13 files changed, 3176 insertions(+), 135 deletions(-)
> create mode 100644 drivers/media/usb/dvb-usb/cxusb-analog.c

Hi,
Any status on this?

Regards,
Lukas Straub


Attachments:
(No filename) (849.00 B)
OpenPGP digital signature

2020-06-03 15:04:46

by Maciej S. Szmigiero

[permalink] [raw]
Subject: Re: [PATCH v12 0/8] [media] Add analog mode support for Medion MD95700

On 03.06.2020 16:33, Lukas Straub wrote:
> On Mon, 29 Apr 2019 18:16:51 +0200
> "Maciej S. Szmigiero" <[email protected]> wrote:
>
>> This series adds support for analog part of Medion 95700 in the cxusb
>> driver.
>>
(..)
> Hi,
> Any status on this?

It has been merged already a year ago.

> Regards,
> Lukas Straub
>

Maciej