diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-05-05 17:18:44 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-05-05 17:18:44 -0700 |
commit | 1062ae4982cabbf60f89b4e069fbb7def7edc8f7 (patch) | |
tree | 01f6944b55d5b69615234f8c1f52b4e721f3d8e6 | |
parent | 1a5fb64fee203f9f5a9274c67ddbb821a29f723f (diff) | |
parent | 644b4930bf7e2adeffbe842e1097f7933c6a9158 (diff) |
Merge tag 'drm-forgot-about-tegra-for-v4.12-rc1' of git://people.freedesktop.org/~airlied/linux
Pull drm tegra updates from Dave Airlie:
"I missed a pull request from Thierry, this stuff has been in
linux-next for a while anyways.
It does contain a branch from the iommu tree, but Thierry said it
should be fine"
* tag 'drm-forgot-about-tegra-for-v4.12-rc1' of git://people.freedesktop.org/~airlied/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
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 ac0ce262150c..8944b472b90f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6843,6 +6843,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) + +#define FALCON_DMATRFBASE 0x00001110 + +#define FALCON_DMATRFMOFFS 0x00001114 + +#define FALCON_DMATRFCMD 0x00001118 +#define FALCO |