[PATCH v4 3/5] media: mali-c55: Add Mali-C55 ISP driver

Dan Scally dan.scally at ideasonboard.com
Thu May 23 06:44:06 PDT 2024


Hi Sakari - thanks for the review. Snipping some bits for which I have no comment...

On 23/05/2024 09:03, Sakari Ailus wrote:

<snip>
>> +
>> +static unsigned int mali_c55_calculate_bank_num(struct mali_c55 *mali_c55,
>> +						unsigned int crop,
>> +						unsigned int scale)
>> +{
>> +	unsigned int tmp;
>> +	unsigned int i;
>> +
>> +	tmp = (scale * 1000) / crop;
> This looks like something that can overflow. Can it?


Shouldn't be able to; maximum scale width is 8192.

>
>> +
>> +	for (i = 0; i < ARRAY_SIZE(mali_c55_coefficient_banks); i++) {
>> +		if (tmp >= mali_c55_coefficient_banks[i].bottom &&
>> +		    tmp <= mali_c55_coefficient_banks[i].top)
>> +			return mali_c55_coefficient_banks[i].bank;
>> +	}
>> +
>> +	/*
>> +	 * We shouldn't ever get here, in theory. As we have no good choices
>> +	 * simply warn the user and use the first bank of coefficients.
>> +	 */
>> +	dev_warn(mali_c55->dev, "scaling factor outside defined bounds\n");
>> +	return 0;
>> +}
>> +
>> +#endif /* _MALI_C55_RESIZER_COEFS_H */
>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c
>> new file mode 100644
>> index 000000000000..8e0669a5f391
>> --- /dev/null
>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c
>> @@ -0,0 +1,740 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * ARM Mali-C55 ISP Driver - Image signal processor
>> + *
>> + * Copyright (C) 2024 Ideas on Board Oy
>> + */
>> +
>> +#include <linux/math.h>
>> +#include <linux/minmax.h>
>> +
>> +#include <media/media-entity.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +#include "mali-c55-common.h"
>> +#include "mali-c55-registers.h"
>> +#include "mali-c55-resizer-coefs.h"
>> +
>> +/* Scaling factor in Q4.20 format. */
>> +#define MALI_C55_RZR_SCALER_FACTOR	1048576
> (1U << 20)
>
> ?
>
>> +
>> +static const u32 rzr_non_bypass_src_fmts[] = {
>> +	MEDIA_BUS_FMT_RGB121212_1X36,
>> +	MEDIA_BUS_FMT_YUV10_1X30
>> +};
>> +
>> +static const char * const mali_c55_resizer_names[] = {
>> +	[MALI_C55_RZR_FR] = "resizer fr",
>> +	[MALI_C55_RZR_DS] = "resizer ds",
>> +};
>> +
>> +static int mali_c55_rzr_program_crop(struct mali_c55_resizer *rzr,
>> +				     struct v4l2_subdev_state *state)
>> +{
>> +	unsigned int reg_offset = rzr->cap_dev->reg_offset;
>> +	struct mali_c55 *mali_c55 = rzr->mali_c55;
>> +	struct v4l2_mbus_framefmt *fmt;
>> +	struct v4l2_rect *crop;
>> +
>> +	/* Verify if crop should be enabled. */
>> +	fmt = v4l2_subdev_state_get_format(state, MALI_C55_RZR_SINK_PAD, 0);
>> +	crop = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD, 0);
>> +
>> +	if (fmt->width == crop->width && fmt->height == crop->height)
>> +		return MALI_C55_BYPASS_CROP;
>> +
>> +	mali_c55_write(mali_c55, MALI_C55_REG_CROP_X_START(reg_offset),
>> +		       crop->left);
>> +	mali_c55_write(mali_c55, MALI_C55_REG_CROP_Y_START(reg_offset),
>> +		       crop->top);
>> +	mali_c55_write(mali_c55, MALI_C55_REG_CROP_X_SIZE(reg_offset),
>> +		       crop->width);
>> +	mali_c55_write(mali_c55, MALI_C55_REG_CROP_Y_SIZE(reg_offset),
>> +		       crop->height);
>> +
>> +	mali_c55_write(mali_c55, MALI_C55_REG_CROP_EN(reg_offset),
>> +		       MALI_C55_CROP_ENABLE);
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_rzr_program_resizer(struct mali_c55_resizer *rzr,
>> +					struct v4l2_subdev_state *state)
>> +{
>> +	unsigned int reg_offset = rzr->cap_dev->reg_offset;
>> +	struct mali_c55 *mali_c55 = rzr->mali_c55;
>> +	struct v4l2_rect *crop, *scale;
>> +	unsigned int h_bank, v_bank;
>> +	u64 h_scale, v_scale;
>> +
>> +	/* Verify if scaling should be enabled. */
>> +	crop = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD, 0);
>> +	scale = v4l2_subdev_state_get_compose(state, MALI_C55_RZR_SINK_PAD, 0);
>> +
>> +	if (crop->width == scale->width && crop->height == scale->height)
>> +		return MALI_C55_BYPASS_SCALER;
>> +
>> +	/* Program the V/H scaling factor in Q4.20 format. */
>> +	h_scale = crop->width * MALI_C55_RZR_SCALER_FACTOR;
>> +	v_scale = crop->height * MALI_C55_RZR_SCALER_FACTOR;
>> +
>> +	do_div(h_scale, scale->width);
>> +	do_div(v_scale, scale->height);
>> +
>> +	mali_c55_write(mali_c55,
>> +		       MALI_C55_REG_SCALER_IN_WIDTH(reg_offset),
>> +		       crop->width);
>> +	mali_c55_write(mali_c55,
>> +		       MALI_C55_REG_SCALER_IN_HEIGHT(reg_offset),
>> +		       crop->height);
>> +
>> +	mali_c55_write(mali_c55,
>> +		       MALI_C55_REG_SCALER_OUT_WIDTH(reg_offset),
>> +		       scale->width);
>> +	mali_c55_write(mali_c55,
>> +		       MALI_C55_REG_SCALER_OUT_HEIGHT(reg_offset),
>> +		       scale->height);
>> +
>> +	mali_c55_write(mali_c55,
>> +		       MALI_C55_REG_SCALER_HFILT_TINC(reg_offset),
>> +		       h_scale);
>> +	mali_c55_write(mali_c55,
>> +		       MALI_C55_REG_SCALER_VFILT_TINC(reg_offset),
>> +		       v_scale);
>> +
>> +	h_bank = mali_c55_calculate_bank_num(mali_c55, crop->width,
>> +					     scale->width);
>> +	mali_c55_write(mali_c55,
>> +		       MALI_C55_REG_SCALER_HFILT_COEF(reg_offset),
>> +		       h_bank);
>> +
>> +	v_bank = mali_c55_calculate_bank_num(mali_c55, crop->height,
>> +					     scale->height);
>> +	mali_c55_write(mali_c55,
>> +		       MALI_C55_REG_SCALER_VFILT_COEF(reg_offset),
>> +		       v_bank);
>> +
>> +	return 0;
>> +}
>> +
>> +static void mali_c55_rzr_program(struct mali_c55_resizer *rzr,
>> +				 struct v4l2_subdev_state *state)
>> +{
>> +	struct mali_c55 *mali_c55 = rzr->mali_c55;
>> +	u32 bypass = 0;
>> +
>> +	/* Verify if cropping and scaling should be enabled. */
>> +	bypass |= mali_c55_rzr_program_crop(rzr, state);
>> +	bypass |= mali_c55_rzr_program_resizer(rzr, state);
>> +
>> +	mali_c55_update_bits(mali_c55, rzr->id == MALI_C55_RZR_FR ?
>> +			     MALI_C55_REG_FR_BYPASS : MALI_C55_REG_DS_BYPASS,
>> +			     MALI_C55_BYPASS_CROP | MALI_C55_BYPASS_SCALER,
>> +			     bypass);
>> +}
>> +
>> +/*
>> + * Inspect the routing table to know which of the two (mutually exclusive)
>> + * routes is enabled and return the sink pad id of the active route.
>> + */
>> +static unsigned int mali_c55_rzr_get_active_sink(struct v4l2_subdev_state *state)
>> +{
>> +	struct v4l2_subdev_krouting *routing = &state->routing;
>> +	struct v4l2_subdev_route *route;
>> +
>> +	/* A single route is enabled at a time. */
>> +	for_each_active_route(routing, route)
>> +		return route->sink_pad;
>> +
>> +	return MALI_C55_RZR_SINK_PAD;
>> +}
>> +
>> +static int __mali_c55_rzr_set_routing(struct v4l2_subdev *sd,
>> +				      struct v4l2_subdev_state *state,
>> +				      struct v4l2_subdev_krouting *routing)
>> +{
>> +	struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
>> +						    sd);
>> +	unsigned int active_sink = UINT_MAX;
>> +	struct v4l2_rect *crop, *compose;
>> +	struct v4l2_subdev_route *route;
>> +	unsigned int active_routes = 0;
>> +	struct v4l2_mbus_framefmt *fmt;
>> +	int ret;
>> +
>> +	ret = v4l2_subdev_routing_validate(sd, routing, 0);
>> +	if (ret)
>> +		return ret;
>> +
>> +	/* Only a single route can be enabled at a time. */
>> +	for_each_active_route(routing, route) {
>> +		if (++active_routes > 1) {
>> +			dev_err(rzr->mali_c55->dev,
>> +				"Only one route can be active");
>> +			return -EINVAL;
>> +		}
>> +
>> +		active_sink = route->sink_pad;
>> +	}
>> +	if (active_sink == UINT_MAX) {
>> +		dev_err(rzr->mali_c55->dev, "One route has to be active");
>> +		return -EINVAL;
>> +	}
>> +
>> +	ret = v4l2_subdev_set_routing(sd, state, routing);
>> +	if (ret) {
>> +		dev_err(rzr->mali_c55->dev, "Failed to set routing\n");
>> +		return ret;
>> +	}
>> +
>> +	fmt = v4l2_subdev_state_get_format(state, active_sink, 0);
>> +	crop = v4l2_subdev_state_get_crop(state, active_sink, 0);
>> +	compose = v4l2_subdev_state_get_compose(state, active_sink, 0);
>> +
>> +	fmt->width = MALI_C55_DEFAULT_WIDTH;
>> +	fmt->height = MALI_C55_DEFAULT_HEIGHT;
>> +	fmt->colorspace = V4L2_COLORSPACE_SRGB;
>> +	fmt->field = V4L2_FIELD_NONE;
>> +
>> +	if (active_sink == MALI_C55_RZR_SINK_PAD) {
>> +		fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
>> +
>> +		crop->left = crop->top = 0;
>> +		crop->width = MALI_C55_DEFAULT_WIDTH;
>> +		crop->height = MALI_C55_DEFAULT_HEIGHT;
>> +
>> +		*compose = *crop;
>> +	} else {
>> +		fmt->code = MEDIA_BUS_FMT_SRGGB12_1X12;
>> +	}
>> +
>> +	/* Propagate the format to the source pad */
>> +	*v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD, 0) = *fmt;
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_rzr_enum_mbus_code(struct v4l2_subdev *sd,
>> +				       struct v4l2_subdev_state *state,
>> +				       struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +	struct v4l2_mbus_framefmt *sink_fmt;
>> +	const struct mali_c55_isp_fmt *fmt;
>> +	unsigned int index = 0;
>> +	u32 sink_pad;
>> +
>> +	switch (code->pad) {
>> +	case MALI_C55_RZR_SINK_PAD:
>> +		if (code->index)
>> +			return -EINVAL;
>> +
>> +		code->code = MEDIA_BUS_FMT_RGB121212_1X36;
>> +
>> +		return 0;
>> +	case MALI_C55_RZR_SOURCE_PAD:
>> +		sink_pad = mali_c55_rzr_get_active_sink(state);
>> +		sink_fmt = v4l2_subdev_state_get_format(state, sink_pad, 0);
>> +
>> +		/*
>> +		 * If the active route is from the Bypass sink pad, then the
>> +		 * source pad is a simple passthrough of the sink format.
>> +		 */
>> +
>> +		if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) {
>> +			if (code->index)
>> +				return -EINVAL;
>> +
>> +			code->code = sink_fmt->code;
>> +			return 0;
>> +		}
>> +
>> +		/*
>> +		 * If the active route is from the non-bypass sink then we can
>> +		 * select either RGB or conversion to YUV.
>> +		 */
>> +
>> +		if (code->index >= ARRAY_SIZE(rzr_non_bypass_src_fmts))
>> +			return -EINVAL;
>> +
>> +		code->code = rzr_non_bypass_src_fmts[code->index];
>> +
>> +		return 0;
>> +	case MALI_C55_RZR_SINK_BYPASS_PAD:
>> +		for_each_mali_isp_fmt(fmt) {
>> +			if (index++ == code->index) {
>> +				code->code = fmt->code;
>> +				return 0;
>> +			}
>> +		}
>> +
>> +		break;
>> +	}
>> +
>> +	return -EINVAL;
>> +}
>> +
>> +static int mali_c55_rzr_enum_frame_size(struct v4l2_subdev *sd,
>> +					struct v4l2_subdev_state *state,
>> +					struct v4l2_subdev_frame_size_enum *fse)
>> +{
>> +	if (fse->index)
>> +		return -EINVAL;
>> +
>> +	fse->max_width = MALI_C55_MAX_WIDTH;
>> +	fse->max_height = MALI_C55_MAX_HEIGHT;
>> +	fse->min_width = MALI_C55_MIN_WIDTH;
>> +	fse->min_height = MALI_C55_MIN_HEIGHT;
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_rzr_set_sink_fmt(struct v4l2_subdev *sd,
>> +				     struct v4l2_subdev_state *state,
>> +				     struct v4l2_subdev_format *format)
>> +{
>> +	struct v4l2_mbus_framefmt *fmt = &format->format;
>> +	struct v4l2_rect *rect;
>> +	unsigned int sink_pad;
>> +
>> +	/*
>> +	 * Clamp to min/max and then reset crop and compose rectangles to the
>> +	 * newly applied size.
>> +	 */
>> +	clamp_t(unsigned int, fmt->width,
>> +		MALI_C55_MIN_WIDTH, MALI_C55_MAX_WIDTH);
>> +	clamp_t(unsigned int, fmt->height,
>> +		MALI_C55_MIN_HEIGHT, MALI_C55_MAX_HEIGHT);
>> +
>> +	sink_pad = mali_c55_rzr_get_active_sink(state);
>> +	if (sink_pad == MALI_C55_RZR_SINK_PAD) {
>> +		fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
>> +
>> +		rect = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD);
>> +		rect->left = 0;
>> +		rect->top = 0;
>> +		rect->width = fmt->width;
>> +		rect->height = fmt->height;
>> +
>> +		rect = v4l2_subdev_state_get_compose(state,
>> +						     MALI_C55_RZR_SINK_PAD);
>> +		rect->left = 0;
>> +		rect->top = 0;
>> +		rect->width = fmt->width;
>> +		rect->height = fmt->height;
>> +	} else {
>> +		/*
>> +		 * Make sure the media bus code is one of the supported
>> +		 * ISP input media bus codes.
>> +		 */
>> +		if (!mali_c55_isp_is_format_supported(fmt->code))
>> +			fmt->code = MALI_C55_DEFAULT_MEDIA_BUS_FMT;
>> +	}
>> +
>> +	*v4l2_subdev_state_get_format(state, sink_pad, 0) = *fmt;
>> +	*v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD, 0) = *fmt;
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_rzr_set_source_fmt(struct v4l2_subdev *sd,
>> +				       struct v4l2_subdev_state *state,
>> +				       struct v4l2_subdev_format *format)
>> +{
>> +	struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
>> +						    sd);
>> +	struct v4l2_mbus_framefmt *fmt = &format->format;
>> +	struct v4l2_mbus_framefmt *sink_fmt;
>> +	struct v4l2_rect *crop, *compose;
>> +	unsigned int sink_pad;
>> +	unsigned int i;
>> +
>> +	sink_pad = mali_c55_rzr_get_active_sink(state);
>> +	sink_fmt = v4l2_subdev_state_get_format(state, sink_pad, 0);
>> +	crop = v4l2_subdev_state_get_crop(state, sink_pad, 0);
>> +	compose = v4l2_subdev_state_get_compose(state, sink_pad, 0);
>> +
>> +	/* FR Bypass pipe. */
>> +
>> +	if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) {
>> +		/*
>> +		 * Format on the source pad is the same as the one on the
>> +		 * sink pad.
>> +		 */
>> +		fmt->code = sink_fmt->code;
>> +
>> +		/* RAW bypass disables scaling and cropping. */
>> +		crop->top = compose->top = 0;
>> +		crop->left = compose->left = 0;
>> +		fmt->width = crop->width = compose->width = sink_fmt->width;
>> +		fmt->height = crop->height = compose->height = sink_fmt->height;
>> +
>> +		*v4l2_subdev_state_get_format(state,
>> +					      MALI_C55_RZR_SOURCE_PAD) = *fmt;
>> +
>> +		return 0;
>> +	}
>> +
>> +	/* Regular processing pipe. */
>> +
>> +	for (i = 0; i < ARRAY_SIZE(rzr_non_bypass_src_fmts); i++) {
>> +		if (fmt->code == rzr_non_bypass_src_fmts[i])
>> +			break;
>> +	}
>> +
>> +	if (i == ARRAY_SIZE(rzr_non_bypass_src_fmts)) {
>> +		dev_dbg(rzr->mali_c55->dev,
>> +			"Unsupported mbus code 0x%x: using default\n",
>> +			fmt->code);
>> +		fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
>> +	}
>> +
>> +	/*
>> +	 * The source pad format size comes directly from the sink pad
>> +	 * compose rectangle.
>> +	 */
>> +	fmt->width = compose->width;
>> +	fmt->height = compose->height;
>> +
>> +	*v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD) = *fmt;
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_rzr_set_fmt(struct v4l2_subdev *sd,
>> +				struct v4l2_subdev_state *state,
>> +				struct v4l2_subdev_format *format)
>> +{
>> +	/*
>> +	 * On sink pads fmt is either fixed for the 'regular' processing
>> +	 * pad or a RAW format or 20-bit wide RGB/YUV format for the FR bypass
>> +	 * pad.
>> +	 *
>> +	 * On source pad sizes are the result of crop+compose on the sink
>> +	 * pad sizes, while the format depends on the active route.
>> +	 */
>> +
>> +	if (format->pad != MALI_C55_RZR_SOURCE_PAD)
>> +		return mali_c55_rzr_set_sink_fmt(sd, state, format);
>> +
>> +	return mali_c55_rzr_set_source_fmt(sd, state, format);
>> +}
>> +
>> +static int mali_c55_rzr_get_selection(struct v4l2_subdev *sd,
>> +				      struct v4l2_subdev_state *state,
>> +				      struct v4l2_subdev_selection *sel)
>> +{
>> +	if (sel->pad != MALI_C55_RZR_SINK_PAD)
>> +		return -EINVAL;
>> +
>> +	if (sel->target != V4L2_SEL_TGT_CROP &&
>> +	    sel->target != V4L2_SEL_TGT_COMPOSE)
>> +		return -EINVAL;
>> +
>> +	sel->r = sel->target == V4L2_SEL_TGT_CROP
>> +	       ? *v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD)
>> +	       : *v4l2_subdev_state_get_compose(state, MALI_C55_RZR_SINK_PAD);
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_rzr_set_selection(struct v4l2_subdev *sd,
>> +				      struct v4l2_subdev_state *state,
>> +				      struct v4l2_subdev_selection *sel)
>> +{
>> +	struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
>> +						    sd);
>> +	struct v4l2_mbus_framefmt *source_fmt;
>> +	struct v4l2_mbus_framefmt *sink_fmt;
>> +	struct v4l2_rect *crop, *compose;
>> +
>> +	if (sel->pad != MALI_C55_RZR_SINK_PAD)
>> +		return -EINVAL;
>> +
>> +	if (sel->target != V4L2_SEL_TGT_CROP &&
>> +	    sel->target != V4L2_SEL_TGT_COMPOSE)
>> +		return -EINVAL;
>> +
>> +	source_fmt = v4l2_subdev_state_get_format(state,
>> +						  MALI_C55_RZR_SOURCE_PAD);
>> +	sink_fmt = v4l2_subdev_state_get_format(state, MALI_C55_RZR_SINK_PAD);
>> +	crop = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD);
>> +	compose = v4l2_subdev_state_get_compose(state, MALI_C55_RZR_SINK_PAD);
>> +
>> +	/* RAW bypass disables crop/scaling. */
>> +	if (mali_c55_format_is_raw(source_fmt->code)) {
>> +		crop->top = compose->top = 0;
>> +		crop->left = compose->left = 0;
>> +		crop->width = compose->width = sink_fmt->width;
>> +		crop->height = compose->height = sink_fmt->height;
>> +
>> +		sel->r = sel->target == V4L2_SEL_TGT_CROP ? *crop : *compose;
>> +
>> +		return 0;
>> +	}
>> +
>> +	/* During streaming, it is allowed to only change the crop rectangle. */
>> +	if (rzr->streaming && sel->target != V4L2_SEL_TGT_CROP)
>> +		return -EINVAL;
>> +
>> +	 /*
>> +	  * Update the desired target and then clamp the crop rectangle to the
>> +	  * sink format sizes and the compose size to the crop sizes.
>> +	  */
>> +	if (sel->target == V4L2_SEL_TGT_CROP)
>> +		*crop = sel->r;
>> +	else
>> +		*compose = sel->r;
>> +
>> +	clamp_t(unsigned int, crop->left, 0,  sink_fmt->width);
>> +	clamp_t(unsigned int, crop->top, 0,  sink_fmt->height);
>> +	clamp_t(unsigned int, crop->width, MALI_C55_MIN_WIDTH,
>> +		sink_fmt->width - crop->left);
>> +	clamp_t(unsigned int, crop->height, MALI_C55_MIN_HEIGHT,
>> +		sink_fmt->height - crop->top);
>> +
>> +	if (rzr->streaming) {
>> +		/*
>> +		 * Apply at runtime a crop rectangle on the resizer's sink only
>> +		 * if it doesn't require re-programming the scaler output sizes
>> +		 * as it would require changing the output buffer sizes as well.
>> +		 */
>> +		if (sel->r.width < compose->width ||
>> +		    sel->r.height < compose->height)
>> +			return -EINVAL;
>> +
>> +		*crop = sel->r;
>> +		mali_c55_rzr_program(rzr, state);
>> +
>> +		return 0;
>> +	}
>> +
>> +	compose->left = 0;
>> +	compose->top = 0;
>> +	clamp_t(unsigned int, compose->left, 0,  sink_fmt->width);
>> +	clamp_t(unsigned int, compose->top, 0,  sink_fmt->height);
>> +	clamp_t(unsigned int, compose->width, crop->width / 8, crop->width);
>> +	clamp_t(unsigned int, compose->height, crop->height / 8, crop->height);
>> +
>> +	sel->r = sel->target == V4L2_SEL_TGT_CROP ? *crop : *compose;
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_rzr_set_routing(struct v4l2_subdev *sd,
>> +				    struct v4l2_subdev_state *state,
>> +				    enum v4l2_subdev_format_whence which,
>> +				    struct v4l2_subdev_krouting *routing)
>> +{
>> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
>> +	    media_entity_is_streaming(&sd->entity))
>> +		return -EBUSY;
>> +
>> +	return __mali_c55_rzr_set_routing(sd, state, routing);
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops mali_c55_resizer_pad_ops = {
>> +	.enum_mbus_code		= mali_c55_rzr_enum_mbus_code,
>> +	.enum_frame_size	= mali_c55_rzr_enum_frame_size,
>> +	.get_fmt		= v4l2_subdev_get_fmt,
>> +	.set_fmt		= mali_c55_rzr_set_fmt,
>> +	.get_selection		= mali_c55_rzr_get_selection,
>> +	.set_selection		= mali_c55_rzr_set_selection,
>> +	.set_routing		= mali_c55_rzr_set_routing,
>> +};
>> +
>> +void mali_c55_rzr_start_stream(struct mali_c55_resizer *rzr)
>> +{
>> +	struct mali_c55 *mali_c55 = rzr->mali_c55;
>> +	struct v4l2_subdev *sd = &rzr->sd;
>> +	struct v4l2_subdev_state *state;
>> +	unsigned int sink_pad;
>> +
>> +	state = v4l2_subdev_lock_and_get_active_state(sd);
>> +
>> +	sink_pad = mali_c55_rzr_get_active_sink(state);
>> +	if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) {
>> +		/* Bypass FR pipe processing if the bypass route is active. */
>> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_ISP_RAW_BYPASS,
>> +				     MALI_C55_ISP_RAW_BYPASS_FR_BYPASS_MASK,
>> +				     MALI_C55_ISP_RAW_BYPASS_RAW_FR_BYPASS);
>> +		goto unlock_state;
>> +	}
>> +
>> +	/* Disable bypass and use regular processing. */
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_ISP_RAW_BYPASS,
>> +			     MALI_C55_ISP_RAW_BYPASS_FR_BYPASS_MASK, 0);
>> +	mali_c55_rzr_program(rzr, state);
>> +
>> +unlock_state:
>> +	rzr->streaming = true;
>> +	v4l2_subdev_unlock_state(state);
>> +}
>> +
>> +void mali_c55_rzr_stop_stream(struct mali_c55_resizer *rzr)
>> +{
>> +	struct v4l2_subdev *sd = &rzr->sd;
>> +	struct v4l2_subdev_state *state;
>> +
>> +	state = v4l2_subdev_lock_and_get_active_state(sd);
>> +	rzr->streaming = false;
>> +	v4l2_subdev_unlock_state(state);
>> +}
>> +
>> +static const struct v4l2_subdev_ops mali_c55_resizer_ops = {
>> +	.pad	= &mali_c55_resizer_pad_ops,
>> +};
>> +
>> +static int mali_c55_rzr_init_state(struct v4l2_subdev *sd,
>> +				   struct v4l2_subdev_state *state)
>> +{
>> +	struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
>> +						    sd);
>> +	struct v4l2_subdev_krouting routing = { };
>> +	struct v4l2_subdev_route *routes;
>> +	unsigned int i;
>> +	int ret;
>> +
>> +	routes = kcalloc(rzr->num_routes, sizeof(*routes), GFP_KERNEL);
>> +	if (!routes)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < rzr->num_routes; ++i) {
>> +		struct v4l2_subdev_route *route = &routes[i];
>> +
>> +		route->sink_pad = i
>> +				? MALI_C55_RZR_SINK_BYPASS_PAD
>> +				: MALI_C55_RZR_SINK_PAD;
>> +		route->source_pad = MALI_C55_RZR_SOURCE_PAD;
>> +		if (route->sink_pad != MALI_C55_RZR_SINK_BYPASS_PAD)
>> +			route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
>> +	}
>> +
>> +	routing.num_routes = rzr->num_routes;
>> +	routing.routes = routes;
>> +
>> +	ret = __mali_c55_rzr_set_routing(sd, state, &routing);
>> +	kfree(routes);
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct v4l2_subdev_internal_ops mali_c55_resizer_internal_ops = {
>> +	.init_state = mali_c55_rzr_init_state,
>> +};
>> +
>> +static void mali_c55_resizer_program_coefficients(struct mali_c55 *mali_c55,
>> +						  unsigned int index)
>> +{
>> +	unsigned int scaler_filt_coefmem_addrs[][2] = {
> This should be const.
>
>> +		[MALI_C55_RZR_FR] = {
>> +			0x034A8, /* hfilt */
>> +			0x044A8  /* vfilt */
>> +		},
>> +		[MALI_C55_RZR_DS] = {
>> +			0x014A8, /* hfilt */
>> +			0x024A8  /* vfilt */
>> +		},
>> +	};
>> +	unsigned int haddr = scaler_filt_coefmem_addrs[index][0];
>> +	unsigned int vaddr = scaler_filt_coefmem_addrs[index][1];
>> +	unsigned int i, j;
>> +
>> +	for (i = 0; i < MALI_C55_RESIZER_COEFS_NUM_BANKS; i++) {
>> +		for (j = 0; j < MALI_C55_RESIZER_COEFS_NUM_ENTRIES; j++) {
>> +			mali_c55_write(mali_c55, haddr,
>> +				mali_c55_scaler_h_filter_coefficients[i][j]);
>> +			mali_c55_write(mali_c55, vaddr,
>> +				mali_c55_scaler_v_filter_coefficients[i][j]);
>> +
>> +			haddr += 4;
>> +			vaddr += 4;
> sizeof(u32) ?
>
> Up to you.


I think I'll keep it if it's all the same to you

>
>> +		}
>> +	}
>> +}
>> +
>> +int mali_c55_register_resizers(struct mali_c55 *mali_c55)
>> +{
>> +	unsigned int i;
>> +	int ret;
>> +
>> +	for (i = 0; i < MALI_C55_NUM_RZRS; ++i) {
>> +		struct mali_c55_resizer *rzr = &mali_c55->resizers[i];
>> +		struct v4l2_subdev *sd = &rzr->sd;
>> +		unsigned int num_pads = MALI_C55_RZR_NUM_PADS;
>> +
>> +		rzr->id = i;
>> +		rzr->streaming = false;
>> +
>> +		if (rzr->id == MALI_C55_RZR_FR)
>> +			rzr->cap_dev = &mali_c55->cap_devs[MALI_C55_CAP_DEV_FR];
>> +		else
>> +			rzr->cap_dev = &mali_c55->cap_devs[MALI_C55_CAP_DEV_DS];
>> +
>> +		mali_c55_resizer_program_coefficients(mali_c55, i);
>> +
>> +		v4l2_subdev_init(sd, &mali_c55_resizer_ops);
>> +		sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE
>> +			  | V4L2_SUBDEV_FL_STREAMS;
> "|" should be aligned with beginning of the rvalue, i.e. "V" of
> V4L2_SUBDEV_FL_HAS_DEVNODE.
>
>> +		sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
>> +		sd->owner = THIS_MODULE;
>> +		sd->internal_ops = &mali_c55_resizer_internal_ops;
>> +		snprintf(sd->name, ARRAY_SIZE(sd->name), "%s %s",
>> +			 MALI_C55_DRIVER_NAME, mali_c55_resizer_names[i]);
>> +
>> +		rzr->pads[MALI_C55_RZR_SINK_PAD].flags = MEDIA_PAD_FL_SINK;
>> +		rzr->pads[MALI_C55_RZR_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE;
>> +
>> +		/* Only the FR pipe has a bypass pad. */
>> +		if (rzr->id == MALI_C55_RZR_FR) {
>> +			rzr->pads[MALI_C55_RZR_SINK_BYPASS_PAD].flags =
>> +							MEDIA_PAD_FL_SINK;
>> +			rzr->num_routes = 2;
>> +		} else {
>> +			num_pads -= 1;
>> +			rzr->num_routes = 1;
>> +		}
>> +
>> +		ret = media_entity_pads_init(&sd->entity, num_pads, rzr->pads);
>> +		if (ret)
>> +			return ret;
>> +
>> +		ret = v4l2_subdev_init_finalize(sd);
>> +		if (ret)
>> +			goto err_cleanup;
>> +
>> +		ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd);
>> +		if (ret)
>> +			goto err_cleanup;
>> +
>> +		rzr->mali_c55 = mali_c55;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_cleanup:
>> +	for (; i >= 0; --i) {
>> +		struct mali_c55_resizer *rzr = &mali_c55->resizers[i];
>> +		struct v4l2_subdev *sd = &rzr->sd;
>> +
>> +		v4l2_subdev_cleanup(sd);
>> +		media_entity_cleanup(&sd->entity);
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +void mali_c55_unregister_resizers(struct mali_c55 *mali_c55)
>> +{
>> +	unsigned int i;
>> +
>> +	for (i = 0; i < MALI_C55_NUM_RZRS; i++) {
>> +		struct mali_c55_resizer *resizer = &mali_c55->resizers[i];
>> +
>> +		if (!resizer->mali_c55)
>> +			continue;
>> +
>> +		v4l2_device_unregister_subdev(&resizer->sd);
>> +		v4l2_subdev_cleanup(&resizer->sd);
>> +		media_entity_cleanup(&resizer->sd.entity);
>> +	}
>> +}
>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c
>> new file mode 100644
>> index 000000000000..042851a4b42d
>> --- /dev/null
>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c
>> @@ -0,0 +1,424 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * ARM Mali-C55 ISP Driver - Test pattern generator
>> + *
>> + * Copyright (C) 2024 Ideas on Board Oy
>> + */
>> +
>> +#include <linux/minmax.h>
>> +#include <linux/string.h>
>> +
>> +#include <media/media-entity.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +#include "mali-c55-common.h"
>> +#include "mali-c55-registers.h"
>> +
>> +#define MALI_C55_TPG_SRC_PAD		0
>> +#define MALI_C55_TPG_FIXED_HBLANK	0x20
>> +#define MALI_C55_TPG_MAX_VBLANK		0xFFFF
>> +#define MALI_C55_TPG_PIXEL_RATE		100000000
>> +
>> +static const char * const mali_c55_tpg_test_pattern_menu[] = {
>> +	"Flat field",
>> +	"Horizontal gradient",
>> +	"Vertical gradient",
>> +	"Vertical bars",
>> +	"Arbitrary rectangle",
>> +	"White frame on black field"
>> +};
>> +
>> +static const u32 mali_c55_tpg_mbus_codes[] = {
>> +	MEDIA_BUS_FMT_SRGGB16_1X16,
>> +	/*
>> +	 * This is a lie. In RGB mode the Test Pattern Generator actually output
>> +	 * 16-bits-per-colour data. However, RGB data follows one of the Bypass
>> +	 * paths which has a 12-bit limit at the insertion point, meaning it
>> +	 * would be truncated there to match the internal 12-bit format that
>> +	 * would be output from the debayering block. The same is true of RGB
>> +	 * data output by a sensor and streamed to the ISP's input port, however
>> +	 * in that case the ISP's input port requires that data be converted to
>> +	 * a 20-bit MSB aligned format. Given:
>> +	 *
>> +	 *	1. Our chosen topology represents the TPG as a subdevice
>> +	 *	   linked to the ISP's input port.
>> +	 *	2. We need to restrict the ISP's sink pad to only accepting that
>> +	 *	   20-bit RGB format from sensors / CSI-2 receivers.
>> +	 *	3. All the data ultimately ends up in the same format anyway and
>> +	 *	   these data from the TPG are purely internal to the ISP
>> +	 *
>> +	 * It seems best to reduce the programming complexity by simply
>> +	 * pretending that the TPG outputs data in the same format that the ISP
>> +	 * input port requires, even though it doesn't really.
>> +	 */
>> +	MEDIA_BUS_FMT_RGB202020_1X60,
>> +};
>> +
>> +static void __mali_c55_tpg_calc_vblank(struct v4l2_mbus_framefmt *format,
>> +				       int *def_vblank, int *min_vblank)
>> +{
>> +	unsigned int hts;
>> +	int tgt_fps;
>> +	int vblank;
>> +
>> +	hts = format->width + MALI_C55_TPG_FIXED_HBLANK;
>> +
>> +	/*
>> +	 * The ISP has minimum vertical blanking requirements that must be
>> +	 * adhered to by the TPG. The minimum is a function of the Iridix blocks
>> +	 * clocking requirements and the width of the image and horizontal
>> +	 * blanking, but if we assume the worst case iVariance and sVariance
>> +	 * values then it boils down to the below.
>> +	 */
>> +	*min_vblank = 15 + (120500 / hts);
>> +
>> +	/*
>> +	 * We need to set a sensible default vblank for whatever format height
>> +	 * we happen to be given from set_fmt(). This function just targets
>> +	 * an even multiple of 15fps. If we can't get 15fps, let's target 5fps.
>> +	 * If we can't get 5fps we'll take whatever the minimum vblank gives us.
>> +	 */
>> +	tgt_fps = MALI_C55_TPG_PIXEL_RATE / hts / (format->height + *min_vblank);
>> +
>> +	if (tgt_fps < 5)
>> +		vblank = *min_vblank;
>> +	else
>> +		vblank = MALI_C55_TPG_PIXEL_RATE / hts
>> +		       / max(rounddown(tgt_fps, 15), 5);
>> +
>> +	*def_vblank = ALIGN_DOWN(vblank, 2) - format->height;
>> +}
>> +
>> +static int mali_c55_tpg_s_ctrl(struct v4l2_ctrl *ctrl)
>> +{
>> +	struct mali_c55_tpg *tpg = container_of(ctrl->handler,
>> +						struct mali_c55_tpg,
>> +						ctrls.handler);
>> +	struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg);
>> +
>> +	switch (ctrl->id) {
>> +	case V4L2_CID_TEST_PATTERN:
>> +		mali_c55_write(mali_c55, MALI_C55_REG_TEST_GEN_CH0_PATTERN_TYPE,
>> +			       ctrl->val);
>> +		break;
>> +	case V4L2_CID_VBLANK:
>> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_BLANKING,
>> +				     MALI_C55_REG_VBLANK_MASK, ctrl->val);
>> +		break;
>> +	default:
>> +		dev_err(mali_c55->dev, "invalid V4L2 control ID\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_ctrl_ops mali_c55_tpg_ctrl_ops = {
>> +	.s_ctrl = &mali_c55_tpg_s_ctrl,
>> +};
>> +
>> +static void mali_c55_tpg_configure(struct mali_c55 *mali_c55,
>> +				   struct v4l2_subdev *sd)
>> +{
>> +	struct v4l2_subdev_state *state;
>> +	struct v4l2_mbus_framefmt *fmt;
>> +
>> +	/*
>> +	 * hblank needs setting, but is a read-only control and thus won't be
>> +	 * called during __v4l2_ctrl_handler_setup(). Do it here instead.
>> +	 */
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_BLANKING,
>> +			     MALI_C55_REG_HBLANK_MASK,
>> +			     MALI_C55_TPG_FIXED_HBLANK);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO,
>> +			     MALI_C55_REG_GEN_VIDEO_MULTI_MASK, 0x01);
>> +
>> +	state = v4l2_subdev_lock_and_get_active_state(sd);
>> +	fmt = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD);
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_TPG_CH0,
>> +			     MALI_C55_TEST_PATTERN_RGB_MASK,
>> +			     fmt->code == MEDIA_BUS_FMT_RGB202020_1X60 ?
>> +					  0x01 : 0x0);
>> +
>> +	v4l2_subdev_unlock_state(state);
>> +}
>> +
>> +static int mali_c55_tpg_s_stream(struct v4l2_subdev *sd, int enable)
>> +{
>> +	struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd);
>> +	struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg);
>> +
>> +	if (!enable) {
>> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_TPG_CH0,
>> +				     MALI_C55_TEST_PATTERN_ON_OFF, 0x00);
>> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO,
>> +				     MALI_C55_REG_GEN_VIDEO_ON_MASK, 0x00);
>> +	} else {
>> +		/*
>> +		 * One might reasonably expect the framesize to be set here
>> +		 * given it's configurable in .set_fmt(), but it's done in the
>> +		 * ISP subdevice's .s_stream() instead, as the same register is
>> +		 * also used to indicate the size of the data coming from the
>> +		 * sensor.
>> +		 */
>> +		mali_c55_tpg_configure(mali_c55, sd);
>> +		__v4l2_ctrl_handler_setup(sd->ctrl_handler);
>> +
>> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_TPG_CH0,
>> +				     MALI_C55_TEST_PATTERN_ON_OFF,
>> +				     MALI_C55_TEST_PATTERN_ON_OFF);
>> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO,
>> +				     MALI_C55_REG_GEN_VIDEO_ON_MASK,
>> +				     MALI_C55_REG_GEN_VIDEO_ON_MASK);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_video_ops mali_c55_tpg_video_ops = {
>> +	.s_stream = &mali_c55_tpg_s_stream,
>> +};
>> +
>> +static int mali_c55_tpg_enum_mbus_code(struct v4l2_subdev *sd,
>> +				       struct v4l2_subdev_state *state,
>> +				       struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +	if (code->pad >= sd->entity.num_pads)
>> +		return -EINVAL;
> This check is done by the framework, you can drop it here.
>
>> +
>> +	if (code->index >= ARRAY_SIZE(mali_c55_tpg_mbus_codes))
>> +		return -EINVAL;
>> +
>> +	code->code = mali_c55_tpg_mbus_codes[code->index];
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_tpg_enum_frame_size(struct v4l2_subdev *sd,
>> +					struct v4l2_subdev_state *state,
>> +					struct v4l2_subdev_frame_size_enum *fse)
>> +{
>> +	if (fse->index > 0 || fse->pad > sd->entity.num_pads)
>> +		return -EINVAL;
>> +
>> +	fse->min_width = MALI_C55_MIN_WIDTH;
>> +	fse->max_width = MALI_C55_MAX_WIDTH;
>> +	fse->min_height = MALI_C55_MIN_HEIGHT;
>> +	fse->max_height = MALI_C55_MAX_HEIGHT;
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_tpg_set_fmt(struct v4l2_subdev *sd,
>> +				struct v4l2_subdev_state *state,
>> +				struct v4l2_subdev_format *format)
>> +{
>> +	struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd);
>> +	struct v4l2_mbus_framefmt *fmt = &format->format;
>> +	int vblank_def, vblank_min;
>> +	unsigned int i;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(mali_c55_tpg_mbus_codes); i++) {
>> +		if (fmt->code == mali_c55_tpg_mbus_codes[i])
>> +			break;
>> +	}
>> +
>> +	if (i == ARRAY_SIZE(mali_c55_tpg_mbus_codes))
>> +		fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
>> +
>> +	/*
>> +	 * The TPG says that the test frame timing generation logic expects a
>> +	 * minimum framesize of 4x4 pixels, but given the rest of the ISP can't
>> +	 * handle anything smaller than 128x128 it seems pointless to allow a
>> +	 * smaller frame.
>> +	 */
>> +	clamp_t(unsigned int, fmt->width, MALI_C55_MIN_WIDTH,
>> +		MALI_C55_MAX_WIDTH);
>> +	clamp_t(unsigned int, fmt->height, MALI_C55_MIN_HEIGHT,
>> +		MALI_C55_MAX_HEIGHT);
>> +
>> +	*v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD) = *fmt;
>> +
> Shouldn't the controls below only be changed for the active format?


Yes! Thank you

>
>> +	__mali_c55_tpg_calc_vblank(fmt, &vblank_def, &vblank_min);
>> +	__v4l2_ctrl_modify_range(tpg->ctrls.vblank, vblank_min,
>> +				 MALI_C55_TPG_MAX_VBLANK, 1, vblank_def);
>> +	__v4l2_ctrl_s_ctrl(tpg->ctrls.vblank, vblank_def);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops mali_c55_tpg_pad_ops = {
>> +	.enum_mbus_code		= mali_c55_tpg_enum_mbus_code,
>> +	.enum_frame_size	= mali_c55_tpg_enum_frame_size,
>> +	.get_fmt		= v4l2_subdev_get_fmt,
>> +	.set_fmt		= mali_c55_tpg_set_fmt,
>> +};
>> +
>> +static const struct v4l2_subdev_ops mali_c55_tpg_ops = {
>> +	.video	= &mali_c55_tpg_video_ops,
>> +	.pad	= &mali_c55_tpg_pad_ops,
>> +};
>> +
>> +static int mali_c55_tpg_init_state(struct v4l2_subdev *sd,
>> +				   struct v4l2_subdev_state *sd_state)
>> +{
>> +	struct v4l2_mbus_framefmt *fmt;
>> +
>> +	fmt = v4l2_subdev_state_get_format(sd_state, MALI_C55_TPG_SRC_PAD);
> Can be assigned in the declaration.


How would you make it fit that way?

>
>> +
>> +	fmt->width = MALI_C55_DEFAULT_WIDTH;
>> +	fmt->height = MALI_C55_DEFAULT_HEIGHT;
>> +	fmt->field = V4L2_FIELD_NONE;
>> +	fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_internal_ops mali_c55_tpg_internal_ops = {
>> +	.init_state = mali_c55_tpg_init_state,
>> +};
>> +
>> +static int mali_c55_tpg_init_controls(struct mali_c55 *mali_c55)
>> +{
>> +	struct mali_c55_tpg_ctrls *ctrls = &mali_c55->tpg.ctrls;
>> +	struct v4l2_subdev *sd = &mali_c55->tpg.sd;
>> +	struct v4l2_mbus_framefmt *format;
>> +	struct v4l2_subdev_state *state;
>> +	int vblank_def, vblank_min;
>> +	int ret;
>> +
>> +	state = v4l2_subdev_lock_and_get_active_state(sd);
>> +	format = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD);
>> +
>> +	ret = v4l2_ctrl_handler_init(&ctrls->handler, 1);
>> +	if (ret)
>> +		goto err_unlock;
>> +
>> +	ctrls->test_pattern = v4l2_ctrl_new_std_menu_items(&ctrls->handler,
>> +				&mali_c55_tpg_ctrl_ops, V4L2_CID_TEST_PATTERN,
>> +				ARRAY_SIZE(mali_c55_tpg_test_pattern_menu) - 1,
>> +				0, 3, mali_c55_tpg_test_pattern_menu);
>> +
>> +	/*
>> +	 * We fix hblank at the minimum allowed value and control framerate
>> +	 * solely through the vblank control.
>> +	 */
>> +	ctrls->hblank = v4l2_ctrl_new_std(&ctrls->handler,
>> +				&mali_c55_tpg_ctrl_ops,
>> +				V4L2_CID_HBLANK, MALI_C55_TPG_FIXED_HBLANK,
>> +				MALI_C55_TPG_FIXED_HBLANK, 1,
>> +				MALI_C55_TPG_FIXED_HBLANK);
>> +	if (ctrls->hblank)
>> +		ctrls->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +
>> +	__mali_c55_tpg_calc_vblank(format, &vblank_def, &vblank_min);
>> +	ctrls->vblank = v4l2_ctrl_new_std(&ctrls->handler,
>> +					  &mali_c55_tpg_ctrl_ops,
>> +					  V4L2_CID_VBLANK, vblank_min,
>> +					  MALI_C55_TPG_MAX_VBLANK, 1,
>> +					  vblank_def);
>> +
>> +	if (ctrls->handler.error) {
>> +		dev_err(mali_c55->dev, "Error during v4l2 controls init\n");
>> +		ret = ctrls->handler.error;
>> +		goto err_free_handler;
>> +	}
>> +
>> +	ctrls->handler.lock = &mali_c55->tpg.lock;
>> +	mali_c55->tpg.sd.ctrl_handler = &ctrls->handler;
>> +
>> +	v4l2_subdev_unlock_state(state);
>> +
>> +	return 0;
>> +
>> +err_free_handler:
>> +	v4l2_ctrl_handler_free(&ctrls->handler);
>> +err_unlock:
>> +	v4l2_subdev_unlock_state(state);
>> +	return ret;
>> +}
>> +
>> +int mali_c55_register_tpg(struct mali_c55 *mali_c55)
>> +{
>> +	struct mali_c55_tpg *tpg = &mali_c55->tpg;
>> +	struct v4l2_subdev *sd = &tpg->sd;
>> +	struct media_pad *pad = &tpg->pad;
>> +	int ret;
>> +
>> +	mutex_init(&tpg->lock);
>> +
>> +	v4l2_subdev_init(sd, &mali_c55_tpg_ops);
>> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
>> +	sd->owner = THIS_MODULE;
>> +	sd->internal_ops = &mali_c55_tpg_internal_ops;
>> +	strscpy(sd->name, MALI_C55_DRIVER_NAME " tpg", sizeof(sd->name));
>> +
>> +	pad->flags = MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT;
>> +	ret = media_entity_pads_init(&sd->entity, 1, pad);
>> +	if (ret) {
>> +		dev_err(mali_c55->dev,
>> +			"Failed to initialize media entity pads\n");
>> +		goto err_destroy_mutex;
>> +	}
>> +
>> +	ret = v4l2_subdev_init_finalize(sd);
>> +	if (ret)
>> +		goto err_cleanup_media_entity;
>> +
>> +	ret = mali_c55_tpg_init_controls(mali_c55);
>> +	if (ret) {
>> +		dev_err(mali_c55->dev,
>> +			"Error initialising controls\n");
>> +		goto err_cleanup_subdev;
>> +	}
>> +
>> +	ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd);
>> +	if (ret) {
>> +		dev_err(mali_c55->dev, "Failed to register tpg subdev\n");
>> +		goto err_free_ctrl_handler;
>> +	}
>> +
>> +	/*
>> +	 * By default the colour settings lead to a very dim image that is
>> +	 * nearly indistinguishable from black on some monitor settings. Ramp
>> +	 * them up a bit so the image is brighter.
>> +	 */
>> +	mali_c55_write(mali_c55, MALI_C55_REG_TPG_R_BACKGROUND,
>> +		       MALI_C55_TPG_BACKGROUND_MAX);
>> +	mali_c55_write(mali_c55, MALI_C55_REG_TPG_G_BACKGROUND,
>> +		       MALI_C55_TPG_BACKGROUND_MAX);
>> +	mali_c55_write(mali_c55, MALI_C55_REG_TPG_B_BACKGROUND,
>> +		       MALI_C55_TPG_BACKGROUND_MAX);
>> +
>> +	tpg->mali_c55 = mali_c55;
>> +
>> +	return 0;
>> +
>> +err_free_ctrl_handler:
>> +	v4l2_ctrl_handler_free(&tpg->ctrls.handler);
>> +err_cleanup_subdev:
>> +	v4l2_subdev_cleanup(sd);
>> +err_cleanup_media_entity:
>> +	media_entity_cleanup(&sd->entity);
>> +err_destroy_mutex:
>> +	mutex_destroy(&tpg->lock);
>> +
>> +	return ret;
>> +}
>> +
>> +void mali_c55_unregister_tpg(struct mali_c55 *mali_c55)
>> +{
>> +	struct mali_c55_tpg *tpg = &mali_c55->tpg;
>> +
>> +	if (!tpg->mali_c55)
>> +		return;
>> +
>> +	v4l2_device_unregister_subdev(&tpg->sd);
>> +	v4l2_subdev_cleanup(&tpg->sd);
>> +	media_entity_cleanup(&tpg->sd.entity);
>> +	v4l2_ctrl_handler_free(&tpg->ctrls.handler);
>> +	mutex_destroy(&tpg->lock);
>> +}



More information about the linux-arm-kernel mailing list