android_kernel_cmhtcleo/arch/arm/mach-msm/hw3d.c

889 lines
22 KiB
C
Raw Normal View History

2010-08-27 09:19:57 +00:00
/* arch/arm/mach-msm/hw3d.c
*
* Register/Interrupt access for userspace 3D library.
*
* Copyright (C) 2007 Google, Inc.
* Author: Brian Swetland <swetland@google.com>
* Heavily modified: Dima Zavin <dima@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
/* #define DEBUG */
/* #define VERBOSE */
#include <linux/clk.h>
#include <linux/earlysuspend.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/cdev.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/msm_hw3d.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/wakelock.h>
#include <asm/io.h>
#include <mach/board.h>
#if defined(VERBOSE)
#define VDBG(x...) pr_debug(x)
#else
#define VDBG(x...) do {} while(0)
#endif
struct mem_region {
unsigned long pbase;
unsigned long size;
void __iomem *vbase;
};
/* Device minor numbers for master and client */
#define MINOR_MASTER 0
#define MINOR_CLIENT 1
struct hw3d_info {
dev_t devno;
struct cdev master_cdev;
struct cdev client_cdev;
struct clk *grp_clk;
struct clk *imem_clk;
int irq;
struct mem_region regions[HW3D_NUM_REGIONS];
wait_queue_head_t irq_wq;
bool irq_pending;
bool irq_en;
bool suspending;
bool revoking;
bool enabled;
struct timer_list revoke_timer;
wait_queue_head_t revoke_wq;
wait_queue_head_t revoke_done_wq;
unsigned int waiter_cnt;
spinlock_t lock;
struct file *client_file;
struct task_struct *client_task;
struct early_suspend early_suspend;
struct wake_lock wake_lock;
};
static struct hw3d_info *hw3d_info;
struct hw3d_data {
struct vm_area_struct *vmas[HW3D_NUM_REGIONS];
struct mutex mutex;
bool closing;
};
#define REGION_PAGE_ID(addr) \
((((uint32_t)(addr)) >> (28 - PAGE_SHIFT)) & 0xf)
#define REGION_PAGE_OFFS(addr) \
((((uint32_t)(addr)) & ~(0xf << (28 - PAGE_SHIFT))))
static int hw3d_open(struct inode *, struct file *);
static int hw3d_release(struct inode *, struct file *);
static int hw3d_mmap(struct file *, struct vm_area_struct *);
static int hw3d_flush(struct file *, fl_owner_t);
static long hw3d_ioctl(struct file *, unsigned int, unsigned long);
static void hw3d_vma_open(struct vm_area_struct *);
static void hw3d_vma_close(struct vm_area_struct *);
static struct file_operations hw3d_fops = {
.open = hw3d_open,
.release = hw3d_release,
.mmap = hw3d_mmap,
.flush = hw3d_flush,
.unlocked_ioctl = hw3d_ioctl,
};
static struct vm_operations_struct hw3d_vm_ops = {
.open = hw3d_vma_open,
.close = hw3d_vma_close,
};
static bool is_master(struct hw3d_info *info, struct file *file)
{
int fmin = MINOR(file->f_dentry->d_inode->i_rdev);
return fmin == MINOR(info->master_cdev.dev);
}
static bool is_client(struct hw3d_info *info, struct file *file)
{
int fmin = MINOR(file->f_dentry->d_inode->i_rdev);
return fmin == MINOR(info->client_cdev.dev);
}
inline static void locked_hw3d_irq_disable(struct hw3d_info *info)
{
if (info->irq_en) {
disable_irq_nosync(info->irq);
info->irq_en = 0;
}
}
inline static void locked_hw3d_irq_enable(struct hw3d_info *info)
{
if (!info->irq_en) {
enable_irq(info->irq);
info->irq_en = 1;
}
}
static void hw3d_disable_interrupt(struct hw3d_info *info)
{
unsigned long flags;
spin_lock_irqsave(&info->lock, flags);
locked_hw3d_irq_disable(info);
spin_unlock_irqrestore(&info->lock, flags);
}
static irqreturn_t hw3d_irq_handler(int irq, void *data)
{
struct hw3d_info *info = data;
unsigned long flags;
spin_lock_irqsave(&info->lock, flags);
locked_hw3d_irq_disable(info);
info->irq_pending = 1;
spin_unlock_irqrestore(&info->lock, flags);
wake_up(&info->irq_wq);
return IRQ_HANDLED;
}
static long hw3d_wait_for_interrupt(struct hw3d_info *info, struct file *filp)
{
struct hw3d_data *data = filp->private_data;
unsigned long flags;
int ret;
if (is_master(info, filp)) {
pr_err("%s: cannot wait for irqs on master node\n", __func__);
return -EPERM;
}
for (;;) {
spin_lock_irqsave(&info->lock, flags);
if (info->irq_pending) {
info->irq_pending = 0;
spin_unlock_irqrestore(&info->lock, flags);
return 0;
}
locked_hw3d_irq_enable(info);
spin_unlock_irqrestore(&info->lock, flags);
ret = wait_event_interruptible(info->irq_wq,
info->irq_pending ||
info->revoking ||
data->closing);
/* always make sure the irq gets disabled */
if (ret == 0 && !info->irq_pending)
ret = -EPIPE;
if (ret < 0) {
hw3d_disable_interrupt(info);
return ret;
}
}
return 0;
}
static long hw3d_wait_for_revoke(struct hw3d_info *info, struct file *filp)
{
struct hw3d_data *data = filp->private_data;
int ret;
if (is_master(info, filp)) {
pr_err("%s: cannot revoke on master node\n", __func__);
return -EPERM;
}
ret = wait_event_interruptible(info->revoke_wq,
info->revoking ||
data->closing);
if (ret == 0 && data->closing)
ret = -EPIPE;
if (ret < 0)
return ret;
return 0;
}
static void locked_hw3d_client_done(struct hw3d_info *info, int had_timer)
{
if (info->enabled) {
pr_debug("hw3d: was enabled\n");
info->enabled = 0;
clk_disable(info->grp_clk);
clk_disable(info->imem_clk);
}
info->revoking = 0;
/* double check that the irqs are disabled */
locked_hw3d_irq_disable(info);
if (had_timer)
wake_unlock(&info->wake_lock);
wake_up(&info->revoke_done_wq);
}
static void do_force_revoke(struct hw3d_info *info)
{
unsigned long flags;
/* at this point, the task had a chance to relinquish the gpu, but
* it hasn't. So, we kill it */
spin_lock_irqsave(&info->lock, flags);
pr_debug("hw3d: forcing revoke\n");
locked_hw3d_irq_disable(info);
if (info->client_task) {
pr_info("hw3d: force revoke from pid=%d\n",
info->client_task->pid);
force_sig(SIGKILL, info->client_task);
put_task_struct(info->client_task);
info->client_task = NULL;
}
locked_hw3d_client_done(info, 1);
pr_debug("hw3d: done forcing revoke\n");
spin_unlock_irqrestore(&info->lock, flags);
}
#define REVOKE_TIMEOUT (2 * HZ)
static void locked_hw3d_revoke(struct hw3d_info *info)
{
/* force us to wait to suspend until the revoke is done. If the
* user doesn't release the gpu, the timer will turn off the gpu,
* and force kill the process. */
wake_lock(&info->wake_lock);
info->revoking = 1;
wake_up(&info->revoke_wq);
mod_timer(&info->revoke_timer, jiffies + REVOKE_TIMEOUT);
}
bool is_msm_hw3d_file(struct file *file)
{
struct hw3d_info *info = hw3d_info;
if (MAJOR(file->f_dentry->d_inode->i_rdev) == MAJOR(info->devno) &&
(is_master(info, file) || is_client(info, file)))
return 1;
return 0;
}
void put_msm_hw3d_file(struct file *file)
{
if (!is_msm_hw3d_file(file))
return;
fput(file);
}
int get_msm_hw3d_file(int fd, uint32_t *offs, unsigned long *pbase,
unsigned long *len, struct file **filp)
{
struct hw3d_info *info = hw3d_info;
struct file *file;
struct hw3d_data *data;
uint32_t offset = HW3D_OFFSET_IN_REGION(*offs);
int region = HW3D_REGION_ID(*offs);
int ret = 0;
if (unlikely(region >= HW3D_NUM_REGIONS)) {
VDBG("hw3d: invalid region %d requested\n", region);
return -EINVAL;
} else if (unlikely(offset >= info->regions[region].size)) {
VDBG("hw3d: offset %08x outside of the requested region %d\n",
offset, region);
return -EINVAL;
}
file = fget(fd);
if (unlikely(file == NULL)) {
pr_info("%s: requested data from file descriptor that doesn't "
"exist.", __func__);
return -EINVAL;
} else if (!is_msm_hw3d_file(file)) {
ret = -1;
goto err;
}
data = file->private_data;
if (unlikely(!data)) {
VDBG("hw3d: invalid file\n");
ret = -EINVAL;
goto err;
}
mutex_lock(&data->mutex);
if (unlikely(!data->vmas[region])) {
mutex_unlock(&data->mutex);
VDBG("hw3d: requested hw3d region is not mapped\n");
ret = -ENOENT;
goto err;
}
*offs = offset;
*pbase = info->regions[region].pbase;
*filp = file;
*len = data->vmas[region]->vm_end - data->vmas[region]->vm_start;
mutex_unlock(&data->mutex);
return 0;
err:
fput(file);
return ret;
}
static int hw3d_flush(struct file *filp, fl_owner_t id)
{
struct hw3d_info *info = hw3d_info;
struct hw3d_data *data = filp->private_data;
if (!data) {
pr_err("%s: no private data\n", __func__);
return -EINVAL;
}
if (is_master(info, filp))
return 0;
pr_debug("hw3d: closing\n");
/* releases any blocked ioctls */
data->closing = 1;
wake_up(&info->revoke_wq);
wake_up(&info->irq_wq);
return 0;
}
static int should_wakeup(struct hw3d_info *info, unsigned int cnt)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&info->lock, flags);
ret = (cnt != info->waiter_cnt) ||
(!info->suspending && !info->client_file);
spin_unlock_irqrestore(&info->lock, flags);
return ret;
}
static int locked_open_wait_for_gpu(struct hw3d_info *info,
unsigned long *flags)
{
unsigned int my_cnt;
int ret;
my_cnt = ++info->waiter_cnt;
pr_debug("%s: wait_for_open %d\n", __func__, my_cnt);
/* in case there are other waiters, wake and release them. */
wake_up(&info->revoke_done_wq);
if (info->suspending)
pr_info("%s: suspended, waiting (%d %d)\n", __func__,
current->group_leader->pid, current->pid);
if (info->client_file)
pr_info("%s: has client, waiting (%d %d)\n", __func__,
current->group_leader->pid, current->pid);
spin_unlock_irqrestore(&info->lock, *flags);
ret = wait_event_interruptible(info->revoke_done_wq,
should_wakeup(info, my_cnt));
spin_lock_irqsave(&info->lock, *flags);
pr_debug("%s: woke up (%d %d %p)\n", __func__,
info->waiter_cnt, info->suspending, info->client_file);
if (ret >= 0) {
if (my_cnt != info->waiter_cnt) {
pr_info("%s: someone else asked for gpu after us %d:%d"
"(%d %d)\n", __func__,
current->group_leader->pid, current->pid,
my_cnt, info->waiter_cnt);
ret = -EBUSY;
} else if (info->suspending || info->client_file) {
pr_err("%s: couldn't get the gpu for %d:%d (%d %p)\n",
__func__, current->group_leader->pid,
current->pid, info->suspending,
info->client_file);
ret = -EBUSY;
} else
ret = 0;
}
return ret;
}
static int hw3d_open(struct inode *inode, struct file *file)
{
struct hw3d_info *info = hw3d_info;
struct hw3d_data *data;
unsigned long flags;
int ret = 0;
pr_info("%s: pid %d tid %d opening %s node\n", __func__,
current->group_leader->pid, current->pid,
is_master(info, file) ? "master" : "client");
if (file->private_data != NULL)
return -EINVAL;
data = kzalloc(sizeof(struct hw3d_data), GFP_KERNEL);
if (!data) {
pr_err("%s: unable to allocate memory for hw3d_data.\n",
__func__);
return -ENOMEM;
}
mutex_init(&data->mutex);
file->private_data = data;
/* master always succeeds, so we are done */
if (is_master(info, file))
return 0;
spin_lock_irqsave(&info->lock, flags);
if (info->client_file) {
pr_debug("hw3d: have client_file, need revoke\n");
locked_hw3d_revoke(info);
}
ret = locked_open_wait_for_gpu(info, &flags);
if (ret < 0) {
spin_unlock_irqrestore(&info->lock, flags);
goto err;
}
pr_info("%s: pid %d tid %d got gpu\n", __func__,
current->group_leader->pid, current->pid);
info->client_file = file;
get_task_struct(current->group_leader);
info->client_task = current->group_leader;
/* XXX: we enable these clocks if the client connects..
* probably not right? Should only turn the clocks on when the user
* tries to map the registers? */
clk_enable(info->imem_clk);
clk_enable(info->grp_clk);
info->enabled = 1;
spin_unlock_irqrestore(&info->lock, flags);
return 0;
err:
file->private_data = NULL;
kfree(data);
return ret;
}
static int hw3d_release(struct inode *inode, struct file *file)
{
struct hw3d_info *info = hw3d_info;
struct hw3d_data *data = file->private_data;
unsigned long flags;
BUG_ON(!data);
file->private_data = NULL;
if (is_master(info, file))
goto done;
pr_info("%s: in release for pid=%d tid=%d\n", __func__,
current->group_leader->pid, current->pid);
spin_lock_irqsave(&info->lock, flags);
if (info->client_task && info->client_task == current->group_leader) {
pr_debug("hw3d: releasing %d\n", info->client_task->pid);
put_task_struct(info->client_task);
info->client_task = NULL;
}
if (info->client_file && info->client_file == file) {
int pending;
/* this will be true if we are still the "owner" of the gpu */
pr_debug("hw3d: had file\n");
pending = del_timer(&info->revoke_timer);
locked_hw3d_client_done(info, pending);
info->client_file = NULL;
} else
pr_warning("hw3d: release without client_file.\n");
spin_unlock_irqrestore(&info->lock, flags);
done:
kfree(data);
return 0;
}
static void hw3d_vma_open(struct vm_area_struct *vma)
{
/* XXX: should the master be allowed to fork and keep the mappings? */
/* TODO: remap garbage page into here.
*
* For now, just pull the mapping. The user shouldn't be forking
* and using it anyway. */
zap_page_range(vma, vma->vm_start, vma->vm_end - vma->vm_start, NULL);
}
static void hw3d_vma_close(struct vm_area_struct *vma)
{
struct file *file = vma->vm_file;
struct hw3d_data *data = file->private_data;
int i;
pr_debug("hw3d: current %u ppid %u file %p count %ld\n",
current->pid, current->parent->pid, file, file_count(file));
BUG_ON(!data);
mutex_lock(&data->mutex);
for (i = 0; i < HW3D_NUM_REGIONS; ++i) {
if (data->vmas[i] == vma) {
data->vmas[i] = NULL;
goto done;
}
}
pr_warning("%s: vma %p not of ours during vma_close\n", __func__, vma);
done:
mutex_unlock(&data->mutex);
}
static int hw3d_mmap(struct file *file, struct vm_area_struct *vma)
{
struct hw3d_info *info = hw3d_info;
struct hw3d_data *data = file->private_data;
unsigned long vma_size = vma->vm_end - vma->vm_start;
int ret = 0;
int region = REGION_PAGE_ID(vma->vm_pgoff);
if (region >= HW3D_NUM_REGIONS) {
pr_err("%s: Trying to mmap unknown region %d\n", __func__,
region);
return -EINVAL;
} else if (vma_size > info->regions[region].size) {
pr_err("%s: VMA size %ld exceeds region %d size %ld\n",
__func__, vma_size, region,
info->regions[region].size);
return -EINVAL;
} else if (REGION_PAGE_OFFS(vma->vm_pgoff) != 0 ||
(vma_size & ~PAGE_MASK)) {
pr_err("%s: Can't remap part of the region %d\n", __func__,
region);
return -EINVAL;
} else if (!is_master(info, file) &&
current->group_leader != info->client_task) {
pr_err("%s: current(%d) != client_task(%d)\n", __func__,
current->group_leader->pid, info->client_task->pid);
return -EPERM;
} else if (!is_master(info, file) &&
(info->revoking || info->suspending)) {
pr_err("%s: cannot mmap while revoking(%d) or suspending(%d)\n",
__func__, info->revoking, info->suspending);
return -EPERM;
}
mutex_lock(&data->mutex);
if (data->vmas[region] != NULL) {
pr_err("%s: Region %d already mapped (pid=%d tid=%d)\n",
__func__, region, current->group_leader->pid,
current->pid);
ret = -EBUSY;
goto done;
}
/* our mappings are always noncached */
#ifdef pgprot_noncached
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
#endif
ret = io_remap_pfn_range(vma, vma->vm_start,
info->regions[region].pbase >> PAGE_SHIFT,
vma_size, vma->vm_page_prot);
if (ret) {
pr_err("%s: Cannot remap page range for region %d!\n", __func__,
region);
ret = -EAGAIN;
goto done;
}
/* Prevent a malicious client from stealing another client's data
* by forcing a revoke on it and then mmapping the GPU buffers.
*/
if (region != HW3D_REGS)
memset(info->regions[region].vbase, 0,
info->regions[region].size);
vma->vm_ops = &hw3d_vm_ops;
/* mark this region as mapped */
data->vmas[region] = vma;
done:
mutex_unlock(&data->mutex);
return ret;
}
static long hw3d_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct hw3d_info *info = hw3d_info;
struct hw3d_region regions[HW3D_NUM_REGIONS];
int i;
if (!file->private_data)
return -EINVAL;
switch (cmd) {
case HW3D_WAIT_FOR_REVOKE:
return hw3d_wait_for_revoke(info, file);
case HW3D_WAIT_FOR_INTERRUPT:
return hw3d_wait_for_interrupt(info, file);
case HW3D_GET_REGIONS:
for (i = 0; i < HW3D_NUM_REGIONS; ++i) {
regions[i].phys = info->regions[i].pbase;
regions[i].map_offset = HW3D_REGION_OFFSET(i);
regions[i].len = info->regions[i].size;
}
if (copy_to_user((void __user *)arg, regions, sizeof(regions)))
return -EFAULT;
break;
default:
return -EINVAL;
}
return 0;
}
static void hw3d_early_suspend(struct early_suspend *h)
{
unsigned long flags;
struct hw3d_info *info;
info = container_of(h, struct hw3d_info, early_suspend);
spin_lock_irqsave(&info->lock, flags);
info->suspending = 1;
if (info->client_file) {
pr_info("hw3d: Requesting revoke for suspend\n");
locked_hw3d_revoke(info);
}
spin_unlock_irqrestore(&info->lock, flags);
}
static void hw3d_late_resume(struct early_suspend *h)
{
unsigned long flags;
struct hw3d_info *info;
info = container_of(h, struct hw3d_info, early_suspend);
spin_lock_irqsave(&info->lock, flags);
if (info->suspending)
pr_info("%s: resuming\n", __func__);
info->suspending = 0;
wake_up(&info->revoke_done_wq);
spin_unlock_irqrestore(&info->lock, flags);
}
static int hw3d_resume(struct platform_device *pdev)
{
struct hw3d_info *info = platform_get_drvdata(pdev);
unsigned long flags;
spin_lock_irqsave(&info->lock, flags);
if (info->suspending)
pr_info("%s: resuming\n", __func__);
info->suspending = 0;
spin_unlock_irqrestore(&info->lock, flags);
return 0;
}
static int __init hw3d_probe(struct platform_device *pdev)
{
struct hw3d_info *info;
#define DEV_MASTER MKDEV(MAJOR(info->devno), MINOR_MASTER)
#define DEV_CLIENT MKDEV(MAJOR(info->devno), MINOR_CLIENT)
struct class *hw3d_class;
struct device *master_dev;
struct device *client_dev;
struct resource *res[HW3D_NUM_REGIONS];
int i;
int irq;
int ret = 0;
res[HW3D_REGS] = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"regs");
res[HW3D_SMI] = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"smi");
res[HW3D_EBI] = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"ebi");
irq = platform_get_irq(pdev, 0);
if (!res[HW3D_REGS] || !res[HW3D_SMI] || !res[HW3D_EBI] || irq < 0) {
pr_err("%s: incomplete resources\n", __func__);
return -EINVAL;
}
info = kzalloc(sizeof(struct hw3d_info), GFP_KERNEL);
if (info == NULL) {
pr_err("%s: Cannot allocate memory for hw3d_info\n", __func__);
ret = -ENOMEM;
goto err_alloc;
}
info->irq = irq;
wake_lock_init(&info->wake_lock, WAKE_LOCK_SUSPEND, "hw3d_revoke_lock");
spin_lock_init(&info->lock);
init_waitqueue_head(&info->irq_wq);
init_waitqueue_head(&info->revoke_wq);
init_waitqueue_head(&info->revoke_done_wq);
setup_timer(&info->revoke_timer,
(void (*)(unsigned long))do_force_revoke,
(unsigned long)info);
platform_set_drvdata(pdev, info);
info->grp_clk = clk_get(NULL, "grp_clk");
if (IS_ERR(info->grp_clk)) {
pr_err("%s: Cannot get grp_clk\n", __func__);
ret = PTR_ERR(info->grp_clk);
goto err_get_grp_clk;
}
info->imem_clk = clk_get(NULL, "imem_clk");
if (IS_ERR(info->imem_clk)) {
pr_err("%s: Cannot get imem_clk\n", __func__);
ret = PTR_ERR(info->imem_clk);
goto err_get_imem_clk;
}
for (i = 0; i < HW3D_NUM_REGIONS; ++i) {
info->regions[i].pbase = res[i]->start;
info->regions[i].size = res[i]->end - res[i]->start + 1;
info->regions[i].vbase = ioremap(info->regions[i].pbase,
info->regions[i].size);
if (info->regions[i].vbase == 0) {
pr_err("%s: Cannot remap region %d\n", __func__, i);
goto err_remap_region;
}
}
hw3d_class = class_create(THIS_MODULE, "msm_hw3d");
if (IS_ERR(hw3d_class))
goto err_fail_create_class;
ret = alloc_chrdev_region(&info->devno, 0, 2, "msm_hw3d");
if (ret < 0)
goto err_fail_alloc_region;
/* register the master/client devices */
master_dev = device_create(hw3d_class, &pdev->dev,
DEV_MASTER, "%s", "msm_hw3dm");
if (IS_ERR(master_dev))
goto err_dev_master;
cdev_init(&info->master_cdev, &hw3d_fops);
info->master_cdev.owner = THIS_MODULE;
ret = cdev_add(&info->master_cdev, DEV_MASTER, 1);
if (ret < 0) {
pr_err("%s: Cannot register master device node\n", __func__);
goto err_reg_master;
}
client_dev = device_create(hw3d_class, &pdev->dev,
DEV_CLIENT, "%s", "msm_hw3dc");
if (IS_ERR(client_dev))
goto err_dev_client;
cdev_init(&info->client_cdev, &hw3d_fops);
info->client_cdev.owner = THIS_MODULE;
ret = cdev_add(&info->client_cdev, DEV_CLIENT, 1);
if (ret < 0) {
pr_err("%s: Cannot register client device node\n", __func__);
goto err_reg_client;
}
info->early_suspend.suspend = hw3d_early_suspend;
info->early_suspend.resume = hw3d_late_resume;
info->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB;
register_early_suspend(&info->early_suspend);
info->irq_en = 1;
ret = request_irq(info->irq, hw3d_irq_handler, IRQF_TRIGGER_HIGH,
"hw3d", info);
if (ret != 0) {
pr_err("%s: Cannot request irq\n", __func__);
goto err_req_irq;
}
hw3d_disable_interrupt(info);
hw3d_info = info;
return 0;
err_req_irq:
unregister_early_suspend(&info->early_suspend);
cdev_del(&info->client_cdev);
err_reg_client:
device_destroy(hw3d_class, DEV_CLIENT);
err_dev_client:
cdev_del(&info->master_cdev);
err_reg_master:
device_destroy(hw3d_class, DEV_MASTER);
err_dev_master:
unregister_chrdev_region(info->devno, 2);
err_fail_alloc_region:
class_unregister(hw3d_class);
err_fail_create_class:
err_remap_region:
for (i = 0; i < HW3D_NUM_REGIONS; ++i)
if (info->regions[i].vbase != 0)
iounmap(info->regions[i].vbase);
clk_put(info->imem_clk);
err_get_imem_clk:
clk_put(info->grp_clk);
err_get_grp_clk:
wake_lock_destroy(&info->wake_lock);
kfree(info);
platform_set_drvdata(pdev, NULL);
err_alloc:
hw3d_info = NULL;
return ret;
}
static struct platform_driver msm_hw3d_driver = {
.probe = hw3d_probe,
.resume = hw3d_resume,
.driver = {
.name = "msm_hw3d",
.owner = THIS_MODULE,
},
};
static int __init hw3d_init(void)
{
return platform_driver_register(&msm_hw3d_driver);
}
device_initcall(hw3d_init);