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