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