/* * Copyright (C) 2015 Allwinnertech, * * 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 #include #include #include #include #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) #include #endif #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_EXTCON) #include #endif /*#include */ #include #include #include #include #include #include #include /*#include */ #ifdef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY #include #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 pixels,448%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");