This patch allows to receive sync lock notifications from
the source subdevice and temporarily pauses the stream until
the source can lock onto its signal again.

Signed-off-by: Philipp Zabel <p.za...@pengutronix.de>
---
 drivers/media/platform/imx/imx-ipuv3-csi.c | 160 +++++++++++++++++++++++++++++
 1 file changed, 160 insertions(+)

diff --git a/drivers/media/platform/imx/imx-ipuv3-csi.c 
b/drivers/media/platform/imx/imx-ipuv3-csi.c
index 0dd40a4..ab22cad 100644
--- a/drivers/media/platform/imx/imx-ipuv3-csi.c
+++ b/drivers/media/platform/imx/imx-ipuv3-csi.c
@@ -17,6 +17,7 @@
 #include <linux/moduleparam.h>
 #include <linux/interrupt.h>
 #include <linux/videodev2.h>
+#include <linux/workqueue.h>
 #include <linux/version.h>
 #include <linux/device.h>
 #include <linux/kernel.h>
@@ -260,6 +261,7 @@ struct ipucsi {
        struct v4l2_mbus_framefmt       format_mbus[2];
        struct ipu_media_link           *link;
        struct v4l2_fh                  fh;
+       bool                            paused;
 };
 
 static struct ipucsi_buffer *to_ipucsi_vb(struct vb2_buffer *vb)
@@ -443,6 +445,159 @@ static inline void ipucsi_set_inactive_buffer(struct 
ipucsi *ipucsi,
        ipu_idmac_select_buffer(ipucsi->ipuch, bufptr);
 }
 
+int ipucsi_resume_stream(struct ipucsi *ipucsi)
+{
+       struct ipucsi_buffer *buf;
+       struct vb2_buffer *vb;
+       unsigned long flags;
+       dma_addr_t eba;
+
+       if (!ipucsi->paused)
+               return 0;
+
+       spin_lock_irqsave(&ipucsi->lock, flags);
+
+       if (list_empty(&ipucsi->capture)) {
+               spin_unlock_irqrestore(&ipucsi->lock, flags);
+               return -EAGAIN;
+       }
+
+       buf = list_first_entry(&ipucsi->capture, struct ipucsi_buffer, queue);
+       vb = &buf->vb;
+
+       ipu_idmac_set_double_buffer(ipucsi->ipuch, 1);
+
+       eba = vb2_dma_contig_plane_dma_addr(vb, 0);
+       if (ipucsi->ilo < 0)
+               eba -= ipucsi->ilo;
+
+       ipu_cpmem_set_buffer(ipu_get_cpmem(ipucsi->ipuch), 0, eba);
+
+       ipu_idmac_select_buffer(ipucsi->ipuch, 0);
+
+       /*
+        * Point the inactive buffer address to the next queued buffer,
+        * if available. Otherwise, prepare to reuse the currently active
+        * buffer, unless ipucsi_videobuf_queue gets called in time.
+        */
+       if (!list_is_singular(&ipucsi->capture)) {
+               buf = list_entry(ipucsi->capture.next->next,
+                                struct ipucsi_buffer, queue);
+               vb = &buf->vb;
+       }
+
+       eba = vb2_dma_contig_plane_dma_addr(vb, 0);
+       if (ipucsi->ilo < 0)
+               eba -= ipucsi->ilo;
+
+       ipu_cpmem_set_buffer(ipu_get_cpmem(ipucsi->ipuch), 1, eba);
+
+       ipu_idmac_select_buffer(ipucsi->ipuch, 1);
+
+       spin_unlock_irqrestore(&ipucsi->lock, flags);
+
+       ipu_smfc_enable(ipucsi->ipu);
+       ipu_idmac_enable_channel(ipucsi->ipuch);
+       ipu_csi_enable(ipucsi->ipu, ipucsi->id);
+
+       ipucsi->active = buf;
+       ipucsi->paused = false;
+
+       return 0;
+}
+
+static void ipucsi_clear_buffer(struct vb2_buffer *vb, u32 fourcc)
+{
+       u32 black, *p, *end;
+
+       switch (fourcc) {
+       case V4L2_PIX_FMT_UYVY:
+               black = 0x00800080;
+               break;
+       case V4L2_PIX_FMT_YUYV:
+               black = 0x80008000;
+               break;
+       default:
+               black = 0x00000000;
+               break;
+       }
+
+       p = vb2_plane_vaddr(vb, 0);
+       end = p + vb2_plane_size(vb, 0) / 4;
+
+       while (p < end)
+               *p++ = black;
+}
+
+int ipucsi_pause_stream(struct ipucsi *ipucsi)
+{
+       unsigned long flags;
+
+       if (ipucsi->paused)
+               return 0;
+
+       ipu_csi_disable(ipucsi->ipu, ipucsi->id);
+       ipu_idmac_disable_channel(ipucsi->ipuch);
+       ipu_smfc_disable(ipucsi->ipu);
+
+       ipucsi->paused = true;
+
+       /*
+        * If there is a previously active frame, clear it to black and mark
+        * it as done to hand it off to userspace, unless the list is singular
+        */
+
+       spin_lock_irqsave(&ipucsi->lock, flags);
+
+       if (ipucsi->active && !list_is_singular(&ipucsi->capture)) {
+               struct ipucsi_buffer *buf = ipucsi->active;
+
+               ipucsi->active = NULL;
+               list_del_init(&buf->queue);
+
+               spin_unlock_irqrestore(&ipucsi->lock, flags);
+
+               ipucsi_clear_buffer(&buf->vb,
+                                   ipucsi->format.fmt.pix.pixelformat);
+
+               vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
+
+               spin_lock_irqsave(&ipucsi->lock, flags);
+       }
+
+       spin_unlock_irqrestore(&ipucsi->lock, flags);
+
+       return 0;
+}
+
+static void ipucsi_v4l2_dev_notify(struct v4l2_subdev *sd,
+                                  unsigned int notification, void *arg)
+{
+       if (sd == NULL)
+               return;
+
+       if (notification == V4L2_SUBDEV_SYNC_LOCK_NOTIFY) {
+               struct media_entity_graph graph;
+               struct media_entity *entity;
+               struct ipucsi *ipucsi;
+               bool lock = *(bool *)arg;
+
+               /* Find the CSI (first subdevice entity of the graph) */
+               media_entity_graph_walk_start(&graph, &sd->entity);
+               while ((entity = media_entity_graph_walk_next(&graph)) &&
+                      media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV);
+               if (!entity)
+                       return;
+               sd = media_entity_to_v4l2_subdev(entity);
+               ipucsi = container_of(sd, struct ipucsi, subdev);
+
+               if (lock)
+                       ipucsi_resume_stream(ipucsi);
+               else
+                       ipucsi_pause_stream(ipucsi);
+       }
+}
+
 static irqreturn_t ipucsi_new_frame_handler(int irq, void *context)
 {
        struct ipucsi *ipucsi = context;
@@ -735,6 +890,8 @@ static int ipucsi_videobuf_start_streaming(struct vb2_queue 
*vq, unsigned int co
        ipu_smfc_enable(ipucsi->ipu);
        ipu_csi_enable(ipucsi->ipu, ipucsi->id);
 
+       ipucsi->paused = false;
+
        ret = v4l2_media_subdev_s_stream(&ipucsi->subdev.entity, 1);
        if (ret)
                goto free_irq;
@@ -758,6 +915,8 @@ static int ipucsi_videobuf_stop_streaming(struct vb2_queue 
*vq)
        ipu_idmac_disable_channel(ipucsi->ipuch);
        ipu_smfc_disable(ipucsi->ipu);
 
+       ipucsi->paused = false;
+
        spin_lock_irqsave(&ipucsi->lock, flags);
        while (!list_empty(&ipucsi->capture)) {
                struct ipucsi_buffer *buf = list_entry(ipucsi->capture.next,
@@ -1437,6 +1596,7 @@ static int ipucsi_probe(struct platform_device *pdev)
        ipucsi->ipu = ipu;
        ipucsi->dev = &pdev->dev;
        ipucsi->v4l2_dev = ipu_media_get_v4l2_dev();
+       ipucsi->v4l2_dev->notify = ipucsi_v4l2_dev_notify;
        if (!ipucsi->v4l2_dev)
                return -EPROBE_DEFER;
 
-- 
2.0.0.rc2

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to