File Index
Files and directories
Sign in
FILE

mali-c55-isp.c

File preview with syntax highlighting.

linux-6.19.14/drivers/media/platform/arm/mali-c55/mali-c55-isp.c C
  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
 27static 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
 66const struct mali_c55_isp_format_info *
 67mali_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
 75const struct mali_c55_isp_format_info *
 76mali_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
 88const struct mali_c55_isp_format_info *
 89mali_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
101static 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
111static 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
175static 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
200static 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
232static 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
294static 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
307static 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
345static 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
387static 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
407static 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
419void 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
429static int
430mali_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
443static 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
448static 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
453static 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
514static const struct v4l2_subdev_internal_ops mali_c55_isp_internal_ops = {
515	.init_state = mali_c55_isp_init_state,
516};
517
518static 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
530static const struct media_entity_operations mali_c55_isp_media_ops = {
531	.link_validate		= mali_c55_subdev_link_validate,
532};
533
534static 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
543static 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 */
548static 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
570static 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
600int 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
643err_cleanup_subdev:
644	v4l2_subdev_cleanup(sd);
645err_free_ctrl_handler:
646	v4l2_ctrl_handler_free(&isp->handler);
647err_cleanup_media_entity:
648	media_entity_cleanup(&sd->entity);
649	isp->mali_c55 = NULL;
650
651	return ret;
652}
653
654void 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}
~/ Read-only file preview.