diff --git a/Documentation/admin-guide/media/vimc.rst b/Documentation/admin-guide/media/vimc.rst index 29d843a8ddb1..a1b7de769e23 100644 --- a/Documentation/admin-guide/media/vimc.rst +++ b/Documentation/admin-guide/media/vimc.rst @@ -4,7 +4,7 @@ The Virtual Media Controller Driver (vimc) ========================================== The vimc driver emulates complex video hardware using the V4L2 API and the Media -API. It has a capture device and three subdevices: sensor, debayer and scaler. +API. It has a capture device and three subdevices: sensor, debayer, scaler and RGB/YUV input entity. Topology -------- @@ -29,17 +29,28 @@ configuration on each linked subdevice to stream frames through the pipeline. If the configuration doesn't match, the stream will fail. The ``v4l-utils`` package is a bundle of user-space applications, that comes with ``media-ctl`` and ``v4l2-ctl`` that can be used to configure the vimc configuration. This sequence -of commands fits for the default topology: +-of commands fits for the default topology: .. code-block:: bash - media-ctl -d platform:vimc -V '"Sensor A":0[fmt:SBGGR8_1X8/640x480]' - media-ctl -d platform:vimc -V '"Debayer A":0[fmt:SBGGR8_1X8/640x480]' - media-ctl -d platform:vimc -V '"Scaler":0[fmt:RGB888_1X24/640x480]' - media-ctl -d platform:vimc -V '"Scaler":0[crop:(100,50)/400x150]' - media-ctl -d platform:vimc -V '"Scaler":1[fmt:RGB888_1X24/300x700]' - v4l2-ctl -z platform:vimc -d "RGB/YUV Capture" -v width=300,height=700 - v4l2-ctl -z platform:vimc -d "Raw Capture 0" -v pixelformat=BA81 + media-ctl -d platform:vimc.0 -V '"Sensor A":0[fmt:SBGGR8_1X8/640x480]' + media-ctl -d platform:vimc.0 -V '"Debayer A":0[fmt:SBGGR8_1X8/640x480]' + media-ctl -d platform:vimc.0 -V '"Scaler":0[fmt:RGB888_1X24/640x480]' + media-ctl -d platform:vimc.0 -V '"Scaler":0[crop:(100,50)/400x150]' + media-ctl -d platform:vimc.0 -V '"Scaler":1[fmt:RGB888_1X24/300x700]' + v4l2-ctl -z platform:vimc.0 -d "RGB/YUV Capture" -v width=300,height=700 + v4l2-ctl -z platform:vimc.0 -d "Raw Capture 0" -v pixelformat=BA81 + +The following commands switch the scaler input to the RGB/YUV entity and request +ARGB output with a padded capture stride. + +.. code-block:: bash + + media-ctl -d platform:vimc.0 -V '"RGB/YUV Input":0[fmt:RGB888_1X24/640x480]' + media-ctl -d platform:vimc.0 -V '"Scaler":0[fmt:RGB888_1X24/640x480]' + media-ctl -d platform:vimc.0 -V '"Scaler":1[fmt:ARGB8888_1X32/640x480]' + v4l2-ctl -z platform:vimc.0 -d "RGB/YUV Capture" \ + -v pixelformat=RGB3,width=640,height=480,bytesperline=4096 Subdevices ---------- @@ -53,6 +64,16 @@ vimc-sensor: * 1 Pad source +vimc-input: + Simulates an RGB/YUV frame source for pipelines that start after a real + debayer stage. It exposes a single source pad that initially advertises + ``MEDIA_BUS_FMT_RGB888_1X24`` and accepts the usual width/height updates via + ``media-ctl``, which is useful for software-driven frame injection + experiments. + Exposes: + + * 1 Pad source + vimc-lens: Ancillary lens for a sensor. Supports auto focus control. Linked to a vimc-sensor using an ancillary link. The lens supports FOCUS_ABSOLUTE diff --git a/drivers/media/test-drivers/vimc/Makefile b/drivers/media/test-drivers/vimc/Makefile index 9b9631562473..7e1fdb2f2a78 100644 --- a/drivers/media/test-drivers/vimc/Makefile +++ b/drivers/media/test-drivers/vimc/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 vimc-y := vimc-core.o vimc-common.o vimc-streamer.o vimc-capture.o \ - vimc-debayer.o vimc-scaler.o vimc-sensor.o vimc-lens.o + vimc-debayer.o vimc-scaler.o vimc-sensor.o vimc-lens.o \ + vimc-input.o obj-$(CONFIG_VIDEO_VIMC) += vimc.o diff --git a/drivers/media/test-drivers/vimc/vimc-capture.c b/drivers/media/test-drivers/vimc/vimc-capture.c index 7f6124025fc9..7164ec51eb80 100644 --- a/drivers/media/test-drivers/vimc/vimc-capture.c +++ b/drivers/media/test-drivers/vimc/vimc-capture.c @@ -85,6 +85,7 @@ static int vimc_capture_try_fmt_vid_cap(struct file *file, void *priv, { struct v4l2_pix_format *format = &f->fmt.pix; const struct vimc_pix_map *vpix; + u32 min_bpl, max_bpl; format->width = clamp_t(u32, format->width, VIMC_FRAME_MIN_WIDTH, VIMC_FRAME_MAX_WIDTH) & ~1; @@ -97,8 +98,18 @@ static int vimc_capture_try_fmt_vid_cap(struct file *file, void *priv, format->pixelformat = fmt_default.pixelformat; vpix = vimc_pix_map_by_pixelformat(format->pixelformat); } - /* TODO: Add support for custom bytesperline values */ - format->bytesperline = format->width * vpix->bpp; + + /* Calculate the minimum supported bytesperline value */ + min_bpl = format->width * vpix->bpp; + /* Calculate the maximum supported bytesperline value */ + max_bpl = VIMC_FRAME_MAX_WIDTH * vpix->bpp; + + /* Clamp bytesperline to the valid range */ + if (format->bytesperline > max_bpl) + format->bytesperline = max_bpl; + if (format->bytesperline < min_bpl) + format->bytesperline = min_bpl; + format->sizeimage = format->bytesperline * format->height; if (format->field == V4L2_FIELD_ANY) diff --git a/drivers/media/test-drivers/vimc/vimc-common.h b/drivers/media/test-drivers/vimc/vimc-common.h index 7a45a2117748..6c94b1635fa8 100644 --- a/drivers/media/test-drivers/vimc/vimc-common.h +++ b/drivers/media/test-drivers/vimc/vimc-common.h @@ -172,6 +172,7 @@ extern const struct vimc_ent_type vimc_debayer_type; extern const struct vimc_ent_type vimc_scaler_type; extern const struct vimc_ent_type vimc_capture_type; extern const struct vimc_ent_type vimc_lens_type; +extern const struct vimc_ent_type vimc_input_type; /** * vimc_pix_map_by_index - get vimc_pix_map struct by its index diff --git a/drivers/media/test-drivers/vimc/vimc-core.c b/drivers/media/test-drivers/vimc/vimc-core.c index f632c77e52f5..2f6846facb23 100644 --- a/drivers/media/test-drivers/vimc/vimc-core.c +++ b/drivers/media/test-drivers/vimc/vimc-core.c @@ -107,9 +107,8 @@ static const struct vimc_ent_config ent_config[] = { .type = &vimc_capture_type }, [RGB_YUV_INPUT] = { - /* TODO: change this to vimc-input when it is implemented */ .name = "RGB/YUV Input", - .type = &vimc_sensor_type + .type = &vimc_input_type }, [SCALER] = { .name = "Scaler", diff --git a/drivers/media/test-drivers/vimc/vimc-debayer.c b/drivers/media/test-drivers/vimc/vimc-debayer.c index bbb7c7a86df0..0fa1cb8d3be1 100644 --- a/drivers/media/test-drivers/vimc/vimc-debayer.c +++ b/drivers/media/test-drivers/vimc/vimc-debayer.c @@ -15,9 +15,6 @@ #include "vimc-common.h" -/* TODO: Add support for more output formats, we only support RGB888 for now. */ -#define VIMC_DEBAYER_SOURCE_MBUS_FMT MEDIA_BUS_FMT_RGB888_1X24 - enum vimc_debayer_rgb_colors { VIMC_DEBAYER_RED = 0, VIMC_DEBAYER_GREEN = 1, @@ -73,6 +70,7 @@ static const u32 vimc_debayer_src_mbus_codes[] = { MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, MEDIA_BUS_FMT_RGB888_1X32_PADHI, + MEDIA_BUS_FMT_ARGB8888_1X32, }; static const struct vimc_debayer_pix_map vimc_debayer_pix_map_list[] = { @@ -170,7 +168,7 @@ static int vimc_debayer_init_state(struct v4l2_subdev *sd, mf = v4l2_subdev_state_get_format(sd_state, 1); *mf = sink_fmt_default; - mf->code = VIMC_DEBAYER_SOURCE_MBUS_FMT; + mf->code = vimc_debayer_src_mbus_codes[0]; return 0; } @@ -239,6 +237,14 @@ static void vimc_debayer_adjust_sink_fmt(struct v4l2_mbus_framefmt *fmt) vimc_colorimetry_clamp(fmt); } +static void vimc_debayer_set_rgb_mbus_fmt_default(struct v4l2_mbus_framefmt *fmt) +{ + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; +} + static int vimc_debayer_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) @@ -250,12 +256,30 @@ static int vimc_debayer_set_fmt(struct v4l2_subdev *sd, if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE && vdebayer->src_frame) return -EBUSY; - /* - * Do not change the format of the source pad, it is propagated from - * the sink. - */ - if (VIMC_IS_SRC(fmt->pad)) - return v4l2_subdev_get_fmt(sd, sd_state, fmt); + if (VIMC_IS_SRC(fmt->pad)) { + struct v4l2_mbus_framefmt *source_fmt; + struct v4l2_mbus_framefmt *sink_fmt; + + /* Validate the requested source format */ + if (!vimc_debayer_src_code_is_valid(fmt->format.code)) + return -EINVAL; + + /* Get current formats */ + source_fmt = v4l2_subdev_state_get_format(sd_state, 1); + sink_fmt = v4l2_subdev_state_get_format(sd_state, 0); + + /* Update source format with appropriate properties for RGB output */ + source_fmt->code = fmt->format.code; + source_fmt->width = sink_fmt->width; /* Size should match */ + source_fmt->height = sink_fmt->height; /* Size should match */ + source_fmt->field = sink_fmt->field; /* Field handling should match */ + + /* Set appropriate colorimetry for RGB output */ + vimc_debayer_set_rgb_mbus_fmt_default(source_fmt); + + fmt->format = *source_fmt; + return 0; + } /* Set the new format in the sink pad. */ vimc_debayer_adjust_sink_fmt(&fmt->format); @@ -278,8 +302,25 @@ static int vimc_debayer_set_fmt(struct v4l2_subdev *sd, /* Propagate the format to the source pad. */ format = v4l2_subdev_state_get_format(sd_state, 1); - *format = fmt->format; - format->code = VIMC_DEBAYER_SOURCE_MBUS_FMT; + + /* Propagate size and field from sink, but maintain source code */ + format->width = fmt->format.width; + format->height = fmt->format.height; + format->field = fmt->format.field; + + /* + * Source code should always be valid (set during init or via set_fmt). + * If somehow it's not, this is a bug - log warning and fix it. + */ + if (!vimc_debayer_src_code_is_valid(format->code)) { + dev_warn(vdebayer->ved.dev, + "%s: Invalid source code 0x%x, resetting to default\n", + vdebayer->sd.name, format->code); + format->code = vimc_debayer_src_mbus_codes[0]; + } + + /* Set appropriate colorimetry for RGB output */ + vimc_debayer_set_rgb_mbus_fmt_default(format); return 0; } @@ -297,19 +338,45 @@ static void vimc_debayer_process_rgb_frame(struct vimc_debayer_device *vdebayer, unsigned int rgb[3]) { const struct vimc_pix_map *vpix; - unsigned int i, index; + unsigned int index; vpix = vimc_pix_map_by_code(vdebayer->hw.src_code); - index = VIMC_FRAME_INDEX(lin, col, vdebayer->hw.size.width, 3); - for (i = 0; i < 3; i++) { - switch (vpix->pixelformat) { - case V4L2_PIX_FMT_RGB24: - vdebayer->src_frame[index + i] = rgb[i]; - break; - case V4L2_PIX_FMT_BGR24: - vdebayer->src_frame[index + i] = rgb[2 - i]; - break; - } + if (!vpix) { + dev_dbg(vdebayer->ved.dev, "Invalid source code: 0x%x\n", + vdebayer->hw.src_code); + return; + } + + index = VIMC_FRAME_INDEX(lin, col, vdebayer->hw.size.width, vpix->bpp); + + switch (vpix->pixelformat) { + case V4L2_PIX_FMT_RGB24: + /* RGB24: R-G-B */ + vdebayer->src_frame[index + 0] = rgb[0]; /* Red */ + vdebayer->src_frame[index + 1] = rgb[1]; /* Green */ + vdebayer->src_frame[index + 2] = rgb[2]; /* Blue */ + break; + + case V4L2_PIX_FMT_BGR24: + /* BGR24: B-G-R */ + vdebayer->src_frame[index + 0] = rgb[2]; /* Blue */ + vdebayer->src_frame[index + 1] = rgb[1]; /* Green */ + vdebayer->src_frame[index + 2] = rgb[0]; /* Red */ + break; + + case V4L2_PIX_FMT_ARGB32: + /* ARGB32: A-R-G-B (set alpha to 255) */ + vdebayer->src_frame[index + 0] = 255; /* Alpha */ + vdebayer->src_frame[index + 1] = rgb[0]; /* Red */ + vdebayer->src_frame[index + 2] = rgb[1]; /* Green */ + vdebayer->src_frame[index + 3] = rgb[2]; /* Blue */ + break; + + default: + dev_dbg(vdebayer->ved.dev, + "Unsupported pixel format for debayer: 0x%x\n", + vpix->pixelformat); + break; } } diff --git a/drivers/media/test-drivers/vimc/vimc-input.c b/drivers/media/test-drivers/vimc/vimc-input.c new file mode 100644 index 000000000000..cedcc450d59e --- /dev/null +++ b/drivers/media/test-drivers/vimc/vimc-input.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * vimc-input.c Virtual Media Controller Driver + * + * Copyright (C) 2025 Virtual Input Entity Implementation + */ + +#include +#include + +#include "vimc-common.h" + +struct vimc_input_device { + struct vimc_ent_device ved; + struct v4l2_subdev sd; + struct media_pad pad; +}; + +static const struct v4l2_mbus_framefmt fmt_default = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, +}; + +static int vimc_input_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *mf; + unsigned int i; + + for (i = 0; i < sd->entity.num_pads; i++) { + mf = v4l2_subdev_state_get_format(sd_state, i); + *mf = fmt_default; + } + + return 0; +} + +static int vimc_input_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_RGB888_1X24; + + return 0; +} + +static int vimc_input_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + const struct vimc_pix_map *vpix; + + if (fse->index) + return -EINVAL; + + /* Only accept code in the pix map table */ + vpix = vimc_pix_map_by_code(fse->code); + if (!vpix) + return -EINVAL; + + fse->min_width = VIMC_FRAME_MIN_WIDTH; + fse->max_width = VIMC_FRAME_MAX_WIDTH; + fse->min_height = VIMC_FRAME_MIN_HEIGHT; + fse->max_height = VIMC_FRAME_MAX_HEIGHT; + + return 0; +} + +static int vimc_input_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_state_get_format(sd_state, fmt->pad); + + fmt->format = *mf; + + return 0; +} + +static int vimc_input_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_state_get_format(sd_state, fmt->pad); + + /* Set the new format */ + *mf = fmt->format; + + return 0; +} + +static int vimc_input_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + /* For input entity, we don't allocate frames since we expect + * external frame injection. Just mark that streaming is active. + * + * TODO: For future enhancement, consider implementing frame generation + * or userspace frame injection mechanism. This would require: + * - Frame buffer allocation (similar to vimc-sensor.c) + * - Interface for userspace to inject frames (e.g., via sysfs/debugfs) + * - Frame rate control for generated test patterns + * - Integration with VIMC's streaming infrastructure + * This would make the input entity suitable for more testing scenarios. + */ + return 0; +} + +static int vimc_input_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + /* Streaming stopped - no cleanup needed for input entity */ + return 0; +} + +static const struct v4l2_subdev_pad_ops vimc_input_pad_ops = { + .enum_mbus_code = vimc_input_enum_mbus_code, + .enum_frame_size = vimc_input_enum_frame_size, + .get_fmt = vimc_input_get_fmt, + .set_fmt = vimc_input_set_fmt, + .enable_streams = vimc_input_enable_streams, + .disable_streams = vimc_input_disable_streams, +}; + +static const struct v4l2_subdev_ops vimc_input_ops = { + .pad = &vimc_input_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops vimc_input_internal_ops = { + .init_state = vimc_input_init_state, +}; + +static void vimc_input_release(struct vimc_ent_device *ved) +{ + struct vimc_input_device *vinput = + container_of(ved, struct vimc_input_device, ved); + + v4l2_subdev_cleanup(&vinput->sd); + media_entity_cleanup(vinput->ved.ent); + kfree(vinput); +} + +/* + * Input process frame function + * For an input entity, just return the received frame unchanged + */ +static void *vimc_input_process_frame(struct vimc_ent_device *ved, + const void *frame) +{ + /* For an input entity, just return the received frame unchanged. + * + * TODO: Future enhancement could implement: + * - Frame validation and format checking + * - Frame transformation or processing + * - Frame injection from userspace buffers + * - Frame rate limiting or buffering + * Currently, this is a simple pass-through for external frame sources. + */ + return (void *)frame; +} + +static struct vimc_ent_device *vimc_input_add(struct vimc_device *vimc, + const char *vcfg_name) +{ + struct v4l2_device *v4l2_dev = &vimc->v4l2_dev; + struct vimc_input_device *vinput; + int ret; + + /* Allocate the vinput struct */ + vinput = kzalloc(sizeof(*vinput), GFP_KERNEL); + if (!vinput) + return ERR_PTR(-ENOMEM); + + /* Initialize the media pad */ + vinput->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = vimc_ent_sd_register(&vinput->ved, &vinput->sd, v4l2_dev, + vcfg_name, + MEDIA_ENT_F_IO_V4L, 1, &vinput->pad, + &vimc_input_internal_ops, &vimc_input_ops); + if (ret) + goto err_free_vinput; + + vinput->ved.process_frame = vimc_input_process_frame; + vinput->ved.dev = vimc->mdev.dev; + + return &vinput->ved; + +err_free_vinput: + kfree(vinput); + + return ERR_PTR(ret); +} + +const struct vimc_ent_type vimc_input_type = { + .add = vimc_input_add, + .release = vimc_input_release +}; diff --git a/drivers/media/test-drivers/vimc/vimc-sensor.c b/drivers/media/test-drivers/vimc/vimc-sensor.c index 027767777763..900c3f329f1c 100644 --- a/drivers/media/test-drivers/vimc/vimc-sensor.c +++ b/drivers/media/test-drivers/vimc/vimc-sensor.c @@ -105,8 +105,10 @@ static void vimc_sensor_tpg_s_format(struct vimc_sensor_device *vsensor, tpg_s_bytesperline(&vsensor->tpg, 0, format->width * vpix->bpp); tpg_s_buf_height(&vsensor->tpg, format->height); tpg_s_fourcc(&vsensor->tpg, vpix->pixelformat); - /* TODO: add support for V4L2_FIELD_ALTERNATE */ - tpg_s_field(&vsensor->tpg, format->field, false); + if (format->field == V4L2_FIELD_ALTERNATE) + tpg_s_field(&vsensor->tpg, V4L2_FIELD_TOP, true); + else + tpg_s_field(&vsensor->tpg, format->field, false); tpg_s_colorspace(&vsensor->tpg, format->colorspace); tpg_s_ycbcr_enc(&vsensor->tpg, format->ycbcr_enc); tpg_s_quantization(&vsensor->tpg, format->quantization); @@ -127,8 +129,7 @@ static void vimc_sensor_adjust_fmt(struct v4l2_mbus_framefmt *fmt) fmt->height = clamp_t(u32, fmt->height, VIMC_FRAME_MIN_HEIGHT, VIMC_FRAME_MAX_HEIGHT) & ~1; - /* TODO: add support for V4L2_FIELD_ALTERNATE */ - if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE) + if (fmt->field == V4L2_FIELD_ANY) fmt->field = fmt_default.field; vimc_colorimetry_clamp(fmt);