File Index
Files and directories
Sign in
FILE

rzv2h-ivc-subdev.c

File preview with syntax highlighting.

linux-6.19.14/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c 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 */
 25static 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
 75static 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
 89static 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
124static 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
159static 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
190static 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
204static 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
211static 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
220static 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
225static const struct v4l2_subdev_ops rzv2h_ivc_subdev_ops = {
226	.core	= &rzv2h_ivc_core_ops,
227	.pad	= &rzv2h_ivc_pad_ops,
228};
229
230static 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
257static 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
264static 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
269static 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
316static const struct media_entity_operations rzv2h_ivc_media_ops = {
317	.link_validate = rzv2h_ivc_link_validate,
318};
319
320int 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
360err_cleanup_subdev:
361	v4l2_subdev_cleanup(sd);
362err_cleanup_subdev_entity:
363	media_entity_cleanup(&sd->entity);
364
365	return ret;
366}
367
368void 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}
~/ Read-only file preview.