1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * ARM Mali-C55 ISP Driver - Test pattern generator 4 * 5 * Copyright (C) 2025 Ideas on Board Oy 6 */ 7 8 #include <linux/minmax.h> 9 #include <linux/pm_runtime.h> 10 #include <linux/string.h> 11 12 #include <media/media-entity.h> 13 #include <media/v4l2-ctrls.h> 14 #include <media/v4l2-event.h> 15 #include <media/v4l2-subdev.h> 16 17 #include "mali-c55-common.h" 18 #include "mali-c55-registers.h" 19 20 #define MALI_C55_TPG_SRC_PAD 0 21 #define MALI_C55_TPG_FIXED_HBLANK 0x20 22 #define MALI_C55_TPG_DEFAULT_MIN_VBLANK 66 23 #define MALI_C55_TPG_DEFAULT_DEF_VBLANK 626 24 #define MALI_C55_TPG_MAX_VBLANK 0xffff 25 #define MALI_C55_TPG_PIXEL_RATE 100000000 26 27 static const char * const mali_c55_tpg_test_pattern_menu[] = { 28 "Flat field", 29 "Horizontal gradient", 30 "Vertical gradient", 31 "Vertical bars", 32 "Arbitrary rectangle", 33 "White frame on black field" 34 }; 35 36 static const u32 mali_c55_tpg_mbus_codes[] = { 37 MEDIA_BUS_FMT_SRGGB20_1X20, 38 MEDIA_BUS_FMT_RGB202020_1X60, 39 }; 40 41 static void mali_c55_tpg_update_vblank(struct mali_c55_tpg *tpg, 42 struct v4l2_mbus_framefmt *format) 43 { 44 unsigned int def_vblank; 45 unsigned int min_vblank; 46 unsigned int hts; 47 int tgt_fps; 48 49 hts = format->width + MALI_C55_TPG_FIXED_HBLANK; 50 51 /* 52 * The ISP has minimum vertical blanking requirements that must be 53 * adhered to by the TPG. The minimum is a function of the Iridix blocks 54 * clocking requirements and the width of the image and horizontal 55 * blanking, but if we assume the worst case iVariance and sVariance 56 * values then it boils down to the below (plus one to the numerator to 57 * ensure the answer is rounded up). 58 */ 59 min_vblank = 15 + (120501 / hts); 60 61 /* 62 * We need to set a sensible default vblank for whatever format height 63 * we happen to be given from set_fmt(). This function just targets 64 * an even multiple of 15fps. If we can't get 15fps, let's target 5fps. 65 * If we can't get 5fps we'll take whatever the minimum vblank gives us. 66 */ 67 tgt_fps = MALI_C55_TPG_PIXEL_RATE / hts / (format->height + min_vblank); 68 69 if (tgt_fps < 5) 70 def_vblank = min_vblank; 71 else 72 def_vblank = (MALI_C55_TPG_PIXEL_RATE / hts 73 / max(rounddown(tgt_fps, 15), 5)) - format->height; 74 75 def_vblank = ALIGN_DOWN(def_vblank, 2); 76 77 __v4l2_ctrl_modify_range(tpg->ctrls.vblank, min_vblank, 78 MALI_C55_TPG_MAX_VBLANK, 1, def_vblank); 79 __v4l2_ctrl_s_ctrl(tpg->ctrls.vblank, def_vblank); 80 } 81 82 static int mali_c55_tpg_s_ctrl(struct v4l2_ctrl *ctrl) 83 { 84 struct mali_c55_tpg *tpg = container_of(ctrl->handler, 85 struct mali_c55_tpg, 86 ctrls.handler); 87 struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg); 88 int ret = 0; 89 90 if (!pm_runtime_get_if_in_use(mali_c55->dev)) 91 return 0; 92 93 switch (ctrl->id) { 94 case V4L2_CID_TEST_PATTERN: 95 mali_c55_ctx_write(mali_c55, 96 MALI_C55_REG_TEST_GEN_CH0_PATTERN_TYPE, 97 ctrl->val); 98 break; 99 case V4L2_CID_VBLANK: 100 mali_c55_update_bits(mali_c55, MALI_C55_REG_BLANKING, 101 MALI_C55_REG_VBLANK_MASK, 102 MALI_C55_VBLANK(ctrl->val)); 103 break; 104 default: 105 ret = -EINVAL; 106 break; 107 } 108 109 pm_runtime_put_autosuspend(mali_c55->dev); 110 111 return ret; 112 } 113 114 static const struct v4l2_ctrl_ops mali_c55_tpg_ctrl_ops = { 115 .s_ctrl = &mali_c55_tpg_s_ctrl, 116 }; 117 118 static void mali_c55_tpg_configure(struct mali_c55_tpg *tpg, 119 struct v4l2_subdev_state *state) 120 { 121 struct v4l2_mbus_framefmt *fmt; 122 u32 test_pattern_format; 123 124 /* 125 * hblank needs setting, but is a read-only control and thus won't be 126 * called during __v4l2_ctrl_handler_setup(). Do it here instead. 127 */ 128 mali_c55_update_bits(tpg->mali_c55, MALI_C55_REG_BLANKING, 129 MALI_C55_REG_HBLANK_MASK, 130 MALI_C55_TPG_FIXED_HBLANK); 131 mali_c55_update_bits(tpg->mali_c55, MALI_C55_REG_GEN_VIDEO, 132 MALI_C55_REG_GEN_VIDEO_MULTI_MASK, 133 MALI_C55_REG_GEN_VIDEO_MULTI_MASK); 134 135 fmt = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD); 136 137 test_pattern_format = fmt->code == MEDIA_BUS_FMT_RGB202020_1X60 ? 138 0x01 : 0x0; 139 140 mali_c55_ctx_update_bits(tpg->mali_c55, MALI_C55_REG_TPG_CH0, 141 MALI_C55_TEST_PATTERN_RGB_MASK, 142 MALI_C55_TEST_PATTERN_RGB(test_pattern_format)); 143 } 144 145 static int mali_c55_tpg_enum_mbus_code(struct v4l2_subdev *sd, 146 struct v4l2_subdev_state *state, 147 struct v4l2_subdev_mbus_code_enum *code) 148 { 149 if (code->index >= ARRAY_SIZE(mali_c55_tpg_mbus_codes)) 150 return -EINVAL; 151 152 code->code = mali_c55_tpg_mbus_codes[code->index]; 153 154 return 0; 155 } 156 157 static int mali_c55_tpg_enum_frame_size(struct v4l2_subdev *sd, 158 struct v4l2_subdev_state *state, 159 struct v4l2_subdev_frame_size_enum *fse) 160 { 161 unsigned int i; 162 163 if (fse->index > 0) 164 return -EINVAL; 165 166 for (i = 0; i < ARRAY_SIZE(mali_c55_tpg_mbus_codes); i++) { 167 if (fse->code == mali_c55_tpg_mbus_codes[i]) 168 break; 169 } 170 171 if (i == ARRAY_SIZE(mali_c55_tpg_mbus_codes)) 172 return -EINVAL; 173 174 fse->min_width = MALI_C55_MIN_WIDTH; 175 fse->max_width = MALI_C55_MAX_WIDTH; 176 fse->min_height = MALI_C55_MIN_HEIGHT; 177 fse->max_height = MALI_C55_MAX_HEIGHT; 178 179 return 0; 180 } 181 182 static int mali_c55_tpg_set_fmt(struct v4l2_subdev *sd, 183 struct v4l2_subdev_state *state, 184 struct v4l2_subdev_format *format) 185 { 186 struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd); 187 struct v4l2_mbus_framefmt *fmt; 188 unsigned int i; 189 190 fmt = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD); 191 fmt->code = format->format.code; 192 193 for (i = 0; i < ARRAY_SIZE(mali_c55_tpg_mbus_codes); i++) { 194 if (fmt->code == mali_c55_tpg_mbus_codes[i]) 195 break; 196 } 197 198 if (i == ARRAY_SIZE(mali_c55_tpg_mbus_codes)) 199 fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20; 200 201 /* 202 * The TPG says that the test frame timing generation logic expects a 203 * minimum framesize of 4x4 pixels, but given the rest of the ISP can't 204 * handle anything smaller than 128x128 it seems pointless to allow a 205 * smaller frame. 206 */ 207 fmt->width = clamp(format->format.width, MALI_C55_MIN_WIDTH, 208 MALI_C55_MAX_WIDTH); 209 fmt->height = clamp(format->format.height, MALI_C55_MIN_HEIGHT, 210 MALI_C55_MAX_HEIGHT); 211 212 format->format = *fmt; 213 214 if (format->which == V4L2_SUBDEV_FORMAT_TRY) 215 return 0; 216 217 mali_c55_tpg_update_vblank(tpg, fmt); 218 219 return 0; 220 } 221 222 static int mali_c55_tpg_enable_streams(struct v4l2_subdev *sd, 223 struct v4l2_subdev_state *state, u32 pad, 224 u64 streams_mask) 225 { 226 struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd); 227 struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg); 228 229 /* 230 * We only have a source pad and a single stream, and v4l2-core already 231 * validated both so we don't need to do that. One might reasonably 232 * expect the framesize to be set here given it's configurable in 233 * .set_fmt(), but it's done in the ISP subdevice's .enable_streams() 234 * instead, as the same register is also used to indicate the size of 235 * the data coming from the sensor. 236 */ 237 mali_c55_tpg_configure(tpg, state); 238 __v4l2_ctrl_handler_setup(sd->ctrl_handler); 239 240 mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_TPG_CH0, 241 MALI_C55_TEST_PATTERN_ON_OFF, 242 MALI_C55_TEST_PATTERN_ON_OFF); 243 mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO, 244 MALI_C55_REG_GEN_VIDEO_ON_MASK, 245 MALI_C55_REG_GEN_VIDEO_ON_MASK); 246 247 return 0; 248 } 249 250 static int mali_c55_tpg_disable_streams(struct v4l2_subdev *sd, 251 struct v4l2_subdev_state *state, u32 pad, 252 u64 streams_mask) 253 { 254 struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd); 255 struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg); 256 257 mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_TPG_CH0, 258 MALI_C55_TEST_PATTERN_ON_OFF, 0x00); 259 mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO, 260 MALI_C55_REG_GEN_VIDEO_ON_MASK, 0x00); 261 262 return 0; 263 } 264 265 static const struct v4l2_subdev_pad_ops mali_c55_tpg_pad_ops = { 266 .enum_mbus_code = mali_c55_tpg_enum_mbus_code, 267 .enum_frame_size = mali_c55_tpg_enum_frame_size, 268 .get_fmt = v4l2_subdev_get_fmt, 269 .set_fmt = mali_c55_tpg_set_fmt, 270 .enable_streams = mali_c55_tpg_enable_streams, 271 .disable_streams = mali_c55_tpg_disable_streams, 272 }; 273 274 static const struct v4l2_subdev_core_ops mali_c55_isp_core_ops = { 275 .subscribe_event = v4l2_ctrl_subdev_subscribe_event, 276 .unsubscribe_event = v4l2_event_subdev_unsubscribe, 277 }; 278 279 static const struct v4l2_subdev_ops mali_c55_tpg_ops = { 280 .core = &mali_c55_isp_core_ops, 281 .pad = &mali_c55_tpg_pad_ops, 282 }; 283 284 static int mali_c55_tpg_init_state(struct v4l2_subdev *sd, 285 struct v4l2_subdev_state *state) 286 { 287 struct v4l2_mbus_framefmt *fmt = 288 v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD); 289 290 fmt->width = MALI_C55_DEFAULT_WIDTH; 291 fmt->height = MALI_C55_DEFAULT_HEIGHT; 292 fmt->field = V4L2_FIELD_NONE; 293 fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20; 294 fmt->colorspace = V4L2_COLORSPACE_RAW; 295 fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); 296 fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); 297 fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(false, 298 fmt->colorspace, 299 fmt->ycbcr_enc); 300 301 return 0; 302 } 303 304 static const struct v4l2_subdev_internal_ops mali_c55_tpg_internal_ops = { 305 .init_state = mali_c55_tpg_init_state, 306 }; 307 308 static int mali_c55_tpg_init_controls(struct mali_c55 *mali_c55) 309 { 310 struct mali_c55_tpg_ctrls *ctrls = &mali_c55->tpg.ctrls; 311 struct v4l2_ctrl *pixel_rate; 312 struct v4l2_ctrl *hblank; 313 int ret; 314 315 ret = v4l2_ctrl_handler_init(&ctrls->handler, 4); 316 if (ret) 317 return ret; 318 319 v4l2_ctrl_new_std_menu_items(&ctrls->handler, &mali_c55_tpg_ctrl_ops, 320 V4L2_CID_TEST_PATTERN, 321 ARRAY_SIZE(mali_c55_tpg_test_pattern_menu) - 1, 322 0, 3, mali_c55_tpg_test_pattern_menu); 323 324 /* 325 * We fix hblank at the minimum allowed value and control framerate 326 * solely through the vblank control. 327 */ 328 hblank = v4l2_ctrl_new_std(&ctrls->handler, &mali_c55_tpg_ctrl_ops, 329 V4L2_CID_HBLANK, MALI_C55_TPG_FIXED_HBLANK, 330 MALI_C55_TPG_FIXED_HBLANK, 1, 331 MALI_C55_TPG_FIXED_HBLANK); 332 if (hblank) 333 hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; 334 335 ctrls->vblank = v4l2_ctrl_new_std(&ctrls->handler, 336 &mali_c55_tpg_ctrl_ops, 337 V4L2_CID_VBLANK, 338 MALI_C55_TPG_DEFAULT_MIN_VBLANK, 339 MALI_C55_TPG_MAX_VBLANK, 1, 340 MALI_C55_TPG_DEFAULT_DEF_VBLANK); 341 342 pixel_rate = v4l2_ctrl_new_std(&ctrls->handler, &mali_c55_tpg_ctrl_ops, 343 V4L2_CID_PIXEL_RATE, 344 MALI_C55_TPG_PIXEL_RATE, 345 MALI_C55_TPG_PIXEL_RATE, 1, 346 MALI_C55_TPG_PIXEL_RATE); 347 if (pixel_rate) 348 pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; 349 350 if (ctrls->handler.error) { 351 dev_err(mali_c55->dev, "Error during v4l2 controls init\n"); 352 v4l2_ctrl_handler_free(&ctrls->handler); 353 return ctrls->handler.error; 354 } 355 356 mali_c55->tpg.sd.ctrl_handler = &ctrls->handler; 357 mali_c55->tpg.sd.state_lock = ctrls->handler.lock; 358 359 return 0; 360 } 361 362 int mali_c55_register_tpg(struct mali_c55 *mali_c55) 363 { 364 struct mali_c55_tpg *tpg = &mali_c55->tpg; 365 struct v4l2_subdev *sd = &tpg->sd; 366 struct media_pad *pad = &tpg->pad; 367 int ret; 368 369 v4l2_subdev_init(sd, &mali_c55_tpg_ops); 370 sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; 371 sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; 372 sd->internal_ops = &mali_c55_tpg_internal_ops; 373 strscpy(sd->name, MALI_C55_DRIVER_NAME " tpg", sizeof(sd->name)); 374 375 pad->flags = MEDIA_PAD_FL_SOURCE; 376 ret = media_entity_pads_init(&sd->entity, 1, pad); 377 if (ret) { 378 dev_err(mali_c55->dev, 379 "Failed to initialize media entity pads\n"); 380 return ret; 381 } 382 383 ret = mali_c55_tpg_init_controls(mali_c55); 384 if (ret) { 385 dev_err(mali_c55->dev, 386 "Error initialising controls\n"); 387 goto err_cleanup_media_entity; 388 } 389 390 ret = v4l2_subdev_init_finalize(sd); 391 if (ret) 392 goto err_free_ctrl_handler; 393 394 ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd); 395 if (ret) { 396 dev_err(mali_c55->dev, "Failed to register tpg subdev\n"); 397 goto err_cleanup_subdev; 398 } 399 400 /* 401 * By default the colour settings lead to a very dim image that is 402 * nearly indistinguishable from black on some monitor settings. Ramp 403 * them up a bit so the image is brighter. 404 */ 405 mali_c55_ctx_write(mali_c55, MALI_C55_REG_TPG_R_BACKGROUND, 406 MALI_C55_TPG_BACKGROUND_MAX); 407 mali_c55_ctx_write(mali_c55, MALI_C55_REG_TPG_G_BACKGROUND, 408 MALI_C55_TPG_BACKGROUND_MAX); 409 mali_c55_ctx_write(mali_c55, MALI_C55_REG_TPG_B_BACKGROUND, 410 MALI_C55_TPG_BACKGROUND_MAX); 411 412 tpg->mali_c55 = mali_c55; 413 414 return 0; 415 416 err_cleanup_subdev: 417 v4l2_subdev_cleanup(sd); 418 err_free_ctrl_handler: 419 v4l2_ctrl_handler_free(&tpg->ctrls.handler); 420 err_cleanup_media_entity: 421 media_entity_cleanup(&sd->entity); 422 423 return ret; 424 } 425 426 void mali_c55_unregister_tpg(struct mali_c55 *mali_c55) 427 { 428 struct mali_c55_tpg *tpg = &mali_c55->tpg; 429 430 if (!tpg->mali_c55) 431 return; 432 433 v4l2_device_unregister_subdev(&tpg->sd); 434 v4l2_ctrl_handler_free(&tpg->ctrls.handler); 435 v4l2_subdev_cleanup(&tpg->sd); 436 media_entity_cleanup(&tpg->sd.entity); 437 } 438