summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2017-05-05 11:47:01 +1000
committerDave Airlie <airlied@redhat.com>2017-05-05 11:47:01 +1000
commit644b4930bf7e2adeffbe842e1097f7933c6a9158 (patch)
treedcb9e56d30af9b590c511f89b0b6815dd67321e6
parent8b03d1ed2c43a2ba5ef3381322ee4515b97381bf (diff)
parentb0d36daa0ab54714e05164f6e21d22f974a5eec1 (diff)
Merge tag 'drm/tegra/for-4.12-rc1' of git://anongit.freedesktop.org/tegra/linux into drm-next
drm/tegra: Changes for v4.12-rc1 This contains various fixes to the host1x driver as well as a plug for a leak of kernel pointers to userspace. A fairly big addition this time around is the Video Image Composer (VIC) support that can be used to accelerate some 2D and image compositing operations. Furthermore the driver now supports FB modifiers, so we no longer rely on a custom IOCTL to set those. Finally this contains a few preparatory patches for Tegra186 support which unfortunately didn't quite make it this time, but will hopefully be ready for v4.13. * tag 'drm/tegra/for-4.12-rc1' of git://anongit.freedesktop.org/tegra/linux: gpu: host1x: Fix host1x driver shutdown gpu: host1x: Support module reset gpu: host1x: Sort includes alphabetically drm/tegra: Add VIC support dt-bindings: Add bindings for the Tegra VIC drm/tegra: Add falcon helper library drm/tegra: Add Tegra DRM allocation API drm/tegra: Add tiling FB modifiers drm/tegra: Don't leak kernel pointer to userspace drm/tegra: Protect IOMMU operations by mutex drm/tegra: Enable IOVA API when IOMMU support is enabled gpu: host1x: Add IOMMU support gpu: host1x: Fix potential out-of-bounds access iommu/iova: Fix compile error with CONFIG_IOMMU_IOVA=m iommu: Add dummy implementations for !IOMMU_IOVA MAINTAINERS: Add related headers to IOMMU section iommu/iova: Consolidate code for adding new node to iovad domain rbtree
-rw-r--r--Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt13
-rw-r--r--MAINTAINERS2
-rw-r--r--drivers/gpu/drm/tegra/Kconfig1
-rw-r--r--drivers/gpu/drm/tegra/Makefile4
-rw-r--r--drivers/gpu/drm/tegra/drm.c283
-rw-r--r--drivers/gpu/drm/tegra/drm.h15
-rw-r--r--drivers/gpu/drm/tegra/falcon.c259
-rw-r--r--drivers/gpu/drm/tegra/falcon.h127
-rw-r--r--drivers/gpu/drm/tegra/fb.c23
-rw-r--r--drivers/gpu/drm/tegra/gem.c12
-rw-r--r--drivers/gpu/drm/tegra/vic.c396
-rw-r--r--drivers/gpu/drm/tegra/vic.h31
-rw-r--r--drivers/gpu/host1x/bus.c68
-rw-r--r--drivers/gpu/host1x/cdma.c74
-rw-r--r--drivers/gpu/host1x/cdma.h6
-rw-r--r--drivers/gpu/host1x/dev.c76
-rw-r--r--drivers/gpu/host1x/dev.h14
-rw-r--r--drivers/gpu/host1x/hw/cdma_hw.c16
-rw-r--r--drivers/gpu/host1x/job.c72
-rw-r--r--drivers/gpu/host1x/job.h1
-rw-r--r--drivers/gpu/host1x/syncpt.c2
-rw-r--r--drivers/iommu/iova.c87
-rw-r--r--include/linux/host1x.h1
-rw-r--r--include/linux/iova.h91
-rw-r--r--include/uapi/drm/drm_fourcc.h45
25 files changed, 1517 insertions, 202 deletions
diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt
index 0fad7ed2ea19..74e1e8add5a1 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.txt
@@ -249,6 +249,19 @@ of the following host1x client modules:
See ../pinctrl/nvidia,tegra124-dpaux-padctl.txt for information
regarding the DPAUX pad controller bindings.
+- vic: Video Image Compositor
+ - compatible : "nvidia,tegra<chip>-vic"
+ - reg: Physical base address and length of the controller's registers.
+ - interrupts: The interrupt outputs from the controller.
+ - clocks: Must contain an entry for each entry in clock-names.
+ See ../clocks/clock-bindings.txt for details.
+ - clock-names: Must include the following entries:
+ - vic: clock input for the VIC hardware
+ - resets: Must contain an entry for each entry in reset-names.
+ See ../reset/reset.txt for details.
+ - reset-names: Must include the following entries:
+ - vic
+
Example:
/ {
diff --git a/MAINTAINERS b/MAINTAINERS
index 66f5b55d205a..ed22fc407635 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6780,6 +6780,8 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/joro/iommu.git
S: Maintained
F: Documentation/devicetree/bindings/iommu/
F: drivers/iommu/
+F: include/linux/iommu.h
+F: include/linux/iova.h
IP MASQUERADING
M: Juanjo Ciarlante <jjciarla@raiz.uncu.edu.ar>
diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig
index bbf5a4b7e0b6..2db29d67193d 100644
--- a/drivers/gpu/drm/tegra/Kconfig
+++ b/drivers/gpu/drm/tegra/Kconfig
@@ -7,6 +7,7 @@ config DRM_TEGRA
select DRM_MIPI_DSI
select DRM_PANEL
select TEGRA_HOST1X
+ select IOMMU_IOVA if IOMMU_SUPPORT
help
Choose this option if you have an NVIDIA Tegra SoC.
diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile
index 2c66a8db9da4..6af3a9ad6565 100644
--- a/drivers/gpu/drm/tegra/Makefile
+++ b/drivers/gpu/drm/tegra/Makefile
@@ -13,6 +13,8 @@ tegra-drm-y := \
sor.o \
dpaux.o \
gr2d.o \
- gr3d.o
+ gr3d.o \
+ falcon.o \
+ vic.o
obj-$(CONFIG_DRM_TEGRA) += tegra-drm.o
diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c
index dba4e090d3df..9a1e34e48f64 100644
--- a/drivers/gpu/drm/tegra/drm.c
+++ b/drivers/gpu/drm/tegra/drm.c
@@ -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>
@@ -23,8 +25,11 @@
#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 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_preclose(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;
}
@@ -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)
diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h
index 5747accb2271..85aa2e3d9d4e 100644
--- a/drivers/gpu/drm/tegra/drm.h
+++ b/drivers/gpu/drm/tegra/drm.h
@@ -12,6 +12,7 @@
#include <uapi/drm/tegra_drm.h>
#include <linux/host1x.h>
+#include <linux/iova.h>
#include <linux/of_gpio.h>
#include <drm/drmP.h>
@@ -42,8 +43,15 @@ struct tegra_drm {
struct drm_device *drm;
struct iommu_domain *domain;
+ struct mutex mm_lock;
struct drm_mm mm;
+ struct {
+ struct iova_domain domain;
+ unsigned long shift;
+ unsigned long limit;
+ } carveout;
+
struct mutex clients_lock;
struct list_head clients;
@@ -67,7 +75,7 @@ struct tegra_drm_client;
struct tegra_drm_context {
struct tegra_drm_client *client;
struct host1x_channel *channel;
- struct list_head list;
+ unsigned int id;
};
struct tegra_drm_client_ops {
@@ -105,6 +113,10 @@ int tegra_drm_unregister_client(struct tegra_drm *tegra,
int tegra_drm_init(struct tegra_drm *tegra, struct drm_device *drm);
int tegra_drm_exit(struct tegra_drm *tegra);
+void *tegra_drm_alloc(struct tegra_drm *tegra, size_t size, dma_addr_t *iova);
+void tegra_drm_free(struct tegra_drm *tegra, size_t size, void *virt,
+ dma_addr_t iova);
+
struct tegra_dc_soc_info;
struct tegra_output;
@@ -283,5 +295,6 @@ extern struct platform_driver tegra_dpaux_driver;
extern struct platform_driver tegra_sor_driver;
extern struct platform_driver tegra_gr2d_driver;
extern struct platform_driver tegra_gr3d_driver;
+extern struct platform_driver tegra_vic_driver;
#endif /* HOST1X_DRM_H */
diff --git a/drivers/gpu/drm/tegra/falcon.c b/drivers/gpu/drm/tegra/falcon.c
new file mode 100644
index 000000000000..f685e72949d1
--- /dev/null
+++ b/drivers/gpu/drm/tegra/falcon.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2015, NVIDIA Corporation.
+ *
+ * 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/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/pci_ids.h>
+#include <linux/iopoll.h>
+
+#include "falcon.h"
+#include "drm.h"
+
+enum falcon_memory {
+ FALCON_MEMORY_IMEM,
+ FALCON_MEMORY_DATA,
+};
+
+static void falcon_writel(struct falcon *falcon, u32 value, u32 offset)
+{
+ writel(value, falcon->regs + offset);
+}
+
+int falcon_wait_idle(struct falcon *falcon)
+{
+ u32 value;
+
+ return readl_poll_timeout(falcon->regs + FALCON_IDLESTATE, value,
+ (value == 0), 10, 100000);
+}
+
+static int falcon_dma_wait_idle(struct falcon *falcon)
+{
+ u32 value;
+
+ return readl_poll_timeout(falcon->regs + FALCON_DMATRFCMD, value,
+ (value & FALCON_DMATRFCMD_IDLE), 10, 100000);
+}
+
+static int falcon_copy_chunk(struct falcon *falcon,
+ phys_addr_t base,
+ unsigned long offset,
+ enum falcon_memory target)
+{
+ u32 cmd = FALCON_DMATRFCMD_SIZE_256B;
+
+ if (target == FALCON_MEMORY_IMEM)
+ cmd |= FALCON_DMATRFCMD_IMEM;
+
+ falcon_writel(falcon, offset, FALCON_DMATRFMOFFS);
+ falcon_writel(falcon, base, FALCON_DMATRFFBOFFS);
+ falcon_writel(falcon, cmd, FALCON_DMATRFCMD);
+
+ return falcon_dma_wait_idle(falcon);
+}
+
+static void falcon_copy_firmware_image(struct falcon *falcon,
+ const struct firmware *firmware)
+{
+ u32 *firmware_vaddr = falcon->firmware.vaddr;
+ dma_addr_t daddr;
+ size_t i;
+ int err;
+
+ /* copy the whole thing taking into account endianness */
+ for (i = 0; i < firmware->size / sizeof(u32); i++)
+ firmware_vaddr[i] = le32_to_cpu(((u32 *)firmware->data)[i]);
+
+ /* ensure that caches are flushed and falcon can see the firmware */
+ daddr = dma_map_single(falcon->dev, firmware_vaddr,
+ falcon->firmware.size, DMA_TO_DEVICE);
+ err = dma_mapping_error(falcon->dev, daddr);
+ if (err) {
+ dev_err(falcon->dev, "failed to map firmware: %d\n", err);
+ return;
+ }
+ dma_sync_single_for_device(falcon->dev, daddr,
+ falcon->firmware.size, DMA_TO_DEVICE);
+ dma_unmap_single(falcon->dev, daddr, falcon->firmware.size,
+ DMA_TO_DEVICE);
+}
+
+static int falcon_parse_firmware_image(struct falcon *falcon)
+{
+ struct falcon_fw_bin_header_v1 *bin = (void *)falcon->firmware.vaddr;
+ struct falcon_fw_os_header_v1 *os;
+
+ /* endian problems would show up right here */
+ if (bin->magic != PCI_VENDOR_ID_NVIDIA) {
+ dev_err(falcon->dev, "incorrect firmware magic\n");
+ return -EINVAL;
+ }
+
+ /* currently only version 1 is supported */
+ if (bin->version != 1) {
+ dev_err(falcon->dev, "unsupported firmware version\n");
+ return -EINVAL;
+ }
+
+ /* check that the firmware size is consistent */
+ if (bin->size > falcon->firmware.size) {
+ dev_err(falcon->dev, "firmware image size inconsistency\n");
+ return -EINVAL;
+ }
+
+ os = falcon->firmware.vaddr + bin->os_header_offset;
+
+ falcon->firmware.bin_data.size = bin->os_size;
+ falcon->firmware.bin_data.offset = bin->os_data_offset;
+ falcon->firmware.code.offset = os->code_offset;
+ falcon->firmware.code.size = os->code_size;
+ falcon->firmware.data.offset = os->data_offset;
+ falcon->firmware.data.size = os->data_size;
+
+ return 0;
+}
+
+int falcon_read_firmware(struct falcon *falcon, const char *name)
+{
+ int err;
+
+ /* request_firmware prints error if it fails */
+ err = request_firmware(&falcon->firmware.firmware, name, falcon->dev);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+int falcon_load_firmware(struct falcon *falcon)
+{
+ const struct firmware *firmware = falcon->firmware.firmware;
+ int err;
+
+ falcon->firmware.size = firmware->size;
+
+ /* allocate iova space for the firmware */
+ falcon->firmware.vaddr = falcon->ops->alloc(falcon, firmware->size,
+ &falcon->firmware.paddr);
+ if (!falcon->firmware.vaddr) {
+ dev_err(falcon->dev, "dma memory mapping failed\n");
+ return -ENOMEM;
+ }
+
+ /* copy firmware image into local area. this also ensures endianness */
+ falcon_copy_firmware_image(falcon, firmware);
+
+ /* parse the image data */
+ err = falcon_parse_firmware_image(falcon);
+ if (err < 0) {
+ dev_err(falcon->dev, "failed to parse firmware image\n");
+ goto err_setup_firmware_image;
+ }
+
+ release_firmware(firmware);
+ falcon->firmware.firmware = NULL;
+
+ return 0;
+
+err_setup_firmware_image:
+ falcon->ops->free(falcon, falcon->firmware.size,
+ falcon->firmware.paddr, falcon->firmware.vaddr);
+
+ return err;
+}
+
+int falcon_init(struct falcon *falcon)
+{
+ /* check mandatory ops */
+ if (!falcon->ops || !falcon->ops->alloc || !falcon->ops->free)
+ return -EINVAL;
+
+ falcon->firmware.vaddr = NULL;
+
+ return 0;
+}
+
+void falcon_exit(struct falcon *falcon)
+{
+ if (falcon->firmware.firmware) {
+ release_firmware(falcon->firmware.firmware);
+ falcon->firmware.firmware = NULL;
+ }
+
+ if (falcon->firmware.vaddr) {
+ falcon->ops->free(falcon, falcon->firmware.size,
+ falcon->firmware.paddr,
+ falcon->firmware.vaddr);
+ falcon->firmware.vaddr = NULL;
+ }
+}
+
+int falcon_boot(struct falcon *falcon)
+{
+ unsigned long offset;
+ int err;
+
+ if (!falcon->firmware.vaddr)
+ return -EINVAL;
+
+ falcon_writel(falcon, 0, FALCON_DMACTL);
+
+ /* setup the address of the binary data so Falcon can access it later */
+ falcon_writel(falcon, (falcon->firmware.paddr +
+ falcon->firmware.bin_data.offset) >> 8,
+ FALCON_DMATRFBASE);
+
+ /* copy the data segment into Falcon internal memory */
+ for (offset = 0; offset < falcon->firmware.data.size; offset += 256)
+ falcon_copy_chunk(falcon,
+ falcon->firmware.data.offset + offset,
+ offset, FALCON_MEMORY_DATA);
+
+ /* copy the first code segment into Falcon internal memory */
+ falcon_copy_chunk(falcon, falcon->firmware.code.offset,
+ 0, FALCON_MEMORY_IMEM);
+
+ /* setup falcon interrupts */
+ falcon_writel(falcon, FALCON_IRQMSET_EXT(0xff) |
+ FALCON_IRQMSET_SWGEN1 |
+ FALCON_IRQMSET_SWGEN0 |
+ FALCON_IRQMSET_EXTERR |
+ FALCON_IRQMSET_HALT |
+ FALCON_IRQMSET_WDTMR,
+ FALCON_IRQMSET);
+ falcon_writel(falcon, FALCON_IRQDEST_EXT(0xff) |
+ FALCON_IRQDEST_SWGEN1 |
+ FALCON_IRQDEST_SWGEN0 |
+ FALCON_IRQDEST_EXTERR |
+ FALCON_IRQDEST_HALT,
+ FALCON_IRQDEST);
+
+ /* enable interface */
+ falcon_writel(falcon, FALCON_ITFEN_MTHDEN |
+ FALCON_ITFEN_CTXEN,
+ FALCON_ITFEN);
+
+ /* boot falcon */
+ falcon_writel(falcon, 0x00000000, FALCON_BOOTVEC);
+ falcon_writel(falcon, FALCON_CPUCTL_STARTCPU, FALCON_CPUCTL);
+
+ err = falcon_wait_idle(falcon);
+ if (err < 0) {
+ dev_err(falcon->dev, "Falcon boot failed due to timeout\n");
+ return err;
+ }
+
+ return 0;
+}
+
+void falcon_execute_method(struct falcon *falcon, u32 method, u32 data)
+{
+ falcon_writel(falcon, method >> 2, FALCON_UCLASS_METHOD_OFFSET);
+ falcon_writel(falcon, data, FALCON_UCLASS_METHOD_DATA);
+}
diff --git a/drivers/gpu/drm/tegra/falcon.h b/drivers/gpu/drm/tegra/falcon.h
new file mode 100644
index 000000000000..4504ed5a199e
--- /dev/null
+++ b/drivers/gpu/drm/tegra/falcon.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2015, NVIDIA Corporation.
+ *
+ * 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.
+ */
+
+#ifndef _FALCON_H_
+#define _FALCON_H_
+
+#include <linux/types.h>
+
+#define FALCON_UCLASS_METHOD_OFFSET 0x00000040
+
+#define FALCON_UCLASS_METHOD_DATA 0x00000044
+
+#define FALCON_IRQMSET 0x00001010
+#define FALCON_IRQMSET_WDTMR (1 << 1)
+#define FALCON_IRQMSET_HALT (1 << 4)
+#define FALCON_IRQMSET_EXTERR (1 << 5)
+#define FALCON_IRQMSET_SWGEN0 (1 << 6)
+#define FALCON_IRQMSET_SWGEN1 (1 << 7)
+#define FALCON_IRQMSET_EXT(v) (((v) & 0xff) << 8)
+
+#define FALCON_IRQDEST 0x0000101c
+#define FALCON_IRQDEST_HALT (1 << 4)
+#define FALCON_IRQDEST_EXTERR (1 << 5)
+#define FALCON_IRQDEST_SWGEN0 (1 << 6)
+#define FALCON_IRQDEST_SWGEN1 (1 << 7)
+#define FALCON_IRQDEST_EXT(v) (((v) & 0xff) << 8)
+
+#define FALCON_ITFEN 0x00001048
+#define FALCON_ITFEN_CTXEN (1 << 0)
+#define FALCON_ITFEN_MTHDEN (1 << 1)
+
+#define FALCON_IDLESTATE 0x0000104c
+
+#define FALCON_CPUCTL 0x00001100
+#define FALCON_CPUCTL_STARTCPU (1 << 1)
+
+#define FALCON_BOOTVEC 0x00001104
+
+#define FALCON_DMACTL 0x0000110c
+#define FALCON_DMACTL_DMEM_SCRUBBING (1 << 1)
+#define FALCON_DMACTL_IMEM_SCRUBBING (1 << 2)<