video: tegra: host: Do not access cmdbuf_ext if NULL
[linux-3.10.git] / drivers / video / tegra / host / bus_client.c
index d3a12a7..030e28c 100644 (file)
@@ -1,9 +1,7 @@
 /*
- * drivers/video/tegra/host/bus_client.c
- *
  * Tegra Graphics Host Client Module
  *
- * Copyright (c) 2010-2013, NVIDIA Corporation. All rights reserved.
+ * Copyright (c) 2010-2014, NVIDIA Corporation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms and conditions of the GNU General Public License,
@@ -56,6 +54,7 @@
 #include "nvhost_job.h"
 #include "nvhost_hwctx.h"
 #include "user_hwctx.h"
+#include "nvhost_sync.h"
 
 static int validate_reg(struct platform_device *ndev, u32 offset, int count)
 {
@@ -87,6 +86,23 @@ static __iomem void *get_aperture(struct platform_device *pdev)
        return pdata->aperture[0];
 }
 
+void host1x_writel(struct platform_device *pdev, u32 r, u32 v)
+{
+       void __iomem *addr = get_aperture(pdev) + r;
+       nvhost_dbg(dbg_reg, " d=%s r=0x%x v=0x%x", pdev->name, r, v);
+       writel(v, addr);
+}
+EXPORT_SYMBOL_GPL(host1x_writel);
+
+u32 host1x_readl(struct platform_device *pdev, u32 r)
+{
+       void __iomem *addr = get_aperture(pdev) + r;
+       u32 v = readl(addr);
+       nvhost_dbg(dbg_reg, " d=%s r=0x%x v=0x%x", pdev->name, r, v);
+       return v;
+}
+EXPORT_SYMBOL_GPL(host1x_readl);
+
 int nvhost_read_module_regs(struct platform_device *ndev,
                        u32 offset, int count, u32 *values)
 {
@@ -139,22 +155,6 @@ int nvhost_write_module_regs(struct platform_device *ndev,
        return 0;
 }
 
-bool nvhost_client_can_writel(struct platform_device *pdev)
-{
-       return !!get_aperture(pdev);
-}
-EXPORT_SYMBOL(nvhost_client_can_writel);
-
-void nvhost_client_writel(struct platform_device *pdev, u32 val, u32 reg)
-{
-       writel(val, get_aperture(pdev) + reg * 4);
-}
-
-u32 nvhost_client_readl(struct platform_device *pdev, u32 reg)
-{
-       return readl(get_aperture(pdev) + reg * 4);
-}
-
 struct nvhost_channel_userctx {
        struct nvhost_channel *ch;
        struct nvhost_hwctx *hwctx;
@@ -217,7 +217,7 @@ static int nvhost_channelopen(struct inode *inode, struct file *filp)
        }
        filp->private_data = priv;
        priv->ch = ch;
-       if(nvhost_module_add_client(ch->dev, priv))
+       if (nvhost_module_add_client(ch->dev, priv))
                goto fail;
 
        if (ch->ctxhandler && ch->ctxhandler->alloc) {
@@ -230,12 +230,11 @@ static int nvhost_channelopen(struct inode *inode, struct file *filp)
        priv->priority = NVHOST_PRIORITY_MEDIUM;
        priv->clientid = atomic_add_return(1,
                        &nvhost_get_host(ch->dev)->clientid);
-       pdata = platform_get_drvdata(ch->dev);
+       pdata = dev_get_drvdata(ch->dev->dev.parent);
        priv->timeout = pdata->nvhost_timeout_default;
        priv->timeout_debug_dump = true;
        if (!tegra_platform_is_silicon())
                priv->timeout = 0;
-
        return 0;
 fail:
        nvhost_channelrelease(inode, filp);
@@ -248,9 +247,9 @@ static int nvhost_ioctl_channel_alloc_obj_ctx(
 {
        int ret;
 
-       BUG_ON(!channel_op().alloc_obj);
+       BUG_ON(!channel_op(ctx->ch).alloc_obj);
        nvhost_module_busy(ctx->ch->dev);
-       ret = channel_op().alloc_obj(ctx->hwctx, args);
+       ret = channel_op(ctx->ch).alloc_obj(ctx->hwctx, args);
        nvhost_module_idle(ctx->ch->dev);
        return ret;
 }
@@ -261,9 +260,9 @@ static int nvhost_ioctl_channel_free_obj_ctx(
 {
        int ret;
 
-       BUG_ON(!channel_op().free_obj);
+       BUG_ON(!channel_op(ctx->ch).free_obj);
        nvhost_module_busy(ctx->ch->dev);
-       ret = channel_op().free_obj(ctx->hwctx, args);
+       ret = channel_op(ctx->ch).free_obj(ctx->hwctx, args);
        nvhost_module_idle(ctx->ch->dev);
        return ret;
 }
@@ -274,13 +273,23 @@ static int nvhost_ioctl_channel_alloc_gpfifo(
 {
        int ret;
 
-       BUG_ON(!channel_op().alloc_gpfifo);
+       BUG_ON(!channel_op(ctx->ch).alloc_gpfifo);
        nvhost_module_busy(ctx->ch->dev);
-       ret = channel_op().alloc_gpfifo(ctx->hwctx, args);
+       ret = channel_op(ctx->ch).alloc_gpfifo(ctx->hwctx, args);
        nvhost_module_idle(ctx->ch->dev);
        return ret;
 }
 
+static int nvhost_ioctl_channel_set_error_notifier(
+       struct nvhost_channel_userctx *ctx,
+       struct nvhost_set_error_notifier *args)
+{
+       int ret;
+       BUG_ON(!channel_op(ctx->ch).set_error_notifier);
+       ret = channel_op(ctx->ch).set_error_notifier(ctx->hwctx, args);
+       return ret;
+}
+
 static int nvhost_ioctl_channel_submit_gpfifo(
        struct nvhost_channel_userctx *ctx,
        struct nvhost_submit_gpfifo_args *args)
@@ -304,10 +313,10 @@ static int nvhost_ioctl_channel_submit_gpfifo(
                goto clean_up;
        }
 
-       BUG_ON(!channel_op().submit_gpfifo);
+       BUG_ON(!channel_op(ctx->ch).submit_gpfifo);
 
        nvhost_module_busy(ctx->ch->dev);
-       ret = channel_op().submit_gpfifo(ctx->hwctx, gpfifo,
+       ret = channel_op(ctx->ch).submit_gpfifo(ctx->hwctx, gpfifo,
                        args->num_entries, &args->fence, args->flags);
        nvhost_module_idle(ctx->ch->dev);
 clean_up:
@@ -321,22 +330,35 @@ static int nvhost_ioctl_channel_wait(
 {
        int ret;
 
-       BUG_ON(!channel_op().wait);
+       BUG_ON(!channel_op(ctx->ch).wait);
        nvhost_module_busy(ctx->ch->dev);
-       ret = channel_op().wait(ctx->hwctx, args);
+       ret = channel_op(ctx->ch).wait(ctx->hwctx, args);
        nvhost_module_idle(ctx->ch->dev);
        return ret;
 }
 
+static int nvhost_ioctl_channel_set_priority(
+       struct nvhost_channel_userctx *ctx,
+       struct nvhost_set_priority_args *args)
+{
+       int ret = 0;
+       if (channel_op(ctx->ch).set_priority) {
+               nvhost_module_busy(ctx->ch->dev);
+               ret = channel_op(ctx->ch).set_priority(ctx->hwctx, args);
+               nvhost_module_idle(ctx->ch->dev);
+       }
+       return ret;
+}
+
 static int nvhost_ioctl_channel_zcull_bind(
        struct nvhost_channel_userctx *ctx,
        struct nvhost_zcull_bind_args *args)
 {
        int ret;
 
-       BUG_ON(!channel_zcull_op().bind);
+       BUG_ON(!channel_zcull_op(ctx->ch).bind);
        nvhost_module_busy(ctx->ch->dev);
-       ret = channel_zcull_op().bind(ctx->hwctx, args);
+       ret = channel_zcull_op(ctx->ch).bind(ctx->hwctx, args);
        nvhost_module_idle(ctx->ch->dev);
        return ret;
 }
@@ -351,6 +373,8 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
        int num_syncpt_incrs = args->num_syncpt_incrs;
        struct nvhost_cmdbuf __user *cmdbufs =
                (struct nvhost_cmdbuf *)(uintptr_t)args->cmdbufs;
+       struct nvhost_cmdbuf __user *cmdbuf_exts =
+               (struct nvhost_cmdbuf *)(uintptr_t)args->cmdbuf_exts;
        struct nvhost_reloc __user *relocs =
                (struct nvhost_reloc *)(uintptr_t)args->relocs;
        struct nvhost_reloc_shift __user *reloc_shifts =
@@ -361,9 +385,10 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
                (struct nvhost_syncpt_incr *)(uintptr_t)args->syncpt_incrs;
        u32 __user *waitbases = (u32 *)(uintptr_t)args->waitbases;
        u32 __user *fences = (u32 *)(uintptr_t)args->fences;
+       u32 __user *class_ids = (u32 *)(uintptr_t)args->class_ids;
 
        struct nvhost_master *host = nvhost_get_host(ctx->ch->dev);
-       u32 *local_waitbases = NULL;
+       u32 *local_waitbases = NULL, *local_class_ids = NULL;
        int err, i, hwctx_syncpt_idx = -1;
 
        if (num_syncpt_incrs > host->info.nb_pts)
@@ -385,17 +410,46 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
        job->priority = ctx->priority;
        job->clientid = ctx->clientid;
 
-       while (num_cmdbufs) {
+       /* mass copy class_ids */
+       if (args->class_ids) {
+               local_class_ids = kzalloc(sizeof(u32) * num_cmdbufs,
+                       GFP_KERNEL);
+               if (!local_class_ids) {
+                       err = -ENOMEM;
+                       goto fail;
+               }
+               err = copy_from_user(local_class_ids, class_ids,
+                       sizeof(u32) * num_cmdbufs);
+               if (err) {
+                       err = -EINVAL;
+                       goto fail;
+               }
+       }
+
+       for (i = 0; i < num_cmdbufs; ++i) {
                struct nvhost_cmdbuf cmdbuf;
-               err = copy_from_user(&cmdbuf, cmdbufs, sizeof(cmdbuf));
+               struct nvhost_cmdbuf_ext cmdbuf_ext;
+               u32 class_id = class_ids ? local_class_ids[i] : 0;
+
+               err = copy_from_user(&cmdbuf, cmdbufs + i, sizeof(cmdbuf));
                if (err)
                        goto fail;
-               nvhost_job_add_gather(job,
-                               cmdbuf.mem, cmdbuf.words, cmdbuf.offset);
-               num_cmdbufs--;
-               cmdbufs++;
+
+               cmdbuf_ext.pre_fence = -1;
+               if (cmdbuf_exts)
+                       err = copy_from_user(&cmdbuf_ext,
+                                       cmdbuf_exts + i, sizeof(cmdbuf_ext));
+               if (err)
+                       cmdbuf_ext.pre_fence = -1;
+
+               nvhost_job_add_gather(job, cmdbuf.mem, cmdbuf.words,
+                                     cmdbuf.offset, class_id,
+                                     cmdbuf_ext.pre_fence);
        }
 
+       kfree(local_class_ids);
+       local_class_ids = NULL;
+
        err = copy_from_user(job->relocarray,
                        relocs, sizeof(*relocs) * num_relocs);
        if (err)
@@ -428,8 +482,8 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
                }
        }
 
-       /* set valid id for hwctx_syncpt_idx if no hwctx is present */
-       if (!ctx->hwctx)
+       /* set valid id for hwctx_syncpt_idx if hwctx does not provide one */
+       if (!ctx->hwctx || ctx->hwctx->h->syncpt == NVSYNCPT_INVALID)
                hwctx_syncpt_idx = 0;
 
        /*
@@ -489,7 +543,9 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
                job->sp[job->hwctx_syncpt_idx].id,
                job->sp[job->hwctx_syncpt_idx].incrs);
 
+       nvhost_module_busy(ctx->ch->dev);
        err = nvhost_job_pin(job, &nvhost_get_host(ctx->ch->dev)->syncpt);
+       nvhost_module_idle(ctx->ch->dev);
        if (err)
                goto fail;
 
@@ -516,7 +572,20 @@ static int nvhost_ioctl_channel_submit(struct nvhost_channel_userctx *ctx,
        /* Deliver the fence using the old mechanism _only_ if a single
         * syncpoint is used. */
 
-       if (num_syncpt_incrs == 1)
+       if (args->flags & BIT(NVHOST_SUBMIT_FLAG_SYNC_FENCE_FD)) {
+               struct nvhost_ctrl_sync_fence_info pts[num_syncpt_incrs];
+
+               for (i = 0; i < num_syncpt_incrs; i++) {
+                       pts[i].id = job->sp[i].id;
+                       pts[i].thresh = job->sp[i].fence;
+               }
+
+               err = nvhost_sync_create_fence(
+                               &nvhost_get_host(ctx->ch->dev)->syncpt,
+                               pts, num_syncpt_incrs, "fence", &args->fence);
+               if (err)
+                       goto fail;
+       } else if (num_syncpt_incrs == 1)
                args->fence = job->sp[job->hwctx_syncpt_idx].fence;
        else
                args->fence = 0;
@@ -529,6 +598,7 @@ fail_submit:
        nvhost_job_unpin(job);
 fail:
        nvhost_job_put(job);
+       kfree(local_class_ids);
        kfree(local_waitbases);
        return err;
 }
@@ -655,14 +725,14 @@ fail:
        return err;
 }
 
-#if defined(CONFIG_TEGRA_GPU_CYCLE_STATS)
+#if defined(CONFIG_GK20A_CYCLE_STATS)
 static int nvhost_ioctl_channel_cycle_stats(
        struct nvhost_channel_userctx *ctx,
        struct nvhost_cycle_stats_args *args)
 {
        int ret;
-       BUG_ON(!channel_op().cycle_stats);
-       ret = channel_op().cycle_stats(ctx->hwctx, args);
+       BUG_ON(!channel_op(ctx->ch).cycle_stats);
+       ret = channel_op(ctx->ch).cycle_stats(ctx->hwctx, args);
        return ret;
 }
 #endif
@@ -776,7 +846,7 @@ static u32 create_mask(u32 *words, int num)
 {
        int i;
        u32 word = 0;
-       for (i = 0; i < num && words[i] && words[i] < BITS_PER_LONG; i++)
+       for (i = 0; i < num && words[i] && words[i] < 32; i++)
                word |= BIT(words[i]);
 
        return word;
@@ -901,7 +971,11 @@ static long nvhost_channelctl(struct file *filp,
        case NVHOST_IOCTL_CHANNEL_ZCULL_BIND:
                err = nvhost_ioctl_channel_zcull_bind(priv, (void *)buf);
                break;
-#if defined(CONFIG_TEGRA_GPU_CYCLE_STATS)
+       case NVHOST_IOCTL_CHANNEL_SET_ERROR_NOTIFIER:
+               err = nvhost_ioctl_channel_set_error_notifier(priv,
+                       (void *)buf);
+               break;
+#if defined(CONFIG_GK20A_CYCLE_STATS)
        case NVHOST_IOCTL_CHANNEL_CYCLE_STATS:
                err = nvhost_ioctl_channel_cycle_stats(priv, (void *)buf);
                break;
@@ -927,36 +1001,92 @@ static long nvhost_channelctl(struct file *filp,
                break;
        }
        case NVHOST_IOCTL_CHANNEL_SET_TIMEOUT:
-               priv->timeout =
+       {
+               u32 timeout =
                        (u32)((struct nvhost_set_timeout_args *)buf)->timeout;
+
+               priv->timeout = timeout;
                dev_dbg(&priv->ch->dev->dev,
                        "%s: setting buffer timeout (%d ms) for userctx 0x%p\n",
                        __func__, priv->timeout, priv);
+               if (priv->hwctx)
+                       priv->hwctx->timeout_ms_max = timeout;
                break;
+       }
        case NVHOST_IOCTL_CHANNEL_GET_TIMEDOUT:
                ((struct nvhost_get_param_args *)buf)->value =
                                priv->hwctx->has_timedout;
                break;
        case NVHOST_IOCTL_CHANNEL_SET_PRIORITY:
+               nvhost_ioctl_channel_set_priority(priv, (void *)buf);
                priv->priority =
                        (u32)((struct nvhost_set_priority_args *)buf)->priority;
                break;
+       case NVHOST32_IOCTL_CHANNEL_MODULE_REGRDWR:
+       {
+               struct nvhost32_ctrl_module_regrdwr_args *args32 =
+                       (struct nvhost32_ctrl_module_regrdwr_args *)buf;
+               struct nvhost_ctrl_module_regrdwr_args args;
+               args.id = args32->id;
+               args.num_offsets = args32->num_offsets;
+               args.block_size = args32->block_size;
+               args.offsets = args32->offsets;
+               args.values = args32->values;
+               args.write = args32->write;
+               err = nvhost_ioctl_channel_module_regrdwr(priv, &args);
+               break;
+       }
        case NVHOST_IOCTL_CHANNEL_MODULE_REGRDWR:
                err = nvhost_ioctl_channel_module_regrdwr(priv, (void *)buf);
                break;
+       case NVHOST32_IOCTL_CHANNEL_SUBMIT:
+       {
+               struct nvhost32_submit_args *args32 = (void *)buf;
+               struct nvhost_submit_args args;
+
+               memset(&args, 0, sizeof(args));
+               args.submit_version = args32->submit_version;
+               args.num_syncpt_incrs = args32->num_syncpt_incrs;
+               args.num_cmdbufs = args32->num_cmdbufs;
+               args.num_relocs = args32->num_relocs;
+               args.num_waitchks = args32->num_waitchks;
+               args.timeout = args32->timeout;
+               args.syncpt_incrs = args32->syncpt_incrs;
+               args.fence = args32->fence;
+
+               args.cmdbufs = args32->cmdbufs;
+               args.relocs = args32->relocs;
+               args.reloc_shifts = args32->reloc_shifts;
+               args.waitchks = args32->waitchks;
+               args.waitbases = args32->waitbases;
+               args.class_ids = args32->class_ids;
+               args.fences = args32->fences;
+
+               err = nvhost_ioctl_channel_submit(priv, &args);
+               args32->fence = args.fence;
+               break;
+       }
        case NVHOST_IOCTL_CHANNEL_SUBMIT:
                err = nvhost_ioctl_channel_submit(priv, (void *)buf);
                break;
        case NVHOST_IOCTL_CHANNEL_SET_TIMEOUT_EX:
-               priv->timeout = (u32)
-                       ((struct nvhost_set_timeout_ex_args *)buf)->timeout;
-               priv->timeout_debug_dump = !((u32)
+       {
+               u32 timeout =
+                       (u32)((struct nvhost_set_timeout_args *)buf)->timeout;
+               bool timeout_debug_dump = !((u32)
                        ((struct nvhost_set_timeout_ex_args *)buf)->flags &
                        (1 << NVHOST_TIMEOUT_FLAG_DISABLE_DUMP));
+               priv->timeout = timeout;
+               priv->timeout_debug_dump = timeout_debug_dump;
                dev_dbg(&priv->ch->dev->dev,
                        "%s: setting buffer timeout (%d ms) for userctx 0x%p\n",
                        __func__, priv->timeout, priv);
+               if (priv->hwctx) {
+                       priv->hwctx->timeout_ms_max = timeout;
+                       priv->hwctx->timeout_debug_dump = timeout_debug_dump;
+               }
                break;
+       }
        case NVHOST_IOCTL_CHANNEL_SET_CTXSWITCH:
                err = nvhost_ioctl_channel_set_ctxswitch(priv, (void *)buf);
                break;
@@ -976,6 +1106,9 @@ static const struct file_operations nvhost_channelops = {
        .owner = THIS_MODULE,
        .release = nvhost_channelrelease,
        .open = nvhost_channelopen,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl = nvhost_channelctl,
+#endif
        .unlocked_ioctl = nvhost_channelctl
 };
 
@@ -1001,6 +1134,9 @@ static const struct file_operations nvhost_asops = {
        .owner = THIS_MODULE,
        .release = nvhost_as_dev_release,
        .open = nvhost_as_dev_open,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl = nvhost_as_dev_ctl,
+#endif
        .unlocked_ioctl = nvhost_as_dev_ctl,
 };
 
@@ -1102,9 +1238,10 @@ int nvhost_client_user_init(struct platform_device *dev)
        struct nvhost_channel *ch = pdata->channel;
 
        BUG_ON(!ch);
-       // reserve 3 minor #s for <dev> and as-<dev> and ctrl-<dev>
+       /* reserve 5 minor #s for <dev> and as-<dev>, ctrl-<dev>,
+        * dbg-<dev> and prof-<dev> */
 
-       err = alloc_chrdev_region(&devno, 0, 3, IFACE_NAME);
+       err = alloc_chrdev_region(&devno, 0, 5, IFACE_NAME);
        if (err < 0) {
                dev_err(&dev->dev, "failed to allocate devno\n");
                goto fail;
@@ -1114,12 +1251,15 @@ int nvhost_client_user_init(struct platform_device *dev)
                                "", devno, &nvhost_channelops);
        if (ch->node == NULL)
                goto fail;
-       ++devno;
-       ch->as_node = nvhost_client_device_create(dev, &ch->as_cdev,
-                               "as-", devno, &nvhost_asops);
-       if (ch->as_node == NULL)
-               goto fail;
+       if (pdata->as_ops) {
+               ++devno;
+               ch->as_node = nvhost_client_device_create(dev, &ch->as_cdev,
+                                       "as-", devno, &nvhost_asops);
+               if (ch->as_node == NULL)
+                       goto fail;
+       }
 
+       /* module control (npn-channel based, global) interface */
        if (pdata->ctrl_ops) {
                ++devno;
                pdata->ctrl_node = nvhost_client_device_create(dev,
@@ -1129,11 +1269,70 @@ int nvhost_client_user_init(struct platform_device *dev)
                        goto fail;
        }
 
+       /* module debugger interface (per channel and global) */
+       if (pdata->dbg_ops) {
+               ++devno;
+               pdata->dbg_node = nvhost_client_device_create(dev,
+                                       &pdata->dbg_cdev, "dbg-",
+                                       devno, pdata->dbg_ops);
+               if (pdata->dbg_node == NULL)
+                       goto fail;
+       }
+
+       /* module profiler interface (per channel and global) */
+       if (pdata->prof_ops) {
+               ++devno;
+               pdata->prof_node = nvhost_client_device_create(dev,
+                                       &pdata->prof_cdev, "prof-",
+                                       devno, pdata->prof_ops);
+               if (pdata->prof_node == NULL)
+                       goto fail;
+       }
+
+
+
        return 0;
 fail:
        return err;
 }
 
+void nvhost_client_user_deinit(struct platform_device *dev)
+{
+       struct nvhost_master *nvhost_master = nvhost_get_host(dev);
+       struct nvhost_device_data *pdata = platform_get_drvdata(dev);
+       struct nvhost_channel *ch = pdata->channel;
+
+       BUG_ON(!ch);
+
+       if (ch->node) {
+               device_destroy(nvhost_master->nvhost_class, ch->cdev.dev);
+               cdev_del(&ch->cdev);
+       }
+
+       if (ch->as_node) {
+               device_destroy(nvhost_master->nvhost_class, ch->as_cdev.dev);
+               cdev_del(&ch->as_cdev);
+       }
+
+       if (pdata->ctrl_node) {
+               device_destroy(nvhost_master->nvhost_class,
+                              pdata->ctrl_cdev.dev);
+               cdev_del(&pdata->ctrl_cdev);
+       }
+
+       if (pdata->dbg_node) {
+               device_destroy(nvhost_master->nvhost_class,
+                              pdata->dbg_cdev.dev);
+               cdev_del(&pdata->dbg_cdev);
+       }
+
+       if (pdata->prof_node) {
+               device_destroy(nvhost_master->nvhost_class,
+                              pdata->prof_cdev.dev);
+               cdev_del(&pdata->prof_cdev);
+       }
+}
+
 int nvhost_client_device_init(struct platform_device *dev)
 {
        int err;
@@ -1153,7 +1352,7 @@ int nvhost_client_device_init(struct platform_device *dev)
 
        err = nvhost_channel_init(ch, nvhost_master, pdata->index);
        if (err)
-               goto fail;
+               goto fail1;
 
        err = nvhost_client_user_init(dev);
        if (err)
@@ -1180,15 +1379,23 @@ int nvhost_client_device_init(struct platform_device *dev)
 
        dev_info(&dev->dev, "initialized\n");
 
-       if (pdata->slave) {
+       if (pdata->slave && !pdata->slave_initialized) {
+               struct nvhost_device_data *slave_pdata =
+                                       pdata->slave->dev.platform_data;
+               slave_pdata->master = dev;
                pdata->slave->dev.parent = dev->dev.parent;
                platform_device_register(pdata->slave);
+               pdata->slave_initialized = 1;
        }
 
        return 0;
 
 fail:
        /* Add clean-up */
+       dev_err(&dev->dev, "failed to init client device\n");
+       nvhost_client_user_deinit(dev);
+fail1:
+       nvhost_device_debug_deinit(dev);
        nvhost_free_channel(ch);
        return err;
 }
@@ -1196,7 +1403,6 @@ EXPORT_SYMBOL(nvhost_client_device_init);
 
 int nvhost_client_device_release(struct platform_device *dev)
 {
-       struct nvhost_master *nvhost_master = nvhost_get_host(dev);
        struct nvhost_channel *ch;
        struct nvhost_device_data *pdata = platform_get_drvdata(dev);
 
@@ -1209,8 +1415,10 @@ int nvhost_client_device_release(struct platform_device *dev)
        nvhost_device_list_remove(dev);
 
        /* Release chardev and device node for user space */
-       device_destroy(nvhost_master->nvhost_class, ch->cdev.dev);
-       cdev_del(&ch->cdev);
+       nvhost_client_user_deinit(dev);
+
+       /* Remove debugFS */
+       nvhost_device_debug_deinit(dev);
 
        /* Free nvhost channel */
        nvhost_free_channel(ch);
@@ -1219,33 +1427,6 @@ int nvhost_client_device_release(struct platform_device *dev)
 }
 EXPORT_SYMBOL(nvhost_client_device_release);
 
-int nvhost_client_device_suspend(struct device *dev)
-{
-       int ret = 0;
-       struct nvhost_device_data *pdata = dev_get_drvdata(dev);
-
-       ret = nvhost_module_suspend(dev);
-       if (ret)
-               return ret;
-
-       ret = nvhost_channel_suspend(pdata->channel);
-       if (ret)
-               return ret;
-
-       dev_info(dev, "suspend status: %d\n", ret);
-
-       return ret;
-}
-EXPORT_SYMBOL(nvhost_client_device_suspend);
-
-int nvhost_client_device_resume(struct device *dev)
-{
-       nvhost_module_resume(dev);
-       dev_info(dev, "resuming\n");
-       return 0;
-}
-EXPORT_SYMBOL(nvhost_client_device_resume);
-
 int nvhost_client_device_get_resources(struct platform_device *dev)
 {
        int i;