/*
* Copyright 2015 www.starterkit.ru <info@starterkit.ru>
*
* Based on:
* Driver for Intersil|Techwell TW6869 based DVR cards
* (c) 2011-12 liran <jli11@intersil.com> [Intersil|Techwell China]
*
* V4L2 PCI Skeleton Driver
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates.
* All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "tw6869.h"
/**
* tw6869 internals
*/
static void tw6869_vch_dma_srst(struct tw6869_dma *dma)
{
tw_set(dma->dev, R8_AVSRST(dma->id), BIT(ID2SC(dma->id)));
}
static void tw6869_vch_dma_ctrl(struct tw6869_dma *dma)
{
struct tw6869_vch *vch = container_of(dma, struct tw6869_vch, dma);
tw_write(dma->dev, R8_BRIGHTNESS_CTRL(dma->id), vch->brightness);
tw_write(dma->dev, R8_CONTRAST_CTRL(dma->id), vch->contrast);
tw_write(dma->dev, R8_SHARPNESS_CTRL(dma->id), vch->sharpness);
tw_write(dma->dev, R8_SAT_U_CTRL(dma->id), vch->saturation);
tw_write(dma->dev, R8_SAT_V_CTRL(dma->id), vch->saturation);
tw_write(dma->dev, R8_HUE_CTRL(dma->id), vch->hue);
}
static int to_tw6869_pixformat(unsigned int pixelformat)
{
switch (pixelformat) {
case V4L2_PIX_FMT_UYVY: return TW_FMT_UYVY;
case V4L2_PIX_FMT_YUYV: return TW_FMT_YUYV;
case V4L2_PIX_FMT_RGB565: return TW_FMT_RGB565;
default:
return -EINVAL;
}
}
static int to_tw6869_std(v4l2_std_id v4l2_std)
{
switch (v4l2_std) {
case V4L2_STD_NTSC: return TW_STD_NTSC;
case V4L2_STD_PAL: return TW_STD_PAL;
case V4L2_STD_SECAM: return TW_STD_SECAM;
case V4L2_STD_NTSC_443: return TW_STD_NTSC_443;
case V4L2_STD_PAL_M: return TW_STD_PAL_M;
case V4L2_STD_PAL_Nc: return TW_STD_PAL_Nc;
case V4L2_STD_PAL_60: return TW_STD_PAL_60;
default:
return -EINVAL;
}
}
static inline const char *to_std_str(v4l2_std_id v4l2_std)
{
switch (v4l2_std) {
case V4L2_STD_NTSC: return "NTSC";
case V4L2_STD_PAL: return "PAL";
case V4L2_STD_SECAM: return "SECAM";
case V4L2_STD_NTSC_443: return "NTSC 443";
case V4L2_STD_PAL_M: return "PAL M";
case V4L2_STD_PAL_Nc: return "PAL Nc";
case V4L2_STD_PAL_60: return "PAL 60";
default:
return "unknown";
}
}
static v4l2_std_id to_v4l2_std(unsigned int tw_std)
{
switch (tw_std) {
case TW_STD_NTSC: return V4L2_STD_NTSC;
case TW_STD_PAL: return V4L2_STD_PAL;
case TW_STD_SECAM: return V4L2_STD_SECAM;
case TW_STD_NTSC_443: return V4L2_STD_NTSC_443;
case TW_STD_PAL_M: return V4L2_STD_PAL_M;
case TW_STD_PAL_Nc: return V4L2_STD_PAL_Nc;
case TW_STD_PAL_60: return V4L2_STD_PAL_60;
default:
return V4L2_STD_UNKNOWN;
}
}
static inline int tw_vch_frame_mode(struct tw6869_vch *vch)
{
return vch->format.height > 288;
}
static void tw6869_vch_dma_frame_isr(struct tw6869_dma *dma)
{
struct tw6869_vch *vch = container_of(dma, struct tw6869_vch, dma);
struct tw6869_buf *done = NULL;
struct tw6869_buf *next = NULL;
unsigned int i = dma->pb & 0x1;
spin_lock(&dma->lock);
if (tw_dma_active(dma) && !list_empty(&vch->buf_list)) {
next = list_first_entry(&vch->buf_list, struct tw6869_buf, list);
list_del(&next->list);
done = dma->buf[i];
dma->buf[i] = next;
}
spin_unlock(&dma->lock);
if (done && next) {
tw_write(dma->dev, dma->reg[i], next->dma_addr);
v4l2_get_timestamp(&done->vb.v4l2_buf.timestamp);
done->vb.v4l2_buf.sequence = vch->sequence++;
done->vb.v4l2_buf.field = V4L2_FIELD_INTERLACED;
vb2_buffer_done(&done->vb, VB2_BUF_STATE_DONE);
} else {
tw_err(dma->dev, "vch%u NOBUF seq=%u dcount=%u\n",
ID2CH(dma->id), vch->sequence, ++vch->dcount);
}
dma->fld = 0x0;
dma->pb ^= 0x1;
}
static void tw6869_vch_dma_field_isr(struct tw6869_dma *dma)
{
struct tw6869_vch *vch = container_of(dma, struct tw6869_vch, dma);
struct tw6869_buf *done = NULL;
struct tw6869_buf *next = NULL;
unsigned int i = ((dma->fld & 0x1) << 1) | (dma->pb & 0x1);
spin_lock(&dma->lock);
if (tw_dma_active(dma) && !list_empty(&vch->buf_list)) {
next = list_first_entry(&vch->buf_list, struct tw6869_buf, list);
list_del(&next->list);
done = dma->buf[i];
dma->buf[i] = next;
}
spin_unlock(&dma->lock);
if (done && next) {
tw_write(dma->dev, dma->reg[i], next->dma_addr);
v4l2_get_timestamp(&done->vb.v4l2_buf.timestamp);
done->vb.v4l2_buf.sequence = vch->sequence++;
done->vb.v4l2_buf.field = V4L2_FIELD_BOTTOM;
vb2_buffer_done(&done->vb, VB2_BUF_STATE_DONE);
} else {
tw_err(dma->dev, "vch%u NOBUF seq=%u dcount=%u\n",
ID2CH(dma->id), vch->sequence, ++vch->dcount);
}
dma->fld ^= 0x1;
if (!dma->fld)
dma->pb ^= 0x1;
}
static unsigned int tw6869_vch_fields_map(struct tw6869_vch *vch)
{
unsigned int map[15] = {
0x00000001, 0x00004001, 0x00104001, 0x00404041, 0x01041041,
0x01104411, 0x01111111, 0x04444445, 0x04511445, 0x05145145,
0x05151515, 0x05515455, 0x05551555, 0x05555555, 0x15555555
};
unsigned int std_625_50[26] = {
14, 0, 0, 1, 2, 2, 3, 3, 4, 4, 5, 6, 6,
7, 7, 8, 9, 9, 10, 10, 11, 12, 12, 13, 13, 14
};
unsigned int std_525_60[31] = {
14, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 14, 14
};
unsigned int i, m;
if (vch->std & V4L2_STD_625_50) {
vch->fps = (!vch->fps || vch->fps > 25) ? 25 : vch->fps;
i = std_625_50[vch->fps];
} else {
vch->fps = (!vch->fps || vch->fps > 30) ? 30 : vch->fps;
i = std_525_60[vch->fps];
}
m = map[i];
if (tw_vch_frame_mode(vch)) {
m = (m << 2) | (m << 1);
m = (m & BIT(30)) ? 0 : m;
}
return m;
}
static void tw6869_vch_frame_period(struct tw6869_vch *vch,
struct v4l2_fract *frameperiod)
{
if (vch->std & V4L2_STD_625_50) {
frameperiod->numerator = 1;
frameperiod->denominator = vch->fps;
} else {
frameperiod->numerator = 1001;
frameperiod->denominator = vch->fps * 1000;
}
}
struct active_window {
unsigned short hactive;
unsigned short hdelay;
unsigned short vactive;
unsigned short vdelay;
unsigned short f2vdelay;
};
const struct active_window ntsc_window = {.hactive = 720, .hdelay = 15, .vactive = 240, .vdelay = 22, .f2vdelay = 22};
void setup_window(struct tw6869_dma *dma, struct v4l2_pix_format *pix, const struct active_window *w)
{
unsigned cfg;
unsigned scale;
cfg = (w->hactive >> 8) | ((w->hdelay >> 8) << 2) |
((w->vactive >> 8) << 4) | ((w->vdelay >> 8) << 6);
tw_write(dma->dev, R8_CROPPING_CONTROL(dma->id), cfg);
tw_write(dma->dev, R8_VERTICAL_DELAY(dma->id), w->vdelay & 0xff);
tw_write(dma->dev, R8_VERTICAL_ACTIVE(dma->id), w->vactive & 0xff);
tw_write(dma->dev, R8_HORIZONTAL_DELAY(dma->id), w->hdelay & 0xff);
tw_write(dma->dev, R8_HORIZONTAL_ACTIVE(dma->id), w->hactive & 0xff);
scale = 720 * 256 / pix->width;
tw_write(dma->dev, R8_SCALING_HIGH(dma->id), ((scale >> 8) & 0x0F) | 0x10);
tw_write(dma->dev, R8_HORIZONTAL_SCALING(dma->id), scale & 0xFF);
tw_write(dma->dev, R8_F2CNT(dma->id), w->vdelay == w->f2vdelay ? 0 : 1);
if (w->vdelay == w->f2vdelay)
return;
cfg = (w->hactive >> 8) | ((w->hdelay >> 8) << 2) |
((w->vactive >> 8) << 4) | ((w->f2vdelay >> 8) << 6);
tw_write(dma->dev, R8_F2CROPPING_CONTROL(dma->id), cfg);
tw_write(dma->dev, R8_F2VERTICAL_DELAY(dma->id), w->f2vdelay & 0xff);
tw_write(dma->dev, R8_F2VERTICAL_ACTIVE(dma->id), w->vactive & 0xff);
tw_write(dma->dev, R8_F2HORIZONTAL_DELAY(dma->id), w->hdelay & 0xff);
tw_write(dma->dev, R8_F2HORIZONTAL_ACTIVE(dma->id), w->hactive & 0xff);
tw_write(dma->dev, R8_F2SCALING_HIGH(dma->id), ((scale >> 8) & 0x0F) | 0x10);
tw_write(dma->dev, R8_F2HORIZONTAL_SCALING(dma->id), scale & 0xFF);
}
static void tw6869_vch_dma_cfg(struct tw6869_dma *dma)
{
struct tw6869