// SPDX-License-Identifier: GPL-2.0+
/*
* Driver for Renesas R-Car VIN
*
* Copyright (C) 2016 Renesas Electronics Corp.
* Copyright (C) 2011-2013 Renesas Solutions Corp.
* Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com>
* Copyright (C) 2008 Magnus Damm
*
* Based on the soc-camera rcar_vin driver
*/
#include <linux/pm_runtime.h>
#include <media/v4l2-event.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mc.h>
#include <media/v4l2-rect.h>
#include "rcar-vin.h"
#define RVIN_DEFAULT_FORMAT V4L2_PIX_FMT_YUYV
#define RVIN_DEFAULT_WIDTH 800
#define RVIN_DEFAULT_HEIGHT 600
#define RVIN_DEFAULT_FIELD V4L2_FIELD_NONE
#define RVIN_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB
/* -----------------------------------------------------------------------------
* Format Conversions
*/
static const struct rvin_video_format rvin_formats[] = {
{
.fourcc = V4L2_PIX_FMT_NV16,
.bpp = 1,
},
{
.fourcc = V4L2_PIX_FMT_YUYV,
.bpp = 2,
},
{
.fourcc = V4L2_PIX_FMT_UYVY,
.bpp = 2,
},
{
.fourcc = V4L2_PIX_FMT_RGB565,
.bpp = 2,
},
{
.fourcc = V4L2_PIX_FMT_XRGB555,
.bpp = 2,
},
{
.fourcc = V4L2_PIX_FMT_XBGR32,
.bpp = 4,
},
{
.fourcc = V4L2_PIX_FMT_ARGB555,
.bpp = 2,
},
{
.fourcc = V4L2_PIX_FMT_ABGR32,
.bpp = 4,
},
};
const struct rvin_video_format *rvin_format_from_pixel(struct rvin_dev *vin,
u32 pixelformat)
{
int i;
if (vin->info->model == RCAR_M1 && pixelformat == V4L2_PIX_FMT_XBGR32)
return NULL;
for (i = 0; i < ARRAY_SIZE(rvin_formats); i++)
if (rvin_formats[i].fourcc == pixelformat)
return rvin_formats + i;
return NULL;
}
static u32 rvin_format_bytesperline(struct rvin_dev *vin,
struct v4l2_pix_format *pix)
{
const struct rvin_video_format *fmt;
u32 align;
fmt = rvin_format_from_pixel(vin, pix->pixelformat);
if (WARN_ON(!fmt))
return -EINVAL;
align = pix->pixelformat == V4L2_PIX_FMT_NV16 ? 0x20 : 0x10;
return ALIGN(pix->width, align) * fmt->bpp;
}
static u32 rvin_format_sizeimage(struct v4l2_pix_format *pix)
{
if (pix->pixelformat == V4L2_PIX_FMT_NV16)
return pix->bytesperline * pix->height * 2;
return pix->bytesperline * pix->height;
}
static void rvin_format_align(struct rvin_dev *vin, struct v4l2_pix_format *pix)
{
u32 walign;
if (!rvin_format_from_pixel(vin, pix->pixelformat))
pix->pixelformat = RVIN_DEFAULT_FORMAT;
switch (pix->field) {
case V4L2_FIELD_TOP:
case V4L2_FIELD_BOTTOM:
case V4L2_FIELD_NONE:
case V4L2_FIELD_INTERLACED_TB:
case V4L2_FIELD_INTERLACED_BT:
case V4L2_FIELD_INTERLACED:
case V4L2_FIELD_ALTERNATE:
break;
default:
pix->field = RVIN_DEFAULT_FIELD;
break;
}
/* HW limit width to a multiple of 32 (2^5) for NV16 else 2 (2^1) */
walign = vin->format.pixelformat == V4L2_PIX_FMT_NV16 ? 5 : 1;
/* Limit to VIN capabilities */
v4l_bound_align_image(&pix->width, 2, vin->info->max_width, walign,
&pix->height, 4, vin->info->max_height, 2, 0);
pix->bytesperline = rvin_format_bytesperline(vin, pix);
pix->sizeimage = rvin_format_sizeimage(pix);
vin_dbg(vin, "Format %ux%u bpl: %u size: %u\n",
pix->width, pix->height, pix->bytesperline, pix->sizeimage);
}
/* -----------------------------------------------------------------------------
* V4L2
*/
static int rvin_reset_format(struct rvin_dev *vin)
{
struct v4l2_subdev_format fmt = {
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
.pad = vin->parallel->source_pad,
};
int ret;
ret = v4l2_subdev_call(vin_to_source(vin), pad, get_fmt, NULL, &fmt);
if (ret)
return ret;
v4l2_fill_pix_format(&vin->format, &fmt.format);
vin->src_rect.top = 0;
vin->src_rect.left = 0;
vin->src_rect.width = vin->format.width;
vin->src_rect.height = vin->format.height;
/* Make use of the hardware interlacer by default. */
if (vin->format.field =