// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2013--2024 Intel Corporation */ #include #include #include #include #include #include #include #include "ipu6-bus.h" #include "ipu6-isys.h" #include "ipu6-isys-subdev.h" unsigned int ipu6_isys_mbus_code_to_bpp(u32 code) { switch (code) { case MEDIA_BUS_FMT_RGB888_1X24: case MEDIA_BUS_FMT_META_24: return 24; case MEDIA_BUS_FMT_RGB565_1X16: case MEDIA_BUS_FMT_UYVY8_1X16: case MEDIA_BUS_FMT_YUYV8_1X16: case MEDIA_BUS_FMT_META_16: return 16; case MEDIA_BUS_FMT_SBGGR12_1X12: case MEDIA_BUS_FMT_SGBRG12_1X12: case MEDIA_BUS_FMT_SGRBG12_1X12: case MEDIA_BUS_FMT_SRGGB12_1X12: case MEDIA_BUS_FMT_META_12: return 12; case MEDIA_BUS_FMT_SBGGR10_1X10: case MEDIA_BUS_FMT_SGBRG10_1X10: case MEDIA_BUS_FMT_SGRBG10_1X10: case MEDIA_BUS_FMT_SRGGB10_1X10: case MEDIA_BUS_FMT_META_10: return 10; case MEDIA_BUS_FMT_SBGGR8_1X8: case MEDIA_BUS_FMT_SGBRG8_1X8: case MEDIA_BUS_FMT_SGRBG8_1X8: case MEDIA_BUS_FMT_SRGGB8_1X8: case MEDIA_BUS_FMT_META_8: return 8; default: WARN_ON(1); return 8; } } unsigned int ipu6_isys_mbus_code_to_mipi(u32 code) { switch (code) { case MEDIA_BUS_FMT_RGB565_1X16: return MIPI_CSI2_DT_RGB565; case MEDIA_BUS_FMT_RGB888_1X24: return MIPI_CSI2_DT_RGB888; case MEDIA_BUS_FMT_UYVY8_1X16: case MEDIA_BUS_FMT_YUYV8_1X16: return MIPI_CSI2_DT_YUV422_8B; case MEDIA_BUS_FMT_SBGGR16_1X16: case MEDIA_BUS_FMT_SGBRG16_1X16: case MEDIA_BUS_FMT_SGRBG16_1X16: case MEDIA_BUS_FMT_SRGGB16_1X16: return MIPI_CSI2_DT_RAW16; case MEDIA_BUS_FMT_SBGGR12_1X12: case MEDIA_BUS_FMT_SGBRG12_1X12: case MEDIA_BUS_FMT_SGRBG12_1X12: case MEDIA_BUS_FMT_SRGGB12_1X12: return MIPI_CSI2_DT_RAW12; case MEDIA_BUS_FMT_SBGGR10_1X10: case MEDIA_BUS_FMT_SGBRG10_1X10: case MEDIA_BUS_FMT_SGRBG10_1X10: case MEDIA_BUS_FMT_SRGGB10_1X10: return MIPI_CSI2_DT_RAW10; case MEDIA_BUS_FMT_SBGGR8_1X8: case MEDIA_BUS_FMT_SGBRG8_1X8: case MEDIA_BUS_FMT_SGRBG8_1X8: case MEDIA_BUS_FMT_SRGGB8_1X8: return MIPI_CSI2_DT_RAW8; default: /* return unavailable MIPI data type - 0x3f */ WARN_ON(1); return 0x3f; } } bool ipu6_isys_is_bayer_format(u32 code) { switch (ipu6_isys_mbus_code_to_mipi(code)) { case MIPI_CSI2_DT_RAW8: case MIPI_CSI2_DT_RAW10: case MIPI_CSI2_DT_RAW12: case MIPI_CSI2_DT_RAW14: case MIPI_CSI2_DT_RAW16: case MIPI_CSI2_DT_RAW20: case MIPI_CSI2_DT_RAW24: case MIPI_CSI2_DT_RAW28: return true; default: return false; } } u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y) { static const u32 code_map[] = { MEDIA_BUS_FMT_SRGGB8_1X8, MEDIA_BUS_FMT_SGRBG8_1X8, MEDIA_BUS_FMT_SGBRG8_1X8, MEDIA_BUS_FMT_SBGGR8_1X8, MEDIA_BUS_FMT_SRGGB10_1X10, MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SRGGB12_1X12, MEDIA_BUS_FMT_SGRBG12_1X12, MEDIA_BUS_FMT_SGBRG12_1X12, MEDIA_BUS_FMT_SBGGR12_1X12, MEDIA_BUS_FMT_SRGGB16_1X16, MEDIA_BUS_FMT_SGRBG16_1X16, MEDIA_BUS_FMT_SGBRG16_1X16, MEDIA_BUS_FMT_SBGGR16_1X16, }; u32 i; for (i = 0; i < ARRAY_SIZE(code_map); i++) if (code_map[i] == code) break; if (WARN_ON(i == ARRAY_SIZE(code_map))) return code; return code_map[i ^ (((y & 1) << 1) | (x & 1))]; } int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); struct v4l2_mbus_framefmt *fmt; struct v4l2_rect *crop; u32 code = asd->supported_codes[0]; u32 other_pad, other_stream; unsigned int i; int ret; /* No transcoding, source and sink formats must match. */ if ((sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SOURCE) && sd->entity.num_pads > 1) return v4l2_subdev_get_fmt(sd, state, format); format->format.width = clamp(format->format.width, IPU6_ISYS_MIN_WIDTH, IPU6_ISYS_MAX_WIDTH); format->format.height = clamp(format->format.height, IPU6_ISYS_MIN_HEIGHT, IPU6_ISYS_MAX_HEIGHT); for (i = 0; asd->supported_codes[i]; i++) { if (asd->supported_codes[i] == format->format.code) { code = asd->supported_codes[i]; break; } } format->format.code = code; format->format.field = V4L2_FIELD_NONE; /* Store the format and propagate it to the source pad. */ fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); if (!fmt) return -EINVAL; *fmt = format->format; if (!(sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SINK)) return 0; /* propagate format to following source pad */ fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, format->stream); if (!fmt) return -EINVAL; *fmt = format->format; ret = v4l2_subdev_routing_find_opposite_end(&state->routing, format->pad, format->stream, &other_pad, &other_stream); if (ret) return -EINVAL; crop = v4l2_subdev_state_get_crop(state, other_pad, other_stream); /* reset crop */ crop->left = 0; crop->top = 0; crop->width = fmt->width; crop->height = fmt->height; return 0; } int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_mbus_code_enum *code) { struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); const u32 *supported_codes = asd->supported_codes; u32 index; for (index = 0; supported_codes[index]; index++) { if (index == code->index) { code->code = supported_codes[index]; return 0; } } return -EINVAL; } static int subdev_set_routing(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_krouting *routing) { static const struct v4l2_mbus_framefmt format = { .width = 4096, .height = 3072, .code = MEDIA_BUS_FMT_SGRBG10_1X10, .field = V4L2_FIELD_NONE, }; int ret; ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); if (ret) return ret; return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); } int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, struct v4l2_mbus_framefmt *format) { struct v4l2_mbus_framefmt *fmt; struct v4l2_subdev_state *state; if (!sd || !format) return -EINVAL; state = v4l2_subdev_lock_and_get_active_state(sd); fmt = v4l2_subdev_state_get_format(state, pad, stream); if (fmt) *format = *fmt; v4l2_subdev_unlock_state(state); return fmt ? 0 : -EINVAL; } int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream, struct v4l2_rect *crop) { struct v4l2_subdev_state *state; struct v4l2_rect *rect; if (!sd || !crop) return -EINVAL; state = v4l2_subdev_lock_and_get_active_state(sd); rect = v4l2_subdev_state_get_crop(state, pad, stream); if (rect) *crop = *rect; v4l2_subdev_unlock_state(state); return rect ? 0 : -EINVAL; } u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad) { struct v4l2_subdev_state *state; struct v4l2_subdev_route *routes; unsigned int i; u32 source_stream = 0; state = v4l2_subdev_lock_and_get_active_state(sd); if (!state) return 0; routes = state->routing.routes; for (i = 0; i < state->routing.num_routes; i++) { if (routes[i].source_pad == pad) { source_stream = routes[i].source_stream; break; } } v4l2_subdev_unlock_state(state); return source_stream; } static int ipu6_isys_subdev_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { struct v4l2_subdev_route route = { .sink_pad = 0, .sink_stream = 0, .source_pad = 1, .source_stream = 0, .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, }; struct v4l2_subdev_krouting routing = { .num_routes = 1, .routes = &route, }; return subdev_set_routing(sd, state, &routing); } int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, enum v4l2_subdev_format_whence which, struct v4l2_subdev_krouting *routing) { return subdev_set_routing(sd, state, routing); } static const struct v4l2_subdev_internal_ops ipu6_isys_subdev_internal_ops = { .init_state = ipu6_isys_subdev_init_state, }; int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd, const struct v4l2_subdev_ops *ops, unsigned int nr_ctrls, unsigned int num_sink_pads, unsigned int num_source_pads) { unsigned int num_pads = num_sink_pads + num_source_pads; unsigned int i; int ret; v4l2_subdev_init(&asd->sd, ops); asd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_STREAMS; asd->sd.owner = THIS_MODULE; asd->sd.dev = &asd->isys->adev->auxdev.dev; asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; asd->sd.internal_ops = &ipu6_isys_subdev_internal_ops; asd->pad = devm_kcalloc(&asd->isys->adev->auxdev.dev, num_pads, sizeof(*asd->pad), GFP_KERNEL); if (!asd->pad) return -ENOMEM; for (i = 0; i < num_sink_pads; i++) asd->pad[i].flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; for (i = num_sink_pads; i < num_pads; i++) asd->pad[i].flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&asd->sd.entity, num_pads, asd->pad); if (ret) return ret; if (asd->ctrl_init) { ret = v4l2_ctrl_handler_init(&asd->ctrl_handler, nr_ctrls); if (ret) goto out_media_entity_cleanup; asd->ctrl_init(&asd->sd); if (asd->ctrl_handler.error) { ret = asd->ctrl_handler.error; goto out_v4l2_ctrl_handler_free; } asd->sd.ctrl_handler = &asd->ctrl_handler; } asd->source = -1; return 0; out_v4l2_ctrl_handler_free: v4l2_ctrl_handler_free(&asd->ctrl_handler); out_media_entity_cleanup: media_entity_cleanup(&asd->sd.entity); return ret; } void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd) { media_entity_cleanup(&asd->sd.entity); v4l2_ctrl_handler_free(&asd->ctrl_handler); }