linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/3] media: mgb4: YUV and variable framerate support
@ 2024-03-22 15:10 tumic
  2024-03-22 15:10 ` [PATCH v4 1/3] media: mgb4: Add support for YUV image formats tumic
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: tumic @ 2024-03-22 15:10 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Hans Verkuil
  Cc: linux-media, linux-kernel, Martin Tůma

From: Martin Tůma <martin.tuma@digiteqautomotive.com>

Recent mgb4 FW update added support for the YUV image format and variable
framerates independent of the signal framerate. The following patches extend
the mgb4 driver with support for both features.

Changes in V4:
- Splitted the output frame_rate handling fix from the variable frame rate
  addition patch.

Changes in V3:
- Use div_u64() for 64b division (fixes build error on ARM32)

Changes in V2:
- Added missing stride limit

Martin Tůma (3):
  media: mgb4: Add support for YUV image formats
  media: mgb4: Add support for V4L2_CAP_TIMEPERFRAME
  media: mgb4: Fixed signal frame rate limit handling

 Documentation/admin-guide/media/mgb4.rst |   8 +-
 drivers/media/pci/mgb4/mgb4_core.c       |   2 +-
 drivers/media/pci/mgb4/mgb4_core.h       |   2 +
 drivers/media/pci/mgb4/mgb4_io.h         |  29 +-
 drivers/media/pci/mgb4/mgb4_sysfs_out.c  |   9 +-
 drivers/media/pci/mgb4/mgb4_vin.c        | 205 ++++++++++++---
 drivers/media/pci/mgb4/mgb4_vin.h        |   3 +-
 drivers/media/pci/mgb4/mgb4_vout.c       | 322 ++++++++++++++++++++---
 drivers/media/pci/mgb4/mgb4_vout.h       |   5 +-
 9 files changed, 504 insertions(+), 81 deletions(-)


base-commit: b14257abe7057def6127f6fb2f14f9adc8acabdb
-- 
2.44.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v4 1/3] media: mgb4: Add support for YUV image formats
  2024-03-22 15:10 [PATCH v4 0/3] media: mgb4: YUV and variable framerate support tumic
@ 2024-03-22 15:10 ` tumic
  2024-04-08  9:58   ` Hans Verkuil
  2024-03-22 15:10 ` [PATCH v4 2/3] media: mgb4: Add support for V4L2_CAP_TIMEPERFRAME tumic
  2024-03-22 15:10 ` [PATCH v4 3/3] media: mgb4: Fixed signal frame rate limit handling tumic
  2 siblings, 1 reply; 11+ messages in thread
From: tumic @ 2024-03-22 15:10 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Hans Verkuil
  Cc: linux-media, linux-kernel, Martin Tůma

From: Martin Tůma <martin.tuma@digiteqautomotive.com>

Recent mgb4 firmwares support YUV in addition to the RGB image format. Enable
YUV in the driver when the FW supports it.

Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
---
 drivers/media/pci/mgb4/mgb4_io.h   |   7 ++
 drivers/media/pci/mgb4/mgb4_vin.c  | 116 ++++++++++++++++++++++++-----
 drivers/media/pci/mgb4/mgb4_vout.c | 116 ++++++++++++++++++++++++-----
 3 files changed, 203 insertions(+), 36 deletions(-)

diff --git a/drivers/media/pci/mgb4/mgb4_io.h b/drivers/media/pci/mgb4/mgb4_io.h
index 8698db1be4a9..204613a6685c 100644
--- a/drivers/media/pci/mgb4/mgb4_io.h
+++ b/drivers/media/pci/mgb4/mgb4_io.h
@@ -30,4 +30,11 @@ static inline struct mgb4_frame_buffer *to_frame_buffer(struct vb2_v4l2_buffer *
 	return container_of(vbuf, struct mgb4_frame_buffer, vb);
 }
 
+static inline bool has_yuv(struct mgb4_regs *video)
+{
+	u32 status = mgb4_read_reg(video, 0xD0);
+
+	return (status & (1U << 8));
+}
+
 #endif
diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c
index 2cd78c539889..19692e4dfb59 100644
--- a/drivers/media/pci/mgb4/mgb4_vin.c
+++ b/drivers/media/pci/mgb4/mgb4_vin.c
@@ -186,8 +186,11 @@ static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
 		       struct device *alloc_devs[])
 {
 	struct mgb4_vin_dev *vindev = vb2_get_drv_priv(q);
+	struct mgb4_regs *video = &vindev->mgbdev->video;
+	u32 config = mgb4_read_reg(video, vindev->config->regs.config);
+	u32 pixelsize = (config & (1U << 16)) ? 2 : 4;
 	unsigned int size = (vindev->timings.bt.width + vindev->padding)
-	 * vindev->timings.bt.height * 4;
+			    * vindev->timings.bt.height * pixelsize;
 
 	/*
 	 * If I/O reconfiguration is in process, do not allow to start
@@ -220,9 +223,12 @@ static int buffer_init(struct vb2_buffer *vb)
 static int buffer_prepare(struct vb2_buffer *vb)
 {
 	struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vb->vb2_queue);
+	struct mgb4_regs *video = &vindev->mgbdev->video;
 	struct device *dev = &vindev->mgbdev->pdev->dev;
+	u32 config = mgb4_read_reg(video, vindev->config->regs.config);
+	u32 pixelsize = (config & (1U << 16)) ? 2 : 4;
 	unsigned int size = (vindev->timings.bt.width + vindev->padding)
-	 * vindev->timings.bt.height * 4;
+			    * vindev->timings.bt.height * pixelsize;
 
 	if (vb2_plane_size(vb, 0) < size) {
 		dev_err(dev, "buffer too small (%lu < %u)\n",
@@ -359,22 +365,33 @@ static int vidioc_querycap(struct file *file, void *priv,
 static int vidioc_enum_fmt(struct file *file, void *priv,
 			   struct v4l2_fmtdesc *f)
 {
-	if (f->index != 0)
-		return -EINVAL;
+	struct mgb4_vin_dev *vindev = video_drvdata(file);
+	struct mgb4_regs *video = &vindev->mgbdev->video;
 
-	f->pixelformat = V4L2_PIX_FMT_ABGR32;
+	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
 
-	return 0;
+	if (f->index == 0) {
+		f->pixelformat = V4L2_PIX_FMT_ABGR32;
+		return 0;
+	} else if (f->index == 1 && has_yuv(video)) {
+		f->pixelformat = V4L2_PIX_FMT_YUYV;
+		return 0;
+	} else {
+		return -EINVAL;
+	}
 }
 
 static int vidioc_enum_frameintervals(struct file *file, void *priv,
 				      struct v4l2_frmivalenum *ival)
 {
 	struct mgb4_vin_dev *vindev = video_drvdata(file);
+	struct mgb4_regs *video = &vindev->mgbdev->video;
 
 	if (ival->index != 0)
 		return -EINVAL;
-	if (ival->pixel_format != V4L2_PIX_FMT_ABGR32)
+	if (!(ival->pixel_format == V4L2_PIX_FMT_ABGR32 ||
+	      ((has_yuv(video) && ival->pixel_format == V4L2_PIX_FMT_YUYV))))
 		return -EINVAL;
 	if (ival->width != vindev->timings.bt.width ||
 	    ival->height != vindev->timings.bt.height)
@@ -393,13 +410,32 @@ static int vidioc_enum_frameintervals(struct file *file, void *priv,
 static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
 {
 	struct mgb4_vin_dev *vindev = video_drvdata(file);
+	struct mgb4_regs *video = &vindev->mgbdev->video;
+	u32 config = mgb4_read_reg(video, vindev->config->regs.config);
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
 
-	f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
 	f->fmt.pix.width = vindev->timings.bt.width;
 	f->fmt.pix.height = vindev->timings.bt.height;
 	f->fmt.pix.field = V4L2_FIELD_NONE;
-	f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
-	f->fmt.pix.bytesperline = (f->fmt.pix.width + vindev->padding) * 4;
+
+	if (config & (1U << 16)) {
+		f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+		if (config & (1U << 20)) {
+			f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709;
+		} else {
+			if (config & (1U << 19))
+				f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+			else
+				f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+		}
+		f->fmt.pix.bytesperline = (f->fmt.pix.width + vindev->padding) * 2;
+	} else {
+		f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
+		f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
+		f->fmt.pix.bytesperline = (f->fmt.pix.width + vindev->padding) * 4;
+	}
 	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
 
 	return 0;
@@ -408,14 +444,33 @@ static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
 static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
 {
 	struct mgb4_vin_dev *vindev = video_drvdata(file);
+	struct mgb4_regs *video = &vindev->mgbdev->video;
+	u32 pixelsize;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
 
-	f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
 	f->fmt.pix.width = vindev->timings.bt.width;
 	f->fmt.pix.height = vindev->timings.bt.height;
 	f->fmt.pix.field = V4L2_FIELD_NONE;
-	f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
-	f->fmt.pix.bytesperline = max(f->fmt.pix.width * 4,
-				      ALIGN_DOWN(f->fmt.pix.bytesperline, 4));
+
+	if (has_yuv(video) && f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
+		pixelsize = 2;
+		if (!(f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709 ||
+		      f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M))
+			f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+	} else {
+		pixelsize = 4;
+		f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
+		f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
+	}
+
+	if (f->fmt.pix.bytesperline > f->fmt.pix.width * pixelsize
+	    && f->fmt.pix.bytesperline < f->fmt.pix.width * pixelsize * 2)
+		f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
+						pixelsize);
+	else
+		f->fmt.pix.bytesperline = f->fmt.pix.width * pixelsize;
 	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
 
 	return 0;
@@ -425,13 +480,39 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
 {
 	struct mgb4_vin_dev *vindev = video_drvdata(file);
 	struct mgb4_regs *video = &vindev->mgbdev->video;
+	u32 config, pixelsize;
+	int ret;
 
 	if (vb2_is_busy(&vindev->queue))
 		return -EBUSY;
 
-	vidioc_try_fmt(file, priv, f);
+	ret = vidioc_try_fmt(file, priv, f);
+	if (ret < 0)
+		return ret;
+
+	config = mgb4_read_reg(video, vindev->config->regs.config);
+	if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
+		pixelsize = 2;
+		config |= 1U << 16;
+
+		if (f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709) {
+			config |= 1U << 20;
+			config |= 1U << 19;
+		} else if (f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M) {
+			config &= ~(1U << 20);
+			config |= 1U << 19;
+		} else {
+			config &= ~(1U << 20);
+			config &= ~(1U << 19);
+		}
+	} else {
+		pixelsize = 4;
+		config &= ~(1U << 16);
+	}
+	mgb4_write_reg(video, vindev->config->regs.config, config);
 
-	vindev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width * 4)) / 4;
+	vindev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width
+			   * pixelsize)) / pixelsize;
 	mgb4_write_reg(video, vindev->config->regs.padding, vindev->padding);
 	set_loopback_padding(vindev, vindev->padding);
 
@@ -467,7 +548,8 @@ static int vidioc_enum_framesizes(struct file *file, void *fh,
 {
 	struct mgb4_vin_dev *vindev = video_drvdata(file);
 
-	if (fsize->index != 0 || fsize->pixel_format != V4L2_PIX_FMT_ABGR32)
+	if (fsize->index != 0 || !(fsize->pixel_format == V4L2_PIX_FMT_ABGR32 ||
+				   fsize->pixel_format == V4L2_PIX_FMT_YUYV))
 		return -EINVAL;
 
 	fsize->discrete.width = vindev->timings.bt.width;
diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c
index 241353ee77a5..243fbeaaceb9 100644
--- a/drivers/media/pci/mgb4/mgb4_vout.c
+++ b/drivers/media/pci/mgb4/mgb4_vout.c
@@ -59,7 +59,11 @@ static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
 		       struct device *alloc_devs[])
 {
 	struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(q);
-	unsigned int size;
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	u32 config = mgb4_read_reg(video, voutdev->config->regs.config);
+	u32 pixelsize = (config & (1U << 16)) ? 2 : 4;
+	unsigned int size = (voutdev->width + voutdev->padding) * voutdev->height
+			    * pixelsize;
 
 	/*
 	 * If I/O reconfiguration is in process, do not allow to start
@@ -69,8 +73,6 @@ static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
 	if (test_bit(0, &voutdev->mgbdev->io_reconfig))
 		return -EBUSY;
 
-	size = (voutdev->width + voutdev->padding) * voutdev->height * 4;
-
 	if (*nplanes)
 		return sizes[0] < size ? -EINVAL : 0;
 	*nplanes = 1;
@@ -93,9 +95,11 @@ static int buffer_prepare(struct vb2_buffer *vb)
 {
 	struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(vb->vb2_queue);
 	struct device *dev = &voutdev->mgbdev->pdev->dev;
-	unsigned int size;
-
-	size = (voutdev->width + voutdev->padding) * voutdev->height * 4;
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	u32 config = mgb4_read_reg(video, voutdev->config->regs.config);
+	u32 pixelsize = (config & (1U << 16)) ? 2 : 4;
+	unsigned int size = (voutdev->width + voutdev->padding) * voutdev->height
+			    * pixelsize;
 
 	if (vb2_plane_size(vb, 0) < size) {
 		dev_err(dev, "buffer too small (%lu < %u)\n",
@@ -194,24 +198,53 @@ static int vidioc_querycap(struct file *file, void *priv,
 static int vidioc_enum_fmt(struct file *file, void *priv,
 			   struct v4l2_fmtdesc *f)
 {
-	if (f->index != 0)
-		return -EINVAL;
+	struct mgb4_vin_dev *voutdev = video_drvdata(file);
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
 
-	f->pixelformat = V4L2_PIX_FMT_ABGR32;
+	if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		return -EINVAL;
 
-	return 0;
+	if (f->index == 0) {
+		f->pixelformat = V4L2_PIX_FMT_ABGR32;
+		return 0;
+	} else if (f->index == 1 && has_yuv(video)) {
+		f->pixelformat = V4L2_PIX_FMT_YUYV;
+		return 0;
+	} else {
+		return -EINVAL;
+	}
 }
 
 static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
 {
 	struct mgb4_vout_dev *voutdev = video_drvdata(file);
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	u32 config = mgb4_read_reg(video, voutdev->config->regs.config);
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		return -EINVAL;
 
-	f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
 	f->fmt.pix.width = voutdev->width;
 	f->fmt.pix.height = voutdev->height;
 	f->fmt.pix.field = V4L2_FIELD_NONE;
-	f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
-	f->fmt.pix.bytesperline = (f->fmt.pix.width + voutdev->padding) * 4;
+
+	if (config & (1U << 16)) {
+		f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+		if (config & (1U << 20)) {
+			f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709;
+		} else {
+			if (config & (1U << 19))
+				f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+			else
+				f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+		}
+		f->fmt.pix.bytesperline = (f->fmt.pix.width + voutdev->padding) * 2;
+	} else {
+		f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
+		f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
+		f->fmt.pix.bytesperline = (f->fmt.pix.width + voutdev->padding) * 4;
+	}
+
 	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
 
 	return 0;
@@ -220,14 +253,33 @@ static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
 static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
 {
 	struct mgb4_vout_dev *voutdev = video_drvdata(file);
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	u32 pixelsize;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		return -EINVAL;
 
-	f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
 	f->fmt.pix.width = voutdev->width;
 	f->fmt.pix.height = voutdev->height;
 	f->fmt.pix.field = V4L2_FIELD_NONE;
-	f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
-	f->fmt.pix.bytesperline = max(f->fmt.pix.width * 4,
-				      ALIGN_DOWN(f->fmt.pix.bytesperline, 4));
+
+	if (has_yuv(video) && f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
+		pixelsize = 2;
+		if (!(f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709 ||
+		      f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M))
+			f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+	} else {
+		pixelsize = 4;
+		f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
+		f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
+	}
+
+	if (f->fmt.pix.bytesperline > f->fmt.pix.width * pixelsize
+	    && f->fmt.pix.bytesperline < f->fmt.pix.width * pixelsize * 2)
+		f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
+						pixelsize);
+	else
+		f->fmt.pix.bytesperline = f->fmt.pix.width * pixelsize;
 	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
 
 	return 0;
@@ -237,13 +289,39 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
 {
 	struct mgb4_vout_dev *voutdev = video_drvdata(file);
 	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	u32 config, pixelsize;
+	int ret;
 
 	if (vb2_is_busy(&voutdev->queue))
 		return -EBUSY;
 
-	vidioc_try_fmt(file, priv, f);
+	ret = vidioc_try_fmt(file, priv, f);
+	if (ret < 0)
+		return ret;
+
+	config = mgb4_read_reg(video, voutdev->config->regs.config);
+	if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
+		pixelsize = 2;
+		config |= 1U << 16;
+
+		if (f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709) {
+			config |= 1U << 20;
+			config |= 1U << 19;
+		} else if (f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M) {
+			config &= ~(1U << 20);
+			config |= 1U << 19;
+		} else {
+			config &= ~(1U << 20);
+			config &= ~(1U << 19);
+		}
+	} else {
+		pixelsize = 4;
+		config &= ~(1U << 16);
+	}
+	mgb4_write_reg(video, voutdev->config->regs.config, config);
 
-	voutdev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width * 4)) / 4;
+	voutdev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width
+			    * pixelsize)) / pixelsize;
 	mgb4_write_reg(video, voutdev->config->regs.padding, voutdev->padding);
 
 	return 0;
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 2/3] media: mgb4: Add support for V4L2_CAP_TIMEPERFRAME
  2024-03-22 15:10 [PATCH v4 0/3] media: mgb4: YUV and variable framerate support tumic
  2024-03-22 15:10 ` [PATCH v4 1/3] media: mgb4: Add support for YUV image formats tumic
@ 2024-03-22 15:10 ` tumic
  2024-04-08 10:40   ` Hans Verkuil
  2024-03-22 15:10 ` [PATCH v4 3/3] media: mgb4: Fixed signal frame rate limit handling tumic
  2 siblings, 1 reply; 11+ messages in thread
From: tumic @ 2024-03-22 15:10 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Hans Verkuil
  Cc: linux-media, linux-kernel, Martin Tůma

From: Martin Tůma <martin.tuma@digiteqautomotive.com>

Recent mgb4 firmwares have support for setting a variable framerate independent
of the signal framerate. Add/fix (the mgb4 driver already did promote
V4L2_CAP_TIMEPERFRAME, but it didn't work) support for V4L2_CAP_TIMEPERFRAME to
the driver to enable this feature.

Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
---
 drivers/media/pci/mgb4/mgb4_core.c      |   2 +-
 drivers/media/pci/mgb4/mgb4_core.h      |   2 +
 drivers/media/pci/mgb4/mgb4_io.h        |  24 ++-
 drivers/media/pci/mgb4/mgb4_sysfs_out.c |   4 +-
 drivers/media/pci/mgb4/mgb4_vin.c       |  89 ++++++++---
 drivers/media/pci/mgb4/mgb4_vin.h       |   3 +-
 drivers/media/pci/mgb4/mgb4_vout.c      | 196 +++++++++++++++++++++++-
 drivers/media/pci/mgb4/mgb4_vout.h      |   3 +-
 8 files changed, 287 insertions(+), 36 deletions(-)

diff --git a/drivers/media/pci/mgb4/mgb4_core.c b/drivers/media/pci/mgb4/mgb4_core.c
index 5bfb8a06202e..9c6294009069 100644
--- a/drivers/media/pci/mgb4/mgb4_core.c
+++ b/drivers/media/pci/mgb4/mgb4_core.c
@@ -302,7 +302,7 @@ static int init_i2c(struct mgb4_dev *mgbdev)
 	/* create dummy clock required by the xiic-i2c adapter */
 	snprintf(clk_name, sizeof(clk_name), "xiic-i2c.%d", id);
 	mgbdev->i2c_clk = clk_hw_register_fixed_rate(NULL, clk_name, NULL,
-						     0, 125000000);
+						     0, MGB4_HW_FREQ);
 	if (IS_ERR(mgbdev->i2c_clk)) {
 		dev_err(dev, "failed to register I2C clock\n");
 		return PTR_ERR(mgbdev->i2c_clk);
diff --git a/drivers/media/pci/mgb4/mgb4_core.h b/drivers/media/pci/mgb4/mgb4_core.h
index 2a946e46aec1..b52cd67270b5 100644
--- a/drivers/media/pci/mgb4/mgb4_core.h
+++ b/drivers/media/pci/mgb4/mgb4_core.h
@@ -13,6 +13,8 @@
 #include <linux/dmaengine.h>
 #include "mgb4_regs.h"
 
+#define MGB4_HW_FREQ 125000000
+
 #define MGB4_VIN_DEVICES  2
 #define MGB4_VOUT_DEVICES 2
 
diff --git a/drivers/media/pci/mgb4/mgb4_io.h b/drivers/media/pci/mgb4/mgb4_io.h
index 204613a6685c..dd8696d7df31 100644
--- a/drivers/media/pci/mgb4/mgb4_io.h
+++ b/drivers/media/pci/mgb4/mgb4_io.h
@@ -7,11 +7,9 @@
 #ifndef __MGB4_IO_H__
 #define __MGB4_IO_H__
 
+#include <linux/math64.h>
 #include <media/v4l2-dev.h>
-
-#define MGB4_DEFAULT_WIDTH     1280
-#define MGB4_DEFAULT_HEIGHT    640
-#define MGB4_DEFAULT_PERIOD    (125000000 / 60)
+#include "mgb4_core.h"
 
 /* Register access error indication */
 #define MGB4_ERR_NO_REG        0xFFFFFFFE
@@ -20,6 +18,9 @@
 #define MGB4_ERR_QUEUE_EMPTY   0xFFFFFFFC
 #define MGB4_ERR_QUEUE_FULL    0xFFFFFFFB
 
+#define MGB4_PERIOD(numerator, denominator) \
+	((u32)div_u64((MGB4_HW_FREQ * (u64)(numerator)), (denominator)))
+
 struct mgb4_frame_buffer {
 	struct vb2_v4l2_buffer vb;
 	struct list_head list;
@@ -30,11 +31,24 @@ static inline struct mgb4_frame_buffer *to_frame_buffer(struct vb2_v4l2_buffer *
 	return container_of(vbuf, struct mgb4_frame_buffer, vb);
 }
 
-static inline bool has_yuv(struct mgb4_regs *video)
+static inline bool has_yuv_and_timeperframe(struct mgb4_regs *video)
 {
 	u32 status = mgb4_read_reg(video, 0xD0);
 
 	return (status & (1U << 8));
 }
 
+#define has_yuv(video) has_yuv_and_timeperframe(video)
+#define has_timeperframe(video) has_yuv_and_timeperframe(video)
+
+static inline u32 pixel_size(struct v4l2_dv_timings *timings)
+{
+	struct v4l2_bt_timings *bt = &timings->bt;
+
+	u32 height = bt->height + bt->vfrontporch + bt->vsync + bt->vbackporch;
+	u32 width = bt->width + bt->hfrontporch + bt->hsync + bt->hbackporch;
+
+	return width * height;
+}
+
 #endif
diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
index 9f6e81c57726..f67ff2a48329 100644
--- a/drivers/media/pci/mgb4/mgb4_sysfs_out.c
+++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
@@ -231,7 +231,7 @@ static ssize_t frame_rate_show(struct device *dev,
 	u32 period = mgb4_read_reg(&voutdev->mgbdev->video,
 				   voutdev->config->regs.frame_period);
 
-	return sprintf(buf, "%u\n", 125000000 / period);
+	return sprintf(buf, "%u\n", MGB4_HW_FREQ / period);
 }
 
 /*
@@ -252,7 +252,7 @@ static ssize_t frame_rate_store(struct device *dev,
 		return ret;
 
 	mgb4_write_reg(&voutdev->mgbdev->video,
-		       voutdev->config->regs.frame_period, 125000000 / val);
+		       voutdev->config->regs.frame_period, MGB4_HW_FREQ / val);
 
 	return count;
 }
diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c
index 19692e4dfb59..560c94d21924 100644
--- a/drivers/media/pci/mgb4/mgb4_vin.c
+++ b/drivers/media/pci/mgb4/mgb4_vin.c
@@ -34,8 +34,8 @@ ATTRIBUTE_GROUPS(mgb4_fpdl3_in);
 ATTRIBUTE_GROUPS(mgb4_gmsl_in);
 
 static const struct mgb4_vin_config vin_cfg[] = {
-	{0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28}},
-	{1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58}}
+	{0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28, 0xE8}},
+	{1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58, 0xEC}}
 };
 
 static const struct i2c_board_info fpdl3_deser_info[] = {
@@ -387,6 +387,7 @@ static int vidioc_enum_frameintervals(struct file *file, void *priv,
 {
 	struct mgb4_vin_dev *vindev = video_drvdata(file);
 	struct mgb4_regs *video = &vindev->mgbdev->video;
+	struct v4l2_dv_timings timings;
 
 	if (ival->index != 0)
 		return -EINVAL;
@@ -397,12 +398,15 @@ static int vidioc_enum_frameintervals(struct file *file, void *priv,
 	    ival->height != vindev->timings.bt.height)
 		return -EINVAL;
 
-	ival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS;
-	ival->stepwise.min.denominator = 60;
-	ival->stepwise.min.numerator = 1;
-	ival->stepwise.max.denominator = 1;
-	ival->stepwise.max.numerator = 1;
-	ival->stepwise.step = ival->stepwise.max;
+	get_timings(vindev, &timings);
+
+	ival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+	ival->stepwise.max.denominator = MGB4_HW_FREQ;
+	ival->stepwise.max.numerator = 0xFFFFFFFF;
+	ival->stepwise.min.denominator = timings.bt.pixelclock;
+	ival->stepwise.min.numerator = pixel_size(&timings);
+	ival->stepwise.step.denominator = MGB4_HW_FREQ;
+	ival->stepwise.step.numerator = 1;
 
 	return 0;
 }
@@ -570,27 +574,66 @@ static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
 	return 0;
 }
 
-static int vidioc_parm(struct file *file, void *priv,
-		       struct v4l2_streamparm *parm)
+static int vidioc_g_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *parm)
 {
 	struct mgb4_vin_dev *vindev = video_drvdata(file);
 	struct mgb4_regs *video = &vindev->mgbdev->video;
-	const struct mgb4_vin_regs *regs = &vindev->config->regs;
-	struct v4l2_fract timeperframe = {
-		.numerator = mgb4_read_reg(video, regs->frame_period),
-		.denominator = 125000000,
-	};
+	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
+	struct v4l2_dv_timings timings;
+	u32 timer;
 
 	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
 		return -EINVAL;
 
 	parm->parm.capture.readbuffers = 2;
-	parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
-	parm->parm.capture.timeperframe = timeperframe;
+
+	if (has_timeperframe(video)) {
+		timer = mgb4_read_reg(video, vindev->config->regs.timer);
+		if (timer < 0xFFFF) {
+			get_timings(vindev, &timings);
+			tpf->numerator = pixel_size(&timings);
+			tpf->denominator = timings.bt.pixelclock;
+		} else {
+			tpf->numerator = timer;
+			tpf->denominator = MGB4_HW_FREQ;
+		}
+
+		parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+	}
 
 	return 0;
 }
 
+static int vidioc_s_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *parm)
+{
+	struct mgb4_vin_dev *vindev = video_drvdata(file);
+	struct mgb4_regs *video = &vindev->mgbdev->video;
+	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
+	struct v4l2_dv_timings timings;
+	u32 period, timer;
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (has_timeperframe(video)) {
+		timer = tpf->denominator ?
+			MGB4_PERIOD(tpf->numerator, tpf->denominator) : 0;
+		if (timer) {
+			get_timings(vindev, &timings);
+			period = MGB4_PERIOD(pixel_size(&timings),
+					     timings.bt.pixelclock);
+			if (timer < period)
+				timer = 0;
+		}
+
+		mgb4_write_reg(video, vindev->config->regs.timer, timer);
+	}
+
+	return vidioc_g_parm(file, priv, parm);
+}
+
 static int vidioc_s_dv_timings(struct file *file, void *fh,
 			       struct v4l2_dv_timings *timings)
 {
@@ -674,8 +717,8 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = {
 	.vidioc_expbuf = vb2_ioctl_expbuf,
 	.vidioc_streamon = vb2_ioctl_streamon,
 	.vidioc_streamoff = vb2_ioctl_streamoff,
-	.vidioc_g_parm = vidioc_parm,
-	.vidioc_s_parm = vidioc_parm,
+	.vidioc_g_parm = vidioc_g_parm,
+	.vidioc_s_parm = vidioc_s_parm,
 	.vidioc_dv_timings_cap = vidioc_dv_timings_cap,
 	.vidioc_enum_dv_timings = vidioc_enum_dv_timings,
 	.vidioc_g_dv_timings = vidioc_g_dv_timings,
@@ -858,10 +901,16 @@ static void debugfs_init(struct mgb4_vin_dev *vindev)
 	vindev->regs[7].offset = vindev->config->regs.signal2;
 	vindev->regs[8].name = "PADDING_PIXELS";
 	vindev->regs[8].offset = vindev->config->regs.padding;
+	if (has_timeperframe(video)) {
+		vindev->regs[9].name = "TIMER";
+		vindev->regs[9].offset = vindev->config->regs.timer;
+		vindev->regset.nregs = 10;
+	} else {
+		vindev->regset.nregs = 9;
+	}
 
 	vindev->regset.base = video->membase;
 	vindev->regset.regs = vindev->regs;
-	vindev->regset.nregs = ARRAY_SIZE(vindev->regs);
 
 	debugfs_create_regset32("registers", 0444, vindev->debugfs,
 				&vindev->regset);
diff --git a/drivers/media/pci/mgb4/mgb4_vin.h b/drivers/media/pci/mgb4/mgb4_vin.h
index 0249b400ad4d..9693bd0ce180 100644
--- a/drivers/media/pci/mgb4/mgb4_vin.h
+++ b/drivers/media/pci/mgb4/mgb4_vin.h
@@ -25,6 +25,7 @@ struct mgb4_vin_regs {
 	u32 signal;
 	u32 signal2;
 	u32 padding;
+	u32 timer;
 };
 
 struct mgb4_vin_config {
@@ -59,7 +60,7 @@ struct mgb4_vin_dev {
 #ifdef CONFIG_DEBUG_FS
 	struct dentry *debugfs;
 	struct debugfs_regset32 regset;
-	struct debugfs_reg32 regs[9];
+	struct debugfs_reg32 regs[sizeof(struct mgb4_vin_regs) / 4];
 #endif
 };
 
diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c
index 243fbeaaceb9..a6b55669f0a8 100644
--- a/drivers/media/pci/mgb4/mgb4_vout.c
+++ b/drivers/media/pci/mgb4/mgb4_vout.c
@@ -16,6 +16,7 @@
 #include <media/v4l2-ioctl.h>
 #include <media/videobuf2-v4l2.h>
 #include <media/videobuf2-dma-sg.h>
+#include <media/v4l2-dv-timings.h>
 #include "mgb4_core.h"
 #include "mgb4_dma.h"
 #include "mgb4_sysfs.h"
@@ -23,12 +24,16 @@
 #include "mgb4_cmt.h"
 #include "mgb4_vout.h"
 
+#define DEFAULT_WIDTH     1280
+#define DEFAULT_HEIGHT    640
+#define DEFAULT_PERIOD    (MGB4_HW_FREQ / 60)
+
 ATTRIBUTE_GROUPS(mgb4_fpdl3_out);
 ATTRIBUTE_GROUPS(mgb4_gmsl_out);
 
 static const struct mgb4_vout_config vout_cfg[] = {
-	{0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7c}},
-	{1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8c, 0x90, 0x9c}}
+	{0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7C, 0xE0}},
+	{1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8C, 0x90, 0x9C, 0xE4}}
 };
 
 static const struct i2c_board_info fpdl3_ser_info[] = {
@@ -40,6 +45,49 @@ static const struct mgb4_i2c_kv fpdl3_i2c[] = {
 	{0x05, 0xFF, 0x04}, {0x06, 0xFF, 0x01}, {0xC2, 0xFF, 0x80}
 };
 
+static const struct v4l2_dv_timings_cap video_timings_cap = {
+	.type = V4L2_DV_BT_656_1120,
+	.bt = {
+		.min_width = 320,
+		.max_width = 4096,
+		.min_height = 240,
+		.max_height = 2160,
+		.min_pixelclock = 1843200, /* 320 x 240 x 24Hz */
+		.max_pixelclock = 530841600, /* 4096 x 2160 x 60Hz */
+		.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
+			V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF,
+		.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
+			V4L2_DV_BT_CAP_CUSTOM,
+	},
+};
+
+static void get_timings(struct mgb4_vout_dev *voutdev,
+			struct v4l2_dv_timings *timings)
+{
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	const struct mgb4_vout_regs *regs = &voutdev->config->regs;
+
+	u32 hsync = mgb4_read_reg(video, regs->hsync);
+	u32 vsync = mgb4_read_reg(video, regs->vsync);
+	u32 resolution = mgb4_read_reg(video, regs->resolution);
+
+	memset(timings, 0, sizeof(*timings));
+	timings->type = V4L2_DV_BT_656_1120;
+	timings->bt.width = resolution >> 16;
+	timings->bt.height = resolution & 0xFFFF;
+	if (hsync & (1U << 31))
+		timings->bt.polarities |= V4L2_DV_HSYNC_POS_POL;
+	if (vsync & (1U << 31))
+		timings->bt.polarities |= V4L2_DV_VSYNC_POS_POL;
+	timings->bt.pixelclock = voutdev->freq * 1000;
+	timings->bt.hsync = (hsync & 0x00FF0000) >> 16;
+	timings->bt.vsync = (vsync & 0x00FF0000) >> 16;
+	timings->bt.hbackporch = (hsync & 0x0000FF00) >> 8;
+	timings->bt.hfrontporch = hsync & 0x000000FF;
+	timings->bt.vbackporch = (vsync & 0x0000FF00) >> 8;
+	timings->bt.vfrontporch = vsync & 0x000000FF;
+}
+
 static void return_all_buffers(struct mgb4_vout_dev *voutdev,
 			       enum vb2_buffer_state state)
 {
@@ -345,11 +393,134 @@ static int vidioc_enum_output(struct file *file, void *priv,
 		return -EINVAL;
 
 	out->type = V4L2_OUTPUT_TYPE_ANALOG;
+	out->capabilities = V4L2_OUT_CAP_DV_TIMINGS;
 	strscpy(out->name, "MGB4", sizeof(out->name));
 
 	return 0;
 }
 
+static int vidioc_enum_frameintervals(struct file *file, void *priv,
+				      struct v4l2_frmivalenum *ival)
+{
+	struct mgb4_vout_dev *voutdev = video_drvdata(file);
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	struct v4l2_dv_timings timings;
+
+	if (ival->index != 0)
+		return -EINVAL;
+	if (!(ival->pixel_format == V4L2_PIX_FMT_ABGR32 ||
+	      ((has_yuv(video) && ival->pixel_format == V4L2_PIX_FMT_YUYV))))
+		return -EINVAL;
+	if (ival->width != voutdev->width || ival->height != voutdev->height)
+		return -EINVAL;
+
+	get_timings(voutdev, &timings);
+
+	ival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+	ival->stepwise.max.denominator = MGB4_HW_FREQ;
+	ival->stepwise.max.numerator = 0xFFFFFFFF;
+	ival->stepwise.min.denominator = timings.bt.pixelclock;
+	ival->stepwise.min.numerator = pixel_size(&timings);
+	ival->stepwise.step.denominator = MGB4_HW_FREQ;
+	ival->stepwise.step.numerator = 1;
+
+	return 0;
+}
+
+static int vidioc_g_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *parm)
+{
+	struct mgb4_vout_dev *voutdev = video_drvdata(file);
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
+	struct v4l2_dv_timings timings;
+	u32 timer;
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		return -EINVAL;
+
+	parm->parm.output.writebuffers = 2;
+
+	if (has_timeperframe(video)) {
+		timer = mgb4_read_reg(video, voutdev->config->regs.timer);
+		if (timer < 0xFFFF) {
+			get_timings(voutdev, &timings);
+			tpf->numerator = pixel_size(&timings);
+			tpf->denominator = timings.bt.pixelclock;
+		} else {
+			tpf->numerator = timer;
+			tpf->denominator = MGB4_HW_FREQ;
+		}
+
+		parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+	}
+
+	return 0;
+}
+
+static int vidioc_s_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *parm)
+{
+	struct mgb4_vout_dev *voutdev = video_drvdata(file);
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
+	struct v4l2_dv_timings timings;
+	u32 timer, period;
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		return -EINVAL;
+
+	if (has_timeperframe(video)) {
+		timer = tpf->denominator ?
+			MGB4_PERIOD(tpf->numerator, tpf->denominator) : 0;
+		if (timer) {
+			get_timings(voutdev, &timings);
+			period = MGB4_PERIOD(pixel_size(&timings),
+					     timings.bt.pixelclock);
+			if (timer < period)
+				timer = 0;
+		}
+
+		mgb4_write_reg(video, voutdev->config->regs.timer, timer);
+	}
+
+	return vidioc_g_parm(file, priv, parm);
+}
+
+static int vidioc_g_dv_timings(struct file *file, void *fh,
+			       struct v4l2_dv_timings *timings)
+{
+	struct mgb4_vout_dev *voutdev = video_drvdata(file);
+
+	get_timings(voutdev, timings);
+
+	return 0;
+}
+
+static int vidioc_s_dv_timings(struct file *file, void *fh,
+			       struct v4l2_dv_timings *timings)
+{
+	struct mgb4_vout_dev *voutdev = video_drvdata(file);
+
+	get_timings(voutdev, timings);
+
+	return 0;
+}
+
+static int vidioc_enum_dv_timings(struct file *file, void *fh,
+				  struct v4l2_enum_dv_timings *timings)
+{
+	return v4l2_enum_dv_timings_cap(timings, &video_timings_cap, NULL, NULL);
+}
+
+static int vidioc_dv_timings_cap(struct file *file, void *fh,
+				 struct v4l2_dv_timings_cap *cap)
+{
+	*cap = video_timings_cap;
+
+	return 0;
+}
+
 static const struct v4l2_ioctl_ops video_ioctl_ops = {
 	.vidioc_querycap = vidioc_querycap,
 	.vidioc_enum_fmt_vid_out = vidioc_enum_fmt,
@@ -357,8 +528,15 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = {
 	.vidioc_s_fmt_vid_out = vidioc_s_fmt,
 	.vidioc_g_fmt_vid_out = vidioc_g_fmt,
 	.vidioc_enum_output = vidioc_enum_output,
+	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
 	.vidioc_g_output = vidioc_g_output,
 	.vidioc_s_output = vidioc_s_output,
+	.vidioc_g_parm = vidioc_g_parm,
+	.vidioc_s_parm = vidioc_s_parm,
+	.vidioc_dv_timings_cap = vidioc_dv_timings_cap,
+	.vidioc_enum_dv_timings = vidioc_enum_dv_timings,
+	.vidioc_g_dv_timings = vidioc_g_dv_timings,
+	.vidioc_s_dv_timings = vidioc_s_dv_timings,
 	.vidioc_reqbufs = vb2_ioctl_reqbufs,
 	.vidioc_create_bufs = vb2_ioctl_create_bufs,
 	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
@@ -501,10 +679,10 @@ static void fpga_init(struct mgb4_vout_dev *voutdev)
 
 	mgb4_write_reg(video, regs->config, 0x00000011);
 	mgb4_write_reg(video, regs->resolution,
-		       (MGB4_DEFAULT_WIDTH << 16) | MGB4_DEFAULT_HEIGHT);
+		       (DEFAULT_WIDTH << 16) | DEFAULT_HEIGHT);
 	mgb4_write_reg(video, regs->hsync, 0x00102020);
 	mgb4_write_reg(video, regs->vsync, 0x40020202);
-	mgb4_write_reg(video, regs->frame_period, MGB4_DEFAULT_PERIOD);
+	mgb4_write_reg(video, regs->frame_period, DEFAULT_PERIOD);
 	mgb4_write_reg(video, regs->padding, 0x00000000);
 
 	voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 70000 >> 1) << 1;
@@ -535,12 +713,18 @@ static void debugfs_init(struct mgb4_vout_dev *voutdev)
 	voutdev->regs[4].offset = voutdev->config->regs.vsync;
 	voutdev->regs[5].name = "FRAME_PERIOD";
 	voutdev->regs[5].offset = voutdev->config->regs.frame_period;
-	voutdev->regs[6].name = "PADDING";
+	voutdev->regs[6].name = "PADDING_PIXELS";
 	voutdev->regs[6].offset = voutdev->config->regs.padding;
+	if (has_timeperframe(video)) {
+		voutdev->regs[7].name = "TIMER";
+		voutdev->regs[7].offset = voutdev->config->regs.timer;
+		voutdev->regset.nregs = 8;
+	} else {
+		voutdev->regset.nregs = 7;
+	}
 
 	voutdev->regset.base = video->membase;
 	voutdev->regset.regs = voutdev->regs;
-	voutdev->regset.nregs = ARRAY_SIZE(voutdev->regs);
 
 	debugfs_create_regset32("registers", 0444, voutdev->debugfs,
 				&voutdev->regset);
diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mgb4_vout.h
index b163dee711fd..ab9b58b1deb7 100644
--- a/drivers/media/pci/mgb4/mgb4_vout.h
+++ b/drivers/media/pci/mgb4/mgb4_vout.h
@@ -23,6 +23,7 @@ struct mgb4_vout_regs {
 	u32 hsync;
 	u32 vsync;
 	u32 padding;
+	u32 timer;
 };
 
 struct mgb4_vout_config {
@@ -55,7 +56,7 @@ struct mgb4_vout_dev {
 #ifdef CONFIG_DEBUG_FS
 	struct dentry *debugfs;
 	struct debugfs_regset32 regset;
-	struct debugfs_reg32 regs[7];
+	struct debugfs_reg32 regs[sizeof(struct mgb4_vout_regs) / 4];
 #endif
 };
 
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 3/3] media: mgb4: Fixed signal frame rate limit handling
  2024-03-22 15:10 [PATCH v4 0/3] media: mgb4: YUV and variable framerate support tumic
  2024-03-22 15:10 ` [PATCH v4 1/3] media: mgb4: Add support for YUV image formats tumic
  2024-03-22 15:10 ` [PATCH v4 2/3] media: mgb4: Add support for V4L2_CAP_TIMEPERFRAME tumic
@ 2024-03-22 15:10 ` tumic
  2024-04-08 10:47   ` Hans Verkuil
  2 siblings, 1 reply; 11+ messages in thread
From: tumic @ 2024-03-22 15:10 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Hans Verkuil
  Cc: linux-media, linux-kernel, Martin Tůma

From: Martin Tůma <martin.tuma@digiteqautomotive.com>

Properly document the function of the mgb4 output frame_rate sysfs parameter
and fix the corner case when the frame rate is set to zero causing a "divide
by zero" kernel panic.

Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
---
 Documentation/admin-guide/media/mgb4.rst |  8 ++++++--
 drivers/media/pci/mgb4/mgb4_sysfs_out.c  |  9 +++++----
 drivers/media/pci/mgb4/mgb4_vout.c       | 12 ++++++------
 drivers/media/pci/mgb4/mgb4_vout.h       |  2 +-
 4 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/Documentation/admin-guide/media/mgb4.rst b/Documentation/admin-guide/media/mgb4.rst
index 2977f74d7e26..6fff886003e2 100644
--- a/Documentation/admin-guide/media/mgb4.rst
+++ b/Documentation/admin-guide/media/mgb4.rst
@@ -228,8 +228,12 @@ Common FPDL3/GMSL output parameters
     open.*
 
 **frame_rate** (RW):
-    Output video frame rate in frames per second. The default frame rate is
-    60Hz.
+    Output video signal frame rate limit in frames per second. Due to
+    the limited output pixel clock steps, the card can not always generate
+    a frame rate perfectly matching the value required by the connected display.
+    Using this parameter one can limit the frame rate by "crippling" the signal
+    so that the lines are not equal but the signal appears like having the exact
+    frame rate to the connected display. The default frame rate limit is 60Hz.
 
 **hsync_polarity** (RW):
     HSYNC signal polarity.
diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
index f67ff2a48329..573aa61c69d4 100644
--- a/drivers/media/pci/mgb4/mgb4_sysfs_out.c
+++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
@@ -229,9 +229,9 @@ static ssize_t frame_rate_show(struct device *dev,
 	struct video_device *vdev = to_video_device(dev);
 	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
 	u32 period = mgb4_read_reg(&voutdev->mgbdev->video,
-				   voutdev->config->regs.frame_period);
+				   voutdev->config->regs.frame_limit);
 
-	return sprintf(buf, "%u\n", MGB4_HW_FREQ / period);
+	return sprintf(buf, "%u\n", period ? MGB4_HW_FREQ / period : 0);
 }
 
 /*
@@ -245,14 +245,15 @@ static ssize_t frame_rate_store(struct device *dev,
 	struct video_device *vdev = to_video_device(dev);
 	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
 	unsigned long val;
-	int ret;
+	int limit, ret;
 
 	ret = kstrtoul(buf, 10, &val);
 	if (ret)
 		return ret;
 
+	limit = val ? MGB4_HW_FREQ / val : 0;
 	mgb4_write_reg(&voutdev->mgbdev->video,
-		       voutdev->config->regs.frame_period, MGB4_HW_FREQ / val);
+		       voutdev->config->regs.frame_limit, limit);
 
 	return count;
 }
diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c
index a6b55669f0a8..cd001ceaae63 100644
--- a/drivers/media/pci/mgb4/mgb4_vout.c
+++ b/drivers/media/pci/mgb4/mgb4_vout.c
@@ -680,12 +680,12 @@ static void fpga_init(struct mgb4_vout_dev *voutdev)
 	mgb4_write_reg(video, regs->config, 0x00000011);
 	mgb4_write_reg(video, regs->resolution,
 		       (DEFAULT_WIDTH << 16) | DEFAULT_HEIGHT);
-	mgb4_write_reg(video, regs->hsync, 0x00102020);
-	mgb4_write_reg(video, regs->vsync, 0x40020202);
-	mgb4_write_reg(video, regs->frame_period, DEFAULT_PERIOD);
+	mgb4_write_reg(video, regs->hsync, 0x00283232);
+	mgb4_write_reg(video, regs->vsync, 0x40141F1E);
+	mgb4_write_reg(video, regs->frame_limit, DEFAULT_PERIOD);
 	mgb4_write_reg(video, regs->padding, 0x00000000);
 
-	voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 70000 >> 1) << 1;
+	voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 61150 >> 1) << 1;
 
 	mgb4_write_reg(video, regs->config,
 		       (voutdev->config->id + MGB4_VIN_DEVICES) << 2 | 1 << 4);
@@ -711,8 +711,8 @@ static void debugfs_init(struct mgb4_vout_dev *voutdev)
 	voutdev->regs[3].offset = voutdev->config->regs.hsync;
 	voutdev->regs[4].name = "VIDEO_PARAMS_2";
 	voutdev->regs[4].offset = voutdev->config->regs.vsync;
-	voutdev->regs[5].name = "FRAME_PERIOD";
-	voutdev->regs[5].offset = voutdev->config->regs.frame_period;
+	voutdev->regs[5].name = "FRAME_LIMIT";
+	voutdev->regs[5].offset = voutdev->config->regs.frame_limit;
 	voutdev->regs[6].name = "PADDING_PIXELS";
 	voutdev->regs[6].offset = voutdev->config->regs.padding;
 	if (has_timeperframe(video)) {
diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mgb4_vout.h
index ab9b58b1deb7..adc8fe1e7ae6 100644
--- a/drivers/media/pci/mgb4/mgb4_vout.h
+++ b/drivers/media/pci/mgb4/mgb4_vout.h
@@ -19,7 +19,7 @@ struct mgb4_vout_regs {
 	u32 config;
 	u32 status;
 	u32 resolution;
-	u32 frame_period;
+	u32 frame_limit;
 	u32 hsync;
 	u32 vsync;
 	u32 padding;
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 1/3] media: mgb4: Add support for YUV image formats
  2024-03-22 15:10 ` [PATCH v4 1/3] media: mgb4: Add support for YUV image formats tumic
@ 2024-04-08  9:58   ` Hans Verkuil
  0 siblings, 0 replies; 11+ messages in thread
From: Hans Verkuil @ 2024-04-08  9:58 UTC (permalink / raw)
  To: tumic, Mauro Carvalho Chehab; +Cc: linux-media, linux-kernel, Martin Tůma

Hi Martin,

Just a few small comments:

On 22/03/2024 16:10, tumic@gpxsee.org wrote:
> From: Martin Tůma <martin.tuma@digiteqautomotive.com>
> 
> Recent mgb4 firmwares support YUV in addition to the RGB image format. Enable
> YUV in the driver when the FW supports it.
> 
> Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
> ---
>  drivers/media/pci/mgb4/mgb4_io.h   |   7 ++
>  drivers/media/pci/mgb4/mgb4_vin.c  | 116 ++++++++++++++++++++++++-----
>  drivers/media/pci/mgb4/mgb4_vout.c | 116 ++++++++++++++++++++++++-----
>  3 files changed, 203 insertions(+), 36 deletions(-)
> 
> diff --git a/drivers/media/pci/mgb4/mgb4_io.h b/drivers/media/pci/mgb4/mgb4_io.h
> index 8698db1be4a9..204613a6685c 100644
> --- a/drivers/media/pci/mgb4/mgb4_io.h
> +++ b/drivers/media/pci/mgb4/mgb4_io.h
> @@ -30,4 +30,11 @@ static inline struct mgb4_frame_buffer *to_frame_buffer(struct vb2_v4l2_buffer *
>  	return container_of(vbuf, struct mgb4_frame_buffer, vb);
>  }
>  
> +static inline bool has_yuv(struct mgb4_regs *video)
> +{
> +	u32 status = mgb4_read_reg(video, 0xD0);
> +
> +	return (status & (1U << 8));
> +}
> +
>  #endif
> diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c
> index 2cd78c539889..19692e4dfb59 100644
> --- a/drivers/media/pci/mgb4/mgb4_vin.c
> +++ b/drivers/media/pci/mgb4/mgb4_vin.c
> @@ -186,8 +186,11 @@ static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
>  		       struct device *alloc_devs[])
>  {
>  	struct mgb4_vin_dev *vindev = vb2_get_drv_priv(q);
> +	struct mgb4_regs *video = &vindev->mgbdev->video;
> +	u32 config = mgb4_read_reg(video, vindev->config->regs.config);
> +	u32 pixelsize = (config & (1U << 16)) ? 2 : 4;
>  	unsigned int size = (vindev->timings.bt.width + vindev->padding)
> -	 * vindev->timings.bt.height * 4;
> +			    * vindev->timings.bt.height * pixelsize;
>  
>  	/*
>  	 * If I/O reconfiguration is in process, do not allow to start
> @@ -220,9 +223,12 @@ static int buffer_init(struct vb2_buffer *vb)
>  static int buffer_prepare(struct vb2_buffer *vb)
>  {
>  	struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vb->vb2_queue);
> +	struct mgb4_regs *video = &vindev->mgbdev->video;
>  	struct device *dev = &vindev->mgbdev->pdev->dev;
> +	u32 config = mgb4_read_reg(video, vindev->config->regs.config);
> +	u32 pixelsize = (config & (1U << 16)) ? 2 : 4;
>  	unsigned int size = (vindev->timings.bt.width + vindev->padding)
> -	 * vindev->timings.bt.height * 4;
> +			    * vindev->timings.bt.height * pixelsize;
>  
>  	if (vb2_plane_size(vb, 0) < size) {
>  		dev_err(dev, "buffer too small (%lu < %u)\n",
> @@ -359,22 +365,33 @@ static int vidioc_querycap(struct file *file, void *priv,
>  static int vidioc_enum_fmt(struct file *file, void *priv,
>  			   struct v4l2_fmtdesc *f)
>  {
> -	if (f->index != 0)
> -		return -EINVAL;
> +	struct mgb4_vin_dev *vindev = video_drvdata(file);
> +	struct mgb4_regs *video = &vindev->mgbdev->video;
>  
> -	f->pixelformat = V4L2_PIX_FMT_ABGR32;
> +	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> +		return -EINVAL;

No need to test for this, it is guaranteed to be correct (the v4l2 core handles
that).

>  
> -	return 0;
> +	if (f->index == 0) {
> +		f->pixelformat = V4L2_PIX_FMT_ABGR32;
> +		return 0;
> +	} else if (f->index == 1 && has_yuv(video)) {
> +		f->pixelformat = V4L2_PIX_FMT_YUYV;
> +		return 0;
> +	} else {
> +		return -EINVAL;
> +	}
>  }
>  
>  static int vidioc_enum_frameintervals(struct file *file, void *priv,
>  				      struct v4l2_frmivalenum *ival)
>  {
>  	struct mgb4_vin_dev *vindev = video_drvdata(file);
> +	struct mgb4_regs *video = &vindev->mgbdev->video;
>  
>  	if (ival->index != 0)
>  		return -EINVAL;
> -	if (ival->pixel_format != V4L2_PIX_FMT_ABGR32)
> +	if (!(ival->pixel_format == V4L2_PIX_FMT_ABGR32 ||
> +	      ((has_yuv(video) && ival->pixel_format == V4L2_PIX_FMT_YUYV))))
>  		return -EINVAL;
>  	if (ival->width != vindev->timings.bt.width ||
>  	    ival->height != vindev->timings.bt.height)
> @@ -393,13 +410,32 @@ static int vidioc_enum_frameintervals(struct file *file, void *priv,
>  static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
>  {
>  	struct mgb4_vin_dev *vindev = video_drvdata(file);
> +	struct mgb4_regs *video = &vindev->mgbdev->video;
> +	u32 config = mgb4_read_reg(video, vindev->config->regs.config);
> +
> +	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> +		return -EINVAL;

No need for this test.

>  
> -	f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
>  	f->fmt.pix.width = vindev->timings.bt.width;
>  	f->fmt.pix.height = vindev->timings.bt.height;
>  	f->fmt.pix.field = V4L2_FIELD_NONE;
> -	f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
> -	f->fmt.pix.bytesperline = (f->fmt.pix.width + vindev->padding) * 4;
> +
> +	if (config & (1U << 16)) {
> +		f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
> +		if (config & (1U << 20)) {
> +			f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709;
> +		} else {
> +			if (config & (1U << 19))
> +				f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
> +			else
> +				f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> +		}
> +		f->fmt.pix.bytesperline = (f->fmt.pix.width + vindev->padding) * 2;
> +	} else {
> +		f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
> +		f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
> +		f->fmt.pix.bytesperline = (f->fmt.pix.width + vindev->padding) * 4;
> +	}
>  	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
>  
>  	return 0;
> @@ -408,14 +444,33 @@ static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
>  static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
>  {
>  	struct mgb4_vin_dev *vindev = video_drvdata(file);
> +	struct mgb4_regs *video = &vindev->mgbdev->video;
> +	u32 pixelsize;
> +
> +	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> +		return -EINVAL;

No need for this test.

>  
> -	f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
>  	f->fmt.pix.width = vindev->timings.bt.width;
>  	f->fmt.pix.height = vindev->timings.bt.height;
>  	f->fmt.pix.field = V4L2_FIELD_NONE;
> -	f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
> -	f->fmt.pix.bytesperline = max(f->fmt.pix.width * 4,
> -				      ALIGN_DOWN(f->fmt.pix.bytesperline, 4));
> +
> +	if (has_yuv(video) && f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
> +		pixelsize = 2;
> +		if (!(f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709 ||
> +		      f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M))
> +			f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> +	} else {
> +		pixelsize = 4;
> +		f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
> +		f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
> +	}
> +
> +	if (f->fmt.pix.bytesperline > f->fmt.pix.width * pixelsize
> +	    && f->fmt.pix.bytesperline < f->fmt.pix.width * pixelsize * 2)

Put the '&&' at the end of the previous line instead. checkpatch warns on this.

> +		f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
> +						pixelsize);
> +	else
> +		f->fmt.pix.bytesperline = f->fmt.pix.width * pixelsize;
>  	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
>  
>  	return 0;
> @@ -425,13 +480,39 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
>  {
>  	struct mgb4_vin_dev *vindev = video_drvdata(file);
>  	struct mgb4_regs *video = &vindev->mgbdev->video;
> +	u32 config, pixelsize;
> +	int ret;
>  
>  	if (vb2_is_busy(&vindev->queue))
>  		return -EBUSY;
>  
> -	vidioc_try_fmt(file, priv, f);
> +	ret = vidioc_try_fmt(file, priv, f);
> +	if (ret < 0)
> +		return ret;

It is better to move the 'vb2_is_busy()' call to after the try_fmt.

That way, even if the vb2 is busy, the fmt is still updated by the
try_fmt call.

> +
> +	config = mgb4_read_reg(video, vindev->config->regs.config);
> +	if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
> +		pixelsize = 2;
> +		config |= 1U << 16;
> +
> +		if (f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709) {
> +			config |= 1U << 20;
> +			config |= 1U << 19;
> +		} else if (f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M) {
> +			config &= ~(1U << 20);
> +			config |= 1U << 19;
> +		} else {
> +			config &= ~(1U << 20);
> +			config &= ~(1U << 19);
> +		}
> +	} else {
> +		pixelsize = 4;
> +		config &= ~(1U << 16);
> +	}
> +	mgb4_write_reg(video, vindev->config->regs.config, config);
>  
> -	vindev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width * 4)) / 4;
> +	vindev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width
> +			   * pixelsize)) / pixelsize;
>  	mgb4_write_reg(video, vindev->config->regs.padding, vindev->padding);
>  	set_loopback_padding(vindev, vindev->padding);
>  
> @@ -467,7 +548,8 @@ static int vidioc_enum_framesizes(struct file *file, void *fh,
>  {
>  	struct mgb4_vin_dev *vindev = video_drvdata(file);
>  
> -	if (fsize->index != 0 || fsize->pixel_format != V4L2_PIX_FMT_ABGR32)
> +	if (fsize->index != 0 || !(fsize->pixel_format == V4L2_PIX_FMT_ABGR32 ||
> +				   fsize->pixel_format == V4L2_PIX_FMT_YUYV))
>  		return -EINVAL;
>  
>  	fsize->discrete.width = vindev->timings.bt.width;
> diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c
> index 241353ee77a5..243fbeaaceb9 100644
> --- a/drivers/media/pci/mgb4/mgb4_vout.c
> +++ b/drivers/media/pci/mgb4/mgb4_vout.c
> @@ -59,7 +59,11 @@ static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
>  		       struct device *alloc_devs[])
>  {
>  	struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(q);
> -	unsigned int size;
> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
> +	u32 config = mgb4_read_reg(video, voutdev->config->regs.config);
> +	u32 pixelsize = (config & (1U << 16)) ? 2 : 4;
> +	unsigned int size = (voutdev->width + voutdev->padding) * voutdev->height
> +			    * pixelsize;
>  
>  	/*
>  	 * If I/O reconfiguration is in process, do not allow to start
> @@ -69,8 +73,6 @@ static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
>  	if (test_bit(0, &voutdev->mgbdev->io_reconfig))
>  		return -EBUSY;
>  
> -	size = (voutdev->width + voutdev->padding) * voutdev->height * 4;
> -
>  	if (*nplanes)
>  		return sizes[0] < size ? -EINVAL : 0;
>  	*nplanes = 1;
> @@ -93,9 +95,11 @@ static int buffer_prepare(struct vb2_buffer *vb)
>  {
>  	struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(vb->vb2_queue);
>  	struct device *dev = &voutdev->mgbdev->pdev->dev;
> -	unsigned int size;
> -
> -	size = (voutdev->width + voutdev->padding) * voutdev->height * 4;
> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
> +	u32 config = mgb4_read_reg(video, voutdev->config->regs.config);
> +	u32 pixelsize = (config & (1U << 16)) ? 2 : 4;
> +	unsigned int size = (voutdev->width + voutdev->padding) * voutdev->height
> +			    * pixelsize;
>  
>  	if (vb2_plane_size(vb, 0) < size) {
>  		dev_err(dev, "buffer too small (%lu < %u)\n",
> @@ -194,24 +198,53 @@ static int vidioc_querycap(struct file *file, void *priv,
>  static int vidioc_enum_fmt(struct file *file, void *priv,
>  			   struct v4l2_fmtdesc *f)
>  {
> -	if (f->index != 0)
> -		return -EINVAL;
> +	struct mgb4_vin_dev *voutdev = video_drvdata(file);
> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
>  
> -	f->pixelformat = V4L2_PIX_FMT_ABGR32;
> +	if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
> +		return -EINVAL;

Test is not needed.

>  
> -	return 0;
> +	if (f->index == 0) {
> +		f->pixelformat = V4L2_PIX_FMT_ABGR32;
> +		return 0;
> +	} else if (f->index == 1 && has_yuv(video)) {
> +		f->pixelformat = V4L2_PIX_FMT_YUYV;
> +		return 0;
> +	} else {
> +		return -EINVAL;
> +	}
>  }
>  
>  static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
>  {
>  	struct mgb4_vout_dev *voutdev = video_drvdata(file);
> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
> +	u32 config = mgb4_read_reg(video, voutdev->config->regs.config);
> +
> +	if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
> +		return -EINVAL;

Ditto.

>  
> -	f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
>  	f->fmt.pix.width = voutdev->width;
>  	f->fmt.pix.height = voutdev->height;
>  	f->fmt.pix.field = V4L2_FIELD_NONE;
> -	f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
> -	f->fmt.pix.bytesperline = (f->fmt.pix.width + voutdev->padding) * 4;
> +
> +	if (config & (1U << 16)) {
> +		f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
> +		if (config & (1U << 20)) {
> +			f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709;
> +		} else {
> +			if (config & (1U << 19))
> +				f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
> +			else
> +				f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> +		}
> +		f->fmt.pix.bytesperline = (f->fmt.pix.width + voutdev->padding) * 2;
> +	} else {
> +		f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
> +		f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
> +		f->fmt.pix.bytesperline = (f->fmt.pix.width + voutdev->padding) * 4;
> +	}
> +
>  	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
>  
>  	return 0;
> @@ -220,14 +253,33 @@ static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
>  static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
>  {
>  	struct mgb4_vout_dev *voutdev = video_drvdata(file);
> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
> +	u32 pixelsize;
> +
> +	if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
> +		return -EINVAL;

Ditto.

>  
> -	f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
>  	f->fmt.pix.width = voutdev->width;
>  	f->fmt.pix.height = voutdev->height;
>  	f->fmt.pix.field = V4L2_FIELD_NONE;
> -	f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
> -	f->fmt.pix.bytesperline = max(f->fmt.pix.width * 4,
> -				      ALIGN_DOWN(f->fmt.pix.bytesperline, 4));
> +
> +	if (has_yuv(video) && f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
> +		pixelsize = 2;
> +		if (!(f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709 ||
> +		      f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M))
> +			f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
> +	} else {
> +		pixelsize = 4;
> +		f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
> +		f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
> +	}
> +
> +	if (f->fmt.pix.bytesperline > f->fmt.pix.width * pixelsize
> +	    && f->fmt.pix.bytesperline < f->fmt.pix.width * pixelsize * 2)

Move '&&' to the end of the previous line.

> +		f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
> +						pixelsize);
> +	else
> +		f->fmt.pix.bytesperline = f->fmt.pix.width * pixelsize;
>  	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
>  
>  	return 0;
> @@ -237,13 +289,39 @@ static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
>  {
>  	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>  	struct mgb4_regs *video = &voutdev->mgbdev->video;
> +	u32 config, pixelsize;
> +	int ret;
>  
>  	if (vb2_is_busy(&voutdev->queue))
>  		return -EBUSY;
>  
> -	vidioc_try_fmt(file, priv, f);
> +	ret = vidioc_try_fmt(file, priv, f);
> +	if (ret < 0)
> +		return ret;

Move vb2_is_busy call to here.

> +
> +	config = mgb4_read_reg(video, voutdev->config->regs.config);
> +	if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
> +		pixelsize = 2;
> +		config |= 1U << 16;
> +
> +		if (f->fmt.pix.colorspace == V4L2_COLORSPACE_REC709) {
> +			config |= 1U << 20;
> +			config |= 1U << 19;
> +		} else if (f->fmt.pix.colorspace == V4L2_COLORSPACE_SMPTE170M) {
> +			config &= ~(1U << 20);
> +			config |= 1U << 19;
> +		} else {
> +			config &= ~(1U << 20);
> +			config &= ~(1U << 19);
> +		}
> +	} else {
> +		pixelsize = 4;
> +		config &= ~(1U << 16);
> +	}
> +	mgb4_write_reg(video, voutdev->config->regs.config, config);
>  
> -	voutdev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width * 4)) / 4;
> +	voutdev->padding = (f->fmt.pix.bytesperline - (f->fmt.pix.width
> +			    * pixelsize)) / pixelsize;
>  	mgb4_write_reg(video, voutdev->config->regs.padding, voutdev->padding);
>  
>  	return 0;

Regards,

	Hans

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 2/3] media: mgb4: Add support for V4L2_CAP_TIMEPERFRAME
  2024-03-22 15:10 ` [PATCH v4 2/3] media: mgb4: Add support for V4L2_CAP_TIMEPERFRAME tumic
@ 2024-04-08 10:40   ` Hans Verkuil
  2024-04-08 11:47     ` Martin Tůma
  2024-04-08 15:18     ` Martin Tůma
  0 siblings, 2 replies; 11+ messages in thread
From: Hans Verkuil @ 2024-04-08 10:40 UTC (permalink / raw)
  To: tumic, Mauro Carvalho Chehab; +Cc: linux-media, linux-kernel, Martin Tůma

On 22/03/2024 16:10, tumic@gpxsee.org wrote:
> From: Martin Tůma <martin.tuma@digiteqautomotive.com>
> 
> Recent mgb4 firmwares have support for setting a variable framerate independent
> of the signal framerate. Add/fix (the mgb4 driver already did promote
> V4L2_CAP_TIMEPERFRAME, but it didn't work) support for V4L2_CAP_TIMEPERFRAME to
> the driver to enable this feature.

Both the subject line and commit message talk about V4L2_CAP_TIMEPERFRAME,
but most of the code is about adding dv_timings support. There is a mismatch
there.

And what do you mean with "variable framerate independent of the signal framerate"?

Does that mean that the firmware will skip or repeat frames depending on the
selected framerate?

While I do have a few comments below, I will postpone further review until
I have a clearer understanding of what the actual feature is that you are
implementing.

> 
> Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
> ---
>  drivers/media/pci/mgb4/mgb4_core.c      |   2 +-
>  drivers/media/pci/mgb4/mgb4_core.h      |   2 +
>  drivers/media/pci/mgb4/mgb4_io.h        |  24 ++-
>  drivers/media/pci/mgb4/mgb4_sysfs_out.c |   4 +-
>  drivers/media/pci/mgb4/mgb4_vin.c       |  89 ++++++++---
>  drivers/media/pci/mgb4/mgb4_vin.h       |   3 +-
>  drivers/media/pci/mgb4/mgb4_vout.c      | 196 +++++++++++++++++++++++-
>  drivers/media/pci/mgb4/mgb4_vout.h      |   3 +-
>  8 files changed, 287 insertions(+), 36 deletions(-)
> 
> diff --git a/drivers/media/pci/mgb4/mgb4_core.c b/drivers/media/pci/mgb4/mgb4_core.c
> index 5bfb8a06202e..9c6294009069 100644
> --- a/drivers/media/pci/mgb4/mgb4_core.c
> +++ b/drivers/media/pci/mgb4/mgb4_core.c
> @@ -302,7 +302,7 @@ static int init_i2c(struct mgb4_dev *mgbdev)
>  	/* create dummy clock required by the xiic-i2c adapter */
>  	snprintf(clk_name, sizeof(clk_name), "xiic-i2c.%d", id);
>  	mgbdev->i2c_clk = clk_hw_register_fixed_rate(NULL, clk_name, NULL,
> -						     0, 125000000);
> +						     0, MGB4_HW_FREQ);
>  	if (IS_ERR(mgbdev->i2c_clk)) {
>  		dev_err(dev, "failed to register I2C clock\n");
>  		return PTR_ERR(mgbdev->i2c_clk);
> diff --git a/drivers/media/pci/mgb4/mgb4_core.h b/drivers/media/pci/mgb4/mgb4_core.h
> index 2a946e46aec1..b52cd67270b5 100644
> --- a/drivers/media/pci/mgb4/mgb4_core.h
> +++ b/drivers/media/pci/mgb4/mgb4_core.h
> @@ -13,6 +13,8 @@
>  #include <linux/dmaengine.h>
>  #include "mgb4_regs.h"
>  
> +#define MGB4_HW_FREQ 125000000
> +
>  #define MGB4_VIN_DEVICES  2
>  #define MGB4_VOUT_DEVICES 2
>  
> diff --git a/drivers/media/pci/mgb4/mgb4_io.h b/drivers/media/pci/mgb4/mgb4_io.h
> index 204613a6685c..dd8696d7df31 100644
> --- a/drivers/media/pci/mgb4/mgb4_io.h
> +++ b/drivers/media/pci/mgb4/mgb4_io.h
> @@ -7,11 +7,9 @@
>  #ifndef __MGB4_IO_H__
>  #define __MGB4_IO_H__
>  
> +#include <linux/math64.h>
>  #include <media/v4l2-dev.h>
> -
> -#define MGB4_DEFAULT_WIDTH     1280
> -#define MGB4_DEFAULT_HEIGHT    640
> -#define MGB4_DEFAULT_PERIOD    (125000000 / 60)
> +#include "mgb4_core.h"
>  
>  /* Register access error indication */
>  #define MGB4_ERR_NO_REG        0xFFFFFFFE
> @@ -20,6 +18,9 @@
>  #define MGB4_ERR_QUEUE_EMPTY   0xFFFFFFFC
>  #define MGB4_ERR_QUEUE_FULL    0xFFFFFFFB
>  
> +#define MGB4_PERIOD(numerator, denominator) \
> +	((u32)div_u64((MGB4_HW_FREQ * (u64)(numerator)), (denominator)))
> +
>  struct mgb4_frame_buffer {
>  	struct vb2_v4l2_buffer vb;
>  	struct list_head list;
> @@ -30,11 +31,24 @@ static inline struct mgb4_frame_buffer *to_frame_buffer(struct vb2_v4l2_buffer *
>  	return container_of(vbuf, struct mgb4_frame_buffer, vb);
>  }
>  
> -static inline bool has_yuv(struct mgb4_regs *video)
> +static inline bool has_yuv_and_timeperframe(struct mgb4_regs *video)
>  {
>  	u32 status = mgb4_read_reg(video, 0xD0);
>  
>  	return (status & (1U << 8));
>  }
>  
> +#define has_yuv(video) has_yuv_and_timeperframe(video)
> +#define has_timeperframe(video) has_yuv_and_timeperframe(video)
> +
> +static inline u32 pixel_size(struct v4l2_dv_timings *timings)
> +{
> +	struct v4l2_bt_timings *bt = &timings->bt;
> +
> +	u32 height = bt->height + bt->vfrontporch + bt->vsync + bt->vbackporch;
> +	u32 width = bt->width + bt->hfrontporch + bt->hsync + bt->hbackporch;

You can use V4L2_DV_BT_FRAME_WIDTH and V4L2_DV_BT_FRAME_HEIGHT here.

> +
> +	return width * height;
> +}
> +
>  #endif
> diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
> index 9f6e81c57726..f67ff2a48329 100644
> --- a/drivers/media/pci/mgb4/mgb4_sysfs_out.c
> +++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
> @@ -231,7 +231,7 @@ static ssize_t frame_rate_show(struct device *dev,
>  	u32 period = mgb4_read_reg(&voutdev->mgbdev->video,
>  				   voutdev->config->regs.frame_period);
>  
> -	return sprintf(buf, "%u\n", 125000000 / period);
> +	return sprintf(buf, "%u\n", MGB4_HW_FREQ / period);
>  }
>  
>  /*
> @@ -252,7 +252,7 @@ static ssize_t frame_rate_store(struct device *dev,
>  		return ret;
>  
>  	mgb4_write_reg(&voutdev->mgbdev->video,
> -		       voutdev->config->regs.frame_period, 125000000 / val);
> +		       voutdev->config->regs.frame_period, MGB4_HW_FREQ / val);
>  
>  	return count;
>  }
> diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c
> index 19692e4dfb59..560c94d21924 100644
> --- a/drivers/media/pci/mgb4/mgb4_vin.c
> +++ b/drivers/media/pci/mgb4/mgb4_vin.c
> @@ -34,8 +34,8 @@ ATTRIBUTE_GROUPS(mgb4_fpdl3_in);
>  ATTRIBUTE_GROUPS(mgb4_gmsl_in);
>  
>  static const struct mgb4_vin_config vin_cfg[] = {
> -	{0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28}},
> -	{1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58}}
> +	{0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28, 0xE8}},
> +	{1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58, 0xEC}}
>  };
>  
>  static const struct i2c_board_info fpdl3_deser_info[] = {
> @@ -387,6 +387,7 @@ static int vidioc_enum_frameintervals(struct file *file, void *priv,

Why exactly did this driver support enum_frameintervals again?
Was that due to historical reasons? Normally drivers that use the dv_timings API
do not support this ioctl. It is really meant for sensors with discrete frameintervals.

>  {
>  	struct mgb4_vin_dev *vindev = video_drvdata(file);
>  	struct mgb4_regs *video = &vindev->mgbdev->video;
> +	struct v4l2_dv_timings timings;
>  
>  	if (ival->index != 0)
>  		return -EINVAL;
> @@ -397,12 +398,15 @@ static int vidioc_enum_frameintervals(struct file *file, void *priv,
>  	    ival->height != vindev->timings.bt.height)
>  		return -EINVAL;
>  
> -	ival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS;
> -	ival->stepwise.min.denominator = 60;
> -	ival->stepwise.min.numerator = 1;
> -	ival->stepwise.max.denominator = 1;
> -	ival->stepwise.max.numerator = 1;
> -	ival->stepwise.step = ival->stepwise.max;
> +	get_timings(vindev, &timings);
> +
> +	ival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
> +	ival->stepwise.max.denominator = MGB4_HW_FREQ;
> +	ival->stepwise.max.numerator = 0xFFFFFFFF;

That's a max frame interval of 2062 ms, is that really realistic?

> +	ival->stepwise.min.denominator = timings.bt.pixelclock;
> +	ival->stepwise.min.numerator = pixel_size(&timings);
> +	ival->stepwise.step.denominator = MGB4_HW_FREQ;
> +	ival->stepwise.step.numerator = 1;
>  
>  	return 0;
>  }

Regards,

	Hans

> @@ -570,27 +574,66 @@ static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
>  	return 0;
>  }
>  
> -static int vidioc_parm(struct file *file, void *priv,
> -		       struct v4l2_streamparm *parm)
> +static int vidioc_g_parm(struct file *file, void *priv,
> +			 struct v4l2_streamparm *parm)
>  {
>  	struct mgb4_vin_dev *vindev = video_drvdata(file);
>  	struct mgb4_regs *video = &vindev->mgbdev->video;
> -	const struct mgb4_vin_regs *regs = &vindev->config->regs;
> -	struct v4l2_fract timeperframe = {
> -		.numerator = mgb4_read_reg(video, regs->frame_period),
> -		.denominator = 125000000,
> -	};
> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
> +	struct v4l2_dv_timings timings;
> +	u32 timer;
>  
>  	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
>  		return -EINVAL;
>  
>  	parm->parm.capture.readbuffers = 2;
> -	parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
> -	parm->parm.capture.timeperframe = timeperframe;
> +
> +	if (has_timeperframe(video)) {
> +		timer = mgb4_read_reg(video, vindev->config->regs.timer);
> +		if (timer < 0xFFFF) {
> +			get_timings(vindev, &timings);
> +			tpf->numerator = pixel_size(&timings);
> +			tpf->denominator = timings.bt.pixelclock;
> +		} else {
> +			tpf->numerator = timer;
> +			tpf->denominator = MGB4_HW_FREQ;
> +		}
> +
> +		parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
> +	}
>  
>  	return 0;
>  }
>  
> +static int vidioc_s_parm(struct file *file, void *priv,
> +			 struct v4l2_streamparm *parm)
> +{
> +	struct mgb4_vin_dev *vindev = video_drvdata(file);
> +	struct mgb4_regs *video = &vindev->mgbdev->video;
> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
> +	struct v4l2_dv_timings timings;
> +	u32 period, timer;
> +
> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
> +		return -EINVAL;
> +
> +	if (has_timeperframe(video)) {
> +		timer = tpf->denominator ?
> +			MGB4_PERIOD(tpf->numerator, tpf->denominator) : 0;
> +		if (timer) {
> +			get_timings(vindev, &timings);
> +			period = MGB4_PERIOD(pixel_size(&timings),
> +					     timings.bt.pixelclock);
> +			if (timer < period)
> +				timer = 0;
> +		}
> +
> +		mgb4_write_reg(video, vindev->config->regs.timer, timer);
> +	}
> +
> +	return vidioc_g_parm(file, priv, parm);
> +}
> +
>  static int vidioc_s_dv_timings(struct file *file, void *fh,
>  			       struct v4l2_dv_timings *timings)
>  {
> @@ -674,8 +717,8 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = {
>  	.vidioc_expbuf = vb2_ioctl_expbuf,
>  	.vidioc_streamon = vb2_ioctl_streamon,
>  	.vidioc_streamoff = vb2_ioctl_streamoff,
> -	.vidioc_g_parm = vidioc_parm,
> -	.vidioc_s_parm = vidioc_parm,
> +	.vidioc_g_parm = vidioc_g_parm,
> +	.vidioc_s_parm = vidioc_s_parm,
>  	.vidioc_dv_timings_cap = vidioc_dv_timings_cap,
>  	.vidioc_enum_dv_timings = vidioc_enum_dv_timings,
>  	.vidioc_g_dv_timings = vidioc_g_dv_timings,
> @@ -858,10 +901,16 @@ static void debugfs_init(struct mgb4_vin_dev *vindev)
>  	vindev->regs[7].offset = vindev->config->regs.signal2;
>  	vindev->regs[8].name = "PADDING_PIXELS";
>  	vindev->regs[8].offset = vindev->config->regs.padding;
> +	if (has_timeperframe(video)) {
> +		vindev->regs[9].name = "TIMER";
> +		vindev->regs[9].offset = vindev->config->regs.timer;
> +		vindev->regset.nregs = 10;
> +	} else {
> +		vindev->regset.nregs = 9;
> +	}
>  
>  	vindev->regset.base = video->membase;
>  	vindev->regset.regs = vindev->regs;
> -	vindev->regset.nregs = ARRAY_SIZE(vindev->regs);
>  
>  	debugfs_create_regset32("registers", 0444, vindev->debugfs,
>  				&vindev->regset);
> diff --git a/drivers/media/pci/mgb4/mgb4_vin.h b/drivers/media/pci/mgb4/mgb4_vin.h
> index 0249b400ad4d..9693bd0ce180 100644
> --- a/drivers/media/pci/mgb4/mgb4_vin.h
> +++ b/drivers/media/pci/mgb4/mgb4_vin.h
> @@ -25,6 +25,7 @@ struct mgb4_vin_regs {
>  	u32 signal;
>  	u32 signal2;
>  	u32 padding;
> +	u32 timer;
>  };
>  
>  struct mgb4_vin_config {
> @@ -59,7 +60,7 @@ struct mgb4_vin_dev {
>  #ifdef CONFIG_DEBUG_FS
>  	struct dentry *debugfs;
>  	struct debugfs_regset32 regset;
> -	struct debugfs_reg32 regs[9];
> +	struct debugfs_reg32 regs[sizeof(struct mgb4_vin_regs) / 4];
>  #endif
>  };
>  
> diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c
> index 243fbeaaceb9..a6b55669f0a8 100644
> --- a/drivers/media/pci/mgb4/mgb4_vout.c
> +++ b/drivers/media/pci/mgb4/mgb4_vout.c
> @@ -16,6 +16,7 @@
>  #include <media/v4l2-ioctl.h>
>  #include <media/videobuf2-v4l2.h>
>  #include <media/videobuf2-dma-sg.h>
> +#include <media/v4l2-dv-timings.h>
>  #include "mgb4_core.h"
>  #include "mgb4_dma.h"
>  #include "mgb4_sysfs.h"
> @@ -23,12 +24,16 @@
>  #include "mgb4_cmt.h"
>  #include "mgb4_vout.h"
>  
> +#define DEFAULT_WIDTH     1280
> +#define DEFAULT_HEIGHT    640
> +#define DEFAULT_PERIOD    (MGB4_HW_FREQ / 60)
> +
>  ATTRIBUTE_GROUPS(mgb4_fpdl3_out);
>  ATTRIBUTE_GROUPS(mgb4_gmsl_out);
>  
>  static const struct mgb4_vout_config vout_cfg[] = {
> -	{0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7c}},
> -	{1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8c, 0x90, 0x9c}}
> +	{0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7C, 0xE0}},
> +	{1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8C, 0x90, 0x9C, 0xE4}}
>  };
>  
>  static const struct i2c_board_info fpdl3_ser_info[] = {
> @@ -40,6 +45,49 @@ static const struct mgb4_i2c_kv fpdl3_i2c[] = {
>  	{0x05, 0xFF, 0x04}, {0x06, 0xFF, 0x01}, {0xC2, 0xFF, 0x80}
>  };
>  
> +static const struct v4l2_dv_timings_cap video_timings_cap = {
> +	.type = V4L2_DV_BT_656_1120,
> +	.bt = {
> +		.min_width = 320,
> +		.max_width = 4096,
> +		.min_height = 240,
> +		.max_height = 2160,
> +		.min_pixelclock = 1843200, /* 320 x 240 x 24Hz */
> +		.max_pixelclock = 530841600, /* 4096 x 2160 x 60Hz */
> +		.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
> +			V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF,
> +		.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
> +			V4L2_DV_BT_CAP_CUSTOM,
> +	},
> +};
> +
> +static void get_timings(struct mgb4_vout_dev *voutdev,
> +			struct v4l2_dv_timings *timings)
> +{
> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
> +	const struct mgb4_vout_regs *regs = &voutdev->config->regs;
> +
> +	u32 hsync = mgb4_read_reg(video, regs->hsync);
> +	u32 vsync = mgb4_read_reg(video, regs->vsync);
> +	u32 resolution = mgb4_read_reg(video, regs->resolution);
> +
> +	memset(timings, 0, sizeof(*timings));
> +	timings->type = V4L2_DV_BT_656_1120;
> +	timings->bt.width = resolution >> 16;
> +	timings->bt.height = resolution & 0xFFFF;
> +	if (hsync & (1U << 31))
> +		timings->bt.polarities |= V4L2_DV_HSYNC_POS_POL;
> +	if (vsync & (1U << 31))
> +		timings->bt.polarities |= V4L2_DV_VSYNC_POS_POL;
> +	timings->bt.pixelclock = voutdev->freq * 1000;
> +	timings->bt.hsync = (hsync & 0x00FF0000) >> 16;
> +	timings->bt.vsync = (vsync & 0x00FF0000) >> 16;
> +	timings->bt.hbackporch = (hsync & 0x0000FF00) >> 8;
> +	timings->bt.hfrontporch = hsync & 0x000000FF;
> +	timings->bt.vbackporch = (vsync & 0x0000FF00) >> 8;
> +	timings->bt.vfrontporch = vsync & 0x000000FF;
> +}
> +
>  static void return_all_buffers(struct mgb4_vout_dev *voutdev,
>  			       enum vb2_buffer_state state)
>  {
> @@ -345,11 +393,134 @@ static int vidioc_enum_output(struct file *file, void *priv,
>  		return -EINVAL;
>  
>  	out->type = V4L2_OUTPUT_TYPE_ANALOG;
> +	out->capabilities = V4L2_OUT_CAP_DV_TIMINGS;
>  	strscpy(out->name, "MGB4", sizeof(out->name));
>  
>  	return 0;
>  }
>  
> +static int vidioc_enum_frameintervals(struct file *file, void *priv,
> +				      struct v4l2_frmivalenum *ival)
> +{
> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
> +	struct v4l2_dv_timings timings;
> +
> +	if (ival->index != 0)
> +		return -EINVAL;
> +	if (!(ival->pixel_format == V4L2_PIX_FMT_ABGR32 ||
> +	      ((has_yuv(video) && ival->pixel_format == V4L2_PIX_FMT_YUYV))))
> +		return -EINVAL;
> +	if (ival->width != voutdev->width || ival->height != voutdev->height)
> +		return -EINVAL;
> +
> +	get_timings(voutdev, &timings);
> +
> +	ival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
> +	ival->stepwise.max.denominator = MGB4_HW_FREQ;
> +	ival->stepwise.max.numerator = 0xFFFFFFFF;
> +	ival->stepwise.min.denominator = timings.bt.pixelclock;
> +	ival->stepwise.min.numerator = pixel_size(&timings);
> +	ival->stepwise.step.denominator = MGB4_HW_FREQ;
> +	ival->stepwise.step.numerator = 1;
> +
> +	return 0;
> +}
> +
> +static int vidioc_g_parm(struct file *file, void *priv,
> +			 struct v4l2_streamparm *parm)
> +{
> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
> +	struct v4l2_dv_timings timings;
> +	u32 timer;
> +
> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
> +		return -EINVAL;
> +
> +	parm->parm.output.writebuffers = 2;
> +
> +	if (has_timeperframe(video)) {
> +		timer = mgb4_read_reg(video, voutdev->config->regs.timer);
> +		if (timer < 0xFFFF) {
> +			get_timings(voutdev, &timings);
> +			tpf->numerator = pixel_size(&timings);
> +			tpf->denominator = timings.bt.pixelclock;
> +		} else {
> +			tpf->numerator = timer;
> +			tpf->denominator = MGB4_HW_FREQ;
> +		}
> +
> +		parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
> +	}
> +
> +	return 0;
> +}
> +
> +static int vidioc_s_parm(struct file *file, void *priv,
> +			 struct v4l2_streamparm *parm)
> +{
> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
> +	struct v4l2_dv_timings timings;
> +	u32 timer, period;
> +
> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
> +		return -EINVAL;
> +
> +	if (has_timeperframe(video)) {
> +		timer = tpf->denominator ?
> +			MGB4_PERIOD(tpf->numerator, tpf->denominator) : 0;
> +		if (timer) {
> +			get_timings(voutdev, &timings);
> +			period = MGB4_PERIOD(pixel_size(&timings),
> +					     timings.bt.pixelclock);
> +			if (timer < period)
> +				timer = 0;
> +		}
> +
> +		mgb4_write_reg(video, voutdev->config->regs.timer, timer);
> +	}
> +
> +	return vidioc_g_parm(file, priv, parm);
> +}
> +
> +static int vidioc_g_dv_timings(struct file *file, void *fh,
> +			       struct v4l2_dv_timings *timings)
> +{
> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
> +
> +	get_timings(voutdev, timings);
> +
> +	return 0;
> +}
> +
> +static int vidioc_s_dv_timings(struct file *file, void *fh,
> +			       struct v4l2_dv_timings *timings)
> +{
> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
> +
> +	get_timings(voutdev, timings);
> +
> +	return 0;
> +}
> +
> +static int vidioc_enum_dv_timings(struct file *file, void *fh,
> +				  struct v4l2_enum_dv_timings *timings)
> +{
> +	return v4l2_enum_dv_timings_cap(timings, &video_timings_cap, NULL, NULL);
> +}
> +
> +static int vidioc_dv_timings_cap(struct file *file, void *fh,
> +				 struct v4l2_dv_timings_cap *cap)
> +{
> +	*cap = video_timings_cap;
> +
> +	return 0;
> +}
> +
>  static const struct v4l2_ioctl_ops video_ioctl_ops = {
>  	.vidioc_querycap = vidioc_querycap,
>  	.vidioc_enum_fmt_vid_out = vidioc_enum_fmt,
> @@ -357,8 +528,15 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = {
>  	.vidioc_s_fmt_vid_out = vidioc_s_fmt,
>  	.vidioc_g_fmt_vid_out = vidioc_g_fmt,
>  	.vidioc_enum_output = vidioc_enum_output,
> +	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
>  	.vidioc_g_output = vidioc_g_output,
>  	.vidioc_s_output = vidioc_s_output,
> +	.vidioc_g_parm = vidioc_g_parm,
> +	.vidioc_s_parm = vidioc_s_parm,
> +	.vidioc_dv_timings_cap = vidioc_dv_timings_cap,
> +	.vidioc_enum_dv_timings = vidioc_enum_dv_timings,
> +	.vidioc_g_dv_timings = vidioc_g_dv_timings,
> +	.vidioc_s_dv_timings = vidioc_s_dv_timings,
>  	.vidioc_reqbufs = vb2_ioctl_reqbufs,
>  	.vidioc_create_bufs = vb2_ioctl_create_bufs,
>  	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> @@ -501,10 +679,10 @@ static void fpga_init(struct mgb4_vout_dev *voutdev)
>  
>  	mgb4_write_reg(video, regs->config, 0x00000011);
>  	mgb4_write_reg(video, regs->resolution,
> -		       (MGB4_DEFAULT_WIDTH << 16) | MGB4_DEFAULT_HEIGHT);
> +		       (DEFAULT_WIDTH << 16) | DEFAULT_HEIGHT);
>  	mgb4_write_reg(video, regs->hsync, 0x00102020);
>  	mgb4_write_reg(video, regs->vsync, 0x40020202);
> -	mgb4_write_reg(video, regs->frame_period, MGB4_DEFAULT_PERIOD);
> +	mgb4_write_reg(video, regs->frame_period, DEFAULT_PERIOD);
>  	mgb4_write_reg(video, regs->padding, 0x00000000);
>  
>  	voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 70000 >> 1) << 1;
> @@ -535,12 +713,18 @@ static void debugfs_init(struct mgb4_vout_dev *voutdev)
>  	voutdev->regs[4].offset = voutdev->config->regs.vsync;
>  	voutdev->regs[5].name = "FRAME_PERIOD";
>  	voutdev->regs[5].offset = voutdev->config->regs.frame_period;
> -	voutdev->regs[6].name = "PADDING";
> +	voutdev->regs[6].name = "PADDING_PIXELS";
>  	voutdev->regs[6].offset = voutdev->config->regs.padding;
> +	if (has_timeperframe(video)) {
> +		voutdev->regs[7].name = "TIMER";
> +		voutdev->regs[7].offset = voutdev->config->regs.timer;
> +		voutdev->regset.nregs = 8;
> +	} else {
> +		voutdev->regset.nregs = 7;
> +	}
>  
>  	voutdev->regset.base = video->membase;
>  	voutdev->regset.regs = voutdev->regs;
> -	voutdev->regset.nregs = ARRAY_SIZE(voutdev->regs);
>  
>  	debugfs_create_regset32("registers", 0444, voutdev->debugfs,
>  				&voutdev->regset);
> diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mgb4_vout.h
> index b163dee711fd..ab9b58b1deb7 100644
> --- a/drivers/media/pci/mgb4/mgb4_vout.h
> +++ b/drivers/media/pci/mgb4/mgb4_vout.h
> @@ -23,6 +23,7 @@ struct mgb4_vout_regs {
>  	u32 hsync;
>  	u32 vsync;
>  	u32 padding;
> +	u32 timer;
>  };
>  
>  struct mgb4_vout_config {
> @@ -55,7 +56,7 @@ struct mgb4_vout_dev {
>  #ifdef CONFIG_DEBUG_FS
>  	struct dentry *debugfs;
>  	struct debugfs_regset32 regset;
> -	struct debugfs_reg32 regs[7];
> +	struct debugfs_reg32 regs[sizeof(struct mgb4_vout_regs) / 4];
>  #endif
>  };
>  


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 3/3] media: mgb4: Fixed signal frame rate limit handling
  2024-03-22 15:10 ` [PATCH v4 3/3] media: mgb4: Fixed signal frame rate limit handling tumic
@ 2024-04-08 10:47   ` Hans Verkuil
  2024-04-08 12:16     ` Martin Tůma
  2024-04-09  9:57     ` Martin Tůma
  0 siblings, 2 replies; 11+ messages in thread
From: Hans Verkuil @ 2024-04-08 10:47 UTC (permalink / raw)
  To: tumic, Mauro Carvalho Chehab; +Cc: linux-media, linux-kernel, Martin Tůma

On 22/03/2024 16:10, tumic@gpxsee.org wrote:
> From: Martin Tůma <martin.tuma@digiteqautomotive.com>
> 
> Properly document the function of the mgb4 output frame_rate sysfs parameter
> and fix the corner case when the frame rate is set to zero causing a "divide
> by zero" kernel panic.

This is mixing a fix and a documentation improvement into one patch. This
should be split.

Also, shouldn't the fix be either part of the previous patch or come before
that patch?

> 
> Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
> ---
>  Documentation/admin-guide/media/mgb4.rst |  8 ++++++--
>  drivers/media/pci/mgb4/mgb4_sysfs_out.c  |  9 +++++----
>  drivers/media/pci/mgb4/mgb4_vout.c       | 12 ++++++------
>  drivers/media/pci/mgb4/mgb4_vout.h       |  2 +-
>  4 files changed, 18 insertions(+), 13 deletions(-)
> 
> diff --git a/Documentation/admin-guide/media/mgb4.rst b/Documentation/admin-guide/media/mgb4.rst
> index 2977f74d7e26..6fff886003e2 100644
> --- a/Documentation/admin-guide/media/mgb4.rst
> +++ b/Documentation/admin-guide/media/mgb4.rst
> @@ -228,8 +228,12 @@ Common FPDL3/GMSL output parameters
>      open.*
>  
>  **frame_rate** (RW):
> -    Output video frame rate in frames per second. The default frame rate is
> -    60Hz.
> +    Output video signal frame rate limit in frames per second. Due to
> +    the limited output pixel clock steps, the card can not always generate
> +    a frame rate perfectly matching the value required by the connected display.
> +    Using this parameter one can limit the frame rate by "crippling" the signal
> +    so that the lines are not equal but the signal appears like having the exact
> +    frame rate to the connected display. The default frame rate limit is 60Hz.

It's not clear what is meant with 'crippling'. Normally when dealing with video
framerates the driver will pick the closest video timing to the requested framerate.
It is understood that you can't always get the exact framerate, so drivers can
make adjustments.

Regards,

	Hans

>  
>  **hsync_polarity** (RW):
>      HSYNC signal polarity.
> diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
> index f67ff2a48329..573aa61c69d4 100644
> --- a/drivers/media/pci/mgb4/mgb4_sysfs_out.c
> +++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
> @@ -229,9 +229,9 @@ static ssize_t frame_rate_show(struct device *dev,
>  	struct video_device *vdev = to_video_device(dev);
>  	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
>  	u32 period = mgb4_read_reg(&voutdev->mgbdev->video,
> -				   voutdev->config->regs.frame_period);
> +				   voutdev->config->regs.frame_limit);
>  
> -	return sprintf(buf, "%u\n", MGB4_HW_FREQ / period);
> +	return sprintf(buf, "%u\n", period ? MGB4_HW_FREQ / period : 0);
>  }
>  
>  /*
> @@ -245,14 +245,15 @@ static ssize_t frame_rate_store(struct device *dev,
>  	struct video_device *vdev = to_video_device(dev);
>  	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
>  	unsigned long val;
> -	int ret;
> +	int limit, ret;
>  
>  	ret = kstrtoul(buf, 10, &val);
>  	if (ret)
>  		return ret;
>  
> +	limit = val ? MGB4_HW_FREQ / val : 0;
>  	mgb4_write_reg(&voutdev->mgbdev->video,
> -		       voutdev->config->regs.frame_period, MGB4_HW_FREQ / val);
> +		       voutdev->config->regs.frame_limit, limit);
>  
>  	return count;
>  }
> diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c
> index a6b55669f0a8..cd001ceaae63 100644
> --- a/drivers/media/pci/mgb4/mgb4_vout.c
> +++ b/drivers/media/pci/mgb4/mgb4_vout.c
> @@ -680,12 +680,12 @@ static void fpga_init(struct mgb4_vout_dev *voutdev)
>  	mgb4_write_reg(video, regs->config, 0x00000011);
>  	mgb4_write_reg(video, regs->resolution,
>  		       (DEFAULT_WIDTH << 16) | DEFAULT_HEIGHT);
> -	mgb4_write_reg(video, regs->hsync, 0x00102020);
> -	mgb4_write_reg(video, regs->vsync, 0x40020202);
> -	mgb4_write_reg(video, regs->frame_period, DEFAULT_PERIOD);
> +	mgb4_write_reg(video, regs->hsync, 0x00283232);
> +	mgb4_write_reg(video, regs->vsync, 0x40141F1E);
> +	mgb4_write_reg(video, regs->frame_limit, DEFAULT_PERIOD);
>  	mgb4_write_reg(video, regs->padding, 0x00000000);
>  
> -	voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 70000 >> 1) << 1;
> +	voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 61150 >> 1) << 1;
>  
>  	mgb4_write_reg(video, regs->config,
>  		       (voutdev->config->id + MGB4_VIN_DEVICES) << 2 | 1 << 4);
> @@ -711,8 +711,8 @@ static void debugfs_init(struct mgb4_vout_dev *voutdev)
>  	voutdev->regs[3].offset = voutdev->config->regs.hsync;
>  	voutdev->regs[4].name = "VIDEO_PARAMS_2";
>  	voutdev->regs[4].offset = voutdev->config->regs.vsync;
> -	voutdev->regs[5].name = "FRAME_PERIOD";
> -	voutdev->regs[5].offset = voutdev->config->regs.frame_period;
> +	voutdev->regs[5].name = "FRAME_LIMIT";
> +	voutdev->regs[5].offset = voutdev->config->regs.frame_limit;
>  	voutdev->regs[6].name = "PADDING_PIXELS";
>  	voutdev->regs[6].offset = voutdev->config->regs.padding;
>  	if (has_timeperframe(video)) {
> diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mgb4_vout.h
> index ab9b58b1deb7..adc8fe1e7ae6 100644
> --- a/drivers/media/pci/mgb4/mgb4_vout.h
> +++ b/drivers/media/pci/mgb4/mgb4_vout.h
> @@ -19,7 +19,7 @@ struct mgb4_vout_regs {
>  	u32 config;
>  	u32 status;
>  	u32 resolution;
> -	u32 frame_period;
> +	u32 frame_limit;
>  	u32 hsync;
>  	u32 vsync;
>  	u32 padding;


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 2/3] media: mgb4: Add support for V4L2_CAP_TIMEPERFRAME
  2024-04-08 10:40   ` Hans Verkuil
@ 2024-04-08 11:47     ` Martin Tůma
  2024-04-08 15:18     ` Martin Tůma
  1 sibling, 0 replies; 11+ messages in thread
From: Martin Tůma @ 2024-04-08 11:47 UTC (permalink / raw)
  To: Hans Verkuil, Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Martin Tůma

Hi Hans,

On 08. 04. 24 12:40, Hans Verkuil wrote:
> On 22/03/2024 16:10, tumic@gpxsee.org wrote:
>> From: Martin Tůma <martin.tuma@digiteqautomotive.com>
>>
>> Recent mgb4 firmwares have support for setting a variable framerate independent
>> of the signal framerate. Add/fix (the mgb4 driver already did promote
>> V4L2_CAP_TIMEPERFRAME, but it didn't work) support for V4L2_CAP_TIMEPERFRAME to
>> the driver to enable this feature.
> 
> Both the subject line and commit message talk about V4L2_CAP_TIMEPERFRAME,
> but most of the code is about adding dv_timings support. There is a mismatch
> there.
> 

The reason why this patch exists is really to implement/fix 
V4L2_CAP_TIMEPERFRAME. As the dv_timings support is needed for the 
implementation (and was missing for the outputs) it is also added as a 
"side-effect".

> And what do you mean with "variable framerate independent of the signal framerate"?
> 
> Does that mean that the firmware will skip or repeat frames depending on the
> selected framerate?

Exactly. The FW now has a new register where you can set an arbitrary 
frame rate and you get the frames from the card with a different rate 
than the signal on the line when this register is set.

M.

> 
> While I do have a few comments below, I will postpone further review until
> I have a clearer understanding of what the actual feature is that you are
> implementing.
> 
>>
>> Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
>> ---
>>   drivers/media/pci/mgb4/mgb4_core.c      |   2 +-
>>   drivers/media/pci/mgb4/mgb4_core.h      |   2 +
>>   drivers/media/pci/mgb4/mgb4_io.h        |  24 ++-
>>   drivers/media/pci/mgb4/mgb4_sysfs_out.c |   4 +-
>>   drivers/media/pci/mgb4/mgb4_vin.c       |  89 ++++++++---
>>   drivers/media/pci/mgb4/mgb4_vin.h       |   3 +-
>>   drivers/media/pci/mgb4/mgb4_vout.c      | 196 +++++++++++++++++++++++-
>>   drivers/media/pci/mgb4/mgb4_vout.h      |   3 +-
>>   8 files changed, 287 insertions(+), 36 deletions(-)
>>
>> diff --git a/drivers/media/pci/mgb4/mgb4_core.c b/drivers/media/pci/mgb4/mgb4_core.c
>> index 5bfb8a06202e..9c6294009069 100644
>> --- a/drivers/media/pci/mgb4/mgb4_core.c
>> +++ b/drivers/media/pci/mgb4/mgb4_core.c
>> @@ -302,7 +302,7 @@ static int init_i2c(struct mgb4_dev *mgbdev)
>>   	/* create dummy clock required by the xiic-i2c adapter */
>>   	snprintf(clk_name, sizeof(clk_name), "xiic-i2c.%d", id);
>>   	mgbdev->i2c_clk = clk_hw_register_fixed_rate(NULL, clk_name, NULL,
>> -						     0, 125000000);
>> +						     0, MGB4_HW_FREQ);
>>   	if (IS_ERR(mgbdev->i2c_clk)) {
>>   		dev_err(dev, "failed to register I2C clock\n");
>>   		return PTR_ERR(mgbdev->i2c_clk);
>> diff --git a/drivers/media/pci/mgb4/mgb4_core.h b/drivers/media/pci/mgb4/mgb4_core.h
>> index 2a946e46aec1..b52cd67270b5 100644
>> --- a/drivers/media/pci/mgb4/mgb4_core.h
>> +++ b/drivers/media/pci/mgb4/mgb4_core.h
>> @@ -13,6 +13,8 @@
>>   #include <linux/dmaengine.h>
>>   #include "mgb4_regs.h"
>>   
>> +#define MGB4_HW_FREQ 125000000
>> +
>>   #define MGB4_VIN_DEVICES  2
>>   #define MGB4_VOUT_DEVICES 2
>>   
>> diff --git a/drivers/media/pci/mgb4/mgb4_io.h b/drivers/media/pci/mgb4/mgb4_io.h
>> index 204613a6685c..dd8696d7df31 100644
>> --- a/drivers/media/pci/mgb4/mgb4_io.h
>> +++ b/drivers/media/pci/mgb4/mgb4_io.h
>> @@ -7,11 +7,9 @@
>>   #ifndef __MGB4_IO_H__
>>   #define __MGB4_IO_H__
>>   
>> +#include <linux/math64.h>
>>   #include <media/v4l2-dev.h>
>> -
>> -#define MGB4_DEFAULT_WIDTH     1280
>> -#define MGB4_DEFAULT_HEIGHT    640
>> -#define MGB4_DEFAULT_PERIOD    (125000000 / 60)
>> +#include "mgb4_core.h"
>>   
>>   /* Register access error indication */
>>   #define MGB4_ERR_NO_REG        0xFFFFFFFE
>> @@ -20,6 +18,9 @@
>>   #define MGB4_ERR_QUEUE_EMPTY   0xFFFFFFFC
>>   #define MGB4_ERR_QUEUE_FULL    0xFFFFFFFB
>>   
>> +#define MGB4_PERIOD(numerator, denominator) \
>> +	((u32)div_u64((MGB4_HW_FREQ * (u64)(numerator)), (denominator)))
>> +
>>   struct mgb4_frame_buffer {
>>   	struct vb2_v4l2_buffer vb;
>>   	struct list_head list;
>> @@ -30,11 +31,24 @@ static inline struct mgb4_frame_buffer *to_frame_buffer(struct vb2_v4l2_buffer *
>>   	return container_of(vbuf, struct mgb4_frame_buffer, vb);
>>   }
>>   
>> -static inline bool has_yuv(struct mgb4_regs *video)
>> +static inline bool has_yuv_and_timeperframe(struct mgb4_regs *video)
>>   {
>>   	u32 status = mgb4_read_reg(video, 0xD0);
>>   
>>   	return (status & (1U << 8));
>>   }
>>   
>> +#define has_yuv(video) has_yuv_and_timeperframe(video)
>> +#define has_timeperframe(video) has_yuv_and_timeperframe(video)
>> +
>> +static inline u32 pixel_size(struct v4l2_dv_timings *timings)
>> +{
>> +	struct v4l2_bt_timings *bt = &timings->bt;
>> +
>> +	u32 height = bt->height + bt->vfrontporch + bt->vsync + bt->vbackporch;
>> +	u32 width = bt->width + bt->hfrontporch + bt->hsync + bt->hbackporch;
> 
> You can use V4L2_DV_BT_FRAME_WIDTH and V4L2_DV_BT_FRAME_HEIGHT here.
> 
>> +
>> +	return width * height;
>> +}
>> +
>>   #endif
>> diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
>> index 9f6e81c57726..f67ff2a48329 100644
>> --- a/drivers/media/pci/mgb4/mgb4_sysfs_out.c
>> +++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
>> @@ -231,7 +231,7 @@ static ssize_t frame_rate_show(struct device *dev,
>>   	u32 period = mgb4_read_reg(&voutdev->mgbdev->video,
>>   				   voutdev->config->regs.frame_period);
>>   
>> -	return sprintf(buf, "%u\n", 125000000 / period);
>> +	return sprintf(buf, "%u\n", MGB4_HW_FREQ / period);
>>   }
>>   
>>   /*
>> @@ -252,7 +252,7 @@ static ssize_t frame_rate_store(struct device *dev,
>>   		return ret;
>>   
>>   	mgb4_write_reg(&voutdev->mgbdev->video,
>> -		       voutdev->config->regs.frame_period, 125000000 / val);
>> +		       voutdev->config->regs.frame_period, MGB4_HW_FREQ / val);
>>   
>>   	return count;
>>   }
>> diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c
>> index 19692e4dfb59..560c94d21924 100644
>> --- a/drivers/media/pci/mgb4/mgb4_vin.c
>> +++ b/drivers/media/pci/mgb4/mgb4_vin.c
>> @@ -34,8 +34,8 @@ ATTRIBUTE_GROUPS(mgb4_fpdl3_in);
>>   ATTRIBUTE_GROUPS(mgb4_gmsl_in);
>>   
>>   static const struct mgb4_vin_config vin_cfg[] = {
>> -	{0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28}},
>> -	{1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58}}
>> +	{0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28, 0xE8}},
>> +	{1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58, 0xEC}}
>>   };
>>   
>>   static const struct i2c_board_info fpdl3_deser_info[] = {
>> @@ -387,6 +387,7 @@ static int vidioc_enum_frameintervals(struct file *file, void *priv,
> 
> Why exactly did this driver support enum_frameintervals again?
> Was that due to historical reasons? Normally drivers that use the dv_timings API
> do not support this ioctl. It is really meant for sensors with discrete frameintervals.
> 
>>   {
>>   	struct mgb4_vin_dev *vindev = video_drvdata(file);
>>   	struct mgb4_regs *video = &vindev->mgbdev->video;
>> +	struct v4l2_dv_timings timings;
>>   
>>   	if (ival->index != 0)
>>   		return -EINVAL;
>> @@ -397,12 +398,15 @@ static int vidioc_enum_frameintervals(struct file *file, void *priv,
>>   	    ival->height != vindev->timings.bt.height)
>>   		return -EINVAL;
>>   
>> -	ival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS;
>> -	ival->stepwise.min.denominator = 60;
>> -	ival->stepwise.min.numerator = 1;
>> -	ival->stepwise.max.denominator = 1;
>> -	ival->stepwise.max.numerator = 1;
>> -	ival->stepwise.step = ival->stepwise.max;
>> +	get_timings(vindev, &timings);
>> +
>> +	ival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
>> +	ival->stepwise.max.denominator = MGB4_HW_FREQ;
>> +	ival->stepwise.max.numerator = 0xFFFFFFFF;
> 
> That's a max frame interval of 2062 ms, is that really realistic?
> 
>> +	ival->stepwise.min.denominator = timings.bt.pixelclock;
>> +	ival->stepwise.min.numerator = pixel_size(&timings);
>> +	ival->stepwise.step.denominator = MGB4_HW_FREQ;
>> +	ival->stepwise.step.numerator = 1;
>>   
>>   	return 0;
>>   }
> 
> Regards,
> 
> 	Hans
> 
>> @@ -570,27 +574,66 @@ static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
>>   	return 0;
>>   }
>>   
>> -static int vidioc_parm(struct file *file, void *priv,
>> -		       struct v4l2_streamparm *parm)
>> +static int vidioc_g_parm(struct file *file, void *priv,
>> +			 struct v4l2_streamparm *parm)
>>   {
>>   	struct mgb4_vin_dev *vindev = video_drvdata(file);
>>   	struct mgb4_regs *video = &vindev->mgbdev->video;
>> -	const struct mgb4_vin_regs *regs = &vindev->config->regs;
>> -	struct v4l2_fract timeperframe = {
>> -		.numerator = mgb4_read_reg(video, regs->frame_period),
>> -		.denominator = 125000000,
>> -	};
>> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
>> +	struct v4l2_dv_timings timings;
>> +	u32 timer;
>>   
>>   	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
>>   		return -EINVAL;
>>   
>>   	parm->parm.capture.readbuffers = 2;
>> -	parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
>> -	parm->parm.capture.timeperframe = timeperframe;
>> +
>> +	if (has_timeperframe(video)) {
>> +		timer = mgb4_read_reg(video, vindev->config->regs.timer);
>> +		if (timer < 0xFFFF) {
>> +			get_timings(vindev, &timings);
>> +			tpf->numerator = pixel_size(&timings);
>> +			tpf->denominator = timings.bt.pixelclock;
>> +		} else {
>> +			tpf->numerator = timer;
>> +			tpf->denominator = MGB4_HW_FREQ;
>> +		}
>> +
>> +		parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
>> +	}
>>   
>>   	return 0;
>>   }
>>   
>> +static int vidioc_s_parm(struct file *file, void *priv,
>> +			 struct v4l2_streamparm *parm)
>> +{
>> +	struct mgb4_vin_dev *vindev = video_drvdata(file);
>> +	struct mgb4_regs *video = &vindev->mgbdev->video;
>> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
>> +	struct v4l2_dv_timings timings;
>> +	u32 period, timer;
>> +
>> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
>> +		return -EINVAL;
>> +
>> +	if (has_timeperframe(video)) {
>> +		timer = tpf->denominator ?
>> +			MGB4_PERIOD(tpf->numerator, tpf->denominator) : 0;
>> +		if (timer) {
>> +			get_timings(vindev, &timings);
>> +			period = MGB4_PERIOD(pixel_size(&timings),
>> +					     timings.bt.pixelclock);
>> +			if (timer < period)
>> +				timer = 0;
>> +		}
>> +
>> +		mgb4_write_reg(video, vindev->config->regs.timer, timer);
>> +	}
>> +
>> +	return vidioc_g_parm(file, priv, parm);
>> +}
>> +
>>   static int vidioc_s_dv_timings(struct file *file, void *fh,
>>   			       struct v4l2_dv_timings *timings)
>>   {
>> @@ -674,8 +717,8 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = {
>>   	.vidioc_expbuf = vb2_ioctl_expbuf,
>>   	.vidioc_streamon = vb2_ioctl_streamon,
>>   	.vidioc_streamoff = vb2_ioctl_streamoff,
>> -	.vidioc_g_parm = vidioc_parm,
>> -	.vidioc_s_parm = vidioc_parm,
>> +	.vidioc_g_parm = vidioc_g_parm,
>> +	.vidioc_s_parm = vidioc_s_parm,
>>   	.vidioc_dv_timings_cap = vidioc_dv_timings_cap,
>>   	.vidioc_enum_dv_timings = vidioc_enum_dv_timings,
>>   	.vidioc_g_dv_timings = vidioc_g_dv_timings,
>> @@ -858,10 +901,16 @@ static void debugfs_init(struct mgb4_vin_dev *vindev)
>>   	vindev->regs[7].offset = vindev->config->regs.signal2;
>>   	vindev->regs[8].name = "PADDING_PIXELS";
>>   	vindev->regs[8].offset = vindev->config->regs.padding;
>> +	if (has_timeperframe(video)) {
>> +		vindev->regs[9].name = "TIMER";
>> +		vindev->regs[9].offset = vindev->config->regs.timer;
>> +		vindev->regset.nregs = 10;
>> +	} else {
>> +		vindev->regset.nregs = 9;
>> +	}
>>   
>>   	vindev->regset.base = video->membase;
>>   	vindev->regset.regs = vindev->regs;
>> -	vindev->regset.nregs = ARRAY_SIZE(vindev->regs);
>>   
>>   	debugfs_create_regset32("registers", 0444, vindev->debugfs,
>>   				&vindev->regset);
>> diff --git a/drivers/media/pci/mgb4/mgb4_vin.h b/drivers/media/pci/mgb4/mgb4_vin.h
>> index 0249b400ad4d..9693bd0ce180 100644
>> --- a/drivers/media/pci/mgb4/mgb4_vin.h
>> +++ b/drivers/media/pci/mgb4/mgb4_vin.h
>> @@ -25,6 +25,7 @@ struct mgb4_vin_regs {
>>   	u32 signal;
>>   	u32 signal2;
>>   	u32 padding;
>> +	u32 timer;
>>   };
>>   
>>   struct mgb4_vin_config {
>> @@ -59,7 +60,7 @@ struct mgb4_vin_dev {
>>   #ifdef CONFIG_DEBUG_FS
>>   	struct dentry *debugfs;
>>   	struct debugfs_regset32 regset;
>> -	struct debugfs_reg32 regs[9];
>> +	struct debugfs_reg32 regs[sizeof(struct mgb4_vin_regs) / 4];
>>   #endif
>>   };
>>   
>> diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c
>> index 243fbeaaceb9..a6b55669f0a8 100644
>> --- a/drivers/media/pci/mgb4/mgb4_vout.c
>> +++ b/drivers/media/pci/mgb4/mgb4_vout.c
>> @@ -16,6 +16,7 @@
>>   #include <media/v4l2-ioctl.h>
>>   #include <media/videobuf2-v4l2.h>
>>   #include <media/videobuf2-dma-sg.h>
>> +#include <media/v4l2-dv-timings.h>
>>   #include "mgb4_core.h"
>>   #include "mgb4_dma.h"
>>   #include "mgb4_sysfs.h"
>> @@ -23,12 +24,16 @@
>>   #include "mgb4_cmt.h"
>>   #include "mgb4_vout.h"
>>   
>> +#define DEFAULT_WIDTH     1280
>> +#define DEFAULT_HEIGHT    640
>> +#define DEFAULT_PERIOD    (MGB4_HW_FREQ / 60)
>> +
>>   ATTRIBUTE_GROUPS(mgb4_fpdl3_out);
>>   ATTRIBUTE_GROUPS(mgb4_gmsl_out);
>>   
>>   static const struct mgb4_vout_config vout_cfg[] = {
>> -	{0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7c}},
>> -	{1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8c, 0x90, 0x9c}}
>> +	{0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7C, 0xE0}},
>> +	{1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8C, 0x90, 0x9C, 0xE4}}
>>   };
>>   
>>   static const struct i2c_board_info fpdl3_ser_info[] = {
>> @@ -40,6 +45,49 @@ static const struct mgb4_i2c_kv fpdl3_i2c[] = {
>>   	{0x05, 0xFF, 0x04}, {0x06, 0xFF, 0x01}, {0xC2, 0xFF, 0x80}
>>   };
>>   
>> +static const struct v4l2_dv_timings_cap video_timings_cap = {
>> +	.type = V4L2_DV_BT_656_1120,
>> +	.bt = {
>> +		.min_width = 320,
>> +		.max_width = 4096,
>> +		.min_height = 240,
>> +		.max_height = 2160,
>> +		.min_pixelclock = 1843200, /* 320 x 240 x 24Hz */
>> +		.max_pixelclock = 530841600, /* 4096 x 2160 x 60Hz */
>> +		.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
>> +			V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF,
>> +		.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
>> +			V4L2_DV_BT_CAP_CUSTOM,
>> +	},
>> +};
>> +
>> +static void get_timings(struct mgb4_vout_dev *voutdev,
>> +			struct v4l2_dv_timings *timings)
>> +{
>> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
>> +	const struct mgb4_vout_regs *regs = &voutdev->config->regs;
>> +
>> +	u32 hsync = mgb4_read_reg(video, regs->hsync);
>> +	u32 vsync = mgb4_read_reg(video, regs->vsync);
>> +	u32 resolution = mgb4_read_reg(video, regs->resolution);
>> +
>> +	memset(timings, 0, sizeof(*timings));
>> +	timings->type = V4L2_DV_BT_656_1120;
>> +	timings->bt.width = resolution >> 16;
>> +	timings->bt.height = resolution & 0xFFFF;
>> +	if (hsync & (1U << 31))
>> +		timings->bt.polarities |= V4L2_DV_HSYNC_POS_POL;
>> +	if (vsync & (1U << 31))
>> +		timings->bt.polarities |= V4L2_DV_VSYNC_POS_POL;
>> +	timings->bt.pixelclock = voutdev->freq * 1000;
>> +	timings->bt.hsync = (hsync & 0x00FF0000) >> 16;
>> +	timings->bt.vsync = (vsync & 0x00FF0000) >> 16;
>> +	timings->bt.hbackporch = (hsync & 0x0000FF00) >> 8;
>> +	timings->bt.hfrontporch = hsync & 0x000000FF;
>> +	timings->bt.vbackporch = (vsync & 0x0000FF00) >> 8;
>> +	timings->bt.vfrontporch = vsync & 0x000000FF;
>> +}
>> +
>>   static void return_all_buffers(struct mgb4_vout_dev *voutdev,
>>   			       enum vb2_buffer_state state)
>>   {
>> @@ -345,11 +393,134 @@ static int vidioc_enum_output(struct file *file, void *priv,
>>   		return -EINVAL;
>>   
>>   	out->type = V4L2_OUTPUT_TYPE_ANALOG;
>> +	out->capabilities = V4L2_OUT_CAP_DV_TIMINGS;
>>   	strscpy(out->name, "MGB4", sizeof(out->name));
>>   
>>   	return 0;
>>   }
>>   
>> +static int vidioc_enum_frameintervals(struct file *file, void *priv,
>> +				      struct v4l2_frmivalenum *ival)
>> +{
>> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
>> +	struct v4l2_dv_timings timings;
>> +
>> +	if (ival->index != 0)
>> +		return -EINVAL;
>> +	if (!(ival->pixel_format == V4L2_PIX_FMT_ABGR32 ||
>> +	      ((has_yuv(video) && ival->pixel_format == V4L2_PIX_FMT_YUYV))))
>> +		return -EINVAL;
>> +	if (ival->width != voutdev->width || ival->height != voutdev->height)
>> +		return -EINVAL;
>> +
>> +	get_timings(voutdev, &timings);
>> +
>> +	ival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
>> +	ival->stepwise.max.denominator = MGB4_HW_FREQ;
>> +	ival->stepwise.max.numerator = 0xFFFFFFFF;
>> +	ival->stepwise.min.denominator = timings.bt.pixelclock;
>> +	ival->stepwise.min.numerator = pixel_size(&timings);
>> +	ival->stepwise.step.denominator = MGB4_HW_FREQ;
>> +	ival->stepwise.step.numerator = 1;
>> +
>> +	return 0;
>> +}
>> +
>> +static int vidioc_g_parm(struct file *file, void *priv,
>> +			 struct v4l2_streamparm *parm)
>> +{
>> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
>> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
>> +	struct v4l2_dv_timings timings;
>> +	u32 timer;
>> +
>> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
>> +		return -EINVAL;
>> +
>> +	parm->parm.output.writebuffers = 2;
>> +
>> +	if (has_timeperframe(video)) {
>> +		timer = mgb4_read_reg(video, voutdev->config->regs.timer);
>> +		if (timer < 0xFFFF) {
>> +			get_timings(voutdev, &timings);
>> +			tpf->numerator = pixel_size(&timings);
>> +			tpf->denominator = timings.bt.pixelclock;
>> +		} else {
>> +			tpf->numerator = timer;
>> +			tpf->denominator = MGB4_HW_FREQ;
>> +		}
>> +
>> +		parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int vidioc_s_parm(struct file *file, void *priv,
>> +			 struct v4l2_streamparm *parm)
>> +{
>> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
>> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
>> +	struct v4l2_dv_timings timings;
>> +	u32 timer, period;
>> +
>> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
>> +		return -EINVAL;
>> +
>> +	if (has_timeperframe(video)) {
>> +		timer = tpf->denominator ?
>> +			MGB4_PERIOD(tpf->numerator, tpf->denominator) : 0;
>> +		if (timer) {
>> +			get_timings(voutdev, &timings);
>> +			period = MGB4_PERIOD(pixel_size(&timings),
>> +					     timings.bt.pixelclock);
>> +			if (timer < period)
>> +				timer = 0;
>> +		}
>> +
>> +		mgb4_write_reg(video, voutdev->config->regs.timer, timer);
>> +	}
>> +
>> +	return vidioc_g_parm(file, priv, parm);
>> +}
>> +
>> +static int vidioc_g_dv_timings(struct file *file, void *fh,
>> +			       struct v4l2_dv_timings *timings)
>> +{
>> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>> +
>> +	get_timings(voutdev, timings);
>> +
>> +	return 0;
>> +}
>> +
>> +static int vidioc_s_dv_timings(struct file *file, void *fh,
>> +			       struct v4l2_dv_timings *timings)
>> +{
>> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>> +
>> +	get_timings(voutdev, timings);
>> +
>> +	return 0;
>> +}
>> +
>> +static int vidioc_enum_dv_timings(struct file *file, void *fh,
>> +				  struct v4l2_enum_dv_timings *timings)
>> +{
>> +	return v4l2_enum_dv_timings_cap(timings, &video_timings_cap, NULL, NULL);
>> +}
>> +
>> +static int vidioc_dv_timings_cap(struct file *file, void *fh,
>> +				 struct v4l2_dv_timings_cap *cap)
>> +{
>> +	*cap = video_timings_cap;
>> +
>> +	return 0;
>> +}
>> +
>>   static const struct v4l2_ioctl_ops video_ioctl_ops = {
>>   	.vidioc_querycap = vidioc_querycap,
>>   	.vidioc_enum_fmt_vid_out = vidioc_enum_fmt,
>> @@ -357,8 +528,15 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = {
>>   	.vidioc_s_fmt_vid_out = vidioc_s_fmt,
>>   	.vidioc_g_fmt_vid_out = vidioc_g_fmt,
>>   	.vidioc_enum_output = vidioc_enum_output,
>> +	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
>>   	.vidioc_g_output = vidioc_g_output,
>>   	.vidioc_s_output = vidioc_s_output,
>> +	.vidioc_g_parm = vidioc_g_parm,
>> +	.vidioc_s_parm = vidioc_s_parm,
>> +	.vidioc_dv_timings_cap = vidioc_dv_timings_cap,
>> +	.vidioc_enum_dv_timings = vidioc_enum_dv_timings,
>> +	.vidioc_g_dv_timings = vidioc_g_dv_timings,
>> +	.vidioc_s_dv_timings = vidioc_s_dv_timings,
>>   	.vidioc_reqbufs = vb2_ioctl_reqbufs,
>>   	.vidioc_create_bufs = vb2_ioctl_create_bufs,
>>   	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
>> @@ -501,10 +679,10 @@ static void fpga_init(struct mgb4_vout_dev *voutdev)
>>   
>>   	mgb4_write_reg(video, regs->config, 0x00000011);
>>   	mgb4_write_reg(video, regs->resolution,
>> -		       (MGB4_DEFAULT_WIDTH << 16) | MGB4_DEFAULT_HEIGHT);
>> +		       (DEFAULT_WIDTH << 16) | DEFAULT_HEIGHT);
>>   	mgb4_write_reg(video, regs->hsync, 0x00102020);
>>   	mgb4_write_reg(video, regs->vsync, 0x40020202);
>> -	mgb4_write_reg(video, regs->frame_period, MGB4_DEFAULT_PERIOD);
>> +	mgb4_write_reg(video, regs->frame_period, DEFAULT_PERIOD);
>>   	mgb4_write_reg(video, regs->padding, 0x00000000);
>>   
>>   	voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 70000 >> 1) << 1;
>> @@ -535,12 +713,18 @@ static void debugfs_init(struct mgb4_vout_dev *voutdev)
>>   	voutdev->regs[4].offset = voutdev->config->regs.vsync;
>>   	voutdev->regs[5].name = "FRAME_PERIOD";
>>   	voutdev->regs[5].offset = voutdev->config->regs.frame_period;
>> -	voutdev->regs[6].name = "PADDING";
>> +	voutdev->regs[6].name = "PADDING_PIXELS";
>>   	voutdev->regs[6].offset = voutdev->config->regs.padding;
>> +	if (has_timeperframe(video)) {
>> +		voutdev->regs[7].name = "TIMER";
>> +		voutdev->regs[7].offset = voutdev->config->regs.timer;
>> +		voutdev->regset.nregs = 8;
>> +	} else {
>> +		voutdev->regset.nregs = 7;
>> +	}
>>   
>>   	voutdev->regset.base = video->membase;
>>   	voutdev->regset.regs = voutdev->regs;
>> -	voutdev->regset.nregs = ARRAY_SIZE(voutdev->regs);
>>   
>>   	debugfs_create_regset32("registers", 0444, voutdev->debugfs,
>>   				&voutdev->regset);
>> diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mgb4_vout.h
>> index b163dee711fd..ab9b58b1deb7 100644
>> --- a/drivers/media/pci/mgb4/mgb4_vout.h
>> +++ b/drivers/media/pci/mgb4/mgb4_vout.h
>> @@ -23,6 +23,7 @@ struct mgb4_vout_regs {
>>   	u32 hsync;
>>   	u32 vsync;
>>   	u32 padding;
>> +	u32 timer;
>>   };
>>   
>>   struct mgb4_vout_config {
>> @@ -55,7 +56,7 @@ struct mgb4_vout_dev {
>>   #ifdef CONFIG_DEBUG_FS
>>   	struct dentry *debugfs;
>>   	struct debugfs_regset32 regset;
>> -	struct debugfs_reg32 regs[7];
>> +	struct debugfs_reg32 regs[sizeof(struct mgb4_vout_regs) / 4];
>>   #endif
>>   };
>>   
> 


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 3/3] media: mgb4: Fixed signal frame rate limit handling
  2024-04-08 10:47   ` Hans Verkuil
@ 2024-04-08 12:16     ` Martin Tůma
  2024-04-09  9:57     ` Martin Tůma
  1 sibling, 0 replies; 11+ messages in thread
From: Martin Tůma @ 2024-04-08 12:16 UTC (permalink / raw)
  To: Hans Verkuil, Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Martin Tůma

Hi,

On 08. 04. 24 12:47, Hans Verkuil wrote:
> On 22/03/2024 16:10, tumic@gpxsee.org wrote:
>> From: Martin Tůma <martin.tuma@digiteqautomotive.com>
>>
>> Properly document the function of the mgb4 output frame_rate sysfs parameter
>> and fix the corner case when the frame rate is set to zero causing a "divide
>> by zero" kernel panic.
> 
> This is mixing a fix and a documentation improvement into one patch. This
> should be split.
> 

Well, the "core" of the patch is really just the correct description 
what the "magic" frame_rate sysfs parameter really does. The code "just" 
adjusts the internal naming to better fit the real purpose of the parameter.

The remaining lines change the default timings parameters so that the 
default gives a correct "timings equation" without the need of 
"crippling" the signal using the frame rate limit. While the numbers are 
different, the outcome is the same - the defaults are still for the same 
default display (Škoda Octavia) as before, but the numbers make more 
sense (and the signal on the line is not "crippled")

> Also, shouldn't the fix be either part of the previous patch or come before
> that patch?
>

While "detected" when fiddling with the previous patch and "by name" it 
looks like it has something to do with it, in reality it is something 
different. In the V1 patch it was even send together with the 
V4L2_CAP_TIMEPERFRAME patch but I have then explicitly splitted it to 
make clear it is something different. This change (and our "magic" 
frame_rate sysfs parameter) affects the signal on the line while the 
V4L2_CAP_TIMEPERFRAME is about the frame rate the user uses to provide 
the frames to the card and should be a standard v4l2 mechanism.

M.

>>
>> Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
>> ---
>>   Documentation/admin-guide/media/mgb4.rst |  8 ++++++--
>>   drivers/media/pci/mgb4/mgb4_sysfs_out.c  |  9 +++++----
>>   drivers/media/pci/mgb4/mgb4_vout.c       | 12 ++++++------
>>   drivers/media/pci/mgb4/mgb4_vout.h       |  2 +-
>>   4 files changed, 18 insertions(+), 13 deletions(-)
>>
>> diff --git a/Documentation/admin-guide/media/mgb4.rst b/Documentation/admin-guide/media/mgb4.rst
>> index 2977f74d7e26..6fff886003e2 100644
>> --- a/Documentation/admin-guide/media/mgb4.rst
>> +++ b/Documentation/admin-guide/media/mgb4.rst
>> @@ -228,8 +228,12 @@ Common FPDL3/GMSL output parameters
>>       open.*
>>   
>>   **frame_rate** (RW):
>> -    Output video frame rate in frames per second. The default frame rate is
>> -    60Hz.
>> +    Output video signal frame rate limit in frames per second. Due to
>> +    the limited output pixel clock steps, the card can not always generate
>> +    a frame rate perfectly matching the value required by the connected display.
>> +    Using this parameter one can limit the frame rate by "crippling" the signal
>> +    so that the lines are not equal but the signal appears like having the exact
>> +    frame rate to the connected display. The default frame rate limit is 60Hz.
> 
> It's not clear what is meant with 'crippling'. Normally when dealing with video
> framerates the driver will pick the closest video timing to the requested framerate.
> It is understood that you can't always get the exact framerate, so drivers can
> make adjustments.
> 
> Regards,
> 
> 	Hans
> 
>>   
>>   **hsync_polarity** (RW):
>>       HSYNC signal polarity.
>> diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
>> index f67ff2a48329..573aa61c69d4 100644
>> --- a/drivers/media/pci/mgb4/mgb4_sysfs_out.c
>> +++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
>> @@ -229,9 +229,9 @@ static ssize_t frame_rate_show(struct device *dev,
>>   	struct video_device *vdev = to_video_device(dev);
>>   	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
>>   	u32 period = mgb4_read_reg(&voutdev->mgbdev->video,
>> -				   voutdev->config->regs.frame_period);
>> +				   voutdev->config->regs.frame_limit);
>>   
>> -	return sprintf(buf, "%u\n", MGB4_HW_FREQ / period);
>> +	return sprintf(buf, "%u\n", period ? MGB4_HW_FREQ / period : 0);
>>   }
>>   
>>   /*
>> @@ -245,14 +245,15 @@ static ssize_t frame_rate_store(struct device *dev,
>>   	struct video_device *vdev = to_video_device(dev);
>>   	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
>>   	unsigned long val;
>> -	int ret;
>> +	int limit, ret;
>>   
>>   	ret = kstrtoul(buf, 10, &val);
>>   	if (ret)
>>   		return ret;
>>   
>> +	limit = val ? MGB4_HW_FREQ / val : 0;
>>   	mgb4_write_reg(&voutdev->mgbdev->video,
>> -		       voutdev->config->regs.frame_period, MGB4_HW_FREQ / val);
>> +		       voutdev->config->regs.frame_limit, limit);
>>   
>>   	return count;
>>   }
>> diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c
>> index a6b55669f0a8..cd001ceaae63 100644
>> --- a/drivers/media/pci/mgb4/mgb4_vout.c
>> +++ b/drivers/media/pci/mgb4/mgb4_vout.c
>> @@ -680,12 +680,12 @@ static void fpga_init(struct mgb4_vout_dev *voutdev)
>>   	mgb4_write_reg(video, regs->config, 0x00000011);
>>   	mgb4_write_reg(video, regs->resolution,
>>   		       (DEFAULT_WIDTH << 16) | DEFAULT_HEIGHT);
>> -	mgb4_write_reg(video, regs->hsync, 0x00102020);
>> -	mgb4_write_reg(video, regs->vsync, 0x40020202);
>> -	mgb4_write_reg(video, regs->frame_period, DEFAULT_PERIOD);
>> +	mgb4_write_reg(video, regs->hsync, 0x00283232);
>> +	mgb4_write_reg(video, regs->vsync, 0x40141F1E);
>> +	mgb4_write_reg(video, regs->frame_limit, DEFAULT_PERIOD);
>>   	mgb4_write_reg(video, regs->padding, 0x00000000);
>>   
>> -	voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 70000 >> 1) << 1;
>> +	voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 61150 >> 1) << 1;
>>   
>>   	mgb4_write_reg(video, regs->config,
>>   		       (voutdev->config->id + MGB4_VIN_DEVICES) << 2 | 1 << 4);
>> @@ -711,8 +711,8 @@ static void debugfs_init(struct mgb4_vout_dev *voutdev)
>>   	voutdev->regs[3].offset = voutdev->config->regs.hsync;
>>   	voutdev->regs[4].name = "VIDEO_PARAMS_2";
>>   	voutdev->regs[4].offset = voutdev->config->regs.vsync;
>> -	voutdev->regs[5].name = "FRAME_PERIOD";
>> -	voutdev->regs[5].offset = voutdev->config->regs.frame_period;
>> +	voutdev->regs[5].name = "FRAME_LIMIT";
>> +	voutdev->regs[5].offset = voutdev->config->regs.frame_limit;
>>   	voutdev->regs[6].name = "PADDING_PIXELS";
>>   	voutdev->regs[6].offset = voutdev->config->regs.padding;
>>   	if (has_timeperframe(video)) {
>> diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mgb4_vout.h
>> index ab9b58b1deb7..adc8fe1e7ae6 100644
>> --- a/drivers/media/pci/mgb4/mgb4_vout.h
>> +++ b/drivers/media/pci/mgb4/mgb4_vout.h
>> @@ -19,7 +19,7 @@ struct mgb4_vout_regs {
>>   	u32 config;
>>   	u32 status;
>>   	u32 resolution;
>> -	u32 frame_period;
>> +	u32 frame_limit;
>>   	u32 hsync;
>>   	u32 vsync;
>>   	u32 padding;
> 


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 2/3] media: mgb4: Add support for V4L2_CAP_TIMEPERFRAME
  2024-04-08 10:40   ` Hans Verkuil
  2024-04-08 11:47     ` Martin Tůma
@ 2024-04-08 15:18     ` Martin Tůma
  1 sibling, 0 replies; 11+ messages in thread
From: Martin Tůma @ 2024-04-08 15:18 UTC (permalink / raw)
  To: Hans Verkuil, Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Martin Tůma

Hi,
I have stopped reading on "I will postpone further review until I have a 
clearer understanding of what the actual feature is that you are 
implementing." and didn't realize there are some more questions, so this 
is a follow up with answers to them ;-) See below

On 08. 04. 24 12:40, Hans Verkuil wrote:
> On 22/03/2024 16:10, tumic@gpxsee.org wrote:
>> From: Martin Tůma <martin.tuma@digiteqautomotive.com>
>>
>> Recent mgb4 firmwares have support for setting a variable framerate independent
>> of the signal framerate. Add/fix (the mgb4 driver already did promote
>> V4L2_CAP_TIMEPERFRAME, but it didn't work) support for V4L2_CAP_TIMEPERFRAME to
>> the driver to enable this feature.
> 
> Both the subject line and commit message talk about V4L2_CAP_TIMEPERFRAME,
> but most of the code is about adding dv_timings support. There is a mismatch
> there.
> 
> And what do you mean with "variable framerate independent of the signal framerate"?
> 
> Does that mean that the firmware will skip or repeat frames depending on the
> selected framerate?
> 
> While I do have a few comments below, I will postpone further review until
> I have a clearer understanding of what the actual feature is that you are
> implementing.
> 
>>
>> Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
>> ---
>>   drivers/media/pci/mgb4/mgb4_core.c      |   2 +-
>>   drivers/media/pci/mgb4/mgb4_core.h      |   2 +
>>   drivers/media/pci/mgb4/mgb4_io.h        |  24 ++-
>>   drivers/media/pci/mgb4/mgb4_sysfs_out.c |   4 +-
>>   drivers/media/pci/mgb4/mgb4_vin.c       |  89 ++++++++---
>>   drivers/media/pci/mgb4/mgb4_vin.h       |   3 +-
>>   drivers/media/pci/mgb4/mgb4_vout.c      | 196 +++++++++++++++++++++++-
>>   drivers/media/pci/mgb4/mgb4_vout.h      |   3 +-
>>   8 files changed, 287 insertions(+), 36 deletions(-)
>>
>> diff --git a/drivers/media/pci/mgb4/mgb4_core.c b/drivers/media/pci/mgb4/mgb4_core.c
>> index 5bfb8a06202e..9c6294009069 100644
>> --- a/drivers/media/pci/mgb4/mgb4_core.c
>> +++ b/drivers/media/pci/mgb4/mgb4_core.c
>> @@ -302,7 +302,7 @@ static int init_i2c(struct mgb4_dev *mgbdev)
>>   	/* create dummy clock required by the xiic-i2c adapter */
>>   	snprintf(clk_name, sizeof(clk_name), "xiic-i2c.%d", id);
>>   	mgbdev->i2c_clk = clk_hw_register_fixed_rate(NULL, clk_name, NULL,
>> -						     0, 125000000);
>> +						     0, MGB4_HW_FREQ);
>>   	if (IS_ERR(mgbdev->i2c_clk)) {
>>   		dev_err(dev, "failed to register I2C clock\n");
>>   		return PTR_ERR(mgbdev->i2c_clk);
>> diff --git a/drivers/media/pci/mgb4/mgb4_core.h b/drivers/media/pci/mgb4/mgb4_core.h
>> index 2a946e46aec1..b52cd67270b5 100644
>> --- a/drivers/media/pci/mgb4/mgb4_core.h
>> +++ b/drivers/media/pci/mgb4/mgb4_core.h
>> @@ -13,6 +13,8 @@
>>   #include <linux/dmaengine.h>
>>   #include "mgb4_regs.h"
>>   
>> +#define MGB4_HW_FREQ 125000000
>> +
>>   #define MGB4_VIN_DEVICES  2
>>   #define MGB4_VOUT_DEVICES 2
>>   
>> diff --git a/drivers/media/pci/mgb4/mgb4_io.h b/drivers/media/pci/mgb4/mgb4_io.h
>> index 204613a6685c..dd8696d7df31 100644
>> --- a/drivers/media/pci/mgb4/mgb4_io.h
>> +++ b/drivers/media/pci/mgb4/mgb4_io.h
>> @@ -7,11 +7,9 @@
>>   #ifndef __MGB4_IO_H__
>>   #define __MGB4_IO_H__
>>   
>> +#include <linux/math64.h>
>>   #include <media/v4l2-dev.h>
>> -
>> -#define MGB4_DEFAULT_WIDTH     1280
>> -#define MGB4_DEFAULT_HEIGHT    640
>> -#define MGB4_DEFAULT_PERIOD    (125000000 / 60)
>> +#include "mgb4_core.h"
>>   
>>   /* Register access error indication */
>>   #define MGB4_ERR_NO_REG        0xFFFFFFFE
>> @@ -20,6 +18,9 @@
>>   #define MGB4_ERR_QUEUE_EMPTY   0xFFFFFFFC
>>   #define MGB4_ERR_QUEUE_FULL    0xFFFFFFFB
>>   
>> +#define MGB4_PERIOD(numerator, denominator) \
>> +	((u32)div_u64((MGB4_HW_FREQ * (u64)(numerator)), (denominator)))
>> +
>>   struct mgb4_frame_buffer {
>>   	struct vb2_v4l2_buffer vb;
>>   	struct list_head list;
>> @@ -30,11 +31,24 @@ static inline struct mgb4_frame_buffer *to_frame_buffer(struct vb2_v4l2_buffer *
>>   	return container_of(vbuf, struct mgb4_frame_buffer, vb);
>>   }
>>   
>> -static inline bool has_yuv(struct mgb4_regs *video)
>> +static inline bool has_yuv_and_timeperframe(struct mgb4_regs *video)
>>   {
>>   	u32 status = mgb4_read_reg(video, 0xD0);
>>   
>>   	return (status & (1U << 8));
>>   }
>>   
>> +#define has_yuv(video) has_yuv_and_timeperframe(video)
>> +#define has_timeperframe(video) has_yuv_and_timeperframe(video)
>> +
>> +static inline u32 pixel_size(struct v4l2_dv_timings *timings)
>> +{
>> +	struct v4l2_bt_timings *bt = &timings->bt;
>> +
>> +	u32 height = bt->height + bt->vfrontporch + bt->vsync + bt->vbackporch;
>> +	u32 width = bt->width + bt->hfrontporch + bt->hsync + bt->hbackporch;
> 
> You can use V4L2_DV_BT_FRAME_WIDTH and V4L2_DV_BT_FRAME_HEIGHT here.
> 
>> +
>> +	return width * height;
>> +}
>> +
>>   #endif
>> diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
>> index 9f6e81c57726..f67ff2a48329 100644
>> --- a/drivers/media/pci/mgb4/mgb4_sysfs_out.c
>> +++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
>> @@ -231,7 +231,7 @@ static ssize_t frame_rate_show(struct device *dev,
>>   	u32 period = mgb4_read_reg(&voutdev->mgbdev->video,
>>   				   voutdev->config->regs.frame_period);
>>   
>> -	return sprintf(buf, "%u\n", 125000000 / period);
>> +	return sprintf(buf, "%u\n", MGB4_HW_FREQ / period);
>>   }
>>   
>>   /*
>> @@ -252,7 +252,7 @@ static ssize_t frame_rate_store(struct device *dev,
>>   		return ret;
>>   
>>   	mgb4_write_reg(&voutdev->mgbdev->video,
>> -		       voutdev->config->regs.frame_period, 125000000 / val);
>> +		       voutdev->config->regs.frame_period, MGB4_HW_FREQ / val);
>>   
>>   	return count;
>>   }
>> diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c
>> index 19692e4dfb59..560c94d21924 100644
>> --- a/drivers/media/pci/mgb4/mgb4_vin.c
>> +++ b/drivers/media/pci/mgb4/mgb4_vin.c
>> @@ -34,8 +34,8 @@ ATTRIBUTE_GROUPS(mgb4_fpdl3_in);
>>   ATTRIBUTE_GROUPS(mgb4_gmsl_in);
>>   
>>   static const struct mgb4_vin_config vin_cfg[] = {
>> -	{0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28}},
>> -	{1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58}}
>> +	{0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28, 0xE8}},
>> +	{1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58, 0xEC}}
>>   };
>>   
>>   static const struct i2c_board_info fpdl3_deser_info[] = {
>> @@ -387,6 +387,7 @@ static int vidioc_enum_frameintervals(struct file *file, void *priv,
> 
> Why exactly did this driver support enum_frameintervals again?
> Was that due to historical reasons? Normally drivers that use the dv_timings API
> do not support this ioctl. It is really meant for sensors with discrete frameintervals.
> 

Well, as far as I can remember, the answer is: because a driver 
user/customer has requested it and since then it is part of the driver. 
But to be honest I don't think this is a bad reasoning as according to 
the v4l2 documentation there is no reason why this info should not be 
present for a grabber device. Moreover the user also gave the argument 
that a different v4l2 device - a camera - also provides this info.

The current output of v4l-ctl:

[tumic@mgb4 ~]$ v4l2-ctl --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
         Type: Video Capture

         [0]: 'AR24' (32-bit BGRA 8-8-8-8)
                 Size: Discrete 1280x640
                         Interval: Stepwise 0.017s - 34.360s with step 
0.000s (0.029-59.729 fps)
         [1]: 'YUYV' (YUYV 4:2:2)
                 Size: Discrete 1280x640
                         Interval: Stepwise 0.017s - 34.360s with step 
0.000s (0.029-59.729 fps)

seems to me also much better and informative than without the "Interval" 
info, especially now that V4L2_CAP_TIMEPERFRAME is properly implemented 
and one would like to know what framerates he can use.

>>   {
>>   	struct mgb4_vin_dev *vindev = video_drvdata(file);
>>   	struct mgb4_regs *video = &vindev->mgbdev->video;
>> +	struct v4l2_dv_timings timings;
>>   
>>   	if (ival->index != 0)
>>   		return -EINVAL;
>> @@ -397,12 +398,15 @@ static int vidioc_enum_frameintervals(struct file *file, void *priv,
>>   	    ival->height != vindev->timings.bt.height)
>>   		return -EINVAL;
>>   
>> -	ival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS;
>> -	ival->stepwise.min.denominator = 60;
>> -	ival->stepwise.min.numerator = 1;
>> -	ival->stepwise.max.denominator = 1;
>> -	ival->stepwise.max.numerator = 1;
>> -	ival->stepwise.step = ival->stepwise.max;
>> +	get_timings(vindev, &timings);
>> +
>> +	ival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
>> +	ival->stepwise.max.denominator = MGB4_HW_FREQ;
>> +	ival->stepwise.max.numerator = 0xFFFFFFFF;
> 
> That's a max frame interval of 2062 ms, is that really realistic?
> 

It is even more (~35s) as shown above, but it really works, I have 
tested it. Whether that makes any sense for someone is a different 
question but I see no reason for some kind of an artificial limit here. 
I would be more concerned about the minimum (0.017s) which you may not 
be able to achieve as it is (in the driver as well as in the v4l2 
documentation) limited to the actual signal frame rate. But theoretical 
it should also work or at least not damage the HW ;-)

>> +	ival->stepwise.min.denominator = timings.bt.pixelclock;
>> +	ival->stepwise.min.numerator = pixel_size(&timings);
>> +	ival->stepwise.step.denominator = MGB4_HW_FREQ;
>> +	ival->stepwise.step.numerator = 1;
>>   
>>   	return 0;
>>   }
> 
> Regards,
> 
> 	Hans
> 
>> @@ -570,27 +574,66 @@ static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
>>   	return 0;
>>   }
>>   
>> -static int vidioc_parm(struct file *file, void *priv,
>> -		       struct v4l2_streamparm *parm)
>> +static int vidioc_g_parm(struct file *file, void *priv,
>> +			 struct v4l2_streamparm *parm)
>>   {
>>   	struct mgb4_vin_dev *vindev = video_drvdata(file);
>>   	struct mgb4_regs *video = &vindev->mgbdev->video;
>> -	const struct mgb4_vin_regs *regs = &vindev->config->regs;
>> -	struct v4l2_fract timeperframe = {
>> -		.numerator = mgb4_read_reg(video, regs->frame_period),
>> -		.denominator = 125000000,
>> -	};
>> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
>> +	struct v4l2_dv_timings timings;
>> +	u32 timer;
>>   
>>   	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
>>   		return -EINVAL;
>>   
>>   	parm->parm.capture.readbuffers = 2;
>> -	parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
>> -	parm->parm.capture.timeperframe = timeperframe;
>> +
>> +	if (has_timeperframe(video)) {
>> +		timer = mgb4_read_reg(video, vindev->config->regs.timer);
>> +		if (timer < 0xFFFF) {
>> +			get_timings(vindev, &timings);
>> +			tpf->numerator = pixel_size(&timings);
>> +			tpf->denominator = timings.bt.pixelclock;
>> +		} else {
>> +			tpf->numerator = timer;
>> +			tpf->denominator = MGB4_HW_FREQ;
>> +		}
>> +
>> +		parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
>> +	}
>>   
>>   	return 0;
>>   }
>>   
>> +static int vidioc_s_parm(struct file *file, void *priv,
>> +			 struct v4l2_streamparm *parm)
>> +{
>> +	struct mgb4_vin_dev *vindev = video_drvdata(file);
>> +	struct mgb4_regs *video = &vindev->mgbdev->video;
>> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
>> +	struct v4l2_dv_timings timings;
>> +	u32 period, timer;
>> +
>> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
>> +		return -EINVAL;
>> +
>> +	if (has_timeperframe(video)) {
>> +		timer = tpf->denominator ?
>> +			MGB4_PERIOD(tpf->numerator, tpf->denominator) : 0;
>> +		if (timer) {
>> +			get_timings(vindev, &timings);
>> +			period = MGB4_PERIOD(pixel_size(&timings),
>> +					     timings.bt.pixelclock);
>> +			if (timer < period)
>> +				timer = 0;
>> +		}
>> +
>> +		mgb4_write_reg(video, vindev->config->regs.timer, timer);
>> +	}
>> +
>> +	return vidioc_g_parm(file, priv, parm);
>> +}
>> +
>>   static int vidioc_s_dv_timings(struct file *file, void *fh,
>>   			       struct v4l2_dv_timings *timings)
>>   {
>> @@ -674,8 +717,8 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = {
>>   	.vidioc_expbuf = vb2_ioctl_expbuf,
>>   	.vidioc_streamon = vb2_ioctl_streamon,
>>   	.vidioc_streamoff = vb2_ioctl_streamoff,
>> -	.vidioc_g_parm = vidioc_parm,
>> -	.vidioc_s_parm = vidioc_parm,
>> +	.vidioc_g_parm = vidioc_g_parm,
>> +	.vidioc_s_parm = vidioc_s_parm,
>>   	.vidioc_dv_timings_cap = vidioc_dv_timings_cap,
>>   	.vidioc_enum_dv_timings = vidioc_enum_dv_timings,
>>   	.vidioc_g_dv_timings = vidioc_g_dv_timings,
>> @@ -858,10 +901,16 @@ static void debugfs_init(struct mgb4_vin_dev *vindev)
>>   	vindev->regs[7].offset = vindev->config->regs.signal2;
>>   	vindev->regs[8].name = "PADDING_PIXELS";
>>   	vindev->regs[8].offset = vindev->config->regs.padding;
>> +	if (has_timeperframe(video)) {
>> +		vindev->regs[9].name = "TIMER";
>> +		vindev->regs[9].offset = vindev->config->regs.timer;
>> +		vindev->regset.nregs = 10;
>> +	} else {
>> +		vindev->regset.nregs = 9;
>> +	}
>>   
>>   	vindev->regset.base = video->membase;
>>   	vindev->regset.regs = vindev->regs;
>> -	vindev->regset.nregs = ARRAY_SIZE(vindev->regs);
>>   
>>   	debugfs_create_regset32("registers", 0444, vindev->debugfs,
>>   				&vindev->regset);
>> diff --git a/drivers/media/pci/mgb4/mgb4_vin.h b/drivers/media/pci/mgb4/mgb4_vin.h
>> index 0249b400ad4d..9693bd0ce180 100644
>> --- a/drivers/media/pci/mgb4/mgb4_vin.h
>> +++ b/drivers/media/pci/mgb4/mgb4_vin.h
>> @@ -25,6 +25,7 @@ struct mgb4_vin_regs {
>>   	u32 signal;
>>   	u32 signal2;
>>   	u32 padding;
>> +	u32 timer;
>>   };
>>   
>>   struct mgb4_vin_config {
>> @@ -59,7 +60,7 @@ struct mgb4_vin_dev {
>>   #ifdef CONFIG_DEBUG_FS
>>   	struct dentry *debugfs;
>>   	struct debugfs_regset32 regset;
>> -	struct debugfs_reg32 regs[9];
>> +	struct debugfs_reg32 regs[sizeof(struct mgb4_vin_regs) / 4];
>>   #endif
>>   };
>>   
>> diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c
>> index 243fbeaaceb9..a6b55669f0a8 100644
>> --- a/drivers/media/pci/mgb4/mgb4_vout.c
>> +++ b/drivers/media/pci/mgb4/mgb4_vout.c
>> @@ -16,6 +16,7 @@
>>   #include <media/v4l2-ioctl.h>
>>   #include <media/videobuf2-v4l2.h>
>>   #include <media/videobuf2-dma-sg.h>
>> +#include <media/v4l2-dv-timings.h>
>>   #include "mgb4_core.h"
>>   #include "mgb4_dma.h"
>>   #include "mgb4_sysfs.h"
>> @@ -23,12 +24,16 @@
>>   #include "mgb4_cmt.h"
>>   #include "mgb4_vout.h"
>>   
>> +#define DEFAULT_WIDTH     1280
>> +#define DEFAULT_HEIGHT    640
>> +#define DEFAULT_PERIOD    (MGB4_HW_FREQ / 60)
>> +
>>   ATTRIBUTE_GROUPS(mgb4_fpdl3_out);
>>   ATTRIBUTE_GROUPS(mgb4_gmsl_out);
>>   
>>   static const struct mgb4_vout_config vout_cfg[] = {
>> -	{0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7c}},
>> -	{1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8c, 0x90, 0x9c}}
>> +	{0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7C, 0xE0}},
>> +	{1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8C, 0x90, 0x9C, 0xE4}}
>>   };
>>   
>>   static const struct i2c_board_info fpdl3_ser_info[] = {
>> @@ -40,6 +45,49 @@ static const struct mgb4_i2c_kv fpdl3_i2c[] = {
>>   	{0x05, 0xFF, 0x04}, {0x06, 0xFF, 0x01}, {0xC2, 0xFF, 0x80}
>>   };
>>   
>> +static const struct v4l2_dv_timings_cap video_timings_cap = {
>> +	.type = V4L2_DV_BT_656_1120,
>> +	.bt = {
>> +		.min_width = 320,
>> +		.max_width = 4096,
>> +		.min_height = 240,
>> +		.max_height = 2160,
>> +		.min_pixelclock = 1843200, /* 320 x 240 x 24Hz */
>> +		.max_pixelclock = 530841600, /* 4096 x 2160 x 60Hz */
>> +		.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
>> +			V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF,
>> +		.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
>> +			V4L2_DV_BT_CAP_CUSTOM,
>> +	},
>> +};
>> +
>> +static void get_timings(struct mgb4_vout_dev *voutdev,
>> +			struct v4l2_dv_timings *timings)
>> +{
>> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
>> +	const struct mgb4_vout_regs *regs = &voutdev->config->regs;
>> +
>> +	u32 hsync = mgb4_read_reg(video, regs->hsync);
>> +	u32 vsync = mgb4_read_reg(video, regs->vsync);
>> +	u32 resolution = mgb4_read_reg(video, regs->resolution);
>> +
>> +	memset(timings, 0, sizeof(*timings));
>> +	timings->type = V4L2_DV_BT_656_1120;
>> +	timings->bt.width = resolution >> 16;
>> +	timings->bt.height = resolution & 0xFFFF;
>> +	if (hsync & (1U << 31))
>> +		timings->bt.polarities |= V4L2_DV_HSYNC_POS_POL;
>> +	if (vsync & (1U << 31))
>> +		timings->bt.polarities |= V4L2_DV_VSYNC_POS_POL;
>> +	timings->bt.pixelclock = voutdev->freq * 1000;
>> +	timings->bt.hsync = (hsync & 0x00FF0000) >> 16;
>> +	timings->bt.vsync = (vsync & 0x00FF0000) >> 16;
>> +	timings->bt.hbackporch = (hsync & 0x0000FF00) >> 8;
>> +	timings->bt.hfrontporch = hsync & 0x000000FF;
>> +	timings->bt.vbackporch = (vsync & 0x0000FF00) >> 8;
>> +	timings->bt.vfrontporch = vsync & 0x000000FF;
>> +}
>> +
>>   static void return_all_buffers(struct mgb4_vout_dev *voutdev,
>>   			       enum vb2_buffer_state state)
>>   {
>> @@ -345,11 +393,134 @@ static int vidioc_enum_output(struct file *file, void *priv,
>>   		return -EINVAL;
>>   
>>   	out->type = V4L2_OUTPUT_TYPE_ANALOG;
>> +	out->capabilities = V4L2_OUT_CAP_DV_TIMINGS;
>>   	strscpy(out->name, "MGB4", sizeof(out->name));
>>   
>>   	return 0;
>>   }
>>   
>> +static int vidioc_enum_frameintervals(struct file *file, void *priv,
>> +				      struct v4l2_frmivalenum *ival)
>> +{
>> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
>> +	struct v4l2_dv_timings timings;
>> +
>> +	if (ival->index != 0)
>> +		return -EINVAL;
>> +	if (!(ival->pixel_format == V4L2_PIX_FMT_ABGR32 ||
>> +	      ((has_yuv(video) && ival->pixel_format == V4L2_PIX_FMT_YUYV))))
>> +		return -EINVAL;
>> +	if (ival->width != voutdev->width || ival->height != voutdev->height)
>> +		return -EINVAL;
>> +
>> +	get_timings(voutdev, &timings);
>> +
>> +	ival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
>> +	ival->stepwise.max.denominator = MGB4_HW_FREQ;
>> +	ival->stepwise.max.numerator = 0xFFFFFFFF;
>> +	ival->stepwise.min.denominator = timings.bt.pixelclock;
>> +	ival->stepwise.min.numerator = pixel_size(&timings);
>> +	ival->stepwise.step.denominator = MGB4_HW_FREQ;
>> +	ival->stepwise.step.numerator = 1;
>> +
>> +	return 0;
>> +}
>> +
>> +static int vidioc_g_parm(struct file *file, void *priv,
>> +			 struct v4l2_streamparm *parm)
>> +{
>> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
>> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
>> +	struct v4l2_dv_timings timings;
>> +	u32 timer;
>> +
>> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
>> +		return -EINVAL;
>> +
>> +	parm->parm.output.writebuffers = 2;
>> +
>> +	if (has_timeperframe(video)) {
>> +		timer = mgb4_read_reg(video, voutdev->config->regs.timer);
>> +		if (timer < 0xFFFF) {
>> +			get_timings(voutdev, &timings);
>> +			tpf->numerator = pixel_size(&timings);
>> +			tpf->denominator = timings.bt.pixelclock;
>> +		} else {
>> +			tpf->numerator = timer;
>> +			tpf->denominator = MGB4_HW_FREQ;
>> +		}
>> +
>> +		parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int vidioc_s_parm(struct file *file, void *priv,
>> +			 struct v4l2_streamparm *parm)
>> +{
>> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>> +	struct mgb4_regs *video = &voutdev->mgbdev->video;
>> +	struct v4l2_fract *tpf = &parm->parm.output.timeperframe;
>> +	struct v4l2_dv_timings timings;
>> +	u32 timer, period;
>> +
>> +	if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
>> +		return -EINVAL;
>> +
>> +	if (has_timeperframe(video)) {
>> +		timer = tpf->denominator ?
>> +			MGB4_PERIOD(tpf->numerator, tpf->denominator) : 0;
>> +		if (timer) {
>> +			get_timings(voutdev, &timings);
>> +			period = MGB4_PERIOD(pixel_size(&timings),
>> +					     timings.bt.pixelclock);
>> +			if (timer < period)
>> +				timer = 0;
>> +		}
>> +
>> +		mgb4_write_reg(video, voutdev->config->regs.timer, timer);
>> +	}
>> +
>> +	return vidioc_g_parm(file, priv, parm);
>> +}
>> +
>> +static int vidioc_g_dv_timings(struct file *file, void *fh,
>> +			       struct v4l2_dv_timings *timings)
>> +{
>> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>> +
>> +	get_timings(voutdev, timings);
>> +
>> +	return 0;
>> +}
>> +
>> +static int vidioc_s_dv_timings(struct file *file, void *fh,
>> +			       struct v4l2_dv_timings *timings)
>> +{
>> +	struct mgb4_vout_dev *voutdev = video_drvdata(file);
>> +
>> +	get_timings(voutdev, timings);
>> +
>> +	return 0;
>> +}
>> +
>> +static int vidioc_enum_dv_timings(struct file *file, void *fh,
>> +				  struct v4l2_enum_dv_timings *timings)
>> +{
>> +	return v4l2_enum_dv_timings_cap(timings, &video_timings_cap, NULL, NULL);
>> +}
>> +
>> +static int vidioc_dv_timings_cap(struct file *file, void *fh,
>> +				 struct v4l2_dv_timings_cap *cap)
>> +{
>> +	*cap = video_timings_cap;
>> +
>> +	return 0;
>> +}
>> +
>>   static const struct v4l2_ioctl_ops video_ioctl_ops = {
>>   	.vidioc_querycap = vidioc_querycap,
>>   	.vidioc_enum_fmt_vid_out = vidioc_enum_fmt,
>> @@ -357,8 +528,15 @@ static const struct v4l2_ioctl_ops video_ioctl_ops = {
>>   	.vidioc_s_fmt_vid_out = vidioc_s_fmt,
>>   	.vidioc_g_fmt_vid_out = vidioc_g_fmt,
>>   	.vidioc_enum_output = vidioc_enum_output,
>> +	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
>>   	.vidioc_g_output = vidioc_g_output,
>>   	.vidioc_s_output = vidioc_s_output,
>> +	.vidioc_g_parm = vidioc_g_parm,
>> +	.vidioc_s_parm = vidioc_s_parm,
>> +	.vidioc_dv_timings_cap = vidioc_dv_timings_cap,
>> +	.vidioc_enum_dv_timings = vidioc_enum_dv_timings,
>> +	.vidioc_g_dv_timings = vidioc_g_dv_timings,
>> +	.vidioc_s_dv_timings = vidioc_s_dv_timings,
>>   	.vidioc_reqbufs = vb2_ioctl_reqbufs,
>>   	.vidioc_create_bufs = vb2_ioctl_create_bufs,
>>   	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
>> @@ -501,10 +679,10 @@ static void fpga_init(struct mgb4_vout_dev *voutdev)
>>   
>>   	mgb4_write_reg(video, regs->config, 0x00000011);
>>   	mgb4_write_reg(video, regs->resolution,
>> -		       (MGB4_DEFAULT_WIDTH << 16) | MGB4_DEFAULT_HEIGHT);
>> +		       (DEFAULT_WIDTH << 16) | DEFAULT_HEIGHT);
>>   	mgb4_write_reg(video, regs->hsync, 0x00102020);
>>   	mgb4_write_reg(video, regs->vsync, 0x40020202);
>> -	mgb4_write_reg(video, regs->frame_period, MGB4_DEFAULT_PERIOD);
>> +	mgb4_write_reg(video, regs->frame_period, DEFAULT_PERIOD);
>>   	mgb4_write_reg(video, regs->padding, 0x00000000);
>>   
>>   	voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 70000 >> 1) << 1;
>> @@ -535,12 +713,18 @@ static void debugfs_init(struct mgb4_vout_dev *voutdev)
>>   	voutdev->regs[4].offset = voutdev->config->regs.vsync;
>>   	voutdev->regs[5].name = "FRAME_PERIOD";
>>   	voutdev->regs[5].offset = voutdev->config->regs.frame_period;
>> -	voutdev->regs[6].name = "PADDING";
>> +	voutdev->regs[6].name = "PADDING_PIXELS";
>>   	voutdev->regs[6].offset = voutdev->config->regs.padding;
>> +	if (has_timeperframe(video)) {
>> +		voutdev->regs[7].name = "TIMER";
>> +		voutdev->regs[7].offset = voutdev->config->regs.timer;
>> +		voutdev->regset.nregs = 8;
>> +	} else {
>> +		voutdev->regset.nregs = 7;
>> +	}
>>   
>>   	voutdev->regset.base = video->membase;
>>   	voutdev->regset.regs = voutdev->regs;
>> -	voutdev->regset.nregs = ARRAY_SIZE(voutdev->regs);
>>   
>>   	debugfs_create_regset32("registers", 0444, voutdev->debugfs,
>>   				&voutdev->regset);
>> diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mgb4_vout.h
>> index b163dee711fd..ab9b58b1deb7 100644
>> --- a/drivers/media/pci/mgb4/mgb4_vout.h
>> +++ b/drivers/media/pci/mgb4/mgb4_vout.h
>> @@ -23,6 +23,7 @@ struct mgb4_vout_regs {
>>   	u32 hsync;
>>   	u32 vsync;
>>   	u32 padding;
>> +	u32 timer;
>>   };
>>   
>>   struct mgb4_vout_config {
>> @@ -55,7 +56,7 @@ struct mgb4_vout_dev {
>>   #ifdef CONFIG_DEBUG_FS
>>   	struct dentry *debugfs;
>>   	struct debugfs_regset32 regset;
>> -	struct debugfs_reg32 regs[7];
>> +	struct debugfs_reg32 regs[sizeof(struct mgb4_vout_regs) / 4];
>>   #endif
>>   };
>>   
> 
> 


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 3/3] media: mgb4: Fixed signal frame rate limit handling
  2024-04-08 10:47   ` Hans Verkuil
  2024-04-08 12:16     ` Martin Tůma
@ 2024-04-09  9:57     ` Martin Tůma
  1 sibling, 0 replies; 11+ messages in thread
From: Martin Tůma @ 2024-04-09  9:57 UTC (permalink / raw)
  To: Hans Verkuil, Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Martin Tůma

On 08. 04. 24 12:47, Hans Verkuil wrote:

>> diff --git a/Documentation/admin-guide/media/mgb4.rst b/Documentation/admin-guide/media/mgb4.rst
>> index 2977f74d7e26..6fff886003e2 100644
>> --- a/Documentation/admin-guide/media/mgb4.rst
>> +++ b/Documentation/admin-guide/media/mgb4.rst
>> @@ -228,8 +228,12 @@ Common FPDL3/GMSL output parameters
>>       open.*
>>   
>>   **frame_rate** (RW):
>> -    Output video frame rate in frames per second. The default frame rate is
>> -    60Hz.
>> +    Output video signal frame rate limit in frames per second. Due to
>> +    the limited output pixel clock steps, the card can not always generate
>> +    a frame rate perfectly matching the value required by the connected display.
>> +    Using this parameter one can limit the frame rate by "crippling" the signal
>> +    so that the lines are not equal but the signal appears like having the exact
>> +    frame rate to the connected display. The default frame rate limit is 60Hz.
> 
> It's not clear what is meant with 'crippling'. Normally when dealing with video
> framerates the driver will pick the closest video timing to the requested framerate.
> It is understood that you can't always get the exact framerate, so drivers can
> make adjustments.
> 

By "crippling" I mean the signal is modified in a obscure way so that a 
frame has not all lines equal. The HW somehow (the exact way is not 
known to me, the documentation is very sparse on this - before my 
investigation it stated only "frames per second" which was very 
confusing and the reason this patch exists) modifies the last 
line(lines?) so that the overall clock ticks per frame is the desired 
value. Some blanking stuff (the porches?) you have set are not equal for 
all the frame lines.

What I'm trying to do is to change the original documentation which is 
definitely wrong (the users are confused how you can set the timings AND 
the frame rate with different values at the same time) without saying 
too much about the exact algorithm as it is not exactly known and may 
even slightly change in different FW versions.

Does that all make sense to you now?

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2024-04-09  9:57 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-03-22 15:10 [PATCH v4 0/3] media: mgb4: YUV and variable framerate support tumic
2024-03-22 15:10 ` [PATCH v4 1/3] media: mgb4: Add support for YUV image formats tumic
2024-04-08  9:58   ` Hans Verkuil
2024-03-22 15:10 ` [PATCH v4 2/3] media: mgb4: Add support for V4L2_CAP_TIMEPERFRAME tumic
2024-04-08 10:40   ` Hans Verkuil
2024-04-08 11:47     ` Martin Tůma
2024-04-08 15:18     ` Martin Tůma
2024-03-22 15:10 ` [PATCH v4 3/3] media: mgb4: Fixed signal frame rate limit handling tumic
2024-04-08 10:47   ` Hans Verkuil
2024-04-08 12:16     ` Martin Tůma
2024-04-09  9:57     ` Martin Tůma

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).