xref: /linux/drivers/media/platform/arm/mali-c55/mali-c55-isp.c (revision 24f171c7e145f43b9f187578e89b0982ce87e54c)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * ARM Mali-C55 ISP Driver - Image signal processor
4  *
5  * Copyright (C) 2025 Ideas on Board Oy
6  */
7 
8 #include <linux/media/arm/mali-c55-config.h>
9 
10 #include <linux/delay.h>
11 #include <linux/iopoll.h>
12 #include <linux/property.h>
13 #include <linux/string.h>
14 
15 #include <uapi/linux/media/arm/mali-c55-config.h>
16 
17 #include <media/media-entity.h>
18 #include <media/v4l2-common.h>
19 #include <media/v4l2-ctrls.h>
20 #include <media/v4l2-event.h>
21 #include <media/v4l2-mc.h>
22 #include <media/v4l2-subdev.h>
23 
24 #include "mali-c55-common.h"
25 #include "mali-c55-registers.h"
26 
27 static const struct mali_c55_isp_format_info mali_c55_isp_fmts[] = {
28 	{
29 		.code = MEDIA_BUS_FMT_SRGGB20_1X20,
30 		.shifted_code = MEDIA_BUS_FMT_SRGGB16_1X16,
31 		.order = MALI_C55_BAYER_ORDER_RGGB,
32 		.bypass = false,
33 	},
34 	{
35 		.code = MEDIA_BUS_FMT_SGRBG20_1X20,
36 		.shifted_code = MEDIA_BUS_FMT_SGRBG16_1X16,
37 		.order = MALI_C55_BAYER_ORDER_GRBG,
38 		.bypass = false,
39 	},
40 	{
41 		.code = MEDIA_BUS_FMT_SGBRG20_1X20,
42 		.shifted_code = MEDIA_BUS_FMT_SGBRG16_1X16,
43 		.order = MALI_C55_BAYER_ORDER_GBRG,
44 		.bypass = false,
45 	},
46 	{
47 		.code = MEDIA_BUS_FMT_SBGGR20_1X20,
48 		.shifted_code = MEDIA_BUS_FMT_SBGGR16_1X16,
49 		.order = MALI_C55_BAYER_ORDER_BGGR,
50 		.bypass = false,
51 	},
52 	{
53 		.code = MEDIA_BUS_FMT_RGB202020_1X60,
54 		.shifted_code = 0, /* Not relevant for this format */
55 		.order = 0, /* Not relevant for this format */
56 		.bypass = true,
57 	}
58 	/*
59 	 * TODO: Support MEDIA_BUS_FMT_YUV20_1X60 here. This is so that we can
60 	 * also support YUV input from a sensor passed-through to the output. At
61 	 * present we have no mechanism to test that though so it may have to
62 	 * wait a while...
63 	 */
64 };
65 
66 const struct mali_c55_isp_format_info *
67 mali_c55_isp_get_mbus_config_by_index(u32 index)
68 {
69 	if (index < ARRAY_SIZE(mali_c55_isp_fmts))
70 		return &mali_c55_isp_fmts[index];
71 
72 	return NULL;
73 }
74 
75 const struct mali_c55_isp_format_info *
76 mali_c55_isp_get_mbus_config_by_code(u32 code)
77 {
78 	unsigned int i;
79 
80 	for (i = 0; i < ARRAY_SIZE(mali_c55_isp_fmts); i++) {
81 		if (mali_c55_isp_fmts[i].code == code)
82 			return &mali_c55_isp_fmts[i];
83 	}
84 
85 	return NULL;
86 }
87 
88 const struct mali_c55_isp_format_info *
89 mali_c55_isp_get_mbus_config_by_shifted_code(u32 code)
90 {
91 	unsigned int i;
92 
93 	for (i = 0; i < ARRAY_SIZE(mali_c55_isp_fmts); i++) {
94 		if (mali_c55_isp_fmts[i].shifted_code == code)
95 			return &mali_c55_isp_fmts[i];
96 	}
97 
98 	return NULL;
99 }
100 
101 static void mali_c55_isp_stop(struct mali_c55 *mali_c55)
102 {
103 	u32 val;
104 
105 	mali_c55_write(mali_c55, MALI_C55_REG_INPUT_MODE_REQUEST,
106 		       MALI_C55_INPUT_SAFE_STOP);
107 	readl_poll_timeout(mali_c55->base + MALI_C55_REG_MODE_STATUS,
108 			   val, !val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC);
109 }
110 
111 static int mali_c55_isp_start(struct mali_c55 *mali_c55,
112 			      const struct v4l2_subdev_state *state)
113 {
114 	struct mali_c55_context *ctx = mali_c55_get_active_context(mali_c55);
115 	const struct mali_c55_isp_format_info *cfg;
116 	const struct v4l2_mbus_framefmt *format;
117 	const struct v4l2_rect *crop;
118 	u32 val;
119 	int ret;
120 
121 	mali_c55_update_bits(mali_c55, MALI_C55_REG_MCU_CONFIG,
122 			     MALI_C55_REG_MCU_CONFIG_WRITE_MASK,
123 			     MALI_C55_REG_MCU_CONFIG_WRITE_PING);
124 
125 	/* Apply input windowing */
126 	crop = v4l2_subdev_state_get_crop(state, MALI_C55_ISP_PAD_SINK_VIDEO);
127 	format = v4l2_subdev_state_get_format(state,
128 					      MALI_C55_ISP_PAD_SINK_VIDEO);
129 	cfg = mali_c55_isp_get_mbus_config_by_code(format->code);
130 
131 	mali_c55_write(mali_c55, MALI_C55_REG_HC_START,
132 		       MALI_C55_HC_START(crop->left));
133 	mali_c55_write(mali_c55, MALI_C55_REG_HC_SIZE,
134 		       MALI_C55_HC_SIZE(crop->width));
135 	mali_c55_write(mali_c55, MALI_C55_REG_VC_START_SIZE,
136 		       MALI_C55_VC_START(crop->top) |
137 		       MALI_C55_VC_SIZE(crop->height));
138 	mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_BASE_ADDR,
139 				 MALI_C55_REG_ACTIVE_WIDTH_MASK, format->width);
140 	mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_BASE_ADDR,
141 				 MALI_C55_REG_ACTIVE_HEIGHT_MASK,
142 				 format->height << 16);
143 	mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_BAYER_ORDER,
144 				 MALI_C55_BAYER_ORDER_MASK, cfg->order);
145 	mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_INPUT_WIDTH,
146 				 MALI_C55_INPUT_WIDTH_MASK,
147 				 MALI_C55_INPUT_WIDTH_20BIT);
148 
149 	mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_ISP_RAW_BYPASS,
150 				 MALI_C55_ISP_RAW_BYPASS_BYPASS_MASK,
151 				 cfg->bypass ? MALI_C55_ISP_RAW_BYPASS_BYPASS_MASK :
152 					     0x00);
153 
154 	mali_c55_params_write_config(mali_c55);
155 	ret = mali_c55_config_write(ctx, MALI_C55_CONFIG_PING, true);
156 	if (ret) {
157 		dev_err(mali_c55->dev, "failed to write ISP config\n");
158 		return ret;
159 	}
160 
161 	mali_c55_write(mali_c55, MALI_C55_REG_INPUT_MODE_REQUEST,
162 		       MALI_C55_INPUT_SAFE_START);
163 
164 	ret = readl_poll_timeout(mali_c55->base + MALI_C55_REG_MODE_STATUS, val,
165 				 val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC);
166 	if (ret) {
167 		mali_c55_isp_stop(mali_c55);
168 		dev_err(mali_c55->dev, "timeout starting ISP\n");
169 		return ret;
170 	}
171 
172 	return 0;
173 }
174 
175 static int mali_c55_isp_enum_mbus_code(struct v4l2_subdev *sd,
176 				       struct v4l2_subdev_state *state,
177 				       struct v4l2_subdev_mbus_code_enum *code)
178 {
179 	/*
180 	 * Only the internal RGB processed format is allowed on the regular
181 	 * processing source pad.
182 	 */
183 	if (code->pad == MALI_C55_ISP_PAD_SOURCE_VIDEO) {
184 		if (code->index)
185 			return -EINVAL;
186 
187 		code->code = MEDIA_BUS_FMT_RGB121212_1X36;
188 		return 0;
189 	}
190 
191 	/* On the sink and bypass pads all the supported formats are allowed. */
192 	if (code->index >= ARRAY_SIZE(mali_c55_isp_fmts))
193 		return -EINVAL;
194 
195 	code->code = mali_c55_isp_fmts[code->index].code;
196 
197 	return 0;
198 }
199 
200 static int mali_c55_isp_enum_frame_size(struct v4l2_subdev *sd,
201 					struct v4l2_subdev_state *state,
202 					struct v4l2_subdev_frame_size_enum *fse)
203 {
204 	const struct mali_c55_isp_format_info *cfg;
205 
206 	if (fse->index > 0)
207 		return -EINVAL;
208 
209 	/*
210 	 * Only the internal RGB processed format is allowed on the regular
211 	 * processing source pad.
212 	 *
213 	 * On the sink and bypass pads all the supported formats are allowed.
214 	 */
215 	if (fse->pad == MALI_C55_ISP_PAD_SOURCE_VIDEO) {
216 		if (fse->code != MEDIA_BUS_FMT_RGB121212_1X36)
217 			return -EINVAL;
218 	} else {
219 		cfg = mali_c55_isp_get_mbus_config_by_code(fse->code);
220 		if (!cfg)
221 			return -EINVAL;
222 	}
223 
224 	fse->min_width = MALI_C55_MIN_WIDTH;
225 	fse->min_height = MALI_C55_MIN_HEIGHT;
226 	fse->max_width = MALI_C55_MAX_WIDTH;
227 	fse->max_height = MALI_C55_MAX_HEIGHT;
228 
229 	return 0;
230 }
231 
232 static int mali_c55_isp_set_fmt(struct v4l2_subdev *sd,
233 				struct v4l2_subdev_state *state,
234 				struct v4l2_subdev_format *format)
235 {
236 	struct v4l2_mbus_framefmt *fmt = &format->format;
237 	struct v4l2_mbus_framefmt *src_fmt, *sink_fmt;
238 	const struct mali_c55_isp_format_info *cfg;
239 	struct v4l2_rect *crop;
240 
241 	/*
242 	 * Disallow set_fmt on the source pads; format is fixed and the sizes
243 	 * are the result of applying the sink crop rectangle to the sink
244 	 * format.
245 	 */
246 	if (format->pad != MALI_C55_ISP_PAD_SINK_VIDEO)
247 		return v4l2_subdev_get_fmt(sd, state, format);
248 
249 	sink_fmt = v4l2_subdev_state_get_format(state,
250 						MALI_C55_ISP_PAD_SINK_VIDEO);
251 
252 	cfg = mali_c55_isp_get_mbus_config_by_code(fmt->code);
253 	sink_fmt->code = cfg ? fmt->code : MEDIA_BUS_FMT_SRGGB20_1X20;
254 
255 	/*
256 	 * Clamp sizes in the accepted limits and clamp the crop rectangle in
257 	 * the new sizes.
258 	 */
259 	sink_fmt->width = clamp(fmt->width, MALI_C55_MIN_WIDTH,
260 				MALI_C55_MAX_WIDTH);
261 	sink_fmt->height = clamp(fmt->height, MALI_C55_MIN_HEIGHT,
262 				 MALI_C55_MAX_HEIGHT);
263 
264 	*fmt = *sink_fmt;
265 
266 	crop = v4l2_subdev_state_get_crop(state, MALI_C55_ISP_PAD_SINK_VIDEO);
267 	crop->left = 0;
268 	crop->top = 0;
269 	crop->width = sink_fmt->width;
270 	crop->height = sink_fmt->height;
271 
272 	/*
273 	 * Propagate format to source pads. On the 'regular' output pad use
274 	 * the internal RGB processed format, while on the bypass pad simply
275 	 * replicate the ISP sink format. The sizes on both pads are the same as
276 	 * the ISP sink crop rectangle. The "field" and "colorspace" fields are
277 	 * set in .init_state() and fixed for both source pads, as is the "code"
278 	 * field for the processed data source pad.
279 	 */
280 	src_fmt = v4l2_subdev_state_get_format(state,
281 					       MALI_C55_ISP_PAD_SOURCE_VIDEO);
282 	src_fmt->width = crop->width;
283 	src_fmt->height = crop->height;
284 
285 	src_fmt = v4l2_subdev_state_get_format(state,
286 					       MALI_C55_ISP_PAD_SOURCE_BYPASS);
287 	src_fmt->code = sink_fmt->code;
288 	src_fmt->width = crop->width;
289 	src_fmt->height = crop->height;
290 
291 	return 0;
292 }
293 
294 static int mali_c55_isp_get_selection(struct v4l2_subdev *sd,
295 				      struct v4l2_subdev_state *state,
296 				      struct v4l2_subdev_selection *sel)
297 {
298 	if (sel->pad != MALI_C55_ISP_PAD_SINK_VIDEO ||
299 	    sel->target != V4L2_SEL_TGT_CROP)
300 		return -EINVAL;
301 
302 	sel->r = *v4l2_subdev_state_get_crop(state, MALI_C55_ISP_PAD_SINK_VIDEO);
303 
304 	return 0;
305 }
306 
307 static int mali_c55_isp_set_selection(struct v4l2_subdev *sd,
308 				      struct v4l2_subdev_state *state,
309 				      struct v4l2_subdev_selection *sel)
310 {
311 	struct v4l2_mbus_framefmt *src_fmt;
312 	const struct v4l2_mbus_framefmt *fmt;
313 	struct v4l2_rect *crop;
314 
315 	if (sel->pad != MALI_C55_ISP_PAD_SINK_VIDEO ||
316 	    sel->target != V4L2_SEL_TGT_CROP)
317 		return -EINVAL;
318 
319 	fmt = v4l2_subdev_state_get_format(state, MALI_C55_ISP_PAD_SINK_VIDEO);
320 
321 	sel->r.left = clamp_t(unsigned int, sel->r.left, 0, fmt->width);
322 	sel->r.top = clamp_t(unsigned int, sel->r.top, 0, fmt->height);
323 	sel->r.width = clamp_t(unsigned int, sel->r.width, MALI_C55_MIN_WIDTH,
324 			       fmt->width - sel->r.left);
325 	sel->r.height = clamp_t(unsigned int, sel->r.height,
326 				MALI_C55_MIN_HEIGHT,
327 				fmt->height - sel->r.top);
328 
329 	crop = v4l2_subdev_state_get_crop(state, MALI_C55_ISP_PAD_SINK_VIDEO);
330 	*crop = sel->r;
331 
332 	/*
333 	 * Propagate the crop rectangle sizes to the source pad format. The crop
334 	 * isn't propagated to the bypass source pad, because the bypassed data
335 	 * cannot be cropped.
336 	 */
337 	src_fmt = v4l2_subdev_state_get_format(state,
338 					       MALI_C55_ISP_PAD_SOURCE_VIDEO);
339 	src_fmt->width = crop->width;
340 	src_fmt->height = crop->height;
341 
342 	return 0;
343 }
344 
345 static int mali_c55_isp_enable_streams(struct v4l2_subdev *sd,
346 				       struct v4l2_subdev_state *state, u32 pad,
347 				       u64 streams_mask)
348 {
349 	struct mali_c55_isp *isp = container_of(sd, struct mali_c55_isp, sd);
350 	struct mali_c55 *mali_c55 = isp->mali_c55;
351 	struct v4l2_subdev *src_sd;
352 	struct media_pad *sink_pad;
353 	int ret;
354 
355 	/*
356 	 * We have two source pads, both of which have only a single stream. The
357 	 * core v4l2 code already validated those parameters so we can just get
358 	 * on with starting the ISP.
359 	 */
360 
361 	sink_pad = &isp->pads[MALI_C55_ISP_PAD_SINK_VIDEO];
362 	isp->remote_src = media_pad_remote_pad_unique(sink_pad);
363 	src_sd = media_entity_to_v4l2_subdev(isp->remote_src->entity);
364 
365 	isp->frame_sequence = 0;
366 	ret = mali_c55_isp_start(mali_c55, state);
367 	if (ret) {
368 		dev_err(mali_c55->dev, "Failed to start ISP\n");
369 		isp->remote_src = NULL;
370 		return ret;
371 	}
372 
373 	/*
374 	 * We only support a single input stream, so we can just enable the 1st
375 	 * entry in the streams mask.
376 	 */
377 	ret = v4l2_subdev_enable_streams(src_sd, isp->remote_src->index, BIT(0));
378 	if (ret) {
379 		dev_err(mali_c55->dev, "Failed to start ISP source\n");
380 		mali_c55_isp_stop(mali_c55);
381 		return ret;
382 	}
383 
384 	return 0;
385 }
386 
387 static int mali_c55_isp_disable_streams(struct v4l2_subdev *sd,
388 					struct v4l2_subdev_state *state, u32 pad,
389 					u64 streams_mask)
390 {
391 	struct mali_c55_isp *isp = container_of(sd, struct mali_c55_isp, sd);
392 	struct mali_c55 *mali_c55 = isp->mali_c55;
393 	struct v4l2_subdev *src_sd;
394 
395 	if (isp->remote_src) {
396 		src_sd = media_entity_to_v4l2_subdev(isp->remote_src->entity);
397 		v4l2_subdev_disable_streams(src_sd, isp->remote_src->index,
398 					    BIT(0));
399 	}
400 	isp->remote_src = NULL;
401 
402 	mali_c55_isp_stop(mali_c55);
403 
404 	return 0;
405 }
406 
407 static const struct v4l2_subdev_pad_ops mali_c55_isp_pad_ops = {
408 	.enum_mbus_code		= mali_c55_isp_enum_mbus_code,
409 	.enum_frame_size	= mali_c55_isp_enum_frame_size,
410 	.get_fmt		= v4l2_subdev_get_fmt,
411 	.set_fmt		= mali_c55_isp_set_fmt,
412 	.get_selection		= mali_c55_isp_get_selection,
413 	.set_selection		= mali_c55_isp_set_selection,
414 	.link_validate		= v4l2_subdev_link_validate_default,
415 	.enable_streams		= mali_c55_isp_enable_streams,
416 	.disable_streams	= mali_c55_isp_disable_streams,
417 };
418 
419 void mali_c55_isp_queue_event_sof(struct mali_c55 *mali_c55)
420 {
421 	struct v4l2_event event = {
422 		.type = V4L2_EVENT_FRAME_SYNC,
423 	};
424 
425 	event.u.frame_sync.frame_sequence = mali_c55->isp.frame_sequence;
426 	v4l2_event_queue(mali_c55->isp.sd.devnode, &event);
427 }
428 
429 static int
430 mali_c55_isp_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
431 			     struct v4l2_event_subscription *sub)
432 {
433 	switch (sub->type) {
434 	case V4L2_EVENT_FRAME_SYNC:
435 		return v4l2_event_subscribe(fh, sub, 0, NULL);
436 	case V4L2_EVENT_CTRL:
437 		return v4l2_ctrl_subscribe_event(fh, sub);
438 	default:
439 		return -EINVAL;
440 	}
441 }
442 
443 static const struct v4l2_subdev_core_ops mali_c55_isp_core_ops = {
444 	.subscribe_event = mali_c55_isp_subscribe_event,
445 	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
446 };
447 
448 static const struct v4l2_subdev_ops mali_c55_isp_ops = {
449 	.pad	= &mali_c55_isp_pad_ops,
450 	.core	= &mali_c55_isp_core_ops,
451 };
452 
453 static int mali_c55_isp_init_state(struct v4l2_subdev *sd,
454 				   struct v4l2_subdev_state *state)
455 {
456 	struct v4l2_mbus_framefmt *sink_fmt, *src_fmt;
457 	struct v4l2_rect *in_crop;
458 
459 	sink_fmt = v4l2_subdev_state_get_format(state,
460 						MALI_C55_ISP_PAD_SINK_VIDEO);
461 	src_fmt = v4l2_subdev_state_get_format(state,
462 					       MALI_C55_ISP_PAD_SOURCE_VIDEO);
463 	in_crop = v4l2_subdev_state_get_crop(state,
464 					     MALI_C55_ISP_PAD_SINK_VIDEO);
465 
466 	sink_fmt->width = MALI_C55_DEFAULT_WIDTH;
467 	sink_fmt->height = MALI_C55_DEFAULT_HEIGHT;
468 	sink_fmt->field = V4L2_FIELD_NONE;
469 	sink_fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
470 	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
471 	sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
472 	sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
473 	sink_fmt->quantization =
474 		V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
475 					      sink_fmt->ycbcr_enc);
476 
477 	*v4l2_subdev_state_get_format(state,
478 			      MALI_C55_ISP_PAD_SOURCE_BYPASS) = *sink_fmt;
479 
480 	src_fmt->width = MALI_C55_DEFAULT_WIDTH;
481 	src_fmt->height = MALI_C55_DEFAULT_HEIGHT;
482 	src_fmt->field = V4L2_FIELD_NONE;
483 	src_fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
484 	src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
485 	src_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
486 	src_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
487 	src_fmt->quantization =
488 		V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace,
489 					      sink_fmt->ycbcr_enc);
490 
491 	in_crop->top = 0;
492 	in_crop->left = 0;
493 	in_crop->width = MALI_C55_DEFAULT_WIDTH;
494 	in_crop->height = MALI_C55_DEFAULT_HEIGHT;
495 
496 	src_fmt = v4l2_subdev_state_get_format(state,
497 					       MALI_C55_ISP_PAD_SOURCE_STATS);
498 	sink_fmt = v4l2_subdev_state_get_format(state,
499 						MALI_C55_ISP_PAD_SINK_PARAMS);
500 
501 	src_fmt->width = 0;
502 	src_fmt->height = 0;
503 	src_fmt->field = V4L2_FIELD_NONE;
504 	src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
505 
506 	sink_fmt->width = 0;
507 	sink_fmt->height = 0;
508 	sink_fmt->field = V4L2_FIELD_NONE;
509 	sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED;
510 
511 	return 0;
512 }
513 
514 static const struct v4l2_subdev_internal_ops mali_c55_isp_internal_ops = {
515 	.init_state = mali_c55_isp_init_state,
516 };
517 
518 static int mali_c55_subdev_link_validate(struct media_link *link)
519 {
520 	/*
521 	 * Skip validation for the parameters sink pad, as the source is not
522 	 * a subdevice.
523 	 */
524 	if (link->sink->index == MALI_C55_ISP_PAD_SINK_PARAMS)
525 		return 0;
526 
527 	return v4l2_subdev_link_validate(link);
528 }
529 
530 static const struct media_entity_operations mali_c55_isp_media_ops = {
531 	.link_validate		= mali_c55_subdev_link_validate,
532 };
533 
534 static int mali_c55_isp_s_ctrl(struct v4l2_ctrl *ctrl)
535 {
536 	/*
537 	 * .s_ctrl() is a mandatory operation, but the driver has only a single
538 	 * read only control. If we got here, something went badly wrong.
539 	 */
540 	return -EINVAL;
541 }
542 
543 static const struct v4l2_ctrl_ops mali_c55_isp_ctrl_ops = {
544 	.s_ctrl = mali_c55_isp_s_ctrl,
545 };
546 
547 /* NOT const because the default needs to be filled in at runtime */
548 static struct v4l2_ctrl_config mali_c55_isp_v4l2_custom_ctrls[] = {
549 	{
550 		.ops = &mali_c55_isp_ctrl_ops,
551 		.id = V4L2_CID_MALI_C55_CAPABILITIES,
552 		.name = "Mali-C55 ISP Capabilities",
553 		.type = V4L2_CTRL_TYPE_BITMASK,
554 		.min = 0,
555 		.max = MALI_C55_GPS_PONG_FITTED |
556 		       MALI_C55_GPS_WDR_FITTED |
557 		       MALI_C55_GPS_COMPRESSION_FITTED |
558 		       MALI_C55_GPS_TEMPER_FITTED |
559 		       MALI_C55_GPS_SINTER_LITE_FITTED |
560 		       MALI_C55_GPS_SINTER_FITTED |
561 		       MALI_C55_GPS_IRIDIX_LTM_FITTED |
562 		       MALI_C55_GPS_IRIDIX_GTM_FITTED |
563 		       MALI_C55_GPS_CNR_FITTED |
564 		       MALI_C55_GPS_FRSCALER_FITTED |
565 		       MALI_C55_GPS_DS_PIPE_FITTED,
566 		.def = 0,
567 	},
568 };
569 
570 static int mali_c55_isp_init_controls(struct mali_c55 *mali_c55)
571 {
572 	struct v4l2_ctrl_handler *handler = &mali_c55->isp.handler;
573 	struct v4l2_ctrl *capabilities;
574 	int ret;
575 
576 	ret = v4l2_ctrl_handler_init(handler, 1);
577 	if (ret)
578 		return ret;
579 
580 	mali_c55_isp_v4l2_custom_ctrls[0].def = mali_c55->capabilities;
581 
582 	capabilities = v4l2_ctrl_new_custom(handler,
583 					    &mali_c55_isp_v4l2_custom_ctrls[0],
584 					    NULL);
585 	if (capabilities)
586 		capabilities->flags |= V4L2_CTRL_FLAG_READ_ONLY;
587 
588 	if (handler->error) {
589 		dev_err(mali_c55->dev, "failed to register capabilities control\n");
590 		ret = handler->error;
591 		v4l2_ctrl_handler_free(handler);
592 		return ret;
593 	}
594 
595 	mali_c55->isp.sd.ctrl_handler = handler;
596 
597 	return 0;
598 }
599 
600 int mali_c55_register_isp(struct mali_c55 *mali_c55)
601 {
602 	struct mali_c55_isp *isp = &mali_c55->isp;
603 	struct v4l2_subdev *sd = &isp->sd;
604 	int ret;
605 
606 	isp->mali_c55 = mali_c55;
607 
608 	v4l2_subdev_init(sd, &mali_c55_isp_ops);
609 	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
610 	sd->entity.ops = &mali_c55_isp_media_ops;
611 	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP;
612 	sd->internal_ops = &mali_c55_isp_internal_ops;
613 	strscpy(sd->name, MALI_C55_DRIVER_NAME " isp", sizeof(sd->name));
614 
615 	isp->pads[MALI_C55_ISP_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK |
616 						       MEDIA_PAD_FL_MUST_CONNECT;
617 	isp->pads[MALI_C55_ISP_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
618 	isp->pads[MALI_C55_ISP_PAD_SOURCE_BYPASS].flags = MEDIA_PAD_FL_SOURCE;
619 	isp->pads[MALI_C55_ISP_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
620 	isp->pads[MALI_C55_ISP_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
621 
622 	ret = media_entity_pads_init(&sd->entity, MALI_C55_ISP_NUM_PADS,
623 				     isp->pads);
624 	if (ret)
625 		return ret;
626 
627 	ret = mali_c55_isp_init_controls(mali_c55);
628 	if (ret)
629 		goto err_cleanup_media_entity;
630 
631 	ret = v4l2_subdev_init_finalize(sd);
632 	if (ret)
633 		goto err_free_ctrl_handler;
634 
635 	ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd);
636 	if (ret)
637 		goto err_cleanup_subdev;
638 
639 	mutex_init(&isp->capture_lock);
640 
641 	return 0;
642 
643 err_cleanup_subdev:
644 	v4l2_subdev_cleanup(sd);
645 err_free_ctrl_handler:
646 	v4l2_ctrl_handler_free(&isp->handler);
647 err_cleanup_media_entity:
648 	media_entity_cleanup(&sd->entity);
649 	isp->mali_c55 = NULL;
650 
651 	return ret;
652 }
653 
654 void mali_c55_unregister_isp(struct mali_c55 *mali_c55)
655 {
656 	struct mali_c55_isp *isp = &mali_c55->isp;
657 
658 	if (!isp->mali_c55)
659 		return;
660 
661 	mutex_destroy(&isp->capture_lock);
662 	v4l2_device_unregister_subdev(&isp->sd);
663 	v4l2_subdev_cleanup(&isp->sd);
664 	media_entity_cleanup(&isp->sd.entity);
665 }
666