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 mergable.
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.
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
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 <[email protected]>
---
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 ([email protected])
* Copyright (C) 2006 Michael Krufky ([email protected])
* Copyright (C) 2006, 2007 Chris Pascoe ([email protected])
+ * Copyright (C) 2011, 2017 Maciej S. Szmigiero ([email protected])
*
* 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 <[email protected]>");
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 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];
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 <[email protected]>
---
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)
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 <[email protected]>
---
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 ([email protected])
+//
+// 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(¶m->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 ([email protected])
* Copyright (C) 2006 Michael Krufky ([email protected])
@@ -2549,5 +2548,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..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);
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 <[email protected]>
---
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..7dc3d0870808 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 <[email protected]>
*
+ * CX2584x pin to pad mapping and output format configuration support are
+ * Copyright (C) 2011 Maciej S. Szmigiero <[email protected]>
+ *
* 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 & 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 &
+ 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 & 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 &
+ 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 & 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
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 <[email protected]>
---
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);
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 <[email protected]>
---
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];
Hi Maciej,
Thank you for the patch! Perhaps something to improve:
url: https://github.com/0day-ci/linux/commits/Maciej-S-Szmigiero/Add-analog-mode-support-for-Medion-MD95700/20180630-050341
base: git://linuxtv.org/media_tree.git master
New smatch warnings:
drivers/media/i2c/cx25840/cx25840-core.c:468 cx25840_s_io_pin_config() warn: bitwise AND condition is false here
Old smatch warnings:
drivers/media/i2c/cx25840/cx25840-core.c:497 cx25840_s_io_pin_config() warn: bitwise AND condition is false here
drivers/media/i2c/cx25840/cx25840-core.c:526 cx25840_s_io_pin_config() warn: bitwise AND condition is false here
# https://github.com/0day-ci/linux/commit/64372f380e540a77b73d4628a585c9c92956a7fd
git remote add linux-review https://github.com/0day-ci/linux
git remote update linux-review
git checkout 64372f380e540a77b73d4628a585c9c92956a7fd
vim +468 drivers/media/i2c/cx25840/cx25840-core.c
64372f38 Maciej S. Szmigiero 2018-06-29 435
64372f38 Maciej S. Szmigiero 2018-06-29 436 static int cx25840_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
64372f38 Maciej S. Szmigiero 2018-06-29 437 struct v4l2_subdev_io_pin_config *p)
64372f38 Maciej S. Szmigiero 2018-06-29 438 {
64372f38 Maciej S. Szmigiero 2018-06-29 439 struct i2c_client *client = v4l2_get_subdevdata(sd);
64372f38 Maciej S. Szmigiero 2018-06-29 440 unsigned int i;
64372f38 Maciej S. Szmigiero 2018-06-29 441 u8 pinctrl[6], pinconf[10], voutctrl4;
64372f38 Maciej S. Szmigiero 2018-06-29 442
64372f38 Maciej S. Szmigiero 2018-06-29 443 for (i = 0; i < 6; i++)
64372f38 Maciej S. Szmigiero 2018-06-29 444 pinctrl[i] = cx25840_read(client, 0x114 + i);
64372f38 Maciej S. Szmigiero 2018-06-29 445
64372f38 Maciej S. Szmigiero 2018-06-29 446 for (i = 0; i < 10; i++)
64372f38 Maciej S. Szmigiero 2018-06-29 447 pinconf[i] = cx25840_read(client, 0x11c + i);
64372f38 Maciej S. Szmigiero 2018-06-29 448
64372f38 Maciej S. Szmigiero 2018-06-29 449 voutctrl4 = cx25840_read(client, 0x407);
64372f38 Maciej S. Szmigiero 2018-06-29 450
64372f38 Maciej S. Szmigiero 2018-06-29 451 for (i = 0; i < n; i++) {
64372f38 Maciej S. Szmigiero 2018-06-29 452 u8 strength = p[i].strength;
64372f38 Maciej S. Szmigiero 2018-06-29 453
64372f38 Maciej S. Szmigiero 2018-06-29 454 if (strength != CX25840_PIN_DRIVE_SLOW &&
64372f38 Maciej S. Szmigiero 2018-06-29 455 strength != CX25840_PIN_DRIVE_MEDIUM &&
64372f38 Maciej S. Szmigiero 2018-06-29 456 strength != CX25840_PIN_DRIVE_FAST) {
64372f38 Maciej S. Szmigiero 2018-06-29 457
64372f38 Maciej S. Szmigiero 2018-06-29 458 v4l_err(client,
64372f38 Maciej S. Szmigiero 2018-06-29 459 "invalid drive speed for pin %u (%u), assuming fast\n",
64372f38 Maciej S. Szmigiero 2018-06-29 460 (unsigned int)p[i].pin,
64372f38 Maciej S. Szmigiero 2018-06-29 461 (unsigned int)strength);
64372f38 Maciej S. Szmigiero 2018-06-29 462
64372f38 Maciej S. Szmigiero 2018-06-29 463 strength = CX25840_PIN_DRIVE_FAST;
64372f38 Maciej S. Szmigiero 2018-06-29 464 }
64372f38 Maciej S. Szmigiero 2018-06-29 465
64372f38 Maciej S. Szmigiero 2018-06-29 466 switch (p[i].pin) {
64372f38 Maciej S. Szmigiero 2018-06-29 467 case CX25840_PIN_DVALID_PRGM0:
64372f38 Maciej S. Szmigiero 2018-06-29 @468 if (p[i].flags & V4L2_SUBDEV_IO_PIN_DISABLE)
V4L2_SUBDEV_IO_PIN_DISABLE is zero. It's sometimes used as a bit zero
BIT(V4L2_SUBDEV_IO_PIN_DISABLE) and presumably that's what is intended
here.
64372f38 Maciej S. Szmigiero 2018-06-29 469 pinctrl[0] &= ~BIT(6);
64372f38 Maciej S. Szmigiero 2018-06-29 470 else
64372f38 Maciej S. Szmigiero 2018-06-29 471 pinctrl[0] |= BIT(6);
64372f38 Maciej S. Szmigiero 2018-06-29 472
64372f38 Maciej S. Szmigiero 2018-06-29 473 pinconf[3] &= 0xf0;
64372f38 Maciej S. Szmigiero 2018-06-29 474 pinconf[3] |= cx25840_function_to_pad(client,
64372f38 Maciej S. Szmigiero 2018-06-29 475 p[i].function);
64372f38 Maciej S. Szmigiero 2018-06-29 476
64372f38 Maciej S. Szmigiero 2018-06-29 477 cx25840_set_invert(&pinctrl[3], &voutctrl4,
64372f38 Maciej S. Szmigiero 2018-06-29 478 p[i].function,
64372f38 Maciej S. Szmigiero 2018-06-29 479 CX25840_PIN_DVALID_PRGM0,
64372f38 Maciej S. Szmigiero 2018-06-29 480 p[i].flags &
64372f38 Maciej S. Szmigiero 2018-06-29 481 V4L2_SUBDEV_IO_PIN_ACTIVE_LOW);
64372f38 Maciej S. Szmigiero 2018-06-29 482
64372f38 Maciej S. Szmigiero 2018-06-29 483 pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */
64372f38 Maciej S. Szmigiero 2018-06-29 484 switch (strength) {
64372f38 Maciej S. Szmigiero 2018-06-29 485 case CX25840_PIN_DRIVE_SLOW:
64372f38 Maciej S. Szmigiero 2018-06-29 486 pinctrl[4] |= 1 << 2;
64372f38 Maciej S. Szmigiero 2018-06-29 487 break;
64372f38 Maciej S. Szmigiero 2018-06-29 488
64372f38 Maciej S. Szmigiero 2018-06-29 489 case CX25840_PIN_DRIVE_FAST:
64372f38 Maciej S. Szmigiero 2018-06-29 490 pinctrl[4] |= 2 << 2;
64372f38 Maciej S. Szmigiero 2018-06-29 491 break;
64372f38 Maciej S. Szmigiero 2018-06-29 492 }
64372f38 Maciej S. Szmigiero 2018-06-29 493
64372f38 Maciej S. Szmigiero 2018-06-29 494 break;
64372f38 Maciej S. Szmigiero 2018-06-29 495
64372f38 Maciej S. Szmigiero 2018-06-29 496 case CX25840_PIN_HRESET_PRGM2:
64372f38 Maciej S. Szmigiero 2018-06-29 497 if (p[i].flags & V4L2_SUBDEV_IO_PIN_DISABLE)
64372f38 Maciej S. Szmigiero 2018-06-29 498 pinctrl[1] &= ~BIT(0);
64372f38 Maciej S. Szmigiero 2018-06-29 499 else
64372f38 Maciej S. Szmigiero 2018-06-29 500 pinctrl[1] |= BIT(0);
64372f38 Maciej S. Szmigiero 2018-06-29 501
64372f38 Maciej S. Szmigiero 2018-06-29 502 pinconf[4] &= 0xf0;
64372f38 Maciej S. Szmigiero 2018-06-29 503 pinconf[4] |= cx25840_function_to_pad(client,
64372f38 Maciej S. Szmigiero 2018-06-29 504 p[i].function);
64372f38 Maciej S. Szmigiero 2018-06-29 505
64372f38 Maciej S. Szmigiero 2018-06-29 506 cx25840_set_invert(&pinctrl[3], &voutctrl4,
64372f38 Maciej S. Szmigiero 2018-06-29 507 p[i].function,
64372f38 Maciej S. Szmigiero 2018-06-29 508 CX25840_PIN_HRESET_PRGM2,
64372f38 Maciej S. Szmigiero 2018-06-29 509 p[i].flags &
64372f38 Maciej S. Szmigiero 2018-06-29 510 V4L2_SUBDEV_IO_PIN_ACTIVE_LOW);
64372f38 Maciej S. Szmigiero 2018-06-29 511
64372f38 Maciej S. Szmigiero 2018-06-29 512 pinctrl[4] &= ~(3 << 2); /* CX25840_PIN_DRIVE_MEDIUM */
64372f38 Maciej S. Szmigiero 2018-06-29 513 switch (strength) {
64372f38 Maciej S. Szmigiero 2018-06-29 514 case CX25840_PIN_DRIVE_SLOW:
64372f38 Maciej S. Szmigiero 2018-06-29 515 pinctrl[4] |= 1 << 2;
64372f38 Maciej S. Szmigiero 2018-06-29 516 break;
64372f38 Maciej S. Szmigiero 2018-06-29 517
64372f38 Maciej S. Szmigiero 2018-06-29 518 case CX25840_PIN_DRIVE_FAST:
64372f38 Maciej S. Szmigiero 2018-06-29 519 pinctrl[4] |= 2 << 2;
64372f38 Maciej S. Szmigiero 2018-06-29 520 break;
64372f38 Maciej S. Szmigiero 2018-06-29 521 }
64372f38 Maciej S. Szmigiero 2018-06-29 522
64372f38 Maciej S. Szmigiero 2018-06-29 523 break;
64372f38 Maciej S. Szmigiero 2018-06-29 524
64372f38 Maciej S. Szmigiero 2018-06-29 525 case CX25840_PIN_PLL_CLK_PRGM7:
64372f38 Maciej S. Szmigiero 2018-06-29 526 if (p[i].flags & V4L2_SUBDEV_IO_PIN_DISABLE)
64372f38 Maciej S. Szmigiero 2018-06-29 527 pinctrl[2] &= ~BIT(2);
64372f38 Maciej S. Szmigiero 2018-06-29 528 else
64372f38 Maciej S. Szmigiero 2018-06-29 529 pinctrl[2] |= BIT(2);
64372f38 Maciej S. Szmigiero 2018-06-29 530
64372f38 Maciej S. Szmigiero 2018-06-29 531 switch (p[i].function) {
64372f38 Maciej S. Szmigiero 2018-06-29 532 case CX25840_PAD_XTI_X5_DLL:
64372f38 Maciej S. Szmigiero 2018-06-29 533 pinconf[6] = 0;
64372f38 Maciej S. Szmigiero 2018-06-29 534 break;
64372f38 Maciej S. Szmigiero 2018-06-29 535
64372f38 Maciej S. Szmigiero 2018-06-29 536 case CX25840_PAD_AUX_PLL:
64372f38 Maciej S. Szmigiero 2018-06-29 537 pinconf[6] = 1;
64372f38 Maciej S. Szmigiero 2018-06-29 538 break;
64372f38 Maciej S. Szmigiero 2018-06-29 539
64372f38 Maciej S. Szmigiero 2018-06-29 540 case CX25840_PAD_VID_PLL:
64372f38 Maciej S. Szmigiero 2018-06-29 541 pinconf[6] = 5;
64372f38 Maciej S. Szmigiero 2018-06-29 542 break;
64372f38 Maciej S. Szmigiero 2018-06-29 543
64372f38 Maciej S. Szmigiero 2018-06-29 544 case CX25840_PAD_XTI:
64372f38 Maciej S. Szmigiero 2018-06-29 545 pinconf[6] = 2;
64372f38 Maciej S. Szmigiero 2018-06-29 546 break;
64372f38 Maciej S. Szmigiero 2018-06-29 547
64372f38 Maciej S. Szmigiero 2018-06-29 548 default:
64372f38 Maciej S. Szmigiero 2018-06-29 549 pinconf[6] = 3;
64372f38 Maciej S. Szmigiero 2018-06-29 550 pinconf[6] |=
64372f38 Maciej S. Szmigiero 2018-06-29 551 cx25840_function_to_pad(client,
64372f38 Maciej S. Szmigiero 2018-06-29 552 p[i].function)
64372f38 Maciej S. Szmigiero 2018-06-29 553 << 4;
64372f38 Maciej S. Szmigiero 2018-06-29 554 }
64372f38 Maciej S. Szmigiero 2018-06-29 555
64372f38 Maciej S. Szmigiero 2018-06-29 556 break;
64372f38 Maciej S. Szmigiero 2018-06-29 557
64372f38 Maciej S. Szmigiero 2018-06-29 558 default:
64372f38 Maciej S. Szmigiero 2018-06-29 559 v4l_err(client, "invalid or unsupported pin %u\n",
64372f38 Maciej S. Szmigiero 2018-06-29 560 (unsigned int)p[i].pin);
64372f38 Maciej S. Szmigiero 2018-06-29 561 break;
64372f38 Maciej S. Szmigiero 2018-06-29 562 }
64372f38 Maciej S. Szmigiero 2018-06-29 563 }
64372f38 Maciej S. Szmigiero 2018-06-29 564
64372f38 Maciej S. Szmigiero 2018-06-29 565 cx25840_write(client, 0x407, voutctrl4);
64372f38 Maciej S. Szmigiero 2018-06-29 566
64372f38 Maciej S. Szmigiero 2018-06-29 567 for (i = 0; i < 6; i++)
64372f38 Maciej S. Szmigiero 2018-06-29 568 cx25840_write(client, 0x114 + i, pinctrl[i]);
64372f38 Maciej S. Szmigiero 2018-06-29 569
64372f38 Maciej S. Szmigiero 2018-06-29 570 for (i = 0; i < 10; i++)
64372f38 Maciej S. Szmigiero 2018-06-29 571 cx25840_write(client, 0x11c + i, pinconf[i]);
64372f38 Maciej S. Szmigiero 2018-06-29 572
64372f38 Maciej S. Szmigiero 2018-06-29 573 return 0;
64372f38 Maciej S. Szmigiero 2018-06-29 574 }
64372f38 Maciej S. Szmigiero 2018-06-29 575
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
Hi Dan,
On 02.07.2018 07:55, Dan Carpenter wrote:
> Hi Maciej,
>
> Thank you for the patch! Perhaps something to improve:
>
> url: https://github.com/0day-ci/linux/commits/Maciej-S-Szmigiero/Add-analog-mode-support-for-Medion-MD95700/20180630-050341
> base: git://linuxtv.org/media_tree.git master
>
> New smatch warnings:
> drivers/media/i2c/cx25840/cx25840-core.c:468 cx25840_s_io_pin_config() warn: bitwise AND condition is false here
>
> Old smatch warnings:
> drivers/media/i2c/cx25840/cx25840-core.c:497 cx25840_s_io_pin_config() warn: bitwise AND condition is false here
> drivers/media/i2c/cx25840/cx25840-core.c:526 cx25840_s_io_pin_config() warn: bitwise AND condition is false here
>
> # https://github.com/0day-ci/linux/commit/64372f380e540a77b73d4628a585c9c92956a7fd
> git remote add linux-review https://github.com/0day-ci/linux
> git remote update linux-review
> git checkout 64372f380e540a77b73d4628a585c9c92956a7fd
> vim +468 drivers/media/i2c/cx25840/cx25840-core.c
>
> 64372f38 Maciej S. Szmigiero 2018-06-29 435
> 64372f38 Maciej S. Szmigiero 2018-06-29 436 static int cx25840_s_io_pin_config(struct v4l2_subdev *sd, size_t n,
> 64372f38 Maciej S. Szmigiero 2018-06-29 437 struct v4l2_subdev_io_pin_config *p)
> 64372f38 Maciej S. Szmigiero 2018-06-29 438 {
> 64372f38 Maciej S. Szmigiero 2018-06-29 439 struct i2c_client *client = v4l2_get_subdevdata(sd);
> 64372f38 Maciej S. Szmigiero 2018-06-29 440 unsigned int i;
> 64372f38 Maciej S. Szmigiero 2018-06-29 441 u8 pinctrl[6], pinconf[10], voutctrl4;
> 64372f38 Maciej S. Szmigiero 2018-06-29 442
> 64372f38 Maciej S. Szmigiero 2018-06-29 443 for (i = 0; i < 6; i++)
> 64372f38 Maciej S. Szmigiero 2018-06-29 444 pinctrl[i] = cx25840_read(client, 0x114 + i);
> 64372f38 Maciej S. Szmigiero 2018-06-29 445
> 64372f38 Maciej S. Szmigiero 2018-06-29 446 for (i = 0; i < 10; i++)
> 64372f38 Maciej S. Szmigiero 2018-06-29 447 pinconf[i] = cx25840_read(client, 0x11c + i);
> 64372f38 Maciej S. Szmigiero 2018-06-29 448
> 64372f38 Maciej S. Szmigiero 2018-06-29 449 voutctrl4 = cx25840_read(client, 0x407);
> 64372f38 Maciej S. Szmigiero 2018-06-29 450
> 64372f38 Maciej S. Szmigiero 2018-06-29 451 for (i = 0; i < n; i++) {
> 64372f38 Maciej S. Szmigiero 2018-06-29 452 u8 strength = p[i].strength;
> 64372f38 Maciej S. Szmigiero 2018-06-29 453
> 64372f38 Maciej S. Szmigiero 2018-06-29 454 if (strength != CX25840_PIN_DRIVE_SLOW &&
> 64372f38 Maciej S. Szmigiero 2018-06-29 455 strength != CX25840_PIN_DRIVE_MEDIUM &&
> 64372f38 Maciej S. Szmigiero 2018-06-29 456 strength != CX25840_PIN_DRIVE_FAST) {
> 64372f38 Maciej S. Szmigiero 2018-06-29 457
> 64372f38 Maciej S. Szmigiero 2018-06-29 458 v4l_err(client,
> 64372f38 Maciej S. Szmigiero 2018-06-29 459 "invalid drive speed for pin %u (%u), assuming fast\n",
> 64372f38 Maciej S. Szmigiero 2018-06-29 460 (unsigned int)p[i].pin,
> 64372f38 Maciej S. Szmigiero 2018-06-29 461 (unsigned int)strength);
> 64372f38 Maciej S. Szmigiero 2018-06-29 462
> 64372f38 Maciej S. Szmigiero 2018-06-29 463 strength = CX25840_PIN_DRIVE_FAST;
> 64372f38 Maciej S. Szmigiero 2018-06-29 464 }
> 64372f38 Maciej S. Szmigiero 2018-06-29 465
> 64372f38 Maciej S. Szmigiero 2018-06-29 466 switch (p[i].pin) {
> 64372f38 Maciej S. Szmigiero 2018-06-29 467 case CX25840_PIN_DVALID_PRGM0:
> 64372f38 Maciej S. Szmigiero 2018-06-29 @468 if (p[i].flags & V4L2_SUBDEV_IO_PIN_DISABLE)
>
> V4L2_SUBDEV_IO_PIN_DISABLE is zero. It's sometimes used as a bit zero
> BIT(V4L2_SUBDEV_IO_PIN_DISABLE) and presumably that's what is intended
> here.
Good catch, thanks.
Before commit 4eb2f55728abbe ("media: v4l2-subdev: better document IO pin configuration flags")
these identifiers were bit masks, now they are bit numbers.
This wasn't caught in testing since the cxusb driver always passes zero
as flags anyway.
Maciej