As a top MC video driver, the microchip-isc should not propagate the format to the subdevice, it should rather check at start_streaming() time if the subdev is properly configured with a compatible format. Removed the whole format finding logic, and reworked the format verification at start_streaming time, such that the ISC will return an error if the subdevice is not properly configured. To achieve this, media_pipeline_start is called and a link_validate callback is created to check the formats. With this being done, the module parameter 'sensor_preferred' makes no sense anymore. The ISC should not decide which format the sensor is using. The ISC should only cope with the situation and inform userspace if the streaming is possible in the current configuration. The redesign of the format propagation has also risen the question of the enumfmt callback. If enumfmt is called with an mbus_code, the enumfmt handler should only return the formats that are supported for this mbus_code. Otherwise, the enumfmt will report all the formats that the ISC could output. With this rework, the dynamic list of user formats is removed. It makes no more sense to identify at complete time which formats the sensor could emit, and add those into a separate dynamic list. The ISC will start with a simple preconfigured default format, and at link validate time, decide whether it can use the format that is configured on the sink or not. >From now on, the driver also advertises the IO_MC capability. Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
268 lines
7.5 KiB
C
268 lines
7.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Microchip Image Sensor Controller (ISC) Scaler entity support
|
|
*
|
|
* Copyright (C) 2022 Microchip Technology, Inc.
|
|
*
|
|
* Author: Eugen Hristev <eugen.hristev@microchip.com>
|
|
*
|
|
*/
|
|
|
|
#include <media/media-device.h>
|
|
#include <media/media-entity.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-subdev.h>
|
|
|
|
#include "microchip-isc-regs.h"
|
|
#include "microchip-isc.h"
|
|
|
|
static void isc_scaler_prepare_fmt(struct v4l2_mbus_framefmt *framefmt)
|
|
{
|
|
framefmt->colorspace = V4L2_COLORSPACE_SRGB;
|
|
framefmt->field = V4L2_FIELD_NONE;
|
|
framefmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
|
|
framefmt->quantization = V4L2_QUANTIZATION_DEFAULT;
|
|
framefmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
|
|
};
|
|
|
|
static int isc_scaler_get_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *sd_state,
|
|
struct v4l2_subdev_format *format)
|
|
{
|
|
struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
|
|
struct v4l2_mbus_framefmt *v4l2_try_fmt;
|
|
|
|
if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
|
|
v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
|
|
format->pad);
|
|
format->format = *v4l2_try_fmt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
format->format = isc->scaler_format[format->pad];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int isc_scaler_set_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *sd_state,
|
|
struct v4l2_subdev_format *req_fmt)
|
|
{
|
|
struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
|
|
struct v4l2_mbus_framefmt *v4l2_try_fmt;
|
|
struct isc_format *fmt;
|
|
unsigned int i;
|
|
|
|
/* Source format is fixed, we cannot change it */
|
|
if (req_fmt->pad == ISC_SCALER_PAD_SOURCE) {
|
|
req_fmt->format = isc->scaler_format[ISC_SCALER_PAD_SOURCE];
|
|
return 0;
|
|
}
|
|
|
|
/* There is no limit on the frame size on the sink pad */
|
|
v4l_bound_align_image(&req_fmt->format.width, 16, UINT_MAX, 0,
|
|
&req_fmt->format.height, 16, UINT_MAX, 0, 0);
|
|
|
|
isc_scaler_prepare_fmt(&req_fmt->format);
|
|
|
|
fmt = isc_find_format_by_code(isc, req_fmt->format.code, &i);
|
|
|
|
if (!fmt)
|
|
fmt = &isc->formats_list[0];
|
|
|
|
req_fmt->format.code = fmt->mbus_code;
|
|
|
|
if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
|
|
v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
|
|
req_fmt->pad);
|
|
*v4l2_try_fmt = req_fmt->format;
|
|
/* Trying on the sink pad makes the source pad change too */
|
|
v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
|
|
ISC_SCALER_PAD_SOURCE);
|
|
*v4l2_try_fmt = req_fmt->format;
|
|
|
|
v4l_bound_align_image(&v4l2_try_fmt->width,
|
|
16, isc->max_width, 0,
|
|
&v4l2_try_fmt->height,
|
|
16, isc->max_height, 0, 0);
|
|
/* if we are just trying, we are done */
|
|
return 0;
|
|
}
|
|
|
|
isc->scaler_format[ISC_SCALER_PAD_SINK] = req_fmt->format;
|
|
|
|
/* The source pad is the same as the sink, but we have to crop it */
|
|
isc->scaler_format[ISC_SCALER_PAD_SOURCE] =
|
|
isc->scaler_format[ISC_SCALER_PAD_SINK];
|
|
v4l_bound_align_image
|
|
(&isc->scaler_format[ISC_SCALER_PAD_SOURCE].width, 16,
|
|
isc->max_width, 0,
|
|
&isc->scaler_format[ISC_SCALER_PAD_SOURCE].height, 16,
|
|
isc->max_height, 0, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *sd_state,
|
|
struct v4l2_subdev_mbus_code_enum *code)
|
|
{
|
|
struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
|
|
|
|
/*
|
|
* All formats supported by the ISC are supported by the scaler.
|
|
* Advertise the formats which the ISC can take as input, as the scaler
|
|
* entity cropping is part of the PFE module (parallel front end)
|
|
*/
|
|
if (code->index < isc->formats_list_size) {
|
|
code->code = isc->formats_list[code->index].mbus_code;
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int isc_scaler_g_sel(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *sd_state,
|
|
struct v4l2_subdev_selection *sel)
|
|
{
|
|
struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
|
|
|
|
if (sel->pad == ISC_SCALER_PAD_SOURCE)
|
|
return -EINVAL;
|
|
|
|
if (sel->target != V4L2_SEL_TGT_CROP_BOUNDS &&
|
|
sel->target != V4L2_SEL_TGT_CROP)
|
|
return -EINVAL;
|
|
|
|
sel->r.height = isc->scaler_format[ISC_SCALER_PAD_SOURCE].height;
|
|
sel->r.width = isc->scaler_format[ISC_SCALER_PAD_SOURCE].width;
|
|
|
|
sel->r.left = 0;
|
|
sel->r.top = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int isc_scaler_init_cfg(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_state *sd_state)
|
|
{
|
|
struct v4l2_mbus_framefmt *v4l2_try_fmt =
|
|
v4l2_subdev_get_try_format(sd, sd_state, 0);
|
|
struct v4l2_rect *try_crop;
|
|
struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
|
|
|
|
*v4l2_try_fmt = isc->scaler_format[ISC_SCALER_PAD_SOURCE];
|
|
|
|
try_crop = v4l2_subdev_get_try_crop(sd, sd_state, 0);
|
|
|
|
try_crop->top = 0;
|
|
try_crop->left = 0;
|
|
try_crop->width = v4l2_try_fmt->width;
|
|
try_crop->height = v4l2_try_fmt->height;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_subdev_pad_ops isc_scaler_pad_ops = {
|
|
.enum_mbus_code = isc_scaler_enum_mbus_code,
|
|
.set_fmt = isc_scaler_set_fmt,
|
|
.get_fmt = isc_scaler_get_fmt,
|
|
.get_selection = isc_scaler_g_sel,
|
|
.init_cfg = isc_scaler_init_cfg,
|
|
};
|
|
|
|
static const struct media_entity_operations isc_scaler_entity_ops = {
|
|
.link_validate = v4l2_subdev_link_validate,
|
|
};
|
|
|
|
static const struct v4l2_subdev_ops xisc_scaler_subdev_ops = {
|
|
.pad = &isc_scaler_pad_ops,
|
|
};
|
|
|
|
int isc_scaler_init(struct isc_device *isc)
|
|
{
|
|
int ret;
|
|
|
|
v4l2_subdev_init(&isc->scaler_sd, &xisc_scaler_subdev_ops);
|
|
|
|
isc->scaler_sd.owner = THIS_MODULE;
|
|
isc->scaler_sd.dev = isc->dev;
|
|
snprintf(isc->scaler_sd.name, sizeof(isc->scaler_sd.name),
|
|
"microchip_isc_scaler");
|
|
|
|
isc->scaler_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
isc->scaler_sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
|
|
isc->scaler_sd.entity.ops = &isc_scaler_entity_ops;
|
|
isc->scaler_pads[ISC_SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
|
|
isc->scaler_pads[ISC_SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
|
|
|
|
isc_scaler_prepare_fmt(&isc->scaler_format[ISC_SCALER_PAD_SOURCE]);
|
|
isc->scaler_format[ISC_SCALER_PAD_SOURCE].height = isc->max_height;
|
|
isc->scaler_format[ISC_SCALER_PAD_SOURCE].width = isc->max_width;
|
|
isc->scaler_format[ISC_SCALER_PAD_SOURCE].code =
|
|
isc->formats_list[0].mbus_code;
|
|
|
|
isc->scaler_format[ISC_SCALER_PAD_SINK] =
|
|
isc->scaler_format[ISC_SCALER_PAD_SOURCE];
|
|
|
|
ret = media_entity_pads_init(&isc->scaler_sd.entity,
|
|
ISC_SCALER_PADS_NUM,
|
|
isc->scaler_pads);
|
|
if (ret < 0) {
|
|
dev_err(isc->dev, "scaler sd media entity init failed\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = v4l2_device_register_subdev(&isc->v4l2_dev, &isc->scaler_sd);
|
|
if (ret < 0) {
|
|
dev_err(isc->dev, "scaler sd failed to register subdev\n");
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(isc_scaler_init);
|
|
|
|
int isc_scaler_link(struct isc_device *isc)
|
|
{
|
|
int ret;
|
|
|
|
ret = media_create_pad_link(&isc->current_subdev->sd->entity,
|
|
isc->remote_pad, &isc->scaler_sd.entity,
|
|
ISC_SCALER_PAD_SINK,
|
|
MEDIA_LNK_FL_ENABLED |
|
|
MEDIA_LNK_FL_IMMUTABLE);
|
|
|
|
if (ret < 0) {
|
|
dev_err(isc->dev, "Failed to create pad link: %s to %s\n",
|
|
isc->current_subdev->sd->entity.name,
|
|
isc->scaler_sd.entity.name);
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(isc->dev, "link with %s pad: %d\n",
|
|
isc->current_subdev->sd->name, isc->remote_pad);
|
|
|
|
ret = media_create_pad_link(&isc->scaler_sd.entity,
|
|
ISC_SCALER_PAD_SOURCE,
|
|
&isc->video_dev.entity, ISC_PAD_SINK,
|
|
MEDIA_LNK_FL_ENABLED |
|
|
MEDIA_LNK_FL_IMMUTABLE);
|
|
|
|
if (ret < 0) {
|
|
dev_err(isc->dev, "Failed to create pad link: %s to %s\n",
|
|
isc->scaler_sd.entity.name,
|
|
isc->video_dev.entity.name);
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(isc->dev, "link with %s pad: %d\n", isc->scaler_sd.name,
|
|
ISC_SCALER_PAD_SOURCE);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(isc_scaler_link);
|
|
|