File Index
Files and directories
Sign in
FILE

vsp1_uds.c

File preview with syntax highlighting.

linux-6.19.14/drivers/media/platform/renesas/vsp1/vsp1_uds.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * vsp1_uds.c  --  R-Car VSP1 Up and Down Scaler
  4 *
  5 * Copyright (C) 2013-2014 Renesas Electronics Corporation
  6 *
  7 * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
  8 */
  9
 10#include <linux/device.h>
 11#include <linux/gfp.h>
 12
 13#include <media/v4l2-subdev.h>
 14
 15#include "vsp1.h"
 16#include "vsp1_dl.h"
 17#include "vsp1_entity.h"
 18#include "vsp1_pipe.h"
 19#include "vsp1_uds.h"
 20
 21#define UDS_MIN_SIZE				4U
 22#define UDS_MAX_SIZE				8190U
 23
 24#define UDS_MIN_FACTOR				0x0100
 25#define UDS_MAX_FACTOR				0xffff
 26
 27/* -----------------------------------------------------------------------------
 28 * Device Access
 29 */
 30
 31static inline void vsp1_uds_write(struct vsp1_uds *uds,
 32				  struct vsp1_dl_body *dlb, u32 reg, u32 data)
 33{
 34	vsp1_dl_body_write(dlb, reg + uds->entity.index * VI6_UDS_OFFSET, data);
 35}
 36
 37/* -----------------------------------------------------------------------------
 38 * Scaling Computation
 39 */
 40
 41void vsp1_uds_set_alpha(struct vsp1_entity *entity, struct vsp1_dl_body *dlb,
 42			unsigned int alpha)
 43{
 44	struct vsp1_uds *uds = to_uds(&entity->subdev);
 45
 46	vsp1_uds_write(uds, dlb, VI6_UDS_ALPVAL,
 47		       alpha << VI6_UDS_ALPVAL_VAL0_SHIFT);
 48}
 49
 50/*
 51 * uds_output_size - Return the output size for an input size and scaling ratio
 52 * @input: input size in pixels
 53 * @ratio: scaling ratio in U4.12 fixed-point format
 54 */
 55static unsigned int uds_output_size(unsigned int input, unsigned int ratio)
 56{
 57	if (ratio > 4096) {
 58		/* Down-scaling */
 59		unsigned int mp;
 60
 61		mp = ratio / 4096;
 62		mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4);
 63
 64		return (input - 1) / mp * mp * 4096 / ratio + 1;
 65	} else {
 66		/* Up-scaling */
 67		return (input - 1) * 4096 / ratio + 1;
 68	}
 69}
 70
 71/*
 72 * uds_output_limits - Return the min and max output sizes for an input size
 73 * @input: input size in pixels
 74 * @minimum: minimum output size (returned)
 75 * @maximum: maximum output size (returned)
 76 */
 77static void uds_output_limits(unsigned int input,
 78			      unsigned int *minimum, unsigned int *maximum)
 79{
 80	*minimum = max(uds_output_size(input, UDS_MAX_FACTOR), UDS_MIN_SIZE);
 81	*maximum = min(uds_output_size(input, UDS_MIN_FACTOR), UDS_MAX_SIZE);
 82}
 83
 84/*
 85 * uds_passband_width - Return the passband filter width for a scaling ratio
 86 * @ratio: scaling ratio in U4.12 fixed-point format
 87 */
 88static unsigned int uds_passband_width(unsigned int ratio)
 89{
 90	if (ratio >= 4096) {
 91		/* Down-scaling */
 92		unsigned int mp;
 93
 94		mp = ratio / 4096;
 95		mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4);
 96
 97		return 64 * 4096 * mp / ratio;
 98	} else {
 99		/* Up-scaling */
100		return 64;
101	}
102}
103
104static unsigned int uds_compute_ratio(unsigned int input, unsigned int output)
105{
106	/* TODO: This is an approximation that will need to be refined. */
107	return (input - 1) * 4096 / (output - 1);
108}
109
110/* -----------------------------------------------------------------------------
111 * V4L2 Subdevice Pad Operations
112 */
113
114static int uds_enum_mbus_code(struct v4l2_subdev *subdev,
115			      struct v4l2_subdev_state *sd_state,
116			      struct v4l2_subdev_mbus_code_enum *code)
117{
118	static const unsigned int codes[] = {
119		MEDIA_BUS_FMT_ARGB8888_1X32,
120		MEDIA_BUS_FMT_AYUV8_1X32,
121	};
122
123	return vsp1_subdev_enum_mbus_code(subdev, sd_state, code, codes,
124					  ARRAY_SIZE(codes));
125}
126
127static int uds_enum_frame_size(struct v4l2_subdev *subdev,
128			       struct v4l2_subdev_state *sd_state,
129			       struct v4l2_subdev_frame_size_enum *fse)
130{
131	struct vsp1_uds *uds = to_uds(subdev);
132	struct v4l2_subdev_state *state;
133	struct v4l2_mbus_framefmt *format;
134	int ret = 0;
135
136	state = vsp1_entity_get_state(&uds->entity, sd_state, fse->which);
137	if (!state)
138		return -EINVAL;
139
140	format = v4l2_subdev_state_get_format(state, UDS_PAD_SINK);
141
142	mutex_lock(&uds->entity.lock);
143
144	if (fse->index || fse->code != format->code) {
145		ret = -EINVAL;
146		goto done;
147	}
148
149	if (fse->pad == UDS_PAD_SINK) {
150		fse->min_width = UDS_MIN_SIZE;
151		fse->max_width = UDS_MAX_SIZE;
152		fse->min_height = UDS_MIN_SIZE;
153		fse->max_height = UDS_MAX_SIZE;
154	} else {
155		uds_output_limits(format->width, &fse->min_width,
156				  &fse->max_width);
157		uds_output_limits(format->height, &fse->min_height,
158				  &fse->max_height);
159	}
160
161done:
162	mutex_unlock(&uds->entity.lock);
163	return ret;
164}
165
166static void uds_try_format(struct vsp1_uds *uds,
167			   struct v4l2_subdev_state *sd_state,
168			   unsigned int pad, struct v4l2_mbus_framefmt *fmt)
169{
170	struct v4l2_mbus_framefmt *format;
171	unsigned int minimum;
172	unsigned int maximum;
173
174	switch (pad) {
175	case UDS_PAD_SINK:
176		/* Default to YUV if the requested format is not supported. */
177		if (fmt->code != MEDIA_BUS_FMT_ARGB8888_1X32 &&
178		    fmt->code != MEDIA_BUS_FMT_AYUV8_1X32)
179			fmt->code = MEDIA_BUS_FMT_AYUV8_1X32;
180
181		vsp1_entity_adjust_color_space(fmt);
182
183		fmt->width = clamp(fmt->width, UDS_MIN_SIZE, UDS_MAX_SIZE);
184		fmt->height = clamp(fmt->height, UDS_MIN_SIZE, UDS_MAX_SIZE);
185		break;
186
187	case UDS_PAD_SOURCE:
188		/* The UDS scales but can't perform format conversion. */
189		format = v4l2_subdev_state_get_format(sd_state, UDS_PAD_SINK);
190		fmt->code = format->code;
191
192		fmt->colorspace = format->colorspace;
193		fmt->xfer_func = format->xfer_func;
194		fmt->ycbcr_enc = format->ycbcr_enc;
195		fmt->quantization = format->quantization;
196
197		uds_output_limits(format->width, &minimum, &maximum);
198		fmt->width = clamp(fmt->width, minimum, maximum);
199		uds_output_limits(format->height, &minimum, &maximum);
200		fmt->height = clamp(fmt->height, minimum, maximum);
201		break;
202	}
203
204	fmt->field = V4L2_FIELD_NONE;
205}
206
207static int uds_set_format(struct v4l2_subdev *subdev,
208			  struct v4l2_subdev_state *sd_state,
209			  struct v4l2_subdev_format *fmt)
210{
211	struct vsp1_uds *uds = to_uds(subdev);
212	struct v4l2_subdev_state *state;
213	struct v4l2_mbus_framefmt *format;
214	int ret = 0;
215
216	mutex_lock(&uds->entity.lock);
217
218	state = vsp1_entity_get_state(&uds->entity, sd_state, fmt->which);
219	if (!state) {
220		ret = -EINVAL;
221		goto done;
222	}
223
224	uds_try_format(uds, state, fmt->pad, &fmt->format);
225
226	format = v4l2_subdev_state_get_format(state, fmt->pad);
227	*format = fmt->format;
228
229	if (fmt->pad == UDS_PAD_SINK) {
230		/* Propagate the format to the source pad. */
231		format = v4l2_subdev_state_get_format(state, UDS_PAD_SOURCE);
232		*format = fmt->format;
233
234		uds_try_format(uds, state, UDS_PAD_SOURCE, format);
235	}
236
237done:
238	mutex_unlock(&uds->entity.lock);
239	return ret;
240}
241
242/* -----------------------------------------------------------------------------
243 * V4L2 Subdevice Operations
244 */
245
246static const struct v4l2_subdev_pad_ops uds_pad_ops = {
247	.enum_mbus_code = uds_enum_mbus_code,
248	.enum_frame_size = uds_enum_frame_size,
249	.get_fmt = vsp1_subdev_get_pad_format,
250	.set_fmt = uds_set_format,
251};
252
253static const struct v4l2_subdev_ops uds_ops = {
254	.pad    = &uds_pad_ops,
255};
256
257/* -----------------------------------------------------------------------------
258 * VSP1 Entity Operations
259 */
260
261static void uds_configure_stream(struct vsp1_entity *entity,
262				 struct v4l2_subdev_state *state,
263				 struct vsp1_pipeline *pipe,
264				 struct vsp1_dl_list *dl,
265				 struct vsp1_dl_body *dlb)
266{
267	struct vsp1_uds *uds = to_uds(&entity->subdev);
268	const struct v4l2_mbus_framefmt *output;
269	const struct v4l2_mbus_framefmt *input;
270	unsigned int hscale;
271	unsigned int vscale;
272	bool multitap;
273
274	input = v4l2_subdev_state_get_format(state, UDS_PAD_SINK);
275	output = v4l2_subdev_state_get_format(state, UDS_PAD_SOURCE);
276
277	hscale = uds_compute_ratio(input->width, output->width);
278	vscale = uds_compute_ratio(input->height, output->height);
279
280	dev_dbg(uds->entity.vsp1->dev, "hscale %u vscale %u\n", hscale, vscale);
281
282	/*
283	 * Multi-tap scaling can't be enabled along with alpha scaling when
284	 * scaling down with a factor lower than or equal to 1/2 in either
285	 * direction.
286	 */
287	if (uds->scale_alpha && (hscale >= 8192 || vscale >= 8192))
288		multitap = false;
289	else
290		multitap = true;
291
292	vsp1_uds_write(uds, dlb, VI6_UDS_CTRL,
293		       (uds->scale_alpha ? VI6_UDS_CTRL_AON : 0) |
294		       (multitap ? VI6_UDS_CTRL_BC : 0));
295
296	vsp1_uds_write(uds, dlb, VI6_UDS_PASS_BWIDTH,
297		       (uds_passband_width(hscale)
298				<< VI6_UDS_PASS_BWIDTH_H_SHIFT) |
299		       (uds_passband_width(vscale)
300				<< VI6_UDS_PASS_BWIDTH_V_SHIFT));
301
302	/* Set the scaling ratios. */
303	vsp1_uds_write(uds, dlb, VI6_UDS_SCALE,
304		       (hscale << VI6_UDS_SCALE_HFRAC_SHIFT) |
305		       (vscale << VI6_UDS_SCALE_VFRAC_SHIFT));
306}
307
308static void uds_configure_partition(struct vsp1_entity *entity,
309				    struct vsp1_pipeline *pipe,
310				    const struct vsp1_partition *partition,
311				    struct vsp1_dl_list *dl,
312				    struct vsp1_dl_body *dlb)
313{
314	struct vsp1_uds *uds = to_uds(&entity->subdev);
315
316	/* Input size clipping. */
317	vsp1_uds_write(uds, dlb, VI6_UDS_HSZCLIP, VI6_UDS_HSZCLIP_HCEN |
318		       (0 << VI6_UDS_HSZCLIP_HCL_OFST_SHIFT) |
319		       (partition->uds_sink.width
320				<< VI6_UDS_HSZCLIP_HCL_SIZE_SHIFT));
321
322	/* Output size clipping. */
323	vsp1_uds_write(uds, dlb, VI6_UDS_CLIP_SIZE,
324		       (partition->uds_source.width
325				<< VI6_UDS_CLIP_SIZE_HSIZE_SHIFT) |
326		       (partition->uds_source.height
327				<< VI6_UDS_CLIP_SIZE_VSIZE_SHIFT));
328}
329
330static unsigned int uds_max_width(struct vsp1_entity *entity,
331				  struct v4l2_subdev_state *state,
332				  struct vsp1_pipeline *pipe)
333{
334	const struct v4l2_mbus_framefmt *output;
335	const struct v4l2_mbus_framefmt *input;
336	unsigned int hscale;
337
338	input = v4l2_subdev_state_get_format(state, UDS_PAD_SINK);
339	output = v4l2_subdev_state_get_format(state, UDS_PAD_SOURCE);
340	hscale = output->width / input->width;
341
342	/*
343	 * The maximum width of the UDS is 304 pixels. These are input pixels
344	 * in the event of up-scaling, and output pixels in the event of
345	 * downscaling.
346	 *
347	 * To support overlapping partition windows we clamp at units of 256 and
348	 * the remaining pixels are reserved.
349	 */
350	if (hscale <= 2)
351		return 256;
352	else if (hscale <= 4)
353		return 512;
354	else if (hscale <= 8)
355		return 1024;
356	else
357		return 2048;
358}
359
360/* -----------------------------------------------------------------------------
361 * Partition Algorithm Support
362 */
363
364static void uds_partition(struct vsp1_entity *entity,
365			  struct v4l2_subdev_state *state,
366			  struct vsp1_pipeline *pipe,
367			  struct vsp1_partition *partition,
368			  unsigned int partition_idx,
369			  struct v4l2_rect *window)
370{
371	const struct v4l2_mbus_framefmt *output;
372	const struct v4l2_mbus_framefmt *input;
373
374	input = v4l2_subdev_state_get_format(state, UDS_PAD_SINK);
375	output = v4l2_subdev_state_get_format(state, UDS_PAD_SOURCE);
376
377	partition->uds_sink.width = window->width * input->width
378				  / output->width;
379	partition->uds_sink.left = window->left * input->width
380				 / output->width;
381	partition->uds_sink.height = input->height;
382	partition->uds_sink.top = 0;
383
384	partition->uds_source = *window;
385
386	*window = partition->uds_sink;
387}
388
389static const struct vsp1_entity_operations uds_entity_ops = {
390	.configure_stream = uds_configure_stream,
391	.configure_partition = uds_configure_partition,
392	.max_width = uds_max_width,
393	.partition = uds_partition,
394};
395
396/* -----------------------------------------------------------------------------
397 * Initialization and Cleanup
398 */
399
400struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index)
401{
402	struct vsp1_uds *uds;
403	char name[6];
404	int ret;
405
406	uds = devm_kzalloc(vsp1->dev, sizeof(*uds), GFP_KERNEL);
407	if (uds == NULL)
408		return ERR_PTR(-ENOMEM);
409
410	uds->entity.ops = &uds_entity_ops;
411	uds->entity.type = VSP1_ENTITY_UDS;
412	uds->entity.index = index;
413
414	sprintf(name, "uds.%u", index);
415	ret = vsp1_entity_init(vsp1, &uds->entity, name, 2, &uds_ops,
416			       MEDIA_ENT_F_PROC_VIDEO_SCALER);
417	if (ret < 0)
418		return ERR_PTR(ret);
419
420	return uds;
421}
~/ Read-only file preview.