xref: /linux/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c (revision 24f171c7e145f43b9f187578e89b0982ce87e54c)
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