summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/nv/nvrm/core/common/nvrm_moduleloader.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-tegra/nv/nvrm/core/common/nvrm_moduleloader.c')
-rw-r--r--arch/arm/mach-tegra/nv/nvrm/core/common/nvrm_moduleloader.c811
1 files changed, 811 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/nv/nvrm/core/common/nvrm_moduleloader.c b/arch/arm/mach-tegra/nv/nvrm/core/common/nvrm_moduleloader.c
new file mode 100644
index 000000000000..a4d47cd1748b
--- /dev/null
+++ b/arch/arm/mach-tegra/nv/nvrm/core/common/nvrm_moduleloader.c
@@ -0,0 +1,811 @@
+/*
+ * arch/arm/mach-tegra/nvrm/core/common/nvrm_moduleloader.c
+ *
+ * AVP firmware module loader
+ *
+ * Copyright (c) 2010, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#define NV_ENABLE_DEBUG_PRINTS 0
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/device.h>
+#include <linux/string.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/firmware.h>
+#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <asm/cacheflush.h>
+#include <asm/io.h>
+#include <mach/nvmap.h>
+
+#include "nvcommon.h"
+#include "nvassert.h"
+#include "nvos.h"
+#include "nvutil.h"
+#include "nvrm_hardware_access.h"
+#include "nvrm_message.h"
+#include "nvrm_rpc.h"
+#include "nvrm_moduleloader.h"
+#include "nvrm_moduleloader_private.h"
+#include "nvrm_graphics_private.h"
+#include "nvrm_structure.h"
+#include "nvfw.h"
+#include "ap15/arflow_ctlr.h"
+#include "ap15/arevp.h"
+#include "mach/io.h"
+#include "mach/iomap.h"
+#include "headavp.h"
+
+#define DEVICE_NAME "nvfw"
+
+#define _TEGRA_AVP_RESET_VECTOR_ADDR \
+ (IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE) + EVP_COP_RESET_VECTOR_0)
+
+static const struct firmware *s_FwEntry;
+static NvRmRPCHandle s_RPCHandle = NULL;
+
+static struct nvmap_handle_ref *s_KernelImage = NULL;
+struct nvmap_client *s_AvpClient = NULL;
+
+static NvError SendMsgDetachModule(NvRmLibraryHandle hLibHandle);
+static NvError SendMsgAttachModule(
+ NvRmLibraryHandle hLibHandle,
+ void* pArgs,
+ NvU32 loadAddress,
+ NvU32 fileSize,
+ NvBool greedy,
+ NvU32 sizeOfArgs);
+NvU32 NvRmModuleGetChipId(NvRmDeviceHandle hDevice);
+NvError NvRmPrivInitModuleLoaderRPC(NvRmDeviceHandle hDevice);
+void NvRmPrivDeInitModuleLoaderRPC(void);
+static NvError NvRmPrivInitAvp(NvRmDeviceHandle hDevice);
+
+#define AVP_KERNEL_SIZE_MAX SZ_1M
+
+#define ADD_MASK 0x00000001
+#define SUB_MASK 0xFFFFFFFD
+
+static int nvfw_open(struct inode *inode, struct file *file);
+static int nvfw_close(struct inode *inode, struct file *file);
+static long nvfw_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
+static ssize_t nvfw_write(struct file *, const char __user *, size_t, loff_t *);
+
+static NvError NvRmPrivInitAvp(NvRmDeviceHandle hRm);
+
+static const struct file_operations nvfw_fops =
+{
+ .owner = THIS_MODULE,
+ .open = nvfw_open,
+ .release = nvfw_close,
+ .write = nvfw_write,
+ .unlocked_ioctl = nvfw_ioctl,
+};
+
+static struct miscdevice nvfw_dev =
+{
+ .name = DEVICE_NAME,
+ .fops = &nvfw_fops,
+ .minor = MISC_DYNAMIC_MINOR,
+};
+
+// FIXME: This function is just for debugging.
+ssize_t nvfw_write(struct file *file, const char __user *buff, size_t count, loff_t *offp)
+{
+ NvRmDeviceHandle hRmDevice;
+ NvRmLibraryHandle hRmLibHandle;
+ char filename[100];
+ int error;
+
+ error = copy_from_user(filename, buff, count);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+ filename[count] = 0;
+ error = NvRmOpen( &hRmDevice, 0 );
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ error = NvRmLoadLibrary(hRmDevice, filename, NULL, 0, &hRmLibHandle);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ return count;
+}
+
+int nvfw_open(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+int nvfw_close(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static int nvfw_ioctl_load_library(struct file *filp, void __user *arg)
+{
+ struct nvfw_load_handle op;
+ NvRmDeviceHandle hRmDevice;
+ NvRmLibraryHandle hRmLibHandle;
+ char *filename = NULL;
+ void *args = NULL;
+ int error;
+
+ error = copy_from_user(&op, arg, sizeof(op));
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ filename = NvOsAlloc(op.length + 1);
+ error = copy_from_user(filename, op.filename, op.length + 1);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ args = NvOsAlloc(op.argssize);
+ error = copy_from_user(args, op.args, op.argssize);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ error = NvRmOpen( &hRmDevice, 0 );
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ error = NvRmLoadLibrary(hRmDevice, filename, args, op.argssize, &hRmLibHandle);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ op.handle = hRmLibHandle;
+ error = copy_to_user(arg, &op, sizeof(op));
+
+ NvOsFree(filename);
+ NvOsFree(args);
+ return error;
+}
+
+static int nvfw_ioctl_load_library_ex(struct file *filp, void __user *arg)
+{
+ struct nvfw_load_handle op;
+ NvRmDeviceHandle hRmDevice;
+ NvRmLibraryHandle hRmLibHandle;
+ char *filename = NULL;
+ void *args = NULL;
+ int error;
+
+ error = copy_from_user(&op, arg, sizeof(op));
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ filename = NvOsAlloc(op.length + 1);
+ error = copy_from_user(filename, op.filename, op.length + 1);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ args = NvOsAlloc(op.argssize);
+ error = copy_from_user(args, op.args, op.argssize);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ error = NvRmOpen( &hRmDevice, 0 );
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ error = NvRmLoadLibraryEx(hRmDevice, filename, args, op.argssize, op.greedy, &hRmLibHandle);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ op.handle = hRmLibHandle;
+ error = copy_to_user(arg, &op, sizeof(op));
+
+ NvOsFree(filename);
+ NvOsFree(args);
+ return error;
+}
+
+static int nvfw_ioctl_free_library(struct file *filp, void __user *arg)
+{
+ struct nvfw_load_handle op;
+ NvRmDeviceHandle hRmDevice;
+ int error;
+
+ error = copy_from_user(&op, arg, sizeof(op));
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ error = NvRmOpen( &hRmDevice, 0 );
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ error = NvRmFreeLibrary(op.handle);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ return error;
+}
+
+static int nvfw_ioctl_get_proc_address(struct file *filp, void __user *arg)
+{
+ struct nvfw_get_proc_address_handle op;
+ NvRmDeviceHandle hRmDevice;
+ char *symbolname;
+ int error;
+
+ error = copy_from_user(&op, arg, sizeof(op));
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ symbolname = NvOsAlloc(op.length + 1);
+ error = copy_from_user(symbolname, op.symbolname, op.length + 1);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ error = NvRmOpen( &hRmDevice, 0 );
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ error = NvRmGetProcAddress(op.handle, symbolname, &op.address);
+ if (error) panic("%s: line=%d\n", __func__, __LINE__);
+
+ error = copy_to_user(arg, &op, sizeof(op));
+
+ NvOsFree(symbolname);
+ return error;
+}
+
+static long nvfw_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ void __user *uarg = (void __user *)arg;
+
+ switch (cmd) {
+ case NVFW_IOC_LOAD_LIBRARY:
+ err = nvfw_ioctl_load_library(filp, uarg);
+ break;
+ case NVFW_IOC_LOAD_LIBRARY_EX:
+ err = nvfw_ioctl_load_library_ex(filp, uarg);
+ break;
+ case NVFW_IOC_FREE_LIBRARY:
+ err = nvfw_ioctl_free_library(filp, uarg);
+ break;
+ case NVFW_IOC_GET_PROC_ADDRESS:
+ err = nvfw_ioctl_get_proc_address(filp, uarg);
+ break;
+ default:
+ return -ENOTTY;
+ }
+ return err;
+}
+
+static NvError PrivateOsFopen(
+ const char *filename,
+ NvU32 flags,
+ PrivateOsFileHandle *file)
+{
+ PrivateOsFileHandle hFile;
+
+ hFile = NvOsAlloc(sizeof(PrivateOsFile));
+ if (hFile == NULL)
+ return NvError_InsufficientMemory;
+
+ pr_debug("%s <kernel impl>: file=%s\n", __func__, filename);
+ if (request_firmware(&s_FwEntry, filename, nvfw_dev.this_device) != 0)
+ {
+ pr_err("%s: Cannot read firmware '%s'\n", __func__, filename);
+ return NvError_FileReadFailed;
+ }
+ hFile->pstart = s_FwEntry->data;
+ hFile->pread = s_FwEntry->data;
+ hFile->pend = s_FwEntry->data + s_FwEntry->size;
+
+ *file = hFile;
+
+ return NvError_Success;
+}
+
+static void PrivateOsFclose(PrivateOsFileHandle hFile)
+{
+ release_firmware(s_FwEntry);
+ NV_ASSERT(hFile);
+ NvOsFree(hFile);
+}
+
+NvError NvRmLoadLibrary(
+ NvRmDeviceHandle hDevice,
+ const char *pLibName,
+ void* pArgs,
+ NvU32 sizeOfArgs,
+ NvRmLibraryHandle *hLibHandle)
+{
+ NvError Error = NvSuccess;
+ NV_ASSERT(sizeOfArgs <= MAX_ARGS_SIZE);
+
+ Error = NvRmLoadLibraryEx(hDevice, pLibName, pArgs, sizeOfArgs, NV_FALSE,
+ hLibHandle);
+ return Error;
+}
+
+NvError NvRmLoadLibraryEx(
+ NvRmDeviceHandle hDevice,
+ const char *pLibName,
+ void* pArgs,
+ NvU32 sizeOfArgs,
+ NvBool IsApproachGreedy,
+ NvRmLibraryHandle *hLibHandle)
+{
+ NvRmLibraryHandle library = NULL;
+ NvError e = NvSuccess;
+ PrivateOsFileHandle hFile = NULL;
+ struct nvmap_handle_ref *staging = NULL;
+ void *loadAddr = NULL;
+ NvU32 len = 0;
+ NvU32 physAddr;
+
+ NV_ASSERT(sizeOfArgs <= MAX_ARGS_SIZE);
+
+ NV_CHECK_ERROR_CLEANUP(NvRmPrivInitAvp(hDevice));
+
+ e = NvRmPrivRPCConnect(s_RPCHandle);
+ if (e != NvSuccess)
+ {
+ NvOsDebugPrintf("RPCConnect timed out during NvRmLoadLibrary\n");
+ goto fail;
+ }
+
+ library = NvOsAlloc(sizeof(*library));
+ if (!library)
+ {
+ e = NvError_InsufficientMemory;
+ goto fail;
+ }
+
+ NV_CHECK_ERROR_CLEANUP(PrivateOsFopen(pLibName, NVOS_OPEN_READ, &hFile));
+ len = (NvU32)hFile->pend - (NvU32)hFile->pstart;
+
+ staging = nvmap_alloc(s_AvpClient, len, L1_CACHE_BYTES,
+ NVMAP_HANDLE_WRITE_COMBINE);
+ if (IS_ERR(staging)) {
+ e = NvError_InsufficientMemory;
+ goto fail;
+ }
+ loadAddr = nvmap_mmap(staging);
+ if (!loadAddr) {
+ e = NvError_InsufficientMemory;
+ goto fail;
+ }
+ physAddr = nvmap_pin(s_AvpClient, staging);
+ if (IS_ERR((void*)physAddr)) {
+ e = NvError_InsufficientMemory;
+ goto fail;
+ }
+
+ NvOsMemcpy(loadAddr, hFile->pstart, len);
+
+ memcpy(loadAddr, hFile->pstart, len);
+ wmb();
+
+ NV_CHECK_ERROR_CLEANUP(SendMsgAttachModule(library, pArgs, physAddr, len,
+ IsApproachGreedy, sizeOfArgs));
+
+fail:
+ if (loadAddr)
+ {
+ if (!IS_ERR((void*)physAddr))
+ nvmap_unpin(s_AvpClient, staging);
+
+ nvmap_munmap(staging, loadAddr);
+ }
+
+ if (!IS_ERR_OR_NULL(staging))
+ nvmap_free(s_AvpClient, staging);
+
+ if (hFile)
+ PrivateOsFclose(hFile);
+
+ if (e != NvSuccess)
+ {
+ NvOsFree(library);
+ library = NULL;
+ }
+
+ *hLibHandle = library;
+ return e;
+}
+
+NvError NvRmGetProcAddress(
+ NvRmLibraryHandle Handle,
+ const char *pSymbol,
+ void **pSymAddress)
+{
+ NvError Error = NvSuccess;
+ NV_ASSERT(Handle);
+ Error = NvRmPrivGetProcAddress(Handle, pSymbol, pSymAddress);
+ return Error;
+}
+
+NvError NvRmFreeLibrary(NvRmLibraryHandle hLibHandle)
+{
+ NvError e = NvSuccess;
+ NV_ASSERT(hLibHandle);
+
+ e = NvRmPrivRPCConnect(s_RPCHandle);
+ if (e != NvSuccess)
+ return e;
+
+ e = SendMsgDetachModule(hLibHandle);
+ if (e != NvSuccess)
+ return e;
+
+ NvOsFree(hLibHandle);
+ return NvSuccess;
+}
+
+//before unloading loading send message to avp with args and entry point via transport
+static NvError SendMsgDetachModule(NvRmLibraryHandle hLibHandle)
+{
+ NvU32 RecvMsgSize;
+ NvRmMessage_DetachModule Msg;
+ NvRmMessage_DetachModuleResponse MsgR;
+
+ Msg.msg = NvRmMsg_DetachModule;
+
+ Msg.msg = NvRmMsg_DetachModule;
+ Msg.reason = NvRmModuleLoaderReason_Detach;
+ Msg.libraryId = hLibHandle->libraryId;
+ RecvMsgSize = sizeof(NvRmMessage_DetachModuleResponse);
+ NvRmPrivRPCSendMsgWithResponse(s_RPCHandle, &MsgR, RecvMsgSize,
+ &RecvMsgSize, &Msg, sizeof(Msg));
+
+ return MsgR.error;
+}
+
+//after successful loading send message to avp with args and entry point via transport
+static NvError SendMsgAttachModule(
+ NvRmLibraryHandle hLibHandle,
+ void* pArgs,
+ NvU32 loadAddress,
+ NvU32 fileSize,
+ NvBool greedy,
+ NvU32 sizeOfArgs)
+{
+ NvU32 RecvMsgSize;
+ NvRmMessage_AttachModule Msg;
+ NvRmMessage_AttachModuleResponse MsgR;
+
+ NvOsMemset(&Msg, 0, sizeof(Msg));
+ Msg.msg = NvRmMsg_AttachModule;
+
+ if(pArgs)
+ NvOsMemcpy(Msg.args, pArgs, sizeOfArgs);
+
+ Msg.size = sizeOfArgs;
+ Msg.address = loadAddress;
+ Msg.filesize = fileSize;
+ if (greedy)
+ Msg.reason = NvRmModuleLoaderReason_AttachGreedy;
+ else
+ Msg.reason = NvRmModuleLoaderReason_Attach;
+
+ RecvMsgSize = sizeof(NvRmMessage_AttachModuleResponse);
+
+ NvRmPrivRPCSendMsgWithResponse(s_RPCHandle, &MsgR, RecvMsgSize,
+ &RecvMsgSize, &Msg, sizeof(Msg));
+
+ hLibHandle->libraryId = MsgR.libraryId;
+ return MsgR.error;
+}
+
+
+NvError NvRmPrivInitModuleLoaderRPC(NvRmDeviceHandle hDevice)
+{
+ NvError err = NvSuccess;
+
+ if (s_RPCHandle)
+ return NvError_Success;
+
+ NvOsDebugPrintf("%s <kernel impl>: NvRmPrivRPCInit(RPC_AVP_PORT)\n", __func__);
+ err = NvRmPrivRPCInit(hDevice, "RPC_AVP_PORT", &s_RPCHandle);
+ if (err) panic("%s: NvRmPrivRPCInit FAILED\n", __func__);
+
+ return err;
+}
+
+void NvRmPrivDeInitModuleLoaderRPC()
+{
+ NvRmPrivRPCDeInit(s_RPCHandle);
+}
+
+NvError NvRmPrivGetProcAddress(
+ NvRmLibraryHandle Handle,
+ const char *pSymbol,
+ void **pSymAddress)
+{
+ NvRmLibHandle *hHandle = Handle;
+
+ if (hHandle->libraryId == 0)
+ return NvError_SymbolNotFound;
+
+ *pSymAddress = (void *)hHandle->libraryId;
+ return NvSuccess;
+}
+
+static void NvRmPrivResetAvp(NvRmDeviceHandle hRm, unsigned long reset_va)
+{
+ u32 *stub_va = &_tegra_avp_launcher_stub_data[AVP_LAUNCHER_START_VA];
+ unsigned long stub_addr = virt_to_phys(_tegra_avp_launcher_stub);
+ unsigned int tmp;
+ unsigned long timeout;
+
+ *stub_va = reset_va;
+ __cpuc_flush_dcache_area(stub_va, sizeof(*stub_va));
+ outer_clean_range(__pa(stub_va), __pa(stub_va+1));
+
+ tmp = readl(_TEGRA_AVP_RESET_VECTOR_ADDR);
+ writel(stub_addr, _TEGRA_AVP_RESET_VECTOR_ADDR);
+ barrier();
+ NvRmModuleReset(hRm, NvRmModuleID_Avp);
+ writel(0, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + FLOW_CTRL_HALT_COP);
+
+ barrier();
+ timeout = jiffies + HZ;
+ /* the AVP firmware will reprogram its reset vector as the kernel
+ * starts, so a dead kernel can be detected by polling this value */
+ while (time_before(jiffies, timeout)) {
+ if (readl(_TEGRA_AVP_RESET_VECTOR_ADDR) != stub_addr)
+ break;
+ cpu_relax();
+ }
+
+ WARN_ON(readl(_TEGRA_AVP_RESET_VECTOR_ADDR) == stub_addr);
+}
+
+void NvRmPrivXpcSendMsgAddress(void);
+
+static NvError NvRmPrivInitAvp(NvRmDeviceHandle hRm)
+{
+ u32 *stub_phys = &_tegra_avp_launcher_stub_data[AVP_LAUNCHER_MMU_PHYSICAL];
+ PrivateOsFileHandle kernel;
+ void *map = NULL;
+ NvError e;
+ NvU32 len;
+ NvU32 phys;
+
+ if (s_KernelImage)
+ return NvSuccess;
+
+ s_AvpClient = nvmap_create_client(nvmap_dev, "nvrm");
+ if (IS_ERR(s_AvpClient)) {
+ e = NvError_InsufficientMemory;
+ goto fail;
+ }
+
+ s_KernelImage = nvmap_alloc(s_AvpClient, SZ_1M, SZ_1M,
+ NVMAP_HANDLE_WRITE_COMBINE);
+ if (IS_ERR(s_KernelImage)) {
+ e = NvError_InsufficientMemory;
+ goto fail;
+ }
+
+ map = nvmap_mmap(s_KernelImage);
+ if (map == NULL) {
+ e = NvError_InsufficientMemory;
+ goto fail;
+ }
+
+ phys = nvmap_pin(s_AvpClient, s_KernelImage);
+ if (IS_ERR((void *)phys)) {
+ e = NvError_InsufficientMemory;
+ goto fail;
+ }
+
+ NV_CHECK_ERROR_CLEANUP(PrivateOsFopen("nvrm_avp.bin",
+ NVOS_OPEN_READ, &kernel));
+
+ memset(map, 0, SZ_1M);
+ len = (NvU32)kernel->pend - (NvU32)kernel->pstart;
+ memcpy(map, kernel->pstart, len);
+ wmb();
+
+ PrivateOsFclose(kernel);
+
+ *stub_phys = phys;
+ __cpuc_flush_dcache_area(stub_phys, sizeof(*stub_phys));
+ outer_clean_range(__pa(stub_phys), __pa(stub_phys+1));
+
+ NvRmPrivResetAvp(hRm, 0x00100000ul);
+
+ NV_CHECK_ERROR_CLEANUP(NvRmPrivInitService(hRm));
+
+ NvRmPrivXpcSendMsgAddress();
+
+ e = NvRmPrivInitModuleLoaderRPC(hRm);
+ if (e != NvSuccess)
+ {
+ NvRmPrivServiceDeInit();
+ goto fail;
+ }
+
+ nvmap_munmap(s_KernelImage, map);
+
+ return NvSuccess;
+
+fail:
+ writel(2 << 29, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + FLOW_CTRL_HALT_COP);
+ if (map)
+ {
+ if (!IS_ERR_OR_NULL((void *)phys))
+ nvmap_unpin(s_AvpClient, s_KernelImage);
+ }
+ if (!IS_ERR_OR_NULL(s_KernelImage))
+ nvmap_free(s_AvpClient, s_KernelImage);
+ if (!IS_ERR_OR_NULL(s_AvpClient))
+ nvmap_client_put(s_AvpClient);
+ s_KernelImage = NULL;
+ s_AvpClient = NULL;
+ return e;
+}
+
+static void __iomem *iram_base = IO_ADDRESS(TEGRA_IRAM_BASE);
+static void __iomem *iram_backup;
+static dma_addr_t iram_backup_addr;
+static u32 iram_size = TEGRA_IRAM_SIZE;
+static u32 iram_backup_size = TEGRA_IRAM_SIZE + 4;
+static u32 avp_resume_addr;
+
+static NvError NvRmPrivSuspendAvp(NvRmRPCHandle hRPCHandle)
+{
+ NvError err = NvSuccess;
+ NvRmMessage_InitiateLP0 lp0_msg;
+ void *avp_suspend_done = iram_backup + iram_size;
+ unsigned long timeout;
+
+ pr_info("%s()+\n", __func__);
+
+ if (!s_KernelImage)
+ goto done;
+ else if (!iram_backup_addr) {
+ /* XXX: should we return error? */
+ pr_warning("%s: iram backup ram missing, not suspending avp\n",
+ __func__);
+ goto done;
+ }
+
+ NV_ASSERT(hRPCHandle->svcTransportHandle != NULL);
+
+ lp0_msg.msg = NvRmMsg_InitiateLP0;
+ lp0_msg.sourceAddr = (u32)TEGRA_IRAM_BASE;
+ lp0_msg.bufferAddr = (u32)iram_backup_addr;
+ lp0_msg.bufferSize = (u32)iram_size;
+
+ writel(0, avp_suspend_done);
+
+ NvOsMutexLock(hRPCHandle->RecvLock);
+ err = NvRmTransportSendMsg(hRPCHandle->svcTransportHandle, &lp0_msg,
+ sizeof(lp0_msg), 1000);
+ NvOsMutexUnlock(hRPCHandle->RecvLock);
+
+ if (err != NvSuccess) {
+ pr_err("%s: cannot send AVP LP0 message\n", __func__);
+ goto done;
+ }
+
+ timeout = jiffies + msecs_to_jiffies(1000);
+ while (!readl(avp_suspend_done) && time_before(jiffies, timeout)) {
+ udelay(10);
+ cpu_relax();
+ }
+
+ if (!readl(avp_suspend_done)) {
+ pr_err("%s: AVP failed to suspend\n", __func__);
+ err = NvError_Timeout;
+ goto done;
+ }
+
+ avp_resume_addr = readl(iram_base);
+ if (!avp_resume_addr) {
+ pr_err("%s: AVP failed to set it's resume address\n", __func__);
+ err = NvError_InvalidState;
+ goto done;
+ }
+
+ pr_info("avp_suspend: resume_addr=%x\n", avp_resume_addr);
+ avp_resume_addr &= 0xFFFFFFFE;
+
+ pr_info("%s()-\n", __func__);
+
+done:
+ return err;
+}
+
+static NvError NvRmPrivResumeAvp(NvRmRPCHandle hRPCHandle)
+{
+ NvError ret = NvSuccess;
+
+ pr_info("%s()+\n", __func__);
+ if (!s_KernelImage || !avp_resume_addr)
+ goto done;
+
+ NvRmPrivResetAvp(hRPCHandle->hRmDevice, avp_resume_addr);
+ avp_resume_addr = 0;
+
+ pr_info("%s()-\n", __func__);
+
+done:
+ return ret;
+}
+
+int __init _avp_suspend_resume_init(void)
+{
+ /* allocate an iram sized chunk of ram to give to the AVP */
+ iram_backup = dma_alloc_coherent(NULL, iram_backup_size,
+ &iram_backup_addr, GFP_KERNEL);
+ if (!iram_backup)
+ {
+ pr_err("%s: Unable to allocate iram backup mem\n", __func__);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int avp_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ NvError err;
+
+ err = NvRmPrivSuspendAvp(s_RPCHandle);
+ if (err != NvSuccess)
+ return -EIO;
+ return 0;
+}
+
+static int avp_resume(struct platform_device *pdev)
+{
+ NvError err;
+
+ err = NvRmPrivResumeAvp(s_RPCHandle);
+ if (err != NvSuccess)
+ return -EIO;
+ return 0;
+}
+
+static struct platform_driver avp_nvfw_driver = {
+ .suspend = avp_suspend,
+ .resume = avp_resume,
+ .driver = {
+ .name = "nvfw-avp-device",
+ .owner = THIS_MODULE,
+ },
+};
+
+int __init _avp_suspend_resume_init(void);
+
+static int __init nvfw_init(void)
+{
+ int ret = 0;
+ struct platform_device *pdev;
+
+ ret = misc_register(&nvfw_dev);
+ s_KernelImage = NULL;
+ if (ret) panic("%s: misc_register FAILED\n", __func__);
+
+ ret = _avp_suspend_resume_init();
+ if (ret)
+ goto err;
+ pdev = platform_create_bundle(&avp_nvfw_driver, NULL, NULL, 0, NULL, 0);
+ if (!pdev) {
+ pr_err("%s: Can't reg platform driver\n", __func__);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ return 0;
+
+err:
+ return ret;
+}
+
+static void __exit nvfw_deinit(void)
+{
+ misc_deregister(&nvfw_dev);
+}
+
+module_init(nvfw_init);
+module_exit(nvfw_deinit);