[PATCH] media: imx: vdic: add frame skipping support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The VDIC can skip frames, allowing to reduce the frame rate at its
output pad by small fractions.

With this commit, once can specify the frame interval with media-ctl.

media-ctl -V "'ipu1_vdic':2 [fmt: UYVY8_2X8/720x576@1/30 field:interlaced-tb]"

The commit is an adaptation for VDIC of the commit fb30ee795576 ([media]
media: imx: csi: add frame skipping support).

Signed-off-by: Gaël PORTAY <gael.portay@xxxxxxxxxxxxx>
---
 drivers/gpu/ipu-v3/ipu-common.c            |  28 +++++
 drivers/staging/media/imx/imx-media-vdic.c | 129 +++++++++++++++++++--
 include/video/imx-ipu-v3.h                 |   1 +
 3 files changed, 149 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/ipu-v3/ipu-common.c b/drivers/gpu/ipu-v3/ipu-common.c
index 474b00e19697..19e1e50dc469 100644
--- a/drivers/gpu/ipu-v3/ipu-common.c
+++ b/drivers/gpu/ipu-v3/ipu-common.c
@@ -35,6 +35,12 @@
 #include <video/imx-ipu-v3.h>
 #include "ipu-prv.h"
 
+/* IPU Register Fields */
+#define VDI_MAX_RATIO_SKIP_MASK			0x000f0000
+#define VDI_MAX_RATIO_SKIP_SHIFT		16
+#define VDI_SKIP_MASK_MASK			0xfff00000
+#define VDI_SKIP_SHIFT_SHIFT			20
+
 static inline u32 ipu_cm_read(struct ipu_soc *ipu, unsigned offset)
 {
 	return readl(ipu->cm_reg + offset);
@@ -267,6 +273,28 @@ int ipu_rot_mode_to_degrees(int *degrees, enum ipu_rotate_mode mode,
 }
 EXPORT_SYMBOL_GPL(ipu_rot_mode_to_degrees);
 
+int ipu_set_skip_vdi(struct ipu_soc *ipu, u32 skip, u32 max_ratio)
+{
+	unsigned long flags;
+	u32 temp;
+
+	if (max_ratio > 15)
+		return -EINVAL;
+
+	spin_lock_irqsave(&ipu->lock, flags);
+
+	temp = ipu_cm_read(ipu, IPU_SKIP);
+	temp &= ~(VDI_MAX_RATIO_SKIP_MASK | VDI_SKIP_MASK_MASK);
+	temp |= (max_ratio << VDI_MAX_RATIO_SKIP_SHIFT) |
+		(skip << VDI_SKIP_SHIFT_SHIFT);
+	ipu_cm_write(ipu, temp, IPU_SKIP);
+
+	spin_unlock_irqrestore(&ipu->lock, flags);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ipu_set_skip_vdi);
+
 struct ipuv3_channel *ipu_idmac_get(struct ipu_soc *ipu, unsigned num)
 {
 	struct ipuv3_channel *channel;
diff --git a/drivers/staging/media/imx/imx-media-vdic.c b/drivers/staging/media/imx/imx-media-vdic.c
index 2808662e2597..a12ac4dd5afd 100644
--- a/drivers/staging/media/imx/imx-media-vdic.c
+++ b/drivers/staging/media/imx/imx-media-vdic.c
@@ -9,6 +9,7 @@
  * (at your option) any later version.
  */
 #include <linux/delay.h>
+#include <linux/gcd.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
@@ -68,6 +69,18 @@ struct vdic_pipeline_ops {
 #define H_ALIGN    1 /* multiple of 2 lines */
 #define S_ALIGN    1 /* multiple of 2 */
 
+/*
+ * struct vdic_skip_desc - VDIC frame skipping descriptor
+ * @keep - number of frames kept per max_ratio frames
+ * @max_ratio - width of skip, written to MAX_RATIO bitfield
+ * @skip - skip pattern written to the SKIP bitfield
+ */
+struct vdic_skip_desc {
+	u8 keep;
+	u8 max_ratio;
+	u8 skip;
+};
+
 struct vdic_priv {
 	struct device        *dev;
 	struct ipu_soc       *ipu;
@@ -111,6 +124,7 @@ struct vdic_priv {
 	struct v4l2_mbus_framefmt format_mbus[VDIC_NUM_PADS];
 	const struct imx_media_pixfmt *cc[VDIC_NUM_PADS];
 	struct v4l2_fract frame_interval[VDIC_NUM_PADS];
+	const struct vdic_skip_desc *skip;
 
 	/* the video device at IDMAC input pad */
 	struct imx_media_video_dev *vdev;
@@ -388,6 +402,7 @@ static int vdic_start(struct vdic_priv *priv)
 		      infmt->width, infmt->height);
 	ipu_vdi_set_field_order(priv->vdi, V4L2_STD_UNKNOWN, infmt->field);
 	ipu_vdi_set_motion(priv->vdi, priv->motion);
+	ipu_set_skip_vdi(priv->ipu, priv->skip->skip, priv->skip->max_ratio - 1);
 
 	ret = priv->ops->setup(priv);
 	if (ret)
@@ -558,6 +573,63 @@ static int vdic_enum_mbus_code(struct v4l2_subdev *sd,
 	return imx_media_enum_ipu_format(&code->code, code->index, CS_SEL_YUV);
 }
 
+static const struct vdic_skip_desc vdic_skip[12] = {
+	{ 1, 1, 0x00 }, /* Keep all frames */
+	{ 5, 6, 0x10 }, /* Skip every sixth frame */
+	{ 4, 5, 0x08 }, /* Skip every fifth frame */
+	{ 3, 4, 0x04 }, /* Skip every fourth frame */
+	{ 2, 3, 0x02 }, /* Skip every third frame */
+	{ 3, 5, 0x0a }, /* Skip frames 1 and 3 of every 5 */
+	{ 1, 2, 0x01 }, /* Skip every second frame */
+	{ 2, 5, 0x0b }, /* Keep frames 1 and 4 of every 5 */
+	{ 1, 3, 0x03 }, /* Keep one in three frames */
+	{ 1, 4, 0x07 }, /* Keep one in four frames */
+	{ 1, 5, 0x0f }, /* Keep one in five frames */
+	{ 1, 6, 0x1f }, /* Keep one in six frames */
+};
+
+static void vdic_apply_skip_interval(const struct vdic_skip_desc *skip,
+				     struct v4l2_fract *interval)
+{
+	unsigned int div;
+
+	interval->numerator *= skip->max_ratio;
+	interval->denominator *= skip->keep;
+
+	/* Reduce fraction to lowest terms */
+	div = gcd(interval->numerator, interval->denominator);
+	if (div > 1) {
+		interval->numerator /= div;
+		interval->denominator /= div;
+	}
+}
+
+static int vdic_enum_frame_interval(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_pad_config *cfg,
+				    struct v4l2_subdev_frame_interval_enum *fie)
+{
+	struct vdic_priv *priv = v4l2_get_subdevdata(sd);
+	struct v4l2_fract *input_fi;
+	int ret = 0;
+
+	if (fie->pad >= VDIC_NUM_PADS ||
+	    fie->index >= (fie->pad != VDIC_SRC_PAD_DIRECT ?
+			   1 : ARRAY_SIZE(vdic_skip)))
+		return -EINVAL;
+
+	mutex_lock(&priv->lock);
+
+	input_fi = &priv->frame_interval[CSI_SINK_PAD];
+	fie->interval = *input_fi;
+
+	if (fie->pad == VDIC_SRC_PAD_DIRECT)
+		vdic_apply_skip_interval(&vdic_skip[fie->index],
+					 &fie->interval);
+
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
 static int vdic_get_fmt(struct v4l2_subdev *sd,
 			struct v4l2_subdev_pad_config *cfg,
 			struct v4l2_subdev_format *sdformat)
@@ -786,6 +858,48 @@ static int vdic_link_validate(struct v4l2_subdev *sd,
 	return ret;
 }
 
+/*
+ * Find the skip pattern to produce the output frame interval closest to the
+ * requested one, for the given input frame interval. Updates the output frame
+ * interval to the exact value.
+ */
+static const struct vdic_skip_desc *vdic_find_best_skip(struct v4l2_fract *in,
+							struct v4l2_fract *out)
+{
+	const struct vdic_skip_desc *skip = &vdic_skip[0], *best_skip = skip;
+	u32 min_err = UINT_MAX;
+	u64 want_us;
+	int i;
+
+	/* Default to 1:1 ratio */
+	if (out->numerator == 0 || out->denominator == 0 ||
+	    in->numerator == 0 || in->denominator == 0) {
+		*out = *in;
+		return best_skip;
+	}
+
+	want_us = div_u64((u64)USEC_PER_SEC * out->numerator, out->denominator);
+
+	/* Find the reduction closest to the requested time per frame */
+	for (i = 0; i < ARRAY_SIZE(vdic_skip); i++, skip++) {
+		u64 tmp, err;
+
+		tmp = div_u64((u64)USEC_PER_SEC * in->numerator *
+			      skip->max_ratio, in->denominator * skip->keep);
+
+		err = abs((s64)tmp - want_us);
+		if (err < min_err) {
+			min_err = err;
+			best_skip = skip;
+		}
+	}
+
+	*out = *in;
+	vdic_apply_skip_interval(best_skip, out);
+
+	return best_skip;
+}
+
 static int vdic_g_frame_interval(struct v4l2_subdev *sd,
 				struct v4l2_subdev_frame_interval *fi)
 {
@@ -826,17 +940,10 @@ static int vdic_s_frame_interval(struct v4l2_subdev *sd,
 		*output_fi = fi->interval;
 		if (priv->csi_direct)
 			output_fi->denominator *= 2;
+		priv->skip = &vdic_skip[0];
 		break;
 	case VDIC_SRC_PAD_DIRECT:
-		/*
-		 * frame rate at output pad is double input
-		 * rate when using direct CSI->VDIC pipeline.
-		 *
-		 * TODO: implement VDIC frame skipping
-		 */
-		fi->interval = *input_fi;
-		if (priv->csi_direct)
-			fi->interval.denominator *= 2;
+		priv->skip = vdic_find_best_skip(output_fi, &fi->interval);
 		break;
 	default:
 		ret = -EINVAL;
@@ -883,6 +990,9 @@ static int vdic_registered(struct v4l2_subdev *sd)
 			priv->frame_interval[i].denominator *= 2;
 	}
 
+	/* disable frame skipping */
+	priv->skip = &vdic_skip[0];
+
 	priv->active_input_pad = VDIC_SINK_PAD_DIRECT;
 
 	ret = vdic_init_controls(priv);
@@ -906,6 +1016,7 @@ static void vdic_unregistered(struct v4l2_subdev *sd)
 static const struct v4l2_subdev_pad_ops vdic_pad_ops = {
 	.init_cfg = imx_media_init_cfg,
 	.enum_mbus_code = vdic_enum_mbus_code,
+	.enum_frame_interval = vdic_enum_frame_interval,
 	.get_fmt = vdic_get_fmt,
 	.set_fmt = vdic_set_fmt,
 	.link_validate = vdic_link_validate,
diff --git a/include/video/imx-ipu-v3.h b/include/video/imx-ipu-v3.h
index c887f4bee5f8..026c4340a8da 100644
--- a/include/video/imx-ipu-v3.h
+++ b/include/video/imx-ipu-v3.h
@@ -203,6 +203,7 @@ int ipu_idmac_channel_irq(struct ipu_soc *ipu, struct ipuv3_channel *channel,
  * IPU Common functions
  */
 int ipu_get_num(struct ipu_soc *ipu);
+int ipu_set_skip_vdi(struct ipu_soc *csi, u32 skip, u32 max_ratio);
 void ipu_set_csi_src_mux(struct ipu_soc *ipu, int csi_id, bool mipi_csi2);
 void ipu_set_ic_src_mux(struct ipu_soc *ipu, int csi_id, bool vdi);
 void ipu_dump(struct ipu_soc *ipu);
-- 
2.20.1




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux