/* * Copyright (C) 2015 Allwinnertech, z.q * * 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 #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 CVBS_INTERFACE 0 #define YPBPRI_INTERFACE 1 #define YPBPRP_INTERFACE 2 #define NTSC 0 #define PAL 1 #define NONE 2 #define TVD_MAX_POWER_NUM 2 #define TVD_MAX_GPIO_NUM 2 #define TVD_MAX 4 #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) 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]; static struct tvd_status tvd_status[4]; static struct tvd_dev *tvd[4]; static unsigned int tvd_hot_plug; /* 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) { return sprintf(buf, "%d\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; err = strict_strtoul(buf, 10, &val); if (err) { pr_debug("Invalid size\n"); return err; } if(val < 0 || val > 1) { pr_debug("Invalid value, 0~1 is expected!\n"); } else { tvd_dbg_en = val; pr_debug("tvd_dbg_en = %ld\n", val); } return count; } static ssize_t tvd_dbg_lv_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\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; err = strict_strtoul(buf, 10, &val); if (err) { pr_debug("Invalid size\n"); return err; } if(val < 0 || val > 4) { pr_debug("Invalid value, 0~3 is expected!\n"); } else { tvd_dbg_sel = val; pr_debug("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; buf_addr = dma_alloc_coherent(dev, TVD_DBG_DUMP_LEN, (dma_addr_t *)&buf_dma_addr, GFP_KERNEL); if (!buf_addr) { pr_warn("%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)) { pr_warn("%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)) { pr_warn("%s, open %s err\n", __func__, tvd_dump_file_name); goto exit; } pr_warn("%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)) pr_warn("%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) { 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); pr_info("%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_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 }, }; 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 int inline tvd_is_generating(struct tvd_dev *dev) { int ret; ret = test_bit(0, &dev->generating); return ret; } static void inline tvd_start_generating(struct tvd_dev *dev) { set_bit(0, &dev->generating); return; } static void inline tvd_stop_generating(struct tvd_dev *dev) { clear_bit(0, &dev->generating); return; } static int tvd_is_opened(struct tvd_dev *dev) { int ret; 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) { mutex_lock(&dev->opened_lock); set_bit(0, &dev->opened); mutex_unlock(&dev->opened_lock); } static void tvd_stop_opened(struct tvd_dev *dev) { 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; unsigned long p = 297000000; pr_debug("%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) { /* cvbs interface */ if (PAL == dev->system) div = 10; else div = 11; } else if (dev->interface == YPBPRI_INTERFACE) { /* ypbprI interface */ div = 11; } else if (dev->interface == YPBPRP_INTERFACE) { /* ypbprP interface */ ret = clk_set_rate(dev->parent, 594000000); p = 594000000; div = 11; } else { pr_err("%s: interface is err!\n", __func__); return -EINVAL; } pr_debug("div = %d\n", div); p /= div; ret = clk_set_rate(dev->clk, p); if (ret) { ret = -EINVAL; goto out; } pr_debug("%s: parent = %lu, clk = %lu\n", __func__, 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; ret = clk_prepare_enable(dev->clk_top); if (ret) { pr_err("%s: tvd top clk enable err!", __func__); clk_disable(dev->clk_top); return ret; } ret = clk_prepare_enable(dev->clk); if (ret) { pr_err("%s: tvd clk enable err!", __func__); clk_disable(dev->clk); } return ret; } static int __tvd_clk_disable(struct tvd_dev *dev) { int ret = 0; clk_disable(dev->clk); clk_disable(dev->clk_top); return ret; } static int __tvd_init(struct tvd_dev *dev) { tvd_top_set_reg_base((unsigned long)dev->regs_top); tvd_set_reg_base(dev->sel, (unsigned long)dev->regs_tvd); return 0; } static int __tvd_config(struct tvd_dev *dev) { tvd_init(dev->sel, dev->interface); tvd_config(dev->sel, 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) 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; 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)) { pr_err("%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; 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; if(vb_buf == NULL || vb_buf->planes[0].mem_priv == NULL) { pr_err("%s: vb_buf->priv is NULL!\n", __func__); return; } 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; } /* set y_addr,c_addr */ pr_debug("%s: format:%d, addr_org = 0x%x, addr_org + c_offset = 0x%x\n", __func__, dev->format, addr_org, addr_org + c_offset); tvd_set_wb_addr(dev->sel, addr_org, addr_org + c_offset); } /* * the interrupt routine */ static irqreturn_t tvd_isr(int irq, void *priv) { struct tvd_buffer *buf; unsigned long flags; struct list_head *entry_tmp; u32 irq_status = 0; struct tvd_dev *dev = (struct tvd_dev *)priv; struct tvd_dmaqueue *dma_q = &dev->vidq; u32 value = (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); 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 & value) != 0) tvd_dma_irq_status_clear_err_flag(dev->sel, value); spin_lock_irqsave(&dev->slock, flags); 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)) { pr_debug("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)) { pr_debug("%s: Nobody is waiting on this video buffer,buf = 0x%p\n",__func__, buf); } entry_tmp = &buf->list; if ((entry_tmp == NULL) || (entry_tmp->prev == NULL) || (entry_tmp->next == NULL) \ || (entry_tmp->prev == LIST_POISON2) || (entry_tmp->next == LIST_POISON1) \ || (entry_tmp->prev->next == NULL) || (entry_tmp->next->prev == NULL)) { if (entry_tmp == NULL) pr_err("%s: buf NULL pointer error \n", __func__); pr_err("%s: buf list error \n", __func__); goto unlock; } list_del(&buf->list); dev->ms += jiffies_to_msecs(jiffies - dev->jiffies); dev->jiffies = jiffies; vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE); if (list_empty(&dma_q->active)) { pr_debug("%s: No more free frame\n", __func__); goto unlock; } /* hardware need one frame */ if ((&dma_q->active) == dma_q->active.next->next) { pr_debug("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, const struct v4l2_format *fmt, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[]) { struct tvd_dev *dev = vb2_get_drv_priv(vq); unsigned int size; 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; pr_err("buffer conunt invalid, min is 3!\n"); } else if (*nbuffers > 10) { *nbuffers = 10; pr_err("buffer conunt invalid, max 10!\n"); } dev->frame_size = size; sizes[0] = size; *nplanes = 1; alloc_ctxs[0] = dev->alloc_ctx; pr_debug("%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; pr_debug("%s: buffer_prepare\n", __func__); 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) { pr_err("%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->v4l2_planes[0].m.mem_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; pr_debug("%s: \n", __func__); 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); pr_debug("%s:\n", __func__); tvd_start_generating(dev); return 0; } /* abort streaming and wait for last buffer */ static int 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; pr_debug("%s:\n", __func__); tvd_stop_generating(dev); spin_lock_irqsave(&dev->slock, flags); /* Release all active buffers */ 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); pr_debug("[%p/%d] done\n", buf, buf->vb.v4l2_buf.index); } spin_unlock_irqrestore(&dev->slock, flags); return 0; } 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); 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; return 0; } static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { struct tvd_fmt *fmt; 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; } static void __get_status(struct tvd_dev *dev, unsigned int *locked, unsigned int *system) { int i = 0; 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); tvd_get_status(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; f->fmt.pix.width = dev->width; f->fmt.pix.height = dev->height; __get_status(dev, &locked, &system); f->fmt.pix.priv = dev->interface; if (!locked) { pr_debug("%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 { pr_err("system is not sure.\n"); } } pr_debug("system = %d, w = %d, h = %d\n", system, f->fmt.pix.width, f->fmt.pix.height); return 0; } static struct tvd_fmt *__get_format(struct v4l2_format *f) { struct tvd_fmt *fmt; unsigned int i; for (i = 0; i < ARRAY_SIZE(formats); i++) { fmt = &formats[i]; /* user defined struct raw_data: 5->pixelformat */ if (fmt->fourcc == f->fmt.pix.pixelformat) { pr_debug("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) { return 0; } static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tvd_dev *dev = video_drvdata(file); int ret = 0; int used = 0; int value = 0, mode = 0; if (tvd_is_generating(dev)) { pr_err("%s device busy\n", __func__); return -EBUSY; } dev->fmt = __get_format(f); if (!dev->fmt) { pr_err("Fourcc format (0x%08x) invalid.\n", f->fmt.pix.pixelformat); return -EINVAL; } /* tvd ypbpr now only support 720*480 & 720*576 */ 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; } pr_debug("interface=%d\n",dev->interface); pr_debug("system=%d\n",dev->system); pr_debug("format=%d\n",dev->format); pr_debug("width=%d\n",dev->width); pr_debug("height=%d\n",dev->height); __tvd_config(dev); /* agc function */ ret = __tvd_fetch_sysconfig(dev->sel, (char *)"agc_auto_enable", &mode); if (ret) goto cagc; if (mode == 0) { /* manual mode */ ret = __tvd_fetch_sysconfig(dev->sel, (char *)"agc_manual_value", &value); if (ret) goto cagc; tvd_agc_manual_config(dev->sel, (u32)value); } else { /* auto mode */ tvd_agc_auto_config(dev->sel); } cagc: ret = __tvd_fetch_sysconfig(dev->sel, (char *)"cagc_enable", &value); if (ret) goto _3d_fliter; tvd_cagc_config(dev->sel, (u32)value); _3d_fliter: /* 3d fliter */ __tvd_fetch_sysconfig(dev->sel, (char *)"fliter_used", &used); 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); goto out; } fliter_count++; } tvd_3d_mode(dev->sel, 1, (u32)dev->fliter.phy_address); mutex_unlock(&fliter_lock); } out: return 0; } static int vidioc_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p) { struct tvd_dev *dev = video_drvdata(file); pr_debug("%s:\n", __func__); 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); pr_debug("%s:\n", __func__); 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; mutex_lock(&dev->stream_lock); if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) { ret = -EINVAL; goto streamon_unlock; } if (tvd_is_generating(dev)) { pr_err("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); tvd_capture_on(dev->sel); tvd_start_generating(dev); 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); pr_debug("%s:\n", __func__); if (!tvd_is_generating(dev)) { pr_err("%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); 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) { pr_err("%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) { if (inp->index > NUM_INPUTS-1) { pr_err("%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); pr_debug("%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); pr_debug("%s: input_num = %d\n", __func__, i); /* only one device */ if (i > 0) { pr_err("%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); pr_debug("%s\n", __func__); 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); 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, }, }; /* 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)) { pr_err("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); if(tvd_is_generating(dev)) { return vb2_read(&dev->vb_vidq, data, count, ppos, file->f_flags & O_NONBLOCK); } else { pr_err("%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 { pr_err("%s: tvd is not generating!\n", __func__); return -EINVAL; } } static int __tvd_power_enable(struct regulator *regu, bool is_true) { int ret = 0; if (IS_ERR_OR_NULL(regu)) { pr_err("regulator is err.\n"); return -EBUSY; } if (is_true) ret = regulator_enable(regu); else ret = regulator_disable(regu); 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) { pr_err("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; pr_debug("%s:\n", __func__); if (tvd_is_opened(dev)) { pr_err("%s: device open busy\n", __func__); return -EBUSY; } 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(regu[i], true); if (ret) pr_err("power(%s) enable failed.\n", &tvd_power[i][0]); } mutex_unlock(&power_lock); if (__tvd_clk_init(dev)) { pr_err("%s: clock init fail!\n", __func__); } ret = __tvd_clk_enable(dev); __tvd_init(dev); tvd_init(dev->sel, dev->interface); dev->input = 0; /* default input null */ tvd_start_opened(dev); return ret; } static int tvd_close(struct file *file) { struct tvd_dev *dev = video_drvdata(file); int ret = 0; int i = 0; pr_debug("tvd_close\n"); tvd_stop_generating(dev); __tvd_clk_disable(dev); vb2_queue_release(&dev->vb_vidq); tvd_stop_opened(dev); mutex_lock(&fliter_lock); if (fliter_count > 0 && dev->fliter.used) { __tvd_3d_comp_mem_free(dev); fliter_count--; } mutex_unlock(&fliter_lock); /* close pmu power */ mutex_lock(&power_lock); for (i = 0; i < atomic_read(&tvd_used_power_num); i++) { ret = __tvd_power_enable(regu[i], false); if (ret) pr_err("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); pr_debug("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; pr_debug("%s: mmap called, vma=0x%08lx\n", __func__, (unsigned long)vma); ret = vb2_mmap(&dev->vb_vidq, vma); pr_debug("%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; 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; struct v4l2_control c; pr_debug("%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: pr_debug("%s: V4L2_CID_BRIGHTNESS sel=%d, val=%d,\n", __func__, dev->sel, ctrl->val); tvd_set_luma(dev->sel, ctrl->val); break; case V4L2_CID_CONTRAST: pr_debug("%s: V4L2_CID_CONTRAST sel=%d, val=%d,\n", __func__, dev->sel, ctrl->val); tvd_set_contrast(dev->sel, ctrl->val); break; case V4L2_CID_SATURATION: pr_debug("%s: V4L2_CID_SATURATION sel=%d, val=%d,\n", __func__, dev->sel, ctrl->val); 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) { pr_err("%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); pr_debug("%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; pr_debug("%s:\n", __func__); if (tvd_is_opened(dev)) { pr_err("%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(regu[i], true); if (ret) pr_err("power(%s) enable failed.\n", &tvd_power[i][0]); } mutex_unlock(&power_lock); INIT_LIST_HEAD(&active->active); INIT_LIST_HEAD(&done->active); if (__tvd_clk_init(dev)) pr_err("%s: clock init fail!\n", __func__); ret = __tvd_clk_enable(dev); __tvd_init(dev); 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; pr_debug("%s:\n", __func__); tvd_stop_generating(dev); __tvd_clk_disable(dev); tvd_stop_opened(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(regu[i], false); if (ret) pr_err("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; pr_debug("%s:\n", __func__); if (tvd_is_generating(dev)) { pr_err("%s device busy\n", __func__); return -EBUSY; } dev->fmt = __get_format(f); if (!dev->fmt) { pr_err("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); pr_debug("interface=%d\n", dev->interface); pr_debug("system=%d\n", dev->system); pr_debug("format=%d\n", dev->format); pr_debug("width=%d\n", dev->width); pr_debug("height=%d\n", dev->height); 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) { pr_debug("%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 { pr_err("system is not sure.\n"); } } pr_debug("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; pr_debug("%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)) { pr_err("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 { pr_err("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); pr_debug("%s:\n", __func__); if (!tvd_is_generating(dev)) { pr_err("%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) { pr_err("%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) { pr_debug("%s\n", __func__); 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; int need_callback = 0; if (tvd_is_generating(dev) == 0) { tvd_irq_status_clear(dev->sel, TVD_IRQ_FRAME_END); return IRQ_HANDLED; } spin_lock_irqsave(&dev->slock, flags); 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)) { pr_debug("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); need_callback = 1; if (list_empty(&dma_q->active)) { pr_debug("%s: No more free frame\n", __func__); goto unlock; } if ((&dma_q->active) == dma_q->active.next->next) { pr_debug("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: spin_unlock(&dev->slock); if (need_callback && tvd_buffer_done) tvd_buffer_done(dev->id); 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, .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, }, }; static int __tvd_init_controls(struct v4l2_ctrl_handler *hdl) { unsigned int ret = 0; 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) { pr_err("%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; len = sprintf(compat, "allwinner,sunxi-tvd%d", sel); if (len > 32) pr_err("size of mian_name is out of range\n"); node = of_find_compatible_node(NULL, NULL, compat); if (!node) { pr_err("of_find_compatible_node %s fail\n", compat); return -EINVAL; } if (of_property_read_u32_array(node, sub_name, value, 1)) { pr_err("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; if (id > 3 || id < 0) { pr_err("%s: id is wrong!\n", __func__); return -ENODEV; } /* first set sel as id */ dev->sel = id; pr_debug("%s: sel = %d.\n", __func__, dev->sel); ret = __tvd_fetch_sysconfig(id, (char *)"tvd_used", &tvd_status[id].tvd_used); if (ret) { pr_err("%s: fetch tvd_used%d err!", __func__, id); return -EINVAL; } if (!tvd_status[id].tvd_used) { pr_debug("%s: tvd_status[%d].used is null.\n", __func__, id); return -ENODEV; } ret = __tvd_fetch_sysconfig(id, (char *)"tvd_if", &tvd_status[id].tvd_if); if (ret) { pr_err("%s: fetch tvd_if%d err!", __func__, id); return -EINVAL; } dev->interface = tvd_status[id].tvd_if; if (id > 0) { if (tvd_status[0].tvd_used && tvd_status[0].tvd_if > 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_SWITCH) || defined(CONFIG_ANDROID_SWITCH) static char switch_lock_name[20]; static char switch_system_name[20]; static struct switch_dev switch_lock[TVD_MAX]; static struct switch_dev switch_system[TVD_MAX]; static struct task_struct *tvd_task; static int __tvd_auto_plug_init(struct tvd_dev *dev) { int ret = 0; snprintf(switch_lock_name, sizeof(switch_lock_name), "tvd_lock%d", dev->sel); switch_lock[dev->sel].name = switch_lock_name; ret = switch_dev_register(&switch_lock[dev->sel]); snprintf(switch_system_name, sizeof(switch_system_name), "tvd_system%d", dev->sel); switch_system[dev->sel].name = switch_system_name; ret = switch_dev_register(&switch_system[dev->sel]); return ret; } static void __tvd_auto_plug_exit(struct tvd_dev *dev) { switch_dev_unregister(&switch_lock[dev->sel]); switch_dev_unregister(&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; pr_warn("%s, plug in\n", __func__); } /* plug out */ if ((hpd[0] == true) && (hpd[1] == true) && (hpd[2] == true) && (hpd_cur == false)) { ret = true; pr_warn("%s, plug in\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))) { pr_debug("reverse hpd=%d, i = %d\n", locked, i); switch_set_state(&switch_lock[i], locked); } if (systems[i] != system) { pr_debug("system = %d, i = %d\n", system, i); systems[i] = system; switch_set_state(&switch_system[i], 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(regu[i], true); if (ret) pr_err("power(%s) enable failed.\n", &tvd_power[i][0]); } mutex_unlock(&power_lock); if (__tvd_clk_init(dev)) pr_err("%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(regu[i], false); if (ret) pr_err("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) { pr_warn("there is no switch class for tvd\n"); return 0; } static int __tvd_auto_plug_disable(struct tvd_dev *dev) { return 0; } #endif static void __iomem *tvd_top; struct clk* tvd_clk_top; 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; pr_debug("%s: \n", __func__); /*request mem for dev*/ dev = kzalloc(sizeof(struct tvd_dev), GFP_KERNEL); if (!dev) { pr_err("request dev mem failed!\n"); return -ENOMEM; } pdev->id = sel; if (pdev->id < 0) { pr_err("TVD failed to get alias id\n"); ret = -EINVAL; goto freedev; } 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) { pr_err("%s:tvd%d is not used by sysconfig.\n", __func__, dev->id); ret = -EINVAL; goto freedev; } tvd[dev->id] = dev; tvd_count++; dev->irq = irq_of_parse_and_map(np, 0); if (dev->irq <= 0) { pr_err("failed to get IRQ resource\n"); ret = -ENXIO; goto iomap_tvd_err; } 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; /* register irq */ ret = request_irq(dev->irq, tvd_isr, IRQF_DISABLED, pdev->name, dev); /* get tvd clk ,name fix */ dev->clk = of_clk_get(np, 0);//fix if (IS_ERR_OR_NULL(dev->clk)) { pr_err("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) { pr_err("Error registering v4l2 device\n"); goto iomap_tvd_err; } ret = __tvd_init_controls(&dev->ctrl_handler); if (ret) { pr_err("Error v4l2 ctrls new!!\n"); goto v4l2_register_err; } dev->v4l2_dev.ctrl_handler = &dev->ctrl_handler; dev_set_drvdata(&dev->pdev->dev, dev); pr_info("%s: v4l2 subdev register.\n", __func__); #ifdef CONFIG_PM_RUNTIME pm_runtime_enable(&dev->pdev->dev); #endif vfd = video_device_alloc(); if (!vfd) { pr_err("%s: Error video_device_alloc!\n", __func__); goto v4l2_register_err; } *vfd = tvd_template[dev->id]; vfd->v4l2_dev = &dev->v4l2_dev; ret = video_register_device(vfd, VFL_TYPE_GRABBER, dev->id + 4); if (ret < 0) { pr_err("Error video_register_device!!\n"); goto video_device_alloc_err; } video_set_drvdata(vfd, dev); list_add_tail(&dev->devlist, &devlist); //use this for what? dev->vfd = vfd; pr_info("V4L2 tvd device registered as %s\n",video_device_node_name(vfd)); /* Initialize videobuf2 queue as per the buffer type */ dev->alloc_ctx = vb2_dma_contig_init_ctx(&dev->pdev->dev); if (IS_ERR(dev->alloc_ctx)) { pr_err("Failed to get the context\n"); goto video_device_register_err; } /* 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_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; ret = vb2_queue_init(q); if (ret) { pr_err("vb2_queue_init failed\n"); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); goto video_device_register_err; } INIT_LIST_HEAD(&dev->vidq.active); ret = sysfs_create_group(&dev->pdev->dev.kobj, &tvd_attribute_group[dev->id]); if (ret) { pr_err("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) { __tvd_auto_plug_init(dev); __tvd_auto_plug_enable(dev); } 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; unsigned int tvd_num = 0; struct device_node *sub_tvd = NULL; struct platform_device *sub_pdev = NULL; struct device_node *np = pdev->dev.of_node; char sub_name[32] = {0}; const char *str; 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)) { pr_err("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]); } } 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_num) < 0) { dev_err(&pdev->dev, "unable to get tvd-number, force to one!\n"); tvd_num = 1; } for (i = 0; i < tvd_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 */ pr_debug("tvd%d init is failed\n", i); ret = 0; } } } 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; pr_debug("%s: \n", __func__); while (!list_empty(&devlist)) { list = devlist.next; list_del(list); dev = list_entry(list, struct tvd_dev, devlist); kfree(dev); } pr_debug("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; 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); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); for (i = 0; i < atomic_read(&tvd_used_power_num); i++) regulator_put(regu[i]); 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 { pr_err("%s, tvd device is null\n", __func__); } return 0; } #endif static int tvd_suspend(struct device *d) { if (tvd_task) { if (!kthread_stop(tvd_task)) tvd_task = NULL; } return 0; } static int tvd_resume(struct device *d) { 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 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; pr_info("Welcome to tv decoder driver\n"); /*add sysconfig judge,if no use,return.*/ ret = platform_driver_register(&tvd_driver); if (ret) { pr_err("platform driver register failed\n"); return ret; } pr_info("tvd_init end\n"); return 0; } static void __exit tvd_module_exit(void) { int i = 0; pr_info("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); } subsys_initcall_sync(tvd_module_init); module_exit(tvd_module_exit); MODULE_AUTHOR("zengqi"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("tvd driver for sunxi");