/*
* Copyright (C) 2015 Industrial Research Institute for Automation
* and Measurements PIAP
*
* Written by Krzysztof Ha?asa.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <media/v4l2-common.h>
#include <media/v4l2-event.h>
#include "tw686x-kh.h"
#include "tw686x-kh-regs.h"
#define MAX_SG_ENTRY_SIZE (/* 8192 - 128 */ 4096)
#define MAX_SG_DESC_COUNT 256 /* PAL 704x576 needs up to 198 4-KB pages */
static const struct tw686x_format formats[] = {
{
.name = "4:2:2 packed, UYVY", /* aka Y422 */
.fourcc = V4L2_PIX_FMT_UYVY,
.mode = 0,
.depth = 16,
}, {
#if 0
.name = "4:2:0 packed, YUV",
.mode = 1, /* non-standard */
.depth = 12,
}, {
.name = "4:1:1 packed, YUV",
.mode = 2, /* non-standard */
.depth = 12,
}, {
#endif
.name = "4:1:1 packed, YUV",
.fourcc = V4L2_PIX_FMT_Y41P,
.mode = 3,
.depth = 12,
}, {
.name = "15 bpp RGB",
.fourcc = V4L2_PIX_FMT_RGB555,
.mode = 4,
.depth = 16,
}, {
.name = "16 bpp RGB",
.fourcc = V4L2_PIX_FMT_RGB565,
.mode = 5,
.depth = 16,
}, {
.name = "4:2:2 packed, YUYV",
.fourcc = V4L2_PIX_FMT_YUYV,
.mode = 6,
.depth = 16,
}
/* mode 7 is "reserved" */
};
static const v4l2_std_id video_standards[7] = {
V4L2_STD_NTSC,
V4L2_STD_PAL,
V4L2_STD_SECAM,
V4L2_STD_NTSC_443,
V4L2_STD_PAL_M,
V4L2_STD_PAL_N,
V4L2_STD_PAL_60,
};
static const struct tw686x_format *format_by_fourcc(unsigned int fourcc)
{
unsigned int cnt;
for (cnt = 0; cnt < ARRAY_SIZE(formats); cnt++)
if (formats[cnt].fourcc == fourcc)
return &formats[cnt];
return NULL;
}
static void tw686x_get_format(struct tw686x_video_channel *vc,
struct v4l2_format *f)
{
const struct tw686x_format *format;
unsigned int width, height, height_div = 1;
format = format_by_fourcc(f->fmt.pix.pixelformat);
if (!format) {
format = &formats[0];
f->fmt.pix.pixelformat = format->fourcc;
}
width = 704;
if (f->fmt.pix.width < width * 3 / 4 /* halfway */)
width /= 2;
height = (vc->video_standard & V4L2_STD_625_50) ? 576 : 480;
if (f->fmt.pix.height < height * 3 / 4 /* halfway */)
height_div = 2;
switch (f->fmt.pix.field) {
case V4L2_FIELD_TOP:
case V4L2_FIELD_BOTTOM:
height_div = 2;
break;
case V4L2_FIELD_SEQ_BT:
if (height_div > 1)
f->fmt.pix.field = V4L2_FIELD_BOTTOM;
break;
default:
if (height_div > 1)
f->fmt.pix.field = V4L2_FIELD_TOP;
else
f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
}
height /= height_div;
f->fmt.pix.width = width;
f->fmt.pix.height = height;
f->fmt.pix.bytesperline = f->fmt.pix.width * format->depth / 8;
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
}
/* video queue operations */
static int tw686x_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[],
struct device *alloc_devs[])
{
struct tw686x_video_channel *vc = vb2_get_drv_priv(vq);
unsigned int size = vc->width * vc->height * vc->format->depth / 8;
if (*nbuffers < 2)
*nbuffers = 2;
if (*nplanes)
return sizes[0] < size ? -EINVAL : 0;
sizes[0] = size;
*nplanes = 1; /* packed formats only */
return 0;
}
static void tw686x_buf_queue(struct vb2_buffer *vb)
{
struct tw686x_video_channel *vc = vb2_get_drv_priv(vb->vb2_queue);
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct tw686x_vb2_buf *buf;
buf = container_of(vbuf, struct tw686x_vb2_buf, vb);
spin_lock(&vc->qlock);
list_add_tail(&buf->list, &vc->vidq_queued);
spin_unlock(&vc->qlock);
}
static void setup_descs(struct tw686x_video_channel *vc, unsigned int n)
{
loop:
while (!list_empty(&vc->