summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/media/video/tegra/Kconfig9
-rw-r--r--drivers/media/video/tegra/Makefile1
-rw-r--r--drivers/media/video/tegra/tegra_dtv.c1058
-rw-r--r--include/media/tegra_dtv.h104
4 files changed, 1172 insertions, 0 deletions
diff --git a/drivers/media/video/tegra/Kconfig b/drivers/media/video/tegra/Kconfig
index b0f9fa3e6850..404d771a717e 100644
--- a/drivers/media/video/tegra/Kconfig
+++ b/drivers/media/video/tegra/Kconfig
@@ -11,6 +11,15 @@ config TEGRA_CAMERA
If unsure, say Y
+config TEGRA_DTV
+ bool "Enable support for tegra dtv interface"
+ depends on ARCH_TEGRA
+ default y
+ help
+ Enables support for the Tegra dtv interface
+
+ If unsure, say Y
+
config VIDEO_OV5650
tristate "OV5650 camera sensor support"
depends on I2C && ARCH_TEGRA
diff --git a/drivers/media/video/tegra/Makefile b/drivers/media/video/tegra/Makefile
index a3990435570d..5d404e44fd20 100644
--- a/drivers/media/video/tegra/Makefile
+++ b/drivers/media/video/tegra/Makefile
@@ -5,6 +5,7 @@ GCOV_PROFILE := y
obj-y += avp/
obj-$(CONFIG_TEGRA_MEDIASERVER) += mediaserver/
obj-$(CONFIG_TEGRA_NVAVP) += nvavp/
+obj-$(CONFIG_TEGRA_DTV) += tegra_dtv.o
obj-$(CONFIG_TEGRA_CAMERA) += tegra_camera.o
obj-$(CONFIG_VIDEO_AR0832) += ar0832_main.o
obj-$(CONFIG_VIDEO_OV5650) += ov5650.o
diff --git a/drivers/media/video/tegra/tegra_dtv.c b/drivers/media/video/tegra/tegra_dtv.c
new file mode 100644
index 000000000000..9001e568b7c4
--- /dev/null
+++ b/drivers/media/video/tegra/tegra_dtv.c
@@ -0,0 +1,1058 @@
+/*
+ * tegra_dtv.c - Tegra DTV interface driver
+ *
+ * Author: Adam Jiang <chaoj@nvidia.com>
+ * Copyright (c) 2011, NVIDIA Corporation.
+ * Copyright (c) 2012, NVIDIA Corporation.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/fs.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/wakelock.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/delay.h>
+
+#include <media/tegra_dtv.h>
+
+#include <linux/uaccess.h>
+#include <mach/iomap.h>
+#include <mach/dma.h>
+
+/* offsets from TEGRA_DTV_BASE */
+#define DTV_SPI_CONTROL 0x40
+#define DTV_MODE 0x44
+#define DTV_CTRL 0x48
+#define DTV_PACKET_COUNT 0x4c
+#define DTV_ERROR_COUNT 0x50
+#define DTV_INTERRUPT_STATUS 0x54
+#define DTV_STATUS 0x58
+#define DTV_RX_FIFO 0x5c
+
+/* DTV_SPI_CONTROL */
+#define DTV_SPI_CONTROL_ENABLE_DTV 1
+
+/* DTV_MODE_0 */
+#define DTV_MODE_BYTE_SWIZZLE_SHIFT 6
+#define DTV_MODE_BYTE_SWIZZLE (1 << DTV_MODE_BYTE_SWIZZLE_SHIFT)
+#define DTV_MODE_BIT_SWIZZLE_SHIFT 5
+#define DTV_MODE_BIT_SWIZZLE (1 << DTV_MODE_BIT_SWIZZLE_SHIFT)
+#define DTV_MODE_CLK_EDGE_SHIFT 4
+#define DTV_MODE_CLK_EDGE_MASK 1
+#define DTV_MODE_CLK_EDGE_NEG (1 << DTV_MODE_CLK_EDGE_SHIFT)
+#define DTV_MODE_PRTL_SEL_SHIFT 2
+#define DTV_MODE_PRTL_SEL_MASK (0x3 << DTV_MODE_PRTL_SEL_SHIFT)
+#define DTV_MODE_CLK_MODE_SHIFT 1
+#define DTV_MODE_CLK_MODE_MASK (0x1 << DTV_MODE_CLK_MODE_SHIFT)
+#define DTV_MODE_PRTL_ENABLE 1
+
+/* DTV_CONTROL_0 */
+#define DTV_CTRL_FEC_SIZE_SHIFT 24
+#define DTV_CTRL_FEC_SIZE_MASK (0x7F << DTV_CTRL_FEC_SIZE_SHIFT)
+#define DTV_CTRL_BODY_SIZE_SHIFT 16
+#define DTV_CTRL_BODY_SIZE_MASK (0xFF << DTV_CTRL_BODY_SIZE_SHIFT)
+#define DTV_CTRL_FIFO_ATTN_LEVEL_SHIFT 8
+#define DTV_CTRL_FIFO_ATTN_LEVEL_MASK (0x1F << DTV_CTRL_FIFO_ATTN_LEVEL_SHIFT)
+#define DTV_CTRL_FIFO_ATTN_ONE_WORD (0 << DTV_CTRL_FIFO_ATTN_LEVEL_SHIFT)
+#define DTV_CTRL_FIFO_ATTN_TWO_WORD (1 << DTV_CTRL_FIFO_ATTN_LEVEL_SHIFT)
+#define DTV_CTRL_FIFO_ATTN_THREE_WORD (2 << DTV_CTRL_FIFO_ATTN_LEVEL_SHIFT)
+#define DTV_CTRL_FIFO_ATTN_FOUR_WORD (3 << DTV_CTRL_FIFO_ATTN_LEVEL_SHIFT)
+#define DTV_CTRL_BODY_VALID_SEL_SHIFT 6
+#define DTV_CTRL_BODY_VALID_SEL_MASK (1 << DTV_CTRL_BODY_VALID_SEL_SHIFT)
+#define DTV_CTRL_START_SEL_SHIFT 4
+#define DTV_CTRL_START_SEL_MASK (1 << DTV_CTRL_START_SEL_SHIFT)
+#define DTV_CTRL_ERROR_POLARITY_SHIFT 2
+#define DTV_CTRL_ERROR_POLARITY_MASK (1 << DTV_CTRL_ERROR_POLARITY_SHIFT)
+#define DTV_CTRL_PSYNC_POLARITY_SHIFT 1
+#define DTV_CTRL_PSYNC_POLARITY_MASK (1 << DTV_CTRL_PSYNC_POLARITY_SHIFT)
+#define DTV_CTRL_VALID_POLARITY_SHIFT 0
+#define DTV_CTRL_VALID_POLARITY_MASK (1 << DTV_CTRL_VALID_POLARITY_SHIFT)
+
+/* DTV_INTERRUPT_STATUS_0 */
+#define DTV_INTERRUPT_PACKET_UNDERRUN_ERR 8
+#define DTV_INTERRUPT_BODY_OVERRUN_ERR 4
+#define DTV_INTERRUPT_BODY_UNDERRUN_ERR 2
+#define DTV_INTERRUPT_UPSTREAM_ERR 1
+
+/* DTV_STATUS_0 */
+#define DTV_STATUS_RXF_UNDERRUN 4
+#define DTV_STATUS_RXF_EMPTY 2
+#define DTV_STATUS_RXF_FULL 1
+
+#define TEGRA_DTV_NAME "tegra_dtv"
+
+/* default sw config */
+#define DTV_BUF_SIZE_ORDER PAGE_SHIFT
+#define DTV_MAX_NUM_BUFS 4
+
+#define DTV_FIFO_ATN_LVL_LOW_GEAR 0
+#define DTV_FIFO_ATN_LVL_SECOND_GEAR 1
+#define DTV_FIFO_ATN_LVL_THIRD_GEAR 2
+#define DTV_FIFO_ATN_LVL_TOP_GEAR 3
+
+struct dtv_stream {
+ struct mutex mtx;
+
+ bool xferring; /* is DMA in progress */
+ unsigned num_bufs;
+ void *buffer[DTV_MAX_NUM_BUFS];
+ dma_addr_t buf_phy[DTV_MAX_NUM_BUFS];
+ struct completion comp[DTV_MAX_NUM_BUFS];
+ struct tegra_dma_req dma_req[DTV_MAX_NUM_BUFS];
+ int last_queued;
+
+ int fifo_atn_level;
+
+ struct tegra_dma_channel *dma_chan;
+ bool stopped;
+ struct completion stop_completion;
+ spinlock_t dma_req_lock;
+ size_t buf_size;
+
+ struct work_struct work;
+ struct wake_lock wake_lock;
+ char wake_lock_name[16];
+};
+
+struct tegra_dtv_context {
+ struct tegra_dtv_hw_config config;
+ struct clk *clk;
+ int clk_enabled;
+
+ phys_addr_t phys;
+ void * __iomem base;
+ unsigned long dma_req_sel;
+
+ struct dtv_stream stream;
+ /* debugfs */
+ struct dentry *d;
+ /* for refer back */
+ struct platform_device *pdev;
+};
+
+static inline struct tegra_dtv_context *to_ctx(struct dtv_stream *s)
+{
+ return container_of(s, struct tegra_dtv_context, stream);
+}
+
+/* access control */
+static atomic_t tegra_dtv_instance_nr = ATOMIC_INIT(1);
+
+static inline u32 tegra_dtv_readl(struct tegra_dtv_context *dtv,
+ unsigned long reg)
+{
+ BUG_ON(!dtv->clk_enabled);
+ return readl(dtv->base + reg);
+}
+
+static inline void tegra_dtv_writel(struct tegra_dtv_context *dtv,
+ u32 val, unsigned long reg)
+{
+ BUG_ON(!dtv->clk_enabled);
+ writel(val, dtv->base + reg);
+}
+
+/* process */
+static inline void prevent_suspend(struct dtv_stream *s)
+{
+ pr_debug("%s called.\n", __func__);
+ cancel_work_sync(&s->work);
+ wake_lock(&s->wake_lock);
+}
+
+static void tegra_dtv_worker(struct work_struct *w)
+{
+ struct dtv_stream *s = container_of(w, struct dtv_stream, work);
+ pr_debug("%s called.\n", __func__);
+ wake_unlock(&s->wake_lock);
+}
+
+static inline void wakeup_suspend(struct dtv_stream *s)
+{
+ schedule_work(&s->work);
+}
+
+static inline bool wait_till_stopped(struct dtv_stream *s)
+{
+ int ret;
+
+ pr_debug("%s: wait for completion\n", __func__);
+
+ ret = wait_for_completion_timeout(
+ &s->stop_completion, HZ);
+ if (!ret)
+ pr_err("%s: wait timed out", __func__);
+ if (ret < 0)
+ pr_err("%s: wait error %d\n", __func__, ret);
+
+ wakeup_suspend(s);
+
+ pr_debug("%s: done: %d\n", __func__, ret);
+
+ return true;
+}
+
+/* dma transfer */
+static inline bool are_xfers_pending(struct dtv_stream *s)
+{
+ int i;
+
+ pr_debug("%s called\n", __func__);
+
+ for (i = 0; i < s->num_bufs; i++)
+ if (!completion_done(&s->comp[i]))
+ return true;
+ return false;
+}
+
+static void tegra_dtv_rx_dma_complete(struct tegra_dma_req *req)
+{
+ unsigned long flags;
+ unsigned req_num;
+ struct dtv_stream *s = req->dev;
+
+ spin_lock_irqsave(&s->dma_req_lock, flags);
+
+ pr_debug("%s called.\n", __func__);
+
+ req_num = req - s->dma_req;
+ pr_debug("%s: complete buffer %d size %d bytes\n",
+ __func__, req_num, req->bytes_transferred);
+ BUG_ON(req_num >= s->num_bufs);
+
+ complete(&s->comp[req_num]);
+
+ if (!are_xfers_pending(s))
+ pr_debug("%s: overflow.\n", __func__);
+
+ spin_unlock_irqrestore(&s->dma_req_lock, flags);
+}
+
+/* hw */
+static inline void _dtv_enable_protocol(struct tegra_dtv_context *dtv_ctx)
+{
+ u32 val;
+
+ val = tegra_dtv_readl(dtv_ctx, DTV_MODE);
+ val &= ~0x01;
+ val |= DTV_MODE_PRTL_ENABLE;
+ tegra_dtv_writel(dtv_ctx, val, DTV_MODE);
+}
+
+static inline void _dtv_disable_protocol(struct tegra_dtv_context *dtv_ctx)
+{
+ u32 val;
+
+ val = tegra_dtv_readl(dtv_ctx, DTV_MODE);
+ val &= ~DTV_MODE_PRTL_ENABLE;
+ tegra_dtv_writel(dtv_ctx, val, DTV_MODE);
+}
+
+static inline u32 _dtv_get_status(struct tegra_dtv_context *dtv_ctx)
+{
+ return tegra_dtv_readl(dtv_ctx, DTV_STATUS);
+}
+
+static inline void _dtv_set_attn_level(struct tegra_dtv_context *dtv_ctx)
+{
+ /* TODO: consider have this set to corresponding transfer request */
+ u32 val;
+
+ val = tegra_dtv_readl(dtv_ctx, DTV_CTRL);
+ val &= ~DTV_CTRL_FIFO_ATTN_LEVEL_MASK;
+ val |= DTV_CTRL_FIFO_ATTN_FOUR_WORD;
+ tegra_dtv_writel(dtv_ctx, val, DTV_CTRL);
+}
+
+/* ioctl */
+static inline void _dtv_set_hw_params(struct tegra_dtv_context *dtv_ctx)
+{
+ u32 val = 0;
+ u32 reg;
+ struct tegra_dtv_hw_config *cfg = &dtv_ctx->config;
+
+ val = (cfg->clk_edge << DTV_MODE_CLK_EDGE_SHIFT) |
+ (cfg->protocol_sel << DTV_MODE_PRTL_SEL_SHIFT) |
+ (cfg->clk_mode << DTV_MODE_CLK_MODE_SHIFT);
+ reg = tegra_dtv_readl(dtv_ctx, DTV_MODE);
+ reg &= ~(DTV_MODE_CLK_EDGE_MASK |
+ DTV_MODE_PRTL_SEL_MASK |
+ DTV_MODE_CLK_MODE_MASK);
+ reg |= val;
+ tegra_dtv_writel(dtv_ctx, reg, DTV_MODE);
+
+ val = 0;
+ reg = 0;
+ val = (cfg->fec_size << DTV_CTRL_FEC_SIZE_SHIFT) |
+ (cfg->body_size << DTV_CTRL_BODY_SIZE_SHIFT) |
+ (cfg->body_valid_sel << DTV_CTRL_BODY_VALID_SEL_SHIFT) |
+ (cfg->start_sel << DTV_CTRL_START_SEL_SHIFT) |
+ (cfg->err_pol << DTV_CTRL_ERROR_POLARITY_SHIFT) |
+ (cfg->psync_pol << DTV_CTRL_PSYNC_POLARITY_SHIFT) |
+ (cfg->valid_pol << DTV_CTRL_VALID_POLARITY_SHIFT);
+ reg = tegra_dtv_readl(dtv_ctx, DTV_CTRL);
+ reg &= ~(DTV_CTRL_FEC_SIZE_MASK |
+ DTV_CTRL_BODY_SIZE_MASK |
+ DTV_CTRL_BODY_VALID_SEL_MASK |
+ DTV_CTRL_START_SEL_MASK |
+ DTV_CTRL_ERROR_POLARITY_MASK |
+ DTV_CTRL_PSYNC_POLARITY_MASK |
+ DTV_CTRL_VALID_POLARITY_MASK);
+ reg |= val;
+ tegra_dtv_writel(dtv_ctx, reg, DTV_CTRL);
+}
+
+#define DTV_GET_REG_VAL(x, reg, seg) \
+ ((x & reg##_##seg##_MASK) >> reg##_##seg##_SHIFT)
+
+static inline void _dtv_get_hw_params(struct tegra_dtv_context *dtv_ctx,
+ struct tegra_dtv_hw_config *cfg)
+{
+ u32 reg;
+
+ reg = tegra_dtv_readl(dtv_ctx, DTV_MODE);
+ cfg->clk_edge = DTV_GET_REG_VAL(reg, DTV_MODE, CLK_EDGE);
+ cfg->protocol_sel = DTV_GET_REG_VAL(reg, DTV_MODE, PRTL_SEL);
+ cfg->clk_mode = DTV_GET_REG_VAL(reg, DTV_MODE, CLK_MODE);
+
+ reg = tegra_dtv_readl(dtv_ctx, DTV_CTRL);
+ cfg->fec_size = DTV_GET_REG_VAL(reg, DTV_CTRL, FEC_SIZE);
+ cfg->body_size = DTV_GET_REG_VAL(reg, DTV_CTRL, BODY_SIZE);
+ cfg->body_valid_sel = DTV_GET_REG_VAL(reg, DTV_CTRL, BODY_VALID_SEL);
+ cfg->start_sel = DTV_GET_REG_VAL(reg, DTV_CTRL, START_SEL);
+ cfg->err_pol = DTV_GET_REG_VAL(reg, DTV_CTRL, ERROR_POLARITY);
+ cfg->psync_pol = DTV_GET_REG_VAL(reg, DTV_CTRL, PSYNC_POLARITY);
+ cfg->valid_pol = DTV_GET_REG_VAL(reg, DTV_CTRL, VALID_POLARITY);
+}
+
+/* must call with stream->dma_req_lock held. */
+static int stop_xfer_unsafe(struct dtv_stream *s)
+{
+ int spin = 0;
+ struct tegra_dtv_context *dtv_ctx = to_ctx(s);
+
+ pr_debug("%s called\n", __func__);
+ tegra_dma_cancel(s->dma_chan);
+ _dtv_disable_protocol(dtv_ctx);
+ while ((_dtv_get_status(dtv_ctx) & DTV_STATUS_RXF_FULL) &&
+ spin < 100) {
+ udelay(10);
+ if (spin++ > 50)
+ pr_info("%s : spin %d\n", __func__, spin);
+ }
+ if (spin == 100)
+ pr_warn("%s : spinny\n", __func__);
+
+ return 0;
+}
+
+/* must call with stream->mtx held */
+static void __force_xfer_stop(struct dtv_stream *s)
+{
+ int i;
+
+ pr_debug("%s called.\n", __func__);
+
+ if (!s->stopped) {
+ s->stopped = true;
+ if (are_xfers_pending(s))
+ wait_till_stopped(s);
+ for (i = 0; i < s->num_bufs; i++) {
+ init_completion(&s->comp[i]);
+ complete(&s->comp[i]);
+ }
+ }
+
+ /* just in case. dma should be cancelled before this */
+ if (!tegra_dma_is_empty(s->dma_chan))
+ pr_err("%s: DMA channel is not empty!\n", __func__);
+ tegra_dma_cancel(s->dma_chan);
+ s->xferring = false;
+
+ pr_debug("%s: done\n", __func__);
+}
+
+static long tegra_dtv_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = 0;
+ struct tegra_dtv_context *dtv_ctx;
+ struct dtv_stream *s;
+
+ dtv_ctx = (struct tegra_dtv_context *) file->private_data;
+ s = &dtv_ctx->stream;
+
+ /* process may sleep on this */
+ mutex_lock(&s->mtx);
+
+ switch (cmd) {
+ case TEGRA_DTV_IOCTL_START:
+ pr_debug("%s: run serial ts handling.\n", __func__);
+ s->stopped = false;
+ break;
+ case TEGRA_DTV_IOCTL_STOP:
+ pr_debug("%s: stop serial ts handling.\n", __func__);
+ if (s->xferring) {
+ stop_xfer_unsafe(s);
+ complete(&s->stop_completion);
+ __force_xfer_stop(s);
+ s->stopped = true;
+ }
+ break;
+ case TEGRA_DTV_IOCTL_SET_HW_CONFIG:
+ {
+ struct tegra_dtv_hw_config cfg;
+
+ if (s->xferring) {
+ pr_err("%s: tranfering is in progress.\n", __func__);
+ ret = -EBUSY;
+ break;
+ }
+
+ if (copy_from_user(&cfg, (const void __user *) arg,
+ sizeof(cfg))) {
+ ret = -EFAULT;
+ break;
+ }
+
+ dtv_ctx->config = cfg;
+ _dtv_set_hw_params(dtv_ctx);
+ break;
+ }
+ case TEGRA_DTV_IOCTL_GET_HW_CONFIG:
+ {
+ struct tegra_dtv_hw_config cfg;
+
+ _dtv_get_hw_params(dtv_ctx, &cfg);
+
+ if (copy_to_user((void __user *)arg, &cfg,
+ sizeof(cfg)))
+ ret = -EFAULT;
+ break;
+ }
+ default:
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&s->mtx);
+
+ return ret;
+}
+
+/* must call with stream->dma_req_lock held. */
+static int start_xfer_unsafe(struct dtv_stream *s, size_t size)
+{
+ int i;
+ u32 reg;
+ struct tegra_dtv_context *dtv_ctx = to_ctx(s);
+
+ BUG_ON(are_xfers_pending(s));
+
+ pr_debug("%s called.\n", __func__);
+
+ for (i = 0; i < s->num_bufs; i++) {
+ init_completion(&s->comp[i]);
+ s->dma_req[i].dest_addr = s->buf_phy[i];
+ s->dma_req[i].size = size;
+ tegra_dma_enqueue_req(s->dma_chan, &s->dma_req[i]);
+ }
+
+ s->last_queued = s->num_bufs - 1;
+
+ /* too late ? */
+ _dtv_set_attn_level(dtv_ctx);
+ _dtv_enable_protocol(dtv_ctx);
+
+ reg = tegra_dtv_readl(dtv_ctx, DTV_MODE);
+ pr_debug("DTV_MODE = 0x%08x\n", reg);
+
+ return 0;
+}
+
+static int try_start_fill_buf(struct dtv_stream *s, size_t size)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ pr_debug("%s called\n", __func__);
+
+ prevent_suspend(s);
+
+ spin_lock_irqsave(&s->dma_req_lock, flags);
+ if (!s->stopped && !are_xfers_pending(s)) {
+ ret = start_xfer_unsafe(s, size);
+ if (ret) {
+ pr_err("%s: start tranfer failed.\n", __func__);
+ /* let process not wait stupid */
+ wakeup_suspend(s);
+ }
+ }
+ spin_unlock_irqrestore(&s->dma_req_lock, flags);
+
+ return ret;
+}
+
+static ssize_t tegra_dtv_read(struct file *file, char __user *buf,
+ size_t size, loff_t *off)
+{
+ ssize_t ret;
+ ssize_t xfer_size = 0;
+ int buf_no;
+ struct tegra_dma_req *req;
+ struct tegra_dtv_context *dtv_ctx;
+
+ dtv_ctx = (struct tegra_dtv_context *) file->private_data;
+
+ mutex_lock(&dtv_ctx->stream.mtx);
+
+ if (!IS_ALIGNED(size, 4) || size < 4 ||
+ size > dtv_ctx->stream.buf_size) {
+ pr_err("%s: invalid user size %d\n", __func__, size);
+ ret = -EINVAL;
+ mutex_unlock(&dtv_ctx->stream.mtx);
+ return ret;
+ }
+
+ pr_debug("%s: read %d bytes.\n", __func__, size);
+
+ if (dtv_ctx->stream.stopped) {
+ pr_debug("%s: tegra dtv transferring is stopped.\n",
+ __func__);
+ ret = 0;
+ mutex_unlock(&dtv_ctx->stream.mtx);
+ return ret;
+ }
+
+ /* start dma transfer */
+ ret = try_start_fill_buf(&dtv_ctx->stream, size);
+ if (ret < 0 && ret != -EALREADY) {
+ pr_err("%s: could not start recording.\n", __func__);
+ mutex_unlock(&dtv_ctx->stream.mtx);
+ return ret;
+ }
+ dtv_ctx->stream.xferring = true;
+
+ buf_no = (dtv_ctx->stream.last_queued + 1) % dtv_ctx->stream.num_bufs;
+ pr_debug("%s: buf_no = %d\n", __func__, buf_no);
+
+ /* Wait for the buffers to be filled up. The maximum timeout
+ *value should be caculated dynamically based on
+ * buf_size(dtv_ctx->stream).buf_size. For isdb-t 1seg signal,
+ *it bit rate is 300 - 456 kpbs, if buf_size = 4096 bytes, then
+ * to fill up one buffer takes ~77ms.
+ */
+ ret = wait_for_completion_interruptible_timeout(
+ &dtv_ctx->stream.comp[buf_no], HZ);
+ if (!ret) {
+ pr_err("%s: timeout", __func__);
+ ret = -ETIMEDOUT;
+ mutex_unlock(&dtv_ctx->stream.mtx);
+ return ret;
+ } else if (ret < 0) {
+ pr_err("%s: wait error %d", __func__, ret);
+ mutex_unlock(&dtv_ctx->stream.mtx);
+ return ret;
+ }
+
+ req = &dtv_ctx->stream.dma_req[buf_no];
+
+ /* xfer cannot exceed buffer size */
+ xfer_size = size > req->size ? req->size : size;
+ req->size = size;
+ dma_sync_single_for_cpu(NULL,
+ dtv_ctx->stream.dma_req[buf_no].dest_addr,
+ dtv_ctx->stream.dma_req[buf_no].size,
+ DMA_FROM_DEVICE);
+ ret = copy_to_user(buf, dtv_ctx->stream.buffer[buf_no], xfer_size);
+ if (ret) {
+ ret = -EFAULT;
+ mutex_unlock(&dtv_ctx->stream.mtx);
+ return ret;
+ }
+
+ /* not stopped, reinitial stop */
+ init_completion(&dtv_ctx->stream.stop_completion);
+
+ dtv_ctx->stream.last_queued = buf_no;
+
+ /* refill copied buffer */
+ ret = tegra_dma_enqueue_req(dtv_ctx->stream.dma_chan, req);
+ BUG_ON(ret);
+
+ ret = xfer_size;
+ *off += xfer_size;
+
+ mutex_unlock(&dtv_ctx->stream.mtx);
+
+ pr_debug("%s : done with ret = %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int tegra_dtv_open(struct inode *inode, struct file *file)
+{
+ int i;
+ struct tegra_dtv_context *dtv_ctx;
+
+ dtv_ctx = (struct tegra_dtv_context *) file->private_data;
+
+ pr_debug("%s called\n", __func__);
+
+ /* can be opened once */
+ if (!atomic_dec_and_test(&tegra_dtv_instance_nr)) {
+ atomic_inc(&tegra_dtv_instance_nr);
+ pr_err("tegra_dtv device can only be opened once.\n");
+ return -EBUSY;
+ }
+
+ mutex_lock(&dtv_ctx->stream.mtx);
+
+ dtv_ctx->stream.stopped = false;
+
+ /* cleanup completion */
+ for (i = 0; i < dtv_ctx->stream.num_bufs; i++) {
+ init_completion(&dtv_ctx->stream.comp[i]);
+ /* complete all */
+ complete(&dtv_ctx->stream.comp[i]);
+ }
+
+ mutex_unlock(&dtv_ctx->stream.mtx);
+
+ return 0;
+}
+
+static int tegra_dtv_release(struct inode *inode, struct file *file)
+{
+ struct tegra_dtv_context *dtv_ctx =
+ (struct tegra_dtv_context *) file->private_data;
+
+ pr_debug("%s called\n", __func__);
+
+ atomic_inc(&tegra_dtv_instance_nr);
+
+ mutex_lock(&dtv_ctx->stream.mtx);
+ if (dtv_ctx->stream.xferring) {
+ stop_xfer_unsafe(&dtv_ctx->stream);
+ /* clean up stop condition */
+ complete(&dtv_ctx->stream.stop_completion);
+ __force_xfer_stop(&dtv_ctx->stream);
+ }
+ /* wakeup any pending process */
+ wakeup_suspend(&dtv_ctx->stream);
+ mutex_unlock(&dtv_ctx->stream.mtx);
+
+ pr_debug("%s : done\n", __func__);
+
+ return 0;
+}
+
+static const struct file_operations tegra_dtv_fops = {
+ .owner = THIS_MODULE,
+ .open = tegra_dtv_open,
+ .read = tegra_dtv_read,
+ .unlocked_ioctl = tegra_dtv_ioctl,
+ .release = tegra_dtv_release,
+};
+
+static struct miscdevice tegra_dtv_misc_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = TEGRA_DTV_NAME,
+ .fops = &tegra_dtv_fops,
+};
+
+#ifdef CONFIG_DEBUG_FS
+static int dtv_reg_show(struct seq_file *s, void *unused)
+{
+ struct tegra_dtv_context *dtv_ctx = s->private;
+
+ seq_printf(s, "tegra_dtv register list\n");
+ seq_printf(s, "-------------------------------\n");
+ seq_printf(s, "DTV_SPI_CONTROL_0: 0x%08x\n",
+ tegra_dtv_readl(dtv_ctx, DTV_SPI_CONTROL));
+ seq_printf(s, "DTV_MODE_0: 0x%08x\n",
+ tegra_dtv_readl(dtv_ctx, DTV_MODE));
+ seq_printf(s, "DTV_CONTROL: 0x%08x\n",
+ tegra_dtv_readl(dtv_ctx, DTV_CTRL));
+ seq_printf(s, "DTV_FIFO: 0x%08x\n",
+ tegra_dtv_readl(dtv_ctx, DTV_RX_FIFO));
+
+ return 0;
+
+}
+
+static int dtv_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dtv_reg_show, inode->i_private);
+}
+
+static const struct file_operations dtv_debugfs_fops = {
+ .open = dtv_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int dtv_debugfs_init(struct tegra_dtv_context *dtv_ctx)
+{
+ struct dentry *d;
+
+ d = debugfs_create_file("tegra_dtv", S_IRUGO, NULL, dtv_ctx,
+ &dtv_debugfs_fops);
+ if (!d)
+ return -ENOMEM;
+
+ dtv_ctx->d = d;
+
+ return 0;
+}
+
+static void dtv_debugfs_exit(struct tegra_dtv_context *dtv_ctx)
+{
+ debugfs_remove(dtv_ctx->d);
+}
+#else
+static int dtv_debugfs_init(struct tegra_dtv_context *dtv_ctx) { return 0 }
+static void dtv_debugfs_exit(struct tegra_dtv_context *dtv_ctx) {};
+#endif
+
+static void setup_dma_rx_request(struct tegra_dma_req *req,
+ struct dtv_stream *s)
+{
+ struct tegra_dtv_context *dtv_ctx;
+
+ pr_debug("%s before to_ctx\n", __func__);
+ dtv_ctx = to_ctx(s);
+
+ pr_debug("%s called\n", __func__);
+
+ memset(req, 0, sizeof(*req));
+
+ req->complete = tegra_dtv_rx_dma_complete;
+ req->dev = s;
+ req->to_memory = true;
+ req->req_sel = TEGRA_DMA_REQ_SEL_DTV;
+
+ req->source_addr = dtv_ctx->phys + DTV_RX_FIFO;
+ req->source_wrap = 4;
+ req->source_bus_width = 32;
+
+ req->dest_wrap = 0;
+ req->dest_bus_width = 32;
+}
+
+static int setup_dma(struct tegra_dtv_context *dtv_ctx)
+{
+ int ret = 0;
+ int i;
+
+ pr_debug("%s called\n", __func__);
+
+ for (i = 0; i < dtv_ctx->stream.num_bufs; i++) {
+ dtv_ctx->stream.buf_phy[i] = dma_map_single(
+ &dtv_ctx->pdev->dev,
+ dtv_ctx->stream.buffer[i],
+ dtv_ctx->stream.buf_size,
+ DMA_FROM_DEVICE);
+ BUG_ON(!dtv_ctx->stream.buf_phy[i]);
+ setup_dma_rx_request(&dtv_ctx->stream.dma_req[i],
+ &dtv_ctx->stream);
+ dtv_ctx->stream.dma_req[i].dest_addr =
+ dtv_ctx->stream.buf_phy[i];
+ }
+ dtv_ctx->stream.dma_chan = tegra_dma_allocate_channel(
+ TEGRA_DMA_MODE_CONTINUOUS_SINGLE,
+ "tegra_dtv_rx", dtv_ctx->dma_req_sel);
+ if (!dtv_ctx->stream.dma_chan) {
+ pr_err("%s : cannot allocate input DMA channel: %ld\n",
+ __func__, PTR_ERR(dtv_ctx->stream.dma_chan));
+ ret = -ENODEV;
+ /* release */
+ for (i = 0; i < dtv_ctx->stream.num_bufs; i++) {
+ dma_unmap_single(&dtv_ctx->pdev->dev,
+ dtv_ctx->stream.buf_phy[i],
+ 1 << DTV_BUF_SIZE_ORDER,
+ DMA_FROM_DEVICE);
+ dtv_ctx->stream.buf_phy[i] = 0;
+ }
+ tegra_dma_free_channel(dtv_ctx->stream.dma_chan);
+ dtv_ctx->stream.dma_chan = 0;
+
+ return ret;
+ }
+
+ return ret;
+}
+
+static void tear_down_dma(struct tegra_dtv_context *dtv_ctx)
+{
+ int i;
+
+ pr_debug("%s called\n", __func__);
+
+ for (i = 0; i < dtv_ctx->stream.num_bufs; i++) {
+ dma_unmap_single(&dtv_ctx->pdev->dev,
+ dtv_ctx->stream.buf_phy[i],
+ 1 << DTV_BUF_SIZE_ORDER,
+ DMA_FROM_DEVICE);
+ dtv_ctx->stream.buf_phy[i] = 0;
+ }
+ tegra_dma_free_channel(dtv_ctx->stream.dma_chan);
+ dtv_ctx->stream.dma_chan = 0;
+}
+
+static int init_stream_buffer(struct dtv_stream *s, unsigned num)
+{
+ int ret;
+ int i, j;
+
+ pr_debug("%s (num %d)\n", __func__, num);
+
+ for (i = 0; i < num; i++) {
+ kfree(s->buffer[i]);
+ s->buffer[i] = kmalloc((1 << DTV_BUF_SIZE_ORDER),
+ GFP_KERNEL | GFP_DMA);
+ if (!s->buffer[i]) {
+ pr_err("%s : cannot allocate buffer.\n", __func__);
+ for (j = i - 1; j >= 0; j--) {
+ kfree(s->buffer[j]);
+ s->buffer[j] = 0;
+ }
+ ret = -ENOMEM;
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void release_stream_buffer(struct dtv_stream *s, unsigned num)
+{
+ int i;
+
+ pr_debug("%s (num %d)\n", __func__, num);
+
+ for (i = 0; i < num; i++) {
+ kfree(s->buffer[i]);
+ s->buffer[i] = 0;
+ }
+}
+
+static int setup_stream(struct dtv_stream *stream)
+{
+ int ret = 0;
+ int i;
+
+ pr_debug("%s called\n", __func__);
+
+ stream->xferring = false;
+ mutex_init(&stream->mtx);
+ init_completion(&stream->stop_completion);
+ spin_lock_init(&stream->dma_req_lock);
+ stream->dma_chan = NULL;
+ stream->fifo_atn_level = DTV_FIFO_ATN_LVL_TOP_GEAR;
+ stream->buf_size = 1 << DTV_BUF_SIZE_ORDER;
+ stream->num_bufs = DTV_MAX_NUM_BUFS;
+ /* init each buffer */
+ for (i = 0; i < stream->num_bufs; i++) {
+ init_completion(&stream->comp[i]);
+ /* complete all at this moment */
+ complete(&stream->comp[i]);
+ stream->buffer[i] = 0;
+ stream->buf_phy[i] = 0;
+ }
+ stream->last_queued = 0;
+ ret = init_stream_buffer(stream, stream->num_bufs);
+ if (ret < 0)
+ return ret;
+
+ INIT_WORK(&stream->work, tegra_dtv_worker);
+ wake_lock_init(&stream->wake_lock, WAKE_LOCK_SUSPEND, "tegra_dtv");
+
+ return ret;
+}
+
+static int tegra_dtv_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct tegra_dtv_context *dtv_ctx;
+ struct clk *clk;
+ struct resource *res;
+
+ pr_info("%s: probing dtv.\n", __func__);
+
+ dtv_ctx = devm_kzalloc(&pdev->dev, sizeof(struct tegra_dtv_context),
+ GFP_KERNEL);
+ if (!dtv_ctx) {
+ pr_err("%s: Failed to allocate memory for dtv context.\n",
+ __func__);
+ ret = -ENOMEM;
+ return ret;
+ }
+ platform_set_drvdata(pdev, dtv_ctx);
+
+ /* for refer back */
+ dtv_ctx->pdev = pdev;
+
+ /* enable clk for dtv */
+ clk = clk_get(&pdev->dev, NULL);
+ if (!clk) {
+ dev_err(&pdev->dev, "cannot get clock for tegra_dtv.\n");
+ ret = -EIO;
+ goto fail_no_clk;
+ }
+ ret = clk_enable(clk);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot enable clk for tegra_dtv.\n");
+ goto fail_clk_enable;
+ }
+ dtv_ctx->clk_enabled = 1;
+
+ /* get resource */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (unlikely(!res)) {
+ pr_err("%s: Failed to get resource for dtv.\n",
+ __func__);
+ ret = -ENODEV;
+ goto fail_no_res;
+ }
+
+ if (!devm_request_mem_region(&pdev->dev, res->start,
+ resource_size(res), dev_name(&pdev->dev))) {
+ ret = -EBUSY;
+ return ret;
+ }
+ dtv_ctx->phys = res->start;
+ dtv_ctx->base = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!dtv_ctx->base) {
+ dev_err(&pdev->dev, "cannot ioremap iomem.\n");
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ ret = setup_stream(&dtv_ctx->stream);
+ if (ret < 0)
+ goto fail_setup_stream;
+
+ ret = setup_dma(dtv_ctx);
+ if (ret < 0)
+ goto fail_setup_dma;
+
+ /* register as a misc device */
+ ret = misc_register(&tegra_dtv_misc_device);
+ if (ret) {
+ pr_err("%s: Unable to register misc device.\n",
+ __func__);
+ ret = -ENODEV;
+ goto fail_misc_reg;
+ }
+
+ ret = dtv_debugfs_init(dtv_ctx);
+ if (ret) {
+ pr_err("%s: Unable to register debugfs entry.\n",
+ __func__);
+ goto fail_debugfs_reg;
+ }
+
+ return 0;
+
+fail_debugfs_reg:
+ dtv_debugfs_exit(dtv_ctx);
+fail_misc_reg:
+ misc_deregister(&tegra_dtv_misc_device);
+fail_setup_stream:
+fail_setup_dma:
+ tear_down_dma(dtv_ctx);
+fail_no_res:
+fail_clk_enable:
+fail_no_clk:
+ if (clk)
+ clk_put(clk);
+
+ return ret;
+}
+
+static int __devexit tegra_dtv_remove(struct platform_device *pdev)
+{
+ struct tegra_dtv_context *dtv_ctx;
+
+ pr_info("%s: remove dtv.\n", __func__);
+
+ misc_deregister(&tegra_dtv_misc_device);
+
+ dtv_ctx = platform_get_drvdata(pdev);
+
+ dtv_debugfs_exit(dtv_ctx);
+ tear_down_dma(dtv_ctx);
+ release_stream_buffer(&dtv_ctx->stream, dtv_ctx->stream.num_bufs);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int tegra_dtv_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+static int tegra_dtv_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static struct platform_driver tegra_dtv_driver = {
+ .driver = {
+ .name = TEGRA_DTV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = tegra_dtv_probe,
+ .remove = __devexit_p(tegra_dtv_remove),
+#ifdef CONFIG_PM
+ .suspend = tegra_dtv_suspend,
+ .resume = tegra_dtv_resume,
+#endif
+};
+
+static int __init tegra_dtv_init(void)
+{
+ return platform_driver_register(&tegra_dtv_driver);
+}
+
+static void __exit tegra_dtv_exit(void)
+{
+ platform_driver_unregister(&tegra_dtv_driver);
+}
+
+module_init(tegra_dtv_init);
+module_exit(tegra_dtv_exit);
+
+MODULE_AUTHOR("Adam Jiang <chaoj@nvidia.com>");
+MODULE_DESCRIPTION("Tegra DTV interface driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" TEGRA_DTV_NAME);
diff --git a/include/media/tegra_dtv.h b/include/media/tegra_dtv.h
new file mode 100644
index 000000000000..5ecaa47dea82
--- /dev/null
+++ b/include/media/tegra_dtv.h
@@ -0,0 +1,104 @@
+/*
+ *
+ * Copyright (c) 2011, NVIDIA Corporation.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __TEGRA_DTV_H__
+#define __TEGRA_DTV_H__
+
+#include <linux/ioctl.h>
+
+#define TEGRA_DTV_MAGIC 'v'
+
+#define TEGRA_DTV_IOCTL_START _IO(TEGRA_DTV_MAGIC, 0)
+#define TEGRA_DTV_IOCTL_STOP _IO(TEGRA_DTV_MAGIC, 1)
+
+struct tegra_dtv_hw_config {
+ int clk_edge;
+ int byte_swz_enabled;
+ int bit_swz_enabled;
+
+ int protocol_sel;
+ int clk_mode;
+ int fec_size;
+ int body_size;
+ int body_valid_sel;
+ int start_sel;
+ int err_pol;
+ int psync_pol;
+ int valid_pol;
+};
+
+#define TEGRA_DTV_IOCTL_SET_HW_CONFIG _IOW(TEGRA_DTV_MAGIC, 2, \
+ const struct tegra_dtv_hw_config *)
+#define TEGRA_DTV_IOCTL_GET_HW_CONFIG _IOR(TEGRA_DTV_MAGIC, 3, \
+ struct tegra_dtv_hw_config *)
+
+/* for selecting the pin configuration for VD(valid).
+ * NONE : ERROR is tied to 0, PSYNC is tied to 0
+ * ERROR: ERROR is tied to VD, PSYNC is tied to 0
+ * PSYNC: ERROR is tied to 0, PSYNC is tied to VD
+ */
+enum {
+ TEGRA_DTV_PROTOCOL_NONE = 0,
+ TEGRA_DTV_PROTOCOL_ERROR,
+ TEGRA_DTV_PROTOCOL_PSYNC,
+};
+
+enum {
+ TEGRA_DTV_CLK_DISCONTINUOUS = 0,
+ TEGRA_DTV_CLK_CONTINUOUS,
+};
+
+enum {
+ TEGRA_DTV_BODY_VALID_IGNORE = 0,
+ TEGRA_DTV_BODY_VALID_GATE,
+};
+
+enum {
+ TEGRA_DTV_START_RESERVED = 0, /* never use this */
+ TEGRA_DTV_START_PSYNC,
+ TEGRA_DTV_START_VALID,
+ TEGRA_DTV_START_BOTH,
+};
+
+enum {
+ TEGRA_DTV_ERROR_POLARITY_HIGH = 0,
+ TEGRA_DTV_ERROR_POLARITY_LOW,
+};
+
+enum {
+ TEGRA_DTV_PSYNC_POLARITY_HIGH = 0,
+ TEGRA_DTV_PSYNC_POLARITY_LOW,
+};
+
+enum {
+ TEGRA_DTV_VALID_POLARITY_HIGH = 0,
+ TEGRA_DTV_VALID_POLARITY_LOW,
+};
+
+#ifdef __KERNEL__
+enum {
+ TEGRA_DTV_CLK_POSEDGE,
+ TEGRA_DTV_CLK_NEGEDGE,
+};
+
+struct tegra_dtv_platform_data {
+ unsigned int dma_buf_size;
+ int clk_edge;
+ bool byte_swz_enabled;
+ bool bit_swz_enabled;
+};
+#endif /* __KERNEL__ */
+
+#endif /* __TEGRA_DTV_H__ */