summaryrefslogtreecommitdiff
path: root/drivers/media/video/tegra/nvavp/nvavp_dev.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/video/tegra/nvavp/nvavp_dev.c')
-rw-r--r--drivers/media/video/tegra/nvavp/nvavp_dev.c1840
1 files changed, 1840 insertions, 0 deletions
diff --git a/drivers/media/video/tegra/nvavp/nvavp_dev.c b/drivers/media/video/tegra/nvavp/nvavp_dev.c
new file mode 100644
index 000000000000..d5edcbe685bf
--- /dev/null
+++ b/drivers/media/video/tegra/nvavp/nvavp_dev.c
@@ -0,0 +1,1840 @@
+/*
+ * drivers/media/video/tegra/nvavp/nvavp_dev.c
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/uaccess.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/irq.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/nvhost.h>
+#include <linux/platform_device.h>
+#include <linux/rbtree.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/tegra_nvavp.h>
+#include <linux/types.h>
+#include <linux/vmalloc.h>
+#include <linux/workqueue.h>
+
+#include <mach/clk.h>
+#include <mach/hardware.h>
+#include <mach/io.h>
+#include <mach/iomap.h>
+#include <mach/legacy_irq.h>
+#include <linux/nvmap.h>
+
+#if defined(CONFIG_TEGRA_AVP_KERNEL_ON_MMU)
+#include "../avp/headavp.h"
+#endif
+#include "nvavp_os.h"
+
+#define TEGRA_NVAVP_NAME "nvavp"
+
+#define NVAVP_PUSHBUFFER_SIZE 4096
+
+#define NVAVP_PUSHBUFFER_MIN_UPDATE_SPACE (sizeof(u32) * 3)
+
+#define TEGRA_NVAVP_RESET_VECTOR_ADDR \
+ (IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE) + 0x200)
+
+#define FLOW_CTRL_HALT_COP_EVENTS IO_ADDRESS(TEGRA_FLOW_CTRL_BASE + 0x4)
+#define FLOW_MODE_STOP (0x2 << 29)
+#define FLOW_MODE_NONE 0x0
+
+#define NVAVP_OS_INBOX IO_ADDRESS(TEGRA_RES_SEMA_BASE + 0x10)
+#define NVAVP_OS_OUTBOX IO_ADDRESS(TEGRA_RES_SEMA_BASE + 0x20)
+
+#define NVAVP_INBOX_VALID (1 << 29)
+
+/* AVP behavior params */
+#define NVAVP_OS_IDLE_TIMEOUT 100 /* milli-seconds */
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+/* Two control channels: Audio and Video channels */
+#define NVAVP_NUM_CHANNELS 2
+
+#define NVAVP_AUDIO_CHANNEL 1
+
+#define IS_AUDIO_CHANNEL_ID(channel_id) (channel_id == NVAVP_AUDIO_CHANNEL ? 1: 0)
+#else
+#define NVAVP_NUM_CHANNELS 1
+#endif
+
+/* Channel ID 0 represents the Video channel control area */
+#define NVAVP_VIDEO_CHANNEL 0
+/* Channel ID 1 represents the Audio channel control area */
+
+#define IS_VIDEO_CHANNEL_ID(channel_id) (channel_id == NVAVP_VIDEO_CHANNEL ? 1: 0)
+
+
+struct nvavp_channel {
+ struct mutex pushbuffer_lock;
+ struct nvmap_handle_ref *pushbuf_handle;
+ unsigned long pushbuf_phys;
+ u8 *pushbuf_data;
+ u32 pushbuf_index;
+ u32 pushbuf_fence;
+ struct nv_e276_control *os_control;
+};
+
+struct nvavp_info {
+ u32 clk_enabled;
+ struct clk *bsev_clk;
+ struct clk *vde_clk;
+ struct clk *cop_clk;
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ struct clk *bsea_clk;
+ struct clk *vcp_clk;
+#endif
+
+ /* used for dvfs */
+ struct clk *sclk;
+ struct clk *emc_clk;
+ unsigned long sclk_rate;
+ unsigned long emc_clk_rate;
+
+ int mbox_from_avp_pend_irq;
+
+ struct mutex open_lock;
+ int refcount;
+ int video_initialized;
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ int audio_initialized;
+ struct work_struct app_notify_work;
+#endif
+ struct work_struct clock_disable_work;
+
+ /* os information */
+ struct nvavp_os_info os_info;
+
+ /* ucode information */
+ struct nvavp_ucode_info ucode_info;
+
+ /* client for driver allocations, persistent */
+ struct nvmap_client *nvmap;
+
+ bool pending;
+
+ struct nvavp_channel channel_info[NVAVP_NUM_CHANNELS];
+
+ u32 syncpt_id;
+ u32 syncpt_value;
+
+ struct nvhost_device *nvhost_dev;
+ struct miscdevice video_misc_dev;
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ struct miscdevice audio_misc_dev;
+#endif
+};
+
+struct nvavp_clientctx {
+ struct nvmap_client *nvmap;
+ struct nvavp_pushbuffer_submit_hdr submit_hdr;
+ struct nvavp_reloc relocs[NVAVP_MAX_RELOCATION_COUNT];
+ struct nvmap_handle_ref *gather_mem;
+ int num_relocs;
+ struct nvavp_info *nvavp;
+ u32 clk_reqs;
+ int channel_id;
+};
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+static int nvavp_get_audio_init_status(struct nvavp_info *nvavp)
+{
+ return nvavp->audio_initialized;
+}
+
+static void nvavp_set_audio_init_status(struct nvavp_info *nvavp, int status)
+{
+ nvavp->audio_initialized = status;
+}
+#endif
+
+static void nvavp_set_video_init_status(struct nvavp_info *nvavp, int status)
+{
+ nvavp->video_initialized = status;
+}
+
+static int nvavp_get_video_init_status(struct nvavp_info *nvavp)
+{
+ return nvavp->video_initialized;
+}
+
+static struct nvavp_channel *nvavp_get_channel_info(struct nvavp_info *nvavp, int channel_id)
+{
+ return &nvavp->channel_info[channel_id];
+}
+
+static void nvavp_set_channel_control_area(struct nvavp_info *nvavp, int channel_id)
+{
+ struct nv_e276_control *control;
+ struct nvavp_os_info *os = &nvavp->os_info;
+ u32 temp;
+ void *ptr;
+ struct nvavp_channel *channel_info;
+
+ ptr = os->data + os->control_offset + (sizeof(struct nv_e276_control) * channel_id);
+
+ channel_info = nvavp_get_channel_info(nvavp, channel_id);
+ channel_info->os_control = (struct nv_e276_control *)ptr;
+
+ control = channel_info->os_control;
+
+ /* init get and put pointers */
+ writel(0x0, &control->put);
+ writel(0x0, &control->get);
+
+ pr_debug("nvavp_set_channel_control_area for channel_id (%d):\
+ control->put (0x%08x) control->get (0x%08x)\n",
+ channel_id, (u32) &control->put, (u32) &control->get);
+
+ /* enable avp VDE clock control and disable iram clock gating */
+ writel(0x0, &control->idle_clk_enable);
+ writel(0x0, &control->iram_clk_gating);
+
+ /* enable avp idle timeout interrupt */
+ writel(0x1, &control->idle_notify_enable);
+ writel(NVAVP_OS_IDLE_TIMEOUT, &control->idle_notify_delay);
+
+ /* init dma start and end pointers */
+ writel(channel_info->pushbuf_phys, &control->dma_start);
+ writel((channel_info->pushbuf_phys + NVAVP_PUSHBUFFER_SIZE),
+ &control->dma_end);
+
+ writel(0x00, &channel_info->pushbuf_index);
+ temp = NVAVP_PUSHBUFFER_SIZE - NVAVP_PUSHBUFFER_MIN_UPDATE_SPACE;
+ writel(temp, &channel_info->pushbuf_fence);
+}
+
+static struct clk *nvavp_clk_get(struct nvavp_info *nvavp, int id)
+{
+ if (!nvavp)
+ return NULL;
+
+ if (id == NVAVP_MODULE_ID_AVP)
+ return nvavp->sclk;
+ if (id == NVAVP_MODULE_ID_VDE)
+ return nvavp->vde_clk;
+ if (id == NVAVP_MODULE_ID_EMC)
+ return nvavp->emc_clk;
+
+ return NULL;
+}
+
+static void nvavp_clks_enable(struct nvavp_info *nvavp)
+{
+ if (nvavp->clk_enabled++ == 0) {
+ nvhost_module_busy_ext(nvhost_get_parent(nvavp->nvhost_dev));
+ clk_enable(nvavp->bsev_clk);
+ clk_enable(nvavp->vde_clk);
+ clk_set_rate(nvavp->emc_clk, nvavp->emc_clk_rate);
+ clk_set_rate(nvavp->sclk, nvavp->sclk_rate);
+ dev_dbg(&nvavp->nvhost_dev->dev, "%s: setting sclk to %lu\n",
+ __func__, nvavp->sclk_rate);
+ dev_dbg(&nvavp->nvhost_dev->dev, "%s: setting emc_clk to %lu\n",
+ __func__, nvavp->emc_clk_rate);
+ }
+}
+
+static void nvavp_clks_disable(struct nvavp_info *nvavp)
+{
+ if (--nvavp->clk_enabled == 0) {
+ clk_disable(nvavp->bsev_clk);
+ clk_disable(nvavp->vde_clk);
+ clk_set_rate(nvavp->emc_clk, 0);
+ clk_set_rate(nvavp->sclk, 0);
+ nvhost_module_idle_ext(nvhost_get_parent(nvavp->nvhost_dev));
+ dev_dbg(&nvavp->nvhost_dev->dev, "%s: resetting emc_clk "
+ "and sclk\n", __func__);
+ }
+}
+
+static u32 nvavp_check_idle(struct nvavp_info *nvavp, int channel_id)
+{
+ struct nvavp_channel *channel_info = nvavp_get_channel_info(nvavp, channel_id);
+ struct nv_e276_control *control = channel_info->os_control;
+
+ return (control->put == control->get) ? 1 : 0;
+}
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+static void app_notify_handler(struct work_struct *work)
+{
+ struct nvavp_info *nvavp;
+
+ nvavp = container_of(work, struct nvavp_info,
+ app_notify_work);
+
+ kobject_uevent(&nvavp->nvhost_dev->dev.kobj, KOBJ_CHANGE);
+}
+#endif
+
+static void clock_disable_handler(struct work_struct *work)
+{
+ struct nvavp_info *nvavp;
+ struct nvavp_channel *channel_info;
+
+ nvavp = container_of(work, struct nvavp_info,
+ clock_disable_work);
+ channel_info = nvavp_get_channel_info(nvavp, NVAVP_VIDEO_CHANNEL);
+
+ mutex_lock(&channel_info->pushbuffer_lock);
+ mutex_lock(&nvavp->open_lock);
+ if (nvavp_check_idle(nvavp, NVAVP_VIDEO_CHANNEL) && nvavp->pending) {
+ nvavp->pending = false;
+ nvavp_clks_disable(nvavp);
+ }
+ mutex_unlock(&nvavp->open_lock);
+ mutex_unlock(&channel_info->pushbuffer_lock);
+}
+
+static int nvavp_service(struct nvavp_info *nvavp)
+{
+ struct nvavp_os_info *os = &nvavp->os_info;
+ u8 *debug_print;
+ u32 inbox;
+
+ inbox = readl(NVAVP_OS_INBOX);
+ if (!(inbox & NVAVP_INBOX_VALID))
+ inbox = 0x00000000;
+
+ if (inbox & NVE276_OS_INTERRUPT_VIDEO_IDLE)
+ schedule_work(&nvavp->clock_disable_work);
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ if (inbox & NVE276_OS_INTERRUPT_AUDIO_IDLE)
+ pr_debug("nvavp_service NVE276_OS_INTERRUPT_AUDIO_IDLE\n");
+#endif
+ if (inbox & NVE276_OS_INTERRUPT_DEBUG_STRING) {
+ /* Should only occur with debug AVP OS builds */
+ debug_print = os->data;
+ debug_print += os->debug_offset;
+ dev_info(&nvavp->nvhost_dev->dev, "%s\n", debug_print);
+ }
+ if (inbox & (NVE276_OS_INTERRUPT_SEMAPHORE_AWAKEN |
+ NVE276_OS_INTERRUPT_EXECUTE_AWAKEN)) {
+ dev_info(&nvavp->nvhost_dev->dev,
+ "AVP awaken event (0x%x)\n", inbox);
+ }
+ if (inbox & NVE276_OS_INTERRUPT_AVP_FATAL_ERROR) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "fatal AVP error (0x%08X)\n", inbox);
+ }
+ if (inbox & NVE276_OS_INTERRUPT_AVP_BREAKPOINT)
+ dev_err(&nvavp->nvhost_dev->dev, "AVP breakpoint hit\n");
+ if (inbox & NVE276_OS_INTERRUPT_TIMEOUT)
+ dev_err(&nvavp->nvhost_dev->dev, "AVP timeout\n");
+ writel(inbox & NVAVP_INBOX_VALID, NVAVP_OS_INBOX);
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ if (inbox & NVE276_OS_INTERRUPT_APP_NOTIFY) {
+ pr_debug("nvavp_service NVE276_OS_INTERRUPT_APP_NOTIFY\n");
+ schedule_work(&nvavp->app_notify_work);
+ }
+#endif
+
+ return 0;
+}
+
+static irqreturn_t nvavp_mbox_pending_isr(int irq, void *data)
+{
+ struct nvavp_info *nvavp = data;
+
+ nvavp_service(nvavp);
+
+ return IRQ_HANDLED;
+}
+
+static void nvavp_halt_avp(struct nvavp_info *nvavp)
+{
+ /* ensure the AVP is halted */
+ writel(FLOW_MODE_STOP, FLOW_CTRL_HALT_COP_EVENTS);
+ tegra_periph_reset_assert(nvavp->cop_clk);
+
+ writel(0, NVAVP_OS_OUTBOX);
+ writel(0, NVAVP_OS_INBOX);
+}
+
+static int nvavp_reset_avp(struct nvavp_info *nvavp, unsigned long reset_addr)
+{
+#if defined(CONFIG_TEGRA_AVP_KERNEL_ON_MMU)
+ unsigned long stub_code_phys = virt_to_phys(_tegra_avp_boot_stub);
+ dma_addr_t stub_data_phys;
+
+ _tegra_avp_boot_stub_data.map_phys_addr = avp->kernel_phys;
+ _tegra_avp_boot_stub_data.jump_addr = reset_addr;
+ wmb();
+ stub_data_phys = dma_map_single(NULL, &_tegra_avp_boot_stub_data,
+ sizeof(_tegra_avp_boot_stub_data),
+ DMA_TO_DEVICE);
+ rmb();
+ reset_addr = (unsigned long)stub_data_phys;
+#endif
+ writel(FLOW_MODE_STOP, FLOW_CTRL_HALT_COP_EVENTS);
+
+ writel(reset_addr, TEGRA_NVAVP_RESET_VECTOR_ADDR);
+
+ clk_enable(nvavp->sclk);
+ clk_enable(nvavp->emc_clk);
+
+ /* If sclk_rate and emc_clk is not set by user space,
+ * max clock in dvfs table will be used to get best performance.
+ */
+ nvavp->sclk_rate = ULONG_MAX;
+ nvavp->emc_clk_rate = ULONG_MAX;
+
+ tegra_periph_reset_assert(nvavp->cop_clk);
+ udelay(2);
+ tegra_periph_reset_deassert(nvavp->cop_clk);
+
+ writel(FLOW_MODE_NONE, FLOW_CTRL_HALT_COP_EVENTS);
+
+#if defined(CONFIG_TEGRA_AVP_KERNEL_ON_MMU)
+ dma_unmap_single(NULL, stub_data_phys,
+ sizeof(_tegra_avp_boot_stub_data),
+ DMA_TO_DEVICE);
+#endif
+ return 0;
+}
+
+static void nvavp_halt_vde(struct nvavp_info *nvavp)
+{
+ if (nvavp->clk_enabled && !nvavp->pending)
+ BUG();
+
+ if (nvavp->pending) {
+ nvavp_clks_disable(nvavp);
+ nvavp->pending = false;
+ }
+
+ tegra_periph_reset_assert(nvavp->bsev_clk);
+ tegra_periph_reset_assert(nvavp->vde_clk);
+}
+
+static int nvavp_reset_vde(struct nvavp_info *nvavp)
+{
+ if (nvavp->clk_enabled)
+ BUG();
+
+ nvavp_clks_enable(nvavp);
+
+ tegra_periph_reset_assert(nvavp->bsev_clk);
+ udelay(2);
+ tegra_periph_reset_deassert(nvavp->bsev_clk);
+
+ tegra_periph_reset_assert(nvavp->vde_clk);
+ udelay(2);
+ tegra_periph_reset_deassert(nvavp->vde_clk);
+
+ /*
+ * VDE clock is set to max freq by default.
+ * VDE clock can be set to different freq if needed
+ * through ioctl.
+ */
+ clk_set_rate(nvavp->vde_clk, ULONG_MAX);
+
+ nvavp_clks_disable(nvavp);
+
+ return 0;
+}
+
+static int nvavp_pushbuffer_alloc(struct nvavp_info *nvavp, int channel_id)
+{
+ int ret = 0;
+
+ struct nvavp_channel *channel_info = nvavp_get_channel_info(
+ nvavp, channel_id);
+
+ channel_info->pushbuf_handle = nvmap_alloc(nvavp->nvmap,
+ NVAVP_PUSHBUFFER_SIZE,
+ SZ_1M, NVMAP_HANDLE_UNCACHEABLE,
+ 0);
+ if (IS_ERR(channel_info->pushbuf_handle)) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot create pushbuffer handle\n");
+ ret = PTR_ERR(channel_info->pushbuf_handle);
+ goto err_pushbuf_alloc;
+ }
+ channel_info->pushbuf_data = (u8 *)nvmap_mmap(
+ channel_info->pushbuf_handle);
+
+ if (!channel_info->pushbuf_data) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot map pushbuffer handle\n");
+ ret = -ENOMEM;
+ goto err_pushbuf_mmap;
+ }
+ channel_info->pushbuf_phys = nvmap_pin(nvavp->nvmap,
+ channel_info->pushbuf_handle);
+ if (IS_ERR((void *)channel_info->pushbuf_phys)) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot pin pushbuffer handle\n");
+ ret = PTR_ERR((void *)channel_info->pushbuf_phys);
+ goto err_pushbuf_pin;
+ }
+
+ memset(channel_info->pushbuf_data, 0, NVAVP_PUSHBUFFER_SIZE);
+
+ return 0;
+
+err_pushbuf_pin:
+ nvmap_munmap(channel_info->pushbuf_handle, channel_info->pushbuf_data);
+err_pushbuf_mmap:
+ nvmap_free(nvavp->nvmap, channel_info->pushbuf_handle);
+err_pushbuf_alloc:
+ return ret;
+}
+
+static void nvavp_pushbuffer_free(struct nvavp_info *nvavp)
+{
+ int channel_id;
+
+ for (channel_id = 0; channel_id < NVAVP_NUM_CHANNELS; channel_id++) {
+ if (nvavp->channel_info[channel_id].pushbuf_data) {
+ nvmap_unpin(nvavp->nvmap,
+ nvavp->channel_info[channel_id].pushbuf_handle);
+ nvmap_munmap(
+ nvavp->channel_info[channel_id].pushbuf_handle,
+ nvavp->channel_info[channel_id].pushbuf_data);
+ nvmap_free(nvavp->nvmap,
+ nvavp->channel_info[channel_id].pushbuf_handle);
+ }
+ }
+}
+
+
+static int nvavp_pushbuffer_init(struct nvavp_info *nvavp)
+{
+ int ret, channel_id;
+
+ for (channel_id = 0; channel_id < NVAVP_NUM_CHANNELS; channel_id++) {
+ ret = nvavp_pushbuffer_alloc(nvavp, channel_id);
+ if (ret) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "unable to alloc pushbuffer\n");
+ return ret;
+ }
+ nvavp_set_channel_control_area(nvavp, channel_id);
+ if (IS_VIDEO_CHANNEL_ID(channel_id)) {
+ nvavp->syncpt_id = NVSYNCPT_AVP_0;
+ nvavp->syncpt_value = nvhost_syncpt_read_ext(
+ nvavp->nvhost_dev, nvavp->syncpt_id);
+ }
+
+ }
+ return 0;
+}
+
+static void nvavp_pushbuffer_deinit(struct nvavp_info *nvavp)
+{
+ nvavp_pushbuffer_free(nvavp);
+}
+
+static int nvavp_pushbuffer_update(struct nvavp_info *nvavp, u32 phys_addr,
+ u32 gather_count, struct nvavp_syncpt *syncpt,
+ u32 ext_ucode_flag, int channel_id)
+{
+ struct nvavp_channel *channel_info;
+ struct nv_e276_control *control;
+ u32 gather_cmd, setucode_cmd, sync = 0;
+ u32 wordcount = 0;
+ u32 index, value = -1;
+
+ channel_info = nvavp_get_channel_info(nvavp, channel_id);
+
+ control = channel_info->os_control;
+ pr_debug("nvavp_pushbuffer_update for channel_id (%d):\
+ control->put (0x%x) control->get (0x%x)\n",
+ channel_id, (u32) &control->put, (u32) &control->get);
+
+ mutex_lock(&channel_info->pushbuffer_lock);
+
+ /* check for pushbuffer wrapping */
+ if (channel_info->pushbuf_index >= channel_info->pushbuf_fence)
+ channel_info->pushbuf_index = 0;
+
+ if (!ext_ucode_flag) {
+ setucode_cmd =
+ NVE26E_CH_OPCODE_INCR(NVE276_SET_MICROCODE_A, 3);
+
+ index = wordcount + channel_info->pushbuf_index;
+ writel(setucode_cmd, (channel_info->pushbuf_data + index));
+ wordcount += sizeof(u32);
+
+ index = wordcount + channel_info->pushbuf_index;
+ writel(0, (channel_info->pushbuf_data + index));
+ wordcount += sizeof(u32);
+
+ index = wordcount + channel_info->pushbuf_index;
+ writel(nvavp->ucode_info.phys,
+ (channel_info->pushbuf_data + index));
+ wordcount += sizeof(u32);
+
+ index = wordcount + channel_info->pushbuf_index;
+ writel(nvavp->ucode_info.size,
+ (channel_info->pushbuf_data + index));
+ wordcount += sizeof(u32);
+ }
+
+ gather_cmd = NVE26E_CH_OPCODE_GATHER(0, 0, 0, gather_count);
+
+ if (syncpt) {
+ value = ++nvavp->syncpt_value;
+ /* XXX: NvSchedValueWrappingComparison */
+ sync = NVE26E_CH_OPCODE_IMM(NVE26E_HOST1X_INCR_SYNCPT,
+ (NVE26E_HOST1X_INCR_SYNCPT_COND_OP_DONE << 8) |
+ (nvavp->syncpt_id & 0xFF));
+ }
+
+ /* write commands out */
+ index = wordcount + channel_info->pushbuf_index;
+ writel(gather_cmd, (channel_info->pushbuf_data + index));
+ wordcount += sizeof(u32);
+
+ index = wordcount + channel_info->pushbuf_index;
+ writel(phys_addr, (channel_info->pushbuf_data + index));
+ wordcount += sizeof(u32);
+
+ if (syncpt) {
+ index = wordcount + channel_info->pushbuf_index;
+ writel(sync, (channel_info->pushbuf_data + index));
+ wordcount += sizeof(u32);
+ }
+
+ /* enable clocks to VDE/BSEV */
+ if (IS_VIDEO_CHANNEL_ID(channel_id)) {
+ mutex_lock(&nvavp->open_lock);
+ if (!nvavp->pending) {
+ nvavp_clks_enable(nvavp);
+ nvavp->pending = true;
+ }
+ mutex_unlock(&nvavp->open_lock);
+ }
+
+ /* update put pointer */
+ channel_info->pushbuf_index = (channel_info->pushbuf_index + wordcount)&
+ (NVAVP_PUSHBUFFER_SIZE - 1);
+
+ writel(channel_info->pushbuf_index, &control->put);
+ wmb();
+
+ /* wake up avp */
+
+ if (IS_VIDEO_CHANNEL_ID(channel_id)) {
+ pr_debug("Wake up Video Channel\n");
+ writel(0xA0000001, NVAVP_OS_OUTBOX);
+ }
+ else {
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ if (IS_AUDIO_CHANNEL_ID(channel_id)) {
+ pr_debug("Wake up Audio Channel\n");
+ writel(0xA0000002, NVAVP_OS_OUTBOX);
+ }
+#endif
+ }
+ /* Fill out fence struct */
+ if (syncpt) {
+ syncpt->id = nvavp->syncpt_id;
+ syncpt->value = value;
+ }
+
+ mutex_unlock(&channel_info->pushbuffer_lock);
+
+ return 0;
+}
+
+static void nvavp_unload_ucode(struct nvavp_info *nvavp)
+{
+ nvmap_unpin(nvavp->nvmap, nvavp->ucode_info.handle);
+ nvmap_munmap(nvavp->ucode_info.handle, nvavp->ucode_info.data);
+ nvmap_free(nvavp->nvmap, nvavp->ucode_info.handle);
+ kfree(nvavp->ucode_info.ucode_bin);
+}
+
+static int nvavp_load_ucode(struct nvavp_info *nvavp)
+{
+ struct nvavp_ucode_info *ucode_info = &nvavp->ucode_info;
+ const struct firmware *nvavp_ucode_fw;
+ char fw_ucode_file[32];
+ void *ptr;
+ int ret = 0;
+
+ if (!ucode_info->ucode_bin) {
+ sprintf(fw_ucode_file, "nvavp_vid_ucode.bin");
+
+ ret = request_firmware(&nvavp_ucode_fw, fw_ucode_file,
+ nvavp->video_misc_dev.this_device);
+ if (ret) {
+ /* Try alternative version */
+ sprintf(fw_ucode_file, "nvavp_vid_ucode_alt.bin");
+
+ ret = request_firmware(&nvavp_ucode_fw,
+ fw_ucode_file,
+ nvavp->video_misc_dev.this_device);
+
+ if (ret) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot read ucode firmware '%s'\n",
+ fw_ucode_file);
+ goto err_req_ucode;
+ }
+ }
+
+ dev_info(&nvavp->nvhost_dev->dev,
+ "read ucode firmware from '%s' (%d bytes)\n",
+ fw_ucode_file, nvavp_ucode_fw->size);
+
+ ptr = (void *)nvavp_ucode_fw->data;
+
+ if (strncmp((const char *)ptr, "NVAVPAPP", 8)) {
+ dev_info(&nvavp->nvhost_dev->dev,
+ "ucode hdr string mismatch\n");
+ ret = -EINVAL;
+ goto err_req_ucode;
+ }
+ ptr += 8;
+ ucode_info->size = nvavp_ucode_fw->size - 8;
+
+ ucode_info->ucode_bin = kzalloc(ucode_info->size,
+ GFP_KERNEL);
+ if (!ucode_info->ucode_bin) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot allocate ucode bin\n");
+ ret = -ENOMEM;
+ goto err_ubin_alloc;
+ }
+
+ ucode_info->handle = nvmap_alloc(nvavp->nvmap,
+ nvavp->ucode_info.size,
+ SZ_1M, NVMAP_HANDLE_UNCACHEABLE, 0);
+ if (IS_ERR(ucode_info->handle)) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot create ucode handle\n");
+ ret = PTR_ERR(ucode_info->handle);
+ goto err_ucode_alloc;
+ }
+ ucode_info->data = (u8 *)nvmap_mmap(ucode_info->handle);
+ if (!ucode_info->data) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot map ucode handle\n");
+ ret = -ENOMEM;
+ goto err_ucode_mmap;
+ }
+ ucode_info->phys = nvmap_pin(nvavp->nvmap, ucode_info->handle);
+ if (IS_ERR((void *)ucode_info->phys)) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot pin ucode handle\n");
+ ret = PTR_ERR((void *)ucode_info->phys);
+ goto err_ucode_pin;
+ }
+ memcpy(ucode_info->ucode_bin, ptr, ucode_info->size);
+ release_firmware(nvavp_ucode_fw);
+ }
+
+ memcpy(ucode_info->data, ucode_info->ucode_bin, ucode_info->size);
+ return 0;
+
+err_ucode_pin:
+ nvmap_munmap(ucode_info->handle, ucode_info->data);
+err_ucode_mmap:
+ nvmap_free(nvavp->nvmap, ucode_info->handle);
+err_ucode_alloc:
+ kfree(nvavp->ucode_info.ucode_bin);
+err_ubin_alloc:
+ release_firmware(nvavp_ucode_fw);
+err_req_ucode:
+ return ret;
+}
+
+static void nvavp_unload_os(struct nvavp_info *nvavp)
+{
+ nvmap_unpin(nvavp->nvmap, nvavp->os_info.handle);
+ nvmap_munmap(nvavp->os_info.handle, nvavp->os_info.data);
+#if defined(CONFIG_TEGRA_AVP_KERNEL_ON_MMU)
+ nvmap_free(nvavp->nvmap, nvavp->os_info.handle);
+#elif defined(CONFIG_TEGRA_AVP_KERNEL_ON_SMMU)
+ nvmap_free_iovm(nvavp->nvmap, nvavp->os_info.handle);
+#endif
+ kfree(nvavp->os_info.os_bin);
+}
+
+static int nvavp_load_os(struct nvavp_info *nvavp, char *fw_os_file)
+{
+ struct nvavp_os_info *os_info = &nvavp->os_info;
+ const struct firmware *nvavp_os_fw;
+ void *ptr;
+ u32 size;
+ int ret = 0;
+
+ if (!os_info->os_bin) {
+ ret = request_firmware(&nvavp_os_fw, fw_os_file,
+ nvavp->video_misc_dev.this_device);
+ if (ret) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot read os firmware '%s'\n", fw_os_file);
+ goto err_req_fw;
+ }
+
+ dev_info(&nvavp->nvhost_dev->dev,
+ "read firmware from '%s' (%d bytes)\n",
+ fw_os_file, nvavp_os_fw->size);
+
+ ptr = (void *)nvavp_os_fw->data;
+
+ if (strncmp((const char *)ptr, "NVAVP-OS", 8)) {
+ dev_info(&nvavp->nvhost_dev->dev,
+ "os hdr string mismatch\n");
+ ret = -EINVAL;
+ goto err_os_bin;
+ }
+
+ ptr += 8;
+ os_info->entry_offset = *((u32 *)ptr);
+ ptr += sizeof(u32);
+ os_info->control_offset = *((u32 *)ptr);
+ ptr += sizeof(u32);
+ os_info->debug_offset = *((u32 *)ptr);
+ ptr += sizeof(u32);
+
+ size = *((u32 *)ptr); ptr += sizeof(u32);
+
+ os_info->size = size;
+ os_info->os_bin = kzalloc(os_info->size,
+ GFP_KERNEL);
+ if (!os_info->os_bin) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot allocate os bin\n");
+ ret = -ENOMEM;
+ goto err_os_bin;
+ }
+
+ memcpy(os_info->os_bin, ptr, os_info->size);
+ memset(os_info->data + os_info->size, 0, SZ_1M - os_info->size);
+
+ dev_info(&nvavp->nvhost_dev->dev,
+ "entry=%08x control=%08x debug=%08x size=%d\n",
+ os_info->entry_offset, os_info->control_offset,
+ os_info->debug_offset, os_info->size);
+ release_firmware(nvavp_os_fw);
+ }
+
+ memcpy(os_info->data, os_info->os_bin, os_info->size);
+ os_info->reset_addr = os_info->phys + os_info->entry_offset;
+
+ dev_info(&nvavp->nvhost_dev->dev,
+ "AVP os at vaddr=%p paddr=%lx reset_addr=%p\n",
+ os_info->data, (unsigned long)(os_info->phys),
+ (void *)os_info->reset_addr);
+ return 0;
+
+err_os_bin:
+ release_firmware(nvavp_os_fw);
+err_req_fw:
+ return ret;
+}
+
+
+static int nvavp_os_init(struct nvavp_info *nvavp)
+{
+ char fw_os_file[32];
+ int ret = 0;
+ int video_initialized, audio_initialized = 0;
+
+ video_initialized = nvavp_get_video_init_status(nvavp);
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ audio_initialized = nvavp_get_audio_init_status(nvavp);
+#endif
+ pr_debug("video_initialized(%d) audio_initialized(%d)\n",
+ video_initialized, audio_initialized);
+
+ /* Video and Audio both are initialized */
+ if (video_initialized || audio_initialized)
+ return ret;
+
+ /* Video or Audio both are uninitialized */
+ pr_debug("video_initialized == audio_initialized (%d)\n",
+ nvavp->video_initialized);
+#if defined(CONFIG_TEGRA_AVP_KERNEL_ON_MMU) /* Tegra2 with AVP MMU */
+ /* paddr is any address returned from nvmap_pin */
+ /* vaddr is AVP_KERNEL_VIRT_BASE */
+ dev_info(&nvavp->nvhost_dev->dev,
+ "using AVP MMU to relocate AVP os\n");
+ sprintf(fw_os_file, "nvavp_os.bin");
+ nvavp->os_info.reset_addr = AVP_KERNEL_VIRT_BASE;
+#elif defined(CONFIG_TEGRA_AVP_KERNEL_ON_SMMU) /* Tegra3 with SMMU */
+ /* paddr is any address behind SMMU */
+ /* vaddr is TEGRA_SMMU_BASE */
+ dev_info(&nvavp->nvhost_dev->dev,
+ "using SMMU at %lx to load AVP kernel\n",
+ (unsigned long)nvavp->os_info.phys);
+ BUG_ON(nvavp->os_info.phys != 0xeff00000
+ && nvavp->os_info.phys != 0x0ff00000);
+ sprintf(fw_os_file, "nvavp_os_%08lx.bin",
+ (unsigned long)nvavp->os_info.phys);
+ nvavp->os_info.reset_addr = nvavp->os_info.phys;
+#else /* nvmem= carveout */
+ /* paddr is found in nvmem= carveout */
+ /* vaddr is same as paddr */
+ /* Find nvmem carveout */
+ if (!pfn_valid(__phys_to_pfn(0x8e000000))) {
+ nvavp->os_info.phys = 0x8e000000;
+ } else if (!pfn_valid(__phys_to_pfn(0x9e000000))) {
+ nvavp->os_info.phys = 0x9e000000;
+ } else if (!pfn_valid(__phys_to_pfn(0xbe000000))) {
+ nvavp->os_info.phys = 0xbe000000;
+ } else {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "cannot find nvmem= carveout to load AVP os\n");
+ dev_err(&nvavp->nvhost_dev->dev,
+ "check kernel command line "
+ "to see if nvmem= is defined\n");
+ BUG();
+ }
+ dev_info(&nvavp->nvhost_dev->dev,
+ "using nvmem= carveout at %lx to load AVP os\n",
+ nvavp->os_info.phys);
+ sprintf(fw_os_file, "nvavp_os_%08lx.bin", nvavp->os_info.phys);
+ nvavp->os_info.reset_addr = nvavp->os_info.phys;
+ nvavp->os_info.data = ioremap(nvavp->os_info.phys, SZ_1M);
+#endif
+ ret = nvavp_load_os(nvavp, fw_os_file);
+ if (ret) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "unable to load os firmware '%s'\n", fw_os_file);
+ goto err_exit;
+ }
+
+ ret = nvavp_pushbuffer_init(nvavp);
+ if (ret) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "unable to init pushbuffer\n");
+ goto err_exit;
+ }
+ tegra_init_legacy_irq_cop();
+ enable_irq(nvavp->mbox_from_avp_pend_irq);
+err_exit:
+ return ret;
+}
+
+static int nvavp_init(struct nvavp_info *nvavp, int channel_id)
+{
+ int ret = 0;
+
+ ret = nvavp_os_init(nvavp);
+ if (ret) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "unable to load os firmware and allocate buffers\n");
+ }
+
+ if (IS_VIDEO_CHANNEL_ID(channel_id) &&
+ (!nvavp_get_video_init_status(nvavp)) ) {
+ pr_debug("nvavp_init : channel_ID (%d)\n", channel_id);
+ ret = nvavp_load_ucode(nvavp);
+ if (ret) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "unable to load ucode\n");
+ goto err_exit;
+ }
+
+ nvavp_reset_vde(nvavp);
+ nvavp_reset_avp(nvavp, nvavp->os_info.reset_addr);
+
+ nvavp_set_video_init_status(nvavp, 1);
+ }
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ if (IS_AUDIO_CHANNEL_ID(channel_id) &&
+ (!nvavp_get_audio_init_status(nvavp))) {
+ pr_debug("nvavp_init : channel_ID (%d)\n", channel_id);
+ nvavp_reset_avp(nvavp, nvavp->os_info.reset_addr);
+ nvavp_set_audio_init_status(nvavp, 1);
+ }
+#endif
+
+err_exit:
+ return ret;
+}
+
+static void nvavp_uninit(struct nvavp_info *nvavp)
+{
+ int video_initialized, audio_initialized = 0;
+
+ video_initialized = nvavp_get_video_init_status(nvavp);
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ audio_initialized = nvavp_get_audio_init_status(nvavp);
+#endif
+
+ pr_debug("nvavp_uninit video_initialized(%d) audio_initialized(%d)\n",
+ video_initialized, audio_initialized);
+
+ /* Video and Audio both are uninitialized */
+ if (!video_initialized && !audio_initialized)
+ return;
+
+ if (video_initialized) {
+ pr_debug("nvavp_uninit nvavp->video_initialized\n");
+ cancel_work_sync(&nvavp->clock_disable_work);
+ nvavp_halt_vde(nvavp);
+ nvavp_set_video_init_status(nvavp, 0);
+ video_initialized = 0;
+ }
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ if (audio_initialized) {
+ cancel_work_sync(&nvavp->app_notify_work);
+ nvavp_set_audio_init_status(nvavp, 0);
+ audio_initialized = 0;
+ }
+#endif
+
+ /* Video and Audio both becomes uninitialized */
+ if (video_initialized == audio_initialized) {
+ pr_debug("nvavp_uninit both channels unitialized\n");
+
+ clk_disable(nvavp->sclk);
+ clk_disable(nvavp->emc_clk);
+ disable_irq(nvavp->mbox_from_avp_pend_irq);
+ nvavp_pushbuffer_deinit(nvavp);
+ nvavp_halt_avp(nvavp);
+ }
+}
+
+static int nvavp_set_clock_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct nvavp_clientctx *clientctx = filp->private_data;
+ struct nvavp_info *nvavp = clientctx->nvavp;
+ struct clk *c;
+ struct nvavp_clock_args config;
+
+ if (copy_from_user(&config, (void __user *)arg, sizeof(struct nvavp_clock_args)))
+ return -EFAULT;
+
+ dev_dbg(&nvavp->nvhost_dev->dev, "%s: clk_id=%d, clk_rate=%u\n",
+ __func__, config.id, config.rate);
+
+ if (config.id == NVAVP_MODULE_ID_AVP)
+ nvavp->sclk_rate = config.rate;
+ else if (config.id == NVAVP_MODULE_ID_EMC)
+ nvavp->emc_clk_rate = config.rate;
+
+ c = nvavp_clk_get(nvavp, config.id);
+ if (IS_ERR_OR_NULL(c))
+ return -EINVAL;
+
+ clk_enable(c);
+ clk_set_rate(c, config.rate);
+
+ config.rate = clk_get_rate(c);
+ clk_disable(c);
+ if (copy_to_user((void __user *)arg, &config, sizeof(struct nvavp_clock_args)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int nvavp_get_clock_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct nvavp_clientctx *clientctx = filp->private_data;
+ struct nvavp_info *nvavp = clientctx->nvavp;
+ struct clk *c;
+ struct nvavp_clock_args config;
+
+ if (copy_from_user(&config, (void __user *)arg, sizeof(struct nvavp_clock_args)))
+ return -EFAULT;
+
+ c = nvavp_clk_get(nvavp, config.id);
+ if (IS_ERR_OR_NULL(c))
+ return -EINVAL;
+
+ clk_enable(c);
+ config.rate = clk_get_rate(c);
+ clk_disable(c);
+
+ if (copy_to_user((void __user *)arg, &config, sizeof(struct nvavp_clock_args)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int nvavp_get_syncpointid_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct nvavp_clientctx *clientctx = filp->private_data;
+ struct nvavp_info *nvavp = clientctx->nvavp;
+ u32 id = nvavp->syncpt_id;
+
+ if (_IOC_DIR(cmd) & _IOC_READ) {
+ if (copy_to_user((void __user *)arg, &id, sizeof(u32)))
+ return -EFAULT;
+ else
+ return 0;
+ }
+ return -EFAULT;
+}
+
+static int nvavp_set_nvmapfd_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct nvavp_clientctx *clientctx = filp->private_data;
+ struct nvavp_set_nvmap_fd_args buf;
+ struct nvmap_client *new_client;
+ int fd;
+
+ if (_IOC_DIR(cmd) & _IOC_WRITE) {
+ if (copy_from_user(&buf, (void __user *)arg, _IOC_SIZE(cmd)))
+ return -EFAULT;
+ }
+
+ fd = buf.fd;
+ new_client = nvmap_client_get_file(fd);
+ if (IS_ERR(new_client))
+ return PTR_ERR(new_client);
+
+ clientctx->nvmap = new_client;
+ return 0;
+}
+
+static int nvavp_pushbuffer_submit_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct nvavp_clientctx *clientctx = filp->private_data;
+ struct nvavp_info *nvavp = clientctx->nvavp;
+ struct nvavp_pushbuffer_submit_hdr hdr;
+ u32 *cmdbuf_data;
+ struct nvmap_handle *cmdbuf_handle = NULL;
+ struct nvmap_handle_ref *cmdbuf_dupe;
+ int ret = 0, i;
+ unsigned long phys_addr;
+ unsigned long virt_addr;
+ struct nvavp_pushbuffer_submit_hdr *user_hdr =
+ (struct nvavp_pushbuffer_submit_hdr *) arg;
+ struct nvavp_syncpt syncpt;
+
+ syncpt.id = NVSYNCPT_INVALID;
+ syncpt.value = 0;
+
+ if (_IOC_DIR(cmd) & _IOC_WRITE) {
+ if (copy_from_user(&hdr, (void __user *)arg,
+ sizeof(struct nvavp_pushbuffer_submit_hdr)))
+ return -EFAULT;
+ }
+
+ if (!hdr.cmdbuf.mem)
+ return 0;
+
+ if (copy_from_user(clientctx->relocs, (void __user *)hdr.relocs,
+ sizeof(struct nvavp_reloc) * hdr.num_relocs)) {
+ return -EFAULT;
+ }
+
+ cmdbuf_handle = nvmap_get_handle_id(clientctx->nvmap, hdr.cmdbuf.mem);
+ if (cmdbuf_handle == NULL) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "invalid cmd buffer handle %08x\n", hdr.cmdbuf.mem);
+ return -EPERM;
+ }
+
+ /* duplicate the new pushbuffer's handle into the nvavp driver's
+ * nvmap context, to ensure that the handle won't be freed as
+ * long as it is in-use by the fb driver */
+ cmdbuf_dupe = nvmap_duplicate_handle_id(nvavp->nvmap, hdr.cmdbuf.mem);
+ nvmap_handle_put(cmdbuf_handle);
+
+ if (IS_ERR(cmdbuf_dupe)) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "could not duplicate handle\n");
+ return PTR_ERR(cmdbuf_dupe);
+ }
+
+ phys_addr = nvmap_pin(nvavp->nvmap, cmdbuf_dupe);
+ if (IS_ERR((void *)phys_addr)) {
+ dev_err(&nvavp->nvhost_dev->dev, "could not pin handle\n");
+ nvmap_free(nvavp->nvmap, cmdbuf_dupe);
+ return PTR_ERR((void *)phys_addr);
+ }
+
+ virt_addr = (unsigned long)nvmap_mmap(cmdbuf_dupe);
+ if (!virt_addr) {
+ dev_err(&nvavp->nvhost_dev->dev, "cannot map cmdbuf handle\n");
+ ret = -ENOMEM;
+ goto err_cmdbuf_mmap;
+ }
+
+ cmdbuf_data = (u32 *)(virt_addr + hdr.cmdbuf.offset);
+
+ for (i = 0; i < hdr.num_relocs; i++) {
+ u32 *reloc_addr, target_phys_addr;
+
+ if (clientctx->relocs[i].cmdbuf_mem != hdr.cmdbuf.mem) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "reloc info does not match target bufferID\n");
+ ret = -EPERM;
+ goto err_reloc_info;
+ }
+
+ reloc_addr = cmdbuf_data +
+ (clientctx->relocs[i].cmdbuf_offset >> 2);
+
+ target_phys_addr = nvmap_handle_address(clientctx->nvmap,
+ clientctx->relocs[i].target);
+ target_phys_addr += clientctx->relocs[i].target_offset;
+ writel(target_phys_addr, reloc_addr);
+ }
+
+ if (hdr.syncpt) {
+ ret = nvavp_pushbuffer_update(nvavp,
+ (phys_addr + hdr.cmdbuf.offset),
+ hdr.cmdbuf.words, &syncpt,
+ (hdr.flags & NVAVP_UCODE_EXT),
+ clientctx->channel_id);
+
+ if (copy_to_user((void __user *)user_hdr->syncpt, &syncpt,
+ sizeof(struct nvavp_syncpt))) {
+ ret = -EFAULT;
+ goto err_reloc_info;
+ }
+ } else {
+ ret = nvavp_pushbuffer_update(nvavp,
+ (phys_addr + hdr.cmdbuf.offset),
+ hdr.cmdbuf.words, NULL,
+ (hdr.flags & NVAVP_UCODE_EXT),
+ clientctx->channel_id);
+ }
+
+err_reloc_info:
+ nvmap_munmap(cmdbuf_dupe, (void *)virt_addr);
+err_cmdbuf_mmap:
+ nvmap_unpin(nvavp->nvmap, cmdbuf_dupe);
+ nvmap_free(nvavp->nvmap, cmdbuf_dupe);
+ return ret;
+}
+
+static int nvavp_wake_avp_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ wmb();
+ /* wake up avp */
+ writel(0xA0000001, NVAVP_OS_OUTBOX);
+ return 0;
+}
+
+static int nvavp_force_clock_stay_on_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct nvavp_clientctx *clientctx = filp->private_data;
+ struct nvavp_info *nvavp = clientctx->nvavp;
+ struct nvavp_clock_stay_on_state_args clock;
+
+ if (copy_from_user(&clock, (void __user *)arg,
+ sizeof(struct nvavp_clock_stay_on_state_args)))
+ return -EFAULT;
+
+ dev_dbg(&nvavp->nvhost_dev->dev, "%s: state=%d\n",
+ __func__, clock.state);
+
+ if (clock.state != NVAVP_CLOCK_STAY_ON_DISABLED &&
+ clock.state != NVAVP_CLOCK_STAY_ON_ENABLED) {
+ dev_err(&nvavp->nvhost_dev->dev, "%s: invalid argument=%d\n",
+ __func__, clock.state);
+ return -EINVAL;
+ }
+
+ mutex_lock(&nvavp->open_lock);
+ if (clock.state) {
+ if (clientctx->clk_reqs++ == 0)
+ nvavp_clks_enable(nvavp);
+ } else {
+ if (--clientctx->clk_reqs == 0)
+ nvavp_clks_disable(nvavp);
+ }
+ mutex_unlock(&nvavp->open_lock);
+ return 0;
+}
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+static int nvavp_enable_audio_clocks(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct nvavp_clientctx *clientctx = filp->private_data;
+ struct nvavp_info *nvavp = clientctx->nvavp;
+ struct nvavp_clock_args config;
+
+ if (copy_from_user(&config, (void __user *)arg, sizeof(struct nvavp_clock_args)))
+ return -EFAULT;
+
+ dev_dbg(&nvavp->nvhost_dev->dev, "%s: clk_id=%d\n",
+ __func__, config.id);
+
+ if (config.id == NVAVP_MODULE_ID_VCP)
+ clk_enable(nvavp->vcp_clk);
+ else if (config.id == NVAVP_MODULE_ID_BSEA)
+ clk_enable(nvavp->bsea_clk);
+
+ return 0;
+}
+
+static int nvavp_disable_audio_clocks(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct nvavp_clientctx *clientctx = filp->private_data;
+ struct nvavp_info *nvavp = clientctx->nvavp;
+ struct nvavp_clock_args config;
+
+ if (copy_from_user(&config, (void __user *)arg, sizeof(struct nvavp_clock_args)))
+ return -EFAULT;
+
+ dev_dbg(&nvavp->nvhost_dev->dev, "%s: clk_id=%d\n",
+ __func__, config.id);
+
+ if (config.id == NVAVP_MODULE_ID_VCP)
+ clk_disable(nvavp->vcp_clk);
+ else if (config.id == NVAVP_MODULE_ID_BSEA)
+ clk_disable(nvavp->bsea_clk);
+
+ return 0;
+}
+#else
+static int nvavp_enable_audio_clocks(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ return 0;
+}
+
+static int nvavp_disable_audio_clocks(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ return 0;
+}
+#endif
+
+static int tegra_nvavp_open(struct inode *inode, struct file *filp, int channel_id)
+{
+ struct miscdevice *miscdev = filp->private_data;
+ struct nvavp_info *nvavp = dev_get_drvdata(miscdev->parent);
+ int ret = 0;
+ struct nvavp_clientctx *clientctx;
+
+ dev_dbg(&nvavp->nvhost_dev->dev, "%s: ++\n", __func__);
+
+ nonseekable_open(inode, filp);
+
+ clientctx = kzalloc(sizeof(*clientctx), GFP_KERNEL);
+ if (!clientctx)
+ return -ENOMEM;
+
+ mutex_lock(&nvavp->open_lock);
+
+ pr_debug("tegra_nvavp_open channel_id (%d)\n", channel_id);
+
+ clientctx->channel_id = channel_id;
+
+ ret = nvavp_init(nvavp, channel_id);
+
+ if (!ret)
+ nvavp->refcount++;
+
+ clientctx->nvavp = nvavp;
+
+ filp->private_data = clientctx;
+
+ mutex_unlock(&nvavp->open_lock);
+
+ return ret;
+}
+
+static int tegra_nvavp_video_open(struct inode *inode, struct file *filp)
+{
+ pr_debug("tegra_nvavp_video_open NVAVP_VIDEO_CHANNEL\n");
+ return tegra_nvavp_open(inode, filp, NVAVP_VIDEO_CHANNEL);
+}
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+static int tegra_nvavp_audio_open(struct inode *inode, struct file *filp)
+{
+ pr_debug("tegra_nvavp_audio_open NVAVP_AUDIO_CHANNEL\n");
+ return tegra_nvavp_open(inode, filp, NVAVP_AUDIO_CHANNEL);
+}
+#endif
+
+static int tegra_nvavp_release(struct inode *inode, struct file *filp)
+{
+ struct nvavp_clientctx *clientctx = filp->private_data;
+ struct nvavp_info *nvavp = clientctx->nvavp;
+ int ret = 0;
+
+ dev_dbg(&nvavp->nvhost_dev->dev, "%s: ++\n", __func__);
+
+ filp->private_data = NULL;
+
+ mutex_lock(&nvavp->open_lock);
+
+ if (!nvavp->refcount) {
+ dev_err(&nvavp->nvhost_dev->dev,
+ "releasing while in invalid state\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* if this client had any requests, drop our clk ref */
+ if (clientctx->clk_reqs)
+ nvavp_clks_disable(nvavp);
+
+ if (nvavp->refcount > 0)
+ nvavp->refcount--;
+ if (!nvavp->refcount)
+ nvavp_uninit(nvavp);
+
+out:
+ nvmap_client_put(clientctx->nvmap);
+ mutex_unlock(&nvavp->open_lock);
+ kfree(clientctx);
+ return ret;
+}
+
+static long tegra_nvavp_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = 0;
+
+ if (_IOC_TYPE(cmd) != NVAVP_IOCTL_MAGIC ||
+ _IOC_NR(cmd) < NVAVP_IOCTL_MIN_NR ||
+ _IOC_NR(cmd) > NVAVP_IOCTL_MAX_NR)
+ return -EFAULT;
+
+ switch (cmd) {
+ case NVAVP_IOCTL_SET_NVMAP_FD:
+ ret = nvavp_set_nvmapfd_ioctl(filp, cmd, arg);
+ break;
+ case NVAVP_IOCTL_GET_SYNCPOINT_ID:
+ ret = nvavp_get_syncpointid_ioctl(filp, cmd, arg);
+ break;
+ case NVAVP_IOCTL_PUSH_BUFFER_SUBMIT:
+ ret = nvavp_pushbuffer_submit_ioctl(filp, cmd, arg);
+ break;
+ case NVAVP_IOCTL_SET_CLOCK:
+ ret = nvavp_set_clock_ioctl(filp, cmd, arg);
+ break;
+ case NVAVP_IOCTL_GET_CLOCK:
+ ret = nvavp_get_clock_ioctl(filp, cmd, arg);
+ break;
+ case NVAVP_IOCTL_WAKE_AVP:
+ ret = nvavp_wake_avp_ioctl(filp, cmd, arg);
+ break;
+ case NVAVP_IOCTL_FORCE_CLOCK_STAY_ON:
+ ret = nvavp_force_clock_stay_on_ioctl(filp, cmd, arg);
+ break;
+ case NVAVP_IOCTL_ENABLE_AUDIO_CLOCKS:
+ ret = nvavp_enable_audio_clocks(filp, cmd, arg);
+ break;
+ case NVAVP_IOCTL_DISABLE_AUDIO_CLOCKS:
+ ret = nvavp_disable_audio_clocks(filp, cmd, arg);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static const struct file_operations tegra_video_nvavp_fops = {
+ .owner = THIS_MODULE,
+ .open = tegra_nvavp_video_open,
+ .release = tegra_nvavp_release,
+ .unlocked_ioctl = tegra_nvavp_ioctl,
+};
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+static const struct file_operations tegra_audio_nvavp_fops = {
+ .owner = THIS_MODULE,
+ .open = tegra_nvavp_audio_open,
+ .release = tegra_nvavp_release,
+ .unlocked_ioctl = tegra_nvavp_ioctl,
+};
+#endif
+
+static int tegra_nvavp_probe(struct nvhost_device *ndev,
+ struct nvhost_device_id *id_table)
+{
+ struct nvavp_info *nvavp;
+ int irq;
+ unsigned int heap_mask;
+ u32 iovmm_addr;
+ int ret = 0, channel_id;
+
+ irq = nvhost_get_irq_byname(ndev, "mbox_from_nvavp_pending");
+ if (irq < 0) {
+ dev_err(&ndev->dev, "invalid nvhost data\n");
+ return -EINVAL;
+ }
+
+ nvavp = kzalloc(sizeof(struct nvavp_info), GFP_KERNEL);
+ if (!nvavp) {
+ dev_err(&ndev->dev, "cannot allocate avp_info\n");
+ return -ENOMEM;
+ }
+
+ memset(nvavp, 0, sizeof(*nvavp));
+
+ nvavp->nvmap = nvmap_create_client(nvmap_dev, "nvavp_drv");
+ if (IS_ERR_OR_NULL(nvavp->nvmap)) {
+ dev_err(&ndev->dev, "cannot create nvmap client\n");
+ ret = PTR_ERR(nvavp->nvmap);
+ goto err_nvmap_create_drv_client;
+ }
+
+#if defined(CONFIG_TEGRA_AVP_KERNEL_ON_MMU) /* Tegra2 with AVP MMU */
+ heap_mask = NVMAP_HEAP_CARVEOUT_GENERIC;
+#elif defined(CONFIG_TEGRA_AVP_KERNEL_ON_SMMU) /* Tegra3 with SMMU */
+ heap_mask = NVMAP_HEAP_IOVMM;
+#else /* nvmem= carveout */
+ heap_mask = 0;
+#endif
+ switch (heap_mask) {
+ case NVMAP_HEAP_IOVMM:
+
+ iovmm_addr = 0x0ff00000;
+
+ nvavp->os_info.handle = nvmap_alloc_iovm(nvavp->nvmap, SZ_1M,
+ L1_CACHE_BYTES,
+ NVMAP_HANDLE_UNCACHEABLE,
+ iovmm_addr);
+ if (IS_ERR_OR_NULL(nvavp->os_info.handle)) {
+ dev_err(&ndev->dev,
+ "cannot create os handle\n");
+ ret = PTR_ERR(nvavp->os_info.handle);
+ goto err_nvmap_alloc;
+ }
+
+ nvavp->os_info.data = nvmap_mmap(nvavp->os_info.handle);
+ if (!nvavp->os_info.data) {
+ dev_err(&ndev->dev,
+ "cannot map os handle\n");
+ ret = -ENOMEM;
+ goto err_nvmap_mmap;
+ }
+
+ nvavp->os_info.phys =
+ nvmap_pin(nvavp->nvmap, nvavp->os_info.handle);
+ if (IS_ERR_OR_NULL((void *)nvavp->os_info.phys)) {
+ dev_err(&ndev->dev,
+ "cannot pin os handle\n");
+ ret = PTR_ERR((void *)nvavp->os_info.phys);
+ goto err_nvmap_pin;
+ }
+
+ dev_info(&ndev->dev,
+ "allocated IOVM at %lx for AVP os\n",
+ (unsigned long)nvavp->os_info.phys);
+ break;
+ case NVMAP_HEAP_CARVEOUT_GENERIC:
+ nvavp->os_info.handle = nvmap_alloc(nvavp->nvmap, SZ_1M, SZ_1M,
+ NVMAP_HANDLE_UNCACHEABLE, 0);
+ if (IS_ERR_OR_NULL(nvavp->os_info.handle)) {
+ dev_err(&ndev->dev, "cannot create AVP os handle\n");
+ ret = PTR_ERR(nvavp->os_info.handle);
+ goto err_nvmap_alloc;
+ }
+
+ nvavp->os_info.data = nvmap_mmap(nvavp->os_info.handle);
+ if (!nvavp->os_info.data) {
+ dev_err(&ndev->dev, "cannot map AVP os handle\n");
+ ret = -ENOMEM;
+ goto err_nvmap_mmap;
+ }
+
+ nvavp->os_info.phys = nvmap_pin(nvavp->nvmap,
+ nvavp->os_info.handle);
+ if (IS_ERR_OR_NULL((void *)nvavp->os_info.phys)) {
+ dev_err(&ndev->dev, "cannot pin AVP os handle\n");
+ ret = PTR_ERR((void *)nvavp->os_info.phys);
+ goto err_nvmap_pin;
+ }
+
+ dev_info(&ndev->dev,
+ "allocated carveout memory at %lx for AVP os\n",
+ (unsigned long)nvavp->os_info.phys);
+ break;
+ default:
+ dev_err(&ndev->dev, "invalid/non-supported heap for AVP os\n");
+ ret = -EINVAL;
+ goto err_get_syncpt;
+ }
+
+ nvavp->mbox_from_avp_pend_irq = irq;
+ mutex_init(&nvavp->open_lock);
+
+ for (channel_id = 0; channel_id < NVAVP_NUM_CHANNELS; channel_id++)
+ mutex_init(&nvavp->channel_info[channel_id].pushbuffer_lock);
+
+ /* TODO DO NOT USE NVAVP DEVICE */
+ nvavp->cop_clk = clk_get(&ndev->dev, "cop");
+ if (IS_ERR(nvavp->cop_clk)) {
+ dev_err(&ndev->dev, "cannot get cop clock\n");
+ ret = -ENOENT;
+ goto err_get_cop_clk;
+ }
+
+ nvavp->vde_clk = clk_get(&ndev->dev, "vde");
+ if (IS_ERR(nvavp->vde_clk)) {
+ dev_err(&ndev->dev, "cannot get vde clock\n");
+ ret = -ENOENT;
+ goto err_get_vde_clk;
+ }
+
+ nvavp->bsev_clk = clk_get(&ndev->dev, "bsev");
+ if (IS_ERR(nvavp->bsev_clk)) {
+ dev_err(&ndev->dev, "cannot get bsev clock\n");
+ ret = -ENOENT;
+ goto err_get_bsev_clk;
+ }
+
+ nvavp->sclk = clk_get(&ndev->dev, "sclk");
+ if (IS_ERR(nvavp->sclk)) {
+ dev_err(&ndev->dev, "cannot get avp.sclk clock\n");
+ ret = -ENOENT;
+ goto err_get_sclk;
+ }
+
+ nvavp->emc_clk = clk_get(&ndev->dev, "emc");
+ if (IS_ERR(nvavp->emc_clk)) {
+ dev_err(&ndev->dev, "cannot get emc clock\n");
+ ret = -ENOENT;
+ goto err_get_emc_clk;
+ }
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ nvavp->bsea_clk = clk_get(&ndev->dev, "bsea");
+ if (IS_ERR(nvavp->bsea_clk)) {
+ dev_err(&ndev->dev, "cannot get bsea clock\n");
+ ret = -ENOENT;
+ goto err_get_bsea_clk;
+ }
+
+ nvavp->vcp_clk = clk_get(&ndev->dev, "vcp");
+ if (IS_ERR(nvavp->vcp_clk)) {
+ dev_err(&ndev->dev, "cannot get vcp clock\n");
+ ret = -ENOENT;
+ goto err_get_vcp_clk;
+ }
+#endif
+
+ nvavp->clk_enabled = 0;
+ nvavp_halt_avp(nvavp);
+
+ INIT_WORK(&nvavp->clock_disable_work, clock_disable_handler);
+
+ nvavp->video_misc_dev.minor = MISC_DYNAMIC_MINOR;
+ nvavp->video_misc_dev.name = "tegra_avpchannel";
+ nvavp->video_misc_dev.fops = &tegra_video_nvavp_fops;
+ nvavp->video_misc_dev.mode = S_IRWXUGO;
+ nvavp->video_misc_dev.parent = &ndev->dev;
+
+ ret = misc_register(&nvavp->video_misc_dev);
+ if (ret) {
+ dev_err(&ndev->dev, "unable to register misc device!\n");
+ goto err_misc_reg;
+ }
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ INIT_WORK(&nvavp->app_notify_work, app_notify_handler);
+ nvavp->audio_misc_dev.minor = MISC_DYNAMIC_MINOR;
+ nvavp->audio_misc_dev.name = "tegra_audio_avpchannel";
+ nvavp->audio_misc_dev.fops = &tegra_audio_nvavp_fops;
+ nvavp->audio_misc_dev.mode = S_IRWXUGO;
+ nvavp->audio_misc_dev.parent = &ndev->dev;
+
+ ret = misc_register(&nvavp->audio_misc_dev);
+ if (ret) {
+ dev_err(&ndev->dev, "unable to register misc device!\n");
+ goto err_audio_misc_reg;
+ }
+#endif
+
+ ret = request_irq(irq, nvavp_mbox_pending_isr, 0,
+ TEGRA_NVAVP_NAME, nvavp);
+ if (ret) {
+ dev_err(&ndev->dev, "cannot register irq handler\n");
+ goto err_req_irq_pend;
+ }
+ disable_irq(nvavp->mbox_from_avp_pend_irq);
+
+ nvhost_set_drvdata(ndev, nvavp);
+ nvavp->nvhost_dev = ndev;
+
+ return 0;
+
+err_req_irq_pend:
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ misc_deregister(&nvavp->audio_misc_dev);
+err_audio_misc_reg:
+#endif
+ misc_deregister(&nvavp->video_misc_dev);
+err_misc_reg:
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ clk_put(nvavp->vcp_clk);
+err_get_vcp_clk:
+ clk_put(nvavp->bsea_clk);
+err_get_bsea_clk:
+#endif
+ clk_put(nvavp->emc_clk);
+err_get_emc_clk:
+ clk_put(nvavp->sclk);
+err_get_sclk:
+ clk_put(nvavp->bsev_clk);
+err_get_bsev_clk:
+ clk_put(nvavp->vde_clk);
+err_get_vde_clk:
+ clk_put(nvavp->cop_clk);
+err_get_cop_clk:
+ nvmap_unpin(nvavp->nvmap, nvavp->os_info.handle);
+err_nvmap_pin:
+ nvmap_munmap(nvavp->os_info.handle, nvavp->os_info.data);
+err_nvmap_mmap:
+#if defined(CONFIG_TEGRA_AVP_KERNEL_ON_MMU)
+ nvmap_free(nvavp->nvmap, nvavp->os_info.handle);
+#elif defined(CONFIG_TEGRA_AVP_KERNEL_ON_SMMU)
+ nvmap_free_iovm(nvavp->nvmap, nvavp->os_info.handle);
+#endif
+err_nvmap_alloc:
+ nvmap_client_put(nvavp->nvmap);
+err_nvmap_create_drv_client:
+err_get_syncpt:
+ kfree(nvavp);
+ return ret;
+}
+
+static int tegra_nvavp_remove(struct nvhost_device *ndev)
+{
+ struct nvavp_info *nvavp = nvhost_get_drvdata(ndev);
+
+ if (!nvavp)
+ return 0;
+
+ mutex_lock(&nvavp->open_lock);
+ if (nvavp->refcount) {
+ mutex_unlock(&nvavp->open_lock);
+ return -EBUSY;
+ }
+ mutex_unlock(&nvavp->open_lock);
+
+ nvavp_unload_ucode(nvavp);
+ nvavp_unload_os(nvavp);
+
+ misc_deregister(&nvavp->video_misc_dev);
+
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ misc_deregister(&nvavp->audio_misc_dev);
+ clk_put(nvavp->vcp_clk);
+ clk_put(nvavp->bsea_clk);
+#endif
+ clk_put(nvavp->bsev_clk);
+ clk_put(nvavp->vde_clk);
+ clk_put(nvavp->cop_clk);
+
+ clk_put(nvavp->emc_clk);
+ clk_put(nvavp->sclk);
+
+ nvmap_client_put(nvavp->nvmap);
+
+ kfree(nvavp);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int tegra_nvavp_suspend(struct nvhost_device *ndev, pm_message_t state)
+{
+ struct nvavp_info *nvavp = nvhost_get_drvdata(ndev);
+ int ret = 0;
+
+ mutex_lock(&nvavp->open_lock);
+
+ if (nvavp->refcount) {
+ if (!nvavp->clk_enabled) {
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ if (nvavp_check_idle(nvavp, NVAVP_AUDIO_CHANNEL))
+ nvavp_uninit(nvavp);
+ else
+ ret = -EBUSY;
+#else
+ nvavp_uninit(nvavp);
+#endif
+ }
+ else {
+ ret = -EBUSY;
+ }
+ }
+
+ mutex_unlock(&nvavp->open_lock);
+ return ret;
+}
+
+static int tegra_nvavp_resume(struct nvhost_device *ndev)
+{
+ struct nvavp_info *nvavp = nvhost_get_drvdata(ndev);
+
+ mutex_lock(&nvavp->open_lock);
+
+ if (nvavp->refcount) {
+ nvavp_init(nvavp, NVAVP_VIDEO_CHANNEL);
+#if defined(CONFIG_TEGRA_NVAVP_AUDIO)
+ nvavp_init(nvavp, NVAVP_AUDIO_CHANNEL);
+#endif
+ }
+ mutex_unlock(&nvavp->open_lock);
+
+ return 0;
+}
+#endif
+
+static struct nvhost_driver tegra_nvavp_driver = {
+ .driver = {
+ .name = TEGRA_NVAVP_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = tegra_nvavp_probe,
+ .remove = tegra_nvavp_remove,
+#ifdef CONFIG_PM
+ .suspend = tegra_nvavp_suspend,
+ .resume = tegra_nvavp_resume,
+#endif
+};
+
+static int __init tegra_nvavp_init(void)
+{
+ return nvhost_driver_register(&tegra_nvavp_driver);
+}
+
+static void __exit tegra_nvavp_exit(void)
+{
+ nvhost_driver_unregister(&tegra_nvavp_driver);
+}
+
+module_init(tegra_nvavp_init);
+module_exit(tegra_nvavp_exit);
+
+MODULE_AUTHOR("NVIDIA");
+MODULE_DESCRIPTION("Channel based AVP driver for Tegra");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("Dual BSD/GPL");