Index: arch/arm/mach-tegra/nv/nvrm_user.c |
diff --git a/arch/arm/mach-tegra/nv/nvrm_user.c b/arch/arm/mach-tegra/nv/nvrm_user.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7357f61654c5d09c9e860e71b4e68564a3ca3bf6 |
--- /dev/null |
+++ b/arch/arm/mach-tegra/nv/nvrm_user.c |
@@ -0,0 +1,695 @@ |
+/* |
+ * arch/arm/mach-tegra/nvrm_user.c |
+ * |
+ * User-land access to NvRm APIs |
+ * |
+ * Copyright (c) 2008-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. |
+ */ |
+ |
+#include <linux/module.h> |
+#include <linux/proc_fs.h> |
+#include <linux/miscdevice.h> |
+#include <linux/uaccess.h> |
+#include <linux/cpumask.h> |
+#include <linux/sched.h> |
+#include <linux/cpu.h> |
+#include <linux/platform_device.h> |
+#include <linux/freezer.h> |
+#include <linux/suspend.h> |
+#include <linux/percpu.h> |
+#ifdef CONFIG_HAS_EARLYSUSPEND |
+#include <linux/earlysuspend.h> |
+#endif |
+#include <linux/smp.h> |
+#include <asm/smp_twd.h> |
+#include <asm/cpu.h> |
+#include "nvcommon.h" |
+#include "nvassert.h" |
+#include "nvos.h" |
+#include "nvrm_memmgr.h" |
+#include "nvrm_ioctls.h" |
+#include "mach/nvrm_linux.h" |
+#include "linux/nvos_ioctl.h" |
+#include "nvrm_power_private.h" |
+#include "nvreftrack.h" |
+#include "mach/timex.h" |
+ |
+pid_t s_nvrm_daemon_pid = 0; |
+ |
+NvError NvRm_Dispatch(void *InBuffer, |
+ NvU32 InSize, |
+ void *OutBuffer, |
+ NvU32 OutSize, |
+ NvDispatchCtx* Ctx); |
+ |
+static int nvrm_open(struct inode *inode, struct file *file); |
+static int nvrm_close(struct inode *inode, struct file *file); |
+static long nvrm_unlocked_ioctl(struct file *file, |
+ unsigned int cmd, unsigned long arg); |
+static int nvrm_mmap(struct file *file, struct vm_area_struct *vma); |
+extern void reset_cpu(unsigned int cpu, unsigned int reset); |
+ |
+static NvOsThreadHandle s_DfsThread = NULL; |
+static NvRtHandle s_RtHandle = NULL; |
+ |
+#define DEVICE_NAME "nvrm" |
+ |
+static const struct file_operations nvrm_fops = |
+{ |
+ .owner = THIS_MODULE, |
+ .open = nvrm_open, |
+ .release = nvrm_close, |
+ .unlocked_ioctl = nvrm_unlocked_ioctl, |
+ .mmap = nvrm_mmap |
+}; |
+ |
+static struct miscdevice nvrm_dev = |
+{ |
+ .name = DEVICE_NAME, |
+ .fops = &nvrm_fops, |
+ .minor = MISC_DYNAMIC_MINOR, |
+}; |
+ |
+#ifdef GHACK_DFS |
+static void NvRmDfsThread(void *args) |
+{ |
+ NvRmDeviceHandle hRm = (NvRmDeviceHandle)args; |
+ struct cpumask cpu_mask; |
+ |
+ //Ensure that only cpu0 is in the affinity mask |
+ cpumask_clear(&cpu_mask); |
+ cpumask_set_cpu(0, &cpu_mask); |
+ if (sched_setaffinity(0, &cpu_mask)) |
+ { |
+ panic("Unable to setaffinity of DFS thread!\n"); |
+ } |
+ |
+ //Confirm that only CPU0 can run this thread |
+ if (!cpumask_test_cpu(0, &cpu_mask) || cpumask_weight(&cpu_mask) != 1) |
+ { |
+ panic("Unable to setaffinity of DFS thread!\n"); |
+ } |
+ |
+ set_freezable_with_signal(); |
+ |
+ if (NvRmDfsGetState(hRm) > NvRmDfsRunState_Disabled) |
+ { |
+ NvRmFreqKHz CpuKHz, f; |
+ CpuKHz = NvRmPrivDfsGetCurrentKHz(NvRmDfsClockId_Cpu); |
+ local_timer_rescale(CpuKHz); |
+ |
+ NvRmDfsSetState(hRm, NvRmDfsRunState_ClosedLoop); |
+ |
+ for (;;) |
+ { |
+ NvRmPmRequest Request = NvRmPrivPmThread(); |
+ f = NvRmPrivDfsGetCurrentKHz(NvRmDfsClockId_Cpu); |
+ if (CpuKHz != f) |
+ { |
+ CpuKHz = f; |
+ local_timer_rescale(CpuKHz); |
+ twd_set_prescaler(NULL); |
+ smp_call_function(twd_set_prescaler, NULL, NV_TRUE); |
+ } |
+ if (Request & NvRmPmRequest_ExitFlag) |
+ { |
+ break; |
+ } |
+ if (Request & NvRmPmRequest_CpuOnFlag) |
+ { |
+#ifdef CONFIG_HOTPLUG_CPU |
+ printk("DFS requested CPU1 ON\n"); |
+ preset_lpj = per_cpu(cpu_data, 0).loops_per_jiffy; |
+ cpu_up(1); |
+ smp_call_function(twd_set_prescaler, NULL, NV_TRUE); |
+#endif |
+ } |
+ |
+ if (Request & NvRmPmRequest_CpuOffFlag) |
+ { |
+#ifdef CONFIG_HOTPLUG_CPU |
+ printk("DFS requested CPU1 OFF\n"); |
+ cpu_down(1); |
+#endif |
+ } |
+ } |
+ } |
+} |
+#endif |
+ |
+static void client_detach(NvRtClientHandle client) |
+{ |
+ if (NvRtUnregisterClient(s_RtHandle, client)) |
+ { |
+ NvDispatchCtx dctx; |
+ |
+ dctx.Rt = s_RtHandle; |
+ dctx.Client = client; |
+ dctx.PackageIdx = 0; |
+ |
+ for (;;) |
+ { |
+ void* ptr = NvRtFreeObjRef(&dctx, |
+ NvRtObjType_NvRm_NvRmMemHandle, |
+ NULL); |
+ if (!ptr) break; |
+ NVRT_LEAK("NvRm", "NvRmMemHandle", ptr); |
+ NvRmMemHandleFree(ptr); |
+ } |
+ |
+ NvRtUnregisterClient(s_RtHandle, client); |
+ } |
+} |
+ |
+int nvrm_open(struct inode *inode, struct file *file) |
+{ |
+ NvRtClientHandle Client; |
+ |
+ if (NvRtRegisterClient(s_RtHandle, &Client) != NvSuccess) |
+ { |
+ return -ENOMEM; |
+ } |
+ |
+ file->private_data = (void*)Client; |
+ |
+ return 0; |
+} |
+ |
+int nvrm_close(struct inode *inode, struct file *file) |
+{ |
+ client_detach((NvRtClientHandle)file->private_data); |
+ return 0; |
+} |
+ |
+long nvrm_unlocked_ioctl(struct file *file, |
+ unsigned int cmd, unsigned long arg) |
+{ |
+ NvError err; |
+ NvOsIoctlParams p; |
+ NvU32 size; |
+ NvU32 small_buf[8]; |
+ void *ptr = 0; |
+ long e; |
+ NvBool bAlloc = NV_FALSE; |
+ |
+ switch( cmd ) { |
+ case NvRmIoctls_Generic: |
+ { |
+ NvDispatchCtx dctx; |
+ |
+ dctx.Rt = s_RtHandle; |
+ dctx.Client = (NvRtClientHandle)file->private_data; |
+ dctx.PackageIdx = 0; |
+ |
+ err = NvOsCopyIn( &p, (void *)arg, sizeof(p) ); |
+ if( err != NvSuccess ) |
+ { |
+ printk( "NvRmIoctls_Generic: copy in failed\n" ); |
+ goto fail; |
+ } |
+ |
+ //printk( "NvRmIoctls_Generic: %d %d %d\n", p.InBufferSize, |
+ // p.InOutBufferSize, p.OutBufferSize ); |
+ |
+ size = p.InBufferSize + p.InOutBufferSize + p.OutBufferSize; |
+ if( size <= sizeof(small_buf) ) |
+ { |
+ ptr = small_buf; |
+ } |
+ else |
+ { |
+ ptr = NvOsAlloc( size ); |
+ if( !ptr ) |
+ { |
+ printk( "NvRmIoctls_Generic: alloc failure (%d bytes)\n", |
+ size ); |
+ goto fail; |
+ } |
+ |
+ bAlloc = NV_TRUE; |
+ } |
+ |
+ err = NvOsCopyIn( ptr, p.pBuffer, p.InBufferSize + |
+ p.InOutBufferSize ); |
+ if( err != NvSuccess ) |
+ { |
+ printk( "NvRmIoctls_Generic: copy in failure\n" ); |
+ goto fail; |
+ } |
+ |
+ err = NvRm_Dispatch( ptr, p.InBufferSize + p.InOutBufferSize, |
+ ((NvU8 *)ptr) + p.InBufferSize, p.InOutBufferSize + |
+ p.OutBufferSize, &dctx ); |
+ if( err != NvSuccess ) |
+ { |
+ printk( "NvRmIoctls_Generic: dispatch failure\n" ); |
+ goto fail; |
+ } |
+ |
+ if( p.InOutBufferSize || p.OutBufferSize ) |
+ { |
+ err = NvOsCopyOut( ((NvU8 *)((NvOsIoctlParams *)arg)->pBuffer) |
+ + p.InBufferSize, |
+ ((NvU8 *)ptr) + p.InBufferSize, |
+ p.InOutBufferSize + p.OutBufferSize ); |
+ if( err != NvSuccess ) |
+ { |
+ printk( "NvRmIoctls_Generic: copy out failure\n" ); |
+ goto fail; |
+ } |
+ } |
+ |
+ break; |
+ } |
+ case NvRmIoctls_NvRmGraphics: |
+ printk( "NvRmIoctls_NvRmGraphics: not supported\n" ); |
+ goto fail; |
+ case NvRmIoctls_NvRmFbControl: |
+ printk( "NvRmIoctls_NvRmFbControl: deprecated \n" ); |
+ break; |
+ |
+ case NvRmIoctls_NvRmMemRead: |
+ case NvRmIoctls_NvRmMemWrite: |
+ case NvRmIoctls_NvRmMemReadStrided: |
+ case NvRmIoctls_NvRmGetCarveoutInfo: |
+ case NvRmIoctls_NvRmMemWriteStrided: |
+ goto fail; |
+ |
+ case NvRmIoctls_NvRmMemMapIntoCallerPtr: |
+ // FIXME: implement? |
+ printk( "NvRmIoctls_NvRmMemMapIntoCallerPtr: not supported\n" ); |
+ goto fail; |
+ case NvRmIoctls_NvRmBootDone: |
+#ifdef GHACK_DFS |
+ if (!s_DfsThread) |
+ { |
+ if (NvOsInterruptPriorityThreadCreate(NvRmDfsThread, |
+ (void*)s_hRmGlobal, &s_DfsThread)!=NvSuccess) |
+ { |
+ NvOsDebugPrintf("Failed to create DFS processing thread\n"); |
+ goto fail; |
+ } |
+ } |
+#endif |
+ break; |
+ case NvRmIoctls_NvRmGetClientId: |
+ err = NvOsCopyIn(&p, (void*)arg, sizeof(p)); |
+ if (err != NvSuccess) |
+ { |
+ NvOsDebugPrintf("NvRmIoctls_NvRmGetClientId: copy in failed\n"); |
+ goto fail; |
+ } |
+ |
+ NV_ASSERT(p.InBufferSize == 0); |
+ NV_ASSERT(p.OutBufferSize == sizeof(NvRtClientHandle)); |
+ NV_ASSERT(p.InOutBufferSize == 0); |
+ |
+ if (NvOsCopyOut(p.pBuffer, |
+ &file->private_data, |
+ sizeof(NvRtClientHandle)) != NvSuccess) |
+ { |
+ NvOsDebugPrintf("Failed to copy client id\n"); |
+ goto fail; |
+ } |
+ break; |
+ case NvRmIoctls_NvRmClientAttach: |
+ { |
+ NvRtClientHandle Client; |
+ |
+ err = NvOsCopyIn(&p, (void*)arg, sizeof(p)); |
+ if (err != NvSuccess) |
+ { |
+ NvOsDebugPrintf("NvRmIoctls_NvRmClientAttach: copy in failed\n"); |
+ goto fail; |
+ } |
+ |
+ NV_ASSERT(p.InBufferSize == sizeof(NvRtClientHandle)); |
+ NV_ASSERT(p.OutBufferSize == 0); |
+ NV_ASSERT(p.InOutBufferSize == 0); |
+ |
+ if (NvOsCopyIn((void*)&Client, |
+ p.pBuffer, |
+ sizeof(NvRtClientHandle)) != NvSuccess) |
+ { |
+ NvOsDebugPrintf("Failed to copy client id\n"); |
+ goto fail; |
+ } |
+ |
+ NV_ASSERT(Client || !"Bad client"); |
+ |
+ if (Client == (NvRtClientHandle)file->private_data) |
+ { |
+ // The daemon is attaching to itself, no need to add refcount |
+ break; |
+ } |
+ if (NvRtAddClientRef(s_RtHandle, Client) != NvSuccess) |
+ { |
+ NvOsDebugPrintf("Client ref add unsuccessful\n"); |
+ goto fail; |
+ } |
+ break; |
+ } |
+ case NvRmIoctls_NvRmClientDetach: |
+ { |
+ NvRtClientHandle Client; |
+ |
+ err = NvOsCopyIn(&p, (void*)arg, sizeof(p)); |
+ if (err != NvSuccess) |
+ { |
+ NvOsDebugPrintf("NvRmIoctls_NvRmClientAttach: copy in failed\n"); |
+ goto fail; |
+ } |
+ |
+ NV_ASSERT(p.InBufferSize == sizeof(NvRtClientHandle)); |
+ NV_ASSERT(p.OutBufferSize == 0); |
+ NV_ASSERT(p.InOutBufferSize == 0); |
+ |
+ if (NvOsCopyIn((void*)&Client, |
+ p.pBuffer, |
+ sizeof(NvRtClientHandle)) != NvSuccess) |
+ { |
+ NvOsDebugPrintf("Failed to copy client id\n"); |
+ goto fail; |
+ } |
+ |
+ NV_ASSERT(Client || !"Bad client"); |
+ |
+ if (Client == (NvRtClientHandle)file->private_data) |
+ { |
+ // The daemon is detaching from itself, no need to dec refcount |
+ break; |
+ } |
+ |
+ client_detach(Client); |
+ break; |
+ } |
+ // FIXME: power ioctls? |
+ default: |
+ printk( "unknown ioctl code\n" ); |
+ goto fail; |
+ } |
+ |
+ e = 0; |
+ goto clean; |
+ |
+fail: |
+ e = -EINVAL; |
+ |
+clean: |
+ if( bAlloc ) |
+ { |
+ NvOsFree( ptr ); |
+ } |
+ |
+ return e; |
+} |
+ |
+int nvrm_mmap(struct file *file, struct vm_area_struct *vma) |
+{ |
+ return 0; |
+} |
+ |
+static int nvrm_probe(struct platform_device *pdev) |
+{ |
+ int e = 0; |
+ NvU32 NumTypes = NvRtObjType_NvRm_Num; |
+ |
+ printk("nvrm probe\n"); |
+ |
+ NV_ASSERT(s_RtHandle == NULL); |
+ |
+ if (NvRtCreate(1, &NumTypes, &s_RtHandle) != NvSuccess) |
+ { |
+ e = -ENOMEM; |
+ } |
+ |
+ if (e == 0) |
+ { |
+ e = misc_register( &nvrm_dev ); |
+ } |
+ |
+ if( e < 0 ) |
+ { |
+ if (s_RtHandle) |
+ { |
+ NvRtDestroy(s_RtHandle); |
+ s_RtHandle = NULL; |
+ } |
+ |
+ printk("nvrm probe failed to open\n"); |
+ } |
+ return e; |
+} |
+ |
+static int nvrm_remove(struct platform_device *pdev) |
+{ |
+ misc_deregister( &nvrm_dev ); |
+ NvRtDestroy(s_RtHandle); |
+ s_RtHandle = NULL; |
+ return 0; |
+} |
+ |
+static int nvrm_suspend(struct platform_device *pdev, pm_message_t state) |
+{ |
+#ifdef GHACK |
+ if(NvRmKernelPowerSuspend(s_hRmGlobal)) { |
+ printk(KERN_INFO "%s : FAILED\n", __func__); |
+ return -1; |
+ } |
+#endif |
+ return 0; |
+} |
+ |
+static int nvrm_resume(struct platform_device *pdev) |
+{ |
+#ifdef GHACK |
+ if(NvRmKernelPowerResume(s_hRmGlobal)) { |
+ printk(KERN_INFO "%s : FAILED\n", __func__); |
+ return -1; |
+ } |
+#endif |
+ return 0; |
+ |
+} |
+ |
+static struct platform_driver nvrm_driver = |
+{ |
+ .probe = nvrm_probe, |
+ .remove = nvrm_remove, |
+ .suspend = nvrm_suspend, |
+ .resume = nvrm_resume, |
+ .driver = { .name = "nvrm" } |
+}; |
+ |
+#if defined(CONFIG_PM) |
+// |
+// /sys/power/nvrm/notifier |
+// |
+ |
+wait_queue_head_t tegra_pm_notifier_wait; |
+wait_queue_head_t sys_nvrm_notifier_wait; |
+ |
+int tegra_pm_notifier_continue_ok; |
+ |
+struct kobject *nvrm_kobj; |
+ |
+const char* sys_nvrm_notifier; |
+ |
+static const char *STRING_PM_SUSPEND_PREPARE = "PM_SUSPEND_PREPARE"; |
+static const char *STRING_PM_POST_SUSPEND = "PM_POST_SUSPEND"; |
+static const char *STRING_PM_DISPLAY_OFF = "PM_DISPLAY_OFF"; |
+static const char *STRING_PM_DISPLAY_ON = "PM_DISPLAY_ON"; |
+static const char *STRING_PM_CONTINUE = "PM_CONTINUE"; |
+static const char *STRING_PM_SIGNAL = "PM_SIGNAL"; |
+ |
+// Reading blocks if the value is not available. |
+static ssize_t |
+nvrm_notifier_show(struct kobject *kobj, struct kobj_attribute *attr, |
+ char *buf) |
+{ |
+ int nchar; |
+ |
+ // Block if the value is not available yet. |
+ if (! sys_nvrm_notifier) |
+ { |
+ printk(KERN_INFO "%s: blocking\n", __func__); |
+ wait_event_interruptible(sys_nvrm_notifier_wait, sys_nvrm_notifier); |
+ } |
+ |
+ // In case of false wakeup, return "". |
+ if (! sys_nvrm_notifier) |
+ { |
+ printk(KERN_INFO "%s: false wakeup, returning with '\\n'\n", __func__); |
+ nchar = sprintf(buf, "\n"); |
+ return nchar; |
+ } |
+ |
+ // Return the value, and clear. |
+ printk(KERN_INFO "%s: returning with '%s'\n", __func__, sys_nvrm_notifier); |
+ nchar = sprintf(buf, "%s\n", sys_nvrm_notifier); |
+ sys_nvrm_notifier = NULL; |
+ return nchar; |
+} |
+ |
+// Writing is no blocking. |
+static ssize_t |
+nvrm_notifier_store(struct kobject *kobj, struct kobj_attribute *attr, |
+ const char *buf, size_t count) |
+{ |
+ if (!strncmp(buf, STRING_PM_CONTINUE, strlen(STRING_PM_CONTINUE))) { |
+ // Wake up pm_notifier. |
+ tegra_pm_notifier_continue_ok = 1; |
+ wake_up(&tegra_pm_notifier_wait); |
+ } |
+ else if (!strncmp(buf, STRING_PM_SIGNAL, strlen(STRING_PM_SIGNAL))) { |
+ s_nvrm_daemon_pid = 0; |
+ sscanf(buf, "%*s %d", &s_nvrm_daemon_pid); |
+ printk(KERN_INFO "%s: nvrm_daemon=%d\n", __func__, s_nvrm_daemon_pid); |
+ } |
+ else { |
+ printk(KERN_ERR "%s: wrong value '%s'\n", __func__, buf); |
+ } |
+ |
+ return count; |
+} |
+ |
+static struct kobj_attribute nvrm_notifier_attribute = |
+ __ATTR(notifier, 0666, nvrm_notifier_show, nvrm_notifier_store); |
+ |
+// |
+// PM notifier |
+// |
+ |
+static void notify_daemon(const char* notice) |
+{ |
+ long timeout = HZ * 30; |
+ |
+ // In case daemon's pid is not reported, do not signal or wait. |
+ if (!s_nvrm_daemon_pid) { |
+ printk(KERN_ERR "%s: don't know nvrm_daemon's PID\n", __func__); |
+ return; |
+ } |
+ |
+ // Clear before kicking nvrm_daemon. |
+ tegra_pm_notifier_continue_ok = 0; |
+ |
+ // Notify nvrm_daemon. |
+ sys_nvrm_notifier = notice; |
+ wake_up(&sys_nvrm_notifier_wait); |
+ |
+ // Wait for the reply from nvrm_daemon. |
+ printk(KERN_INFO "%s: wait for nvrm_daemon\n", __func__); |
+ if (wait_event_timeout(tegra_pm_notifier_wait, |
+ tegra_pm_notifier_continue_ok, timeout) == 0) { |
+ printk(KERN_ERR "%s: timed out. nvrm_daemon did not reply\n", __func__); |
+ } |
+ |
+ // Go back to the initial state. |
+ sys_nvrm_notifier = NULL; |
+} |
+ |
+int tegra_pm_notifier(struct notifier_block *nb, |
+ unsigned long event, void *nouse) |
+{ |
+ printk(KERN_INFO "%s: start processing event=%lx\n", __func__, event); |
+ |
+ // Notify the event to nvrm_daemon. |
+ if (event == PM_SUSPEND_PREPARE) { |
+#ifndef CONFIG_HAS_EARLYSUSPEND |
+ notify_daemon(STRING_PM_DISPLAY_OFF); |
+#endif |
+ notify_daemon(STRING_PM_SUSPEND_PREPARE); |
+ } |
+ else if (event == PM_POST_SUSPEND) { |
+ notify_daemon(STRING_PM_POST_SUSPEND); |
+#ifndef CONFIG_HAS_EARLYSUSPEND |
+ notify_daemon(STRING_PM_DISPLAY_ON); |
+#endif |
+ } |
+ else { |
+ printk(KERN_ERR "%s: unknown event %ld\n", __func__, event); |
+ return NOTIFY_DONE; |
+ } |
+ |
+ printk(KERN_INFO "%s: finished processing event=%ld\n", __func__, event); |
+ return NOTIFY_OK; |
+} |
+ |
+#ifdef CONFIG_HAS_EARLYSUSPEND |
+void tegra_display_off(struct early_suspend *h) |
+{ |
+ notify_daemon(STRING_PM_DISPLAY_OFF); |
+} |
+ |
+void tegra_display_on(struct early_suspend *h) |
+{ |
+ notify_daemon(STRING_PM_DISPLAY_ON); |
+} |
+ |
+static struct early_suspend tegra_display_power = |
+{ |
+ .suspend = tegra_display_off, |
+ .resume = tegra_display_on, |
+ .level = EARLY_SUSPEND_LEVEL_DISABLE_FB |
+}; |
+#endif |
+#endif |
+ |
+static struct platform_device nvrm_device = |
+{ |
+ .name = "nvrm" |
+}; |
+ |
+ |
+static int __init nvrm_init(void) |
+{ |
+ int ret = 0; |
+ printk(KERN_INFO "%s called\n", __func__); |
+ |
+ #if defined(CONFIG_PM) |
+ // Register PM notifier. |
+ pm_notifier(tegra_pm_notifier, 0); |
+ tegra_pm_notifier_continue_ok = 0; |
+ init_waitqueue_head(&tegra_pm_notifier_wait); |
+ |
+ #if defined(CONFIG_HAS_EARLYSUSPEND) |
+ register_early_suspend(&tegra_display_power); |
+ #endif |
+ |
+ // Create /sys/power/nvrm/notifier. |
+ nvrm_kobj = kobject_create_and_add("nvrm", power_kobj); |
+ sysfs_create_file(nvrm_kobj, &nvrm_notifier_attribute.attr); |
+ sys_nvrm_notifier = NULL; |
+ init_waitqueue_head(&sys_nvrm_notifier_wait); |
+ #endif |
+ |
+ // Register NvRm platform driver. |
+ ret = platform_driver_register(&nvrm_driver); |
+ |
+ platform_device_register(&nvrm_device); |
+ |
+ return ret; |
+} |
+ |
+static void __exit nvrm_deinit(void) |
+{ |
+ printk(KERN_INFO "%s called\n", __func__); |
+ platform_driver_unregister(&nvrm_driver); |
+} |
+ |
+module_init(nvrm_init); |
+module_exit(nvrm_deinit); |