In order to make the frame rate changeable, this patch implements the
ioctls VIDIOC_ENUM_FRAMEINTERVALS, VIDIOC_S_PARM and VIDIOC_G_PARM.
Most of the implementation was based on vivid driver.

The frame rate control is mainly made in vimc_force_frame_rate function,
which sleeps how long is needed to achieve the desired frame rate, or do
not sleep at all if it is unnecessary.

There are some issues:
1. v4l2-compliance is giving one warning. See below.
2. qv4l2 is setting the frame rate to -1 FPS when it starts to stream.

Signed-off-by: Guilherme Alcarde Gallo <gagal...@gmail.com>

---
NOTE: This patch was made on top of Shuah's patch
"Collapse component structure into a single monolithic driver".
https://patchwork.kernel.org/patch/11136201/

v4l2-compliance output:
v4l2-compliance SHA: b393a5408383b7341883857dfda78537f2f85ef6, 64 bits

Compliance test for vimc device /dev/video0:

Driver Info:
        Driver name      : vimc
        Card type        : vimc
        Bus info         : platform:vimc
        Driver version   : 5.3.0
        Capabilities     : 0x84200001
                Video Capture
                Streaming
                Extended Pix Format
                Device Capabilities
        Device Caps      : 0x04200001
                Video Capture
                Streaming
                Extended Pix Format
Media Driver Info:
        Driver name      : vimc
        Model            : VIMC MDEV
        Serial           :
        Bus info         : platform:vimc
        Media version    : 5.3.0
        Hardware revision: 0x00000000 (0)
        Driver version   : 5.3.0
Interface Info:
        ID               : 0x0300000d
        Type             : V4L Video
Entity Info:
        ID               : 0x0000000b (11)
        Name             : Raw Capture 0
        Function         : V4L2 I/O
        Pad 0x0100000c   : 0: Sink
          Link 0x0200001e: from remote pad 0x1000002 of entity 'Sensor A': 
Data, Enabled, Immutable

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_QUERYCAP: OK

Allow for multiple opens:
        test second /dev/video0 open: OK
        test VIDIOC_QUERYCAP: OK
        test VIDIOC_G/S_PRIORITY: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
                warn: v4l2-test-formats.cpp(1320): S_PARM is supported for 
buftype 1, but not for ENUM_FRAMEINTERVALS
        test VIDIOC_G/S_PARM: OK
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK
        test VIDIOC_TRY_FMT: OK
        test VIDIOC_S_FMT: OK
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
        test VIDIOC_EXPBUF: OK
        test Requests: OK (Not Supported)

Total for vimc device /dev/video0: 45, Succeeded: 45, Failed: 0, Warnings: 1

---
---
 drivers/media/platform/vimc/vimc-capture.c  | 129 +++++++++++++++++++-
 drivers/media/platform/vimc/vimc-common.h   |  27 ++++
 drivers/media/platform/vimc/vimc-streamer.c |  73 ++++++++++-
 drivers/media/platform/vimc/vimc-streamer.h |   3 +
 4 files changed, 229 insertions(+), 3 deletions(-)

diff --git a/drivers/media/platform/vimc/vimc-capture.c 
b/drivers/media/platform/vimc/vimc-capture.c
index 602f80323031..b600e0dc2f1d 100644
--- a/drivers/media/platform/vimc/vimc-capture.c
+++ b/drivers/media/platform/vimc/vimc-capture.c
@@ -165,8 +165,9 @@ static int vimc_cap_enum_framesizes(struct file *file, void 
*fh,
                return -EINVAL;
 
        /* Only accept code in the pix map table */
-       vpix = vimc_pix_map_by_code(fsize->pixel_format);
+       vpix = vimc_pix_map_by_pixelformat(fsize->pixel_format);
        if (!vpix)
+               pr_err("Invalid pixelformat given to %s.", __func__);
                return -EINVAL;
 
        fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
@@ -190,6 +191,122 @@ static const struct v4l2_file_operations vimc_cap_fops = {
        .mmap           = vb2_fop_mmap,
 };
 
+static int vimc_enum_frameintervals(struct file *file, void *fh,
+                                   struct v4l2_frmivalenum *fival)
+{
+       const struct vimc_pix_map *vpix =
+               vimc_pix_map_by_pixelformat(fival->pixel_format);
+       const struct video_device *vdev = video_devdata(file);
+
+       if (vdev->vfl_dir != VFL_DIR_RX)
+               return -ENOTTY;
+
+       /* Using step-wise frame rates. Only the first index is retrieved by the
+        * application.
+        */
+       if (fival->index)
+               return -EINVAL;
+
+       if (!vpix) {
+               pr_err("Invalid pixelformat given to %s.", __func__);
+               return -EINVAL;
+       }
+
+       /* TODO: Check the given frame dimensions when VIDIOC_ENUM_FRAMESIZES
+        * is done
+        */
+       if (fival->width < VIMC_FRAME_MIN_WIDTH ||
+           fival->width > VIMC_FRAME_MAX_WIDTH ||
+           fival->height < VIMC_FRAME_MIN_HEIGHT ||
+           fival->height > VIMC_FRAME_MAX_HEIGHT) {
+               pr_err("Invalid frame size given to %s.", __func__);
+               return -EINVAL;
+       }
+
+       /*Setting a continuous step-wise approach to enumerate the frame rates*/
+       fival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS;
+       fival->stepwise.min.numerator = tpf_min.numerator;
+       fival->stepwise.min.denominator = tpf_min.denominator;
+       fival->stepwise.max.numerator = tpf_max.numerator;
+       fival->stepwise.max.denominator = tpf_max.denominator;
+       fival->stepwise.step.numerator = 1;
+       fival->stepwise.step.denominator = 1;
+
+       return 0;
+}
+
+static int vimc_vid_cap_g_parm(struct file *file, void *priv,
+                              struct v4l2_streamparm *parm)
+{
+       struct vimc_cap_device *vcap = video_drvdata(file);
+
+       /* TODO: Update allowed types when multiplanar feature is done. */
+       if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               return -EINVAL;
+
+       parm->parm.capture.capability   = V4L2_CAP_TIMEPERFRAME;
+       parm->parm.capture.timeperframe = vcap->stream.timeperframe_vid_cap;
+       pr_debug("g_parm tpf_vid_cap: %u/%u",
+                vcap->stream.timeperframe_vid_cap.numerator,
+                vcap->stream.timeperframe_vid_cap.denominator);
+       /* Setting readbuffers to 0, because read capability is not supported */
+       parm->parm.capture.readbuffers = 0;
+       return 0;
+}
+
+static int vimc_vid_cap_s_parm(struct file *file, void *priv,
+                              struct v4l2_streamparm *parm)
+{
+       struct vimc_cap_device *vcap = video_drvdata(file);
+       struct v4l2_fract tpf;
+
+       /* TODO: Update allowed types when multiplanar feature is done. */
+       if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+               return -ENOTTY;
+
+       tpf = parm->parm.capture.timeperframe;
+
+       pr_debug("s_parm %u / %u", tpf.numerator, tpf.denominator);
+
+       if (tpf.denominator == 0) {
+               tpf = tpf_max;
+       } else {
+               tpf = V4L2_FRACT_COMPARE(tpf, <, tpf_min) ? tpf_min : tpf;
+               tpf = V4L2_FRACT_COMPARE(tpf, >, tpf_max) ? tpf_max : tpf;
+       }
+
+       pr_debug("final s_parm %u / %u.", tpf.numerator, tpf.denominator);
+       /* resync the thread's timings */
+       vcap->stream.timeperframe_vid_cap = tpf;
+       parm->parm.capture.capability   = V4L2_CAP_TIMEPERFRAME;
+       parm->parm.capture.timeperframe = tpf;
+       /* Setting readbuffers to 0, because read capability is not supported */
+       parm->parm.capture.readbuffers = 0;
+       return 0;
+}
+
+static int vimc_g_parm(struct file *file, void *fh,
+                      struct v4l2_streamparm *parm)
+{
+       struct video_device *vdev = video_devdata(file);
+
+       if (vdev->vfl_dir == VFL_DIR_RX)
+               return vimc_vid_cap_g_parm(file, fh, parm);
+
+       return -ENOTTY;
+}
+
+static int vimc_s_parm(struct file *file, void *fh,
+                      struct v4l2_streamparm *parm)
+{
+       struct video_device *vdev = video_devdata(file);
+
+       if (vdev->vfl_dir == VFL_DIR_RX)
+               return vimc_vid_cap_s_parm(file, fh, parm);
+
+       return -ENOTTY;
+}
+
 static const struct v4l2_ioctl_ops vimc_cap_ioctl_ops = {
        .vidioc_querycap = vimc_cap_querycap,
 
@@ -199,6 +316,10 @@ static const struct v4l2_ioctl_ops vimc_cap_ioctl_ops = {
        .vidioc_enum_fmt_vid_cap = vimc_cap_enum_fmt_vid_cap,
        .vidioc_enum_framesizes = vimc_cap_enum_framesizes,
 
+       .vidioc_enum_frameintervals = vimc_enum_frameintervals,
+       .vidioc_g_parm = vimc_g_parm,
+       .vidioc_s_parm = vimc_s_parm,
+
        .vidioc_reqbufs = vb2_ioctl_reqbufs,
        .vidioc_create_bufs = vb2_ioctl_create_bufs,
        .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
@@ -240,6 +361,10 @@ static int vimc_cap_start_streaming(struct vb2_queue *vq, 
unsigned int count)
                return ret;
        }
 
+       pr_debug("s_stream begin. Timeperframe: %u / %u",
+                vcap->stream.timeperframe_vid_cap.numerator,
+                vcap->stream.timeperframe_vid_cap.denominator);
+
        ret = vimc_streamer_s_stream(&vcap->stream, &vcap->ved, 1);
        if (ret) {
                media_pipeline_stop(entity);
@@ -446,6 +571,8 @@ struct vimc_ent_device *vimc_cap_add(struct vimc_device 
*vimc,
        vcap->format.bytesperline = vcap->format.width * vpix->bpp;
        vcap->format.sizeimage = vcap->format.bytesperline *
                                 vcap->format.height;
+       vcap->stream.timeperframe_vid_cap.numerator = 1;
+       vcap->stream.timeperframe_vid_cap.denominator = 60;
 
        /* Fill the vimc_ent_device struct */
        vcap->ved.ent = &vcap->vdev.entity;
diff --git a/drivers/media/platform/vimc/vimc-common.h 
b/drivers/media/platform/vimc/vimc-common.h
index 236412ad7548..930206bc925b 100644
--- a/drivers/media/platform/vimc/vimc-common.h
+++ b/drivers/media/platform/vimc/vimc-common.h
@@ -25,6 +25,33 @@
 #define VIMC_FRAME_MIN_WIDTH 16
 #define VIMC_FRAME_MIN_HEIGHT 16
 
+/* Maximum allowed frame rate
+ *
+ * vimc will allow setting timeperframe in [1/FPS_MAX - FPS_MAX/1] range.
+ *
+ * Ideally FPS_MAX should be infinity, i.e. practically UINT_MAX, but that
+ * might hit application errors when they manipulate these values.
+ *
+ */
+#define VIMC_FPS_MAX 100
+#define VIMC_FPS_MIN 1
+
+#define DIV64_1000(val)                (div_u64(val, 1000))
+#define MS_TO_S(msec)          (DIV64_1000(msec))
+#define US_TO_MS(usec)         (DIV64_1000(usec))
+#define NS_TO_US(nsec)         (DIV64_1000(nsec))
+#define NS_TO_MS(nsec)         (DIV64_1000(DIV64_1000(nsec)))
+#define US_TO_NS(usec)         ((usec) * 1000)
+#define MS_TO_US(msec)         ((msec) * 1000)
+#define MS_TO_NS(msec)         ((msec) * 1000 * 1000)
+#define S_TO_MS(sec)           ((sec) * 1000)
+#define S_TO_US(sec)           ((sec) * 1000 * 1000)
+
+/* timeperframe: min/max and default */
+static const struct v4l2_fract
+       tpf_min     = {.numerator = 1,          .denominator = VIMC_FPS_MAX},
+       tpf_max     = {.numerator = VIMC_FPS_MAX,       .denominator = 1};
+
 #define VIMC_FRAME_INDEX(lin, col, width, bpp) ((lin * width + col) * bpp)
 
 /* Source and sink pad checks */
diff --git a/drivers/media/platform/vimc/vimc-streamer.c 
b/drivers/media/platform/vimc/vimc-streamer.c
index 048d770e498b..2b1cc7727880 100644
--- a/drivers/media/platform/vimc/vimc-streamer.c
+++ b/drivers/media/platform/vimc/vimc-streamer.c
@@ -6,6 +6,7 @@
  *
  */
 
+#include <linux/delay.h>
 #include <linux/init.h>
 #include <linux/module.h>
 #include <linux/freezer.h>
@@ -125,6 +126,52 @@ static int vimc_streamer_pipeline_init(struct vimc_stream 
*stream,
        return -EINVAL;
 }
 
+static int vimc_force_frame_rate(u64 target_interval_ms, u64 tick)
+{
+       const u64 tock = ktime_get_ns();
+       const u64 target_timespan_usec = MS_TO_US(target_interval_ms);
+       s64 freeze_time_usec = MS_TO_US(target_interval_ms) - NS_TO_US(tock -
+                                                                      tick);
+       u64 frame_current_timespan_usec = 0;
+
+       if (target_interval_ms < S_TO_MS(1) / VIMC_FPS_MAX) {
+               pr_warn("Invalid timespan given. Don't limit the FPS.");
+               return -EINVAL;
+       }
+
+       if (tick > tock) {
+               pr_err("Wrong tick timing given to calculate the frame rate.");
+               return -EINVAL;
+       }
+
+       if (freeze_time_usec < 0LL) {
+               /*No need to skip frames.*/
+               return 0;
+       } else if (freeze_time_usec > MS_TO_US(20)) {
+               /* From: Documentation/timers/timers-howto.rst
+                * If the freeze time is greater than 20 milliseconds, use
+                * msleep or msleep_interruptible.
+                */
+               msleep_interruptible(US_TO_MS(freeze_time_usec));
+       } else {
+               /* From: Documentation/timers/timers-howto.rst
+                * If the freeze time is between 0.01 and 20 milliseconds, use
+                * usleep_range.
+                * For FPS greater than 50.
+                */
+               frame_current_timespan_usec = NS_TO_US(ktime_get_ns() - tick);
+               while (frame_current_timespan_usec < target_timespan_usec) {
+                       usleep_range(0, target_timespan_usec -
+                                    frame_current_timespan_usec);
+                       frame_current_timespan_usec = NS_TO_US(ktime_get_ns() -
+                                                              tick);
+               }
+       }
+
+       /*Frames have been skipped to achieve the target FPS.*/
+       return 0;
+}
+
 /**
  * vimc_streamer_thread - Process frames through the pipeline
  *
@@ -143,6 +190,23 @@ static int vimc_streamer_thread(void *data)
        u8 *frame = NULL;
        int i;
 
+       u64 tick;
+       u32 interval_ms = 0;
+
+       if (V4L2_FRACT_COMPARE(stream->timeperframe_vid_cap, <, tpf_min) ||
+           V4L2_FRACT_COMPARE(stream->timeperframe_vid_cap, >, tpf_max)) {
+               pr_err("Zero timeperframe_vid_cap numerator or denominator");
+               return -EINVAL;
+       }
+
+       interval_ms = S_TO_MS(stream->timeperframe_vid_cap.numerator) /
+               stream->timeperframe_vid_cap.denominator;
+
+       pr_debug("Got interval = %u s / %u fps = %u ms.",
+                stream->timeperframe_vid_cap.numerator,
+                stream->timeperframe_vid_cap.denominator,
+                interval_ms);
+
        set_freezable();
 
        for (;;) {
@@ -150,15 +214,20 @@ static int vimc_streamer_thread(void *data)
                if (kthread_should_stop())
                        break;
 
+               interval_ms = S_TO_MS(stream->timeperframe_vid_cap.numerator) /
+                       stream->timeperframe_vid_cap.denominator;
+               tick = ktime_get_ns();
+
                for (i = stream->pipe_size - 1; i >= 0; i--) {
                        frame = stream->ved_pipeline[i]->process_frame(
                                        stream->ved_pipeline[i], frame);
                        if (!frame || IS_ERR(frame))
                                break;
                }
-               //wait for 60hz
+
                set_current_state(TASK_UNINTERRUPTIBLE);
-               schedule_timeout(HZ / 60);
+
+               vimc_force_frame_rate(interval_ms, tick);
        }
 
        return 0;
diff --git a/drivers/media/platform/vimc/vimc-streamer.h 
b/drivers/media/platform/vimc/vimc-streamer.h
index fe3c51f15fad..04d3d0ecb65c 100644
--- a/drivers/media/platform/vimc/vimc-streamer.h
+++ b/drivers/media/platform/vimc/vimc-streamer.h
@@ -9,6 +9,7 @@
 #ifndef _VIMC_STREAMER_H_
 #define _VIMC_STREAMER_H_
 
+#include <linux/videodev2.h>
 #include <media/media-device.h>
 
 #include "vimc-common.h"
@@ -35,6 +36,8 @@ struct vimc_stream {
        struct vimc_ent_device *ved_pipeline[VIMC_STREAMER_PIPELINE_MAX_SIZE];
        unsigned int pipe_size;
        struct task_struct *kthread;
+
+       struct v4l2_fract timeperframe_vid_cap;
 };
 
 int vimc_streamer_s_stream(struct vimc_stream *stream,
-- 
2.21.0

Reply via email to