diff options
Diffstat (limited to 'drivers/gpu/drm/tegra')
-rw-r--r-- | drivers/gpu/drm/tegra/Kconfig | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/Makefile | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.c | 283 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.h | 15 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/falcon.c | 259 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/falcon.h | 127 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/fb.c | 23 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/gem.c | 12 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/vic.c | 396 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/vic.h | 31 |
10 files changed, 1089 insertions, 62 deletions
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 FALCON_DMATRFCMD_IDLE (1 << 1) +#define FALCON_DMATRFCMD_IMEM (1 << 4) +#define FALCON_DMATRFCMD_SIZE_256B (6 << 8) + +#define FALCON_DMATRFFBOFFS 0x0000111c + +struct falcon_fw_bin_header_v1 { + u32 magic; /* 0x10de */ + u32 version; /* version of bin format (1) */ + u32 size; /* entire image size including this header */ + u32 os_header_offset; + u32 os_data_offset; + u32 os_size; +}; + +struct falcon_fw_os_app_v1 { + u32 offset; + u32 size; +}; + +struct falcon_fw_os_header_v1 { + u32 code_offset; + u32 code_size; + u32 data_offset; + u32 data_size; +}; + +struct falcon; + +struct falcon_ops { + void *(*alloc)(struct falcon *falcon, size_t size, + dma_addr_t *paddr); + void (*free)(struct falcon *falcon, size_t size, + dma_addr_t paddr, void *vaddr); +}; + +struct falcon_firmware_section { + unsigned long offset; + size_t size; +}; + +struct falcon_firmware { + /* Firmware after it is read but not loaded */ + const struct firmware *firmware; + + /* Raw firmware data */ + dma_addr_t paddr; + void *vaddr; + size_t size; + + /* Parsed firmware information */ + struct falcon_firmware_section bin_data; + struct falcon_firmware_section data; + struct falcon_firmware_section code; +}; + +struct falcon { + /* Set by falcon client */ + struct device *dev; + void __iomem *regs; + const struct falcon_ops *ops; + void *data; + + struct falcon_firmware firmware; +}; + +int falcon_init(struct falcon *falcon); +void falcon_exit(struct falcon *falcon); +int falcon_read_firmware(struct falcon *falcon, const char *firmware_name); +int falcon_load_firmware(struct falcon *falcon); +int falcon_boot(struct falcon *falcon); +void falcon_execute_method(struct falcon *falcon, u32 method, u32 data); +int falcon_wait_idle(struct falcon *falcon); + +#endif /* _FALCON_H_ */ diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c index c61d67d16ce3..25acb73ee728 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -52,9 +52,26 @@ int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer, struct tegra_bo_tiling *tiling) { struct tegra_fb *fb = to_tegra_fb(framebuffer); - - /* TODO: handle YUV formats? */ - *tiling = fb->planes[0]->tiling; + uint64_t modifier = fb->base.modifier; + + switch (fourcc_mod_tegra_mod(modifier)) { + case NV_FORMAT_MOD_TEGRA_TILED: + tiling->mode = TEGRA_BO_TILING_MODE_TILED; + tiling->value = 0; + break; + + case NV_FORMAT_MOD_TEGRA_16BX2_BLOCK(0): + tiling->mode = TEGRA_BO_TILING_MODE_BLOCK; + tiling->value = fourcc_mod_tegra_param(modifier); + if (tiling->value > 5) + return -EINVAL; + break; + + default: + /* TODO: handle YUV formats? */ + *tiling = fb->planes[0]->tiling; + break; + } return 0; } diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index 8672f5d2f237..424569b53e57 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -128,12 +128,14 @@ static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo) if (!bo->mm) return -ENOMEM; + mutex_lock(&tegra->mm_lock); + err = drm_mm_insert_node_generic(&tegra->mm, bo->mm, bo->gem.size, PAGE_SIZE, 0, 0); if (err < 0) { dev_err(tegra->drm->dev, "out of I/O virtual memory: %zd\n", err); - goto free; + goto unlock; } bo->paddr = bo->mm->start; @@ -147,11 +149,14 @@ static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo) bo->size = err; + mutex_unlock(&tegra->mm_lock); + return 0; remove: drm_mm_remove_node(bo->mm); -free: +unlock: + mutex_unlock(&tegra->mm_lock); kfree(bo->mm); return err; } @@ -161,8 +166,11 @@ static int tegra_bo_iommu_unmap(struct tegra_drm *tegra, struct tegra_bo *bo) if (!bo->mm) return 0; + mutex_lock(&tegra->mm_lock); iommu_unmap(tegra->domain, bo->paddr, bo->size); drm_mm_remove_node(bo->mm); + mutex_unlock(&tegra->mm_lock); + kfree(bo->mm); return 0; diff --git a/drivers/gpu/drm/tegra/vic.c b/drivers/gpu/drm/tegra/vic.c new file mode 100644 index 000000000000..cd804e404a11 --- /dev/null +++ b/drivers/gpu/drm/tegra/vic.c @@ -0,0 +1,396 @@ +/* + * 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/clk.h> +#include <linux/host1x.h> +#include <linux/iommu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> + +#include <soc/tegra/pmc.h> + +#include "drm.h" +#include "falcon.h" +#include "vic.h" + +struct vic_config { + const char *firmware; +}; + +struct vic { + struct falcon falcon; + bool booted; + + void __iomem *regs; + struct tegra_drm_client client; + struct host1x_channel *channel; + struct iommu_domain *domain; + struct device *dev; + struct clk *clk; + + /* Platform configuration */ + const struct vic_config *config; +}; + +static inline struct vic *to_vic(struct tegra_drm_client *client) +{ + return container_of(client, struct vic, client); +} + +static void vic_writel(struct vic *vic, u32 value, unsigned int offset) +{ + writel(value, vic->regs + offset); +} + +static int vic_runtime_resume(struct device *dev) +{ + struct vic *vic = dev_get_drvdata(dev); + + return clk_prepare_enable(vic->clk); +} + +static int vic_runtime_suspend(struct device *dev) +{ + struct vic *vic = dev_get_drvdata(dev); + + clk_disable_unprepare(vic->clk); + + vic->booted = false; + + return 0; +} + +static int vic_boot(struct vic *vic) +{ + u32 fce_ucode_size, fce_bin_data_offset; + void *hdr; + int err = 0; + + if (vic->booted) + return 0; + + /* setup clockgating registers */ + vic_writel(vic, CG_IDLE_CG_DLY_CNT(4) | + CG_IDLE_CG_EN | + CG_WAKEUP_DLY_CNT(4), + NV_PVIC_MISC_PRI_VIC_CG); + + err = falcon_boot(&vic->falcon); + if (err < 0) + return err; + + hdr = vic->falcon.firmware.vaddr; + fce_bin_data_offset = *(u32 *)(hdr + VIC_UCODE_FCE_DATA_OFFSET); + hdr = vic->falcon.firmware.vaddr + + *(u32 *)(hdr + VIC_UCODE_FCE_HEADER_OFFSET); + fce_ucode_size = *(u32 *)(hdr + FCE_UCODE_SIZE_OFFSET); + + falcon_execute_method(&vic->falcon, VIC_SET_APPLICATION_ID, 1); + falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_SIZE, + fce_ucode_size); + falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_OFFSET, + (vic->falcon.firmware.paddr + fce_bin_data_offset) + >> 8); + + err = falcon_wait_idle(&vic->falcon); + if (err < 0) { + dev_err(vic->dev, + "failed to set application ID and FCE base\n"); + return err; + } + + vic->booted = true; + + return 0; +} + +static void *vic_falcon_alloc(struct falcon *falcon, size_t size, + dma_addr_t *iova) +{ + struct tegra_drm *tegra = falcon->data; + + return tegra_drm_alloc(tegra, size, iova); +} + +static void vic_falcon_free(struct falcon *falcon, size_t size, + dma_addr_t iova, void *va) +{ + struct tegra_drm *tegra = falcon->data; + + return tegra_drm_free(tegra, size, va, iova); +} + +static const struct falcon_ops vic_falcon_ops = { + .alloc = vic_falcon_alloc, + .free = vic_falcon_free +}; + +static int vic_init(struct host1x_client *client) +{ + struct tegra_drm_client *drm = host1x_to_drm_client(client); + struct drm_device *dev = dev_get_drvdata(client->parent); + struct tegra_drm *tegra = dev->dev_private; + struct vic *vic = to_vic(drm); + int err; + + if (tegra->domain) { + err = iommu_attach_device(tegra->domain, vic->dev); + if (err < 0) { + dev_err(vic->dev, "failed to attach to domain: %d\n", + err); + return err; + } + + vic->domain = tegra->domain; + } + + if (!vic->falcon.data) { + vic->falcon.data = tegra; + err = falcon_load_firmware(&vic->falcon); + if (err < 0) + goto detach_device; + } + + vic->channel = host1x_channel_request(client->dev); + if (!vic->channel) { + err = -ENOMEM; + goto detach_device; + } + + client->syncpts[0] = host1x_syncpt_request(client->dev, 0); + if (!client->syncpts[0]) { + err = -ENOMEM; + goto free_channel; + } + + err = tegra_drm_register_client(tegra, drm); + if (err < 0) + goto free_syncpt; + + return 0; + +free_syncpt: + host1x_syncpt_free(client->syncpts[0]); +free_channel: + host1x_channel_free(vic->channel); +detach_device: + if (tegra->domain) + iommu_detach_device(tegra->domain, vic->dev); + + return err; +} + +static int vic_exit(struct host1x_client *client) +{ + struct tegra_drm_client *drm = host1x_to_drm_client(client); + struct drm_device *dev = dev_get_drvdata(client->parent); + struct tegra_drm *tegra = dev->dev_private; + struct vic *vic = to_vic(drm); + int err; + + err = tegra_drm_unregister_client(tegra, drm); + if (err < 0) + return err; + + host1x_syncpt_free(client->syncpts[0]); + host1x_channel_free(vic->channel); + + if (vic->domain) { + iommu_detach_device(vic->domain, vic->dev); + vic->domain = NULL; + } + + return 0; +} + +static const struct host1x_client_ops vic_client_ops = { + .init = vic_init, + .exit = vic_exit, +}; + +static int vic_open_channel(struct tegra_drm_client *client, + struct tegra_drm_context *context) +{ + struct vic *vic = to_vic(client); + int err; + + err = pm_runtime_get_sync(vic->dev); + if (err < 0) + return err; + + err = vic_boot(vic); + if (err < 0) { + pm_runtime_put(vic->dev); + return err; + } + + context->channel = host1x_channel_get(vic->channel); + if (!context->channel) { + pm_runtime_put(vic->dev); + return -ENOMEM; + } + + return 0; +} + +static void vic_close_channel(struct tegra_drm_context *context) +{ + struct vic *vic = to_vic(context->client); + + host1x_channel_put(context->channel); + + pm_runtime_put(vic->dev); +} + +static const struct tegra_drm_client_ops vic_ops = { + .open_channel = vic_open_channel, + .close_channel = vic_close_channel, + .submit = tegra_drm_submit, +}; + +static const struct vic_config vic_t124_config = { + .firmware = "nvidia/tegra124/vic03_ucode.bin", +}; + +static const struct vic_config vic_t210_config = { + .firmware = "nvidia/tegra210/vic04_ucode.bin", +}; + +static const struct of_device_id vic_match[] = { + { .compatible = "nvidia,tegra124-vic", .data = &vic_t124_config }, + { .compatible = "nvidia,tegra210-vic", .data = &vic_t210_config }, + { }, +}; + +static int vic_probe(struct platform_device *pdev) +{ + struct vic_config *vic_config = NULL; + struct device *dev = &pdev->dev; + struct host1x_syncpt **syncpts; + struct resource *regs; + const struct of_device_id *match; + struct vic *vic; + int err; + + match = of_match_device(vic_match, dev); + vic_config = (struct vic_config *)match->data; + + vic = devm_kzalloc(dev, sizeof(*vic), GFP_KERNEL); + if (!vic) + return -ENOMEM; + + syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL); + if (!syncpts) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + dev_err(&pdev->dev, "failed to get registers\n"); + return -ENXIO; + } + + vic->regs = devm_ioremap_resource(dev, regs); + if (IS_ERR(vic->regs)) + return PTR_ERR(vic->regs); + + vic->clk = devm_clk_get(dev, NULL); + if (IS_ERR(vic->clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(vic->clk); + } + + vic->falcon.dev = dev; + vic->falcon.regs = vic->regs; + vic->falcon.ops = &vic_falcon_ops; + + err = falcon_init(&vic->falcon); + if (err < 0) + return err; + + err = falcon_read_firmware(&vic->falcon, vic_config->firmware); + if (err < 0) + goto exit_falcon; + + platform_set_drvdata(pdev, vic); + + INIT_LIST_HEAD(&vic->client.base.list); + vic->client.base.ops = &vic_client_ops; + vic->client.base.dev = dev; + vic->client.base.class = HOST1X_CLASS_VIC; + vic->client.base.syncpts = syncpts; + vic->client.base.num_syncpts = 1; + vic->dev = dev; + vic->config = vic_config; + + INIT_LIST_HEAD(&vic->client.list); + vic->client.ops = &vic_ops; + + err = host1x_client_register(&vic->client.base); + if (err < 0) { + dev_err(dev, "failed to register host1x client: %d\n", err); + platform_set_drvdata(pdev, NULL); + goto exit_falcon; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + err = vic_runtime_resume(&pdev->dev); + if (err < 0) + goto unregister_client; + } + + return 0; + +unregister_client: + host1x_client_unregister(&vic->client.base); +exit_falcon: + falcon_exit(&vic->falcon); + + return err; +} + +static int vic_remove(struct platform_device *pdev) +{ + struct vic *vic = platform_get_drvdata(pdev); + int err; + + err = host1x_client_unregister(&vic->client.base); + if (err < 0) { + dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", + err); + return err; + } + + if (pm_runtime_enabled(&pdev->dev)) + pm_runtime_disable(&pdev->dev); + else + vic_runtime_suspend(&pdev->dev); + + falcon_exit(&vic->falcon); + + return 0; +} + +static const struct dev_pm_ops vic_pm_ops = { + SET_RUNTIME_PM_OPS(vic_runtime_suspend, vic_runtime_resume, NULL) +}; + +struct platform_driver tegra_vic_driver = { + .driver = { + .name = "tegra-vic", + .of_match_table = vic_match, + .pm = &vic_pm_ops + }, + .probe = vic_probe, + .remove = vic_remove, +}; diff --git a/drivers/gpu/drm/tegra/vic.h b/drivers/gpu/drm/tegra/vic.h new file mode 100644 index 000000000000..21844817a7e1 --- /dev/null +++ b/drivers/gpu/drm/tegra/vic.h @@ -0,0 +1,31 @@ +/* + * 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 TEGRA_VIC_H +#define TEGRA_VIC_H + +/* VIC methods */ + +#define VIC_SET_APPLICATION_ID 0x00000200 +#define VIC_SET_FCE_UCODE_SIZE 0x0000071C +#define VIC_SET_FCE_UCODE_OFFSET 0x0000072C + +/* VIC registers */ + +#define NV_PVIC_MISC_PRI_VIC_CG 0x000016d0 +#define CG_IDLE_CG_DLY_CNT(val) ((val & 0x3f) << 0) +#define CG_IDLE_CG_EN (1 << 6) +#define CG_WAKEUP_DLY_CNT(val) ((val & 0xf) << 16) + +/* Firmware offsets */ + +#define VIC_UCODE_FCE_HEADER_OFFSET (6*4) +#define VIC_UCODE_FCE_DATA_OFFSET (7*4) +#define FCE_UCODE_SIZE_OFFSET (2*4) + +#endif /* TEGRA_VIC_H */ |