1/* 2 * vsp1_uds.c -- R-Car VSP1 Up and Down Scaler 3 * 4 * Copyright (C) 2013-2014 Renesas Electronics Corporation 5 * 6 * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 */ 13 14#include <linux/device.h> 15#include <linux/gfp.h> 16 17#include <media/v4l2-subdev.h> 18 19#include "vsp1.h" 20#include "vsp1_uds.h" 21 22#define UDS_MIN_SIZE 4U 23#define UDS_MAX_SIZE 8190U 24 25#define UDS_MIN_FACTOR 0x0100 26#define UDS_MAX_FACTOR 0xffff 27 28/* ----------------------------------------------------------------------------- 29 * Device Access 30 */ 31 32static inline u32 vsp1_uds_read(struct vsp1_uds *uds, u32 reg) 33{ 34 return vsp1_read(uds->entity.vsp1, 35 reg + uds->entity.index * VI6_UDS_OFFSET); 36} 37 38static inline void vsp1_uds_write(struct vsp1_uds *uds, u32 reg, u32 data) 39{ 40 vsp1_write(uds->entity.vsp1, 41 reg + uds->entity.index * VI6_UDS_OFFSET, data); 42} 43 44/* ----------------------------------------------------------------------------- 45 * Scaling Computation 46 */ 47 48void vsp1_uds_set_alpha(struct vsp1_uds *uds, unsigned int alpha) 49{ 50 vsp1_uds_write(uds, VI6_UDS_ALPVAL, alpha << VI6_UDS_ALPVAL_VAL0_SHIFT); 51} 52 53/* 54 * uds_output_size - Return the output size for an input size and scaling ratio 55 * @input: input size in pixels 56 * @ratio: scaling ratio in U4.12 fixed-point format 57 */ 58static unsigned int uds_output_size(unsigned int input, unsigned int ratio) 59{ 60 if (ratio > 4096) { 61 /* Down-scaling */ 62 unsigned int mp; 63 64 mp = ratio / 4096; 65 mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4); 66 67 return (input - 1) / mp * mp * 4096 / ratio + 1; 68 } else { 69 /* Up-scaling */ 70 return (input - 1) * 4096 / ratio + 1; 71 } 72} 73 74/* 75 * uds_output_limits - Return the min and max output sizes for an input size 76 * @input: input size in pixels 77 * @minimum: minimum output size (returned) 78 * @maximum: maximum output size (returned) 79 */ 80static void uds_output_limits(unsigned int input, 81 unsigned int *minimum, unsigned int *maximum) 82{ 83 *minimum = max(uds_output_size(input, UDS_MAX_FACTOR), UDS_MIN_SIZE); 84 *maximum = min(uds_output_size(input, UDS_MIN_FACTOR), UDS_MAX_SIZE); 85} 86 87/* 88 * uds_passband_width - Return the passband filter width for a scaling ratio 89 * @ratio: scaling ratio in U4.12 fixed-point format 90 */ 91static unsigned int uds_passband_width(unsigned int ratio) 92{ 93 if (ratio >= 4096) { 94 /* Down-scaling */ 95 unsigned int mp; 96 97 mp = ratio / 4096; 98 mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4); 99 100 return 64 * 4096 * mp / ratio; 101 } else { 102 /* Up-scaling */ 103 return 64; 104 } 105} 106 107static unsigned int uds_compute_ratio(unsigned int input, unsigned int output) 108{ 109 /* TODO: This is an approximation that will need to be refined. */ 110 return (input - 1) * 4096 / (output - 1); 111} 112 113/* ----------------------------------------------------------------------------- 114 * V4L2 Subdevice Core Operations 115 */ 116 117static int uds_s_stream(struct v4l2_subdev *subdev, int enable) 118{ 119 struct vsp1_uds *uds = to_uds(subdev); 120 const struct v4l2_mbus_framefmt *output; 121 const struct v4l2_mbus_framefmt *input; 122 unsigned int hscale; 123 unsigned int vscale; 124 bool multitap; 125 126 if (!enable) 127 return 0; 128 129 input = &uds->entity.formats[UDS_PAD_SINK]; 130 output = &uds->entity.formats[UDS_PAD_SOURCE]; 131 132 hscale = uds_compute_ratio(input->width, output->width); 133 vscale = uds_compute_ratio(input->height, output->height); 134 135 dev_dbg(uds->entity.vsp1->dev, "hscale %u vscale %u\n", hscale, vscale); 136 137 /* Multi-tap scaling can't be enabled along with alpha scaling when 138 * scaling down with a factor lower than or equal to 1/2 in either 139 * direction. 140 */ 141 if (uds->scale_alpha && (hscale >= 8192 || vscale >= 8192)) 142 multitap = false; 143 else 144 multitap = true; 145 146 vsp1_uds_write(uds, VI6_UDS_CTRL, 147 (uds->scale_alpha ? VI6_UDS_CTRL_AON : 0) | 148 (multitap ? VI6_UDS_CTRL_BC : 0)); 149 150 vsp1_uds_write(uds, VI6_UDS_PASS_BWIDTH, 151 (uds_passband_width(hscale) 152 << VI6_UDS_PASS_BWIDTH_H_SHIFT) | 153 (uds_passband_width(vscale) 154 << VI6_UDS_PASS_BWIDTH_V_SHIFT)); 155 156 /* Set the scaling ratios and the output size. */ 157 vsp1_uds_write(uds, VI6_UDS_SCALE, 158 (hscale << VI6_UDS_SCALE_HFRAC_SHIFT) | 159 (vscale << VI6_UDS_SCALE_VFRAC_SHIFT)); 160 vsp1_uds_write(uds, VI6_UDS_CLIP_SIZE, 161 (output->width << VI6_UDS_CLIP_SIZE_HSIZE_SHIFT) | 162 (output->height << VI6_UDS_CLIP_SIZE_VSIZE_SHIFT)); 163 164 return 0; 165} 166 167/* ----------------------------------------------------------------------------- 168 * V4L2 Subdevice Pad Operations 169 */ 170 171static int uds_enum_mbus_code(struct v4l2_subdev *subdev, 172 struct v4l2_subdev_pad_config *cfg, 173 struct v4l2_subdev_mbus_code_enum *code) 174{ 175 static const unsigned int codes[] = { 176 MEDIA_BUS_FMT_ARGB8888_1X32, 177 MEDIA_BUS_FMT_AYUV8_1X32, 178 }; 179 struct vsp1_uds *uds = to_uds(subdev); 180 181 if (code->pad == UDS_PAD_SINK) { 182 if (code->index >= ARRAY_SIZE(codes)) 183 return -EINVAL; 184 185 code->code = codes[code->index]; 186 } else { 187 struct v4l2_mbus_framefmt *format; 188 189 /* The UDS can't perform format conversion, the sink format is 190 * always identical to the source format. 191 */ 192 if (code->index) 193 return -EINVAL; 194 195 format = vsp1_entity_get_pad_format(&uds->entity, cfg, 196 UDS_PAD_SINK, code->which); 197 code->code = format->code; 198 } 199 200 return 0; 201} 202 203static int uds_enum_frame_size(struct v4l2_subdev *subdev, 204 struct v4l2_subdev_pad_config *cfg, 205 struct v4l2_subdev_frame_size_enum *fse) 206{ 207 struct vsp1_uds *uds = to_uds(subdev); 208 struct v4l2_mbus_framefmt *format; 209 210 format = vsp1_entity_get_pad_format(&uds->entity, cfg, 211 UDS_PAD_SINK, fse->which); 212 213 if (fse->index || fse->code != format->code) 214 return -EINVAL; 215 216 if (fse->pad == UDS_PAD_SINK) { 217 fse->min_width = UDS_MIN_SIZE; 218 fse->max_width = UDS_MAX_SIZE; 219 fse->min_height = UDS_MIN_SIZE; 220 fse->max_height = UDS_MAX_SIZE; 221 } else { 222 uds_output_limits(format->width, &fse->min_width, 223 &fse->max_width); 224 uds_output_limits(format->height, &fse->min_height, 225 &fse->max_height); 226 } 227 228 return 0; 229} 230 231static int uds_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, 232 struct v4l2_subdev_format *fmt) 233{ 234 struct vsp1_uds *uds = to_uds(subdev); 235 236 fmt->format = *vsp1_entity_get_pad_format(&uds->entity, cfg, fmt->pad, 237 fmt->which); 238 239 return 0; 240} 241 242static void uds_try_format(struct vsp1_uds *uds, struct v4l2_subdev_pad_config *cfg, 243 unsigned int pad, struct v4l2_mbus_framefmt *fmt, 244 enum v4l2_subdev_format_whence which) 245{ 246 struct v4l2_mbus_framefmt *format; 247 unsigned int minimum; 248 unsigned int maximum; 249 250 switch (pad) { 251 case UDS_PAD_SINK: 252 /* Default to YUV if the requested format is not supported. */ 253 if (fmt->code != MEDIA_BUS_FMT_ARGB8888_1X32 && 254 fmt->code != MEDIA_BUS_FMT_AYUV8_1X32) 255 fmt->code = MEDIA_BUS_FMT_AYUV8_1X32; 256 257 fmt->width = clamp(fmt->width, UDS_MIN_SIZE, UDS_MAX_SIZE); 258 fmt->height = clamp(fmt->height, UDS_MIN_SIZE, UDS_MAX_SIZE); 259 break; 260 261 case UDS_PAD_SOURCE: 262 /* The UDS scales but can't perform format conversion. */ 263 format = vsp1_entity_get_pad_format(&uds->entity, cfg, 264 UDS_PAD_SINK, which); 265 fmt->code = format->code; 266 267 uds_output_limits(format->width, &minimum, &maximum); 268 fmt->width = clamp(fmt->width, minimum, maximum); 269 uds_output_limits(format->height, &minimum, &maximum); 270 fmt->height = clamp(fmt->height, minimum, maximum); 271 break; 272 } 273 274 fmt->field = V4L2_FIELD_NONE; 275 fmt->colorspace = V4L2_COLORSPACE_SRGB; 276} 277 278static int uds_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, 279 struct v4l2_subdev_format *fmt) 280{ 281 struct vsp1_uds *uds = to_uds(subdev); 282 struct v4l2_mbus_framefmt *format; 283 284 uds_try_format(uds, cfg, fmt->pad, &fmt->format, fmt->which); 285 286 format = vsp1_entity_get_pad_format(&uds->entity, cfg, fmt->pad, 287 fmt->which); 288 *format = fmt->format; 289 290 if (fmt->pad == UDS_PAD_SINK) { 291 /* Propagate the format to the source pad. */ 292 format = vsp1_entity_get_pad_format(&uds->entity, cfg, 293 UDS_PAD_SOURCE, fmt->which); 294 *format = fmt->format; 295 296 uds_try_format(uds, cfg, UDS_PAD_SOURCE, format, fmt->which); 297 } 298 299 return 0; 300} 301 302/* ----------------------------------------------------------------------------- 303 * V4L2 Subdevice Operations 304 */ 305 306static struct v4l2_subdev_video_ops uds_video_ops = { 307 .s_stream = uds_s_stream, 308}; 309 310static struct v4l2_subdev_pad_ops uds_pad_ops = { 311 .enum_mbus_code = uds_enum_mbus_code, 312 .enum_frame_size = uds_enum_frame_size, 313 .get_fmt = uds_get_format, 314 .set_fmt = uds_set_format, 315}; 316 317static struct v4l2_subdev_ops uds_ops = { 318 .video = &uds_video_ops, 319 .pad = &uds_pad_ops, 320}; 321 322/* ----------------------------------------------------------------------------- 323 * Initialization and Cleanup 324 */ 325 326struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index) 327{ 328 struct v4l2_subdev *subdev; 329 struct vsp1_uds *uds; 330 int ret; 331 332 uds = devm_kzalloc(vsp1->dev, sizeof(*uds), GFP_KERNEL); 333 if (uds == NULL) 334 return ERR_PTR(-ENOMEM); 335 336 uds->entity.type = VSP1_ENTITY_UDS; 337 uds->entity.index = index; 338 339 ret = vsp1_entity_init(vsp1, &uds->entity, 2); 340 if (ret < 0) 341 return ERR_PTR(ret); 342 343 /* Initialize the V4L2 subdev. */ 344 subdev = &uds->entity.subdev; 345 v4l2_subdev_init(subdev, &uds_ops); 346 347 subdev->entity.ops = &vsp1_media_ops; 348 subdev->internal_ops = &vsp1_subdev_internal_ops; 349 snprintf(subdev->name, sizeof(subdev->name), "%s uds.%u", 350 dev_name(vsp1->dev), index); 351 v4l2_set_subdevdata(subdev, uds); 352 subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; 353 354 vsp1_entity_init_formats(subdev, NULL); 355 356 return uds; 357} 358