From e0f6bc693b3f1c0cc3a2d8d67a764ecb0aac2145 Mon Sep 17 00:00:00 2001 From: Dongcheng Yan Date: Wed, 26 Mar 2025 13:24:47 +0800 Subject: [PATCH 01/16] media: ipu-bridge: add ACPI HID for lt6911uxe bridge Lontium lt6911uxe is a HDMI to CSI-2 bridge, without a fixed link frequency. Signed-off-by: Dongcheng Yan Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu-bridge.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 1cb745855600..83e682e1a4b7 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -66,6 +66,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("INT347E", 1, 319200000), /* Hynix Hi-556 */ IPU_SENSOR_CONFIG("INT3537", 1, 437000000), + /* Lontium lt6911uxe */ + IPU_SENSOR_CONFIG("INTC10C5", 0), /* Omnivision OV01A10 / OV01A1S */ IPU_SENSOR_CONFIG("OVTI01A0", 1, 400000000), IPU_SENSOR_CONFIG("OVTI01AS", 1, 400000000), -- 2.51.0 From 0c1ab3ce0492a579cf8c3b7c0a6c6ab05e47b4bb Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Wed, 26 Mar 2025 13:34:01 +0200 Subject: [PATCH 02/16] media: ti: cal: Use printk's fourcc formatting Use printk's fourcc formatting instead of a custom one. Reviewed-by: Kieran Bingham Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/ti/cal/cal-video.c | 26 ++++++----------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/drivers/media/platform/ti/cal/cal-video.c b/drivers/media/platform/ti/cal/cal-video.c index e29743ae61e2..4eb77f46f030 100644 --- a/drivers/media/platform/ti/cal/cal-video.c +++ b/drivers/media/platform/ti/cal/cal-video.c @@ -25,20 +25,6 @@ #include "cal.h" -/* Print Four-character-code (FOURCC) */ -static char *fourcc_to_str(u32 fmt) -{ - static char code[5]; - - code[0] = (unsigned char)(fmt & 0xff); - code[1] = (unsigned char)((fmt >> 8) & 0xff); - code[2] = (unsigned char)((fmt >> 16) & 0xff); - code[3] = (unsigned char)((fmt >> 24) & 0xff); - code[4] = '\0'; - - return code; -} - /* ------------------------------------------------------------------ * V4L2 Common IOCTLs * ------------------------------------------------------------------ @@ -180,8 +166,8 @@ static void cal_calc_format_size(struct cal_ctx *ctx, f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; - ctx_dbg(3, ctx, "%s: fourcc: %s size: %dx%d bpl:%d img_size:%d\n", - __func__, fourcc_to_str(f->fmt.pix.pixelformat), + ctx_dbg(3, ctx, "%s: fourcc: %p4cc size: %dx%d bpl:%d img_size:%d\n", + __func__, &f->fmt.pix.pixelformat, f->fmt.pix.width, f->fmt.pix.height, f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); } @@ -509,8 +495,8 @@ static void cal_mc_try_fmt(struct cal_ctx *ctx, struct v4l2_format *f, if (info) *info = fmtinfo; - ctx_dbg(3, ctx, "%s: %s %ux%u (bytesperline %u sizeimage %u)\n", - __func__, fourcc_to_str(format->pixelformat), + ctx_dbg(3, ctx, "%s: %p4cc %ux%u (bytesperline %u sizeimage %u)\n", + __func__, &format->pixelformat, format->width, format->height, format->bytesperline, format->sizeimage); } @@ -866,8 +852,8 @@ static int cal_ctx_v4l2_init_formats(struct cal_ctx *ctx) if (mbus_code.code == fmtinfo->code) { ctx->active_fmt[i] = fmtinfo; ctx_dbg(2, ctx, - "matched fourcc: %s: code: %04x idx: %u\n", - fourcc_to_str(fmtinfo->fourcc), + "matched fourcc: %p4cc: code: %04x idx: %u\n", + &fmtinfo->fourcc, fmtinfo->code, i); ctx->num_active_fmt = ++i; } -- 2.51.0 From a5b18fd769b7dc2e77a9e6a390844cbf50626ae8 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Wed, 26 Mar 2025 13:34:02 +0200 Subject: [PATCH 03/16] media: ti: cal: Fix wrong goto on error path If pm_runtime_resume_and_get() fails, we should unprepare the context, but currently we skip that as we goto to a later line. Reviewed-by: Kieran Bingham Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/ti/cal/cal-video.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/ti/cal/cal-video.c b/drivers/media/platform/ti/cal/cal-video.c index 4eb77f46f030..29c38bf8d7a1 100644 --- a/drivers/media/platform/ti/cal/cal-video.c +++ b/drivers/media/platform/ti/cal/cal-video.c @@ -744,7 +744,7 @@ static int cal_start_streaming(struct vb2_queue *vq, unsigned int count) ret = pm_runtime_resume_and_get(ctx->cal->dev); if (ret < 0) - goto error_pipeline; + goto error_unprepare; cal_ctx_set_dma_addr(ctx, addr); cal_ctx_start(ctx); @@ -761,8 +761,8 @@ static int cal_start_streaming(struct vb2_queue *vq, unsigned int count) error_stop: cal_ctx_stop(ctx); pm_runtime_put_sync(ctx->cal->dev); +error_unprepare: cal_ctx_unprepare(ctx); - error_pipeline: video_device_pipeline_stop(&ctx->vdev); error_release_buffers: -- 2.51.0 From c57e372b7b4230d8c4fbaf08704bc4c492a67684 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Wed, 26 Mar 2025 13:34:03 +0200 Subject: [PATCH 04/16] media: ti: cal: Add streams support Add multiplexed streams support. CAL has 8 DMA-engines and can capture 8 separate streams at the same time. The driver filters the streams based on CSI-2 virtual channel number and datatype. CAL may have (depending on the SoC) two CSI-2 RX blocks, which share the 8 DMA-engines, so the number of capturable streams does not change even if there are two CSI-2 RX blocks. Add 8 video device nodes, each representing a single DMA-engine, and set the number of source pads on CSI-2 RX blocks to 8. Each video node can be connected to any of the source pads on either of the CSI-2 RX instances using media links. CSI-2 RX block's subdevice internal routing is used to route the incoming CSI-2 streams to one of the 8 source pads. Only video data streams are supported at the moment. Signed-off-by: Tomi Valkeinen Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/ti/cal/cal-camerarx.c | 266 +++++++++++++++---- drivers/media/platform/ti/cal/cal-video.c | 127 ++++++--- drivers/media/platform/ti/cal/cal.c | 45 +++- drivers/media/platform/ti/cal/cal.h | 3 +- 4 files changed, 337 insertions(+), 104 deletions(-) diff --git a/drivers/media/platform/ti/cal/cal-camerarx.c b/drivers/media/platform/ti/cal/cal-camerarx.c index 9cc875665695..00a71dac0ff4 100644 --- a/drivers/media/platform/ti/cal/cal-camerarx.c +++ b/drivers/media/platform/ti/cal/cal-camerarx.c @@ -49,21 +49,38 @@ static s64 cal_camerarx_get_ext_link_freq(struct cal_camerarx *phy) { struct v4l2_mbus_config_mipi_csi2 *mipi_csi2 = &phy->endpoint.bus.mipi_csi2; u32 num_lanes = mipi_csi2->num_data_lanes; - const struct cal_format_info *fmtinfo; struct v4l2_subdev_state *state; - struct v4l2_mbus_framefmt *fmt; u32 bpp; s64 freq; + /* + * v4l2_get_link_freq() uses V4L2_CID_LINK_FREQ first, and falls back + * to V4L2_CID_PIXEL_RATE if V4L2_CID_LINK_FREQ is not available. + * + * With multistream input there is no single pixel rate, and thus we + * cannot use V4L2_CID_PIXEL_RATE, so we pass 0 as the bpp which + * causes v4l2_get_link_freq() to return an error if it falls back to + * V4L2_CID_PIXEL_RATE. + */ + state = v4l2_subdev_get_locked_active_state(&phy->subdev); - fmt = v4l2_subdev_state_get_format(state, CAL_CAMERARX_PAD_SINK); + if (state->routing.num_routes > 1) { + bpp = 0; + } else { + struct v4l2_subdev_route *route = &state->routing.routes[0]; + const struct cal_format_info *fmtinfo; + struct v4l2_mbus_framefmt *fmt; - fmtinfo = cal_format_by_code(fmt->code); - if (!fmtinfo) - return -EINVAL; + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); - bpp = fmtinfo->bpp; + fmtinfo = cal_format_by_code(fmt->code); + if (!fmtinfo) + return -EINVAL; + + bpp = fmtinfo->bpp; + } freq = v4l2_get_link_freq(&phy->source->entity.pads[phy->source_pad], bpp, 2 * num_lanes); @@ -285,15 +302,32 @@ static void cal_camerarx_ppi_disable(struct cal_camerarx *phy) 0, CAL_CSI2_PPI_CTRL_IF_EN_MASK); } -static int cal_camerarx_start(struct cal_camerarx *phy) +static int cal_camerarx_start(struct cal_camerarx *phy, u32 sink_stream) { + struct media_pad *remote_pad; s64 link_freq; u32 sscounter; u32 val; int ret; + remote_pad = media_pad_remote_pad_first(&phy->pads[CAL_CAMERARX_PAD_SINK]); + + /* + * We need to enable the PHY hardware when enabling the first stream, + * but for the following streams we just propagate the enable_streams + * to the source. + */ + if (phy->enable_count > 0) { + ret = v4l2_subdev_enable_streams(phy->source, remote_pad->index, + BIT(sink_stream)); + if (ret) { + phy_err(phy, "enable streams failed in source: %d\n", ret); + return ret; + } + phy->enable_count++; + return 0; } @@ -395,7 +429,8 @@ static int cal_camerarx_start(struct cal_camerarx *phy) * Start the source to enable the CSI-2 HS clock. We can now wait for * CSI-2 PHY reset to complete. */ - ret = v4l2_subdev_call(phy->source, video, s_stream, 1); + ret = v4l2_subdev_enable_streams(phy->source, remote_pad->index, + BIT(sink_stream)); if (ret) { v4l2_subdev_call(phy->source, core, s_power, 0); cal_camerarx_disable_irqs(phy); @@ -426,12 +461,22 @@ static int cal_camerarx_start(struct cal_camerarx *phy) return 0; } -static void cal_camerarx_stop(struct cal_camerarx *phy) +static void cal_camerarx_stop(struct cal_camerarx *phy, u32 sink_stream) { + struct media_pad *remote_pad; int ret; - if (--phy->enable_count > 0) + remote_pad = media_pad_remote_pad_first(&phy->pads[CAL_CAMERARX_PAD_SINK]); + + if (--phy->enable_count > 0) { + ret = v4l2_subdev_disable_streams(phy->source, + remote_pad->index, + BIT(sink_stream)); + if (ret) + phy_err(phy, "stream off failed in subdev\n"); + return; + } cal_camerarx_ppi_disable(phy); @@ -451,7 +496,9 @@ static void cal_camerarx_stop(struct cal_camerarx *phy) /* Disable the phy */ cal_camerarx_disable(phy); - if (v4l2_subdev_call(phy->source, video, s_stream, 0)) + ret = v4l2_subdev_disable_streams(phy->source, remote_pad->index, + BIT(sink_stream)); + if (ret) phy_err(phy, "stream off failed in subdev\n"); ret = v4l2_subdev_call(phy->source, core, s_power, 0); @@ -600,22 +647,50 @@ static inline struct cal_camerarx *to_cal_camerarx(struct v4l2_subdev *sd) return container_of(sd, struct cal_camerarx, subdev); } -static int cal_camerarx_sd_s_stream(struct v4l2_subdev *sd, int enable) +struct cal_camerarx * +cal_camerarx_get_phy_from_entity(struct media_entity *entity) +{ + struct v4l2_subdev *sd; + + sd = media_entity_to_v4l2_subdev(entity); + if (!sd) + return NULL; + + return to_cal_camerarx(sd); +} + +static int cal_camerarx_sd_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) { struct cal_camerarx *phy = to_cal_camerarx(sd); - struct v4l2_subdev_state *state; - int ret = 0; + u32 sink_stream; + int ret; - state = v4l2_subdev_lock_and_get_active_state(sd); + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0, + NULL, &sink_stream); + if (ret) + return ret; - if (enable) - ret = cal_camerarx_start(phy); - else - cal_camerarx_stop(phy); + return cal_camerarx_start(phy, sink_stream); +} - v4l2_subdev_unlock_state(state); +static int cal_camerarx_sd_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct cal_camerarx *phy = to_cal_camerarx(sd); + u32 sink_stream; + int ret; - return ret; + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0, + NULL, &sink_stream); + if (ret) + return ret; + + cal_camerarx_stop(phy, sink_stream); + + return 0; } static int cal_camerarx_sd_enum_mbus_code(struct v4l2_subdev *sd, @@ -629,8 +704,12 @@ static int cal_camerarx_sd_enum_mbus_code(struct v4l2_subdev *sd, if (code->index > 0) return -EINVAL; - fmt = v4l2_subdev_state_get_format(state, - CAL_CAMERARX_PAD_SINK); + fmt = v4l2_subdev_state_get_opposite_stream_format(state, + code->pad, + code->stream); + if (!fmt) + return -EINVAL; + code->code = fmt->code; } else { if (code->index >= cal_num_formats) @@ -655,8 +734,12 @@ static int cal_camerarx_sd_enum_frame_size(struct v4l2_subdev *sd, if (cal_rx_pad_is_source(fse->pad)) { struct v4l2_mbus_framefmt *fmt; - fmt = v4l2_subdev_state_get_format(state, - CAL_CAMERARX_PAD_SINK); + fmt = v4l2_subdev_state_get_opposite_stream_format(state, + fse->pad, + fse->stream); + if (!fmt) + return -EINVAL; + if (fse->code != fmt->code) return -EINVAL; @@ -712,36 +795,77 @@ static int cal_camerarx_sd_set_fmt(struct v4l2_subdev *sd, /* Store the format and propagate it to the source pad. */ - fmt = v4l2_subdev_state_get_format(state, CAL_CAMERARX_PAD_SINK); + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + *fmt = format->format; - fmt = v4l2_subdev_state_get_format(state, - CAL_CAMERARX_PAD_FIRST_SOURCE); + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + *fmt = format->format; return 0; } +static int cal_camerarx_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, + }; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 | + V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int cal_camerarx_sd_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + return cal_camerarx_set_routing(sd, state, routing); +} + static int cal_camerarx_sd_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { - struct v4l2_subdev_format format = { - .which = state ? V4L2_SUBDEV_FORMAT_TRY - : V4L2_SUBDEV_FORMAT_ACTIVE, - .pad = CAL_CAMERARX_PAD_SINK, - .format = { - .width = 640, - .height = 480, - .code = MEDIA_BUS_FMT_UYVY8_1X16, - .field = V4L2_FIELD_NONE, - .colorspace = V4L2_COLORSPACE_SRGB, - .ycbcr_enc = V4L2_YCBCR_ENC_601, - .quantization = V4L2_QUANTIZATION_LIM_RANGE, - .xfer_func = V4L2_XFER_FUNC_SRGB, - }, + struct v4l2_subdev_route routes[] = { { + .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 = routes, }; - return cal_camerarx_sd_set_fmt(sd, state, &format); + /* Initialize routing to single route to the fist source pad */ + return cal_camerarx_set_routing(sd, state, &routing); } static int cal_camerarx_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, @@ -750,48 +874,71 @@ static int cal_camerarx_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, struct cal_camerarx *phy = to_cal_camerarx(sd); struct v4l2_mbus_frame_desc remote_desc; const struct media_pad *remote_pad; + struct v4l2_subdev_state *state; + u32 sink_stream; + unsigned int i; int ret; + state = v4l2_subdev_lock_and_get_active_state(sd); + + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, + pad, 0, + NULL, &sink_stream); + if (ret) + goto out_unlock; + remote_pad = media_pad_remote_pad_first(&phy->pads[CAL_CAMERARX_PAD_SINK]); - if (!remote_pad) - return -EPIPE; + if (!remote_pad) { + ret = -EPIPE; + goto out_unlock; + } ret = v4l2_subdev_call(phy->source, pad, get_frame_desc, remote_pad->index, &remote_desc); if (ret) - return ret; + goto out_unlock; if (remote_desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { cal_err(phy->cal, "Frame descriptor does not describe CSI-2 link"); - return -EINVAL; + ret = -EINVAL; + goto out_unlock; } - if (remote_desc.num_entries > 1) - cal_err(phy->cal, - "Multiple streams not supported in remote frame descriptor, using the first one\n"); + for (i = 0; i < remote_desc.num_entries; i++) { + if (remote_desc.entry[i].stream == sink_stream) + break; + } + + if (i == remote_desc.num_entries) { + cal_err(phy->cal, "Stream %u not found in remote frame desc\n", + sink_stream); + ret = -EINVAL; + goto out_unlock; + } fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; fd->num_entries = 1; - fd->entry[0] = remote_desc.entry[0]; + fd->entry[0] = remote_desc.entry[i]; - return 0; -} +out_unlock: + v4l2_subdev_unlock_state(state); -static const struct v4l2_subdev_video_ops cal_camerarx_video_ops = { - .s_stream = cal_camerarx_sd_s_stream, -}; + return ret; +} static const struct v4l2_subdev_pad_ops cal_camerarx_pad_ops = { + .enable_streams = cal_camerarx_sd_enable_streams, + .disable_streams = cal_camerarx_sd_disable_streams, .enum_mbus_code = cal_camerarx_sd_enum_mbus_code, .enum_frame_size = cal_camerarx_sd_enum_frame_size, .get_fmt = v4l2_subdev_get_fmt, .set_fmt = cal_camerarx_sd_set_fmt, + .set_routing = cal_camerarx_sd_set_routing, .get_frame_desc = cal_camerarx_get_frame_desc, }; static const struct v4l2_subdev_ops cal_camerarx_subdev_ops = { - .video = &cal_camerarx_video_ops, .pad = &cal_camerarx_pad_ops, }; @@ -801,6 +948,7 @@ static const struct v4l2_subdev_internal_ops cal_camerarx_internal_ops = { static const struct media_entity_operations cal_camerarx_media_ops = { .link_validate = v4l2_subdev_link_validate, + .has_pad_interdep = v4l2_subdev_has_pad_interdep, }; /* ------------------------------------------------------------------ @@ -852,7 +1000,7 @@ struct cal_camerarx *cal_camerarx_create(struct cal_dev *cal, v4l2_subdev_init(sd, &cal_camerarx_subdev_ops); sd->internal_ops = &cal_camerarx_internal_ops; sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; - sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; snprintf(sd->name, sizeof(sd->name), "CAMERARX%u", instance); sd->dev = cal->dev; diff --git a/drivers/media/platform/ti/cal/cal-video.c b/drivers/media/platform/ti/cal/cal-video.c index 29c38bf8d7a1..d40e24ab1127 100644 --- a/drivers/media/platform/ti/cal/cal-video.c +++ b/drivers/media/platform/ti/cal/cal-video.c @@ -108,9 +108,10 @@ static int __subdev_get_format(struct cal_ctx *ctx, .pad = 0, }; struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format; + struct v4l2_subdev *sd = ctx->phy->source; int ret; - ret = v4l2_subdev_call(ctx->phy->source, pad, get_fmt, NULL, &sd_fmt); + ret = v4l2_subdev_call_state_active(sd, pad, get_fmt, &sd_fmt); if (ret) return ret; @@ -130,11 +131,12 @@ static int __subdev_set_format(struct cal_ctx *ctx, .pad = 0, }; struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format; + struct v4l2_subdev *sd = ctx->phy->source; int ret; *mbus_fmt = *fmt; - ret = v4l2_subdev_call(ctx->phy->source, pad, set_fmt, NULL, &sd_fmt); + ret = v4l2_subdev_call_state_active(sd, pad, set_fmt, &sd_fmt); if (ret) return ret; @@ -176,6 +178,7 @@ static int cal_legacy_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct cal_ctx *ctx = video_drvdata(file); + struct v4l2_subdev *sd = ctx->phy->source; const struct cal_format_info *fmtinfo; struct v4l2_subdev_frame_size_enum fse = { .which = V4L2_SUBDEV_FORMAT_ACTIVE, @@ -201,8 +204,8 @@ static int cal_legacy_try_fmt_vid_cap(struct file *file, void *priv, for (fse.index = 0; ; fse.index++) { int ret; - ret = v4l2_subdev_call(ctx->phy->source, pad, enum_frame_size, - NULL, &fse); + ret = v4l2_subdev_call_state_active(sd, pad, enum_frame_size, + &fse); if (ret) break; @@ -238,6 +241,7 @@ static int cal_legacy_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct cal_ctx *ctx = video_drvdata(file); + struct v4l2_subdev *sd = &ctx->phy->subdev; struct vb2_queue *q = &ctx->vb_vidq; struct v4l2_subdev_format sd_fmt = { .which = V4L2_SUBDEV_FORMAT_ACTIVE, @@ -277,7 +281,7 @@ static int cal_legacy_s_fmt_vid_cap(struct file *file, void *priv, ctx->v_fmt.fmt.pix.field = sd_fmt.format.field; cal_calc_format_size(ctx, fmtinfo, &ctx->v_fmt); - v4l2_subdev_call(&ctx->phy->subdev, pad, set_fmt, NULL, &sd_fmt); + v4l2_subdev_call_state_active(sd, pad, set_fmt, &sd_fmt); ctx->fmtinfo = fmtinfo; *f = ctx->v_fmt; @@ -289,6 +293,7 @@ static int cal_legacy_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *fsize) { struct cal_ctx *ctx = video_drvdata(file); + struct v4l2_subdev *sd = ctx->phy->source; const struct cal_format_info *fmtinfo; struct v4l2_subdev_frame_size_enum fse = { .index = fsize->index, @@ -307,8 +312,7 @@ static int cal_legacy_enum_framesizes(struct file *file, void *fh, fse.code = fmtinfo->code; - ret = v4l2_subdev_call(ctx->phy->source, pad, enum_frame_size, NULL, - &fse); + ret = v4l2_subdev_call_state_active(sd, pad, enum_frame_size, &fse); if (ret) return ret; @@ -350,6 +354,7 @@ static int cal_legacy_enum_frameintervals(struct file *file, void *priv, struct v4l2_frmivalenum *fival) { struct cal_ctx *ctx = video_drvdata(file); + struct v4l2_subdev *sd = ctx->phy->source; const struct cal_format_info *fmtinfo; struct v4l2_subdev_frame_interval_enum fie = { .index = fival->index, @@ -364,8 +369,8 @@ static int cal_legacy_enum_frameintervals(struct file *file, void *priv, return -EINVAL; fie.code = fmtinfo->code; - ret = v4l2_subdev_call(ctx->phy->source, pad, enum_frame_interval, - NULL, &fie); + + ret = v4l2_subdev_call_state_active(sd, pad, enum_frame_interval, &fie); if (ret) return ret; fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; @@ -432,6 +437,9 @@ static int cal_mc_enum_fmt_vid_cap(struct file *file, void *priv, idx = 0; for (i = 0; i < cal_num_formats; ++i) { + if (cal_formats[i].meta) + continue; + if (f->mbus_code && cal_formats[i].code != f->mbus_code) continue; @@ -459,7 +467,7 @@ static void cal_mc_try_fmt(struct cal_ctx *ctx, struct v4l2_format *f, * supported. */ fmtinfo = cal_format_by_fourcc(f->fmt.pix.pixelformat); - if (!fmtinfo) + if (!fmtinfo || fmtinfo->meta) fmtinfo = &cal_formats[0]; /* @@ -675,16 +683,16 @@ static int cal_video_check_format(struct cal_ctx *ctx) { const struct v4l2_mbus_framefmt *format; struct v4l2_subdev_state *state; - struct media_pad *remote_pad; + struct media_pad *phy_source_pad; int ret = 0; - remote_pad = media_pad_remote_pad_first(&ctx->pad); - if (!remote_pad) + phy_source_pad = media_pad_remote_pad_first(&ctx->pad); + if (!phy_source_pad) return -ENODEV; state = v4l2_subdev_lock_and_get_active_state(&ctx->phy->subdev); - format = v4l2_subdev_state_get_format(state, remote_pad->index); + format = v4l2_subdev_state_get_format(state, phy_source_pad->index, 0); if (!format) { ret = -EINVAL; goto out; @@ -707,16 +715,28 @@ out: static int cal_start_streaming(struct vb2_queue *vq, unsigned int count) { struct cal_ctx *ctx = vb2_get_drv_priv(vq); + struct media_pad *phy_source_pad; struct cal_buffer *buf; dma_addr_t addr; int ret; + phy_source_pad = media_pad_remote_pad_first(&ctx->pad); + if (!phy_source_pad) { + ctx_err(ctx, "Context not connected\n"); + ret = -ENODEV; + goto error_release_buffers; + } + ret = video_device_pipeline_alloc_start(&ctx->vdev); if (ret < 0) { ctx_err(ctx, "Failed to start media pipeline: %d\n", ret); goto error_release_buffers; } + /* Find the PHY connected to this video device */ + if (cal_mc_api) + ctx->phy = cal_camerarx_get_phy_from_entity(phy_source_pad->entity); + /* * Verify that the currently configured format matches the output of * the connected CAMERARX. @@ -749,7 +769,8 @@ static int cal_start_streaming(struct vb2_queue *vq, unsigned int count) cal_ctx_set_dma_addr(ctx, addr); cal_ctx_start(ctx); - ret = v4l2_subdev_call(&ctx->phy->subdev, video, s_stream, 1); + ret = v4l2_subdev_enable_streams(&ctx->phy->subdev, + phy_source_pad->index, BIT(0)); if (ret) goto error_stop; @@ -774,10 +795,14 @@ error_release_buffers: static void cal_stop_streaming(struct vb2_queue *vq) { struct cal_ctx *ctx = vb2_get_drv_priv(vq); + struct media_pad *phy_source_pad; cal_ctx_stop(ctx); - v4l2_subdev_call(&ctx->phy->subdev, video, s_stream, 0); + phy_source_pad = media_pad_remote_pad_first(&ctx->pad); + + v4l2_subdev_disable_streams(&ctx->phy->subdev, phy_source_pad->index, + BIT(0)); pm_runtime_put_sync(ctx->cal->dev); @@ -786,6 +811,9 @@ static void cal_stop_streaming(struct vb2_queue *vq) cal_release_buffers(ctx, VB2_BUF_STATE_ERROR); video_device_pipeline_stop(&ctx->vdev); + + if (cal_mc_api) + ctx->phy = NULL; } static const struct vb2_ops cal_video_qops = { @@ -812,6 +840,7 @@ static const struct v4l2_file_operations cal_fops = { static int cal_ctx_v4l2_init_formats(struct cal_ctx *ctx) { + struct v4l2_subdev *sd = ctx->phy->source; struct v4l2_mbus_framefmt mbus_fmt; const struct cal_format_info *fmtinfo; unsigned int i, j, k; @@ -831,20 +860,20 @@ static int cal_ctx_v4l2_init_formats(struct cal_ctx *ctx) .which = V4L2_SUBDEV_FORMAT_ACTIVE, }; - ret = v4l2_subdev_call(ctx->phy->source, pad, enum_mbus_code, - NULL, &mbus_code); + ret = v4l2_subdev_call_state_active(sd, pad, enum_mbus_code, + &mbus_code); if (ret == -EINVAL) break; if (ret) { ctx_err(ctx, "Error enumerating mbus codes in subdev %s: %d\n", - ctx->phy->source->name, ret); + sd->name, ret); return ret; } ctx_dbg(2, ctx, "subdev %s: code: %04x idx: %u\n", - ctx->phy->source->name, mbus_code.code, j); + sd->name, mbus_code.code, j); for (k = 0; k < cal_num_formats; k++) { fmtinfo = &cal_formats[k]; @@ -862,7 +891,7 @@ static int cal_ctx_v4l2_init_formats(struct cal_ctx *ctx) if (i == 0) { ctx_err(ctx, "No suitable format reported by subdev %s\n", - ctx->phy->source->name); + sd->name); return -EINVAL; } @@ -948,16 +977,52 @@ int cal_ctx_v4l2_register(struct cal_ctx *ctx) return ret; } - ret = media_create_pad_link(&ctx->phy->subdev.entity, - CAL_CAMERARX_PAD_FIRST_SOURCE, - &vfd->entity, 0, - MEDIA_LNK_FL_IMMUTABLE | - MEDIA_LNK_FL_ENABLED); - if (ret) { - ctx_err(ctx, "Failed to create media link for context %u\n", - ctx->dma_ctx); - video_unregister_device(vfd); - return ret; + if (cal_mc_api) { + u16 phy_idx; + u16 pad_idx; + + /* Create links from all video nodes to all PHYs */ + + for (phy_idx = 0; phy_idx < ctx->cal->data->num_csi2_phy; + ++phy_idx) { + struct media_entity *phy_entity = + &ctx->cal->phy[phy_idx]->subdev.entity; + + for (pad_idx = 1; pad_idx < CAL_CAMERARX_NUM_PADS; + ++pad_idx) { + /* + * Enable only links from video0 to PHY0 pad 1, + * and video1 to PHY1 pad 1. + */ + bool enable = (ctx->dma_ctx == 0 && + phy_idx == 0 && pad_idx == 1) || + (ctx->dma_ctx == 1 && + phy_idx == 1 && pad_idx == 1); + + ret = media_create_pad_link(phy_entity, pad_idx, + &vfd->entity, 0, + enable ? MEDIA_LNK_FL_ENABLED : 0); + if (ret) { + ctx_err(ctx, + "Failed to create media link for context %u\n", + ctx->dma_ctx); + video_unregister_device(vfd); + return ret; + } + } + } + } else { + ret = media_create_pad_link(&ctx->phy->subdev.entity, + CAL_CAMERARX_PAD_FIRST_SOURCE, + &vfd->entity, 0, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret) { + ctx_err(ctx, + "Failed to create media link for context %u\n", + ctx->dma_ctx); + video_unregister_device(vfd); + return ret; + } } ctx_info(ctx, "V4L2 device registered as %s\n", diff --git a/drivers/media/platform/ti/cal/cal.c b/drivers/media/platform/ti/cal/cal.c index 6cb3e5f49686..b644ed890412 100644 --- a/drivers/media/platform/ti/cal/cal.c +++ b/drivers/media/platform/ti/cal/cal.c @@ -481,8 +481,9 @@ int cal_ctx_prepare(struct cal_ctx *ctx) ctx->vc = 0; ctx->datatype = CAL_CSI2_CTX_DT_ANY; } else if (!ret) { - ctx_dbg(2, ctx, "Framedesc: len %u, vc %u, dt %#x\n", - entry.length, entry.bus.csi2.vc, entry.bus.csi2.dt); + ctx_dbg(2, ctx, "Framedesc: stream %u, len %u, vc %u, dt %#x\n", + entry.stream, entry.length, entry.bus.csi2.vc, + entry.bus.csi2.dt); ctx->vc = entry.bus.csi2.vc; ctx->datatype = entry.bus.csi2.dt; @@ -490,7 +491,7 @@ int cal_ctx_prepare(struct cal_ctx *ctx) return ret; } - ctx->use_pix_proc = !ctx->fmtinfo->meta; + ctx->use_pix_proc = ctx->vb_vidq.type == V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ctx->use_pix_proc) { ret = cal_reserve_pix_proc(ctx->cal); @@ -1016,7 +1017,6 @@ static struct cal_ctx *cal_ctx_create(struct cal_dev *cal, int inst) return NULL; ctx->cal = cal; - ctx->phy = cal->phy[inst]; ctx->dma_ctx = inst; ctx->csi2_ctx = inst; ctx->cport = inst; @@ -1228,18 +1228,37 @@ static int cal_probe(struct platform_device *pdev) } /* Create contexts. */ - for (i = 0; i < cal->data->num_csi2_phy; ++i) { - if (!cal->phy[i]->source_node) - continue; + if (!cal_mc_api) { + for (i = 0; i < cal->data->num_csi2_phy; ++i) { + struct cal_ctx *ctx; + + if (!cal->phy[i]->source_node) + continue; + + ctx = cal_ctx_create(cal, i); + if (!ctx) { + cal_err(cal, "Failed to create context %u\n", cal->num_contexts); + ret = -ENODEV; + goto error_context; + } + + ctx->phy = cal->phy[i]; - cal->ctx[cal->num_contexts] = cal_ctx_create(cal, i); - if (!cal->ctx[cal->num_contexts]) { - cal_err(cal, "Failed to create context %u\n", cal->num_contexts); - ret = -ENODEV; - goto error_context; + cal->ctx[cal->num_contexts++] = ctx; } + } else { + for (i = 0; i < ARRAY_SIZE(cal->ctx); ++i) { + struct cal_ctx *ctx; + + ctx = cal_ctx_create(cal, i); + if (!ctx) { + cal_err(cal, "Failed to create context %u\n", i); + ret = -ENODEV; + goto error_context; + } - cal->num_contexts++; + cal->ctx[cal->num_contexts++] = ctx; + } } /* Register the media device. */ diff --git a/drivers/media/platform/ti/cal/cal.h b/drivers/media/platform/ti/cal/cal.h index 72a246a64d9e..33b1885aafe1 100644 --- a/drivers/media/platform/ti/cal/cal.h +++ b/drivers/media/platform/ti/cal/cal.h @@ -45,7 +45,7 @@ #define CAL_CAMERARX_PAD_SINK 0 #define CAL_CAMERARX_PAD_FIRST_SOURCE 1 -#define CAL_CAMERARX_NUM_SOURCE_PADS 1 +#define CAL_CAMERARX_NUM_SOURCE_PADS 8 #define CAL_CAMERARX_NUM_PADS (1 + CAL_CAMERARX_NUM_SOURCE_PADS) static inline bool cal_rx_pad_is_sink(u32 pad) @@ -320,6 +320,7 @@ const struct cal_format_info *cal_format_by_code(u32 code); void cal_quickdump_regs(struct cal_dev *cal); +struct cal_camerarx *cal_camerarx_get_phy_from_entity(struct media_entity *entity); void cal_camerarx_disable(struct cal_camerarx *phy); void cal_camerarx_i913_errata(struct cal_camerarx *phy); struct cal_camerarx *cal_camerarx_create(struct cal_dev *cal, -- 2.51.0 From f2f0cd892515eda18857f47718eb7be93f9ddaca Mon Sep 17 00:00:00 2001 From: =?utf8?q?Niklas=20S=C3=B6derlund?= Date: Mon, 10 Feb 2025 18:56:11 +0100 Subject: [PATCH 05/16] media: rcar-vin: Remove emulated SEQ_{TB,BT} MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit When the driver was converted from soc_camera software support for V4L2_FIELD_SEQ_TB and V4L2_FIELD_SEQ_BT were added. This was done by capturing twice to the same VB2 buffer, but at different offsets. This turned out to be a bad idea and it never really worked properly in all situations. As the hardware can't support this mode natively remove trying to emulate it in software. It's still possible to capture TOP or BOTTOM fields separately or both ALTERNATING. If user-space wants the same fields in the same buffer the same hack to capture twice to the same buffer can be done. Removing this error prone emulated support pave ways in future work to simplify the internal buffer handling and making it less fragile, while enabling adding support for other features the hardware actually supports. Signed-off-by: Niklas Söderlund Reviewed-by: Tomi Valkeinen Tested-by: Tomi Valkeinen Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-dma.c | 58 +++---------------- .../platform/renesas/rcar-vin/rcar-v4l2.c | 7 --- .../platform/renesas/rcar-vin/rcar-vin.h | 18 ------ 3 files changed, 7 insertions(+), 76 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index edb06730bc7c..fc64f013ec05 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -642,8 +642,6 @@ void rvin_scaler_gen3(struct rvin_dev *vin) case V4L2_FIELD_INTERLACED_TB: case V4L2_FIELD_INTERLACED_BT: case V4L2_FIELD_INTERLACED: - case V4L2_FIELD_SEQ_TB: - case V4L2_FIELD_SEQ_BT: clip_size |= vin->compose.height / 2; break; default: @@ -711,8 +709,6 @@ static int rvin_setup(struct rvin_dev *vin) case V4L2_FIELD_INTERLACED_BT: vnmc = VNMC_IM_FULL | VNMC_FOC; break; - case V4L2_FIELD_SEQ_TB: - case V4L2_FIELD_SEQ_BT: case V4L2_FIELD_NONE: case V4L2_FIELD_ALTERNATE: vnmc = VNMC_IM_ODD_EVEN; @@ -1005,33 +1001,14 @@ static void rvin_fill_hw_slot(struct rvin_dev *vin, int slot) struct rvin_buffer *buf; struct vb2_v4l2_buffer *vbuf; dma_addr_t phys_addr; - int prev; /* A already populated slot shall never be overwritten. */ if (WARN_ON(vin->buf_hw[slot].buffer)) return; - prev = (slot == 0 ? HW_BUFFER_NUM : slot) - 1; - - if (vin->buf_hw[prev].type == HALF_TOP) { - vbuf = vin->buf_hw[prev].buffer; - vin->buf_hw[slot].buffer = vbuf; - vin->buf_hw[slot].type = HALF_BOTTOM; - switch (vin->format.pixelformat) { - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV16: - phys_addr = vin->buf_hw[prev].phys + - vin->format.sizeimage / 4; - break; - default: - phys_addr = vin->buf_hw[prev].phys + - vin->format.sizeimage / 2; - break; - } - } else if ((vin->state != STOPPED && vin->state != RUNNING) || - list_empty(&vin->buf_list)) { + if ((vin->state != STOPPED && vin->state != RUNNING) || + list_empty(&vin->buf_list)) { vin->buf_hw[slot].buffer = NULL; - vin->buf_hw[slot].type = FULL; phys_addr = vin->scratch_phys; } else { /* Keep track of buffer we give to HW */ @@ -1040,16 +1017,12 @@ static void rvin_fill_hw_slot(struct rvin_dev *vin, int slot) list_del_init(to_buf_list(vbuf)); vin->buf_hw[slot].buffer = vbuf; - vin->buf_hw[slot].type = - V4L2_FIELD_IS_SEQUENTIAL(vin->format.field) ? - HALF_TOP : FULL; - /* Setup DMA */ phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); } - vin_dbg(vin, "Filling HW slot: %d type: %d buffer: %p\n", - slot, vin->buf_hw[slot].type, vin->buf_hw[slot].buffer); + vin_dbg(vin, "Filling HW slot: %d buffer: %p\n", + slot, vin->buf_hw[slot].buffer); vin->buf_hw[slot].phys = phys_addr; rvin_set_slot_addr(vin, slot, phys_addr); @@ -1057,15 +1030,12 @@ static void rvin_fill_hw_slot(struct rvin_dev *vin, int slot) static int rvin_capture_start(struct rvin_dev *vin) { - int slot, ret; + int ret; - for (slot = 0; slot < HW_BUFFER_NUM; slot++) { + for (unsigned int slot = 0; slot < HW_BUFFER_NUM; slot++) { vin->buf_hw[slot].buffer = NULL; - vin->buf_hw[slot].type = FULL; - } - - for (slot = 0; slot < HW_BUFFER_NUM; slot++) rvin_fill_hw_slot(vin, slot); + } ret = rvin_setup(vin); if (ret) @@ -1146,16 +1116,6 @@ static irqreturn_t rvin_irq(int irq, void *data) /* Capture frame */ if (vin->buf_hw[slot].buffer) { - /* - * Nothing to do but refill the hardware slot if - * capture only filled first half of vb2 buffer. - */ - if (vin->buf_hw[slot].type == HALF_TOP) { - vin->buf_hw[slot].buffer = NULL; - rvin_fill_hw_slot(vin, slot); - goto done; - } - vin->buf_hw[slot].buffer->field = rvin_get_active_field(vin, vnms); vin->buf_hw[slot].buffer->sequence = vin->sequence; @@ -1306,8 +1266,6 @@ static int rvin_mc_validate_format(struct rvin_dev *vin, struct v4l2_subdev *sd, case V4L2_FIELD_INTERLACED_TB: case V4L2_FIELD_INTERLACED_BT: case V4L2_FIELD_INTERLACED: - case V4L2_FIELD_SEQ_TB: - case V4L2_FIELD_SEQ_BT: /* Supported natively */ break; case V4L2_FIELD_ALTERNATE: @@ -1320,8 +1278,6 @@ static int rvin_mc_validate_format(struct rvin_dev *vin, struct v4l2_subdev *sd, case V4L2_FIELD_INTERLACED_TB: case V4L2_FIELD_INTERLACED_BT: case V4L2_FIELD_INTERLACED: - case V4L2_FIELD_SEQ_TB: - case V4L2_FIELD_SEQ_BT: /* Use VIN hardware to combine the two fields */ fmt.format.height *= 2; break; diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c index 756fdfdbce61..a5763f1c5784 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c @@ -161,9 +161,6 @@ static u32 rvin_format_bytesperline(struct rvin_dev *vin, break; } - if (V4L2_FIELD_IS_SEQUENTIAL(pix->field)) - align = 0x80; - return ALIGN(pix->width, align) * fmt->bpp; } @@ -194,8 +191,6 @@ static void rvin_format_align(struct rvin_dev *vin, struct v4l2_pix_format *pix) case V4L2_FIELD_INTERLACED_BT: case V4L2_FIELD_INTERLACED: case V4L2_FIELD_ALTERNATE: - case V4L2_FIELD_SEQ_TB: - case V4L2_FIELD_SEQ_BT: break; default: pix->field = RVIN_DEFAULT_FIELD; @@ -504,8 +499,6 @@ static int rvin_remote_rectangle(struct rvin_dev *vin, struct v4l2_rect *rect) case V4L2_FIELD_INTERLACED_TB: case V4L2_FIELD_INTERLACED_BT: case V4L2_FIELD_INTERLACED: - case V4L2_FIELD_SEQ_TB: - case V4L2_FIELD_SEQ_BT: rect->height *= 2; break; } diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h index f87d4bc9e53e..d5763462809a 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -77,23 +77,6 @@ enum rvin_dma_state { SUSPENDED, }; -/** - * enum rvin_buffer_type - * - * Describes how a buffer is given to the hardware. To be able - * to capture SEQ_TB/BT it's needed to capture to the same vb2 - * buffer twice so the type of buffer needs to be kept. - * - * @FULL: One capture fills the whole vb2 buffer - * @HALF_TOP: One capture fills the top half of the vb2 buffer - * @HALF_BOTTOM: One capture fills the bottom half of the vb2 buffer - */ -enum rvin_buffer_type { - FULL, - HALF_TOP, - HALF_BOTTOM, -}; - /** * struct rvin_video_format - Data format stored in memory * @fourcc: Pixelformat @@ -237,7 +220,6 @@ struct rvin_dev { spinlock_t qlock; struct { struct vb2_v4l2_buffer *buffer; - enum rvin_buffer_type type; dma_addr_t phys; } buf_hw[HW_BUFFER_NUM]; struct list_head buf_list; -- 2.51.0 From 1dadd89b58108d3df701d6d9e19cd34818f97f7f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Niklas=20S=C3=B6derlund?= Date: Mon, 10 Feb 2025 18:56:12 +0100 Subject: [PATCH 06/16] media: rcar-vin: Remove superfluous suspended state MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The VIN state of suspended is superfluous. The logic was that when the device were suspended and in a RUNNING state the state was set to SUSPENDED. And when resuming it checked if the state is SUSPENDED and if so started the device and changed it to RUNNING. This can be avoided by simply checking if the device is in a RUNNING state at both suspend and resume callbacks. Remove the unneeded complexity. Signed-off-by: Niklas Söderlund Reviewed-by: Tomi Valkeinen Tested-by: Tomi Valkeinen Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-vin/rcar-core.c | 4 +--- drivers/media/platform/renesas/rcar-vin/rcar-vin.h | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index ddfb18e6e7a4..b8e35ef4d9d8 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -1085,8 +1085,6 @@ static int __maybe_unused rvin_suspend(struct device *dev) rvin_stop_streaming(vin); - vin->state = SUSPENDED; - return 0; } @@ -1094,7 +1092,7 @@ static int __maybe_unused rvin_resume(struct device *dev) { struct rvin_dev *vin = dev_get_drvdata(dev); - if (vin->state != SUSPENDED) + if (vin->state != RUNNING) return 0; /* diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h index d5763462809a..4cb25d8bbf32 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -67,14 +67,12 @@ enum rvin_isp_id { * @STARTING: Capture starting up * @RUNNING: Operation in progress have buffers * @STOPPING: Stopping operation - * @SUSPENDED: Capture is suspended */ enum rvin_dma_state { STOPPED = 0, STARTING, RUNNING, STOPPING, - SUSPENDED, }; /** -- 2.51.0 From 25482a986e44fddd3af77458071fbbd222795917 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Niklas=20S=C3=B6derlund?= Date: Mon, 10 Feb 2025 18:56:13 +0100 Subject: [PATCH 07/16] media: rcar-vin: Remove superfluous starting state MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The STARTING state is superfluous and can be replaced with a check of the sequence counter. The design idea is that the first buffer returned from the driver have to come from the first hardware buffer slot. Failing this the first 3 buffers queued to the device can be returned out-of-order. But it's much clearer to check the sequence counter to only return the first buffer if it comes from hardware slot 0 then it is to carry around an extra state just for this. Remove the unneeded state and replace it with a simpler check. Signed-off-by: Niklas Söderlund Reviewed-by: Tomi Valkeinen Tested-by: Tomi Valkeinen Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-vin/rcar-dma.c | 5 ++--- drivers/media/platform/renesas/rcar-vin/rcar-vin.h | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index fc64f013ec05..125af7ae9fbf 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -1048,7 +1048,7 @@ static int rvin_capture_start(struct rvin_dev *vin) /* Continuous Frame Capture Mode */ rvin_write(vin, VNFC_C_FRAME, VNFC_REG); - vin->state = STARTING; + vin->state = RUNNING; return 0; } @@ -1104,14 +1104,13 @@ static irqreturn_t rvin_irq(int irq, void *data) * To hand buffers back in a known order to userspace start * to capture first from slot 0. */ - if (vin->state == STARTING) { + if (!vin->sequence) { if (slot != 0) { vin_dbg(vin, "Starting sync slot: %d\n", slot); goto done; } vin_dbg(vin, "Capture start synced!\n"); - vin->state = RUNNING; } /* Capture frame */ diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h index 4cb25d8bbf32..f13ef379d095 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -64,13 +64,11 @@ enum rvin_isp_id { /** * enum rvin_dma_state - DMA states * @STOPPED: No operation in progress - * @STARTING: Capture starting up * @RUNNING: Operation in progress have buffers * @STOPPING: Stopping operation */ enum rvin_dma_state { STOPPED = 0, - STARTING, RUNNING, STOPPING, }; -- 2.51.0 From c1eefe88c3954390473ed092f0d4718a810bd61c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Niklas=20S=C3=B6derlund?= Date: Mon, 10 Feb 2025 18:56:14 +0100 Subject: [PATCH 08/16] media: rcar-vin: Simplify the shutdown process MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit When shutting down capture extra care was needed to try and complete a buffer that was involved in the emulated support for SEQ_{TB,BT}. This was needed as a buffer might be queued once to the driver, but two times to the hardware using different offsets. As support for SEQ_{TB,BT} is now removed this shutdown process can be greatly simplified. And in addition the state keeping of the VIN device can be reduced to a single boolean value instead of keeping track of this SEQ_{TB,BT} stopping dance. Signed-off-by: Niklas Söderlund Reviewed-by: Tomi Valkeinen Tested-by: Tomi Valkeinen Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-core.c | 4 +- .../platform/renesas/rcar-vin/rcar-dma.c | 75 ++++++------------- .../platform/renesas/rcar-vin/rcar-vin.h | 18 +---- 3 files changed, 26 insertions(+), 71 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index b8e35ef4d9d8..cfbc9ec27706 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -1080,7 +1080,7 @@ static int __maybe_unused rvin_suspend(struct device *dev) { struct rvin_dev *vin = dev_get_drvdata(dev); - if (vin->state != RUNNING) + if (!vin->running) return 0; rvin_stop_streaming(vin); @@ -1092,7 +1092,7 @@ static int __maybe_unused rvin_resume(struct device *dev) { struct rvin_dev *vin = dev_get_drvdata(dev); - if (vin->state != RUNNING) + if (!vin->running) return 0; /* diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index 125af7ae9fbf..f7b80e61a987 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -1006,8 +1006,7 @@ static void rvin_fill_hw_slot(struct rvin_dev *vin, int slot) if (WARN_ON(vin->buf_hw[slot].buffer)) return; - if ((vin->state != STOPPED && vin->state != RUNNING) || - list_empty(&vin->buf_list)) { + if (list_empty(&vin->buf_list)) { vin->buf_hw[slot].buffer = NULL; phys_addr = vin->scratch_phys; } else { @@ -1048,8 +1047,6 @@ static int rvin_capture_start(struct rvin_dev *vin) /* Continuous Frame Capture Mode */ rvin_write(vin, VNFC_C_FRAME, VNFC_REG); - vin->state = RUNNING; - return 0; } @@ -1090,9 +1087,9 @@ static irqreturn_t rvin_irq(int irq, void *data) if (!(int_status & VNINTS_FIS)) goto done; - /* Nothing to do if capture status is 'STOPPED' */ - if (vin->state == STOPPED) { - vin_dbg(vin, "IRQ while state stopped\n"); + /* Nothing to do if not running. */ + if (!vin->running) { + vin_dbg(vin, "IRQ while not running, ignoring\n"); goto done; } @@ -1373,6 +1370,8 @@ int rvin_start_streaming(struct rvin_dev *vin) if (ret) rvin_set_stream(vin, 0); + vin->running = true; + spin_unlock_irqrestore(&vin->qlock, flags); return ret; @@ -1405,44 +1404,21 @@ err_scratch: void rvin_stop_streaming(struct rvin_dev *vin) { - unsigned int i, retries; unsigned long flags; - bool buffersFreed; spin_lock_irqsave(&vin->qlock, flags); - if (vin->state == STOPPED) { + if (!vin->running) { spin_unlock_irqrestore(&vin->qlock, flags); return; } - vin->state = STOPPING; - - /* Wait until only scratch buffer is used, max 3 interrupts. */ - retries = 0; - while (retries++ < RVIN_RETRIES) { - buffersFreed = true; - for (i = 0; i < HW_BUFFER_NUM; i++) - if (vin->buf_hw[i].buffer) - buffersFreed = false; - - if (buffersFreed) - break; - - spin_unlock_irqrestore(&vin->qlock, flags); - msleep(RVIN_TIMEOUT_MS); - spin_lock_irqsave(&vin->qlock, flags); - } - /* Wait for streaming to stop */ - retries = 0; - while (retries++ < RVIN_RETRIES) { - + for (unsigned int i = 0; i < RVIN_RETRIES; i++) { rvin_capture_stop(vin); /* Check if HW is stopped */ if (!rvin_capture_active(vin)) { - vin->state = STOPPED; break; } @@ -1451,32 +1427,25 @@ void rvin_stop_streaming(struct rvin_dev *vin) spin_lock_irqsave(&vin->qlock, flags); } - if (!buffersFreed || vin->state != STOPPED) { - /* - * If this happens something have gone horribly wrong. - * Set state to stopped to prevent the interrupt handler - * to make things worse... - */ - vin_err(vin, "Failed stop HW, something is seriously broken\n"); - vin->state = STOPPED; - } + if (rvin_capture_active(vin)) + vin_err(vin, "Hardware did not stop\n"); - spin_unlock_irqrestore(&vin->qlock, flags); + vin->running = false; - /* If something went wrong, free buffers with an error. */ - if (!buffersFreed) { - return_unused_buffers(vin, VB2_BUF_STATE_ERROR); - for (i = 0; i < HW_BUFFER_NUM; i++) { - if (vin->buf_hw[i].buffer) - vb2_buffer_done(&vin->buf_hw[i].buffer->vb2_buf, - VB2_BUF_STATE_ERROR); - } - } + spin_unlock_irqrestore(&vin->qlock, flags); rvin_set_stream(vin, 0); /* disable interrupts */ rvin_disable_interrupts(vin); + + /* Return unprocessed buffers from hardware. */ + for (unsigned int i = 0; i < HW_BUFFER_NUM; i++) { + if (vin->buf_hw[i].buffer) + vb2_buffer_done(&vin->buf_hw[i].buffer->vb2_buf, + VB2_BUF_STATE_ERROR); + } + } static void rvin_stop_streaming_vq(struct vb2_queue *vq) @@ -1522,8 +1491,6 @@ int rvin_dma_register(struct rvin_dev *vin, int irq) spin_lock_init(&vin->qlock); - vin->state = STOPPED; - for (i = 0; i < HW_BUFFER_NUM; i++) vin->buf_hw[i].buffer = NULL; @@ -1626,7 +1593,7 @@ void rvin_set_alpha(struct rvin_dev *vin, unsigned int alpha) vin->alpha = alpha; - if (vin->state == STOPPED) + if (!vin->running) goto out; switch (vin->format.pixelformat) { diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h index f13ef379d095..934474d2334a 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -61,18 +61,6 @@ enum rvin_isp_id { (((unsigned int)RVIN_CSI_MAX) > ((unsigned int)RVIN_ISP_MAX) ? \ (unsigned int)RVIN_CSI_MAX : (unsigned int)RVIN_ISP_MAX) -/** - * enum rvin_dma_state - DMA states - * @STOPPED: No operation in progress - * @RUNNING: Operation in progress have buffers - * @STOPPING: Stopping operation - */ -enum rvin_dma_state { - STOPPED = 0, - RUNNING, - STOPPING, -}; - /** * struct rvin_video_format - Data format stored in memory * @fourcc: Pixelformat @@ -173,11 +161,11 @@ struct rvin_info { * @scratch: cpu address for scratch buffer * @scratch_phys: physical address of the scratch buffer * - * @qlock: protects @buf_hw, @buf_list, @sequence and @state + * @qlock: Protects @buf_hw, @buf_list, @sequence and @running * @buf_hw: Keeps track of buffers given to HW slot * @buf_list: list of queued buffers * @sequence: V4L2 buffers sequence number - * @state: keeps track of operation state + * @running: Keeps track of if the VIN is running * * @is_csi: flag to mark the VIN as using a CSI-2 subdevice * @chsel: Cached value of the current CSI-2 channel selection @@ -220,7 +208,7 @@ struct rvin_dev { } buf_hw[HW_BUFFER_NUM]; struct list_head buf_list; unsigned int sequence; - enum rvin_dma_state state; + bool running; bool is_csi; unsigned int chsel; -- 2.51.0 From 9396770c3add2142e21813ce8bc152e5e6524557 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Niklas=20S=C3=B6derlund?= Date: Mon, 10 Feb 2025 18:56:15 +0100 Subject: [PATCH 09/16] media: rcar-csi2: Remove hack to detect NTSC content MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit In an effort to emulate support for SEQ_{TB,BT} in the R-Car VIN driver on data captured from a CVBS input a hack was added to detect NTSC vs PAL. This is ugly and as support for emulated SEQ_{TB,BT} have been removed from the VIN driver remove the ugly hack. Signed-off-by: Niklas Söderlund Reviewed-by: Tomi Valkeinen Tested-by: Tomi Valkeinen Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-csi2.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 38a3149f9724..9979de4f6ef1 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -1075,16 +1075,10 @@ static int rcsi2_start_receiver_gen3(struct rcar_csi2 *priv, vcdt2 |= vcdt_part << ((i % 2) * 16); } - if (fmt->field == V4L2_FIELD_ALTERNATE) { + if (fmt->field == V4L2_FIELD_ALTERNATE) fld = FLD_DET_SEL(1) | FLD_FLD_EN4 | FLD_FLD_EN3 | FLD_FLD_EN2 | FLD_FLD_EN; - if (fmt->height == 240) - fld |= FLD_FLD_NUM(0); - else - fld |= FLD_FLD_NUM(1); - } - /* * Get the number of active data lanes inspecting the remote mbus * configuration. -- 2.51.0 From 4e228c365d85a03d08c7f555011919f4b6353dcd Mon Sep 17 00:00:00 2001 From: Philipp Stanner Date: Fri, 4 Apr 2025 15:53:41 +0200 Subject: [PATCH 10/16] media: ipu3-cio2: Replace deprecated PCI functions pcim_iomap_table() and pcim_iomap_regions() have been deprecated. Replace them with pcim_iomap_region(), and pass the actual driver name to that function. Signed-off-by: Philipp Stanner Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu3/ipu3-cio2.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c index 0c365eb59085..16fde96c9fb2 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c @@ -1702,14 +1702,13 @@ static int cio2_pci_probe(struct pci_dev *pci_dev, dev_info(dev, "device 0x%x (rev: 0x%x)\n", pci_dev->device, pci_dev->revision); - r = pcim_iomap_regions(pci_dev, 1 << CIO2_PCI_BAR, pci_name(pci_dev)); + cio2->base = pcim_iomap_region(pci_dev, CIO2_PCI_BAR, CIO2_NAME); + r = PTR_ERR_OR_ZERO(cio2->base); if (r) { dev_err(dev, "failed to remap I/O memory (%d)\n", r); return -ENODEV; } - cio2->base = pcim_iomap_table(pci_dev)[CIO2_PCI_BAR]; - pci_set_drvdata(pci_dev, cio2); pci_set_master(pci_dev); -- 2.51.0 From 3bb6339e2a133abf8e48d12114e6bb11820e36c0 Mon Sep 17 00:00:00 2001 From: Philipp Stanner Date: Fri, 4 Apr 2025 15:53:43 +0200 Subject: [PATCH 11/16] media: intel/ipu6: Replace deprecated PCI functions pcim_iomap_table() and pcim_iomap_regions() have been deprecated. Furthermore, the "name" parameter in pcim_iomap_regions() and its successor, pcim_iomap_region(), should always reflect the driver name, whereas currently it is the device's name. Replace the deprecated functions with pcim_iomap_region() and pass the actual driver name. Signed-off-by: Philipp Stanner Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index b00d0705fefa..1f4f20b9c94d 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -520,11 +520,11 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) phys = pci_resource_start(pdev, IPU6_PCI_BAR); dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys); - ret = pcim_iomap_regions(pdev, 1 << IPU6_PCI_BAR, pci_name(pdev)); - if (ret) - return dev_err_probe(dev, ret, "Failed to I/O mem remapping\n"); + isp->base = pcim_iomap_region(pdev, IPU6_PCI_BAR, IPU6_NAME); + if (IS_ERR(isp->base)) + return dev_err_probe(dev, PTR_ERR(isp->base), + "Failed to I/O mem remapping\n"); - isp->base = pcim_iomap_table(pdev)[IPU6_PCI_BAR]; pci_set_drvdata(pdev, isp); pci_set_master(pdev); -- 2.51.0 From df78f5928c4b699d0699ec0e2554571a45e38626 Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Mon, 7 Apr 2025 10:13:07 +0100 Subject: [PATCH 12/16] media: dt-bindings: Add OmniVision OV02E10 Add bindings for OVO2E10 a two lane MIPI CSI, two megapixel 1080p RGB sensor. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Bryan O'Donoghue Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../bindings/media/i2c/ovti,ov02e10.yaml | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml new file mode 100644 index 000000000000..4ac4e11a16c8 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright (c) 2025 Linaro Ltd. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ovti,ov02e10.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Omnivision OV02E10 CMOS Sensor + +maintainers: + - Bryan O'Donoghue + +description: | + The Omnivision OV02E10 is a 2 megapixel, CMOS image sensor which supports: + - Automatic black level calibration (ABLC) + - Programmable controls for frame rate, mirror and flip, binning, cropping + and windowing + - Output formats 10-bit 4C RGB RAW, 10-bit Bayer RAW + - 2-lane MIPI D-PHY TX @ 720 Mbps per lane + - Dynamic defect pixel cancellation + - Standard SCCB command interface + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: ovti,ov02e10 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + avdd-supply: + description: Analogue circuit voltage supply. + + dovdd-supply: + description: I/O circuit voltage supply. + + dvdd-supply: + description: Digital circuit voltage supply. + + reset-gpios: + description: Active low GPIO connected to XSHUTDOWN pad of the sensor. + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + additionalProperties: false + + properties: + data-lanes: + items: + - const: 1 + - const: 2 + link-frequencies: true + remote-endpoint: true + + required: + - data-lanes + - link-frequencies + - remote-endpoint + +required: + - compatible + - reg + - clocks + - port + +unevaluatedProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ov02e10: camera@10 { + compatible = "ovti,ov02e10"; + reg = <0x10>; + + reset-gpios = <&tlmm 237 GPIO_ACTIVE_LOW>; + pinctrl-names = "default"; + pinctrl-0 = <&cam_rgb_defaultt>; + + clocks = <&ov02e10_clk>; + + assigned-clocks = <&ov02e10_clk>; + assigned-clock-parents = <&ov02e10_clk_parent>; + assigned-clock-rates = <19200000>; + + avdd-supply = <&vreg_l7b_2p8>; + dvdd-supply = <&vreg_l7b_1p8>; + dovdd-supply = <&vreg_l3m_1p8>; + + port { + ov02e10_ep: endpoint { + remote-endpoint = <&csiphy4_ep>; + data-lanes = <1 2>; + link-frequencies = /bits/ 64 <400000000>; + }; + }; + }; + }; +... -- 2.51.0 From 1c734f8ab070716604d0794094e18de3475c8eeb Mon Sep 17 00:00:00 2001 From: Jingjing Xiong Date: Mon, 7 Apr 2025 10:13:08 +0100 Subject: [PATCH 13/16] media: i2c: ov02e10: add OV02E10 image sensor driver Add in the ov02e10 driver from the Intel IPU6 repository. Signed-off-by: Jingjing Xiong Co-developed-by: Hao Yao Signed-off-by: Hao Yao Co-developed-by: Jim Lai Signed-off-by: Jim Lai Co-developed-by: You-Sheng Yang Signed-off-by: You-Sheng Yang Co-developed-by: Alan Stern Signed-off-by: Alan Stern Co-developed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Bryan O'Donoghue Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 9 + drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ov02e10.c | 969 ++++++++++++++++++++++++++++++++++++ 4 files changed, 989 insertions(+) create mode 100644 drivers/media/i2c/ov02e10.c diff --git a/MAINTAINERS b/MAINTAINERS index 393d92c2e610..7a76cb72487b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17808,6 +17808,15 @@ T: git git://linuxtv.org/media.git F: Documentation/devicetree/bindings/media/i2c/ovti,ov02a10.yaml F: drivers/media/i2c/ov02a10.c +OMNIVISION OV02E10 SENSOR DRIVER +M: Bryan O'Donoghue +M: Hans de Goede +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media.git +F: Documentation/devicetree/bindings/media/i2c/ovti,ov02e10.yaml +F: drivers/media/i2c/ov02e10.c + OMNIVISION OV08D10 SENSOR DRIVER M: Jimmy Su L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index e576b213084d..7b8af1c87a0e 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -356,6 +356,16 @@ config VIDEO_OV02A10 To compile this driver as a module, choose M here: the module will be called ov02a10. +config VIDEO_OV02E10 + tristate "OmniVision OV02E10 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV02E10 camera. + + To compile this driver as a module, choose M here: the + module will be called ov02e10. + config VIDEO_OV08D10 tristate "OmniVision OV08D10 sensor support" help diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 6c23a4463527..ed5e62fd6199 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -83,6 +83,7 @@ obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o +obj-$(CONFIG_VIDEO_OV02E10) += ov02e10.o obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o obj-$(CONFIG_VIDEO_OV13858) += ov13858.o diff --git a/drivers/media/i2c/ov02e10.c b/drivers/media/i2c/ov02e10.c new file mode 100644 index 000000000000..d74dc62e189d --- /dev/null +++ b/drivers/media/i2c/ov02e10.c @@ -0,0 +1,969 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2023 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OV02E10_LINK_FREQ_360MHZ 360000000ULL +#define OV02E10_SCLK 36000000LL +#define OV02E10_MCLK 19200000 +#define OV02E10_DATA_LANES 2 +#define OV02E10_RGB_DEPTH 10 + +#define OV02E10_REG_PAGE_FLAG CCI_REG8(0xfd) +#define OV02E10_PAGE_0 0x0 +#define OV02E10_PAGE_1 0x1 +#define OV02E10_PAGE_2 0x2 +#define OV02E10_PAGE_3 0x3 +#define OV02E10_PAGE_5 0x4 +#define OV02E10_PAGE_7 0x5 +#define OV02E10_PAGE_8 0x6 +#define OV02E10_PAGE_9 0xF +#define OV02E10_PAGE_D 0x8 +#define OV02E10_PAGE_E 0x9 +#define OV02E10_PAGE_F 0xA + +#define OV02E10_REG_CHIP_ID CCI_REG32(0x00) +#define OV02E10_CHIP_ID 0x45025610 + +/* Horizontal and vertical flip */ +#define OV02E10_REG_ORIENTATION CCI_REG8(0x32) + +/* vertical-timings from sensor */ +#define OV02E10_REG_VTS CCI_REG16(0x35) +#define OV02E10_VTS_DEF 2244 +#define OV02E10_VTS_MIN 2244 +#define OV02E10_VTS_MAX 0x7fff + +/* horizontal-timings from sensor */ +#define OV02E10_REG_HTS CCI_REG16(0x37) + +/* Exposure controls from sensor */ +#define OV02E10_REG_EXPOSURE CCI_REG16(0x03) +#define OV02E10_EXPOSURE_MIN 1 +#define OV02E10_EXPOSURE_MAX_MARGIN 2 +#define OV02E10_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define OV02E10_REG_ANALOG_GAIN CCI_REG8(0x24) +#define OV02E10_ANAL_GAIN_MIN 0x10 +#define OV02E10_ANAL_GAIN_MAX 0xf8 +#define OV02E10_ANAL_GAIN_STEP 1 + +/* Digital gain controls from sensor */ +#define OV02E10_REG_DIGITAL_GAIN CCI_REG16(0x21) +#define OV02E10_DGTL_GAIN_MIN 256 +#define OV02E10_DGTL_GAIN_MAX 1020 +#define OV02E10_DGTL_GAIN_STEP 1 +#define OV02E10_DGTL_GAIN_DEFAULT 256 + +/* Register update control */ +#define OV02E10_REG_COMMAND_UPDATE CCI_REG8(0xE7) +#define OV02E10_COMMAND_UPDATE 0x00 +#define OV02E10_COMMAND_HOLD 0x01 + +/* Test Pattern Control */ +#define OV02E10_REG_TEST_PATTERN CCI_REG8(0x12) +#define OV02E10_TEST_PATTERN_ENABLE BIT(0) +#define OV02E10_TEST_PATTERN_BAR_SHIFT 1 + +struct reg_sequence_list { + u32 num_regs; + const struct reg_sequence *regs; +}; + +struct ov02e10_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timining size */ + u32 hts; + + /* Default vertical timing */ + u32 vts_def; + + /* Min vertical timining size */ + u32 vts_min; + + /* Sensor register settings for this resolution */ + const struct reg_sequence_list reg_list; +}; + +static const struct reg_sequence mode_1928x1088_30fps_2lane[] = { + { 0xfd, 0x00 }, + { 0x20, 0x00 }, + { 0x20, 0x0b }, + { 0x21, 0x02 }, + { 0x10, 0x23 }, + { 0xc5, 0x04 }, + { 0x21, 0x00 }, + { 0x14, 0x96 }, + { 0x17, 0x01 }, + { 0xfd, 0x01 }, + { 0x03, 0x00 }, + { 0x04, 0x04 }, + { 0x05, 0x04 }, + { 0x06, 0x62 }, + { 0x07, 0x01 }, + { 0x22, 0x80 }, + { 0x24, 0xff }, + { 0x40, 0xc6 }, + { 0x41, 0x18 }, + { 0x45, 0x3f }, + { 0x48, 0x0c }, + { 0x4c, 0x08 }, + { 0x51, 0x12 }, + { 0x52, 0x10 }, + { 0x57, 0x98 }, + { 0x59, 0x06 }, + { 0x5a, 0x04 }, + { 0x5c, 0x38 }, + { 0x5e, 0x10 }, + { 0x67, 0x11 }, + { 0x7b, 0x04 }, + { 0x81, 0x12 }, + { 0x90, 0x51 }, + { 0x91, 0x09 }, + { 0x92, 0x21 }, + { 0x93, 0x28 }, + { 0x95, 0x54 }, + { 0x9d, 0x20 }, + { 0x9e, 0x04 }, + { 0xb1, 0x9a }, + { 0xb2, 0x86 }, + { 0xb6, 0x3f }, + { 0xb9, 0x30 }, + { 0xc1, 0x01 }, + { 0xc5, 0xa0 }, + { 0xc6, 0x73 }, + { 0xc7, 0x04 }, + { 0xc8, 0x25 }, + { 0xc9, 0x05 }, + { 0xca, 0x28 }, + { 0xcb, 0x00 }, + { 0xcf, 0x16 }, + { 0xd2, 0xd0 }, + { 0xd7, 0x3f }, + { 0xd8, 0x40 }, + { 0xd9, 0x40 }, + { 0xda, 0x44 }, + { 0xdb, 0x3d }, + { 0xdc, 0x3d }, + { 0xdd, 0x3d }, + { 0xde, 0x3d }, + { 0xdf, 0xf0 }, + { 0xea, 0x0f }, + { 0xeb, 0x04 }, + { 0xec, 0x29 }, + { 0xee, 0x47 }, + { 0xfd, 0x01 }, + { 0x31, 0x01 }, + { 0x27, 0x00 }, + { 0x2f, 0x41 }, + { 0xfd, 0x02 }, + { 0xa1, 0x01 }, + { 0xfd, 0x02 }, + { 0x9a, 0x03 }, + { 0xfd, 0x03 }, + { 0x9d, 0x0f }, + { 0xfd, 0x07 }, + { 0x42, 0x00 }, + { 0x43, 0xad }, + { 0x44, 0x00 }, + { 0x45, 0xa8 }, + { 0x46, 0x00 }, + { 0x47, 0xa8 }, + { 0x48, 0x00 }, + { 0x49, 0xad }, + { 0xfd, 0x00 }, + { 0xc4, 0x01 }, + { 0xfd, 0x01 }, + { 0x33, 0x03 }, + { 0xfd, 0x00 }, + { 0x20, 0x1f }, +}; + +static const char *const ov02e10_test_pattern_menu[] = { + "Disabled", + "Color Bar", +}; + +static const s64 link_freq_menu_items[] = { + OV02E10_LINK_FREQ_360MHZ, +}; + +static const struct ov02e10_mode supported_modes[] = { + { + .width = 1928, + .height = 1088, + .hts = 534, + .vts_def = 2244, + .vts_min = 2244, + .reg_list = { + .num_regs = ARRAY_SIZE(mode_1928x1088_30fps_2lane), + .regs = mode_1928x1088_30fps_2lane, + }, + }, +}; + +static const char * const ov02e10_supply_names[] = { + "dovdd", /* Digital I/O power */ + "avdd", /* Analog power */ + "dvdd", /* Digital core power */ +}; + +struct ov02e10 { + struct regmap *regmap; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + + struct clk *img_clk; + struct regulator_bulk_data supplies[ARRAY_SIZE(ov02e10_supply_names)]; + struct gpio_desc *reset; + + /* Current mode */ + const struct ov02e10_mode *cur_mode; + + /* MIPI lanes info */ + u32 link_freq_index; + u8 mipi_lanes; +}; + +static inline struct ov02e10 *to_ov02e10(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct ov02e10, sd); +} + +static u64 to_pixel_rate(u32 f_index) +{ + u64 pixel_rate = link_freq_menu_items[f_index] * 2 * OV02E10_DATA_LANES; + + do_div(pixel_rate, OV02E10_RGB_DEPTH); + + return pixel_rate; +} + +static u64 to_pixels_per_line(u32 hts, u32 f_index) +{ + u64 ppl = hts * to_pixel_rate(f_index); + + do_div(ppl, OV02E10_SCLK); + + return ppl; +} + +static void ov02e10_test_pattern(struct ov02e10 *ov02e10, u32 pattern, int *pret) +{ + if (pattern) + pattern = pattern << OV02E10_TEST_PATTERN_BAR_SHIFT | + OV02E10_TEST_PATTERN_ENABLE; + + cci_write(ov02e10->regmap, OV02E10_REG_TEST_PATTERN, pattern, pret); +} + +static int ov02e10_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov02e10 *ov02e10 = container_of(ctrl->handler, + struct ov02e10, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov02e10->sd); + s64 exposure_max; + int ret; + + /* Propagate change of current control to all related controls */ + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = ov02e10->cur_mode->height + ctrl->val - + OV02E10_EXPOSURE_MAX_MARGIN; + ret = __v4l2_ctrl_modify_range(ov02e10->exposure, + ov02e10->exposure->minimum, + exposure_max, + ov02e10->exposure->step, + exposure_max); + if (ret) + return ret; + } + + /* V4L2 controls values will be applied only when power is already up */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + ret = cci_write(ov02e10->regmap, OV02E10_REG_COMMAND_UPDATE, + OV02E10_COMMAND_HOLD, NULL); + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, + OV02E10_PAGE_1, &ret); + cci_write(ov02e10->regmap, OV02E10_REG_ANALOG_GAIN, + ctrl->val, &ret); + break; + + case V4L2_CID_DIGITAL_GAIN: + cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, + OV02E10_PAGE_1, &ret); + cci_write(ov02e10->regmap, OV02E10_REG_DIGITAL_GAIN, + ctrl->val, &ret); + break; + + case V4L2_CID_EXPOSURE: + cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, + OV02E10_PAGE_1, &ret); + cci_write(ov02e10->regmap, OV02E10_REG_EXPOSURE, + ctrl->val, &ret); + break; + + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, + OV02E10_PAGE_1, &ret); + cci_write(ov02e10->regmap, OV02E10_REG_ORIENTATION, + ov02e10->hflip->val | ov02e10->vflip->val << 1, &ret); + break; + case V4L2_CID_VBLANK: + cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, + OV02E10_PAGE_1, &ret); + cci_write(ov02e10->regmap, OV02E10_REG_VTS, + ov02e10->cur_mode->height + ctrl->val, &ret); + break; + + case V4L2_CID_TEST_PATTERN: + cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, + OV02E10_PAGE_1, &ret); + ov02e10_test_pattern(ov02e10, ctrl->val, &ret); + break; + + default: + ret = -EINVAL; + break; + } + + cci_write(ov02e10->regmap, OV02E10_REG_COMMAND_UPDATE, + OV02E10_COMMAND_UPDATE, &ret); + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov02e10_ctrl_ops = { + .s_ctrl = ov02e10_set_ctrl, +}; + +static int ov02e10_init_controls(struct ov02e10 *ov02e10) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov02e10->sd); + struct v4l2_ctrl_handler *ctrl_hdlr = &ov02e10->ctrl_handler; + const struct ov02e10_mode *mode = ov02e10->cur_mode; + u32 vblank_min, vblank_max, vblank_def; + struct v4l2_fwnode_device_properties props; + s64 exposure_max, h_blank, pixel_rate; + int ret; + + v4l2_ctrl_handler_init(ctrl_hdlr, 12); + + ov02e10->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, + &ov02e10_ctrl_ops, + V4L2_CID_LINK_FREQ, + ov02e10->link_freq_index, + 0, link_freq_menu_items); + if (ov02e10->link_freq) + ov02e10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate = to_pixel_rate(ov02e10->link_freq_index); + ov02e10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + pixel_rate, 1, pixel_rate); + + vblank_min = mode->vts_min - mode->height; + vblank_max = OV02E10_VTS_MAX - mode->height; + vblank_def = mode->vts_def - mode->height; + ov02e10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops, + V4L2_CID_VBLANK, vblank_min, + vblank_max, 1, vblank_def); + + h_blank = mode->hts - mode->width; + ov02e10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, + 1, h_blank); + if (ov02e10->hblank) + ov02e10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV02E10_ANAL_GAIN_MIN, OV02E10_ANAL_GAIN_MAX, + OV02E10_ANAL_GAIN_STEP, OV02E10_ANAL_GAIN_MIN); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV02E10_DGTL_GAIN_MIN, OV02E10_DGTL_GAIN_MAX, + OV02E10_DGTL_GAIN_STEP, OV02E10_DGTL_GAIN_DEFAULT); + + exposure_max = mode->vts_def - OV02E10_EXPOSURE_MAX_MARGIN; + ov02e10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops, + V4L2_CID_EXPOSURE, + OV02E10_EXPOSURE_MIN, + exposure_max, + OV02E10_EXPOSURE_STEP, + exposure_max); + + ov02e10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (ov02e10->hflip) + ov02e10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + ov02e10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov02e10_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (ov02e10->vflip) + ov02e10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov02e10_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov02e10_test_pattern_menu) - 1, + 0, 0, ov02e10_test_pattern_menu); + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + return ret; + + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov02e10_ctrl_ops, &props); + + if (ctrl_hdlr->error) + return ctrl_hdlr->error; + + ov02e10->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static void ov02e10_update_pad_format(const struct ov02e10_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = mode->width; + fmt->height = mode->height; + fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->field = V4L2_FIELD_NONE; +} + +static int ov02e10_set_stream_mode(struct ov02e10 *ov02e10, u8 val) +{ + int ret = 0; + + cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, OV02E10_PAGE_0, &ret); + cci_write(ov02e10->regmap, CCI_REG8(0xa0), val, &ret); + cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, OV02E10_PAGE_1, &ret); + cci_write(ov02e10->regmap, CCI_REG8(0x01), 0x02, &ret); + + return ret; +} + +static int ov02e10_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov02e10 *ov02e10 = to_ov02e10(sd); + const struct reg_sequence_list *reg_list; + int ret; + + ret = pm_runtime_resume_and_get(&client->dev); + if (ret) + return ret; + + reg_list = &ov02e10->cur_mode->reg_list; + ret = regmap_multi_reg_write(ov02e10->regmap, reg_list->regs, + reg_list->num_regs); + if (ret) { + dev_err(&client->dev, "failed to set mode\n"); + goto out; + } + + ret = __v4l2_ctrl_handler_setup(ov02e10->sd.ctrl_handler); + if (ret) + goto out; + + ret = ov02e10_set_stream_mode(ov02e10, 1); + +out: + if (ret) + pm_runtime_put(&client->dev); + + return ret; +} + +static int ov02e10_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov02e10 *ov02e10 = to_ov02e10(sd); + + ov02e10_set_stream_mode(ov02e10, 0); + pm_runtime_put(&client->dev); + + return 0; +} + +static int ov02e10_get_pm_resources(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov02e10 *ov02e10 = to_ov02e10(sd); + int i; + + ov02e10->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ov02e10->reset)) + return dev_err_probe(dev, PTR_ERR(ov02e10->reset), + "failed to get reset gpio\n"); + + for (i = 0; i < ARRAY_SIZE(ov02e10_supply_names); i++) + ov02e10->supplies[i].supply = ov02e10_supply_names[i]; + + return devm_regulator_bulk_get(dev, ARRAY_SIZE(ov02e10_supply_names), + ov02e10->supplies); +} + +static int ov02e10_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov02e10 *ov02e10 = to_ov02e10(sd); + + if (ov02e10->reset) + gpiod_set_value_cansleep(ov02e10->reset, 1); + + regulator_bulk_disable(ARRAY_SIZE(ov02e10_supply_names), + ov02e10->supplies); + + clk_disable_unprepare(ov02e10->img_clk); + + return 0; +} + +static int ov02e10_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov02e10 *ov02e10 = to_ov02e10(sd); + int ret; + + ret = clk_prepare_enable(ov02e10->img_clk); + if (ret < 0) { + dev_err(dev, "failed to enable imaging clock: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ov02e10_supply_names), + ov02e10->supplies); + if (ret < 0) { + dev_err(dev, "failed to enable regulators\n"); + goto disable_clk; + } + + if (ov02e10->reset) { + usleep_range(5000, 5100); + gpiod_set_value_cansleep(ov02e10->reset, 0); + usleep_range(8000, 8100); + } + + return 0; + +disable_clk: + clk_disable_unprepare(ov02e10->img_clk); + + return ret; +} + +static int ov02e10_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov02e10 *ov02e10 = to_ov02e10(sd); + const struct ov02e10_mode *mode; + s32 vblank_def, h_blank; + int ret = 0; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, fmt->format.width, + fmt->format.height); + + ov02e10_update_pad_format(mode, &fmt->format); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; + } else { + ov02e10->cur_mode = mode; + ret = __v4l2_ctrl_s_ctrl(ov02e10->link_freq, + ov02e10->link_freq_index); + if (ret) + return ret; + + ret = __v4l2_ctrl_s_ctrl_int64(ov02e10->pixel_rate, + to_pixel_rate(ov02e10->link_freq_index)); + if (ret) + return ret; + + /* Update limits and set FPS to default */ + vblank_def = mode->vts_def - mode->height; + ret = __v4l2_ctrl_modify_range(ov02e10->vblank, + mode->vts_min - mode->height, + OV02E10_VTS_MAX - mode->height, + 1, vblank_def); + if (ret) + return ret; + + ret = __v4l2_ctrl_s_ctrl(ov02e10->vblank, vblank_def); + if (ret) + return ret; + + h_blank = to_pixels_per_line(mode->hts, ov02e10->link_freq_index); + h_blank -= mode->width; + ret = __v4l2_ctrl_modify_range(ov02e10->hblank, h_blank, + h_blank, 1, h_blank); + } + + return ret; +} + +static int ov02e10_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov02e10 *ov02e10 = to_ov02e10(sd); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad); + else + ov02e10_update_pad_format(ov02e10->cur_mode, &fmt->format); + + return 0; +} + +static int ov02e10_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_SGRBG10_1X10; + + return 0; +} + +static int ov02e10_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int ov02e10_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + ov02e10_update_pad_format(&supported_modes[0], + v4l2_subdev_state_get_format(sd_state, 0)); + + return 0; +} + +static const struct v4l2_subdev_video_ops ov02e10_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops ov02e10_pad_ops = { + .set_fmt = ov02e10_set_format, + .get_fmt = ov02e10_get_format, + .enum_mbus_code = ov02e10_enum_mbus_code, + .enum_frame_size = ov02e10_enum_frame_size, + .enable_streams = ov02e10_enable_streams, + .disable_streams = ov02e10_disable_streams, +}; + +static const struct v4l2_subdev_ops ov02e10_subdev_ops = { + .video = &ov02e10_video_ops, + .pad = &ov02e10_pad_ops, +}; + +static const struct media_entity_operations ov02e10_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov02e10_internal_ops = { + .init_state = ov02e10_init_state, +}; + +static int ov02e10_identify_module(struct ov02e10 *ov02e10) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov02e10->sd); + int ret; + u64 val; + + ret = cci_write(ov02e10->regmap, OV02E10_REG_PAGE_FLAG, + OV02E10_PAGE_0, NULL); + cci_read(ov02e10->regmap, OV02E10_REG_CHIP_ID, &val, &ret); + if (ret) + return ret; + + if (val != OV02E10_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + OV02E10_CHIP_ID, (u32)val); + return -ENXIO; + } + + return 0; +} + +static int ov02e10_check_hwcfg(struct device *dev, struct ov02e10 *ov02e10) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + unsigned long link_freq_bitmap; + u32 ext_clk; + int ret; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for fwnode graph endpoint\n"); + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return dev_err_probe(dev, ret, "parsing endpoint failed\n"); + + ov02e10->img_clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(ov02e10->img_clk)) { + ret = dev_err_probe(dev, PTR_ERR(ov02e10->img_clk), + "failed to get imaging clock\n"); + goto out_err; + } + + if (ov02e10->img_clk) { + ext_clk = clk_get_rate(ov02e10->img_clk); + } else { + ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &ext_clk); + if (ret) { + dev_err(dev, "can't get clock frequency\n"); + goto out_err; + } + } + + if (ext_clk != OV02E10_MCLK) { + dev_err(dev, "external clock %d is not supported\n", + ext_clk); + ret = -EINVAL; + goto out_err; + } + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV02E10_DATA_LANES) { + dev_err(dev, "number of CSI2 data lanes %d is not supported\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto out_err; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined\n"); + ret = -EINVAL; + goto out_err; + } + + ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + link_freq_menu_items, + ARRAY_SIZE(link_freq_menu_items), + &link_freq_bitmap); + if (ret) + goto out_err; + + /* v4l2_link_freq_to_bitmap() guarantees at least 1 bit is set */ + ov02e10->link_freq_index = ffs(link_freq_bitmap) - 1; + ov02e10->mipi_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; + +out_err: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static void ov02e10_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(&client->dev); + + if (!pm_runtime_status_suspended(&client->dev)) { + ov02e10_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } +} + +static int ov02e10_probe(struct i2c_client *client) +{ + struct ov02e10 *ov02e10; + int ret; + + ov02e10 = devm_kzalloc(&client->dev, sizeof(*ov02e10), GFP_KERNEL); + if (!ov02e10) + return -ENOMEM; + + v4l2_i2c_subdev_init(&ov02e10->sd, client, &ov02e10_subdev_ops); + + /* Check HW config */ + ret = ov02e10_check_hwcfg(&client->dev, ov02e10); + if (ret) + return ret; + + /* Initialize subdev */ + ov02e10->regmap = devm_cci_regmap_init_i2c(client, 8); + if (IS_ERR(ov02e10->regmap)) + return PTR_ERR(ov02e10->regmap); + + ret = ov02e10_get_pm_resources(&client->dev); + if (ret) + return ret; + + ret = ov02e10_power_on(&client->dev); + if (ret) { + dev_err_probe(&client->dev, ret, "failed to power on\n"); + return ret; + } + + /* Check module identity */ + ret = ov02e10_identify_module(ov02e10); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d\n", ret); + goto probe_error_power_off; + } + + ov02e10->cur_mode = &supported_modes[0]; + ret = ov02e10_init_controls(ov02e10); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d\n", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + /* Initialize subdev */ + ov02e10->sd.internal_ops = &ov02e10_internal_ops; + ov02e10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov02e10->sd.entity.ops = &ov02e10_subdev_entity_ops; + ov02e10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + ov02e10->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov02e10->sd.entity, 1, &ov02e10->pad); + if (ret) { + dev_err(&client->dev, "failed to init entity pads: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ov02e10->sd.state_lock = ov02e10->ctrl_handler.lock; + ret = v4l2_subdev_init_finalize(&ov02e10->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to init subdev: %d", ret); + goto probe_error_media_entity_cleanup; + } + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + + ret = v4l2_async_register_subdev_sensor(&ov02e10->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %d", + ret); + goto probe_error_v4l2_subdev_cleanup; + } + + pm_runtime_idle(&client->dev); + return 0; + +probe_error_v4l2_subdev_cleanup: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + v4l2_subdev_cleanup(&ov02e10->sd); + +probe_error_media_entity_cleanup: + media_entity_cleanup(&ov02e10->sd.entity); + +probe_error_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(ov02e10->sd.ctrl_handler); + +probe_error_power_off: + ov02e10_power_off(&client->dev); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ov02e10_pm_ops, ov02e10_power_off, + ov02e10_power_on, NULL); + +static const struct acpi_device_id ov02e10_acpi_ids[] = { + { "OVTI02E1" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(acpi, ov02e10_acpi_ids); + +static const struct of_device_id ov02e10_of_match[] = { + { .compatible = "ovti,ov02e10" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ov02e10_of_match); + +static struct i2c_driver ov02e10_i2c_driver = { + .driver = { + .name = "ov02e10", + .pm = pm_sleep_ptr(&ov02e10_pm_ops), + .acpi_match_table = ov02e10_acpi_ids, + .of_match_table = ov02e10_of_match, + }, + .probe = ov02e10_probe, + .remove = ov02e10_remove, +}; + +module_i2c_driver(ov02e10_i2c_driver); + +MODULE_AUTHOR("Jingjing Xiong "); +MODULE_AUTHOR("Hans de Goede "); +MODULE_AUTHOR("Alan Stern "); +MODULE_AUTHOR("Bryan O'Donoghue "); +MODULE_DESCRIPTION("OmniVision OV02E10 sensor driver"); +MODULE_LICENSE("GPL"); -- 2.51.0 From 25259379bc796bf718eae395ffe05e76d42cbe5b Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Mon, 21 Apr 2025 10:20:15 +0200 Subject: [PATCH 14/16] media: dt-bindings: Convert Analog Devices ad5820 to DT schema Convert the Analog Devices ad5820 to DT schema format. Acked-by: Pavel Machek Signed-off-by: David Heidelberg Reviewed-by: Rob Herring (Arm) Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/i2c/ad5820.txt | 28 ---------- .../bindings/media/i2c/adi,ad5820.yaml | 56 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 57 insertions(+), 28 deletions(-) delete mode 100644 Documentation/devicetree/bindings/media/i2c/ad5820.txt create mode 100644 Documentation/devicetree/bindings/media/i2c/adi,ad5820.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ad5820.txt b/Documentation/devicetree/bindings/media/i2c/ad5820.txt deleted file mode 100644 index 5764cbedf9b7..000000000000 --- a/Documentation/devicetree/bindings/media/i2c/ad5820.txt +++ /dev/null @@ -1,28 +0,0 @@ -* Analog Devices AD5820 autofocus coil - -Required Properties: - - - compatible: Must contain one of: - - "adi,ad5820" - - "adi,ad5821" - - "adi,ad5823" - - - reg: I2C slave address - - - VANA-supply: supply of voltage for VANA pin - -Optional properties: - - - enable-gpios : GPIO spec for the XSHUTDOWN pin. The XSHUTDOWN signal is -active low, a high level on the pin enables the device. - -Example: - - ad5820: coil@c { - compatible = "adi,ad5820"; - reg = <0x0c>; - - VANA-supply = <&vaux4>; - enable-gpios = <&msmgpio 26 GPIO_ACTIVE_HIGH>; - }; - diff --git a/Documentation/devicetree/bindings/media/i2c/adi,ad5820.yaml b/Documentation/devicetree/bindings/media/i2c/adi,ad5820.yaml new file mode 100644 index 000000000000..0c8f24f692ca --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/adi,ad5820.yaml @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/adi,ad5820.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD5820 autofocus coil + +maintainers: + - Pavel Machek + +description: + The AD5820 is a current sink driver designed for precise control of + voice coil motors (VCMs) in camera autofocus systems. + +properties: + compatible: + enum: + - adi,ad5820 + - adi,ad5821 + - adi,ad5823 + + reg: + maxItems: 1 + + enable-gpios: + maxItems: 1 + description: + GPIO spec for the XSHUTDOWN pin. The XSHUTDOWN signal is active low, + a high level on the pin enables the device. + + VANA-supply: + description: supply of voltage for VANA pin + +required: + - compatible + - reg + - VANA-supply + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + coil@c { + compatible = "adi,ad5820"; + reg = <0x0c>; + + enable-gpios = <&msmgpio 26 GPIO_ACTIVE_HIGH>; + VANA-supply = <&vaux4>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 7a76cb72487b..1b5e8ec57851 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17213,6 +17213,7 @@ M: Pavel Machek M: Sakari Ailus L: linux-media@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/media/i2c/adi,ad5820.yaml F: drivers/media/i2c/ad5820.c F: drivers/media/i2c/et8ek8 -- 2.51.0 From 838a5255698b414897c3b237c00b21f3f1620fef Mon Sep 17 00:00:00 2001 From: Tarang Raval Date: Thu, 6 Mar 2025 11:04:43 +0530 Subject: [PATCH 15/16] media: i2c: imx219: switch to {enable,disable}_streams Switch from s_stream to enable_streams and disable_streams callbacks. Signed-off-by: Tarang Raval Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/imx219.c | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index 04262bbf6306..1b4eb531b315 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -718,9 +718,11 @@ static int imx219_configure_lanes(struct imx219 *imx219) ARRAY_SIZE(imx219_4lane_regs), NULL); }; -static int imx219_start_streaming(struct imx219 *imx219, - struct v4l2_subdev_state *state) +static int imx219_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) { + struct imx219 *imx219 = to_imx219(sd); struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); int ret; @@ -773,8 +775,11 @@ err_rpm_put: return ret; } -static void imx219_stop_streaming(struct imx219 *imx219) +static int imx219_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) { + struct imx219 *imx219 = to_imx219(sd); struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd); int ret; @@ -788,22 +793,7 @@ static void imx219_stop_streaming(struct imx219 *imx219) __v4l2_ctrl_grab(imx219->hflip, false); pm_runtime_put(&client->dev); -} - -static int imx219_set_stream(struct v4l2_subdev *sd, int enable) -{ - struct imx219 *imx219 = to_imx219(sd); - struct v4l2_subdev_state *state; - int ret = 0; - - state = v4l2_subdev_lock_and_get_active_state(sd); - - if (enable) - ret = imx219_start_streaming(imx219, state); - else - imx219_stop_streaming(imx219); - v4l2_subdev_unlock_state(state); return ret; } @@ -995,7 +985,7 @@ static int imx219_init_state(struct v4l2_subdev *sd, } static const struct v4l2_subdev_video_ops imx219_video_ops = { - .s_stream = imx219_set_stream, + .s_stream = v4l2_subdev_s_stream_helper, }; static const struct v4l2_subdev_pad_ops imx219_pad_ops = { @@ -1004,6 +994,8 @@ static const struct v4l2_subdev_pad_ops imx219_pad_ops = { .set_fmt = imx219_set_pad_format, .get_selection = imx219_get_selection, .enum_frame_size = imx219_enum_frame_size, + .enable_streams = imx219_enable_streams, + .disable_streams = imx219_disable_streams, }; static const struct v4l2_subdev_ops imx219_subdev_ops = { -- 2.51.0 From 5bd6b8c1bb2d49d5ff15c7c47d6b33d2d38785f0 Mon Sep 17 00:00:00 2001 From: Tarang Raval Date: Thu, 6 Mar 2025 11:04:44 +0530 Subject: [PATCH 16/16] media: i2c: imx219: media: i2c: imx219: Enable runtime PM autosuspend Use pm_runtime_put_autosuspend() instead of pm_runtime_put() to allow autosuspend. Set a 1000ms autosuspend delay in imx219_probe() to improve power efficiency. Signed-off-by: Tarang Raval Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/imx219.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index 1b4eb531b315..3b4f68543342 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -771,7 +771,8 @@ static int imx219_enable_streams(struct v4l2_subdev *sd, return 0; err_rpm_put: - pm_runtime_put(&client->dev); + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); return ret; } @@ -792,7 +793,8 @@ static int imx219_disable_streams(struct v4l2_subdev *sd, __v4l2_ctrl_grab(imx219->vflip, false); __v4l2_ctrl_grab(imx219->hflip, false); - pm_runtime_put(&client->dev); + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); return ret; } @@ -1272,6 +1274,8 @@ static int imx219_probe(struct i2c_client *client) } pm_runtime_idle(dev); + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); return 0; -- 2.51.0