[PATCH v5 05/16] media: mali-c55: Add Mali-C55 ISP driver

Jacopo Mondi jacopo.mondi at ideasonboard.com
Thu Jun 6 05:47:08 PDT 2024


Hi Laurent

On Fri, May 31, 2024 at 12:43:48AM GMT, Laurent Pinchart wrote:
> And now the second part of the review, addressing mali-c55-capture.c and
> mali-c55-resizer.c. I've reviewed the code from the bottom up, so some
> messages may be repeated in an order that seems weird. Sorry about that.
>

[snip]

A few replies/questions on the resizer module

> >
> > > +
> > > +#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..0a5a2969d3ce
> > > --- /dev/null
> > > +++ b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c
> > > @@ -0,0 +1,779 @@
> > > +// 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	(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;
>
> const
>
> > > +
> > > +	/* 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;
>
> const
>
> Once "[PATCH v4 0/3] media: v4l2-subdev: Support const-awareness in
> state accessors" gets merged, the state argument to this function can be
> made const too. Same for other functions, as applicable.
>
> > > +	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 u32 mali_c55_rzr_shift_mbus_code(u32 mbus_code)
> > > +{
> > > +	u32 corrected_code = 0;
> > > +
> > > +	/*
> > > +	 * The ISP takes input in a 20-bit format, but can only output 16-bit
> > > +	 * RAW bayer data (with the 4 least significant bits from the input
> > > +	 * being lost). Return the 16-bit version of the 20-bit input formats.
> > > +	 */
> > > +	switch (mbus_code) {
> > > +	case MEDIA_BUS_FMT_SBGGR20_1X20:
> > > +		corrected_code = MEDIA_BUS_FMT_SBGGR16_1X16;
> > > +		break;
> > > +	case MEDIA_BUS_FMT_SGBRG20_1X20:
> > > +		corrected_code = MEDIA_BUS_FMT_SGBRG16_1X16;
> > > +		break;
> > > +	case MEDIA_BUS_FMT_SGRBG20_1X20:
> > > +		corrected_code = MEDIA_BUS_FMT_SGRBG16_1X16;
> > > +		break;
> > > +	case MEDIA_BUS_FMT_SRGGB20_1X20:
> > > +		corrected_code = MEDIA_BUS_FMT_SRGGB16_1X16;
> > > +		break;
>
> Would it make sense to add the shifted code to mali_c55_isp_fmt ?
>
> > > +	}
> > > +
> > > +	return corrected_code;
> > > +}
> > > +
> > > +static int __mali_c55_rzr_set_routing(struct v4l2_subdev *sd,
> > > +				      struct v4l2_subdev_state *state,
> > > +				      struct v4l2_subdev_krouting *routing)
>
> I think the last argument can be const.

If I have to adjust the routing table instead of refusing it, it can't

>
> > > +{
> > > +	struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
> > > +						    sd);
>
> A to_mali_c55_resizer() static inline function would be useful. Same for
> other components, where applicable.
>
> > > +	unsigned int active_sink = UINT_MAX;
> > > +	struct v4l2_mbus_framefmt *src_fmt;
> > > +	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");
>
> No kernel log message with a level higher than dev_dbg() from
> user-controlled paths please, here and where applicable. This is to
> avoid giving applications an easy way to flood the kernel log.
>
> > > +			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;
> > > +	}
>
> The recommended handling of invalid routing is to adjust the routing
> table, not to return errors.
>

How should I adjust it ? The error here is due to the fact multiple
routes are set as active, which one should I make active ? the first
one ? Should I go and reset the flags in the subdev_route for the one
that has to be made non-active ?

> > > +
> > > +	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;
>
> There are other colorspace-related fields.
>
> > > +	fmt->field = V4L2_FIELD_NONE;
>
> I wonder if we should really update the sink pad format, or just
> propagate it. If we update it, I think it should be set to defaults on
> both sink pads, not just the active sink pad.
>

If only one route can be active, there will only be one state.stream_config
entry for the active sink, not for the other one (see
v4l2_subdev_init_stream_configs()), this mean I can't reset both sink
formats ?

> > > +
> > > +	if (active_sink == MALI_C55_RZR_SINK_PAD) {
> > > +		fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
> > > +
> > > +		crop->left = crop->top = 0;
>
> 		crop->left = 0;
> 		crop->top = 0;
>
> > > +		crop->width = MALI_C55_DEFAULT_WIDTH;
> > > +		crop->height = MALI_C55_DEFAULT_HEIGHT;
> > > +
> > > +		*compose = *crop;
> > > +	} else {
> > > +		fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
> > > +	}
> > > +
> > > +	/* Propagate the format to the source pad */
> > > +	src_fmt = v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD,
> > > +					       0);
> > > +	*src_fmt = *fmt;
> > > +
> > > +	/* In the event this is the bypass pad the mbus code needs correcting */
> > > +	if (active_sink == MALI_C55_RZR_SINK_BYPASS_PAD)
> > > +		src_fmt->code = mali_c55_rzr_shift_mbus_code(src_fmt->code);
> > > +
> > > +	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,
> > > +		 * downshifted to 16-bits.
> > > +		 */
> > > +
> > > +		if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) {
> > > +			if (code->index)
> > > +				return -EINVAL;
> > > +
> > > +			code->code = mali_c55_rzr_shift_mbus_code(sink_fmt->code);
> > > +			if (!code->code)
> > > +				return -EINVAL;
> > > +
> > > +			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);

also, clamp_t doens't clamp in place

        fmt->width = clamp_t...

> > > +	clamp_t(unsigned int, fmt->height,
> > > +		MALI_C55_MIN_HEIGHT, MALI_C55_MAX_HEIGHT);
>
> Please check comments for other components related to the colorspace
> fields, to decide how to handle them here.
>
> > > +
> > > +	sink_pad = mali_c55_rzr_get_active_sink(state);
> > > +	if (sink_pad == MALI_C55_RZR_SINK_PAD) {
>
> The selection here should depend on format->pad, not the active 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;

And DEFAULT_MEDIA_BUS_FMT is not one of the supported input media bus
codes

> > > +	}
> > > +
> > > +	*v4l2_subdev_state_get_format(state, sink_pad, 0) = *fmt;
> > > +	*v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD, 0) = *fmt;
>
> Propagation to the source pad, however, should depend on the active
> route. If format->pad is routed to the source pad, you should propagate,
> otherwise, you shouldn't.
>
> > > +
> > > +	return 0;

I ended up with

static int mali_c55_rsz_set_sink_fmt(struct v4l2_subdev *sd,
				     struct v4l2_subdev_state *state,
				     struct v4l2_subdev_format *format)
{
	struct v4l2_mbus_framefmt *fmt = &format->format;
	unsigned int active_sink;
	struct v4l2_rect *rect;

	/*
	 * Clamp to min/max and then reset crop and compose rectangles to the
	 * newly applied size.
	 */
	fmt->width = clamp_t(unsigned int, fmt->width, MALI_C55_MIN_WIDTH,
			     MALI_C55_MAX_WIDTH);
	fmt->height = clamp_t(unsigned int, fmt->height, MALI_C55_MIN_HEIGHT,
			      MALI_C55_MAX_HEIGHT);

	rect = v4l2_subdev_state_get_crop(state, format->pad);
	rect->left = 0;
	rect->top = 0;
	rect->width = fmt->width;
	rect->height = fmt->height;

	rect = v4l2_subdev_state_get_compose(state, format->pad);
	rect->left = 0;
	rect->top = 0;
	rect->width = fmt->width;
	rect->height = fmt->height;

	if (format->pad == MALI_C55_RSZ_SINK_BYPASS_PAD) {
		/*
		 * Make sure the media bus code is one of the supported
		 * ISP input media bus codes. Default it to SRGGB otherwise.
		 */
		if (!mali_c55_isp_is_format_supported(fmt->code))
			fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
	} else {
		fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
	}

	*v4l2_subdev_state_get_format(state, format->pad, 0) = *fmt;

	/* If format->pad is routed to the source pad, propagate the format. */
	active_sink = mali_c55_rsz_get_active_sink(state);
	if (active_sink == format->pad) {

		/* If the bypass route is used, downshift the code to 16bpp. */
		if (active_sink == MALI_C55_RSZ_SINK_BYPASS_PAD)
			fmt->code = mali_c55_rsz_shift_mbus_code(fmt->code);

		*v4l2_subdev_state_get_format(state,
					      MALI_C55_RSZ_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, downshifted to 16-bits.
> > > +		 */
> > > +		fmt->code = mali_c55_rzr_shift_mbus_code(sink_fmt->code);
> > > +		if (!fmt->code)
> > > +			return -EINVAL;
> > > +
> > > +		/* 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;
>
> I don't think this is right. This function sets the format on the source
> pad. Subdevs should propagate formats from the sink to the source, not
> the other way around.
>
> The only parameter that can be modified on the source pad (as far as I
> understand) is the media bus code. In the bypass path, I understand it's
> fixed, while in the other path, you can select between RGB and YUV. I
> think the following code is what you need to implement this function.
>
> 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;
>
> 	fmt = v4l2_subdev_state_get_format(state, format->pad);
>
> 	/* In the non-bypass path the output format can be selected. */
> 	if (mali_c55_rzr_get_active_sink(state) == MALI_C55_RZR_SINK_PAD) {
> 		unsigned int i;
>
> 		fmt->code = format->format.code;
>
> 		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))
> 			fmt->code = rzr_non_bypass_src_fmts[0];
> 	}
>
> 	format->format = *fmt;
>
> 	return 0;
> }

Almost. Your proposal doesn't adjust format->format.width/height

I think the following is more appropriate

static int mali_c55_rsz_set_source_fmt(struct v4l2_subdev *sd,
				       struct v4l2_subdev_state *state,
				       struct v4l2_subdev_format *format)
{
	struct v4l2_mbus_framefmt *fmt = &format->format;
	struct v4l2_mbus_framefmt *sink_fmt;
	struct v4l2_rect *sink_compose;
	unsigned int active_sink;

	active_sink = mali_c55_rsz_get_active_sink(state);
	sink_fmt = v4l2_subdev_state_get_format(state, active_sink, 0);
	sink_compose = v4l2_subdev_state_get_compose(state, active_sink, 0);

	/*
	 * The source pad format sizes come directly from the active sink pad
	 * compose rectangle.
	 */
	fmt->width = sink_compose->width;
	fmt->height = sink_compose->height;

	if (active_sink == MALI_C55_RSZ_SINK_PAD) {
		/*
		 * Regular processing pipe: RGB121212 can be color-space
		 * converted to YUV101010.
		 */
		unsigned int i;

		for (i = 0; i < ARRAY_SIZE(rsz_non_bypass_src_fmts); i++) {
			if (fmt->code == rsz_non_bypass_src_fmts[i])
				break;
		}

		if (i == ARRAY_SIZE(rsz_non_bypass_src_fmts))
			fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
	} else {
		/*
		 * Bypass pipe: the source format is the same as the bypass
		 * sink pad downshifted to 16bpp.
		 */
		fmt->code = mali_c55_rsz_shift_mbus_code(sink_fmt->code);
	}

	*v4l2_subdev_state_get_format(state, MALI_C55_RSZ_SOURCE_PAD) = *fmt;

	return 0;
}

I'll handle the colorspace fields as well

>
> > > +
> > > +		*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);
>
> I think you can drop this message.
>
> > > +		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);
>
> Nitpicking,
>
> 	if (format->pad == MALI_C55_RZR_SOURCE_PAD)
> 		return mali_c55_rzr_set_source_fmt(sd, state, format);
>
> 	return mali_c55_rzr_set_sink_fmt(sd, state, format);
>
> to match SOURCE_PAD and source_fmt.
>

Done at the expense a bit more verbose check

	if (format->pad == MALI_C55_RSZ_SINK_PAD ||
	    format->pad == MALI_C55_RSZ_SINK_BYPASS_PAD)
		return mali_c55_rsz_set_sink_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)
>
> Could this be handled through the .enable_streams() and
> .disable_streams() operations ? They ensure that the stream state stored
> internal is correct. That may not matter much today, but I think it will
> become increasingly important in the future for the V4L2 core.
>
> > > +{
> > > +	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;
>
> And hopefully you'll be able to replace this with
> v4l2_subdev_is_streaming(), introduced in "[PATCH v6 00/11] media:
> subdev: Improve stream enable/disable machinery" (Sakari has sent a pull
> request for v6.11 yesterday).
>
> > > +	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;
>
> I think this could be simplified.
>
> 	struct v4l2_subdev_route routes[2] = {
> 		{
> 			.sink_pad = MALI_C55_RZR_SINK_PAD,
> 			.source_pad = MALI_C55_RZR_SOURCE_PAD,
> 			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> 		}, {
> 			.sink_pad = MALI_C55_RZR_SINK_BYPASS_PAD,
> 			.source_pad = MALI_C55_RZR_SOURCE_PAD,
> 		},
> 	};
> 	struct v4l2_subdev_krouting routing = {
> 		.num_routes = rzr->num_routes,
> 		.routes = routes,
> 	};
>
> 	return __mali_c55_rzr_set_routing(sd, state, &routing);
>
> > > +}
> > > +
> > > +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)
> > > +{
> > > +	const unsigned int scaler_filt_coefmem_addrs[][2] = {
> > > +		[MALI_C55_RZR_FR] = {
> > > +			0x034A8, /* hfilt */
> > > +			0x044A8  /* vfilt */
> >
> > Lowercase hex constants.
>
> And addresses belong to the mali-c55-registers.h file.
>
> > > +		},
> > > +		[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 += sizeof(u32);
> > > +			vaddr += sizeof(u32);
> > > +		}
> > > +	}
>
> How about memcpy_toio() ? I suppose this function isn't
> performance sensitive, so maybe usage of mali_c55_write() is better from
> a consistency point of view.
>
> > > +}
> > > +
> > > +int mali_c55_register_resizers(struct mali_c55 *mali_c55)
> > > +{
> > > +	unsigned int i;
> > > +	int ret;
> > > +
> > > +	for (i = 0; i < MALI_C55_NUM_RZRS; ++i) {
>
> Moving the inner content to a separate mali_c55_register_resizer()
> function would increase readability I think, and remove usage of gotos.
> I would probably do the same for unregistration too, for consistency.
>
> > > +		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);
>
> Should this be done at stream start, given that power may be cut off
> between streaming sessions ?
>
> > > +
> > > +		v4l2_subdev_init(sd, &mali_c55_resizer_ops);
> > > +		sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE
> > > +			     | V4L2_SUBDEV_FL_STREAMS;
> > > +		sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
> > > +		sd->internal_ops = &mali_c55_resizer_internal_ops;
> > > +		snprintf(sd->name, ARRAY_SIZE(sd->name), "%s %s",
>
> 		snprintf(sd->name, ARRAY_SIZE(sd->name), "%s resizer %s",
>
> and drop the "resizer " prefix from mali_c55_resizer_names. You can also
> make mali_c55_resizer_names a local static const variable.
>
> > > +			 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);
> > > +	}
> > > +}



More information about the linux-arm-kernel mailing list