Merge tag 'drm-misc-next-2017-05-16' of git://anongit.freedesktop.org/git/drm-misc...
[sfrench/cifs-2.6.git] / drivers / gpu / drm / tegra / drm.c
index dba4e090d3dff8e07828ae9d293c77f60d8d6e0f..51c48a8e00ec018fc5a21a340ed9144d07d1a198 100644 (file)
@@ -1,13 +1,15 @@
 /*
  * Copyright (C) 2012 Avionic Design GmbH
- * Copyright (C) 2012-2013 NVIDIA CORPORATION.  All rights reserved.
+ * Copyright (C) 2012-2016 NVIDIA CORPORATION.  All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
 
+#include <linux/bitops.h>
 #include <linux/host1x.h>
+#include <linux/idr.h>
 #include <linux/iommu.h>
 
 #include <drm/drm_atomic.h>
 #define DRIVER_MINOR 0
 #define DRIVER_PATCHLEVEL 0
 
+#define CARVEOUT_SZ SZ_64M
+
 struct tegra_drm_file {
-       struct list_head contexts;
+       struct idr contexts;
+       struct mutex lock;
 };
 
 static void tegra_atomic_schedule(struct tegra_drm *tegra,
@@ -126,8 +131,9 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
                return -ENOMEM;
 
        if (iommu_present(&platform_bus_type)) {
+               u64 carveout_start, carveout_end, gem_start, gem_end;
                struct iommu_domain_geometry *geometry;
-               u64 start, end;
+               unsigned long order;
 
                tegra->domain = iommu_domain_alloc(&platform_bus_type);
                if (!tegra->domain) {
@@ -136,12 +142,26 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
                }
 
                geometry = &tegra->domain->geometry;
-               start = geometry->aperture_start;
-               end = geometry->aperture_end;
-
-               DRM_DEBUG_DRIVER("IOMMU aperture initialized (%#llx-%#llx)\n",
-                                start, end);
-               drm_mm_init(&tegra->mm, start, end - start + 1);
+               gem_start = geometry->aperture_start;
+               gem_end = geometry->aperture_end - CARVEOUT_SZ;
+               carveout_start = gem_end + 1;
+               carveout_end = geometry->aperture_end;
+
+               order = __ffs(tegra->domain->pgsize_bitmap);
+               init_iova_domain(&tegra->carveout.domain, 1UL << order,
+                                carveout_start >> order,
+                                carveout_end >> order);
+
+               tegra->carveout.shift = iova_shift(&tegra->carveout.domain);
+               tegra->carveout.limit = carveout_end >> tegra->carveout.shift;
+
+               drm_mm_init(&tegra->mm, gem_start, gem_end - gem_start + 1);
+               mutex_init(&tegra->mm_lock);
+
+               DRM_DEBUG("IOMMU apertures:\n");
+               DRM_DEBUG("  GEM: %#llx-%#llx\n", gem_start, gem_end);
+               DRM_DEBUG("  Carveout: %#llx-%#llx\n", carveout_start,
+                         carveout_end);
        }
 
        mutex_init(&tegra->clients_lock);
@@ -161,6 +181,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
        drm->mode_config.max_width = 4096;
        drm->mode_config.max_height = 4096;
 
+       drm->mode_config.allow_fb_modifiers = true;
+
        drm->mode_config.funcs = &tegra_drm_mode_funcs;
 
        err = tegra_drm_fb_prepare(drm);
@@ -208,6 +230,8 @@ config:
        if (tegra->domain) {
                iommu_domain_free(tegra->domain);
                drm_mm_takedown(&tegra->mm);
+               mutex_destroy(&tegra->mm_lock);
+               put_iova_domain(&tegra->carveout.domain);
        }
 free:
        kfree(tegra);
@@ -232,6 +256,8 @@ static void tegra_drm_unload(struct drm_device *drm)
        if (tegra->domain) {
                iommu_domain_free(tegra->domain);
                drm_mm_takedown(&tegra->mm);
+               mutex_destroy(&tegra->mm_lock);
+               put_iova_domain(&tegra->carveout.domain);
        }
 
        kfree(tegra);
@@ -245,7 +271,8 @@ static int tegra_drm_open(struct drm_device *drm, struct drm_file *filp)
        if (!fpriv)
                return -ENOMEM;
 
-       INIT_LIST_HEAD(&fpriv->contexts);
+       idr_init(&fpriv->contexts);
+       mutex_init(&fpriv->lock);
        filp->driver_priv = fpriv;
 
        return 0;
@@ -424,21 +451,16 @@ fail:
 
 
 #ifdef CONFIG_DRM_TEGRA_STAGING
-static struct tegra_drm_context *tegra_drm_get_context(__u64 context)
-{
-       return (struct tegra_drm_context *)(uintptr_t)context;
-}
-
-static bool tegra_drm_file_owns_context(struct tegra_drm_file *file,
-                                       struct tegra_drm_context *context)
+static struct tegra_drm_context *
+tegra_drm_file_get_context(struct tegra_drm_file *file, u32 id)
 {
-       struct tegra_drm_context *ctx;
+       struct tegra_drm_context *context;
 
-       list_for_each_entry(ctx, &file->contexts, list)
-               if (ctx == context)
-                       return true;
+       mutex_lock(&file->lock);
+       context = idr_find(&file->contexts, id);
+       mutex_unlock(&file->lock);
 
-       return false;
+       return context;
 }
 
 static int tegra_gem_create(struct drm_device *drm, void *data,
@@ -519,6 +541,28 @@ static int tegra_syncpt_wait(struct drm_device *drm, void *data,
                                  &args->value);
 }
 
+static int tegra_client_open(struct tegra_drm_file *fpriv,
+                            struct tegra_drm_client *client,
+                            struct tegra_drm_context *context)
+{
+       int err;
+
+       err = client->ops->open_channel(client, context);
+       if (err < 0)
+               return err;
+
+       err = idr_alloc(&fpriv->contexts, context, 0, 0, GFP_KERNEL);
+       if (err < 0) {
+               client->ops->close_channel(context);
+               return err;
+       }
+
+       context->client = client;
+       context->id = err;
+
+       return 0;
+}
+
 static int tegra_open_channel(struct drm_device *drm, void *data,
                              struct drm_file *file)
 {
@@ -533,19 +577,22 @@ static int tegra_open_channel(struct drm_device *drm, void *data,
        if (!context)
                return -ENOMEM;
 
+       mutex_lock(&fpriv->lock);
+
        list_for_each_entry(client, &tegra->clients, list)
                if (client->base.class == args->client) {
-                       err = client->ops->open_channel(client, context);
-                       if (err)
+                       err = tegra_client_open(fpriv, client, context);
+                       if (err < 0)
                                break;
 
-                       list_add(&context->list, &fpriv->contexts);
-                       args->context = (uintptr_t)context;
-                       context->client = client;
-                       return 0;
+                       args->context = context->id;
+                       break;
                }
 
-       kfree(context);
+       if (err < 0)
+               kfree(context);
+
+       mutex_unlock(&fpriv->lock);
        return err;
 }
 
@@ -555,16 +602,22 @@ static int tegra_close_channel(struct drm_device *drm, void *data,
        struct tegra_drm_file *fpriv = file->driver_priv;
        struct drm_tegra_close_channel *args = data;
        struct tegra_drm_context *context;
+       int err = 0;
 
-       context = tegra_drm_get_context(args->context);
+       mutex_lock(&fpriv->lock);
 
-       if (!tegra_drm_file_owns_context(fpriv, context))
-               return -EINVAL;
+       context = tegra_drm_file_get_context(fpriv, args->context);
+       if (!context) {
+               err = -EINVAL;
+               goto unlock;
+       }
 
-       list_del(&context->list);
+       idr_remove(&fpriv->contexts, context->id);
        tegra_drm_context_free(context);
 
-       return 0;
+unlock:
+       mutex_unlock(&fpriv->lock);
+       return err;
 }
 
 static int tegra_get_syncpt(struct drm_device *drm, void *data,
@@ -574,19 +627,27 @@ static int tegra_get_syncpt(struct drm_device *drm, void *data,
        struct drm_tegra_get_syncpt *args = data;
        struct tegra_drm_context *context;
        struct host1x_syncpt *syncpt;
+       int err = 0;
 
-       context = tegra_drm_get_context(args->context);
+       mutex_lock(&fpriv->lock);
 
-       if (!tegra_drm_file_owns_context(fpriv, context))
-               return -ENODEV;
+       context = tegra_drm_file_get_context(fpriv, args->context);
+       if (!context) {
+               err = -ENODEV;
+               goto unlock;
+       }
 
-       if (args->index >= context->client->base.num_syncpts)
-               return -EINVAL;
+       if (args->index >= context->client->base.num_syncpts) {
+               err = -EINVAL;
+               goto unlock;
+       }
 
        syncpt = context->client->base.syncpts[args->index];
        args->id = host1x_syncpt_id(syncpt);
 
-       return 0;
+unlock:
+       mutex_unlock(&fpriv->lock);
+       return err;
 }
 
 static int tegra_submit(struct drm_device *drm, void *data,
@@ -595,13 +656,21 @@ static int tegra_submit(struct drm_device *drm, void *data,
        struct tegra_drm_file *fpriv = file->driver_priv;
        struct drm_tegra_submit *args = data;
        struct tegra_drm_context *context;
+       int err;
 
-       context = tegra_drm_get_context(args->context);
+       mutex_lock(&fpriv->lock);
+
+       context = tegra_drm_file_get_context(fpriv, args->context);
+       if (!context) {
+               err = -ENODEV;
+               goto unlock;
+       }
 
-       if (!tegra_drm_file_owns_context(fpriv, context))
-               return -ENODEV;
+       err = context->client->ops->submit(context, args, drm, file);
 
-       return context->client->ops->submit(context, args, drm, file);
+unlock:
+       mutex_unlock(&fpriv->lock);
+       return err;
 }
 
 static int tegra_get_syncpt_base(struct drm_device *drm, void *data,
@@ -612,24 +681,34 @@ static int tegra_get_syncpt_base(struct drm_device *drm, void *data,
        struct tegra_drm_context *context;
        struct host1x_syncpt_base *base;
        struct host1x_syncpt *syncpt;
+       int err = 0;
 
-       context = tegra_drm_get_context(args->context);
+       mutex_lock(&fpriv->lock);
 
-       if (!tegra_drm_file_owns_context(fpriv, context))
-               return -ENODEV;
+       context = tegra_drm_file_get_context(fpriv, args->context);
+       if (!context) {
+               err = -ENODEV;
+               goto unlock;
+       }
 
-       if (args->syncpt >= context->client->base.num_syncpts)
-               return -EINVAL;
+       if (args->syncpt >= context->client->base.num_syncpts) {
+               err = -EINVAL;
+               goto unlock;
+       }
 
        syncpt = context->client->base.syncpts[args->syncpt];
 
        base = host1x_syncpt_get_base(syncpt);
-       if (!base)
-               return -ENXIO;
+       if (!base) {
+               err = -ENXIO;
+               goto unlock;
+       }
 
        args->id = host1x_syncpt_base_id(base);
 
-       return 0;
+unlock:
+       mutex_unlock(&fpriv->lock);
+       return err;
 }
 
 static int tegra_gem_set_tiling(struct drm_device *drm, void *data,
@@ -804,14 +883,25 @@ static const struct file_operations tegra_drm_fops = {
        .llseek = noop_llseek,
 };
 
-static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file)
+static int tegra_drm_context_cleanup(int id, void *p, void *data)
+{
+       struct tegra_drm_context *context = p;
+
+       tegra_drm_context_free(context);
+
+       return 0;
+}
+
+static void tegra_drm_postclose(struct drm_device *drm, struct drm_file *file)
 {
        struct tegra_drm_file *fpriv = file->driver_priv;
-       struct tegra_drm_context *context, *tmp;
 
-       list_for_each_entry_safe(context, tmp, &fpriv->contexts, list)
-               tegra_drm_context_free(context);
+       mutex_lock(&fpriv->lock);
+       idr_for_each(&fpriv->contexts, tegra_drm_context_cleanup, NULL);
+       mutex_unlock(&fpriv->lock);
 
+       idr_destroy(&fpriv->contexts);
+       mutex_destroy(&fpriv->lock);
        kfree(fpriv);
 }
 
@@ -844,7 +934,9 @@ static int tegra_debugfs_iova(struct seq_file *s, void *data)
        struct tegra_drm *tegra = drm->dev_private;
        struct drm_printer p = drm_seq_file_printer(s);
 
+       mutex_lock(&tegra->mm_lock);
        drm_mm_print(&tegra->mm, &p);
+       mutex_unlock(&tegra->mm_lock);
 
        return 0;
 }
@@ -868,7 +960,7 @@ static struct drm_driver tegra_drm_driver = {
        .load = tegra_drm_load,
        .unload = tegra_drm_unload,
        .open = tegra_drm_open,
-       .preclose = tegra_drm_preclose,
+       .postclose = tegra_drm_postclose,
        .lastclose = tegra_drm_lastclose,
 
 #if defined(CONFIG_DEBUG_FS)
@@ -919,6 +1011,84 @@ int tegra_drm_unregister_client(struct tegra_drm *tegra,
        return 0;
 }
 
+void *tegra_drm_alloc(struct tegra_drm *tegra, size_t size,
+                             dma_addr_t *dma)
+{
+       struct iova *alloc;
+       void *virt;
+       gfp_t gfp;
+       int err;
+
+       if (tegra->domain)
+               size = iova_align(&tegra->carveout.domain, size);
+       else
+               size = PAGE_ALIGN(size);
+
+       gfp = GFP_KERNEL | __GFP_ZERO;
+       if (!tegra->domain) {
+               /*
+                * Many units only support 32-bit addresses, even on 64-bit
+                * SoCs. If there is no IOMMU to translate into a 32-bit IO
+                * virtual address space, force allocations to be in the
+                * lower 32-bit range.
+                */
+               gfp |= GFP_DMA;
+       }
+
+       virt = (void *)__get_free_pages(gfp, get_order(size));
+       if (!virt)
+               return ERR_PTR(-ENOMEM);
+
+       if (!tegra->domain) {
+               /*
+                * If IOMMU is disabled, devices address physical memory
+                * directly.
+                */
+               *dma = virt_to_phys(virt);
+               return virt;
+       }
+
+       alloc = alloc_iova(&tegra->carveout.domain,
+                          size >> tegra->carveout.shift,
+                          tegra->carveout.limit, true);
+       if (!alloc) {
+               err = -EBUSY;
+               goto free_pages;
+       }
+
+       *dma = iova_dma_addr(&tegra->carveout.domain, alloc);
+       err = iommu_map(tegra->domain, *dma, virt_to_phys(virt),
+                       size, IOMMU_READ | IOMMU_WRITE);
+       if (err < 0)
+               goto free_iova;
+
+       return virt;
+
+free_iova:
+       __free_iova(&tegra->carveout.domain, alloc);
+free_pages:
+       free_pages((unsigned long)virt, get_order(size));
+
+       return ERR_PTR(err);
+}
+
+void tegra_drm_free(struct tegra_drm *tegra, size_t size, void *virt,
+                   dma_addr_t dma)
+{
+       if (tegra->domain)
+               size = iova_align(&tegra->carveout.domain, size);
+       else
+               size = PAGE_ALIGN(size);
+
+       if (tegra->domain) {
+               iommu_unmap(tegra->domain, dma, size);
+               free_iova(&tegra->carveout.domain,
+                         iova_pfn(&tegra->carveout.domain, dma));
+       }
+
+       free_pages((unsigned long)virt, get_order(size));
+}
+
 static int host1x_drm_probe(struct host1x_device *dev)
 {
        struct drm_driver *driver = &tegra_drm_driver;
@@ -1003,11 +1173,13 @@ static const struct of_device_id host1x_drm_subdevs[] = {
        { .compatible = "nvidia,tegra124-sor", },
        { .compatible = "nvidia,tegra124-hdmi", },
        { .compatible = "nvidia,tegra124-dsi", },
+       { .compatible = "nvidia,tegra124-vic", },
        { .compatible = "nvidia,tegra132-dsi", },
        { .compatible = "nvidia,tegra210-dc", },
        { .compatible = "nvidia,tegra210-dsi", },
        { .compatible = "nvidia,tegra210-sor", },
        { .compatible = "nvidia,tegra210-sor1", },
+       { .compatible = "nvidia,tegra210-vic", },
        { /* sentinel */ }
 };
 
@@ -1029,6 +1201,7 @@ static struct platform_driver * const drivers[] = {
        &tegra_sor_driver,
        &tegra_gr2d_driver,
        &tegra_gr3d_driver,
+       &tegra_vic_driver,
 };
 
 static int __init host1x_drm_init(void)