aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/usb/au0828
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/usb/au0828')
-rw-r--r--drivers/media/usb/au0828/au0828-core.c456
-rw-r--r--drivers/media/usb/au0828/au0828-dvb.c12
-rw-r--r--drivers/media/usb/au0828/au0828-video.c190
-rw-r--r--drivers/media/usb/au0828/au0828.h27
4 files changed, 475 insertions, 210 deletions
diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c
index 9e29e70a78d7..5dc82e8c8670 100644
--- a/drivers/media/usb/au0828/au0828-core.c
+++ b/drivers/media/usb/au0828/au0828-core.c
@@ -20,6 +20,7 @@
*/
#include "au0828.h"
+#include "au8522.h"
#include <linux/module.h>
#include <linux/slab.h>
@@ -134,16 +135,16 @@ static void au0828_unregister_media_device(struct au0828_dev *dev)
{
#ifdef CONFIG_MEDIA_CONTROLLER
- if (dev->media_dev) {
+ if (dev->media_dev &&
+ media_devnode_is_registered(&dev->media_dev->devnode)) {
media_device_unregister(dev->media_dev);
media_device_cleanup(dev->media_dev);
- kfree(dev->media_dev);
dev->media_dev = NULL;
}
#endif
}
-static void au0828_usb_release(struct au0828_dev *dev)
+void au0828_usb_release(struct au0828_dev *dev)
{
au0828_unregister_media_device(dev);
@@ -153,33 +154,6 @@ static void au0828_usb_release(struct au0828_dev *dev)
kfree(dev);
}
-#ifdef CONFIG_VIDEO_AU0828_V4L2
-
-static void au0828_usb_v4l2_media_release(struct au0828_dev *dev)
-{
-#ifdef CONFIG_MEDIA_CONTROLLER
- int i;
-
- for (i = 0; i < AU0828_MAX_INPUT; i++) {
- if (AUVI_INPUT(i).type == AU0828_VMUX_UNDEFINED)
- return;
- media_device_unregister_entity(&dev->input_ent[i]);
- }
-#endif
-}
-
-static void au0828_usb_v4l2_release(struct v4l2_device *v4l2_dev)
-{
- struct au0828_dev *dev =
- container_of(v4l2_dev, struct au0828_dev, v4l2_dev);
-
- v4l2_ctrl_handler_free(&dev->v4l2_ctrl_hdl);
- v4l2_device_unregister(&dev->v4l2_dev);
- au0828_usb_v4l2_media_release(dev);
- au0828_usb_release(dev);
-}
-#endif
-
static void au0828_usb_disconnect(struct usb_interface *interface)
{
struct au0828_dev *dev = usb_get_intfdata(interface);
@@ -202,18 +176,13 @@ static void au0828_usb_disconnect(struct usb_interface *interface)
mutex_lock(&dev->mutex);
dev->usbdev = NULL;
mutex_unlock(&dev->mutex);
-#ifdef CONFIG_VIDEO_AU0828_V4L2
- if (AUVI_INPUT(0).type != AU0828_VMUX_UNDEFINED) {
- au0828_analog_unregister(dev);
- v4l2_device_disconnect(&dev->v4l2_dev);
- v4l2_device_put(&dev->v4l2_dev);
+ if (au0828_analog_unregister(dev)) {
/*
* No need to call au0828_usb_release() if V4L2 is enabled,
* as this is already called via au0828_usb_v4l2_release()
*/
return;
}
-#endif
au0828_usb_release(dev);
}
@@ -223,103 +192,334 @@ static int au0828_media_device_init(struct au0828_dev *dev,
#ifdef CONFIG_MEDIA_CONTROLLER
struct media_device *mdev;
- mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
+ mdev = media_device_get_devres(&udev->dev);
if (!mdev)
return -ENOMEM;
- mdev->dev = &udev->dev;
-
- if (!dev->board.name)
- strlcpy(mdev->model, "unknown au0828", sizeof(mdev->model));
- else
- strlcpy(mdev->model, dev->board.name, sizeof(mdev->model));
- if (udev->serial)
- strlcpy(mdev->serial, udev->serial, sizeof(mdev->serial));
- strcpy(mdev->bus_info, udev->devpath);
- mdev->hw_revision = le16_to_cpu(udev->descriptor.bcdDevice);
- mdev->driver_version = LINUX_VERSION_CODE;
-
- media_device_init(mdev);
+ /* check if media device is already initialized */
+ if (!mdev->dev)
+ media_device_usb_init(mdev, udev, udev->product);
dev->media_dev = mdev;
#endif
return 0;
}
+#ifdef CONFIG_MEDIA_CONTROLLER
+static void au0828_media_graph_notify(struct media_entity *new,
+ void *notify_data)
+{
+ struct au0828_dev *dev = (struct au0828_dev *) notify_data;
+ int ret;
+ struct media_entity *entity, *mixer = NULL, *decoder = NULL;
+
+ if (!new) {
+ /*
+ * Called during au0828 probe time to connect
+ * entites that were created prior to registering
+ * the notify handler. Find mixer and decoder.
+ */
+ media_device_for_each_entity(entity, dev->media_dev) {
+ if (entity->function == MEDIA_ENT_F_AUDIO_MIXER)
+ mixer = entity;
+ else if (entity->function == MEDIA_ENT_F_ATV_DECODER)
+ decoder = entity;
+ }
+ goto create_link;
+ }
+
+ switch (new->function) {
+ case MEDIA_ENT_F_AUDIO_MIXER:
+ mixer = new;
+ if (dev->decoder)
+ decoder = dev->decoder;
+ break;
+ case MEDIA_ENT_F_ATV_DECODER:
+ /* In case, Mixer is added first, find mixer and create link */
+ media_device_for_each_entity(entity, dev->media_dev) {
+ if (entity->function == MEDIA_ENT_F_AUDIO_MIXER)
+ mixer = entity;
+ }
+ decoder = new;
+ break;
+ default:
+ break;
+ }
+
+create_link:
+ if (decoder && mixer) {
+ ret = media_create_pad_link(decoder,
+ DEMOD_PAD_AUDIO_OUT,
+ mixer, 0,
+ MEDIA_LNK_FL_ENABLED);
+ if (ret)
+ dev_err(&dev->usbdev->dev,
+ "Mixer Pad Link Create Error: %d\n", ret);
+ }
+}
-static int au0828_create_media_graph(struct au0828_dev *dev)
+static int au0828_enable_source(struct media_entity *entity,
+ struct media_pipeline *pipe)
{
-#ifdef CONFIG_MEDIA_CONTROLLER
- struct media_device *mdev = dev->media_dev;
- struct media_entity *entity;
- struct media_entity *tuner = NULL, *decoder = NULL;
- int i, ret;
+ struct media_entity *source, *find_source;
+ struct media_entity *sink;
+ struct media_link *link, *found_link = NULL;
+ int ret = 0;
+ struct media_device *mdev = entity->graph_obj.mdev;
+ struct au0828_dev *dev;
if (!mdev)
- return 0;
+ return -ENODEV;
- media_device_for_each_entity(entity, mdev) {
- switch (entity->function) {
- case MEDIA_ENT_F_TUNER:
- tuner = entity;
- break;
- case MEDIA_ENT_F_ATV_DECODER:
- decoder = entity;
+ mutex_lock(&mdev->graph_mutex);
+
+ dev = mdev->source_priv;
+
+ /*
+ * For Audio and V4L2 entity, find the link to which decoder
+ * is the sink. Look for an active link between decoder and
+ * source (tuner/s-video/Composite), if one exists, nothing
+ * to do. If not, look for any active links between source
+ * and any other entity. If one exists, source is busy. If
+ * source is free, setup link and start pipeline from source.
+ * For DVB FE entity, the source for the link is the tuner.
+ * Check if tuner is available and setup link and start
+ * pipeline.
+ */
+ if (entity->function == MEDIA_ENT_F_DTV_DEMOD) {
+ sink = entity;
+ find_source = dev->tuner;
+ } else {
+ /* Analog isn't configured or register failed */
+ if (!dev->decoder) {
+ ret = -ENODEV;
+ goto end;
+ }
+
+ sink = dev->decoder;
+
+ /*
+ * Default input is tuner and default input_type
+ * is AU0828_VMUX_TELEVISION.
+ * FIXME:
+ * There is a problem when s_input is called to
+ * change the default input. s_input will try to
+ * enable_source before attempting to change the
+ * input on the device, and will end up enabling
+ * default source which is tuner.
+ *
+ * Additional logic is necessary in au0828
+ * to detect that the input has changed and
+ * enable the right source.
+ */
+
+ if (dev->input_type == AU0828_VMUX_TELEVISION)
+ find_source = dev->tuner;
+ else if (dev->input_type == AU0828_VMUX_SVIDEO ||
+ dev->input_type == AU0828_VMUX_COMPOSITE)
+ find_source = &dev->input_ent[dev->input_type];
+ else {
+ /* unknown input - let user select input */
+ ret = 0;
+ goto end;
+ }
+ }
+
+ /* Is an active link between sink and source */
+ if (dev->active_link) {
+ /*
+ * If DVB is using the tuner and calling entity is
+ * audio/video, the following check will be false,
+ * since sink is different. Result is Busy.
+ */
+ if (dev->active_link->sink->entity == sink &&
+ dev->active_link->source->entity == find_source) {
+ /*
+ * Either ALSA or Video own tuner. sink is
+ * the same for both. Prevent Video stepping
+ * on ALSA when ALSA owns the source.
+ */
+ if (dev->active_link_owner != entity &&
+ dev->active_link_owner->function ==
+ MEDIA_ENT_F_AUDIO_CAPTURE) {
+ pr_debug("ALSA has the tuner\n");
+ ret = -EBUSY;
+ goto end;
+ }
+ ret = 0;
+ goto end;
+ } else {
+ ret = -EBUSY;
+ goto end;
+ }
+ }
+
+ list_for_each_entry(link, &sink->links, list) {
+ /* Check sink, and source */
+ if (link->sink->entity == sink &&
+ link->source->entity == find_source) {
+ found_link = link;
break;
}
}
- /* Analog setup, using tuner as a link */
+ if (!found_link) {
+ ret = -ENODEV;
+ goto end;
+ }
- /* Something bad happened! */
- if (!decoder)
- return -EINVAL;
+ /* activate link between source and sink and start pipeline */
+ source = found_link->source->entity;
+ ret = __media_entity_setup_link(found_link, MEDIA_LNK_FL_ENABLED);
+ if (ret) {
+ pr_err("Activate tuner link %s->%s. Error %d\n",
+ source->name, sink->name, ret);
+ goto end;
+ }
- if (tuner) {
- ret = media_create_pad_link(tuner, TUNER_PAD_IF_OUTPUT,
- decoder, 0,
- MEDIA_LNK_FL_ENABLED);
+ ret = __media_entity_pipeline_start(entity, pipe);
+ if (ret) {
+ pr_err("Start Pipeline: %s->%s Error %d\n",
+ source->name, entity->name, ret);
+ ret = __media_entity_setup_link(found_link, 0);
+ pr_err("Deactivate link Error %d\n", ret);
+ goto end;
+ }
+ /*
+ * save active link and active link owner to avoid audio
+ * deactivating video owned link from disable_source and
+ * vice versa
+ */
+ dev->active_link = found_link;
+ dev->active_link_owner = entity;
+ dev->active_source = source;
+ dev->active_sink = sink;
+
+ pr_debug("Enabled Source: %s->%s->%s Ret %d\n",
+ dev->active_source->name, dev->active_sink->name,
+ dev->active_link_owner->name, ret);
+end:
+ mutex_unlock(&mdev->graph_mutex);
+ pr_debug("au0828_enable_source() end %s %d %d\n",
+ entity->name, entity->function, ret);
+ return ret;
+}
+
+static void au0828_disable_source(struct media_entity *entity)
+{
+ int ret = 0;
+ struct media_device *mdev = entity->graph_obj.mdev;
+ struct au0828_dev *dev;
+
+ if (!mdev)
+ return;
+
+ mutex_lock(&mdev->graph_mutex);
+ dev = mdev->source_priv;
+
+ if (!dev->active_link) {
+ ret = -ENODEV;
+ goto end;
+ }
+
+ /* link is active - stop pipeline from source (tuner) */
+ if (dev->active_link->sink->entity == dev->active_sink &&
+ dev->active_link->source->entity == dev->active_source) {
+ /*
+ * prevent video from deactivating link when audio
+ * has active pipeline
+ */
+ if (dev->active_link_owner != entity)
+ goto end;
+ __media_entity_pipeline_stop(entity);
+ ret = __media_entity_setup_link(dev->active_link, 0);
if (ret)
- return ret;
+ pr_err("Deactivate link Error %d\n", ret);
+
+ pr_debug("Disabled Source: %s->%s->%s Ret %d\n",
+ dev->active_source->name, dev->active_sink->name,
+ dev->active_link_owner->name, ret);
+
+ dev->active_link = NULL;
+ dev->active_link_owner = NULL;
+ dev->active_source = NULL;
+ dev->active_sink = NULL;
}
- ret = media_create_pad_link(decoder, 1, &dev->vdev.entity, 0,
- MEDIA_LNK_FL_ENABLED);
- if (ret)
- return ret;
- ret = media_create_pad_link(decoder, 2, &dev->vbi_dev.entity, 0,
- MEDIA_LNK_FL_ENABLED);
- if (ret)
- return ret;
- for (i = 0; i < AU0828_MAX_INPUT; i++) {
- struct media_entity *ent = &dev->input_ent[i];
+end:
+ mutex_unlock(&mdev->graph_mutex);
+}
+#endif
- if (AUVI_INPUT(i).type == AU0828_VMUX_UNDEFINED)
- break;
+static int au0828_media_device_register(struct au0828_dev *dev,
+ struct usb_device *udev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+ int ret;
+ struct media_entity *entity, *demod = NULL, *tuner = NULL;
- switch (AUVI_INPUT(i).type) {
- case AU0828_VMUX_CABLE:
- case AU0828_VMUX_TELEVISION:
- case AU0828_VMUX_DVB:
- if (!tuner)
- break;
-
- ret = media_create_pad_link(ent, 0, tuner,
- TUNER_PAD_RF_INPUT,
- MEDIA_LNK_FL_ENABLED);
- if (ret)
- return ret;
- break;
- case AU0828_VMUX_COMPOSITE:
- case AU0828_VMUX_SVIDEO:
- default: /* AU0828_VMUX_DEBUG */
- /* FIXME: fix the decoder PAD */
- ret = media_create_pad_link(ent, 0, decoder, 0, 0);
- if (ret)
- return ret;
- break;
+ if (!dev->media_dev)
+ return 0;
+
+ if (!media_devnode_is_registered(&dev->media_dev->devnode)) {
+
+ /* register media device */
+ ret = media_device_register(dev->media_dev);
+ if (ret) {
+ dev_err(&udev->dev,
+ "Media Device Register Error: %d\n", ret);
+ return ret;
+ }
+ } else {
+ /*
+ * Call au0828_media_graph_notify() to connect
+ * audio graph to our graph. In this case, audio
+ * driver registered the device and there is no
+ * entity_notify to be called when new entities
+ * are added. Invoke it now.
+ */
+ au0828_media_graph_notify(NULL, (void *) dev);
+ }
+
+ /*
+ * Find tuner and demod to disable the link between
+ * the two to avoid disable step when tuner is requested
+ * by video or audio. Note that this step can't be done
+ * until dvb graph is created during dvb register.
+ */
+ media_device_for_each_entity(entity, dev->media_dev) {
+ if (entity->function == MEDIA_ENT_F_DTV_DEMOD)
+ demod = entity;
+ else if (entity->function == MEDIA_ENT_F_TUNER)
+ tuner = entity;
+ }
+ /* Disable link between tuner and demod */
+ if (tuner && demod) {
+ struct media_link *link;
+
+ list_for_each_entry(link, &demod->links, list) {
+ if (link->sink->entity == demod &&
+ link->source->entity == tuner) {
+ media_entity_setup_link(link, 0);
+ }
}
}
+
+ /* register entity_notify callback */
+ dev->entity_notify.notify_data = (void *) dev;
+ dev->entity_notify.notify = (void *) au0828_media_graph_notify;
+ ret = media_device_register_entity_notify(dev->media_dev,
+ &dev->entity_notify);
+ if (ret) {
+ dev_err(&udev->dev,
+ "Media Device register entity_notify Error: %d\n",
+ ret);
+ return ret;
+ }
+ /* set enable_source */
+ dev->media_dev->source_priv = (void *) dev;
+ dev->media_dev->enable_source = au0828_enable_source;
+ dev->media_dev->disable_source = au0828_disable_source;
#endif
return 0;
}
@@ -378,32 +578,13 @@ static int au0828_usb_probe(struct usb_interface *interface,
return retval;
}
-#ifdef CONFIG_VIDEO_AU0828_V4L2
- dev->v4l2_dev.release = au0828_usb_v4l2_release;
-
- /* Create the v4l2_device */
-#ifdef CONFIG_MEDIA_CONTROLLER
- dev->v4l2_dev.mdev = dev->media_dev;
-#endif
- retval = v4l2_device_register(&interface->dev, &dev->v4l2_dev);
+ retval = au0828_v4l2_device_register(interface, dev);
if (retval) {
- pr_err("%s() v4l2_device_register failed\n",
- __func__);
+ au0828_usb_v4l2_media_release(dev);
mutex_unlock(&dev->lock);
kfree(dev);
return retval;
}
- /* This control handler will inherit the controls from au8522 */
- retval = v4l2_ctrl_handler_init(&dev->v4l2_ctrl_hdl, 4);
- if (retval) {
- pr_err("%s() v4l2_ctrl_handler_init failed\n",
- __func__);
- mutex_unlock(&dev->lock);
- kfree(dev);
- return retval;
- }
- dev->v4l2_dev.ctrl_handler = &dev->v4l2_ctrl_hdl;
-#endif
/* Power Up the bridge */
au0828_write(dev, REG_600, 1 << 4);
@@ -417,11 +598,13 @@ static int au0828_usb_probe(struct usb_interface *interface,
/* Setup */
au0828_card_setup(dev);
-#ifdef CONFIG_VIDEO_AU0828_V4L2
/* Analog TV */
- if (AUVI_INPUT(0).type != AU0828_VMUX_UNDEFINED)
- au0828_analog_register(dev, interface);
-#endif
+ retval = au0828_analog_register(dev, interface);
+ if (retval) {
+ pr_err("%s() au0282_dev_register failed to register on V4L2\n",
+ __func__);
+ goto done;
+ }
/* Digital TV */
retval = au0828_dvb_register(dev);
@@ -443,16 +626,7 @@ static int au0828_usb_probe(struct usb_interface *interface,
mutex_unlock(&dev->lock);
- retval = au0828_create_media_graph(dev);
- if (retval) {
- pr_err("%s() au0282_dev_register failed to create graph\n",
- __func__);
- goto done;
- }
-
-#ifdef CONFIG_MEDIA_CONTROLLER
- retval = media_device_register(dev->media_dev);
-#endif
+ retval = au0828_media_device_register(dev, usbdev);
done:
if (retval < 0)
diff --git a/drivers/media/usb/au0828/au0828-dvb.c b/drivers/media/usb/au0828/au0828-dvb.c
index 94363a3ba400..0e174e860614 100644
--- a/drivers/media/usb/au0828/au0828-dvb.c
+++ b/drivers/media/usb/au0828/au0828-dvb.c
@@ -181,7 +181,7 @@ static int stop_urb_transfer(struct au0828_dev *dev)
static int start_urb_transfer(struct au0828_dev *dev)
{
struct urb *purb;
- int i, ret = -ENOMEM;
+ int i, ret;
dprintk(2, "%s()\n", __func__);
@@ -194,7 +194,7 @@ static int start_urb_transfer(struct au0828_dev *dev)
dev->urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
if (!dev->urbs[i])
- goto err;
+ return -ENOMEM;
purb = dev->urbs[i];
@@ -207,9 +207,10 @@ static int start_urb_transfer(struct au0828_dev *dev)
if (!purb->transfer_buffer) {
usb_free_urb(purb);
dev->urbs[i] = NULL;
+ ret = -ENOMEM;
pr_err("%s: failed big buffer allocation, err = %d\n",
__func__, ret);
- goto err;
+ return ret;
}
purb->status = -EINPROGRESS;
@@ -235,10 +236,7 @@ static int start_urb_transfer(struct au0828_dev *dev)
}
dev->urb_streaming = true;
- ret = 0;
-
-err:
- return ret;
+ return 0;
}
static void au0828_start_transport(struct au0828_dev *dev)
diff --git a/drivers/media/usb/au0828/au0828-video.c b/drivers/media/usb/au0828/au0828-video.c
index a13625722848..13f6dab9ccc2 100644
--- a/drivers/media/usb/au0828/au0828-video.c
+++ b/drivers/media/usb/au0828/au0828-video.c
@@ -28,12 +28,14 @@
*/
#include "au0828.h"
+#include "au8522.h"
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/device.h>
#include <media/v4l2-common.h>
+#include <media/v4l2-mc.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-event.h>
#include <media/tuner.h>
@@ -638,61 +640,64 @@ static inline int au0828_isoc_copy(struct au0828_dev *dev, struct urb *urb)
return rc;
}
-static int au0828_enable_analog_tuner(struct au0828_dev *dev)
+void au0828_usb_v4l2_media_release(struct au0828_dev *dev)
{
#ifdef CONFIG_MEDIA_CONTROLLER
- struct media_device *mdev = dev->media_dev;
- struct media_entity *source;
- struct media_link *link, *found_link = NULL;
- int ret, active_links = 0;
-
- if (!mdev || !dev->decoder)
- return 0;
+ int i;
- /*
- * This will find the tuner that is connected into the decoder.
- * Technically, this is not 100% correct, as the device may be
- * using an analog input instead of the tuner. However, as we can't
- * do DVB streaming while the DMA engine is being used for V4L2,
- * this should be enough for the actual needs.
- */
- list_for_each_entry(link, &dev->decoder->links, list) {
- if (link->sink->entity == dev->decoder) {
- found_link = link;
- if (link->flags & MEDIA_LNK_FL_ENABLED)
- active_links++;
- break;
- }
+ for (i = 0; i < AU0828_MAX_INPUT; i++) {
+ if (AUVI_INPUT(i).type == AU0828_VMUX_UNDEFINED)
+ return;
+ media_device_unregister_entity(&dev->input_ent[i]);
}
+#endif
+}
- if (active_links == 1 || !found_link)
- return 0;
+static void au0828_usb_v4l2_release(struct v4l2_device *v4l2_dev)
+{
+ struct au0828_dev *dev =
+ container_of(v4l2_dev, struct au0828_dev, v4l2_dev);
+
+ v4l2_ctrl_handler_free(&dev->v4l2_ctrl_hdl);
+ v4l2_device_unregister(&dev->v4l2_dev);
+ au0828_usb_v4l2_media_release(dev);
+ au0828_usb_release(dev);
+}
- source = found_link->source->entity;
- list_for_each_entry(link, &source->links, list) {
- struct media_entity *sink;
- int flags = 0;
+int au0828_v4l2_device_register(struct usb_interface *interface,
+ struct au0828_dev *dev)
+{
+ int retval;
- sink = link->sink->entity;
+ if (AUVI_INPUT(0).type == AU0828_VMUX_UNDEFINED)
+ return 0;
- if (sink == dev->decoder)
- flags = MEDIA_LNK_FL_ENABLED;
+ /* Create the v4l2_device */
+#ifdef CONFIG_MEDIA_CONTROLLER
+ dev->v4l2_dev.mdev = dev->media_dev;
+#endif
+ retval = v4l2_device_register(&interface->dev, &dev->v4l2_dev);
+ if (retval) {
+ pr_err("%s() v4l2_device_register failed\n",
+ __func__);
+ mutex_unlock(&dev->lock);
+ kfree(dev);
+ return retval;
+ }
- ret = media_entity_setup_link(link, flags);
- if (ret) {
- pr_err(
- "Couldn't change link %s->%s to %s. Error %d\n",
- source->name, sink->name,
- flags ? "enabled" : "disabled",
- ret);
- return ret;
- } else
- au0828_isocdbg(
- "link %s->%s was %s\n",
- source->name, sink->name,
- flags ? "ENABLED" : "disabled");
+ dev->v4l2_dev.release = au0828_usb_v4l2_release;
+
+ /* This control handler will inherit the controls from au8522 */
+ retval = v4l2_ctrl_handler_init(&dev->v4l2_ctrl_hdl, 4);
+ if (retval) {
+ pr_err("%s() v4l2_ctrl_handler_init failed\n",
+ __func__);
+ mutex_unlock(&dev->lock);
+ kfree(dev);
+ return retval;
}
-#endif
+ dev->v4l2_dev.ctrl_handler = &dev->v4l2_ctrl_hdl;
+
return 0;
}
@@ -707,9 +712,6 @@ static int queue_setup(struct vb2_queue *vq,
return sizes[0] < size ? -EINVAL : 0;
*nplanes = 1;
sizes[0] = size;
-
- au0828_enable_analog_tuner(dev);
-
return 0;
}
@@ -949,13 +951,23 @@ static struct vb2_ops au0828_video_qops = {
* au0828_analog_unregister
* unregister v4l2 devices
*/
-void au0828_analog_unregister(struct au0828_dev *dev)
+int au0828_analog_unregister(struct au0828_dev *dev)
{
dprintk(1, "au0828_analog_unregister called\n");
+
+ /* No analog TV */
+ if (AUVI_INPUT(0).type == AU0828_VMUX_UNDEFINED)
+ return 0;
+
mutex_lock(&au0828_sysfs_lock);
video_unregister_device(&dev->vdev);
video_unregister_device(&dev->vbi_dev);
mutex_unlock(&au0828_sysfs_lock);
+
+ v4l2_device_disconnect(&dev->v4l2_dev);
+ v4l2_device_put(&dev->v4l2_dev);
+
+ return 1;
}
/* This function ensures that video frames continue to be delivered even if
@@ -1067,8 +1079,39 @@ static int au0828_v4l2_close(struct file *filp)
goto end;
if (dev->users == 1) {
- /* Save some power by putting tuner to sleep */
- v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_power, 0);
+ /*
+ * Avoid putting tuner in sleep if DVB or ALSA are
+ * streaming.
+ *
+ * On most USB devices like au0828 the tuner can
+ * be safely put in sleep stare here if ALSA isn't
+ * streaming. Exceptions are some very old USB tuner
+ * models such as em28xx-based WinTV USB2 which have
+ * a separate audio output jack. The devices that have
+ * a separate audio output jack have analog tuners,
+ * like Philips FM1236. Those devices are always on,
+ * so the s_power callback are silently ignored.
+ * So, the current logic here does the following:
+ * Disable (put tuner to sleep) when
+ * - ALSA and DVB aren't not streaming;
+ * - the last V4L2 file handler is closed.
+ *
+ * FIXME:
+ *
+ * Additionally, this logic could be improved to
+ * disable the media source if the above conditions
+ * are met and if the device:
+ * - doesn't have a separate audio out plug (or
+ * - doesn't use a silicon tuner like xc2028/3028/4000/5000).
+ *
+ * Once this additional logic is in place, a callback
+ * is needed to enable the media source and power on
+ * the tuner, for radio to work.
+ */
+ ret = v4l_enable_media_source(vdev);
+ if (ret == 0)
+ v4l2_device_call_all(&dev->v4l2_dev, 0, core,
+ s_power, 0);
dev->std_set_in_tuner_core = 0;
/* When close the device, set the usb intf0 into alt0 to free
@@ -1312,7 +1355,6 @@ static int vidioc_enum_input(struct file *file, void *priv,
[AU0828_VMUX_CABLE] = "Cable TV",
[AU0828_VMUX_TELEVISION] = "Television",
[AU0828_VMUX_DVB] = "DVB",
- [AU0828_VMUX_DEBUG] = "tv debug"
};
dprintk(1, "%s called std_set %d dev_state %d\n", __func__,
@@ -1375,9 +1417,11 @@ static void au0828_s_input(struct au0828_dev *dev, int index)
default:
dprintk(1, "unknown input type set [%d]\n",
AUVI_INPUT(index).type);
- break;
+ return;
}
+ dev->ctrl_input = index;
+
v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing,
AUVI_INPUT(index).vmux, 0, 0);
@@ -1409,6 +1453,7 @@ static void au0828_s_input(struct au0828_dev *dev, int index)
static int vidioc_s_input(struct file *file, void *priv, unsigned int index)
{
struct au0828_dev *dev = video_drvdata(file);
+ struct video_device *vfd = video_devdata(file);
dprintk(1, "VIDIOC_S_INPUT in function %s, input=%d\n", __func__,
index);
@@ -1416,9 +1461,19 @@ static int vidioc_s_input(struct file *file, void *priv, unsigned int index)
return -EINVAL;
if (AUVI_INPUT(index).type == 0)
return -EINVAL;
- dev->ctrl_input = index;
+
+ if (dev->ctrl_input == index)
+ return 0;
+
au0828_s_input(dev, index);
- return 0;
+
+ /*
+ * Input has been changed. Disable the media source
+ * associated with the old input and enable source
+ * for the newly set input
+ */
+ v4l_disable_media_source(vfd);
+ return v4l_enable_media_source(vfd);
}
static int vidioc_enumaudio(struct file *file, void *priv, struct v4l2_audio *a)
@@ -1469,10 +1524,16 @@ static int vidioc_s_audio(struct file *file, void *priv, const struct v4l2_audio
static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t)
{
struct au0828_dev *dev = video_drvdata(file);
+ struct video_device *vfd = video_devdata(file);
+ int ret;
if (t->index != 0)
return -EINVAL;
+ ret = v4l_enable_media_source(vfd);
+ if (ret)
+ return ret;
+
dprintk(1, "%s called std_set %d dev_state %d\n", __func__,
dev->std_set_in_tuner_core, dev->dev_state);
@@ -1804,7 +1865,6 @@ static void au0828_analog_create_entities(struct au0828_dev *dev)
[AU0828_VMUX_CABLE] = "Cable TV",
[AU0828_VMUX_TELEVISION] = "Television",
[AU0828_VMUX_DVB] = "DVB",
- [AU0828_VMUX_DEBUG] = "tv debug"
};
int ret, i;
@@ -1840,10 +1900,9 @@ static void au0828_analog_create_entities(struct au0828_dev *dev)
case AU0828_VMUX_CABLE:
case AU0828_VMUX_TELEVISION:
case AU0828_VMUX_DVB:
+ default: /* Just to shut up a warning */
ent->function = MEDIA_ENT_F_CONN_RF;
break;
- default: /* AU0828_VMUX_DEBUG */
- continue;
}
ret = media_entity_pads_init(ent, 1, &dev->input_pad[i]);
@@ -1870,6 +1929,10 @@ int au0828_analog_register(struct au0828_dev *dev,
dprintk(1, "au0828_analog_register called for intf#%d!\n",
interface->cur_altsetting->desc.bInterfaceNumber);
+ /* No analog TV */
+ if (AUVI_INPUT(0).type == AU0828_VMUX_UNDEFINED)
+ return 0;
+
/* set au0828 usb interface0 to as5 */
retval = usb_set_interface(dev->usbdev,
interface->cur_altsetting->desc.bInterfaceNumber, 5);
@@ -1924,6 +1987,7 @@ int au0828_analog_register(struct au0828_dev *dev,
dev->ctrl_ainput = 0;
dev->ctrl_freq = 960;
dev->std = V4L2_STD_NTSC_M;
+ /* Default input is TV Tuner */
au0828_s_input(dev, 0);
mutex_init(&dev->vb_queue_lock);
@@ -1976,6 +2040,16 @@ int au0828_analog_register(struct au0828_dev *dev,
goto err_reg_vbi_dev;
}
+#ifdef CONFIG_MEDIA_CONTROLLER
+ retval = v4l2_mc_create_media_graph(dev->media_dev);
+ if (retval) {
+ pr_err("%s() au0282_dev_register failed to create graph\n",
+ __func__);
+ ret = -ENODEV;
+ goto err_reg_vbi_dev;
+ }
+#endif
+
dprintk(1, "%s completed!\n", __func__);
return 0;
diff --git a/drivers/media/usb/au0828/au0828.h b/drivers/media/usb/au0828/au0828.h
index 8276072bc55a..ff7f8510fb77 100644
--- a/drivers/media/usb/au0828/au0828.h
+++ b/drivers/media/usb/au0828/au0828.h
@@ -76,7 +76,6 @@ enum au0828_itype {
AU0828_VMUX_CABLE,
AU0828_VMUX_TELEVISION,
AU0828_VMUX_DVB,
- AU0828_VMUX_DEBUG
};
struct au0828_input {
@@ -283,6 +282,12 @@ struct au0828_dev {
struct media_entity *decoder;
struct media_entity input_ent[AU0828_MAX_INPUT];
struct media_pad input_pad[AU0828_MAX_INPUT];
+ struct media_entity_notify entity_notify;
+ struct media_entity *tuner;
+ struct media_link *active_link;
+ struct media_entity *active_link_owner;
+ struct media_entity *active_source;
+ struct media_entity *active_sink;
#endif
};
@@ -301,6 +306,7 @@ struct au0828_dev {
/* au0828-core.c */
extern u32 au0828_read(struct au0828_dev *dev, u16 reg);
extern u32 au0828_write(struct au0828_dev *dev, u16 reg, u32 val);
+extern void au0828_usb_release(struct au0828_dev *dev);
extern int au0828_debug;
/* ----------------------------------------------------------- */
@@ -319,16 +325,29 @@ extern int au0828_i2c_unregister(struct au0828_dev *dev);
/* ----------------------------------------------------------- */
/* au0828-video.c */
-extern int au0828_analog_register(struct au0828_dev *dev,
- struct usb_interface *interface);
-extern void au0828_analog_unregister(struct au0828_dev *dev);
extern int au0828_start_analog_streaming(struct vb2_queue *vq,
unsigned int count);
extern void au0828_stop_vbi_streaming(struct vb2_queue *vq);
#ifdef CONFIG_VIDEO_AU0828_V4L2
+extern int au0828_v4l2_device_register(struct usb_interface *interface,
+ struct au0828_dev *dev);
+
+extern int au0828_analog_register(struct au0828_dev *dev,
+ struct usb_interface *interface);
+extern int au0828_analog_unregister(struct au0828_dev *dev);
+extern void au0828_usb_v4l2_media_release(struct au0828_dev *dev);
extern void au0828_v4l2_suspend(struct au0828_dev *dev);
extern void au0828_v4l2_resume(struct au0828_dev *dev);
#else
+static inline int au0828_v4l2_device_register(struct usb_interface *interface,
+ struct au0828_dev *dev)
+{ return 0; };
+static inline int au0828_analog_register(struct au0828_dev *dev,
+ struct usb_interface *interface)
+{ return 0; };
+static inline int au0828_analog_unregister(struct au0828_dev *dev)
+{ return 0; };
+static inline void au0828_usb_v4l2_media_release(struct au0828_dev *dev) { };
static inline void au0828_v4l2_suspend(struct au0828_dev *dev) { };
static inline void au0828_v4l2_resume(struct au0828_dev *dev) { };
#endif