]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
media: vimc: sensor: Add pixel_rate,vblank and hblank configuration
authorFaizel K B <faizel.kb@gmail.com>
Thu, 12 Mar 2026 23:16:15 +0000 (16:16 -0700)
committerHans Verkuil <hverkuil+cisco@kernel.org>
Tue, 17 Mar 2026 09:50:19 +0000 (10:50 +0100)
pixel_rate and hblank as read only parameter. vblank can be configured
to match the desired frame rate.

Default values are, pixel_rate - 160 MHz, hblank - 800.
vblank defaults to an equivalent value of 30 fps for resolutions less than
or equal to 1920x1080 and 10 fps for higher resolutions. For higher
resolutions, modify pixel_rate in the driver code.
fps = pixel_rate / ((width + hblank) * (height + vblank))
minimum vblank - 4, maximum vblank - 65535

The configured fps delay is pre-calculated into jiffies and
stored in the sensor's hw structure for efficient access by the
streamer thread.

Signed-off-by: Faizel K B <faizel.kb@gmail.com>
Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
drivers/media/test-drivers/vimc/vimc-common.h
drivers/media/test-drivers/vimc/vimc-sensor.c

index 35789add6b4a3ae077f3c5ce9af0ddafb98d5665..861b334ffc65a245fdebc840e18e75f24edcc4c4 100644 (file)
 #define VIMC_FRAME_MIN_WIDTH 16
 #define VIMC_FRAME_MIN_HEIGHT 16
 
+#define VIMC_PIXEL_RATE_FIXED          160000000       /* 160 MHz */
+#define VIMC_HBLANK_FIXED              800
+/* VBLANK - vertical blanking (primary FPS control) */
+#define VIMC_VBLANK_MIN                        4
+#define VIMC_VBLANK_MAX                        65535
+#define VIMC_VBLANK_STEP               1
+#define VIMC_VBLANK_DEFAULT            3223           /* 30fps vga */
+#define VIMC_PIXELS_THRESHOLD_30FPS    (1920 * 1080) /* 2073600 pixels */
+
 #define VIMC_FRAME_INDEX(lin, col, width, bpp) ((lin * width + col) * bpp)
 
 /* Source and sink pad checks */
@@ -173,6 +182,9 @@ struct vimc_sensor_device {
        struct tpg_data tpg;
        struct v4l2_ctrl_handler hdl;
        struct media_pad pad;
+       struct v4l2_ctrl *pixel_rate;
+       struct v4l2_ctrl *hblank;
+       struct v4l2_ctrl *vblank;
 
        u8 *frame;
 
@@ -184,6 +196,7 @@ struct vimc_sensor_device {
                struct v4l2_area size;
                enum vimc_sensor_osd_mode osd_value;
                u64 start_stream_ts;
+               unsigned long fps_jiffies;
        } hw;
 };
 
index 4c64cdab8cda8db3780258c8dbc43a19b6593834..5deebcc78a33bb0cfb2a678fd4f411025760743b 100644 (file)
@@ -25,10 +25,15 @@ static const struct v4l2_mbus_framefmt fmt_default = {
 static int vimc_sensor_init_state(struct v4l2_subdev *sd,
                                  struct v4l2_subdev_state *sd_state)
 {
+       struct vimc_sensor_device *vsensor =
+               container_of(sd, struct vimc_sensor_device, sd);
+
        struct v4l2_mbus_framefmt *mf;
 
        mf = v4l2_subdev_state_get_format(sd_state, 0);
        *mf = fmt_default;
+       vsensor->hw.size.width = fmt_default.width;
+       vsensor->hw.size.height = fmt_default.height;
 
        return 0;
 }
@@ -87,6 +92,26 @@ static void vimc_sensor_tpg_s_format(struct vimc_sensor_device *vsensor,
        tpg_s_xfer_func(&vsensor->tpg, format->xfer_func);
 }
 
+static int vimc_sensor_update_frame_timing(struct v4l2_subdev *sd,
+                                          u32 width, u32 height)
+{
+       struct vimc_sensor_device *vsensor =
+               container_of(sd, struct vimc_sensor_device, sd);
+       u64 pixel_rate = vsensor->pixel_rate->val;
+       u32 hts = width + vsensor->hblank->val;
+       u32 vts = height + vsensor->vblank->val;
+       u64 total_pixels = (u64)hts * vts;
+       u64 frame_interval_ns;
+
+       frame_interval_ns = total_pixels * NSEC_PER_SEC;
+       do_div(frame_interval_ns, pixel_rate);
+       vsensor->hw.fps_jiffies = nsecs_to_jiffies(frame_interval_ns);
+       if (vsensor->hw.fps_jiffies == 0)
+               vsensor->hw.fps_jiffies = 1;
+
+       return 0;
+}
+
 static void vimc_sensor_adjust_fmt(struct v4l2_mbus_framefmt *fmt)
 {
        const struct vimc_pix_map *vpix;
@@ -108,6 +133,24 @@ static void vimc_sensor_adjust_fmt(struct v4l2_mbus_framefmt *fmt)
        vimc_colorimetry_clamp(fmt);
 }
 
+static u32 vimc_calc_vblank(u32 width, u32 height,
+                           s64 pixel_rate, s32 hblank)
+{
+       u32 hts = width + hblank;
+       u32 target_fps;
+       u64 vts;
+
+       target_fps = (width * height <= VIMC_PIXELS_THRESHOLD_30FPS) ? 30 : 10;
+
+       vts = (u64)pixel_rate;
+       do_div(vts, target_fps * hts);
+
+       if (vts > height)
+               return clamp((u32)(vts - height), VIMC_VBLANK_MIN, VIMC_VBLANK_MAX);
+
+       return VIMC_VBLANK_MIN;
+}
+
 static int vimc_sensor_set_fmt(struct v4l2_subdev *sd,
                               struct v4l2_subdev_state *sd_state,
                               struct v4l2_subdev_format *fmt)
@@ -137,6 +180,20 @@ static int vimc_sensor_set_fmt(struct v4l2_subdev *sd,
                fmt->format.xfer_func, fmt->format.ycbcr_enc);
 
        *mf = fmt->format;
+       if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+               u32 vblank_def = vimc_calc_vblank(fmt->format.width,
+                                                 fmt->format.height,
+                                                 vsensor->pixel_rate->val,
+                                                 vsensor->hblank->val);
+               vsensor->hw.size.width = fmt->format.width;
+               vsensor->hw.size.height = fmt->format.height;
+               __v4l2_ctrl_modify_range(vsensor->vblank,
+                                        VIMC_VBLANK_MIN,
+                                        VIMC_VBLANK_MAX,
+                                        VIMC_VBLANK_STEP,
+                                        vblank_def);
+               __v4l2_ctrl_s_ctrl(vsensor->vblank, vblank_def);
+       }
 
        return 0;
 }
@@ -222,6 +279,8 @@ static int vimc_sensor_s_stream(struct v4l2_subdev *sd, int enable)
 
                vsensor->hw.size.width = format->width;
                vsensor->hw.size.height = format->height;
+               vimc_sensor_update_frame_timing(sd, format->width,
+                                               format->height);
 
                v4l2_subdev_unlock_state(state);
 
@@ -293,6 +352,15 @@ static int vimc_sensor_s_ctrl(struct v4l2_ctrl *ctrl)
        case VIMC_CID_OSD_TEXT_MODE:
                vsensor->hw.osd_value = ctrl->val;
                break;
+       case V4L2_CID_PIXEL_RATE:
+               break;
+       case V4L2_CID_HBLANK:
+               break;
+       case V4L2_CID_VBLANK:
+               vimc_sensor_update_frame_timing(&vsensor->sd,
+                                               vsensor->hw.size.width,
+                                               vsensor->hw.size.height);
+               break;
        default:
                return -EINVAL;
        }
@@ -377,6 +445,26 @@ static struct vimc_ent_device *vimc_sensor_add(struct vimc_device *vimc,
                          V4L2_CID_HUE, -128, 127, 1, 0);
        v4l2_ctrl_new_std(&vsensor->hdl, &vimc_sensor_ctrl_ops,
                          V4L2_CID_SATURATION, 0, 255, 1, 128);
+       /* Timing controls for frame interval configuration */
+       vsensor->pixel_rate = v4l2_ctrl_new_std(&vsensor->hdl, &vimc_sensor_ctrl_ops,
+                                               V4L2_CID_PIXEL_RATE,
+                                               VIMC_PIXEL_RATE_FIXED, VIMC_PIXEL_RATE_FIXED,
+                                               1, VIMC_PIXEL_RATE_FIXED);
+       if (vsensor->pixel_rate)
+               vsensor->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       vsensor->hblank = v4l2_ctrl_new_std(&vsensor->hdl, &vimc_sensor_ctrl_ops,
+                                           V4L2_CID_HBLANK,
+                                           VIMC_HBLANK_FIXED, VIMC_HBLANK_FIXED,
+                                           1, VIMC_HBLANK_FIXED);
+       if (vsensor->hblank)
+               vsensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+       vsensor->vblank = v4l2_ctrl_new_std(&vsensor->hdl, &vimc_sensor_ctrl_ops,
+                                           V4L2_CID_VBLANK,
+                                           VIMC_VBLANK_MIN, VIMC_VBLANK_MAX,
+                                           VIMC_VBLANK_STEP, VIMC_VBLANK_DEFAULT);
+
        vsensor->sd.ctrl_handler = &vsensor->hdl;
        if (vsensor->hdl.error) {
                ret = vsensor->hdl.error;