SmartAudio/lichee/linux-4.9/drivers/media/platform/sunxi-tvd/tvd.c

3724 lines
86 KiB
C
Raw Normal View History

2018-12-13 10:48:25 +00:00
/*
* Copyright (C) 2015 Allwinnertech, <zhengxiaobin@allwinnertech.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/version.h>
#include <linux/videodev2.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
#include <linux/freezer.h>
#endif
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/moduleparam.h>
#include <linux/of_gpio.h>
#include <linux/pinctrl/consumer.h>
#include <linux/sunxi-gpio.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#if defined(CONFIG_EXTCON)
#include <linux/extcon.h>
#endif
/*#include <linux/sys_config.h>*/
#include <media/v4l2-common.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mediabus.h>
#include <media/v4l2-subdev.h>
#include <media/videobuf2-core.h>
#include <media/videobuf2-dma-contig.h>
/*#include <linux/clk-private.h>*/
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
#include <linux/sunxi_dramfreq.h>
#endif
#include "tvd.h"
#define TVD_MODULE_NAME "sunxi_tvd"
#define MIN_WIDTH (32)
#define MIN_HEIGHT (32)
#define MAX_WIDTH (4096)
#define MAX_HEIGHT (4096)
#define MAX_BUFFER (32 * 1024 * 1024)
#define NUM_INPUTS 1
#define TVD_MAX_POWER_NUM 2
#define TVD_MAX_GPIO_NUM 2
#define TVD_MAJOR_VERSION 1
#define TVD_MINOR_VERSION 0
#define TVD_RELEASE 0
#define TVD_VERSION \
KERNEL_VERSION(TVD_MAJOR_VERSION, TVD_MINOR_VERSION, TVD_RELEASE)
/*v4l2_format's raw_data index*/
#define RAW_DATA_INTERFACE 1
#define RAW_DATA_SYSTEM 2
#define RAW_DATA_FORMAT 3
#define RAW_DATA_PIXELFORMAT 4
#define RAW_DATA_ROW 5
#define RAW_DATA_COLUMN 6
#define RAW_DATA_CH0_INDEX 7
#define RAW_DATA_CH1_INDEX 8
#define RAW_DATA_CH2_INDEX 9
#define RAW_DATA_CH3_INDEX 10
#define RAW_DATA_CH0_STATUS 11
#define RAW_DATA_CH1_STATUS 12
#define RAW_DATA_CH2_STATUS 13
#define RAW_DATA_CH3_STATUS 14
static unsigned int tvd_dbg_en;
static unsigned int tvd_dbg_sel;
#define TVD_DBG_DUMP_LEN 0x200000
#define TVD_NAME_LEN 32
static char tvd_dump_file_name[TVD_NAME_LEN];
/*todo:alloc dynamic*/
static struct tvd_status tvd_status[TVD_MAX];
static struct tvd_dev *tvd[TVD_MAX];
static void __iomem *tvd_addr[TVD_MAX];
static void __iomem *tvd_top;
static struct clk *tvd_clk_top;
static struct clk *tvd_clk[TVD_MAX];
static int tvd_irq[TVD_MAX];
static unsigned int tvd_hot_plug;
static unsigned int tvd_row;
static unsigned int tvd_column;
static unsigned int tvd_total_num;
/* use for reversr special interfaces */
static int tvd_count;
static int fliter_count;
static struct mutex fliter_lock;
static struct mutex power_lock;
static char tvd_power[TVD_MAX_POWER_NUM][32];
static struct gpio_config tvd_gpio_config[TVD_MAX_GPIO_NUM];
static struct regulator *regu[TVD_MAX_POWER_NUM];
static atomic_t tvd_used_power_num = ATOMIC_INIT(0);
static atomic_t tvd_used_gpio_num = ATOMIC_INIT(0);
static atomic_t gpio_power_enable_count = ATOMIC_INIT(0);
static irqreturn_t tvd_isr(int irq, void *priv);
static irqreturn_t tvd_isr_special(int irq, void *priv);
static int __tvd_fetch_sysconfig(int sel, char *sub_name, int value[]);
static int __tvd_auto_plug_enable(struct tvd_dev *dev);
static int __tvd_auto_plug_disable(struct tvd_dev *dev);
static ssize_t tvd_dbg_en_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
tvd_here;
return sprintf(buf, "%u\n", tvd_dbg_en);
}
static ssize_t tvd_dbg_en_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
int err;
unsigned long val;
tvd_here;
err = kstrtoul(buf, 10, &val);
if (err) {
tvd_dbg("Invalid size\n");
return err;
}
if (val < 0 || val > 1) {
tvd_dbg("Invalid value, 0~1 is expected!\n");
} else {
tvd_dbg_en = val;
tvd_dbg("tvd_dbg_en = %ld\n", val);
}
return count;
}
static ssize_t tvd_dbg_lv_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
tvd_here;
return sprintf(buf, "%u\n", tvd_dbg_sel);
}
static ssize_t tvd_dbg_lv_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
int err;
unsigned long val;
tvd_here;
err = kstrtoul(buf, 10, &val);
if (err) {
tvd_dbg("Invalid size\n");
return err;
}
if (val < 0 || val > 4) {
tvd_dbg("Invalid value, 0~3 is expected!\n");
} else {
tvd_dbg_sel = val;
tvd_dbg("tvd_dbg_sel = %d\n", tvd_dbg_sel);
tvd_isr(46, tvd[tvd_dbg_sel]);
}
return count;
}
static ssize_t tvd_dbg_dump_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tvd_dev *tvd_dev = (struct tvd_dev *)dev_get_drvdata(dev);
struct file *pfile;
mm_segment_t old_fs;
ssize_t bw;
dma_addr_t buf_dma_addr;
void *buf_addr;
tvd_here;
buf_addr = dma_alloc_coherent(dev, TVD_DBG_DUMP_LEN,
(dma_addr_t *)&buf_dma_addr, GFP_KERNEL);
if (!buf_addr) {
tvd_wrn("%s(), dma_alloc_coherent fail, size=0x%x\n", __func__,
TVD_DBG_DUMP_LEN);
return 0;
}
/* start debug mode */
if (tvd_dbgmode_dump_data(tvd_dev->sel, 0, buf_dma_addr,
TVD_DBG_DUMP_LEN / 2)) {
tvd_wrn("%s(), debug mode start fail\n", __func__);
goto exit;
}
pfile = filp_open(tvd_dump_file_name, O_RDWR | O_CREAT | O_EXCL, 0755);
if (IS_ERR(pfile)) {
tvd_wrn("%s, open %s err\n", __func__, tvd_dump_file_name);
goto exit;
}
tvd_wrn("%s, open %s ok\n", __func__, tvd_dump_file_name);
old_fs = get_fs();
set_fs(KERNEL_DS);
bw = pfile->f_op->write(pfile, (const char *)buf_addr, TVD_DBG_DUMP_LEN,
&pfile->f_pos);
if (unlikely(bw != TVD_DBG_DUMP_LEN))
tvd_wrn("%s, write %s err at byte offset %llu\n", __func__,
tvd_dump_file_name, pfile->f_pos);
set_fs(old_fs);
filp_close(pfile, NULL);
pfile = NULL;
exit:
dma_free_coherent(dev, TVD_DBG_DUMP_LEN, buf_addr, buf_dma_addr);
return 0;
}
static ssize_t tvd_dbg_dump_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
tvd_here;
memset(tvd_dump_file_name, '\0', TVD_NAME_LEN);
count = (count > TVD_NAME_LEN) ? TVD_NAME_LEN : count;
memcpy(tvd_dump_file_name, buf, count - 1);
tvd_dbg("%s(), get dump file name %s\n", __func__, tvd_dump_file_name);
return count;
}
static DEVICE_ATTR(tvd_dbg_en, S_IRUGO | S_IWUSR | S_IWGRP, tvd_dbg_en_show,
tvd_dbg_en_store);
static DEVICE_ATTR(tvd_dbg_lv, S_IRUGO | S_IWUSR | S_IWGRP, tvd_dbg_lv_show,
tvd_dbg_lv_store);
static DEVICE_ATTR(tvd_dump, S_IRUGO | S_IWUSR | S_IWGRP, tvd_dbg_dump_show,
tvd_dbg_dump_store);
static struct attribute *tvd0_attributes[] = {&dev_attr_tvd_dbg_en.attr,
&dev_attr_tvd_dbg_lv.attr,
&dev_attr_tvd_dump.attr, NULL};
static struct attribute *tvd1_attributes[] = {&dev_attr_tvd_dbg_en.attr,
&dev_attr_tvd_dbg_lv.attr,
&dev_attr_tvd_dump.attr, NULL};
static struct attribute *tvd2_attributes[] = {&dev_attr_tvd_dbg_en.attr,
&dev_attr_tvd_dbg_lv.attr,
&dev_attr_tvd_dump.attr, NULL};
static struct attribute *tvd3_attributes[] = {&dev_attr_tvd_dbg_en.attr,
&dev_attr_tvd_dbg_lv.attr,
&dev_attr_tvd_dump.attr, NULL};
static struct attribute *tvd_multi_ch_attributes[] = {
&dev_attr_tvd_dbg_en.attr,
&dev_attr_tvd_dbg_lv.attr,
&dev_attr_tvd_dump.attr, NULL};
static struct attribute_group tvd_attribute_group[] = {
{.name = "tvd0_attr", .attrs = tvd0_attributes},
{.name = "tvd1_attr", .attrs = tvd1_attributes},
{.name = "tvd2_attr", .attrs = tvd2_attributes},
{.name = "tvd3_attr", .attrs = tvd3_attributes},
{.name = "tvd_multi_ch_attr", .attrs = tvd_multi_ch_attributes},
};
static struct tvd_fmt formats[] = {
{
.name = "planar UVUV",
.fourcc = V4L2_PIX_FMT_NV12,
.output_fmt = TVD_PL_YUV420,
.depth = 12,
},
{
.name = "planar VUVU",
.fourcc = V4L2_PIX_FMT_NV21,
.output_fmt = TVD_PL_YUV420,
.depth = 12,
},
{
.name = "planar UVUV",
.fourcc = V4L2_PIX_FMT_NV16,
.output_fmt = TVD_PL_YUV422,
.depth = 16,
},
{
.name = "planar VUVU",
.fourcc = V4L2_PIX_FMT_NV61,
.output_fmt = TVD_PL_YUV422,
.depth = 16,
},
/* this format is not standard, just for allwinner. */
{
.name = "planar PACK",
.fourcc = 0,
.output_fmt = TVD_MB_YUV420,
.depth = 12,
},
};
static inline int tvd_is_generating(struct tvd_dev *dev)
{
return test_bit(0, &dev->generating);
}
static inline void tvd_start_generating(struct tvd_dev *dev)
{
tvd_here;
set_bit(0, &dev->generating);
return;
}
static inline void tvd_stop_generating(struct tvd_dev *dev)
{
tvd_here;
clear_bit(0, &dev->generating);
return;
}
static int tvd_is_opened(struct tvd_dev *dev)
{
int ret;
tvd_here;
mutex_lock(&dev->opened_lock);
ret = test_bit(0, &dev->opened);
mutex_unlock(&dev->opened_lock);
return ret;
}
static void tvd_start_opened(struct tvd_dev *dev)
{
tvd_here;
mutex_lock(&dev->opened_lock);
set_bit(0, &dev->opened);
mutex_unlock(&dev->opened_lock);
}
static void tvd_stop_opened(struct tvd_dev *dev)
{
tvd_here;
mutex_lock(&dev->opened_lock);
clear_bit(0, &dev->opened);
mutex_unlock(&dev->opened_lock);
}
static int __tvd_clk_init(struct tvd_dev *dev)
{
int div = 0, ret = 0, i = 0;
unsigned long p = 216000000;
tvd_here;
tvd_dbg("%s: dev->interface = %d, dev->system = %d\n", __func__,
dev->interface, dev->system);
dev->parent = clk_get_parent(dev->clk);
if (IS_ERR_OR_NULL(dev->parent) || IS_ERR_OR_NULL(dev->clk))
return -EINVAL;
/* parent is 297M */
ret = clk_set_rate(dev->parent, p);
if (ret) {
ret = -EINVAL;
goto out;
}
if (dev->interface == CVBS_INTERFACE || (dev->sel == 3)) {
/* cvbs interface */
div = 8;
} else if (dev->interface == YPBPRI_INTERFACE) {
/* ypbprI interface */
div = 8;
} else if (dev->interface == YPBPRP_INTERFACE) {
/* ypbprP interface */
div = 4;
} else {
tvd_wrn("%s: interface is err!\n", __func__);
return -EINVAL;
}
tvd_dbg("div = %d\n", div);
p /= div;
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; i++) {
if (!tvd_status[i].tvd_used)
continue;
ret = clk_set_rate(tvd_clk[i], p);
if (ret) {
ret = -EINVAL;
goto out;
}
tvd_dbg("tvd%d: parent = %lu, clk = %lu\n", i,
clk_get_rate(dev->parent),
clk_get_rate(tvd_clk[i]));
}
} else {
ret = clk_set_rate(dev->clk, p);
if (ret) {
ret = -EINVAL;
goto out;
}
tvd_dbg("tvd%d: parent = %lu, clk = %lu\n", dev->sel,
clk_get_rate(dev->parent), clk_get_rate(dev->clk));
}
out:
return ret;
}
static int __tvd_clk_enable(struct tvd_dev *dev)
{
int ret = 0, i = 0;
tvd_here;
/*if (!dev->clk_top->enable_count) {*/
ret = clk_prepare_enable(dev->clk_top);
if (ret) {
tvd_wrn("%s: tvd top clk enable err!", __func__);
clk_disable(dev->clk_top);
return ret;
}
/*}*/
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; i++) {
if (!tvd_status[i].tvd_used)
continue;
if (!__clk_get_enable_count(tvd_clk[i])) {
ret = clk_prepare_enable(tvd_clk[i]);
if (ret) {
tvd_wrn("%s: tvd clk %d enable err!",
__func__, i);
clk_disable(tvd_clk[i]);
break;
}
}
tvd_status[i].tvd_clk_enabled = 1;
}
} else {
ret = clk_prepare_enable(dev->clk);
if (ret) {
tvd_wrn("%s: tvd clk %d enable err!", __func__, i);
clk_disable(dev->clk);
}
}
return ret;
}
static int __tvd_clk_disable(struct tvd_dev *dev)
{
int ret = 0, i = 0;
tvd_here;
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (!tvd_status[i].tvd_used)
continue;
tvd_dbg("Disable tvd%d clk\n", i);
while (__clk_get_enable_count(tvd_clk[i]))
clk_disable(tvd_clk[i]);
tvd_status[i].tvd_clk_enabled = 0;
}
} else {
/*while (dev->clk->enable_count)*/
clk_disable(dev->clk);
}
/*while (dev->clk_top->enable_count)*/
clk_disable(dev->clk_top);
return ret;
}
static int __tvd_init(struct tvd_dev *dev)
{
int i = 0;
tvd_here;
tvd_top_set_reg_base((unsigned long)dev->regs_top);
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i)
tvd_set_reg_base(i, (unsigned long)tvd_addr[i]);
} else
tvd_set_reg_base(dev->sel, (unsigned long)dev->regs_tvd);
return 0;
}
static int __tvd_config(struct tvd_dev *dev)
{
int i = 0;
tvd_here;
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (!tvd_status[i].tvd_used)
continue;
tvd_init(i, dev->interface);
tvd_config(i, dev->interface, dev->system);
tvd_set_wb_width(i, dev->width / dev->row);
tvd_set_wb_width_jump(i, dev->width);
if (dev->interface == YPBPRP_INTERFACE)
tvd_set_wb_height(i,
dev->height /
dev->column); /*P,no div*/
else
tvd_set_wb_height(
i, dev->height / (2 * dev->column));
/* pl_yuv420, mb_yuv420, pl_yuv422 */
tvd_set_wb_fmt(i, dev->fmt->output_fmt);
switch (dev->fmt->fourcc) {
case V4L2_PIX_FMT_NV12:
case V4L2_PIX_FMT_NV16:
tvd_set_wb_uv_swap(i, 0);
break;
case V4L2_PIX_FMT_NV21:
case V4L2_PIX_FMT_NV61:
tvd_set_wb_uv_swap(i, 1);
break;
}
}
} else {
tvd_init(dev->sel, dev->interface);
tvd_config(dev->sel,
(dev->sel == 3) ? 0 : dev->interface, dev->system);
tvd_set_wb_width(dev->sel, dev->width);
tvd_set_wb_width_jump(dev->sel, dev->width);
if (dev->interface == YPBPRP_INTERFACE && (dev->sel != 3))
tvd_set_wb_height(dev->sel, dev->height); /*P,no div*/
else
tvd_set_wb_height(dev->sel, dev->height / 2);
/* pl_yuv420, mb_yuv420, pl_yuv422 */
tvd_set_wb_fmt(dev->sel, dev->fmt->output_fmt);
switch (dev->fmt->fourcc) {
case V4L2_PIX_FMT_NV12:
case V4L2_PIX_FMT_NV16:
tvd_set_wb_uv_swap(dev->sel, 0);
break;
case V4L2_PIX_FMT_NV21:
case V4L2_PIX_FMT_NV61:
tvd_set_wb_uv_swap(dev->sel, 1);
break;
}
}
return 0;
}
static int __tvd_3d_comp_mem_request(struct tvd_dev *dev, int size)
{
unsigned long phyaddr;
tvd_here;
dev->fliter.size = PAGE_ALIGN(size);
dev->fliter.vir_address = dma_alloc_coherent(
dev->v4l2_dev.dev, size, (dma_addr_t *)&phyaddr, GFP_KERNEL);
dev->fliter.phy_address = (void *)phyaddr;
if (IS_ERR_OR_NULL(dev->fliter.vir_address)) {
tvd_wrn("%s: 3d fliter buf_alloc failed!\n", __func__);
return -EINVAL;
}
return 0;
}
static void __tvd_3d_comp_mem_free(struct tvd_dev *dev)
{
u32 actual_bytes;
tvd_here;
actual_bytes = PAGE_ALIGN(dev->fliter.size);
if (dev->fliter.phy_address && dev->fliter.vir_address)
dma_free_coherent(dev->v4l2_dev.dev, actual_bytes,
dev->fliter.vir_address,
(dma_addr_t)dev->fliter.phy_address);
}
/*
* set width,set height, set jump, set wb addr, set 3d_comb
*/
static void __tvd_set_addr(struct tvd_dev *dev, struct tvd_buffer *buffer)
{
struct tvd_buffer *buf = buffer;
dma_addr_t addr_org;
struct vb2_buffer *vb_buf = &buf->vb;
unsigned int c_offset = 0, i = 0;
if (vb_buf == NULL || vb_buf->planes[0].mem_priv == NULL) {
tvd_wrn("%s: vb_buf->priv is NULL!\n", __func__);
return;
}
/*get one frame buffer buffer physical address from dma*/
addr_org = vb2_dma_contig_plane_dma_addr(vb_buf, 0);
switch (dev->fmt->output_fmt) {
case TVD_PL_YUV422:
case TVD_PL_YUV420:
c_offset = dev->width * dev->height;
break;
case TVD_MB_YUV420:
c_offset = 0;
break;
default:
break;
}
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (!tvd_status[i].tvd_used)
continue;
tvd_set_wb_addr(
i, addr_org + dev->channel_offset_y[i],
addr_org + c_offset + dev->channel_offset_c[i]);
}
} else
tvd_set_wb_addr(dev->sel, addr_org, addr_org + c_offset);
/* set y_addr,c_addr */
/*tvd_dbg("%s: format:%d, addr_org = 0x%x, addr_org + c_offset = 0x%x\n",*/
/*__func__, dev->format, addr_org, addr_org + c_offset);*/
}
/**
* @name :tvd_valid_frame_setting
* @brief :for multichannel mode,make sure all channel is locked
* @param[IN] :sel:index of tvd module
* @return :
*/
s32 tvd_valid_frame_setting(u32 sel, struct tvd_dev *dev)
{
__u32 i = 0;
const __s32 blue_start = -30;
const __s32 blue_middle = 0;
const __s32 blue_end = 10;
static __s32 frame_cntr;
__u32 lock_any_current = 0;
static __u32 lock_any_latest;
for (i = 0; i < tvd_total_num; i++) {
if (!tvd_status[i].tvd_used)
continue;
lock_any_current = tvd_get_lock(i);
if (lock_any_current) {
lock_any_current = 1;
break;
}
}
if (lock_any_current == 0 && lock_any_latest == 1 &&
blue_end == frame_cntr) {
for (i = 0; i < tvd_total_num; ++i) {
if (!tvd_status[i].tvd_used)
continue;
tvd_blue_display_mode(i, 1);
}
frame_cntr = blue_start;
}
if (lock_any_current == 0 && lock_any_latest == 0 &&
blue_middle == frame_cntr) {
for (i = 0; i < tvd_total_num; ++i) {
if (!tvd_status[i].tvd_used)
continue;
tvd_blue_display_mode(i, 2);
}
} else if (lock_any_current == 1 && lock_any_latest == 0 &&
blue_middle == frame_cntr) {
for (i = 0; i < tvd_total_num; ++i) {
if (!tvd_status[i].tvd_used)
continue;
tvd_reset(i);
}
frame_cntr = blue_middle + 1;
}
lock_any_latest = lock_any_current;
if (frame_cntr != blue_middle && frame_cntr != blue_end)
frame_cntr++;
return lock_any_current;
}
/*
* the interrupt routine
*/
static irqreturn_t tvd_isr(int irq, void *priv)
{
struct tvd_buffer *buf;
unsigned long flags;
u32 irq_status = 0;
struct tvd_dev *dev = (struct tvd_dev *)priv;
struct tvd_dmaqueue *dma_q = &dev->vidq;
u32 err = (1 << TVD_IRQ_FIFO_C_O) | (1 << TVD_IRQ_FIFO_Y_O) |
(1 << TVD_IRQ_FIFO_C_U) | (1 << TVD_IRQ_FIFO_Y_U) |
(1 << TVD_IRQ_WB_ADDR_CHANGE_ERR);
static int flag;
u32 tvf = 0;
if (flag == 0) {
tvd_dbg("In tvd_isr\n");
flag = 1;
}
/*todo:handle this fucking specifial*/
if (dev->special_active == 1) {
return tvd_isr_special(irq, priv);
}
if (tvd_is_generating(dev) == 0) {
tvd_irq_status_clear(dev->sel, TVD_IRQ_FRAME_END);
return IRQ_HANDLED;
}
tvd_dma_irq_status_get(dev->sel, &irq_status);
if ((irq_status & err) != 0)
tvd_dma_irq_status_clear_err_flag(dev->sel, err);
spin_lock_irqsave(&dev->slock, flags);
if (dev->mulit_channel_mode) {
tvf = tvd_valid_frame_setting(dev->sel, dev);
if (!tvf)
goto unlock;
}
if (0 == dev->first_flag) {
/* if is first frame, flag set 1 */
dev->first_flag = 1;
goto set_next_addr;
}
if (list_empty(&dma_q->active) ||
dma_q->active.next->next == (&dma_q->active)) {
tvd_dbg("No active queue to serve\n");
goto unlock;
}
buf = list_entry(dma_q->active.next, struct tvd_buffer, list);
/* Nobody is waiting for this buffer*/
if (!waitqueue_active(&buf->vb.vb2_queue->done_wq)) {
tvd_dbg(
"%s: Nobody is waiting on this video buffer,buf = 0x%p\n",
__func__, buf);
}
list_del(&buf->list);
dev->ms += jiffies_to_msecs(jiffies - dev->jiffies);
dev->jiffies = jiffies;
/*setting vb2 buffer state to VB2_BUF_STATE_DONE*/
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
if (list_empty(&dma_q->active)) {
tvd_dbg("%s: No more free frame\n", __func__);
goto unlock;
}
/* hardware need one frame */
if ((&dma_q->active) == dma_q->active.next->next) {
tvd_dbg("No more free frame on next time\n");
goto unlock;
}
set_next_addr:
buf = list_entry(dma_q->active.next->next, struct tvd_buffer, list);
__tvd_set_addr(dev, buf);
unlock:
spin_unlock(&dev->slock);
tvd_irq_status_clear(dev->sel, TVD_IRQ_FRAME_END);
return IRQ_HANDLED;
}
/*
* Videobuf operations
*/
static int queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers, unsigned int *nplanes,
unsigned int sizes[], struct device *alloc_devs[])
{
struct tvd_dev *dev = vb2_get_drv_priv(vq);
unsigned int size;
tvd_here;
switch (dev->fmt->output_fmt) {
case TVD_MB_YUV420:
case TVD_PL_YUV420:
size = dev->width * dev->height * 3 / 2;
break;
case TVD_PL_YUV422:
default:
size = dev->width * dev->height * 2;
break;
}
if (size == 0)
return -EINVAL;
if (*nbuffers < 3) {
*nbuffers = 3;
tvd_wrn("buffer conunt invalid, min is 3!\n");
} else if (*nbuffers > 10) {
*nbuffers = 10;
tvd_wrn("buffer conunt invalid, max 10!\n");
}
dev->frame_size = size;
sizes[0] = size;
*nplanes = 1;
alloc_devs[0] = &dev->pdev->dev;
tvd_dbg("%s, buffer count=%d, size=%d\n", __func__, *nbuffers, size);
return 0;
}
static int buffer_prepare(struct vb2_buffer *vb)
{
struct tvd_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct tvd_buffer *buf = container_of(vb, struct tvd_buffer, vb);
unsigned long size;
if (dev->width < MIN_WIDTH || dev->width > MAX_WIDTH ||
dev->height < MIN_HEIGHT || dev->height > MAX_HEIGHT) {
return -EINVAL;
}
size = dev->frame_size;
if (vb2_plane_size(vb, 0) < size) {
tvd_wrn("%s data will not fit into plane (%lu < %lu)\n",
__func__, vb2_plane_size(vb, 0), size);
return -EINVAL;
}
vb2_set_plane_payload(&buf->vb, 0, size);
vb->planes[0].m.offset = vb2_dma_contig_plane_dma_addr(vb, 0);
return 0;
}
static void buffer_queue(struct vb2_buffer *vb)
{
struct tvd_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct tvd_buffer *buf = container_of(vb, struct tvd_buffer, vb);
struct tvd_dmaqueue *vidq = &dev->vidq;
unsigned long flags = 0;
spin_lock_irqsave(&dev->slock, flags);
list_add_tail(&buf->list, &vidq->active);
spin_unlock_irqrestore(&dev->slock, flags);
}
static int start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct tvd_dev *dev = vb2_get_drv_priv(vq);
tvd_here;
tvd_start_generating(dev);
return 0;
}
/* abort streaming and wait for last buffer */
static void stop_streaming(struct vb2_queue *vq)
{
struct tvd_dev *dev = vb2_get_drv_priv(vq);
struct tvd_dmaqueue *dma_q = &dev->vidq;
unsigned long flags = 0;
tvd_here;
tvd_stop_generating(dev);
/* Release all active buffers */
spin_lock_irqsave(&dev->slock, flags);
while (!list_empty(&dma_q->active)) {
struct tvd_buffer *buf;
buf = list_entry(dma_q->active.next, struct tvd_buffer, list);
list_del(&buf->list);
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
}
spin_unlock_irqrestore(&dev->slock, flags);
}
static void tvd_lock(struct vb2_queue *vq)
{
struct tvd_dev *dev = vb2_get_drv_priv(vq);
mutex_lock(&dev->buf_lock);
}
static void tvd_unlock(struct vb2_queue *vq)
{
struct tvd_dev *dev = vb2_get_drv_priv(vq);
mutex_unlock(&dev->buf_lock);
}
static const struct vb2_ops tvd_video_qops = {
.queue_setup = queue_setup,
.buf_prepare = buffer_prepare,
.buf_queue = buffer_queue,
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
.wait_prepare = tvd_unlock,
.wait_finish = tvd_lock,
};
/*
* IOCTL vidioc handling
*/
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
struct tvd_dev *dev = video_drvdata(file);
tvd_here;
strcpy(cap->driver, "sunxi-tvd");
strcpy(cap->card, "sunxi-tvd");
strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info));
cap->version = TVD_VERSION;
cap->capabilities =
V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE | V4L2_CAP_DEVICE_CAPS;
cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE;
return 0;
}
static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
struct tvd_fmt *fmt;
tvd_here;
if (f->index > ARRAY_SIZE(formats) - 1)
return -EINVAL;
fmt = &formats[f->index];
strlcpy(f->description, fmt->name, sizeof(f->description));
f->pixelformat = fmt->fourcc;
return 0;
}
/**
* @name __get_status
* @brief get specified tvd device's status
* @param[IN] dev
* @param[OUT] locked: 1:signal locked, 0:signal not locked
* @param[OUT] system: 1:pal, 0:ntsc
* @return none
*/
static void __get_status(struct tvd_dev *dev, unsigned int *locked,
unsigned int *system)
{
int i = 0;
unsigned int temp_locked = 0;
unsigned int temp_system = 0;
tvd_here;
if (dev->interface > 0) {
/* ypbpr signal, search i/p */
dev->interface = 1;
for (i = 0; i < 2; i++) {
__tvd_clk_init(dev);
mdelay(200);
tvd_get_status(dev->sel, locked, system);
if (*locked)
break;
if (dev->interface < 2)
dev->interface++;
}
} else if (dev->interface == 0) {
/* cvbs signal */
mdelay(200);
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (tvd_status[i].tvd_used) {
tvd_get_status(i, locked, system);
tvd_dbg("tvd%d locked:%d system:%d\n",
i, *locked, *system);
tvd_status[i].locked = *locked;
tvd_status[i].tvd_system = *system;
if (*locked) {
temp_locked = *locked;
temp_system = *system;
}
}
}
*locked =
temp_locked; /*if one of ch locked then is locked*/
/*if one of ch not insert then follow other chns */
*system = temp_system;
} else {
tvd_get_status(dev->sel, locked, system);
tvd_dbg("tvd%d locked:%d system:%d\n",
dev->sel, *locked, *system);
}
}
}
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct tvd_dev *dev = video_drvdata(file);
u32 locked = 0, system = 2, i = 0;
tvd_here;
__get_status(dev, &locked, &system);
if (dev->mulit_channel_mode) {
f->fmt.raw_data[RAW_DATA_INTERFACE] = dev->interface;
f->fmt.raw_data[RAW_DATA_SYSTEM] = system;
f->fmt.raw_data[RAW_DATA_FORMAT] = dev->format;
f->fmt.raw_data[RAW_DATA_ROW] = dev->row;
f->fmt.raw_data[RAW_DATA_COLUMN] = dev->column;
f->fmt.raw_data[RAW_DATA_CH0_INDEX] = dev->channel_index[0];
f->fmt.raw_data[RAW_DATA_CH1_INDEX] = dev->channel_index[1];
f->fmt.raw_data[RAW_DATA_CH2_INDEX] = dev->channel_index[2];
f->fmt.raw_data[RAW_DATA_CH3_INDEX] = dev->channel_index[3];
for (i = 0; i < tvd_total_num; ++i) {
if (dev->channel_index[i]) {
f->fmt.raw_data[RAW_DATA_CH0_STATUS + i] =
tvd_status[i].locked;
}
}
} else {
f->fmt.pix.width = dev->width;
f->fmt.pix.height = dev->height;
f->fmt.pix.priv = dev->interface;
if (!locked) {
tvd_dbg("%s: signal is not locked.\n", __func__);
return -EAGAIN;
} else {
/* system: 1->pal, 0->ntsc */
f->fmt.raw_data[RAW_DATA_INTERFACE] = dev->interface;
f->fmt.raw_data[RAW_DATA_SYSTEM] = system;
f->fmt.raw_data[RAW_DATA_FORMAT] = dev->format;
f->fmt.raw_data[RAW_DATA_ROW] = dev->row;
f->fmt.raw_data[RAW_DATA_COLUMN] = dev->column;
f->fmt.raw_data[RAW_DATA_CH0_INDEX] = dev->channel_index[0];
f->fmt.raw_data[RAW_DATA_CH1_INDEX] = dev->channel_index[1];
f->fmt.raw_data[RAW_DATA_CH2_INDEX] = dev->channel_index[2];
f->fmt.raw_data[RAW_DATA_CH3_INDEX] = dev->channel_index[3];
if (system == PAL) {
f->fmt.pix.width = 720;
f->fmt.pix.height = 576;
} else if (system == NTSC) {
f->fmt.pix.width = 720;
f->fmt.pix.height = 480;
} else {
tvd_wrn("system is not sure.\n");
}
}
}
tvd_dbg("system = %d, w = %d, h = %d\n", system, f->fmt.pix.width,
f->fmt.pix.height);
return 0;
}
static u32 get_pixelformat_fro_raw_data(struct v4l2_format *f)
{
u32 pixelformat = 0;
tvd_here;
if (f == NULL)
return V4L2_PIX_FMT_NV21;
switch (f->fmt.raw_data[RAW_DATA_PIXELFORMAT]) {
case 0:
pixelformat = V4L2_PIX_FMT_NV12;
break;
case 1:
pixelformat = V4L2_PIX_FMT_NV21;
break;
case 2:
pixelformat = V4L2_PIX_FMT_NV16;
break;
case 3:
pixelformat = V4L2_PIX_FMT_NV61;
break;
case 4:
pixelformat = 0;
break;
default:
pixelformat = V4L2_PIX_FMT_NV21;
break;
}
return pixelformat;
}
static struct tvd_fmt *__get_format(struct tvd_dev *dev, struct v4l2_format *f)
{
struct tvd_fmt *fmt;
__u32 i;
__u32 pixelformat = 0;
tvd_here;
pixelformat = (dev->mulit_channel_mode)
? get_pixelformat_fro_raw_data(f)
: f->fmt.pix.pixelformat;
for (i = 0; i < ARRAY_SIZE(formats); i++) {
fmt = &formats[i];
if (fmt->fourcc == pixelformat) {
tvd_dbg("fourcc = %d\n", fmt->fourcc);
break;
}
}
if (i == ARRAY_SIZE(formats))
return NULL;
return &formats[i];
}
static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
u32 locked = 0, system = 2;
struct tvd_dev *dev = video_drvdata(file);
__get_status(dev, &locked, &system);
if (dev->mulit_channel_mode) {
if (system) {
f->fmt.pix.width = 720*2;
f->fmt.pix.height = 576*2;
} else {
f->fmt.pix.width = 720*2;
f->fmt.pix.height = 480*2;
}
} else {
if (system) {
f->fmt.pix.width = 720;
f->fmt.pix.height = 576;
} else {
f->fmt.pix.width = 720;
f->fmt.pix.height = 480;
}
}
return 0;
}
/**
* @name check_user_setting_rule
* @brief funciton description
* @param[IN] dev: tvd_dev var
* @return 0: match rule; -1: violation; -2: null arg
*/
static int check_user_setting_rule(const struct tvd_dev *dev)
{
u32 tvd_num_to_used = 0, i = 0, ch_num_to_used = 0, j = 0;
tvd_here;
if (dev == NULL) {
tvd_wrn("%s:dev is null\n", __func__);
return -2;
}
tvd_num_to_used = dev->row * dev->column;
if (tvd_num_to_used < 1 || tvd_num_to_used > tvd_total_num) {
tvd_wrn("%s:row:%d*column:%d=%d must between 1 and %d\n",
__func__, dev->row, dev->column, tvd_num_to_used,
tvd_total_num);
return -1;
}
for (i = 0; i < tvd_total_num; ++i) {
if (dev->channel_index[i])
++ch_num_to_used;
if (dev->channel_index[i] > tvd_num_to_used) {
tvd_wrn("%s:ch%d's index greater then tvd_num_to_used\n",
__func__, i);
return -1;
}
for (j = 0; j < tvd_total_num; ++j) {
if (i == j || dev->channel_index[i] == 0)
continue;
if (dev->channel_index[i] == dev->channel_index[j]) {
tvd_wrn("channel index can not be same!\n");
return -1;
}
}
}
if (tvd_num_to_used != ch_num_to_used) {
tvd_wrn("%s:tvd_num_to_used != ch_num_to_used\n", __func__);
return -1;
}
return 0;
}
/**
* @name update_tvd_parm_from_raw_data
* @brief update tvd_dev var from v4l2_format
* @param[OUT] dev: tvd_dev var
* @param[IN] f: v4l2_format
* @return 0: success, negetive: fail, 1: need to reinit clk and tvd
*/
static int update_tvd_parm_from_raw_data(struct tvd_dev *dev,
const struct v4l2_format *f)
{
int ret = 0, i = 0;
u32 height = 0, width = 0;
u32 reinit_flag = 0;
tvd_here;
if (dev == NULL || f == NULL)
return -EINVAL;
if (dev->row != f->fmt.raw_data[RAW_DATA_ROW] ||
dev->column != f->fmt.raw_data[RAW_DATA_COLUMN] ||
dev->system != f->fmt.raw_data[RAW_DATA_SYSTEM] ||
dev->channel_index[0] != f->fmt.raw_data[RAW_DATA_CH0_INDEX] ||
dev->channel_index[1] != f->fmt.raw_data[RAW_DATA_CH1_INDEX] ||
dev->channel_index[2] != f->fmt.raw_data[RAW_DATA_CH2_INDEX] ||
dev->channel_index[3] != f->fmt.raw_data[RAW_DATA_CH3_INDEX]) {
reinit_flag = 1;
}
dev->system = f->fmt.raw_data[RAW_DATA_SYSTEM];
dev->format = f->fmt.raw_data[RAW_DATA_FORMAT];
dev->interface = f->fmt.raw_data[RAW_DATA_INTERFACE];
dev->row = f->fmt.raw_data[RAW_DATA_ROW];
dev->column = f->fmt.raw_data[RAW_DATA_COLUMN];
dev->channel_index[0] = f->fmt.raw_data[RAW_DATA_CH0_INDEX];
dev->channel_index[1] = f->fmt.raw_data[RAW_DATA_CH1_INDEX];
dev->channel_index[2] = f->fmt.raw_data[RAW_DATA_CH2_INDEX];
dev->channel_index[3] = f->fmt.raw_data[RAW_DATA_CH3_INDEX];
tvd_dbg("system:%d rowxcolumn:%dx%d\n", dev->system, dev->row,
dev->column);
if (check_user_setting_rule(dev)) {
tvd_wrn("%s:violation rule\n", __func__);
ret = -1;
goto OUT;
}
/*update info*/
for (i = 0; i < tvd_total_num; ++i) {
if (dev->channel_index[i]) {
tvd_status[i].tvd_used = 1;
tvd_status[i].tvd_opened = 1;
dev->id = i;
dev->sel = i;
} else {
tvd_status[i].tvd_used = 0;
if (tvd_status[i].tvd_clk_enabled) {
tvd_dbg("Disable tvd%d clk\n", i);
while (__clk_get_enable_count(tvd_clk[i]))
clk_disable(tvd_clk[i]);
tvd_status[i].tvd_clk_enabled = 0;
tvd_dbg("Disable tvd%d channel\n", i);
tvd_enable_chanel(i, 0);
tvd_adc_config(i, 0);
tvd_status[i].tvd_opened = 0;
}
}
}
dev->irq = tvd_irq[dev->sel];
dev->clk = tvd_clk[dev->sel];
switch (dev->format) {
case TVD_PL_YUV422:
case TVD_PL_YUV420:
dev->width = dev->row*720;
width = 720;
break;
case TVD_MB_YUV420:
dev->width = dev->row*704;
width = 704;
break;
default:
ret = -1;
tvd_wrn("ERROR,dev->format %d\n", dev->format);
dev->width = dev->row*720;
}
if ((dev->format == TVD_MB_YUV420) && (dev->column > 1)) {
/*cut 32 pixels448%2%32=0*/
height = dev->system ? 576 : 448;
} else {
height = dev->system ? 576 : 480;
}
dev->height = height * dev->column;
for (i = 0; i < tvd_total_num; ++i) {
if (!tvd_status[i].tvd_used)
continue;
if (dev->format == TVD_PL_YUV420) {
dev->channel_offset_y[i] =
((dev->channel_index[i] - 1) % dev->row) * width +
((dev->channel_index[i] - 1) / dev->row) *
dev->row * width * height;
dev->channel_offset_c[i] =
((dev->channel_index[i] - 1) % dev->row) * width +
((dev->channel_index[i] - 1) / dev->row) *
dev->row * width * height / 2;
} else if (dev->format == TVD_PL_YUV422) {
dev->channel_offset_y[i] =
((dev->channel_index[i] - 1) % dev->row) * width +
((dev->channel_index[i] - 1) / dev->row) *
dev->row * width * height;
dev->channel_offset_c[i] = dev->channel_offset_y[i];
} else if (dev->format == TVD_MB_YUV420) {
dev->channel_offset_y[i] =
((dev->channel_index[i] - 1) % dev->row) * width *
32 +
((dev->channel_index[i] - 1) / dev->row) *
dev->row * width * height;
dev->channel_offset_c[i] =
((dev->channel_index[i] - 1) % dev->row) * width *
32 +
((dev->channel_index[i] - 1) / dev->row) *
dev->row * width * height / 2;
}
tvd_dbg("%d:c->%d,y->%d\n", i, dev->channel_offset_c[i],
dev->channel_offset_y[i]);
}
if (ret == 0)
ret = reinit_flag;
OUT:
return ret;
}
static int tvd_cagc_and_3d_config(struct tvd_dev *dev)
{
int used = 0, ret = 0, i = 0;
int value = 0, mode = 0;
u32 tvd_total_num_tmp = tvd_total_num;
tvd_here;
for (; i < tvd_total_num_tmp; ++i) {
/* agc function */
if (dev->mulit_channel_mode && !tvd_status[i].tvd_used)
continue;
if (!dev->mulit_channel_mode && i != dev->sel)
continue;
ret = __tvd_fetch_sysconfig(i, (char *)"agc_auto_enable",
&mode);
if (ret)
goto CAGC;
if (mode == 0) {
/* manual mode */
ret = __tvd_fetch_sysconfig(
i, (char *)"agc_manual_value", &value);
if (ret)
goto CAGC;
tvd_agc_manual_config(i, (u32)value);
tvd_wrn("tvd%d agc manual:0x%x\n", i, value);
} else {
/* auto mode */
tvd_agc_auto_config(i);
tvd_wrn("tvd%d agc auto mode\n", i);
}
CAGC:
ret = __tvd_fetch_sysconfig(i, (char *)"cagc_enable",
&value);
if (ret)
continue;
tvd_cagc_config(i, (u32)value);
tvd_wrn("tvd%d CAGC enable:0x%x\n", i, value);
}
if (dev->mulit_channel_mode)
goto OUT;
ret = __tvd_fetch_sysconfig(dev->sel, (char *)"fliter_used", &used);
if (ret)
dev->fliter.used = 0;
else
dev->fliter.used = used;
if (dev->fliter.used) {
mutex_lock(&fliter_lock);
if (fliter_count < FLITER_NUM) {
if (__tvd_3d_comp_mem_request(
dev, (int)TVD_3D_COMP_BUFFER_SIZE)) {
/* no mem support for 3d fliter */
dev->fliter.used = 0;
mutex_unlock(&fliter_lock);
tvd_dbg(
"no mem support for 3d fliter\n");
goto OUT;
}
fliter_count++;
tvd_3d_mode(dev->sel, 1, (u32)dev->fliter.phy_address);
tvd_wrn("tvd%d 3d enable :0x%x\n", dev->sel,
(u32)dev->fliter.phy_address);
}
mutex_unlock(&fliter_lock);
}
OUT:
tvd_dbg("%s:Enable _3D_FLITER:%d,used:%d\n", __func__, fliter_count,
dev->fliter.used);
return 0;
}
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct tvd_dev *dev;
s32 ret = 0, i = 0, temp_sel = 0, temp_system = 0, reinit_flag = 0;
u32 locked = 0, system = 2;
tvd_here;
dev = video_drvdata(file);
if (tvd_is_generating(dev)) {
tvd_wrn("%s device busy\n", __func__);
return -EBUSY;
}
temp_sel = dev->sel;
if (dev->mulit_channel_mode) {
reinit_flag = update_tvd_parm_from_raw_data(dev, f);
if (reinit_flag < 0) {
ret = reinit_flag;
goto OUT;
}
} else {
dev->width = f->fmt.pix.width;
dev->height = f->fmt.pix.height;
/* tvd ypbpr now only support 720*480 & 720*576 */
temp_system = dev->system;
dev->system = (dev->height == 576) ? PAL : NTSC;
reinit_flag = (temp_system == dev->system) ? 0 : 1;
}
dev->fmt = __get_format(dev, f);
if (!dev->fmt) {
tvd_wrn("Fourcc format (0x%08x) invalid.\n",
f->fmt.pix.pixelformat);
return -EINVAL;
}
dev->fmt->field = V4L2_FIELD_NONE;
if (temp_sel != dev->sel) {
tvd_dbg("Free tvd%d irq:%d\n", temp_sel, tvd_irq[temp_sel]);
free_irq(tvd_irq[temp_sel], dev);
/* register irq again*/
tvd_dbg("request tvd%d's irq:%d\n", dev->sel, dev->irq);
ret = request_irq(dev->irq, tvd_isr, IRQF_SHARED, dev->name,
dev);
if (ret != 0)
goto OUT;
}
if (dev->mulit_channel_mode) {
__get_status(dev, &locked, &system);
if (!locked) {
tvd_wrn("%s: signal is not locked.\n", __func__);
ret = -EAGAIN;
goto OUT;
}
for (i = 0; i < tvd_total_num; ++i) {
if (dev->channel_index[i]) {
f->fmt.raw_data[RAW_DATA_CH0_STATUS + i] =
tvd_status[i].locked;
}
}
if (dev->system != system) {
dev->system = system;
reinit_flag = 1;
}
}
/*param has been updated by user*/
if (reinit_flag == 1) {
tvd_dbg("%s:need to reinit clk and tvd\n", __func__);
if (__tvd_clk_init(dev))
tvd_wrn("%s: clock init fail!\n", __func__);
/*
ret = __tvd_clk_enable(dev);
if (ret != 0)
goto OUT;
*/
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (!tvd_status[i].tvd_used)
continue;
tvd_init(i, dev->interface);
}
} else
tvd_init(dev->sel, dev->interface);
}
/*update status*/
tvd_wrn("\ninterface=%d\nsystem=%s\nformat=%d\noutput_fmt=%s\n",
dev->interface, (!dev->system) ? "NTSC" : "PAL", dev->format,
(!dev->fmt->output_fmt) ? "YUV420" : "YUV422");
tvd_wrn("\nrow=%d\ncolumn=%d\nch[0]=%d\nch[1]=%d\nch[2]=%d\nch[3]=%d\n",
dev->row, dev->column, dev->channel_index[0],
dev->channel_index[1], dev->channel_index[2],
dev->channel_index[3]);
tvd_wrn("\nwidth=%d\nheight=%d\ndev->sel=%d\n", dev->width, dev->height,
dev->sel);
__tvd_config(dev);
tvd_cagc_and_3d_config(dev);
OUT:
return ret;
}
static int vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
struct tvd_dev *dev = video_drvdata(file);
tvd_here;
return vb2_reqbufs(&dev->vb_vidq, p);
}
static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
struct tvd_dev *dev = video_drvdata(file);
return vb2_querybuf(&dev->vb_vidq, p);
}
static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
struct tvd_dev *dev = video_drvdata(file);
return vb2_qbuf(&dev->vb_vidq, p);
}
static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
int ret = 0;
struct tvd_dev *dev = video_drvdata(file);
ret = vb2_dqbuf(&dev->vb_vidq, p, file->f_flags & O_NONBLOCK);
return ret;
}
#ifdef CONFIG_VIDEO_V4L1_COMPAT
static int vidiocgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf)
{
struct tvd_dev *dev = video_drvdata(file);
return videobuf_cgmbuf(&dev->vb_vidq, mbuf, 8);
}
#endif
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct tvd_dev *dev = video_drvdata(file);
struct tvd_dmaqueue *dma_q = &dev->vidq;
struct tvd_buffer *buf;
int ret = 0;
tvd_here;
mutex_lock(&dev->stream_lock);
if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
ret = -EINVAL;
goto streamon_unlock;
}
if (tvd_is_generating(dev)) {
tvd_wrn("stream has been already on\n");
ret = -1;
goto streamon_unlock;
}
/* Resets frame counters */
dev->ms = 0;
dev->jiffies = jiffies;
dma_q->frame = 0;
dma_q->ini_jiffies = jiffies;
ret = vb2_streamon(&dev->vb_vidq, i);
if (ret)
goto streamon_unlock;
buf = list_entry(dma_q->active.next, struct tvd_buffer, list);
__tvd_set_addr(dev, buf);
tvd_irq_status_clear(dev->sel, TVD_IRQ_FRAME_END);
tvd_irq_enable(dev->sel, TVD_IRQ_FRAME_END);
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (!tvd_status[i].tvd_used)
continue;
tvd_capture_on(i);
}
} else
tvd_capture_on(dev->sel);
tvd_start_generating(dev);
tvd_wrn("Out vidioc_streamon:%d\n", dev->sel);
streamon_unlock:
mutex_unlock(&dev->stream_lock);
return ret;
}
static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct tvd_dev *dev = video_drvdata(file);
struct tvd_dmaqueue *dma_q = &dev->vidq;
int ret = 0;
mutex_lock(&dev->stream_lock);
tvd_here;
if (!tvd_is_generating(dev)) {
tvd_wrn("%s: stream has been already off\n", __func__);
ret = 0;
goto streamoff_unlock;
}
tvd_stop_generating(dev);
/* Resets frame counters */
dev->ms = 0;
dev->jiffies = jiffies;
dma_q->frame = 0;
dma_q->ini_jiffies = jiffies;
/* disable hardware */
tvd_irq_disable(dev->sel, TVD_IRQ_FRAME_END);
tvd_irq_status_clear(dev->sel, TVD_IRQ_FRAME_END);
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (tvd_status[i].tvd_used)
tvd_capture_off(i);
}
} else
tvd_capture_off(dev->sel);
if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
ret = -EINVAL;
goto streamoff_unlock;
}
ret = vb2_streamoff(&dev->vb_vidq, i);
if (ret != 0) {
tvd_wrn("%s: videobu_streamoff error!\n", __func__);
goto streamoff_unlock;
}
streamoff_unlock:
mutex_unlock(&dev->stream_lock);
return ret;
}
static int vidioc_enum_input(struct file *file, void *priv,
struct v4l2_input *inp)
{
tvd_here;
if (inp->index > NUM_INPUTS - 1) {
tvd_wrn("%s: input index invalid!\n", __func__);
return -EINVAL;
}
inp->type = V4L2_INPUT_TYPE_CAMERA;
return 0;
}
static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
{
struct tvd_dev *dev = video_drvdata(file);
tvd_here;
tvd_dbg("%s:\n", __func__);
*i = dev->input;
return 0;
}
static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
{
struct tvd_dev *dev = video_drvdata(file);
tvd_dbg("%s: input_num = %d\n", __func__, i);
/* only one device */
if (i > 0) {
tvd_wrn("%s: set input error!\n", __func__);
return -EINVAL;
}
dev->input = i;
return 0;
}
static int vidioc_g_parm(struct file *file, void *priv,
struct v4l2_streamparm *parms)
{
struct tvd_dev *dev = video_drvdata(file);
tvd_here;
if (parms->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
parms->parm.capture.timeperframe.numerator = dev->fps.numerator;
parms->parm.capture.timeperframe.denominator =
dev->fps.denominator;
}
return 0;
}
static int vidioc_s_parm(struct file *file, void *priv,
struct v4l2_streamparm *parms)
{
struct tvd_dev *dev = video_drvdata(file);
tvd_here;
if (parms->parm.capture.capturemode != V4L2_MODE_VIDEO &&
parms->parm.capture.capturemode != V4L2_MODE_IMAGE &&
parms->parm.capture.capturemode != V4L2_MODE_PREVIEW)
parms->parm.capture.capturemode = V4L2_MODE_PREVIEW;
dev->capture_mode = parms->parm.capture.capturemode;
return 0;
}
static int vidioc_enum_framesizes(struct file *file, void *priv,
struct v4l2_frmsizeenum *fsize)
{
int i;
static const struct v4l2_frmsize_discrete sizes[] = {
{
.width = 720, .height = 480,
},
{
.width = 720, .height = 576,
},
};
tvd_here;
/* there are two kinds of framesize*/
if (fsize->index > 1)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(formats); i++)
if (formats[i].fourcc == fsize->pixel_format)
break;
if (i == ARRAY_SIZE(formats)) {
tvd_wrn("format not found\n");
return -EINVAL;
}
fsize->discrete.width = sizes[fsize->index].width;
fsize->discrete.height = sizes[fsize->index].height;
return 0;
}
static ssize_t tvd_read(struct file *file, char __user *data, size_t count,
loff_t *ppos)
{
struct tvd_dev *dev = video_drvdata(file);
tvd_here;
if (tvd_is_generating(dev)) {
return vb2_read(&dev->vb_vidq, data, count, ppos,
file->f_flags & O_NONBLOCK);
} else {
tvd_wrn("%s: tvd is not generating!\n", __func__);
return -EINVAL;
}
}
static unsigned int tvd_poll(struct file *file, struct poll_table_struct *wait)
{
struct tvd_dev *dev = video_drvdata(file);
struct vb2_queue *q = &dev->vb_vidq;
if (tvd_is_generating(dev)) {
return vb2_poll(q, file, wait);
} else {
tvd_wrn("%s: tvd is not generating!\n", __func__);
return -EINVAL;
}
}
static int __tvd_power_enable(int i, bool is_true)
{
int ret = 0;
tvd_here;
regu[i] = regulator_get(NULL, &tvd_power[i][0]);
if (IS_ERR_OR_NULL(regu[i])) {
tvd_wrn("regulator is err.\n");
return -EBUSY;
}
if (is_true) {
regulator_set_voltage(regu[i], 3300000, 3300000);
ret = regulator_enable(regu[i]);
} else
ret = regulator_disable(regu[i]);
regulator_put(regu[i]);
regu[i] = NULL;
return ret;
}
static int __tvd_gpio_request(struct gpio_config *pin_cfg)
{
int ret = 0;
char pin_name[32] = {0};
u32 config;
ret = gpio_request(pin_cfg->gpio, NULL);
if (ret) {
tvd_wrn("tvd gpio(%d) request err!\n", pin_cfg->gpio);
return -EBUSY;
}
sunxi_gpio_to_name(pin_cfg->gpio, pin_name);
config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_FUNC, pin_cfg->mul_sel);
pin_config_set(SUNXI_PINCTRL, pin_name, config);
if (pin_cfg->pull != GPIO_PULL_DEFAULT) {
config =
SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_PUD, pin_cfg->pull);
pin_config_set(SUNXI_PINCTRL, pin_name, config);
}
if (pin_cfg->drv_level != GPIO_DRVLVL_DEFAULT) {
config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DRV,
pin_cfg->drv_level);
pin_config_set(SUNXI_PINCTRL, pin_name, config);
}
if (pin_cfg->data != GPIO_DATA_DEFAULT) {
config =
SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DAT, pin_cfg->data);
pin_config_set(SUNXI_PINCTRL, pin_name, config);
}
return 0;
}
static int tvd_open(struct file *file)
{
struct tvd_dev *dev = video_drvdata(file);
int ret = -1;
int i = 0;
tvd_here;
tvd_dbg("open = %s", file->f_path.dentry->d_iname);
dev->system = 0;
if (!strncmp(file->f_path.dentry->d_iname, "video8", strlen("video8"))) {
dev->row = tvd_row;
dev->column = tvd_column;
dev->mulit_channel_mode = 1;
tvd_dbg("open multi_chanel!,dev->row = %d,dev->column = %d\n", dev->row, dev->column);
} else {
dev->row = 1;
dev->column = 1;
dev->mulit_channel_mode = 0;
tvd_dbg("single_chanel!\n");
}
if (tvd_is_opened(dev)) {
tvd_wrn("%s: device open busy\n", __func__);
return -EBUSY;
}
if (dev->mulit_channel_mode == 0) {
if (tvd_status[dev->sel].tvd_opened) {
tvd_wrn("%s: tvd%d was opened in multich mode\n",
__func__, dev->sel);
return -EBUSY;
}
tvd_status[dev->sel].tvd_opened = 1;
} else {
for (i = 0; i < tvd_total_num; ++i) {
if (tvd_status[i].tvd_used &&
tvd_status[i].tvd_opened) {
tvd_wrn("%s: tvd%d was opened in single mode\n",
__func__, i);
return -EBUSY;
}
if (tvd_status[i].tvd_used)
tvd_status[i].tvd_opened = 1;
}
}
if (tvd_hot_plug)
__tvd_auto_plug_disable(dev);
/*dev->system = NTSC;*/
/* gpio power, open only once */
mutex_lock(&power_lock);
if (!atomic_read(&gpio_power_enable_count)) {
for (i = 0; i < atomic_read(&tvd_used_gpio_num); i++)
ret = __tvd_gpio_request(&tvd_gpio_config[i]);
}
atomic_inc(&gpio_power_enable_count);
/* pmu power */
for (i = 0; i < atomic_read(&tvd_used_power_num); i++) {
ret = __tvd_power_enable(i, true);
if (ret)
tvd_wrn("power(%s) enable failed.\n", &tvd_power[i][0]);
}
mutex_unlock(&power_lock);
__tvd_init(dev); /*init base addr*/
/* register irq */
tvd_dbg("request_irq:%d\n", dev->irq);
ret = request_irq(dev->irq, tvd_isr, IRQF_SHARED, dev->v4l2_dev.name, dev);
if (__tvd_clk_init(dev))
tvd_wrn("%s: clock init fail!\n", __func__);
ret = __tvd_clk_enable(dev);
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (!tvd_status[i].tvd_used)
continue;
tvd_init(i, dev->interface);
}
} else
tvd_init(dev->sel, dev->interface);
dev->input = 0; /* default input null */
tvd_start_opened(dev);
return 0;
}
/*TODO:close all resource*/
static int tvd_close(struct file *file)
{
struct tvd_dev *dev = video_drvdata(file);
int ret = 0;
int i = 0;
tvd_here;
tvd_stop_generating(dev);
tvd_deinit(dev->sel, dev->interface);
mutex_lock(&fliter_lock);
tvd_dbg("%s:Enable _3D_FLITER:%d,used:%d\n", __func__, fliter_count,
dev->fliter.used);
if (fliter_count > 0 && dev->fliter.used) {
__tvd_3d_comp_mem_free(dev);
fliter_count--;
tvd_3d_mode(dev->sel, 0, 0);
}
mutex_unlock(&fliter_lock);
__tvd_clk_disable(dev);
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (tvd_status[i].tvd_used) {
tvd_dbg("Disable tvd%d\n", i);
tvd_status[i].tvd_opened = 0;
}
}
} else {
tvd_status[dev->sel].tvd_opened = 0;
}
vb2_queue_release(&dev->vb_vidq);
tvd_stop_opened(dev);
tvd_dbg("Free tvd%d irq:%d\n", dev->sel, dev->irq);
free_irq(dev->irq, dev);
/* close pmu power */
mutex_lock(&power_lock);
for (i = 0; i < atomic_read(&tvd_used_power_num); i++) {
ret = __tvd_power_enable(i, false);
if (ret)
tvd_wrn("power(%s) disable failed.\n", &tvd_power[i][0]);
}
if (atomic_dec_and_test(&gpio_power_enable_count)) {
for (i = 0; i < atomic_read(&tvd_used_gpio_num); i++)
gpio_free(tvd_gpio_config[i].gpio);
}
mutex_unlock(&power_lock);
if (tvd_hot_plug)
__tvd_auto_plug_enable(dev);
tvd_dbg("tvd_close end\n");
return ret;
}
static int tvd_mmap(struct file *file, struct vm_area_struct *vma)
{
struct tvd_dev *dev = video_drvdata(file);
int ret;
tvd_dbg("%s: mmap called, vma=0x%08lx\n", __func__,
(unsigned long)vma);
ret = vb2_mmap(&dev->vb_vidq, vma);
tvd_dbg("%s: vma start=0x%08lx, size=%ld, ret=%d\n", __func__,
(unsigned long)vma->vm_start,
(unsigned long)vma->vm_end - (unsigned long)vma->vm_start,
ret);
return ret;
}
static int tvd_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
{
int ret = 0;
struct tvd_dev *dev =
container_of(ctrl->handler, struct tvd_dev, ctrl_handler);
struct v4l2_control c;
tvd_here;
memset((void *)&c, 0, sizeof(struct v4l2_control));
c.id = ctrl->id;
switch (c.id) {
case V4L2_CID_BRIGHTNESS:
c.value = tvd_get_luma(dev->sel);
break;
case V4L2_CID_CONTRAST:
c.value = tvd_get_contrast(dev->sel);
break;
case V4L2_CID_SATURATION:
c.value = tvd_get_saturation(dev->sel);
break;
}
ctrl->val = c.value;
return ret;
}
static int tvd_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct tvd_dev *dev =
container_of(ctrl->handler, struct tvd_dev, ctrl_handler);
int ret = 0, i = 0;
struct v4l2_control c;
tvd_dbg("%s: %s set value: 0x%x\n", __func__, ctrl->name, ctrl->val);
c.id = ctrl->id;
c.value = ctrl->val;
switch (ctrl->id) {
case V4L2_CID_BRIGHTNESS:
tvd_dbg("%s: V4L2_CID_BRIGHTNESS sel=%d, val=%d,\n", __func__,
dev->sel, ctrl->val);
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (tvd_status[i].tvd_used)
tvd_set_luma(i, ctrl->val);
}
} else
tvd_set_luma(dev->sel, ctrl->val);
break;
case V4L2_CID_CONTRAST:
tvd_dbg("%s: V4L2_CID_CONTRAST sel=%d, val=%d,\n", __func__,
dev->sel, ctrl->val);
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (tvd_status[i].tvd_used)
tvd_set_contrast(i, ctrl->val);
}
} else
tvd_set_contrast(dev->sel, ctrl->val);
break;
case V4L2_CID_SATURATION:
tvd_dbg("%s: V4L2_CID_SATURATION sel=%d, val=%d,\n", __func__,
dev->sel, ctrl->val);
if (dev->mulit_channel_mode) {
for (i = 0; i < tvd_total_num; ++i) {
if (tvd_status[i].tvd_used)
tvd_set_saturation(i, ctrl->val);
}
} else
tvd_set_saturation(dev->sel, ctrl->val);
break;
}
return ret;
}
static void __tvd_set_addr_special(struct tvd_dev *dev,
struct tvd_buffer *buffer)
{
unsigned long addr_org;
unsigned int c_offset = 0;
if (buffer == NULL || buffer->paddr == NULL) {
tvd_wrn("%s: vb_buf->priv is NULL!\n", __func__);
return;
}
addr_org = (unsigned long)buffer->paddr;
switch (dev->fmt->output_fmt) {
case TVD_PL_YUV422:
case TVD_PL_YUV420:
c_offset = dev->width * dev->height;
break;
case TVD_MB_YUV420:
c_offset = 0;
break;
default:
break;
}
tvd_set_wb_addr(dev->sel, addr_org, addr_org + c_offset);
tvd_dbg("%s: format:%d, addr_org = 0x%p, addr_org + c_offset = 0x%p\n",
__func__, dev->format, (void *)addr_org,
(void *)(addr_org + c_offset));
}
/* tvd device for special, 0 ~ tvd_count-1 */
int tvd_info_special(void)
{
return tvd_count;
}
EXPORT_SYMBOL(tvd_info_special);
int tvd_open_special(int tvd_fd)
{
struct tvd_dev *dev = tvd[tvd_fd];
int ret = -1;
int i = 0;
struct tvd_dmaqueue *active = &dev->vidq_special;
struct tvd_dmaqueue *done = &dev->done_special;
dev->row = 1;
dev->column = 1;
dev->mulit_channel_mode = 0;
tvd_dbg("%s:\n", __func__);
if (tvd_is_opened(dev)) {
tvd_wrn("%s: device open busy\n", __func__);
return -EBUSY;
}
dev->system = NTSC;
/* gpio power, open only once */
mutex_lock(&power_lock);
if (!atomic_read(&gpio_power_enable_count)) {
for (i = 0; i < atomic_read(&tvd_used_gpio_num); i++)
ret = __tvd_gpio_request(&tvd_gpio_config[i]);
}
atomic_inc(&gpio_power_enable_count);
/* pmu power */
for (i = 0; i < atomic_read(&tvd_used_power_num); i++) {
ret = __tvd_power_enable(i, true);
if (ret)
tvd_wrn("power(%s) enable failed.\n", &tvd_power[i][0]);
}
mutex_unlock(&power_lock);
INIT_LIST_HEAD(&active->active);
INIT_LIST_HEAD(&done->active);
__tvd_init(dev);
/* register irq */
tvd_dbg("request_irq:%d\n", dev->irq);
ret = request_irq(dev->irq, tvd_isr, IRQF_SHARED, dev->name, dev);
if (__tvd_clk_init(dev))
tvd_wrn("%s: clock init fail!\n", __func__);
ret = __tvd_clk_enable(dev);
tvd_init(dev->sel, dev->interface);
dev->input = 0;
dev->special_active = 1;
tvd_start_opened(dev);
return ret;
}
EXPORT_SYMBOL(tvd_open_special);
int tvd_close_special(int tvd_fd)
{
struct tvd_dev *dev = tvd[tvd_fd];
int ret = 0;
int i = 0;
struct tvd_dmaqueue *active = &dev->vidq_special;
struct tvd_dmaqueue *done = &dev->done_special;
tvd_dbg("%s:\n", __func__);
tvd_stop_generating(dev);
__tvd_clk_disable(dev);
tvd_stop_opened(dev);
tvd_dbg("Free tvd%d irq:%d\n", dev->sel, dev->irq);
free_irq(dev->irq, dev);
INIT_LIST_HEAD(&active->active);
INIT_LIST_HEAD(&done->active);
dev->special_active = 0;
/* close pmu power */
mutex_lock(&power_lock);
for (i = 0; i < atomic_read(&tvd_used_power_num); i++) {
ret = __tvd_power_enable(i, false);
if (ret)
tvd_wrn("power(%s) disable failed.\n", &tvd_power[i][0]);
}
if (atomic_dec_and_test(&gpio_power_enable_count)) {
for (i = 0; i < atomic_read(&tvd_used_gpio_num); i++)
gpio_free(tvd_gpio_config[i].gpio);
}
mutex_unlock(&power_lock);
return ret;
}
EXPORT_SYMBOL(tvd_close_special);
int vidioc_s_fmt_vid_cap_special(int tvd_fd, struct v4l2_format *f)
{
struct tvd_dev *dev = tvd[tvd_fd];
int ret = 0;
tvd_dbg("%s:\n", __func__);
if (tvd_is_generating(dev)) {
tvd_wrn("%s device busy\n", __func__);
return -EBUSY;
}
dev->fmt = __get_format(dev, f);
if (!dev->fmt) {
tvd_wrn("Fourcc format (0x%08x) invalid.\n",
f->fmt.pix.pixelformat);
return -EINVAL;
}
dev->width = f->fmt.pix.width;
dev->height = f->fmt.pix.height;
dev->fmt->field = V4L2_FIELD_NONE;
if (dev->height == 576) {
dev->system = PAL;
/* To solve the problem of PAL signal is not well.
* Accoding to the hardware designer, tvd need 29.7M
* clk input on PAL system, so here adjust clk again.
* Before this modify, PAL and NTSC have the same
* frequency which is 27M.
*/
__tvd_clk_init(dev);
} else {
dev->system = NTSC;
}
__tvd_config(dev);
tvd_cagc_and_3d_config(dev);
tvd_dbg("\ninterface=%d\nsystem=%s\nformat=%d\noutput_fmt=%s\n",
dev->interface, (!dev->system) ? "NTSC" : "PAL", dev->format,
(!dev->fmt->output_fmt) ? "YUV420" : "YUV422");
tvd_dbg("\nwidth=%d\nheight=%d\ndev->sel=%d\n", dev->width, dev->height,
dev->sel);
return ret;
}
EXPORT_SYMBOL(vidioc_s_fmt_vid_cap_special);
int vidioc_g_fmt_vid_cap_special(int tvd_fd, struct v4l2_format *f)
{
struct tvd_dev *dev = tvd[tvd_fd];
u32 locked = 0, system = 2;
f->fmt.pix.width = dev->width;
f->fmt.pix.height = dev->height;
__get_status(dev, &locked, &system);
if (!locked) {
tvd_dbg("%s: signal is not locked.\n", __func__);
return -EAGAIN;
} else {
/* system: 1->pal, 0->ntsc */
if (system == PAL) {
f->fmt.pix.width = 720;
f->fmt.pix.height = 576;
} else if (system == NTSC) {
f->fmt.pix.width = 720;
f->fmt.pix.height = 480;
} else {
tvd_wrn("system is not sure.\n");
}
}
tvd_dbg("system = %d, w = %d, h = %d\n", system, f->fmt.pix.width,
f->fmt.pix.height);
return 0;
}
EXPORT_SYMBOL(vidioc_g_fmt_vid_cap_special);
int dqbuffer_special(int tvd_fd, struct tvd_buffer **buf)
{
int ret = 0;
unsigned long flags = 0;
struct tvd_dev *dev = tvd[tvd_fd];
struct tvd_dmaqueue *done = &dev->done_special;
spin_lock_irqsave(&dev->slock, flags);
if (!list_empty(&done->active)) {
*buf = list_first_entry(&done->active, struct tvd_buffer, list);
list_del(&((*buf)->list));
(*buf)->state = VB2_BUF_STATE_DEQUEUED;
} else {
ret = -1;
}
spin_unlock_irqrestore(&dev->slock, flags);
return ret;
}
EXPORT_SYMBOL(dqbuffer_special);
int qbuffer_special(int tvd_fd, struct tvd_buffer *buf)
{
struct tvd_dev *dev = tvd[tvd_fd];
struct tvd_dmaqueue *vidq = &dev->vidq_special;
unsigned long flags = 0;
int ret = 0;
spin_lock_irqsave(&dev->slock, flags);
list_add_tail(&buf->list, &vidq->active);
buf->state = VB2_BUF_STATE_QUEUED;
spin_unlock_irqrestore(&dev->slock, flags);
return ret;
}
EXPORT_SYMBOL(qbuffer_special);
int vidioc_streamon_special(int tvd_fd, enum v4l2_buf_type i)
{
struct tvd_dev *dev = tvd[tvd_fd];
struct tvd_dmaqueue *dma_q = &dev->vidq_special;
struct tvd_buffer *buf = NULL;
int ret = 0;
tvd_dbg("%s:\n", __func__);
mutex_lock(&dev->stream_lock);
if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
ret = -EINVAL;
goto streamon_unlock;
}
if (tvd_is_generating(dev)) {
tvd_wrn("stream has been already on\n");
ret = -1;
goto streamon_unlock;
}
dev->ms = 0;
dev->jiffies = jiffies;
dma_q->frame = 0;
dma_q->ini_jiffies = jiffies;
if (!list_empty(&dma_q->active)) {
buf = list_entry(dma_q->active.next, struct tvd_buffer, list);
} else {
tvd_wrn("stream on, but no buffer now.\n");
goto streamon_unlock;
}
__tvd_set_addr_special(dev, buf);
tvd_irq_status_clear(dev->sel, TVD_IRQ_FRAME_END);
tvd_irq_enable(dev->sel, TVD_IRQ_FRAME_END);
tvd_capture_on(dev->sel);
tvd_start_generating(dev);
streamon_unlock:
mutex_unlock(&dev->stream_lock);
return ret;
}
EXPORT_SYMBOL(vidioc_streamon_special);
int vidioc_streamoff_special(int tvd_fd, enum v4l2_buf_type i)
{
struct tvd_dev *dev = tvd[tvd_fd];
struct tvd_dmaqueue *dma_q = &dev->vidq_special;
struct tvd_dmaqueue *donelist = &dev->done_special;
struct tvd_buffer *buffer;
unsigned long flags = 0;
int ret = 0;
mutex_lock(&dev->stream_lock);
tvd_dbg("%s:\n", __func__);
if (!tvd_is_generating(dev)) {
tvd_wrn("%s: stream has been already off\n", __func__);
ret = 0;
goto streamoff_unlock;
}
tvd_stop_generating(dev);
dev->ms = 0;
dev->jiffies = jiffies;
dma_q->frame = 0;
dma_q->ini_jiffies = jiffies;
tvd_irq_disable(dev->sel, TVD_IRQ_FRAME_END);
tvd_irq_status_clear(dev->sel, TVD_IRQ_FRAME_END);
tvd_capture_off(dev->sel);
spin_lock_irqsave(&dev->slock, flags);
while (!list_empty(&dma_q->active)) {
buffer =
list_first_entry(&dma_q->active, struct tvd_buffer, list);
list_del(&buffer->list);
list_add(&buffer->list, &donelist->active);
}
spin_unlock_irqrestore(&dev->slock, flags);
if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
ret = -EINVAL;
goto streamoff_unlock;
}
if (ret != 0) {
tvd_wrn("%s: videobu_streamoff error!\n", __func__);
goto streamoff_unlock;
}
streamoff_unlock:
mutex_unlock(&dev->stream_lock);
return ret;
}
EXPORT_SYMBOL(vidioc_streamoff_special);
static void (*tvd_buffer_done)(int tvd_fd);
void tvd_register_buffer_done_callback(void *func)
{
tvd_here;
tvd_buffer_done = func;
}
EXPORT_SYMBOL(tvd_register_buffer_done_callback);
static irqreturn_t tvd_isr_special(int irq, void *priv)
{
struct tvd_buffer *buf;
/*unsigned long flags;*/
struct tvd_dev *dev = (struct tvd_dev *)priv;
struct tvd_dmaqueue *dma_q = &dev->vidq_special;
struct tvd_dmaqueue *done = &dev->done_special;
if (tvd_is_generating(dev) == 0) {
tvd_irq_status_clear(dev->sel, TVD_IRQ_FRAME_END);
return IRQ_HANDLED;
}
if (0 == dev->first_flag) {
dev->first_flag = 1;
goto set_next_addr;
}
if (list_empty(&dma_q->active) ||
dma_q->active.next->next == (&dma_q->active)) {
tvd_dbg("No active queue to serve\n");
goto unlock;
}
buf = list_entry(dma_q->active.next, struct tvd_buffer, list);
list_del(&buf->list);
dev->ms += jiffies_to_msecs(jiffies - dev->jiffies);
dev->jiffies = jiffies;
list_add_tail(&buf->list, &done->active);
if (tvd_buffer_done)
tvd_buffer_done(dev->id);
if (list_empty(&dma_q->active)) {
tvd_dbg("%s: No more free frame\n", __func__);
goto unlock;
}
if ((&dma_q->active) == dma_q->active.next->next) {
tvd_dbg("No more free frame on next time\n");
goto unlock;
}
set_next_addr:
buf = list_entry(dma_q->active.next->next, struct tvd_buffer, list);
__tvd_set_addr_special(dev, buf);
unlock:
tvd_irq_status_clear(dev->sel, TVD_IRQ_FRAME_END);
return IRQ_HANDLED;
}
/* ------------------------------------------------------------------
File operations for the device
------------------------------------------------------------------*/
static const struct v4l2_ctrl_ops tvd_ctrl_ops = {
.g_volatile_ctrl = tvd_g_volatile_ctrl, .s_ctrl = tvd_s_ctrl,
};
static const struct v4l2_file_operations tvd_fops = {
.owner = THIS_MODULE,
.open = tvd_open,
.release = tvd_close,
.read = tvd_read,
.poll = tvd_poll,
.unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
/*.compat_ioctl32 = tvd_compat_ioctl32,*/
#endif
.mmap = tvd_mmap,
};
static const struct v4l2_ioctl_ops tvd_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_enum_framesizes = vidioc_enum_framesizes,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
.vidioc_g_parm = vidioc_g_parm,
.vidioc_s_parm = vidioc_s_parm,
#ifdef CONFIG_VIDEO_V4L1_COMPAT
.vidiocgmbuf = vidiocgmbuf,
#endif
};
static struct video_device tvd_template[] = {
[0] = {
.name = "tvd_0",
.fops = &tvd_fops,
.ioctl_ops = &tvd_ioctl_ops,
.release = video_device_release,
},
[1] = {
.name = "tvd_1",
.fops = &tvd_fops,
.ioctl_ops = &tvd_ioctl_ops,
.release = video_device_release,
},
[2] = {
.name = "tvd_2",
.fops = &tvd_fops,
.ioctl_ops = &tvd_ioctl_ops,
.release = video_device_release,
},
[3] = {
.name = "tvd_3",
.fops = &tvd_fops,
.ioctl_ops = &tvd_ioctl_ops,
.release = video_device_release,
},
[4] = {
.name = "tvd_multi_ch",
.fops = &tvd_fops,
.ioctl_ops = &tvd_ioctl_ops,
.release = video_device_release,
},
};
static int __tvd_init_controls(struct v4l2_ctrl_handler *hdl)
{
unsigned int ret = 0;
tvd_here;
v4l2_ctrl_handler_init(hdl, 4);
v4l2_ctrl_new_std(hdl, &tvd_ctrl_ops, V4L2_CID_BRIGHTNESS, 0, 255, 1,
128);
v4l2_ctrl_new_std(hdl, &tvd_ctrl_ops, V4L2_CID_CONTRAST, 0, 128, 1, 0);
v4l2_ctrl_new_std(hdl, &tvd_ctrl_ops, V4L2_CID_SATURATION, -4, 4, 1, 0);
if (hdl->error) {
tvd_wrn("%s: hdl init err!\n", __func__);
ret = hdl->error;
v4l2_ctrl_handler_free(hdl);
}
return ret;
}
static int __tvd_fetch_sysconfig(int sel, char *sub_name, int value[])
{
char compat[32];
u32 len = 0;
struct device_node *node;
int ret = 0;
tvd_here;
if (sel == -1)
len = sprintf(compat, "allwinner,sunxi-tvd");
else
len = sprintf(compat, "allwinner,sunxi-tvd%d", sel);
if (len > 32)
tvd_wrn("size of mian_name is out of range\n");
node = of_find_compatible_node(NULL, NULL, compat);
if (!node) {
tvd_wrn("of_find_compatible_node %s fail\n", compat);
return -EINVAL;
}
if (of_property_read_u32_array(node, sub_name, value, 1)) {
tvd_wrn("of_property_read_u32_array %s.%s fail\n", compat,
sub_name);
return -EINVAL;
}
return ret;
}
static int __jude_config(struct tvd_dev *dev)
{
int ret = 0;
int id = dev->id;
int tvd_used[TVD_MAX] = {0};
/*int tvd_if[TVD_MAX] = {0};*/
int value = 0;
tvd_here;
if (id > 3 || id < 0) {
tvd_wrn("%s: id is wrong!\n", __func__);
return -ENODEV;
}
/* first set sel as id */
/*dev->sel = id;*/
tvd_dbg("%s: sel = %d.\n", __func__, dev->sel);
ret = __tvd_fetch_sysconfig(id, (char *)"tvd_used",
&tvd_used[id]);
if (ret) {
tvd_wrn("%s: fetch tvd_used %d err!", __func__, id);
return -EINVAL;
}
if (!tvd_used[id]) {
tvd_dbg("%s: tvd_used[%d] is null.\n", __func__, id);
return -ENODEV;
}
/*
ret = __tvd_fetch_sysconfig(id, (char *)"tvd_if",
&tvd_if[id]);
if (ret) {
tvd_wrn("%s: fetch tvd_if %d err!", __func__, id);
return -EINVAL;
}
dev->interface = tvd_if[id];
*/
ret = __tvd_fetch_sysconfig(-1, (char *)"tvd_interface", &value);
dev->interface = value;
if (id > 0) {
if (tvd_used[id] && dev->interface > 0) {
/* when tvd0 used and was configed as ypbpr,can not use
* tvd1,2 */
if (id == 1 || id == 2) {
return -ENODEV;
} else if (id == 3) {
/* reset id as 1, for video4 ypbpr,video5 cvbs*/
dev->id = 1;
return 0;
}
} else {
return 0;
}
}
return 0;
}
#if defined(CONFIG_EXTCON)
static const unsigned int tvd_cable[] = {
EXTCON_DISP_TVD,
EXTCON_NONE,
};
static char switch_lock_name[20];
static char switch_system_name[20];
static struct extcon_dev *switch_lock[TVD_MAX];
static struct extcon_dev *switch_system[TVD_MAX];
static struct task_struct *tvd_task;
static int __tvd_auto_plug_init(struct tvd_dev *dev)
{
int ret = -1;
snprintf(switch_lock_name, sizeof(switch_lock_name), "tvd_lock%d",
dev->sel);
switch_lock[dev->sel] =
devm_extcon_dev_allocate(&dev->pdev->dev, tvd_cable);
if (IS_ERR_OR_NULL(switch_lock[dev->sel])) {
tvd_wrn("Alloc switch lock error\n");
goto err_lock_alloc;
}
switch_lock[dev->sel]->name = switch_lock_name;
ret = devm_extcon_dev_register(&dev->pdev->dev, switch_lock[dev->sel]);
if (ret) {
tvd_wrn("Register switch lock error\n");
goto err_lock_regsiter;
}
snprintf(switch_system_name, sizeof(switch_system_name), "tvd_system%d",
dev->sel);
switch_system[dev->sel] =
devm_extcon_dev_allocate(&dev->pdev->dev, tvd_cable);
if (IS_ERR_OR_NULL(switch_system[dev->sel])) {
tvd_wrn("Alloc switch lock error\n");
goto err_system_alloc;
}
switch_system[dev->sel]->name = switch_system_name;
ret = devm_extcon_dev_register(&dev->pdev->dev, switch_system[dev->sel]);
if (ret) {
tvd_wrn("Register switch_system error\n");
goto err_system_register;
}
return ret;
err_system_register:
devm_extcon_dev_free(&dev->pdev->dev, switch_system[dev->sel]);
devm_extcon_dev_unregister(&dev->pdev->dev, switch_system[dev->sel]);
err_system_alloc:
err_lock_regsiter:
devm_extcon_dev_free(&dev->pdev->dev, switch_lock[dev->sel]);
devm_extcon_dev_unregister(&dev->pdev->dev, switch_lock[dev->sel]);
err_lock_alloc:
return ret;
}
static void __tvd_auto_plug_exit(struct tvd_dev *dev)
{
devm_extcon_dev_free(&dev->pdev->dev, switch_lock[dev->sel]);
devm_extcon_dev_unregister(&dev->pdev->dev, switch_lock[dev->sel]);
devm_extcon_dev_free(&dev->pdev->dev, switch_system[dev->sel]);
devm_extcon_dev_unregister(&dev->pdev->dev, switch_system[dev->sel]);
}
/**
* __tvd_check_detect_toggle() - check if the hpd status toggled
* @hpd: pointer to hpd status array[3]
* @hpd_cur: current hpd status
*
* Only detect status keeps true for three times, it's plugin,
* when it is false once, it's plugout.
*/
static bool __tvd_check_detect_toggle(bool *hpd, bool hpd_cur)
{
bool ret = false;
/* plug in */
if ((hpd[0] == true) && (hpd[1] == true) && (hpd[2] == false) &&
(hpd_cur == true)) {
ret = true;
tvd_dbg("%s, plug in\n", __func__);
}
/* plug out */
if ((hpd[0] == true) && (hpd[1] == true) && (hpd[2] == true) &&
(hpd_cur == false)) {
ret = true;
tvd_dbg("%s, plug out\n", __func__);
}
hpd[2] = hpd[1];
hpd[1] = hpd[0];
hpd[0] = hpd_cur;
return ret;
}
static int __tvd_detect_thread(void *parg)
{
s32 i = 0;
u32 locked = 0;
u32 system = 2;
static u32 systems[TVD_MAX];
static bool hpd[TVD_MAX][3];
for (i = 0; i < TVD_MAX; i++) {
systems[i] = NONE;
hpd[i][0] = false;
hpd[i][1] = false;
hpd[i][2] = false;
}
for (;;) {
if (kthread_should_stop())
break;
for (i = 0; i < tvd_count; i++) {
tvd_get_status(i, &locked, &system);
if (__tvd_check_detect_toggle(hpd[i], (locked == 1))) {
tvd_wrn("hpd=%d, i = %d\n", locked, i);
extcon_set_state_sync(switch_lock[i], EXTCON_DISP_TVD,
locked);
}
if (systems[i] != system) {
tvd_wrn("system = %d, i = %d\n", system, i);
systems[i] = system;
extcon_set_state_sync(switch_system[i], EXTCON_DISP_TVD,
system);
}
}
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(20);
}
return 0;
}
static int __tvd_auto_plug_enable(struct tvd_dev *dev)
{
int ret = 0;
int i = 0;
dev->system = NTSC;
/* gpio power, open only once */
mutex_lock(&power_lock);
if (!atomic_read(&gpio_power_enable_count)) {
for (i = 0; i < atomic_read(&tvd_used_gpio_num); i++)
ret = __tvd_gpio_request(&tvd_gpio_config[i]);
}
atomic_inc(&gpio_power_enable_count);
/* pmu power */
for (i = 0; i < atomic_read(&tvd_used_power_num); i++) {
ret = __tvd_power_enable(i, true);
if (ret)
tvd_wrn("power(%s) enable failed.\n", &tvd_power[i][0]);
}
mutex_unlock(&power_lock);
if (__tvd_clk_init(dev))
tvd_wrn("%s: clock init fail!\n", __func__);
ret = __tvd_clk_enable(dev);
__tvd_init(dev);
tvd_init(dev->sel, dev->interface);
/* Set system as NTSC */
dev->width = 720;
dev->height = 480;
dev->fmt = &formats[0];
ret = __tvd_config(dev);
/* enable detect thread */
if (!tvd_task) {
tvd_task = kthread_create(__tvd_detect_thread, (void *)0,
"tvd detect");
if (IS_ERR(tvd_task)) {
s32 err = 0;
err = PTR_ERR(tvd_task);
tvd_task = NULL;
return err;
}
wake_up_process(tvd_task);
}
return ret;
}
static int __tvd_auto_plug_disable(struct tvd_dev *dev)
{
int ret = 0;
int i = 0;
__tvd_clk_disable(dev);
/* close pmu power */
mutex_lock(&power_lock);
for (i = 0; i < atomic_read(&tvd_used_power_num); i++) {
ret = __tvd_power_enable(i, false);
if (ret)
tvd_wrn("power(%s) disable failed.\n", &tvd_power[i][0]);
}
if (atomic_dec_and_test(&gpio_power_enable_count)) {
for (i = 0; i < atomic_read(&tvd_used_gpio_num); i++)
gpio_free(tvd_gpio_config[i].gpio);
}
mutex_unlock(&power_lock);
return ret;
}
#else
static int __tvd_auto_plug_init(struct tvd_dev *dev)
{
return 0;
}
static void __tvd_auto_plug_exit(struct tvd_dev *dev)
{
}
static int __tvd_auto_plug_enable(struct tvd_dev *dev)
{
tvd_wrn("You need to enable CONFIG_EXTCON\n");
return 0;
}
static int __tvd_auto_plug_disable(struct tvd_dev *dev)
{
return 0;
}
#endif
/**
* @name __tvd_multi_ch_probe_init
* @brief multi channel into 1 picture
* @param[IN] pdev:tvd driver platform device
* @param[OUT]
* @return 0 if success
*/
static int __tvd_multi_ch_probe_init(struct platform_device *pdev)
{
int ret = 0, i = 0, value = 0;
struct tvd_dev *dev;
struct video_device *vfd;
struct vb2_queue *q;
struct device_node *sub_np[TVD_MAX];
struct platform_device *sub_pdev[TVD_MAX];
struct device_node *sub_tvd = NULL;
char channel_en_name[20];
tvd_here;
for (i = 0; i < tvd_total_num; i++) {
sub_tvd = of_parse_phandle(pdev->dev.of_node, "tvds", i);
sub_pdev[i] = of_find_device_by_node(sub_tvd);
if (sub_pdev[i] == NULL) {
dev_err(&pdev->dev, "fail to find device for tvd%d!\n",
i);
return -1;
}
sub_np[i] = sub_pdev[i]->dev.of_node;
}
tvd_dbg("config tvd for MultiChannel mode\n");
/*request mem for dev */
dev = kzalloc(sizeof(struct tvd_dev), GFP_KERNEL);
if (!dev) {
tvd_wrn("request dev mem failed!\n");
return -ENOMEM;
}
spin_lock_init(&dev->slock);
/* fetch sysconfig,and judge support */
ret = __tvd_fetch_sysconfig(-1, (char *)"tvd_interface", &value);
dev->interface = value;
ret += __tvd_fetch_sysconfig(-1, (char *)"tvd_format", &value);
dev->format = value;
ret += __tvd_fetch_sysconfig(-1, (char *)"tvd_system", &value);
dev->system = value;
if (ret) {
tvd_wrn(
"get tvd tvd_interface, tvd_format or tvd_system failed!");
ret = -EINVAL;
goto FREEDEV;
}
if (dev->interface != CVBS_INTERFACE) {
tvd_wrn("Multi channel mode is only support cvbs interface\n");
ret = -EINVAL;
goto FREEDEV;
}
dev->row = tvd_row;
dev->column = tvd_column;
dev->mulit_channel_mode = 1;
dev->pdev = pdev;
dev->generating = 0;
dev->opened = 0;
dev->regs_top = tvd_top;
dev->clk_top = tvd_clk_top;
/*tvd_status and dev->sel need to be updated*/
for (i = 0; i < tvd_total_num; i++) {
snprintf(channel_en_name, 20, "tvd_channel%d_en", i);
ret = __tvd_fetch_sysconfig(-1, (char *)channel_en_name,
&dev->channel_index[i]);
if (ret)
dev_err(&pdev->dev,
"get tvd_channel%d failed\n", i);
else {
if (dev->channel_index[i]) {
tvd_status[i].tvd_used = 1;
pdev->id = i;
dev->id = i;
dev->sel = i;
}
}
}
if (check_user_setting_rule(dev)) {
dev_err(&pdev->dev, "violation rule\n");
ret = -1;
goto FREEDEV;
}
/*we will save irq info, base addr info and clk info to*/
/*tvd_irq, tvd_addr and tvd_clk*/
/*irq*/
for (i = 0; i < tvd_total_num; ++i) {
tvd_irq[i] = irq_of_parse_and_map(sub_np[i], 0);
if (tvd_irq[i] <= 0) {
dev_err(&pdev->dev,
"failed to get tvd%d IRQ resource\n", i);
ret = -ENXIO;
goto IOMAP_TVD_ERR;
}
}
dev->irq = tvd_irq[dev->sel]; /*need to be updated*/
tvd_dbg("tvd %d device's irq is %d\n", dev->sel, dev->irq);
/*addr*/
for (i = 0; i < tvd_total_num; i++) {
tvd_addr[i] = of_iomap(sub_np[i], 0);
if (IS_ERR_OR_NULL(tvd_addr[i])) {
dev_err(&pdev->dev, "unable to tvd[%d] registers\n", i);
ret = -EINVAL;
goto IOMAP_TOP_ERR;
}
}
/* get tvd clk ,name fix */
for (i = 0; i < tvd_total_num; i++) {
tvd_clk[i] = of_clk_get(sub_np[i], 0);
if (IS_ERR_OR_NULL(tvd_clk[i])) {
tvd_wrn("get tvd[%d] clk error!\n", i);
goto IOMAP_TVD_ERR;
}
}
dev->clk = tvd_clk[dev->sel]; /*need to be updated*/
/* register v4l2 device */
sprintf(dev->v4l2_dev.name, "multich_v4l2_dev");
ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
if (ret) {
tvd_wrn("Error registering v4l2 device\n");
goto IOMAP_TVD_ERR;
}
ret = __tvd_init_controls(&dev->ctrl_handler);
if (ret) {
tvd_wrn("Error v4l2 ctrls new!!\n");
goto V4L2_REGISTER_ERR;
}
dev->v4l2_dev.ctrl_handler = &dev->ctrl_handler;
dev_set_drvdata(&dev->pdev->dev, dev);
tvd_dbg("%s: v4l2 subdev register.\n", __func__);
#ifdef CONFIG_PM_RUNTIME
pm_runtime_enable(&dev->pdev->dev);
#endif
vfd = video_device_alloc();
if (!vfd) {
tvd_wrn("%s: Error video_device_alloc!\n", __func__);
goto V4L2_REGISTER_ERR;
}
*vfd = tvd_template[4];
vfd->v4l2_dev = &dev->v4l2_dev;
/* /dev/video8 for multich. */
ret = video_register_device(vfd, VFL_TYPE_GRABBER, 8);
if (ret < 0) {
tvd_wrn("Error video_register_device!!\n");
goto VIDEO_DEVICE_ALLOC_ERR;
}
video_set_drvdata(vfd, dev);
list_add_tail(&dev->devlist, &devlist);
dev->vfd = vfd;
tvd_dbg("Multi channel mode device registered as %s\n",
video_device_node_name(vfd));
/* initialize queue */
q = &dev->vb_vidq;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
q->drv_priv = dev;
q->buf_struct_size = sizeof(struct tvd_buffer);
q->ops = &tvd_video_qops;
q->mem_ops = &vb2_dma_contig_memops;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
ret = vb2_queue_init(q);
if (ret) {
tvd_wrn("vb2_queue_init failed\n");
goto VIDEO_DEVICE_REGISTER_ERR;
}
INIT_LIST_HEAD(&dev->vidq.active);
ret = sysfs_create_group(&dev->pdev->dev.kobj,
&tvd_attribute_group[4]);
if (ret) {
tvd_wrn("sysfs_create failed\n");
goto VB2_QUEUE_ERR;
}
mutex_init(&dev->stream_lock);
mutex_init(&dev->opened_lock);
mutex_init(&dev->buf_lock);
tvd_dbg("interface:%d, format:%d, system:%d,dev->sel:%d\n",
dev->interface, dev->format, dev->system, dev->sel);
tvd_dbg("tvd_row:%d, tvd_column:%d\n", dev->row, dev->column);
tvd_dbg("%d %d %d %d\n", dev->channel_index[0], dev->channel_index[1],
dev->channel_index[2], dev->channel_index[3]);
return 0;
VB2_QUEUE_ERR:
vb2_queue_release(q);
VIDEO_DEVICE_REGISTER_ERR:
v4l2_device_unregister(&dev->v4l2_dev);
VIDEO_DEVICE_ALLOC_ERR:
video_device_release(vfd);
V4L2_REGISTER_ERR:
v4l2_device_unregister(&dev->v4l2_dev);
IOMAP_TVD_ERR:
iounmap((char __iomem *)dev->regs_tvd);
IOMAP_TOP_ERR:
iounmap((char __iomem *)dev->regs_top);
FREEDEV:
kfree(dev);
return ret;
}
static int __tvd_probe_init(int sel, struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct tvd_dev *dev;
int ret = 0;
struct video_device *vfd;
struct vb2_queue *q;
tvd_here;
/*request mem for dev*/
dev = kzalloc(sizeof(struct tvd_dev), GFP_KERNEL);
if (!dev) {
tvd_wrn("request dev mem failed!\n");
return -ENOMEM;
}
pdev->id = sel;
if (pdev->id < 0) {
tvd_wrn("TVD failed to get alias id\n");
ret = -EINVAL;
goto freedev;
}
dev->mulit_channel_mode = 0;
dev->id = pdev->id;
dev->sel = sel;
dev->pdev = pdev;
dev->generating = 0;
dev->opened = 0;
spin_lock_init(&dev->slock);
/* fetch sysconfig,and judge support */
ret = __jude_config(dev);
if (ret) {
tvd_wrn("%s:tvd%d is not used by sysconfig.\n", __func__,
dev->id);
ret = -EINVAL;
goto freedev;
}
tvd[dev->id] = dev;
memcpy(dev->name, pdev->name, sizeof(pdev->name) + 1);
dev->irq = irq_of_parse_and_map(np, 0);
if (dev->irq <= 0) {
tvd_wrn("failed to get IRQ resource\n");
ret = -ENXIO;
goto iomap_tvd_err;
}
tvd_dbg("tvd %d device's irq is %d\n", dev->sel, dev->irq);
dev->regs_tvd = of_iomap(pdev->dev.of_node, 0);
if (IS_ERR_OR_NULL(dev->regs_tvd)) {
dev_err(&pdev->dev, "unable to tvd registers\n");
ret = -EINVAL;
goto iomap_top_err;
}
dev->regs_top = tvd_top;
dev->clk_top = tvd_clk_top;
/* get tvd clk ,name fix */
dev->clk = of_clk_get(np, 0);
if (IS_ERR_OR_NULL(dev->clk)) {
tvd_wrn("get tvd clk error!\n");
goto iomap_tvd_err;
}
/* register v4l2 device */
sprintf(dev->v4l2_dev.name, "tvd_v4l2_dev%d", dev->id);
ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
if (ret) {
tvd_wrn("Error registering v4l2 device\n");
goto iomap_tvd_err;
}
ret = __tvd_init_controls(&dev->ctrl_handler);
if (ret) {
tvd_wrn("Error v4l2 ctrls new!!\n");
goto v4l2_register_err;
}
dev->v4l2_dev.ctrl_handler = &dev->ctrl_handler;
dev_set_drvdata(&dev->pdev->dev, dev);
tvd_dbg("%s: v4l2 subdev register.\n", __func__);
#ifdef CONFIG_PM_RUNTIME
pm_runtime_enable(&dev->pdev->dev);
#endif
vfd = video_device_alloc();
if (!vfd) {
tvd_wrn("%s: Error video_device_alloc!\n", __func__);
goto v4l2_register_err;
}
*vfd = tvd_template[dev->sel];
vfd->v4l2_dev = &dev->v4l2_dev;
ret = video_register_device(vfd, VFL_TYPE_GRABBER, dev->id + 4);
if (ret < 0) {
tvd_wrn("Error video_register_device!!\n");
goto video_device_alloc_err;
}
video_set_drvdata(vfd, dev);
list_add_tail(&dev->devlist, &devlist);
dev->vfd = vfd;
tvd_dbg("V4L2 tvd device registered as %s\n",
video_device_node_name(vfd));
/* Initialize videobuf2 queue as per the buffer type */
/* initialize queue */
q = &dev->vb_vidq;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
q->drv_priv = dev;
q->buf_struct_size = sizeof(struct tvd_buffer);
q->ops = &tvd_video_qops;
q->mem_ops = &vb2_dma_contig_memops;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
ret = vb2_queue_init(q);
if (ret) {
tvd_wrn("vb2_queue_init failed\n");
goto video_device_register_err;
}
INIT_LIST_HEAD(&dev->vidq.active);
ret = sysfs_create_group(&dev->pdev->dev.kobj,
&tvd_attribute_group[dev->sel]);
if (ret) {
tvd_wrn("sysfs_create failed\n");
goto vb2_queue_err;
}
mutex_init(&dev->stream_lock);
mutex_init(&dev->opened_lock);
mutex_init(&dev->buf_lock);
if (tvd_hot_plug) {
ret = __tvd_auto_plug_init(dev);
if (!ret)
ret = __tvd_auto_plug_enable(dev);
}
tvd_count++;
return 0;
vb2_queue_err:
vb2_queue_release(q);
video_device_register_err:
v4l2_device_unregister(&dev->v4l2_dev);
video_device_alloc_err:
video_device_release(vfd);
v4l2_register_err:
v4l2_device_unregister(&dev->v4l2_dev);
iomap_tvd_err:
iounmap((char __iomem *)dev->regs_tvd);
iomap_top_err:
iounmap((char __iomem *)dev->regs_top);
freedev:
kfree(dev);
return ret;
}
static int tvd_probe(struct platform_device *pdev)
{
int ret = 0, i = 0;
struct device_node *sub_tvd = NULL;
struct platform_device *sub_pdev = NULL;
struct device_node *np = pdev->dev.of_node;
u32 multi_ch_in_1_mode = 0;
char sub_name[32] = {0};
const char *str;
int value = 0;
tvd_here;
mutex_init(&power_lock);
mutex_init(&fliter_lock);
tvd_top = of_iomap(pdev->dev.of_node, 0);
if (IS_ERR_OR_NULL(tvd_top)) {
dev_err(&pdev->dev, "unable to map tvd top registers\n");
ret = -EINVAL;
goto OUT;
}
tvd_clk_top = of_clk_get(np, 0);
if (IS_ERR_OR_NULL(tvd_clk_top)) {
tvd_wrn("get tvd clk error!\n");
goto IOMAP_TVD_ERR;
}
of_property_read_u32(pdev->dev.of_node, "tvd_hot_plug", &tvd_hot_plug);
for (i = 0; i < TVD_MAX_POWER_NUM; i++) {
snprintf(sub_name, sizeof(sub_name), "tvd_power%d", i);
if (!of_property_read_string(pdev->dev.of_node, sub_name,
&str)) {
atomic_inc(&tvd_used_power_num);
memcpy(&tvd_power[i][0], str, strlen(str) + 1);
/*regu[i] = regulator_get(NULL, &tvd_power[i][0]);*/
regu[i] = NULL;
}
}
for (i = 0; i < TVD_MAX_GPIO_NUM; i++) {
int gpio;
snprintf(sub_name, sizeof(sub_name), "tvd_gpio%d", i);
gpio = of_get_named_gpio_flags(
pdev->dev.of_node, sub_name, 0,
(enum of_gpio_flags *)&tvd_gpio_config[i]);
if (gpio_is_valid(gpio))
atomic_inc(&tvd_used_gpio_num);
}
if (of_property_read_u32(pdev->dev.of_node, "tvd-number",
&tvd_total_num) < 0) {
dev_err(&pdev->dev,
"unable to get tvd-number, force to one!\n");
tvd_total_num = 1;
}
if (tvd_total_num > TVD_MAX) {
dev_err(&pdev->dev,
"total number of tvd module is greater then TVD_MAX!\n");
ret = -1;
goto IOMAP_TVD_ERR;
}
ret = __tvd_fetch_sysconfig(-1, (char *)"tvd_row", &value);
if (ret) {
dev_err(&pdev->dev, "unable to get tvd_row, force to one!\n");
tvd_row = 1;
} else
tvd_row = value;
ret = __tvd_fetch_sysconfig(-1, (char *)"tvd_column", &value);
if (ret) {
dev_err(&pdev->dev,
"unable to get tvd_column, force to one!\n");
tvd_column = 1;
} else
tvd_column = value;
multi_ch_in_1_mode = tvd_row * tvd_column;
if (multi_ch_in_1_mode > tvd_total_num) {
dev_err(
&pdev->dev,
"tvd_row*tvd_column is greater then total tvd number!\n");
ret = -1;
goto IOMAP_TVD_ERR;
}
ret = 0;
for (i = 0; i < tvd_total_num; i++) {
sub_tvd = of_parse_phandle(pdev->dev.of_node, "tvds", i);
sub_pdev = of_find_device_by_node(sub_tvd);
if (!sub_pdev) {
dev_err(&pdev->dev, "fail to find device for tvd%d!\n",
i);
continue;
}
if (sub_pdev) {
ret = __tvd_probe_init(i, sub_pdev);
if (ret != 0) {
/* one tvd may init fail because of the
* sysconfig */
tvd_dbg("tvd%d init is failed\n", i);
ret = 0;
}
}
}
ret = __tvd_multi_ch_probe_init(pdev);
return ret;
IOMAP_TVD_ERR:
iounmap((char __iomem *)tvd_top);
OUT:
return ret;
}
static int tvd_release(void) /*fix*/
{
struct tvd_dev *dev;
struct list_head *list;
tvd_here;
tvd_dbg("%s: \n", __func__);
while (!list_empty(&devlist)) {
list = devlist.next;
list_del(list);
dev = list_entry(list, struct tvd_dev, devlist);
kfree(dev);
}
tvd_dbg("tvd_release ok!\n");
return 0;
}
static int tvd_remove(struct platform_device *pdev)
{
struct tvd_dev *dev = (struct tvd_dev *)dev_get_drvdata(&(pdev)->dev);
int i = 0;
tvd_here;
free_irq(dev->irq, dev);
__tvd_clk_disable(dev);
iounmap(dev->regs_top);
iounmap(dev->regs_tvd);
mutex_destroy(&dev->stream_lock);
mutex_destroy(&dev->opened_lock);
mutex_destroy(&dev->buf_lock);
sysfs_remove_group(&dev->pdev->dev.kobj, &tvd_attribute_group[dev->id]);
#ifdef CONFIG_PM_RUNTIME
pm_runtime_disable(&dev->pdev->dev);
#endif
video_unregister_device(dev->vfd);
v4l2_device_unregister(&dev->v4l2_dev);
v4l2_ctrl_handler_free(&dev->ctrl_handler);
for (i = 0; i < atomic_read(&tvd_used_power_num); i++) {
if (regu[i] != NULL) {
regulator_put(regu[i]);
regu[i] = NULL;
}
}
return 0;
}
#ifdef CONFIG_PM_RUNTIME
static int tvd_runtime_suspend(struct device *d)
{
return 0;
}
static int tvd_runtime_resume(struct device *d)
{
return 0;
}
static int tvd_runtime_idle(struct device *d)
{
if (d) {
pm_runtime_mark_last_busy(d);
pm_request_autosuspend(d);
} else {
tvd_wrn("%s, tvd device is null\n", __func__);
}
return 0;
}
#endif
static int tvd_suspend(struct device *d)
{
#if defined(CONFIG_EXTCON)
if (tvd_task && tvd_hot_plug) {
if (!kthread_stop(tvd_task))
tvd_task = NULL;
}
#endif
return 0;
}
static int tvd_resume(struct device *d)
{
#if defined(CONFIG_EXTCON)
if ((!tvd_task) && (tvd_hot_plug)) {
tvd_task = kthread_create(__tvd_detect_thread, (void *)0,
"tvd detect");
if (IS_ERR(tvd_task)) {
s32 err = 0;
err = PTR_ERR(tvd_task);
tvd_task = NULL;
return err;
}
wake_up_process(tvd_task);
}
#endif
return 0;
}
static void tvd_shutdown(struct platform_device *pdev)
{
}
static const struct dev_pm_ops tvd_runtime_pm_ops = {
#ifdef CONFIG_PM_RUNTIME
.runtime_suspend = tvd_runtime_suspend,
.runtime_resume = tvd_runtime_resume,
.runtime_idle = tvd_runtime_idle,
#endif
.suspend = tvd_suspend,
.resume = tvd_resume,
};
static const struct of_device_id sunxi_tvd_match[] = {
{
.compatible = "allwinner,sunxi-tvd",
},
{},
};
static struct platform_driver tvd_driver = {
.probe = tvd_probe,
.remove = tvd_remove,
.shutdown = tvd_shutdown,
.driver = {
.name = TVD_MODULE_NAME,
.owner = THIS_MODULE,
.of_match_table = sunxi_tvd_match,
.pm = &tvd_runtime_pm_ops,
}
};
static int __init tvd_module_init(void)
{
int ret = 0;
int value = 0;
tvd_here;
ret = __tvd_fetch_sysconfig(-1, (char *)"tvd_sw", &value);
if (ret) {
tvd_wrn("Fetch tvd_sw from sys_config.fex fail!\n");
goto OUT;
}
if (value != 1) {
tvd_dbg("tvd not enable in sys_config.fex!\n");
goto OUT;
}
ret = platform_driver_register(&tvd_driver);
if (ret) {
tvd_wrn("platform driver register failed\n");
return ret;
}
OUT:
tvd_dbg("tvd_init end\n");
return ret;
}
static void __exit tvd_module_exit(void)
{
int i = 0;
tvd_dbg("tvd_exit\n");
if (tvd_hot_plug) {
for (i = 0; i < tvd_count; i++)
__tvd_auto_plug_exit(tvd[i]);
}
tvd_release();
platform_driver_unregister(&tvd_driver);
}
#ifdef CONFIG_ARCH_SUN8IW17P1
subsys_initcall_sync(tvd_module_init);
#else
module_init(tvd_module_init);
#endif
module_exit(tvd_module_exit);
MODULE_AUTHOR("zhengxiaobin");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("tvd driver for sunxi");