diff options
author | Gajanan Bhat <gbhat@nvidia.com> | 2011-06-23 19:09:21 -0700 |
---|---|---|
committer | Varun Colbert <vcolbert@nvidia.com> | 2011-07-21 15:11:52 -0700 |
commit | 4f6dcc0453cddac9e08bfc2a9ee468e62edf6774 (patch) | |
tree | 6147d22df172c48d19cc8ffcc502ab387a906236 /drivers | |
parent | 89f82d3c1d06f1952b281516edad965f16297121 (diff) |
media: tegra: avp: Add channel based AVP driver
New AVP driver based on channel model. The AVP
acts as s/w host1x channel and has a syncpoint
allocated to synchronize audio/video operation
submitted by the host. The driver is responsible
for loading the AVP kernel and initializing s/w
channel.
Change-Id: I20b68fc3cbb88b7c95542bae0a1acf5edc52c715
Reviewed-on: http://git-master/r/37420
Reviewed-by: Varun Colbert <vcolbert@nvidia.com>
Tested-by: Varun Colbert <vcolbert@nvidia.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/media/video/tegra/Kconfig | 1 | ||||
-rw-r--r-- | drivers/media/video/tegra/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/video/tegra/nvavp/Kconfig | 10 | ||||
-rw-r--r-- | drivers/media/video/tegra/nvavp/Makefile | 2 | ||||
-rw-r--r-- | drivers/media/video/tegra/nvavp/nvavp_dev.c | 994 | ||||
-rw-r--r-- | drivers/media/video/tegra/nvavp/nvavp_os.h | 77 |
6 files changed, 1085 insertions, 0 deletions
diff --git a/drivers/media/video/tegra/Kconfig b/drivers/media/video/tegra/Kconfig index 33117ec4d256..63a36393257f 100644 --- a/drivers/media/video/tegra/Kconfig +++ b/drivers/media/video/tegra/Kconfig @@ -1,5 +1,6 @@ source "drivers/media/video/tegra/avp/Kconfig" source "drivers/media/video/tegra/mediaserver/Kconfig" +source "drivers/media/video/tegra/nvavp/Kconfig" config TEGRA_CAMERA bool "Enable support for tegra camera/isp hardware" diff --git a/drivers/media/video/tegra/Makefile b/drivers/media/video/tegra/Makefile index d97cf3059a9f..7268b8fba77d 100644 --- a/drivers/media/video/tegra/Makefile +++ b/drivers/media/video/tegra/Makefile @@ -3,6 +3,7 @@ # obj-y += avp/ obj-$(CONFIG_TEGRA_MEDIASERVER) += mediaserver/ +obj-$(CONFIG_TEGRA_NVAVP) += nvavp/ 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/nvavp/Kconfig b/drivers/media/video/tegra/nvavp/Kconfig new file mode 100644 index 000000000000..66d7c860cdcf --- /dev/null +++ b/drivers/media/video/tegra/nvavp/Kconfig @@ -0,0 +1,10 @@ +config TEGRA_NVAVP + bool "Enable support for Tegra NVAVP driver" + depends on ARCH_TEGRA + default n + help + Enables support for the push-buffer mechanism based driver for the Tegra + multimedia framework. Exports the Tegra nvavp interface on device node + /dev/tegra_avpchannel. + + If unsure, say N diff --git a/drivers/media/video/tegra/nvavp/Makefile b/drivers/media/video/tegra/nvavp/Makefile new file mode 100644 index 000000000000..8b16cb4b2c93 --- /dev/null +++ b/drivers/media/video/tegra/nvavp/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_TEGRA_NVAVP) += nvavp_dev.o +obj-$(CONFIG_TEGRA_AVP_KERNEL_ON_MMU) += ../avp/headavp.o 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..05f413c0d6ad --- /dev/null +++ b/drivers/media/video/tegra/nvavp/nvavp_dev.c @@ -0,0 +1,994 @@ +/* + * drivers/media/video/tegra/nvavp/nvavp_dev.c + * + * Copyright (C) 2011 NVIDIA Corp. + * + * 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/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 <mach/clk.h> +#include <mach/io.h> +#include <mach/iomap.h> +#include <mach/legacy_irq.h> +#include <mach/nvmap.h> +#include <mach/nvhost.h> + +#include "../../../../video/tegra/nvmap/nvmap.h" +#include "../../../../video/tegra/host/t20/syncpt_t20.h" +#include "../../../../video/tegra/host/dev.h" +#if defined(CONFIG_TEGRA_AVP_KERNEL_ON_MMU) +#include "../avp/headavp.h" +#endif +#include "nvavp_os.h" + +#define TEGRA_NVAVP_NAME "tegra-avp" + +#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) + +struct nvavp_info { + struct clk *bsev_clk; + struct clk *vde_clk; + struct clk *cop_clk; + + int mbox_from_avp_pend_irq; + + struct mutex open_lock; + int refcount; + + /* os information */ + struct nvavp_os_info os_info; + + /* client for driver allocations, persistent */ + struct nvmap_client *nvmap; + + 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 nvhost_syncpt *nvhost_syncpt; + u32 syncpt_id; + u32 syncpt_value; + + struct nvhost_device *nvhost_dev; + struct miscdevice misc_dev; +}; + +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; +}; + +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; + + writel(0x00000000, NVAVP_OS_INBOX); + + 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_VDE_SHUTDOWN) { + /* TODO: We may want to put some sort of + * synchronization in place (resource semaphores ?) + * so that AVP can set VDE clocks without + * involving RM (as this is likely to occur often) + */ + dev_info(&nvavp->nvhost_dev->dev, "shutting down VDE...\n"); + } + 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"); + + 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); + + 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) +{ + tegra_periph_reset_assert(nvavp->bsev_clk); + clk_disable(nvavp->bsev_clk); + tegra_periph_reset_assert(nvavp->vde_clk); + clk_disable(nvavp->vde_clk); +} + +static int nvavp_reset_vde(struct nvavp_info *nvavp) +{ + clk_enable(nvavp->bsev_clk); + tegra_periph_reset_assert(nvavp->bsev_clk); + udelay(2); + tegra_periph_reset_deassert(nvavp->bsev_clk); + + clk_enable(nvavp->vde_clk); + tegra_periph_reset_assert(nvavp->vde_clk); + udelay(2); + tegra_periph_reset_deassert(nvavp->vde_clk); + return 0; +} + +static int nvavp_pushbuffer_alloc(struct nvavp_info *nvavp) +{ + int ret = 0; + + nvavp->pushbuf_handle = nvmap_alloc(nvavp->nvmap, NVAVP_PUSHBUFFER_SIZE, + SZ_1M, NVMAP_HANDLE_UNCACHEABLE); + if (IS_ERR(nvavp->pushbuf_handle)) { + dev_err(&nvavp->nvhost_dev->dev, + "cannot create pushbuffer handle\n"); + ret = PTR_ERR(nvavp->pushbuf_handle); + goto err_pushbuf_alloc; + } + nvavp->pushbuf_data = (u8 *)nvmap_mmap(nvavp->pushbuf_handle); + if (!nvavp->pushbuf_data) { + dev_err(&nvavp->nvhost_dev->dev, + "cannot map pushbuffer handle\n"); + ret = -ENOMEM; + goto err_pushbuf_mmap; + } + nvavp->pushbuf_phys = nvmap_pin(nvavp->nvmap, nvavp->pushbuf_handle); + if (IS_ERR((void *)nvavp->pushbuf_phys)) { + dev_err(&nvavp->nvhost_dev->dev, + "cannot pin pushbuffer handle\n"); + ret = PTR_ERR((void *)nvavp->pushbuf_phys); + goto err_pushbuf_pin; + } + + memset(nvavp->pushbuf_data, 0, NVAVP_PUSHBUFFER_SIZE); + + return 0; + +err_pushbuf_pin: + nvmap_munmap(nvavp->pushbuf_handle, nvavp->pushbuf_data); +err_pushbuf_mmap: + nvmap_free(nvavp->nvmap, nvavp->pushbuf_handle); +err_pushbuf_alloc: + return ret; +} + +static void nvavp_pushbuffer_free(struct nvavp_info *nvavp) +{ + nvmap_unpin(nvavp->nvmap, nvavp->pushbuf_handle); + nvmap_munmap(nvavp->pushbuf_handle, nvavp->pushbuf_data); + nvmap_free(nvavp->nvmap, nvavp->pushbuf_handle); +} + +static int nvavp_pushbuffer_init(struct nvavp_info *nvavp) +{ + void *ptr; + struct nvavp_os_info *os = &nvavp->os_info; + struct nv_e276_control *control; + u32 temp; + int ret; + + ret = nvavp_pushbuffer_alloc(nvavp); + if (ret) { + dev_err(&nvavp->nvhost_dev->dev, + "unable to alloc pushbuffer\n"); + return ret; + } + + ptr = os->data; + ptr += os->control_offset; + nvavp->os_control = (struct nv_e276_control *)ptr; + + control = nvavp->os_control; + + /* init get and put pointers */ + writel(0x0, &control->put); + writel(0x0, &control->get); + /* enable host clock control and disable iram clock gating */ + writel(0x1, &control->idle_clk_enable); + writel(0x0, &control->iram_clk_gating); + + /* init dma start and end pointers */ + writel(nvavp->pushbuf_phys, &control->dma_start); + writel((nvavp->pushbuf_phys + NVAVP_PUSHBUFFER_SIZE), + &control->dma_end); + + writel(0x00, &nvavp->pushbuf_index); + temp = NVAVP_PUSHBUFFER_SIZE - NVAVP_PUSHBUFFER_MIN_UPDATE_SPACE; + writel(temp, &nvavp->pushbuf_fence); + + nvavp->syncpt_id = NVSYNCPT_AVP_0; + nvavp->syncpt_value = nvhost_syncpt_read(nvavp->nvhost_syncpt, + 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) +{ + struct nv_e276_control *control = nvavp->os_control; + u32 gather_cmd, sync = 0; + u32 wordcount = 0; + u32 index, value = -1; + + mutex_lock(&nvavp->pushbuffer_lock); + + /* check for pushbuffer wrapping */ + if (nvavp->pushbuf_index >= nvavp->pushbuf_fence) + nvavp->pushbuf_index = 0; + + 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 + nvavp->pushbuf_index; + writel(gather_cmd, (nvavp->pushbuf_data + index)); + wordcount += sizeof(u32); + + index = wordcount + nvavp->pushbuf_index; + writel(phys_addr, (nvavp->pushbuf_data + index)); + wordcount += sizeof(u32); + + if (syncpt) { + index = wordcount + nvavp->pushbuf_index; + writel(sync, (nvavp->pushbuf_data + index)); + wordcount += sizeof(u32); + } + /* update put pointer */ + nvavp->pushbuf_index = (nvavp->pushbuf_index + wordcount) & + (NVAVP_PUSHBUFFER_SIZE - 1); + writel(nvavp->pushbuf_index, &control->put); + wmb(); + + /* wake up avp */ + writel(0xA0000001, NVAVP_OS_OUTBOX); + + /* Fill out fence struct */ + if (syncpt) { + syncpt->id = nvavp->syncpt_id; + syncpt->value = value; + } + + mutex_unlock(&nvavp->pushbuffer_lock); + + return 0; +} + +static int nvavp_load_os(struct nvavp_os_info *os, struct device *dev, + struct nvmap_client *nvmap, void *fw_data) +{ + void *ptr = (void *)fw_data; + u32 size; + + if (strncmp((const char *)ptr, "NVAVP-OS", 8)) { + dev_info(dev, "os hdr string mismatch\n"); + return -EINVAL; + } + + ptr += 8; + os->entry_offset = *((u32 *)ptr); + ptr += sizeof(u32); + os->control_offset = *((u32 *)ptr); + ptr += sizeof(u32); + os->debug_offset = *((u32 *)ptr); + ptr += sizeof(u32); + + size = *((u32 *)ptr); ptr += sizeof(u32); + + memcpy(os->data, ptr, size); + memset(os->data + size, 0, SZ_1M - size); + + dev_info(dev, "entry=%08x control=%08x debug=%08x size=%d\n", + os->entry_offset, os->control_offset, os->debug_offset, size); + + os->reset_addr = os->phys + os->entry_offset; + + dev_info(dev, "AVP os at vaddr=%p paddr=%lx reset_addr=%p\n", + os->data, (unsigned long)(os->phys), (void *)os->reset_addr); + + return 0; +} + +static int nvavp_init(struct nvavp_info *nvavp) +{ + const struct firmware *nvavp_os_fw; + char fw_os_file[32]; + int ret = 0; + +#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 != 0xe0000000 + && nvavp->os_info.phys != 0x00001000); + 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 = request_firmware(&nvavp_os_fw, fw_os_file, + nvavp->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); + + ret = nvavp_load_os(&nvavp->os_info, &nvavp->nvhost_dev->dev, + nvavp->nvmap, (void *)nvavp_os_fw->data); + if (ret) { + dev_err(&nvavp->nvhost_dev->dev, + "unable to load os firmware '%s'\n", fw_os_file); + goto err_req_fw; + } + + ret = nvavp_pushbuffer_init(nvavp); + if (ret) { + dev_err(&nvavp->nvhost_dev->dev, + "unable to init pushbuffer\n"); + goto err_req_fw; + } + + tegra_init_legacy_irq_cop(); + + nvavp_reset_vde(nvavp); + nvavp_reset_avp(nvavp, nvavp->os_info.reset_addr); + enable_irq(nvavp->mbox_from_avp_pend_irq); + +err_req_fw: + release_firmware(nvavp_os_fw); + return ret; +} + +static void nvavp_uninit(struct nvavp_info *nvavp) +{ + disable_irq(nvavp->mbox_from_avp_pend_irq); + nvavp_pushbuffer_deinit(nvavp); + + nvavp_halt_vde(nvavp); + nvavp_halt_avp(nvavp); +} + +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 >> 2); + writel(target_phys_addr, reloc_addr); + } + + if (hdr.syncpt) { + ret = nvavp_pushbuffer_update(nvavp, + (phys_addr + hdr.cmdbuf.offset), + hdr.cmdbuf.words, &syncpt); + + 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); + } + +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 tegra_nvavp_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *miscdev = filp->private_data; + struct nvavp_info *nvavp = dev_get_drvdata(miscdev->parent); + int ret = 0; + struct nvavp_clientctx *clientctx; + + nonseekable_open(inode, filp); + + clientctx = kzalloc(sizeof(*clientctx), GFP_KERNEL); + if (!clientctx) + return -ENOMEM; + + mutex_lock(&nvavp->open_lock); + + if (!nvavp->refcount) + ret = nvavp_init(nvavp); + + if (!ret) + nvavp->refcount++; + + clientctx->nvmap = nvavp->nvmap; + clientctx->nvavp = nvavp; + + filp->private_data = clientctx; + + mutex_unlock(&nvavp->open_lock); + + return ret; +} + +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; + + 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 (nvavp->refcount > 0) + nvavp->refcount--; + if (!nvavp->refcount) + nvavp_uninit(nvavp); + +out: + 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_PUSH_BUFFER_SUBMIT: + ret = nvavp_pushbuffer_submit_ioctl(filp, cmd, arg); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static struct nvmap_client *avp_early_nvmap_drv; +static struct nvmap_handle_ref *avp_early_kernel_handle; +static void *avp_early_kernel_data; +static phys_addr_t avp_early_kernel_phys; + +void avp_early_init(void) +{ + int ret; + + avp_early_nvmap_drv = nvmap_create_client(nvmap_dev, "avp_early"); + if (IS_ERR_OR_NULL(avp_early_nvmap_drv)) + pr_crit("%s: nvmap_create_client error\n", __func__); + + avp_early_kernel_handle = + nvmap_create_handle(avp_early_nvmap_drv, SZ_1M); + if (IS_ERR_OR_NULL(avp_early_kernel_handle)) + pr_crit("%s: nvmap_create_handle error\n", __func__); + + ret = nvmap_alloc_handle_id(avp_early_nvmap_drv, + nvmap_ref_to_id(avp_early_kernel_handle), + NVMAP_HEAP_IOVMM, PAGE_SIZE, + NVMAP_HANDLE_WRITE_COMBINE); + if (ret) + pr_crit("%s: nvmap_alloc_handle_id error\n", __func__); + + avp_early_kernel_data = nvmap_mmap(avp_early_kernel_handle); + if (!avp_early_kernel_data) + pr_crit("%s: nvmap_mmap error\n", __func__); + + avp_early_kernel_phys = + nvmap_pin(avp_early_nvmap_drv, avp_early_kernel_handle); + if (IS_ERR_OR_NULL((void *)avp_early_kernel_phys)) + pr_crit("%s: nvmap_pin error\n", __func__); + + pr_info("%s: allocated memory at %x for AVP kernel\n", + __func__, avp_early_kernel_phys); +} + +static const struct file_operations tegra_nvavp_fops = { + .owner = THIS_MODULE, + .open = tegra_nvavp_open, + .release = tegra_nvavp_release, + .unlocked_ioctl = tegra_nvavp_ioctl, +}; + +static int tegra_nvavp_probe(struct nvhost_device *ndev) +{ + struct nvavp_info *nvavp; + int irq; + unsigned int heap_mask; + int ret = 0; + + irq = nvhost_get_irq_byname(ndev, "mbox_from_avp_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->nvhost_syncpt = &ndev->host->syncpt; + if (!nvavp->nvhost_syncpt) { + dev_err(&ndev->dev, "cannot get syncpt handle\n"); + ret = -ENOENT; + goto err_get_syncpt; + } + +#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: + nvavp->nvmap = avp_early_nvmap_drv; + nvavp->os_info.handle = avp_early_kernel_handle; + nvavp->os_info.data = avp_early_kernel_data; + nvavp->os_info.phys = avp_early_kernel_phys; + break; + case NVMAP_HEAP_CARVEOUT_GENERIC: + nvavp->nvmap = nvmap_create_client(nvmap_dev, "nvavp_drv"); + if (IS_ERR(nvavp->nvmap)) { + dev_err(&ndev->dev, "cannot create drv nvmap client\n"); + ret = PTR_ERR(nvavp->nvmap); + goto err_nvmap_create_drv_client; + } + + nvavp->os_info.handle = nvmap_alloc(nvavp->nvmap, SZ_1M, SZ_1M, + NVMAP_HANDLE_WRITE_COMBINE); + 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); + mutex_init(&nvavp->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_halt_avp(nvavp); + + nvavp->misc_dev.minor = MISC_DYNAMIC_MINOR; + nvavp->misc_dev.name = "tegra_avpchannel"; + nvavp->misc_dev.fops = &tegra_nvavp_fops; + nvavp->misc_dev.mode = S_IRWXUGO; + nvavp->misc_dev.parent = &ndev->dev; + + ret = misc_register(&nvavp->misc_dev); + if (ret) { + dev_err(&ndev->dev, "unable to register misc device!\n"); + goto err_misc_reg; + } + + 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: + misc_deregister(&nvavp->misc_dev); +err_misc_reg: + 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: + nvmap_free(nvavp->nvmap, nvavp->os_info.handle); +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); + + misc_deregister(&nvavp->misc_dev); + + clk_put(nvavp->vde_clk); + clk_put(nvavp->cop_clk); + + 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) +{ + return 0; +} + +static int tegra_nvavp_resume(struct nvhost_device *ndev) +{ + 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"); diff --git a/drivers/media/video/tegra/nvavp/nvavp_os.h b/drivers/media/video/tegra/nvavp/nvavp_os.h new file mode 100644 index 000000000000..a1ededd80f27 --- /dev/null +++ b/drivers/media/video/tegra/nvavp/nvavp_os.h @@ -0,0 +1,77 @@ +/* + * drivers/media/video/tegra/nvavp/nvavp_os.h + * + * Copyright (C) 2011 NVIDIA Corp. + * + * 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. + */ + +#ifndef __MEDIA_VIDEO_TEGRA_NVAVP_OS_H +#define __MEDIA_VIDEO_TEGRA_NVAVP_OS_H + +#include <linux/types.h> + +#include "../../../../video/tegra/nvmap/nvmap.h" + +#define NVE2_AVP (0x0000E276) + +struct nv_e276_control { + u32 reserved00[5]; + u32 dma_start; + u32 reserved01[2]; + u32 dma_end; + u32 reserved02[7]; + u32 put; + u32 reserved03[15]; + u32 get; + u32 reserved04[13]; + u32 idle_clk_enable; + u32 iram_clk_gating; + u32 idle; + u32 outbox_data; + u32 app_intr_enable; + u32 app_start_time; + u32 app_in_iram; + u32 iram_ucode_addr; + u32 iram_ucode_size; + u32 dbg_state[57]; + u32 os_method_data[16]; + u32 app_method_data[128]; +}; + +#define NVE26E_HOST1X_INCR_SYNCPT (0x00000000) +#define NVE26E_HOST1X_INCR_SYNCPT_COND_OP_DONE (0x00000001) + +#define NVE26E_CH_OPCODE_IMM(addr, value) \ + /* op, addr, count */ \ + ((4UL << 28) | ((addr) << 16) | (value)) + +#define NVE26E_CH_OPCODE_GATHER(off, ins, type, cnt) \ + /* op, offset, insert, type, count */ \ + ((6UL << 28) | ((off) << 16) | ((ins) << 15) | ((type) << 14) | cnt) + + +/* Interrupt codes through inbox/outbox data codes (cpu->avp or avp->cpu) */ +#define NVE276_OS_INTERRUPT_NOP (0x00000000) /* wake up avp */ +#define NVE276_OS_INTERRUPT_TIMEOUT (0x00000001) +#define NVE276_OS_INTERRUPT_SEMAPHORE_AWAKEN (0x00000002) +#define NVE276_OS_INTERRUPT_EXECUTE_AWAKEN (0x00000004) +#define NVE276_OS_INTERRUPT_DEBUG_STRING (0x00000008) +#define NVE276_OS_INTERRUPT_VDE_SHUTDOWN (0x00000010) +#define NVE276_OS_INTERRUPT_AVP_BREAKPOINT (0x00800000) +#define NVE276_OS_INTERRUPT_AVP_FATAL_ERROR (0x01000000) + +struct nvavp_os_info { + u32 entry_offset; + u32 control_offset; + u32 debug_offset; + + struct nvmap_handle_ref *handle; + void *data; + phys_addr_t phys; + phys_addr_t reset_addr; +}; + +#endif /* __MEDIA_VIDEO_TEGRA_NVAVP_OS_H */ |