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

3724 lines
86 KiB
C
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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");