xref: /linux/drivers/media/platform/renesas/vsp1/vsp1_uds.c (revision fb7399cf2d0b33825b8039f95c45395c7deba25c)
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 
31 static 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 
41 void 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  */
55 static 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  */
77 static 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  */
88 static 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 
104 static 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 
114 static 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 
127 static 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 
161 done:
162 	mutex_unlock(&uds->entity.lock);
163 	return ret;
164 }
165 
166 static 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 
207 static 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 
237 done:
238 	mutex_unlock(&uds->entity.lock);
239 	return ret;
240 }
241 
242 /* -----------------------------------------------------------------------------
243  * V4L2 Subdevice Operations
244  */
245 
246 static 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 
253 static const struct v4l2_subdev_ops uds_ops = {
254 	.pad    = &uds_pad_ops,
255 };
256 
257 /* -----------------------------------------------------------------------------
258  * VSP1 Entity Operations
259  */
260 
261 static 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 
308 static 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 
330 static 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 
364 static 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 
389 static 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 
400 struct 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 }
422