File Index
Files and directories
Sign in
FILE

mali-c55-tpg.c

File preview with syntax highlighting.

linux-6.19.14/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c C
  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
 27static 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
 36static const u32 mali_c55_tpg_mbus_codes[] = {
 37	MEDIA_BUS_FMT_SRGGB20_1X20,
 38	MEDIA_BUS_FMT_RGB202020_1X60,
 39};
 40
 41static 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
 82static 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
114static const struct v4l2_ctrl_ops mali_c55_tpg_ctrl_ops = {
115	.s_ctrl = &mali_c55_tpg_s_ctrl,
116};
117
118static 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
145static 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
157static 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
182static 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
222static 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
250static 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
265static 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
274static 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
279static 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
284static 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
304static const struct v4l2_subdev_internal_ops mali_c55_tpg_internal_ops = {
305	.init_state = mali_c55_tpg_init_state,
306};
307
308static 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
362int 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
416err_cleanup_subdev:
417	v4l2_subdev_cleanup(sd);
418err_free_ctrl_handler:
419	v4l2_ctrl_handler_free(&tpg->ctrls.handler);
420err_cleanup_media_entity:
421	media_entity_cleanup(&sd->entity);
422
423	return ret;
424}
425
426void 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}
~/ Read-only file preview.