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 30static 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 + uds->entity.index * VI6_UDS_OFFSET, data); 34} 35 36/* ----------------------------------------------------------------------------- 37 * Scaling Computation 38 */ 39 40void 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(&entity->subdev); 44 45 vsp1_uds_write(uds, dlb, VI6_UDS_ALPVAL, 46 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 */ 54static 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 */ 76static 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 */ 87static 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 103static 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 113static int uds_enum_mbus_code(struct v4l2_subdev *subdev, 114 struct v4l2_subdev_pad_config *cfg, 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, cfg, code, codes, 123 ARRAY_SIZE(codes)); 124} 125 126static int uds_enum_frame_size(struct v4l2_subdev *subdev, 127 struct v4l2_subdev_pad_config *cfg, 128 struct v4l2_subdev_frame_size_enum *fse) 129{ 130 struct vsp1_uds *uds = to_uds(subdev); 131 struct v4l2_subdev_pad_config *config; 132 struct v4l2_mbus_framefmt *format; 133 int ret = 0; 134 135 config = vsp1_entity_get_pad_config(&uds->entity, cfg, fse->which); 136 if (!config) 137 return -EINVAL; 138 139 format = vsp1_entity_get_pad_format(&uds->entity, config, 140 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_pad_config *config, 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 fmt->width = clamp(fmt->width, UDS_MIN_SIZE, UDS_MAX_SIZE); 182 fmt->height = clamp(fmt->height, UDS_MIN_SIZE, UDS_MAX_SIZE); 183 break; 184 185 case UDS_PAD_SOURCE: 186 /* The UDS scales but can't perform format conversion. */ 187 format = vsp1_entity_get_pad_format(&uds->entity, config, 188 UDS_PAD_SINK); 189 fmt->code = format->code; 190 191 uds_output_limits(format->width, &minimum, &maximum); 192 fmt->width = clamp(fmt->width, minimum, maximum); 193 uds_output_limits(format->height, &minimum, &maximum); 194 fmt->height = clamp(fmt->height, minimum, maximum); 195 break; 196 } 197 198 fmt->field = V4L2_FIELD_NONE; 199 fmt->colorspace = V4L2_COLORSPACE_SRGB; 200} 201 202static int uds_set_format(struct v4l2_subdev *subdev, 203 struct v4l2_subdev_pad_config *cfg, 204 struct v4l2_subdev_format *fmt) 205{ 206 struct vsp1_uds *uds = to_uds(subdev); 207 struct v4l2_subdev_pad_config *config; 208 struct v4l2_mbus_framefmt *format; 209 int ret = 0; 210 211 mutex_lock(&uds->entity.lock); 212 213 config = vsp1_entity_get_pad_config(&uds->entity, cfg, fmt->which); 214 if (!config) { 215 ret = -EINVAL; 216 goto done; 217 } 218 219 uds_try_format(uds, config, fmt->pad, &fmt->format); 220 221 format = vsp1_entity_get_pad_format(&uds->entity, config, fmt->pad); 222 *format = fmt->format; 223 224 if (fmt->pad == UDS_PAD_SINK) { 225 /* Propagate the format to the source pad. */ 226 format = vsp1_entity_get_pad_format(&uds->entity, config, 227 UDS_PAD_SOURCE); 228 *format = fmt->format; 229 230 uds_try_format(uds, config, UDS_PAD_SOURCE, format); 231 } 232 233done: 234 mutex_unlock(&uds->entity.lock); 235 return ret; 236} 237 238/* ----------------------------------------------------------------------------- 239 * V4L2 Subdevice Operations 240 */ 241 242static const struct v4l2_subdev_pad_ops uds_pad_ops = { 243 .init_cfg = vsp1_entity_init_cfg, 244 .enum_mbus_code = uds_enum_mbus_code, 245 .enum_frame_size = uds_enum_frame_size, 246 .get_fmt = vsp1_subdev_get_pad_format, 247 .set_fmt = uds_set_format, 248}; 249 250static const struct v4l2_subdev_ops uds_ops = { 251 .pad = &uds_pad_ops, 252}; 253 254/* ----------------------------------------------------------------------------- 255 * VSP1 Entity Operations 256 */ 257 258static void uds_configure_stream(struct vsp1_entity *entity, 259 struct vsp1_pipeline *pipe, 260 struct vsp1_dl_list *dl, 261 struct vsp1_dl_body *dlb) 262{ 263 struct vsp1_uds *uds = to_uds(&entity->subdev); 264 const struct v4l2_mbus_framefmt *output; 265 const struct v4l2_mbus_framefmt *input; 266 unsigned int hscale; 267 unsigned int vscale; 268 bool multitap; 269 270 input = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, 271 UDS_PAD_SINK); 272 output = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, 273 UDS_PAD_SOURCE); 274 275 hscale = uds_compute_ratio(input->width, output->width); 276 vscale = uds_compute_ratio(input->height, output->height); 277 278 dev_dbg(uds->entity.vsp1->dev, "hscale %u vscale %u\n", hscale, vscale); 279 280 /* 281 * Multi-tap scaling can't be enabled along with alpha scaling when 282 * scaling down with a factor lower than or equal to 1/2 in either 283 * direction. 284 */ 285 if (uds->scale_alpha && (hscale >= 8192 || vscale >= 8192)) 286 multitap = false; 287 else 288 multitap = true; 289 290 vsp1_uds_write(uds, dlb, VI6_UDS_CTRL, 291 (uds->scale_alpha ? VI6_UDS_CTRL_AON : 0) | 292 (multitap ? VI6_UDS_CTRL_BC : 0)); 293 294 vsp1_uds_write(uds, dlb, VI6_UDS_PASS_BWIDTH, 295 (uds_passband_width(hscale) 296 << VI6_UDS_PASS_BWIDTH_H_SHIFT) | 297 (uds_passband_width(vscale) 298 << VI6_UDS_PASS_BWIDTH_V_SHIFT)); 299 300 /* Set the scaling ratios. */ 301 vsp1_uds_write(uds, dlb, VI6_UDS_SCALE, 302 (hscale << VI6_UDS_SCALE_HFRAC_SHIFT) | 303 (vscale << VI6_UDS_SCALE_VFRAC_SHIFT)); 304} 305 306static void uds_configure_partition(struct vsp1_entity *entity, 307 struct vsp1_pipeline *pipe, 308 struct vsp1_dl_list *dl, 309 struct vsp1_dl_body *dlb) 310{ 311 struct vsp1_uds *uds = to_uds(&entity->subdev); 312 struct vsp1_partition *partition = pipe->partition; 313 const struct v4l2_mbus_framefmt *output; 314 315 output = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, 316 UDS_PAD_SOURCE); 317 318 /* Input size clipping. */ 319 vsp1_uds_write(uds, dlb, VI6_UDS_HSZCLIP, VI6_UDS_HSZCLIP_HCEN | 320 (0 << VI6_UDS_HSZCLIP_HCL_OFST_SHIFT) | 321 (partition->uds_sink.width 322 << VI6_UDS_HSZCLIP_HCL_SIZE_SHIFT)); 323 324 /* Output size clipping. */ 325 vsp1_uds_write(uds, dlb, VI6_UDS_CLIP_SIZE, 326 (partition->uds_source.width 327 << VI6_UDS_CLIP_SIZE_HSIZE_SHIFT) | 328 (output->height 329 << VI6_UDS_CLIP_SIZE_VSIZE_SHIFT)); 330} 331 332static unsigned int uds_max_width(struct vsp1_entity *entity, 333 struct vsp1_pipeline *pipe) 334{ 335 struct vsp1_uds *uds = to_uds(&entity->subdev); 336 const struct v4l2_mbus_framefmt *output; 337 const struct v4l2_mbus_framefmt *input; 338 unsigned int hscale; 339 340 input = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, 341 UDS_PAD_SINK); 342 output = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, 343 UDS_PAD_SOURCE); 344 hscale = output->width / input->width; 345 346 /* 347 * The maximum width of the UDS is 304 pixels. These are input pixels 348 * in the event of up-scaling, and output pixels in the event of 349 * downscaling. 350 * 351 * To support overlapping partition windows we clamp at units of 256 and 352 * the remaining pixels are reserved. 353 */ 354 if (hscale <= 2) 355 return 256; 356 else if (hscale <= 4) 357 return 512; 358 else if (hscale <= 8) 359 return 1024; 360 else 361 return 2048; 362} 363 364/* ----------------------------------------------------------------------------- 365 * Partition Algorithm Support 366 */ 367 368static void uds_partition(struct vsp1_entity *entity, 369 struct vsp1_pipeline *pipe, 370 struct vsp1_partition *partition, 371 unsigned int partition_idx, 372 struct vsp1_partition_window *window) 373{ 374 struct vsp1_uds *uds = to_uds(&entity->subdev); 375 const struct v4l2_mbus_framefmt *output; 376 const struct v4l2_mbus_framefmt *input; 377 378 /* Initialise the partition state. */ 379 partition->uds_sink = *window; 380 partition->uds_source = *window; 381 382 input = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, 383 UDS_PAD_SINK); 384 output = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, 385 UDS_PAD_SOURCE); 386 387 partition->uds_sink.width = window->width * input->width 388 / output->width; 389 partition->uds_sink.left = window->left * input->width 390 / output->width; 391 392 *window = partition->uds_sink; 393} 394 395static const struct vsp1_entity_operations uds_entity_ops = { 396 .configure_stream = uds_configure_stream, 397 .configure_partition = uds_configure_partition, 398 .max_width = uds_max_width, 399 .partition = uds_partition, 400}; 401 402/* ----------------------------------------------------------------------------- 403 * Initialization and Cleanup 404 */ 405 406struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index) 407{ 408 struct vsp1_uds *uds; 409 char name[6]; 410 int ret; 411 412 uds = devm_kzalloc(vsp1->dev, sizeof(*uds), GFP_KERNEL); 413 if (uds == NULL) 414 return ERR_PTR(-ENOMEM); 415 416 uds->entity.ops = &uds_entity_ops; 417 uds->entity.type = VSP1_ENTITY_UDS; 418 uds->entity.index = index; 419 420 sprintf(name, "uds.%u", index); 421 ret = vsp1_entity_init(vsp1, &uds->entity, name, 2, &uds_ops, 422 MEDIA_ENT_F_PROC_VIDEO_SCALER); 423 if (ret < 0) 424 return ERR_PTR(ret); 425 426 return uds; 427} 428