| // 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_state_get_format(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_state_get_format(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_state_get_format(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_state(struct v4l2_subdev *sd, | |
| struct v4l2_subdev_state *sd_state) | |
| { | |
| struct v4l2_mbus_framefmt *v4l2_try_fmt = | |
| v4l2_subdev_state_get_format(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_state_get_crop(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, | |
| }; | |
| 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, | |
| }; | |
| static const struct v4l2_subdev_internal_ops isc_scaler_internal_ops = { | |
| .init_state = isc_scaler_init_state, | |
| }; | |
| int isc_scaler_init(struct isc_device *isc) | |
| { | |
| int ret; | |
| v4l2_subdev_init(&isc->scaler_sd, &xisc_scaler_subdev_ops); | |
| isc->scaler_sd.internal_ops = &isc_scaler_internal_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); | |