Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

media: platform: Add Renesas Input Video Control block driver

Add a driver for the Input Video Control block in an RZ/V2H(P) SoC
which feeds data into the Arm Mali-C55 ISP.

[ivc: Remove check on buffers list in start_streaming]
[ivc: put_autosuspend() implies mark_last_busy()]
[media: rzv2h-ivc: Do not delay frame completion]
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
[hverkuil: remove deprecated vb2_ops_wait_prepare/finish callbacks]
[hverkuil: replace "select PM" by "depends on PM"]

authored by

Daniel Scally and committed by
Hans Verkuil
f0b3984d ef5a75b4

+1313
+1
drivers/media/platform/renesas/Kconfig
··· 42 42 source "drivers/media/platform/renesas/rcar-isp/Kconfig" 43 43 source "drivers/media/platform/renesas/rcar-vin/Kconfig" 44 44 source "drivers/media/platform/renesas/rzg2l-cru/Kconfig" 45 + source "drivers/media/platform/renesas/rzv2h-ivc/Kconfig" 45 46 46 47 # Mem2mem drivers 47 48
+1
drivers/media/platform/renesas/Makefile
··· 6 6 obj-y += rcar-isp/ 7 7 obj-y += rcar-vin/ 8 8 obj-y += rzg2l-cru/ 9 + obj-y += rzv2h-ivc/ 9 10 obj-y += vsp1/ 10 11 11 12 obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o
+18
drivers/media/platform/renesas/rzv2h-ivc/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + 3 + config VIDEO_RZV2H_IVC 4 + tristate "Renesas RZ/V2H(P) Input Video Control block driver" 5 + depends on V4L_PLATFORM_DRIVERS 6 + depends on VIDEO_DEV 7 + depends on ARCH_RENESAS || COMPILE_TEST 8 + depends on OF 9 + depends on PM 10 + select VIDEOBUF2_DMA_CONTIG 11 + select MEDIA_CONTROLLER 12 + select VIDEO_V4L2_SUBDEV_API 13 + help 14 + Support for the Renesas RZ/V2H(P) Input Video Control Block 15 + (IVC). 16 + 17 + To compile this driver as a module, choose M here: the 18 + module will be called rzv2h-ivc.
+5
drivers/media/platform/renesas/rzv2h-ivc/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + rzv2h-ivc-y := rzv2h-ivc-dev.o rzv2h-ivc-subdev.o rzv2h-ivc-video.o 4 + 5 + obj-$(CONFIG_VIDEO_RZV2H_IVC) += rzv2h-ivc.o
+251
drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Renesas RZ/V2H(P) Input Video Control Block driver 4 + * 5 + * Copyright (C) 2025 Ideas on Board Oy 6 + */ 7 + 8 + #include "rzv2h-ivc.h" 9 + 10 + #include <linux/device.h> 11 + #include <linux/interrupt.h> 12 + #include <linux/io.h> 13 + #include <linux/platform_device.h> 14 + #include <linux/pm_runtime.h> 15 + #include <linux/reset.h> 16 + 17 + void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val) 18 + { 19 + writel(val, ivc->base + addr); 20 + } 21 + 22 + void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr, 23 + u32 mask, u32 val) 24 + { 25 + u32 orig, new; 26 + 27 + orig = readl(ivc->base + addr); 28 + 29 + new = orig & ~mask; 30 + new |= val & mask; 31 + 32 + if (new != orig) 33 + writel(new, ivc->base + addr); 34 + } 35 + 36 + static int rzv2h_ivc_get_hardware_resources(struct rzv2h_ivc *ivc, 37 + struct platform_device *pdev) 38 + { 39 + static const char * const resource_names[RZV2H_IVC_NUM_HW_RESOURCES] = { 40 + "reg", 41 + "axi", 42 + "isp", 43 + }; 44 + struct resource *res; 45 + int ret; 46 + 47 + ivc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 48 + if (IS_ERR(ivc->base)) 49 + return dev_err_probe(ivc->dev, PTR_ERR(ivc->base), 50 + "failed to map IO memory\n"); 51 + 52 + for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++) 53 + ivc->clks[i].id = resource_names[i]; 54 + 55 + ret = devm_clk_bulk_get(ivc->dev, ARRAY_SIZE(resource_names), ivc->clks); 56 + if (ret) 57 + return dev_err_probe(ivc->dev, ret, "failed to acquire clks\n"); 58 + 59 + for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++) 60 + ivc->resets[i].id = resource_names[i]; 61 + 62 + ret = devm_reset_control_bulk_get_optional_shared(ivc->dev, 63 + ARRAY_SIZE(resource_names), 64 + ivc->resets); 65 + if (ret) 66 + return dev_err_probe(ivc->dev, ret, "failed to acquire resets\n"); 67 + 68 + return 0; 69 + } 70 + 71 + static void rzv2h_ivc_global_config(struct rzv2h_ivc *ivc) 72 + { 73 + /* Currently we only support single-exposure input */ 74 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PLNUM, RZV2H_IVC_ONE_EXPOSURE); 75 + 76 + /* 77 + * Datasheet says we should disable the interrupts before changing mode 78 + * to avoid spurious IFP interrupt. 79 + */ 80 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, 0x0); 81 + 82 + /* 83 + * RZ/V2H(P) documentation says software controlled single context mode 84 + * is not supported, and currently the driver does not support the 85 + * multi-context mode. That being so we just set single context sw-hw 86 + * mode. 87 + */ 88 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_CONTEXT, 89 + RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG); 90 + 91 + /* 92 + * We enable the frame end interrupt so that we know when we should send 93 + * follow-up frames. 94 + */ 95 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, RZV2H_IVC_VVAL_IFPE); 96 + } 97 + 98 + static irqreturn_t rzv2h_ivc_isr(int irq, void *context) 99 + { 100 + struct device *dev = context; 101 + struct rzv2h_ivc *ivc = dev_get_drvdata(dev); 102 + 103 + guard(spinlock)(&ivc->spinlock); 104 + 105 + /* IRQ should never be triggered before vvalid_ifp has been reset to 2 */ 106 + if (WARN_ON(!ivc->vvalid_ifp)) 107 + return IRQ_HANDLED; 108 + 109 + /* 110 + * The first interrupt indicates that the buffer transfer has been 111 + * completed. 112 + */ 113 + if (--ivc->vvalid_ifp) { 114 + rzv2h_ivc_buffer_done(ivc); 115 + return IRQ_HANDLED; 116 + } 117 + 118 + /* 119 + * The second interrupt indicates that the post-frame transfer VBLANK 120 + * has completed, we can now schedule a new frame transfer, if any. 121 + */ 122 + queue_work(ivc->buffers.async_wq, &ivc->buffers.work); 123 + 124 + return IRQ_HANDLED; 125 + } 126 + 127 + static int rzv2h_ivc_runtime_resume(struct device *dev) 128 + { 129 + struct rzv2h_ivc *ivc = dev_get_drvdata(dev); 130 + int ret; 131 + 132 + ret = clk_bulk_prepare_enable(ARRAY_SIZE(ivc->clks), ivc->clks); 133 + if (ret) { 134 + dev_err(ivc->dev, "failed to enable clocks\n"); 135 + return ret; 136 + } 137 + 138 + ret = reset_control_bulk_deassert(ARRAY_SIZE(ivc->resets), ivc->resets); 139 + if (ret) { 140 + dev_err(ivc->dev, "failed to deassert resets\n"); 141 + goto err_disable_clks; 142 + } 143 + 144 + rzv2h_ivc_global_config(ivc); 145 + 146 + ret = request_irq(ivc->irqnum, rzv2h_ivc_isr, 0, dev_driver_string(dev), 147 + dev); 148 + if (ret) { 149 + dev_err(dev, "failed to request irq\n"); 150 + goto err_assert_resets; 151 + } 152 + 153 + return 0; 154 + 155 + err_assert_resets: 156 + reset_control_bulk_assert(ARRAY_SIZE(ivc->resets), ivc->resets); 157 + err_disable_clks: 158 + clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks); 159 + 160 + return ret; 161 + } 162 + 163 + static int rzv2h_ivc_runtime_suspend(struct device *dev) 164 + { 165 + struct rzv2h_ivc *ivc = dev_get_drvdata(dev); 166 + 167 + reset_control_bulk_assert(ARRAY_SIZE(ivc->resets), ivc->resets); 168 + clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks); 169 + free_irq(ivc->irqnum, dev); 170 + 171 + return 0; 172 + } 173 + 174 + static const struct dev_pm_ops rzv2h_ivc_pm_ops = { 175 + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) 176 + RUNTIME_PM_OPS(rzv2h_ivc_runtime_suspend, rzv2h_ivc_runtime_resume, 177 + NULL) 178 + }; 179 + 180 + static int rzv2h_ivc_probe(struct platform_device *pdev) 181 + { 182 + struct device *dev = &pdev->dev; 183 + struct rzv2h_ivc *ivc; 184 + int ret; 185 + 186 + ivc = devm_kzalloc(dev, sizeof(*ivc), GFP_KERNEL); 187 + if (!ivc) 188 + return -ENOMEM; 189 + 190 + ivc->dev = dev; 191 + platform_set_drvdata(pdev, ivc); 192 + 193 + ret = devm_mutex_init(dev, &ivc->lock); 194 + if (ret) 195 + return ret; 196 + 197 + spin_lock_init(&ivc->spinlock); 198 + 199 + ret = rzv2h_ivc_get_hardware_resources(ivc, pdev); 200 + if (ret) 201 + return ret; 202 + 203 + pm_runtime_set_autosuspend_delay(dev, 2000); 204 + pm_runtime_use_autosuspend(dev); 205 + pm_runtime_enable(dev); 206 + 207 + ivc->irqnum = platform_get_irq(pdev, 0); 208 + if (ivc->irqnum < 0) 209 + return ivc->irqnum; 210 + 211 + ret = rzv2h_ivc_initialise_subdevice(ivc); 212 + if (ret) 213 + goto err_disable_pm_runtime; 214 + 215 + return 0; 216 + 217 + err_disable_pm_runtime: 218 + pm_runtime_disable(dev); 219 + 220 + return ret; 221 + } 222 + 223 + static void rzv2h_ivc_remove(struct platform_device *pdev) 224 + { 225 + struct rzv2h_ivc *ivc = platform_get_drvdata(pdev); 226 + 227 + rzv2h_deinit_video_dev_and_queue(ivc); 228 + rzv2h_ivc_deinit_subdevice(ivc); 229 + } 230 + 231 + static const struct of_device_id rzv2h_ivc_of_match[] = { 232 + { .compatible = "renesas,r9a09g057-ivc", }, 233 + { /* Sentinel */ } 234 + }; 235 + MODULE_DEVICE_TABLE(of, rzv2h_ivc_of_match); 236 + 237 + static struct platform_driver rzv2h_ivc_driver = { 238 + .driver = { 239 + .name = "rzv2h-ivc", 240 + .of_match_table = rzv2h_ivc_of_match, 241 + .pm = &rzv2h_ivc_pm_ops, 242 + }, 243 + .probe = rzv2h_ivc_probe, 244 + .remove = rzv2h_ivc_remove, 245 + }; 246 + 247 + module_platform_driver(rzv2h_ivc_driver); 248 + 249 + MODULE_AUTHOR("Daniel Scally <dan.scally@ideasonboard.com>"); 250 + MODULE_DESCRIPTION("Renesas RZ/V2H(P) Input Video Control Block driver"); 251 + MODULE_LICENSE("GPL");
+376
drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Renesas RZ/V2H(P) Input Video Control Block driver 4 + * 5 + * Copyright (C) 2025 Ideas on Board Oy 6 + */ 7 + 8 + #include "rzv2h-ivc.h" 9 + 10 + #include <linux/media.h> 11 + #include <linux/media-bus-format.h> 12 + #include <linux/v4l2-mediabus.h> 13 + 14 + #include <media/v4l2-async.h> 15 + #include <media/v4l2-ctrls.h> 16 + #include <media/v4l2-dev.h> 17 + #include <media/v4l2-event.h> 18 + 19 + #define RZV2H_IVC_N_INPUTS_PER_OUTPUT 6 20 + 21 + /* 22 + * We support 8/10/12/14/16/20 bit input in any bayer order, but the output 23 + * format is fixed at 20-bits with the same order as the input. 24 + */ 25 + static const struct { 26 + u32 inputs[RZV2H_IVC_N_INPUTS_PER_OUTPUT]; 27 + u32 output; 28 + } rzv2h_ivc_formats[] = { 29 + { 30 + .inputs = { 31 + MEDIA_BUS_FMT_SBGGR8_1X8, 32 + MEDIA_BUS_FMT_SBGGR10_1X10, 33 + MEDIA_BUS_FMT_SBGGR12_1X12, 34 + MEDIA_BUS_FMT_SBGGR14_1X14, 35 + MEDIA_BUS_FMT_SBGGR16_1X16, 36 + MEDIA_BUS_FMT_SBGGR20_1X20, 37 + }, 38 + .output = MEDIA_BUS_FMT_SBGGR20_1X20 39 + }, 40 + { 41 + .inputs = { 42 + MEDIA_BUS_FMT_SGBRG8_1X8, 43 + MEDIA_BUS_FMT_SGBRG10_1X10, 44 + MEDIA_BUS_FMT_SGBRG12_1X12, 45 + MEDIA_BUS_FMT_SGBRG14_1X14, 46 + MEDIA_BUS_FMT_SGBRG16_1X16, 47 + MEDIA_BUS_FMT_SGBRG20_1X20, 48 + }, 49 + .output = MEDIA_BUS_FMT_SGBRG20_1X20 50 + }, 51 + { 52 + .inputs = { 53 + MEDIA_BUS_FMT_SGRBG8_1X8, 54 + MEDIA_BUS_FMT_SGRBG10_1X10, 55 + MEDIA_BUS_FMT_SGRBG12_1X12, 56 + MEDIA_BUS_FMT_SGRBG14_1X14, 57 + MEDIA_BUS_FMT_SGRBG16_1X16, 58 + MEDIA_BUS_FMT_SGRBG20_1X20, 59 + }, 60 + .output = MEDIA_BUS_FMT_SGRBG20_1X20 61 + }, 62 + { 63 + .inputs = { 64 + MEDIA_BUS_FMT_SRGGB8_1X8, 65 + MEDIA_BUS_FMT_SRGGB10_1X10, 66 + MEDIA_BUS_FMT_SRGGB12_1X12, 67 + MEDIA_BUS_FMT_SRGGB14_1X14, 68 + MEDIA_BUS_FMT_SRGGB16_1X16, 69 + MEDIA_BUS_FMT_SRGGB20_1X20, 70 + }, 71 + .output = MEDIA_BUS_FMT_SRGGB20_1X20 72 + }, 73 + }; 74 + 75 + static u32 rzv2h_ivc_get_mbus_output_from_input(u32 mbus_code) 76 + { 77 + unsigned int i, j; 78 + 79 + for (i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++) { 80 + for (j = 0; j < RZV2H_IVC_N_INPUTS_PER_OUTPUT; j++) { 81 + if (rzv2h_ivc_formats[i].inputs[j] == mbus_code) 82 + return rzv2h_ivc_formats[i].output; 83 + } 84 + } 85 + 86 + return 0; 87 + } 88 + 89 + static int rzv2h_ivc_enum_mbus_code(struct v4l2_subdev *sd, 90 + struct v4l2_subdev_state *state, 91 + struct v4l2_subdev_mbus_code_enum *code) 92 + { 93 + const struct v4l2_mbus_framefmt *fmt; 94 + unsigned int order_index; 95 + unsigned int index; 96 + 97 + /* 98 + * On the source pad, only the 20-bit format corresponding to the sink 99 + * pad format's bayer order is supported. 100 + */ 101 + if (code->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) { 102 + if (code->index) 103 + return -EINVAL; 104 + 105 + fmt = v4l2_subdev_state_get_format(state, 106 + RZV2H_IVC_SUBDEV_SINK_PAD); 107 + code->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code); 108 + 109 + return 0; 110 + } 111 + 112 + if (code->index >= ARRAY_SIZE(rzv2h_ivc_formats) * 113 + RZV2H_IVC_N_INPUTS_PER_OUTPUT) 114 + return -EINVAL; 115 + 116 + order_index = code->index / RZV2H_IVC_N_INPUTS_PER_OUTPUT; 117 + index = code->index % RZV2H_IVC_N_INPUTS_PER_OUTPUT; 118 + 119 + code->code = rzv2h_ivc_formats[order_index].inputs[index]; 120 + 121 + return 0; 122 + } 123 + 124 + static int rzv2h_ivc_enum_frame_size(struct v4l2_subdev *sd, 125 + struct v4l2_subdev_state *state, 126 + struct v4l2_subdev_frame_size_enum *fse) 127 + { 128 + const struct v4l2_mbus_framefmt *fmt; 129 + 130 + if (fse->index > 0) 131 + return -EINVAL; 132 + 133 + if (fse->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) { 134 + fmt = v4l2_subdev_state_get_format(state, 135 + RZV2H_IVC_SUBDEV_SINK_PAD); 136 + 137 + if (fse->code != rzv2h_ivc_get_mbus_output_from_input(fmt->code)) 138 + return -EINVAL; 139 + 140 + fse->min_width = fmt->width; 141 + fse->max_width = fmt->width; 142 + fse->min_height = fmt->height; 143 + fse->max_height = fmt->height; 144 + 145 + return 0; 146 + } 147 + 148 + if (!rzv2h_ivc_get_mbus_output_from_input(fse->code)) 149 + return -EINVAL; 150 + 151 + fse->min_width = RZV2H_IVC_MIN_WIDTH; 152 + fse->max_width = RZV2H_IVC_MAX_WIDTH; 153 + fse->min_height = RZV2H_IVC_MIN_HEIGHT; 154 + fse->max_height = RZV2H_IVC_MAX_HEIGHT; 155 + 156 + return 0; 157 + } 158 + 159 + static int rzv2h_ivc_set_fmt(struct v4l2_subdev *sd, 160 + struct v4l2_subdev_state *state, 161 + struct v4l2_subdev_format *format) 162 + { 163 + struct v4l2_mbus_framefmt *fmt = &format->format; 164 + struct v4l2_mbus_framefmt *src_fmt, *sink_fmt; 165 + 166 + if (format->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) 167 + return v4l2_subdev_get_fmt(sd, state, format); 168 + 169 + sink_fmt = v4l2_subdev_state_get_format(state, 170 + RZV2H_IVC_SUBDEV_SINK_PAD); 171 + 172 + sink_fmt->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code) ? 173 + fmt->code : rzv2h_ivc_formats[0].inputs[0]; 174 + 175 + sink_fmt->width = clamp(fmt->width, RZV2H_IVC_MIN_WIDTH, 176 + RZV2H_IVC_MAX_WIDTH); 177 + sink_fmt->height = clamp(fmt->height, RZV2H_IVC_MIN_HEIGHT, 178 + RZV2H_IVC_MAX_HEIGHT); 179 + 180 + *fmt = *sink_fmt; 181 + 182 + src_fmt = v4l2_subdev_state_get_format(state, 183 + RZV2H_IVC_SUBDEV_SOURCE_PAD); 184 + *src_fmt = *sink_fmt; 185 + src_fmt->code = rzv2h_ivc_get_mbus_output_from_input(sink_fmt->code); 186 + 187 + return 0; 188 + } 189 + 190 + static int rzv2h_ivc_enable_streams(struct v4l2_subdev *sd, 191 + struct v4l2_subdev_state *state, u32 pad, 192 + u64 streams_mask) 193 + { 194 + /* 195 + * We have a single source pad, which has a single stream. V4L2 core has 196 + * already validated those things. The actual power-on and programming 197 + * of registers will be done through the video device's .vidioc_streamon 198 + * so there's nothing to actually do here... 199 + */ 200 + 201 + return 0; 202 + } 203 + 204 + static int rzv2h_ivc_disable_streams(struct v4l2_subdev *sd, 205 + struct v4l2_subdev_state *state, u32 pad, 206 + u64 streams_mask) 207 + { 208 + return 0; 209 + } 210 + 211 + static const struct v4l2_subdev_pad_ops rzv2h_ivc_pad_ops = { 212 + .enum_mbus_code = rzv2h_ivc_enum_mbus_code, 213 + .enum_frame_size = rzv2h_ivc_enum_frame_size, 214 + .get_fmt = v4l2_subdev_get_fmt, 215 + .set_fmt = rzv2h_ivc_set_fmt, 216 + .enable_streams = rzv2h_ivc_enable_streams, 217 + .disable_streams = rzv2h_ivc_disable_streams, 218 + }; 219 + 220 + static const struct v4l2_subdev_core_ops rzv2h_ivc_core_ops = { 221 + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, 222 + .unsubscribe_event = v4l2_event_subdev_unsubscribe, 223 + }; 224 + 225 + static const struct v4l2_subdev_ops rzv2h_ivc_subdev_ops = { 226 + .core = &rzv2h_ivc_core_ops, 227 + .pad = &rzv2h_ivc_pad_ops, 228 + }; 229 + 230 + static int rzv2h_ivc_init_state(struct v4l2_subdev *sd, 231 + struct v4l2_subdev_state *state) 232 + { 233 + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; 234 + 235 + sink_fmt = v4l2_subdev_state_get_format(state, 236 + RZV2H_IVC_SUBDEV_SINK_PAD); 237 + sink_fmt->width = RZV2H_IVC_DEFAULT_WIDTH; 238 + sink_fmt->height = RZV2H_IVC_DEFAULT_HEIGHT; 239 + sink_fmt->field = V4L2_FIELD_NONE; 240 + sink_fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16; 241 + sink_fmt->colorspace = V4L2_COLORSPACE_RAW; 242 + sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace); 243 + sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace); 244 + sink_fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, 245 + sink_fmt->colorspace, 246 + sink_fmt->ycbcr_enc); 247 + 248 + src_fmt = v4l2_subdev_state_get_format(state, 249 + RZV2H_IVC_SUBDEV_SOURCE_PAD); 250 + 251 + *src_fmt = *sink_fmt; 252 + src_fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20; 253 + 254 + return 0; 255 + } 256 + 257 + static int rzv2h_ivc_registered(struct v4l2_subdev *sd) 258 + { 259 + struct rzv2h_ivc *ivc = container_of(sd, struct rzv2h_ivc, subdev.sd); 260 + 261 + return rzv2h_ivc_init_vdev(ivc, sd->v4l2_dev); 262 + } 263 + 264 + static const struct v4l2_subdev_internal_ops rzv2h_ivc_subdev_internal_ops = { 265 + .init_state = rzv2h_ivc_init_state, 266 + .registered = rzv2h_ivc_registered, 267 + }; 268 + 269 + static int rzv2h_ivc_link_validate(struct media_link *link) 270 + { 271 + struct video_device *vdev = 272 + media_entity_to_video_device(link->source->entity); 273 + struct rzv2h_ivc *ivc = video_get_drvdata(vdev); 274 + struct v4l2_subdev *sd = 275 + media_entity_to_v4l2_subdev(link->sink->entity); 276 + const struct rzv2h_ivc_format *fmt; 277 + const struct v4l2_pix_format_mplane *pix; 278 + struct v4l2_subdev_state *state; 279 + struct v4l2_mbus_framefmt *mf; 280 + unsigned int i; 281 + int ret = 0; 282 + 283 + state = v4l2_subdev_lock_and_get_active_state(sd); 284 + mf = v4l2_subdev_state_get_format(state, link->sink->index); 285 + 286 + pix = &ivc->format.pix; 287 + fmt = ivc->format.fmt; 288 + 289 + if (mf->width != pix->width || mf->height != pix->height) { 290 + dev_dbg(ivc->dev, 291 + "link '%s':%u -> '%s':%u not valid: %ux%u != %ux%u\n", 292 + link->source->entity->name, link->source->index, 293 + link->sink->entity->name, link->sink->index, 294 + mf->width, mf->height, pix->width, pix->height); 295 + ret = -EPIPE; 296 + } 297 + 298 + for (i = 0; i < ARRAY_SIZE(fmt->mbus_codes); i++) 299 + if (mf->code == fmt->mbus_codes[i]) 300 + break; 301 + 302 + if (i == ARRAY_SIZE(fmt->mbus_codes)) { 303 + dev_dbg(ivc->dev, 304 + "link '%s':%u -> '%s':%u not valid: pixel format %p4cc cannot produce mbus_code 0x%04x\n", 305 + link->source->entity->name, link->source->index, 306 + link->sink->entity->name, link->sink->index, 307 + &pix->pixelformat, mf->code); 308 + ret = -EPIPE; 309 + } 310 + 311 + v4l2_subdev_unlock_state(state); 312 + 313 + return ret; 314 + } 315 + 316 + static const struct media_entity_operations rzv2h_ivc_media_ops = { 317 + .link_validate = rzv2h_ivc_link_validate, 318 + }; 319 + 320 + int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc) 321 + { 322 + struct v4l2_subdev *sd; 323 + int ret; 324 + 325 + /* Initialise subdevice */ 326 + sd = &ivc->subdev.sd; 327 + sd->dev = ivc->dev; 328 + v4l2_subdev_init(sd, &rzv2h_ivc_subdev_ops); 329 + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; 330 + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; 331 + sd->internal_ops = &rzv2h_ivc_subdev_internal_ops; 332 + sd->entity.ops = &rzv2h_ivc_media_ops; 333 + 334 + ivc->subdev.pads[RZV2H_IVC_SUBDEV_SINK_PAD].flags = MEDIA_PAD_FL_SINK; 335 + ivc->subdev.pads[RZV2H_IVC_SUBDEV_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE; 336 + 337 + snprintf(sd->name, sizeof(sd->name), "rzv2h ivc block"); 338 + 339 + ret = media_entity_pads_init(&sd->entity, RZV2H_IVC_NUM_SUBDEV_PADS, 340 + ivc->subdev.pads); 341 + if (ret) { 342 + dev_err(ivc->dev, "failed to initialise media entity\n"); 343 + return ret; 344 + } 345 + 346 + ret = v4l2_subdev_init_finalize(sd); 347 + if (ret) { 348 + dev_err(ivc->dev, "failed to finalize subdev init\n"); 349 + goto err_cleanup_subdev_entity; 350 + } 351 + 352 + ret = v4l2_async_register_subdev(sd); 353 + if (ret) { 354 + dev_err(ivc->dev, "failed to register subdevice\n"); 355 + goto err_cleanup_subdev; 356 + } 357 + 358 + return 0; 359 + 360 + err_cleanup_subdev: 361 + v4l2_subdev_cleanup(sd); 362 + err_cleanup_subdev_entity: 363 + media_entity_cleanup(&sd->entity); 364 + 365 + return ret; 366 + } 367 + 368 + void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc) 369 + { 370 + struct v4l2_subdev *sd = &ivc->subdev.sd; 371 + 372 + v4l2_subdev_cleanup(sd); 373 + media_entity_remove_links(&sd->entity); 374 + v4l2_async_unregister_subdev(sd); 375 + media_entity_cleanup(&sd->entity); 376 + }
+531
drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Renesas RZ/V2H(P) Input Video Control Block driver 4 + * 5 + * Copyright (C) 2025 Ideas on Board Oy 6 + */ 7 + 8 + #include "rzv2h-ivc.h" 9 + 10 + #include <linux/cleanup.h> 11 + #include <linux/iopoll.h> 12 + #include <linux/lockdep.h> 13 + #include <linux/media-bus-format.h> 14 + #include <linux/minmax.h> 15 + #include <linux/mutex.h> 16 + #include <linux/pm_runtime.h> 17 + 18 + #include <media/mipi-csi2.h> 19 + #include <media/v4l2-ctrls.h> 20 + #include <media/v4l2-dev.h> 21 + #include <media/v4l2-event.h> 22 + #include <media/v4l2-fh.h> 23 + #include <media/v4l2-ioctl.h> 24 + #include <media/videobuf2-dma-contig.h> 25 + 26 + #define RZV2H_IVC_FIXED_HBLANK 0x20 27 + #define RZV2H_IVC_MIN_VBLANK(hts) max(0x1b, 15 + (120501 / (hts))) 28 + 29 + struct rzv2h_ivc_buf { 30 + struct vb2_v4l2_buffer vb; 31 + struct list_head queue; 32 + dma_addr_t addr; 33 + }; 34 + 35 + #define to_rzv2h_ivc_buf(vbuf) \ 36 + container_of(vbuf, struct rzv2h_ivc_buf, vb) 37 + 38 + static const struct rzv2h_ivc_format rzv2h_ivc_formats[] = { 39 + { 40 + .fourcc = V4L2_PIX_FMT_SBGGR8, 41 + .mbus_codes = { 42 + MEDIA_BUS_FMT_SBGGR8_1X8, 43 + }, 44 + .dtype = MIPI_CSI2_DT_RAW8, 45 + }, 46 + { 47 + .fourcc = V4L2_PIX_FMT_SGBRG8, 48 + .mbus_codes = { 49 + MEDIA_BUS_FMT_SGBRG8_1X8, 50 + }, 51 + .dtype = MIPI_CSI2_DT_RAW8, 52 + }, 53 + { 54 + .fourcc = V4L2_PIX_FMT_SGRBG8, 55 + .mbus_codes = { 56 + MEDIA_BUS_FMT_SGRBG8_1X8, 57 + }, 58 + .dtype = MIPI_CSI2_DT_RAW8, 59 + }, 60 + { 61 + .fourcc = V4L2_PIX_FMT_SRGGB8, 62 + .mbus_codes = { 63 + MEDIA_BUS_FMT_SRGGB8_1X8, 64 + }, 65 + .dtype = MIPI_CSI2_DT_RAW8, 66 + }, 67 + { 68 + .fourcc = V4L2_PIX_FMT_RAW_CRU10, 69 + .mbus_codes = { 70 + MEDIA_BUS_FMT_SBGGR10_1X10, 71 + MEDIA_BUS_FMT_SGBRG10_1X10, 72 + MEDIA_BUS_FMT_SGRBG10_1X10, 73 + MEDIA_BUS_FMT_SRGGB10_1X10 74 + }, 75 + .dtype = MIPI_CSI2_DT_RAW10, 76 + }, 77 + { 78 + .fourcc = V4L2_PIX_FMT_RAW_CRU12, 79 + .mbus_codes = { 80 + MEDIA_BUS_FMT_SBGGR12_1X12, 81 + MEDIA_BUS_FMT_SGBRG12_1X12, 82 + MEDIA_BUS_FMT_SGRBG12_1X12, 83 + MEDIA_BUS_FMT_SRGGB12_1X12 84 + }, 85 + .dtype = MIPI_CSI2_DT_RAW12, 86 + }, 87 + { 88 + .fourcc = V4L2_PIX_FMT_RAW_CRU14, 89 + .mbus_codes = { 90 + MEDIA_BUS_FMT_SBGGR14_1X14, 91 + MEDIA_BUS_FMT_SGBRG14_1X14, 92 + MEDIA_BUS_FMT_SGRBG14_1X14, 93 + MEDIA_BUS_FMT_SRGGB14_1X14 94 + }, 95 + .dtype = MIPI_CSI2_DT_RAW14, 96 + }, 97 + { 98 + .fourcc = V4L2_PIX_FMT_SBGGR16, 99 + .mbus_codes = { 100 + MEDIA_BUS_FMT_SBGGR16_1X16, 101 + }, 102 + .dtype = MIPI_CSI2_DT_RAW16, 103 + }, 104 + { 105 + .fourcc = V4L2_PIX_FMT_SGBRG16, 106 + .mbus_codes = { 107 + MEDIA_BUS_FMT_SGBRG16_1X16, 108 + }, 109 + .dtype = MIPI_CSI2_DT_RAW16, 110 + }, 111 + { 112 + .fourcc = V4L2_PIX_FMT_SGRBG16, 113 + .mbus_codes = { 114 + MEDIA_BUS_FMT_SGRBG16_1X16, 115 + }, 116 + .dtype = MIPI_CSI2_DT_RAW16, 117 + }, 118 + { 119 + .fourcc = V4L2_PIX_FMT_SRGGB16, 120 + .mbus_codes = { 121 + MEDIA_BUS_FMT_SRGGB16_1X16, 122 + }, 123 + .dtype = MIPI_CSI2_DT_RAW16, 124 + }, 125 + }; 126 + 127 + void rzv2h_ivc_buffer_done(struct rzv2h_ivc *ivc) 128 + { 129 + struct rzv2h_ivc_buf *buf; 130 + 131 + lockdep_assert_in_irq(); 132 + 133 + scoped_guard(spinlock, &ivc->buffers.lock) { 134 + if (!ivc->buffers.curr) 135 + return; 136 + 137 + buf = ivc->buffers.curr; 138 + ivc->buffers.curr = NULL; 139 + } 140 + 141 + buf->vb.sequence = ivc->buffers.sequence++; 142 + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); 143 + } 144 + 145 + static void rzv2h_ivc_transfer_buffer(struct work_struct *work) 146 + { 147 + struct rzv2h_ivc *ivc = container_of(work, struct rzv2h_ivc, 148 + buffers.work); 149 + struct rzv2h_ivc_buf *buf; 150 + 151 + /* Setup buffers */ 152 + scoped_guard(spinlock_irqsave, &ivc->buffers.lock) { 153 + buf = list_first_entry_or_null(&ivc->buffers.queue, 154 + struct rzv2h_ivc_buf, queue); 155 + } 156 + 157 + if (!buf) 158 + return; 159 + 160 + list_del(&buf->queue); 161 + 162 + ivc->buffers.curr = buf; 163 + buf->addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); 164 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_SADDL_P0, buf->addr); 165 + 166 + scoped_guard(spinlock_irqsave, &ivc->spinlock) { 167 + ivc->vvalid_ifp = 2; 168 + } 169 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_FRCON, 0x1); 170 + } 171 + 172 + static int rzv2h_ivc_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, 173 + unsigned int *num_planes, unsigned int sizes[], 174 + struct device *alloc_devs[]) 175 + { 176 + struct rzv2h_ivc *ivc = vb2_get_drv_priv(q); 177 + 178 + if (*num_planes && *num_planes > 1) 179 + return -EINVAL; 180 + 181 + if (sizes[0] && sizes[0] < ivc->format.pix.plane_fmt[0].sizeimage) 182 + return -EINVAL; 183 + 184 + *num_planes = 1; 185 + 186 + if (!sizes[0]) 187 + sizes[0] = ivc->format.pix.plane_fmt[0].sizeimage; 188 + 189 + return 0; 190 + } 191 + 192 + static void rzv2h_ivc_buf_queue(struct vb2_buffer *vb) 193 + { 194 + struct rzv2h_ivc *ivc = vb2_get_drv_priv(vb->vb2_queue); 195 + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); 196 + struct rzv2h_ivc_buf *buf = to_rzv2h_ivc_buf(vbuf); 197 + 198 + scoped_guard(spinlock_irq, &ivc->buffers.lock) { 199 + list_add_tail(&buf->queue, &ivc->buffers.queue); 200 + } 201 + 202 + scoped_guard(spinlock_irq, &ivc->spinlock) { 203 + if (vb2_is_streaming(vb->vb2_queue) && !ivc->vvalid_ifp) 204 + queue_work(ivc->buffers.async_wq, &ivc->buffers.work); 205 + } 206 + } 207 + 208 + static void rzv2h_ivc_format_configure(struct rzv2h_ivc *ivc) 209 + { 210 + const struct rzv2h_ivc_format *fmt = ivc->format.fmt; 211 + struct v4l2_pix_format_mplane *pix = &ivc->format.pix; 212 + unsigned int vblank; 213 + unsigned int hts; 214 + 215 + /* Currently only CRU packed pixel formats are supported */ 216 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PXFMT, 217 + RZV2H_IVC_INPUT_FMT_CRU_PACKED); 218 + 219 + rzv2h_ivc_update_bits(ivc, RZV2H_IVC_REG_AXIRX_PXFMT, 220 + RZV2H_IVC_PXFMT_DTYPE, fmt->dtype); 221 + 222 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_HSIZE, pix->width); 223 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_VSIZE, pix->height); 224 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_STRD, 225 + pix->plane_fmt[0].bytesperline); 226 + 227 + /* 228 + * The ISP has minimum vertical blanking requirements that must be 229 + * adhered to by the IVC. The minimum is a function of the Iridix blocks 230 + * clocking requirements and the width of the image and horizontal 231 + * blanking, but if we assume the worst case then it boils down to the 232 + * below (plus one to the numerator to ensure the answer is rounded up) 233 + */ 234 + 235 + hts = pix->width + RZV2H_IVC_FIXED_HBLANK; 236 + vblank = RZV2H_IVC_MIN_VBLANK(hts); 237 + 238 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_BLANK, 239 + RZV2H_IVC_VBLANK(vblank)); 240 + } 241 + 242 + static void rzv2h_ivc_return_buffers(struct rzv2h_ivc *ivc, 243 + enum vb2_buffer_state state) 244 + { 245 + struct rzv2h_ivc_buf *buf, *tmp; 246 + 247 + guard(spinlock_irqsave)(&ivc->buffers.lock); 248 + 249 + if (ivc->buffers.curr) { 250 + vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf, state); 251 + ivc->buffers.curr = NULL; 252 + } 253 + 254 + list_for_each_entry_safe(buf, tmp, &ivc->buffers.queue, queue) { 255 + list_del(&buf->queue); 256 + vb2_buffer_done(&buf->vb.vb2_buf, state); 257 + } 258 + } 259 + 260 + static int rzv2h_ivc_start_streaming(struct vb2_queue *q, unsigned int count) 261 + { 262 + struct rzv2h_ivc *ivc = vb2_get_drv_priv(q); 263 + int ret; 264 + 265 + ivc->buffers.sequence = 0; 266 + ivc->vvalid_ifp = 0; 267 + 268 + ret = pm_runtime_resume_and_get(ivc->dev); 269 + if (ret) 270 + goto err_return_buffers; 271 + 272 + ret = video_device_pipeline_alloc_start(&ivc->vdev.dev); 273 + if (ret) { 274 + dev_err(ivc->dev, "failed to start media pipeline\n"); 275 + goto err_pm_runtime_put; 276 + } 277 + 278 + rzv2h_ivc_format_configure(ivc); 279 + 280 + queue_work(ivc->buffers.async_wq, &ivc->buffers.work); 281 + 282 + return 0; 283 + 284 + err_pm_runtime_put: 285 + pm_runtime_put(ivc->dev); 286 + err_return_buffers: 287 + rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_QUEUED); 288 + 289 + return ret; 290 + } 291 + 292 + static void rzv2h_ivc_stop_streaming(struct vb2_queue *q) 293 + { 294 + struct rzv2h_ivc *ivc = vb2_get_drv_priv(q); 295 + u32 val = 0; 296 + 297 + rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_STOP, 0x1); 298 + readl_poll_timeout(ivc->base + RZV2H_IVC_REG_FM_STOP, 299 + val, !val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC); 300 + 301 + rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_ERROR); 302 + video_device_pipeline_stop(&ivc->vdev.dev); 303 + pm_runtime_put_autosuspend(ivc->dev); 304 + } 305 + 306 + static const struct vb2_ops rzv2h_ivc_vb2_ops = { 307 + .queue_setup = &rzv2h_ivc_queue_setup, 308 + .buf_queue = &rzv2h_ivc_buf_queue, 309 + .start_streaming = &rzv2h_ivc_start_streaming, 310 + .stop_streaming = &rzv2h_ivc_stop_streaming, 311 + }; 312 + 313 + static const struct rzv2h_ivc_format * 314 + rzv2h_ivc_format_from_pixelformat(u32 fourcc) 315 + { 316 + for (unsigned int i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++) 317 + if (fourcc == rzv2h_ivc_formats[i].fourcc) 318 + return &rzv2h_ivc_formats[i]; 319 + 320 + return &rzv2h_ivc_formats[0]; 321 + } 322 + 323 + static int rzv2h_ivc_enum_fmt_vid_out(struct file *file, void *fh, 324 + struct v4l2_fmtdesc *f) 325 + { 326 + if (f->index >= ARRAY_SIZE(rzv2h_ivc_formats)) 327 + return -EINVAL; 328 + 329 + f->pixelformat = rzv2h_ivc_formats[f->index].fourcc; 330 + return 0; 331 + } 332 + 333 + static int rzv2h_ivc_g_fmt_vid_out(struct file *file, void *fh, 334 + struct v4l2_format *f) 335 + { 336 + struct rzv2h_ivc *ivc = video_drvdata(file); 337 + 338 + f->fmt.pix_mp = ivc->format.pix; 339 + 340 + return 0; 341 + } 342 + 343 + static void rzv2h_ivc_try_fmt(struct v4l2_pix_format_mplane *pix, 344 + const struct rzv2h_ivc_format *fmt) 345 + { 346 + pix->pixelformat = fmt->fourcc; 347 + 348 + pix->width = clamp(pix->width, RZV2H_IVC_MIN_WIDTH, 349 + RZV2H_IVC_MAX_WIDTH); 350 + pix->height = clamp(pix->height, RZV2H_IVC_MIN_HEIGHT, 351 + RZV2H_IVC_MAX_HEIGHT); 352 + 353 + pix->field = V4L2_FIELD_NONE; 354 + pix->colorspace = V4L2_COLORSPACE_RAW; 355 + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); 356 + pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, 357 + pix->colorspace, 358 + pix->ycbcr_enc); 359 + 360 + v4l2_fill_pixfmt_mp(pix, pix->pixelformat, pix->width, pix->height); 361 + } 362 + 363 + static void rzv2h_ivc_set_format(struct rzv2h_ivc *ivc, 364 + struct v4l2_pix_format_mplane *pix) 365 + { 366 + const struct rzv2h_ivc_format *fmt; 367 + 368 + fmt = rzv2h_ivc_format_from_pixelformat(pix->pixelformat); 369 + 370 + rzv2h_ivc_try_fmt(pix, fmt); 371 + ivc->format.pix = *pix; 372 + ivc->format.fmt = fmt; 373 + } 374 + 375 + static int rzv2h_ivc_s_fmt_vid_out(struct file *file, void *fh, 376 + struct v4l2_format *f) 377 + { 378 + struct rzv2h_ivc *ivc = video_drvdata(file); 379 + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; 380 + 381 + if (vb2_is_busy(&ivc->vdev.vb2q)) 382 + return -EBUSY; 383 + 384 + rzv2h_ivc_set_format(ivc, pix); 385 + 386 + return 0; 387 + } 388 + 389 + static int rzv2h_ivc_try_fmt_vid_out(struct file *file, void *fh, 390 + struct v4l2_format *f) 391 + { 392 + const struct rzv2h_ivc_format *fmt; 393 + 394 + fmt = rzv2h_ivc_format_from_pixelformat(f->fmt.pix.pixelformat); 395 + rzv2h_ivc_try_fmt(&f->fmt.pix_mp, fmt); 396 + 397 + return 0; 398 + } 399 + 400 + static int rzv2h_ivc_querycap(struct file *file, void *fh, 401 + struct v4l2_capability *cap) 402 + { 403 + strscpy(cap->driver, "rzv2h-ivc", sizeof(cap->driver)); 404 + strscpy(cap->card, "Renesas Input Video Control", sizeof(cap->card)); 405 + 406 + return 0; 407 + } 408 + 409 + static const struct v4l2_ioctl_ops rzv2h_ivc_v4l2_ioctl_ops = { 410 + .vidioc_reqbufs = vb2_ioctl_reqbufs, 411 + .vidioc_querybuf = vb2_ioctl_querybuf, 412 + .vidioc_create_bufs = vb2_ioctl_create_bufs, 413 + .vidioc_qbuf = vb2_ioctl_qbuf, 414 + .vidioc_expbuf = vb2_ioctl_expbuf, 415 + .vidioc_dqbuf = vb2_ioctl_dqbuf, 416 + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, 417 + .vidioc_streamon = vb2_ioctl_streamon, 418 + .vidioc_streamoff = vb2_ioctl_streamoff, 419 + .vidioc_enum_fmt_vid_out = rzv2h_ivc_enum_fmt_vid_out, 420 + .vidioc_g_fmt_vid_out_mplane = rzv2h_ivc_g_fmt_vid_out, 421 + .vidioc_s_fmt_vid_out_mplane = rzv2h_ivc_s_fmt_vid_out, 422 + .vidioc_try_fmt_vid_out_mplane = rzv2h_ivc_try_fmt_vid_out, 423 + .vidioc_querycap = rzv2h_ivc_querycap, 424 + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 425 + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 426 + }; 427 + 428 + static const struct v4l2_file_operations rzv2h_ivc_v4l2_fops = { 429 + .owner = THIS_MODULE, 430 + .unlocked_ioctl = video_ioctl2, 431 + .open = v4l2_fh_open, 432 + .release = vb2_fop_release, 433 + .poll = vb2_fop_poll, 434 + .mmap = vb2_fop_mmap, 435 + }; 436 + 437 + int rzv2h_ivc_init_vdev(struct rzv2h_ivc *ivc, struct v4l2_device *v4l2_dev) 438 + { 439 + struct v4l2_pix_format_mplane pix = { }; 440 + struct video_device *vdev; 441 + struct vb2_queue *vb2q; 442 + int ret; 443 + 444 + spin_lock_init(&ivc->buffers.lock); 445 + INIT_LIST_HEAD(&ivc->buffers.queue); 446 + INIT_WORK(&ivc->buffers.work, rzv2h_ivc_transfer_buffer); 447 + 448 + ivc->buffers.async_wq = alloc_workqueue("rzv2h-ivc", 0, 0); 449 + if (!ivc->buffers.async_wq) 450 + return -EINVAL; 451 + 452 + /* Initialise vb2 queue */ 453 + vb2q = &ivc->vdev.vb2q; 454 + vb2q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; 455 + vb2q->io_modes = VB2_MMAP | VB2_DMABUF; 456 + vb2q->drv_priv = ivc; 457 + vb2q->mem_ops = &vb2_dma_contig_memops; 458 + vb2q->ops = &rzv2h_ivc_vb2_ops; 459 + vb2q->buf_struct_size = sizeof(struct rzv2h_ivc_buf); 460 + vb2q->min_queued_buffers = 0; 461 + vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; 462 + vb2q->lock = &ivc->lock; 463 + vb2q->dev = ivc->dev; 464 + 465 + ret = vb2_queue_init(vb2q); 466 + if (ret) { 467 + dev_err(ivc->dev, "vb2 queue init failed\n"); 468 + goto err_destroy_workqueue; 469 + } 470 + 471 + /* Initialise Video Device */ 472 + vdev = &ivc->vdev.dev; 473 + strscpy(vdev->name, "rzv2h-ivc", sizeof(vdev->name)); 474 + vdev->release = video_device_release_empty; 475 + vdev->fops = &rzv2h_ivc_v4l2_fops; 476 + vdev->ioctl_ops = &rzv2h_ivc_v4l2_ioctl_ops; 477 + vdev->lock = &ivc->lock; 478 + vdev->v4l2_dev = v4l2_dev; 479 + vdev->queue = vb2q; 480 + vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING; 481 + vdev->vfl_dir = VFL_DIR_TX; 482 + video_set_drvdata(vdev, ivc); 483 + 484 + pix.pixelformat = V4L2_PIX_FMT_SRGGB16; 485 + pix.width = RZV2H_IVC_DEFAULT_WIDTH; 486 + pix.height = RZV2H_IVC_DEFAULT_HEIGHT; 487 + rzv2h_ivc_set_format(ivc, &pix); 488 + 489 + ivc->vdev.pad.flags = MEDIA_PAD_FL_SOURCE; 490 + ret = media_entity_pads_init(&ivc->vdev.dev.entity, 1, &ivc->vdev.pad); 491 + if (ret) 492 + goto err_release_vb2q; 493 + 494 + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); 495 + if (ret) { 496 + dev_err(ivc->dev, "failed to register IVC video device\n"); 497 + goto err_cleanup_vdev_entity; 498 + } 499 + 500 + ret = media_create_pad_link(&vdev->entity, 0, &ivc->subdev.sd.entity, 501 + RZV2H_IVC_SUBDEV_SINK_PAD, 502 + MEDIA_LNK_FL_ENABLED | 503 + MEDIA_LNK_FL_IMMUTABLE); 504 + if (ret) { 505 + dev_err(ivc->dev, "failed to create media link\n"); 506 + goto err_unregister_vdev; 507 + } 508 + 509 + return 0; 510 + 511 + err_unregister_vdev: 512 + video_unregister_device(vdev); 513 + err_cleanup_vdev_entity: 514 + media_entity_cleanup(&vdev->entity); 515 + err_release_vb2q: 516 + vb2_queue_release(vb2q); 517 + err_destroy_workqueue: 518 + destroy_workqueue(ivc->buffers.async_wq); 519 + 520 + return ret; 521 + } 522 + 523 + void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc) 524 + { 525 + struct video_device *vdev = &ivc->vdev.dev; 526 + struct vb2_queue *vb2q = &ivc->vdev.vb2q; 527 + 528 + vb2_video_unregister_device(vdev); 529 + media_entity_cleanup(&vdev->entity); 530 + vb2_queue_release(vb2q); 531 + }
+130
drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Renesas RZ/V2H(P) Input Video Control Block driver 4 + * 5 + * Copyright (C) 2025 Ideas on Board Oy 6 + */ 7 + 8 + #include <linux/clk.h> 9 + #include <linux/list.h> 10 + #include <linux/mutex.h> 11 + #include <linux/reset.h> 12 + #include <linux/spinlock.h> 13 + #include <linux/types.h> 14 + #include <linux/videodev2.h> 15 + #include <linux/workqueue.h> 16 + 17 + #include <media/media-entity.h> 18 + #include <media/v4l2-device.h> 19 + #include <media/v4l2-subdev.h> 20 + #include <media/videobuf2-core.h> 21 + #include <media/videobuf2-v4l2.h> 22 + 23 + #define RZV2H_IVC_REG_AXIRX_PLNUM 0x0000 24 + #define RZV2H_IVC_ONE_EXPOSURE 0x00 25 + #define RZV2H_IVC_TWO_EXPOSURE 0x01 26 + #define RZV2H_IVC_REG_AXIRX_PXFMT 0x0004 27 + #define RZV2H_IVC_INPUT_FMT_MIPI (0 << 16) 28 + #define RZV2H_IVC_INPUT_FMT_CRU_PACKED BIT(16) 29 + #define RZV2H_IVC_PXFMT_DTYPE GENMASK(7, 0) 30 + #define RZV2H_IVC_REG_AXIRX_SADDL_P0 0x0010 31 + #define RZV2H_IVC_REG_AXIRX_SADDH_P0 0x0014 32 + #define RZV2H_IVC_REG_AXIRX_SADDL_P1 0x0018 33 + #define RZV2H_IVC_REG_AXIRX_SADDH_P1 0x001c 34 + #define RZV2H_IVC_REG_AXIRX_HSIZE 0x0020 35 + #define RZV2H_IVC_REG_AXIRX_VSIZE 0x0024 36 + #define RZV2H_IVC_REG_AXIRX_BLANK 0x0028 37 + #define RZV2H_IVC_VBLANK(x) ((x) << 16) 38 + #define RZV2H_IVC_REG_AXIRX_STRD 0x0030 39 + #define RZV2H_IVC_REG_AXIRX_ISSU 0x0040 40 + #define RZV2H_IVC_REG_AXIRX_ERACT 0x0048 41 + #define RZV2H_IVC_REG_FM_CONTEXT 0x0100 42 + #define RZV2H_IVC_SOFTWARE_CFG 0x00 43 + #define RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG BIT(0) 44 + #define RZV2H_IVC_MULTI_CONTEXT_SW_HW_CFG BIT(1) 45 + #define RZV2H_IVC_REG_FM_MCON 0x0104 46 + #define RZV2H_IVC_REG_FM_FRCON 0x0108 47 + #define RZV2H_IVC_REG_FM_STOP 0x010c 48 + #define RZV2H_IVC_REG_FM_INT_EN 0x0120 49 + #define RZV2H_IVC_VVAL_IFPE BIT(0) 50 + #define RZV2H_IVC_REG_FM_INT_STA 0x0124 51 + #define RZV2H_IVC_REG_AXIRX_FIFOCAP0 0x0208 52 + #define RZV2H_IVC_REG_CORE_CAPCON 0x020c 53 + #define RZV2H_IVC_REG_CORE_FIFOCAP0 0x0228 54 + #define RZV2H_IVC_REG_CORE_FIFOCAP1 0x022c 55 + 56 + #define RZV2H_IVC_MIN_WIDTH 640 57 + #define RZV2H_IVC_MAX_WIDTH 4096 58 + #define RZV2H_IVC_MIN_HEIGHT 480 59 + #define RZV2H_IVC_MAX_HEIGHT 4096 60 + #define RZV2H_IVC_DEFAULT_WIDTH 1920 61 + #define RZV2H_IVC_DEFAULT_HEIGHT 1080 62 + 63 + #define RZV2H_IVC_NUM_HW_RESOURCES 3 64 + 65 + struct device; 66 + 67 + enum rzv2h_ivc_subdev_pads { 68 + RZV2H_IVC_SUBDEV_SINK_PAD, 69 + RZV2H_IVC_SUBDEV_SOURCE_PAD, 70 + RZV2H_IVC_NUM_SUBDEV_PADS 71 + }; 72 + 73 + struct rzv2h_ivc_format { 74 + u32 fourcc; 75 + /* 76 + * The CRU packed pixel formats are bayer-order agnostic, so each could 77 + * support any one of the 4 possible media bus formats. 78 + */ 79 + u32 mbus_codes[4]; 80 + u8 dtype; 81 + }; 82 + 83 + struct rzv2h_ivc { 84 + struct device *dev; 85 + void __iomem *base; 86 + struct clk_bulk_data clks[RZV2H_IVC_NUM_HW_RESOURCES]; 87 + struct reset_control_bulk_data resets[RZV2H_IVC_NUM_HW_RESOURCES]; 88 + int irqnum; 89 + u8 vvalid_ifp; 90 + 91 + struct { 92 + struct video_device dev; 93 + struct vb2_queue vb2q; 94 + struct media_pad pad; 95 + } vdev; 96 + 97 + struct { 98 + struct v4l2_subdev sd; 99 + struct media_pad pads[RZV2H_IVC_NUM_SUBDEV_PADS]; 100 + } subdev; 101 + 102 + struct { 103 + /* Spinlock to guard buffer queue */ 104 + spinlock_t lock; 105 + struct workqueue_struct *async_wq; 106 + struct work_struct work; 107 + struct list_head queue; 108 + struct rzv2h_ivc_buf *curr; 109 + unsigned int sequence; 110 + } buffers; 111 + 112 + struct { 113 + struct v4l2_pix_format_mplane pix; 114 + const struct rzv2h_ivc_format *fmt; 115 + } format; 116 + 117 + /* Mutex to provide to vb2 */ 118 + struct mutex lock; 119 + /* Lock to protect the interrupt counter */ 120 + spinlock_t spinlock; 121 + }; 122 + 123 + int rzv2h_ivc_init_vdev(struct rzv2h_ivc *ivc, struct v4l2_device *v4l2_dev); 124 + void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc); 125 + void rzv2h_ivc_buffer_done(struct rzv2h_ivc *ivc); 126 + int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc); 127 + void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc); 128 + void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val); 129 + void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr, 130 + u32 mask, u32 val);