diff mbox

[RFC,5/21] DRM: Add VIA DRM driver

Message ID alpine.LFD.2.03.1306081746260.18671@infradead.org (mailing list archive)
State New, archived
Headers show

Commit Message

James Simmons June 8, 2013, 4:47 p.m. UTC
commit fa8599a207fbd0b4ffaa8c6961d34797f615ed7d
Author: James Simmons <jsimmons@infradead.org>
Date:   Fri Jun 7 19:58:14 2013 -0400

    via: ttm bo driver implementation
    
    The openchrome project decided to use the TTM api as the backend to
    support GEM. The core needed to support TTM is struct ttm_bo_driver
    which is implemented in via_ttm.c. This back end supports both AGP
    and PCIe.
    
    Signed-Off-by: James Simmons <jsimmons@infradead.org>
diff mbox

Patch

diff --git a/drivers/gpu/drm/via/via_ttm.c b/drivers/gpu/drm/via/via_ttm.c
new file mode 100644
index 0000000..5d6aead
--- /dev/null
+++ b/drivers/gpu/drm/via/via_ttm.c
@@ -0,0 +1,495 @@ 
+/*
+ * Copyright 2012 James Simmons <jsimmons@infradead.org>. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHOR(S) OR COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include <linux/dma-mapping.h>
+#ifdef CONFIG_SWIOTLB
+#include <linux/swiotlb.h>
+#endif
+
+#include "via_drv.h"
+
+static struct ttm_tt *
+via_ttm_tt_create(struct ttm_bo_device *bdev, unsigned long size,
+			uint32_t page_flags, struct page *dummy_read_page)
+{
+#if __OS_HAS_AGP
+	struct drm_via_private *dev_priv =
+		container_of(bdev, struct drm_via_private, bdev);
+
+	if (drm_pci_device_is_agp(dev_priv->dev))
+		return ttm_agp_tt_create(bdev, dev_priv->dev->agp->bridge,
+					size, page_flags, dummy_read_page);
+#endif
+	return via_sgdma_backend_init(bdev, size, page_flags, dummy_read_page);
+}
+
+static int
+via_ttm_tt_populate(struct ttm_tt *ttm)
+{
+	struct sgdma_tt *dma_tt = (struct sgdma_tt *) ttm;
+	struct ttm_dma_tt *sgdma = &dma_tt->sgdma;
+	struct ttm_bo_device *bdev = ttm->bdev;
+	struct drm_via_private *dev_priv =
+		container_of(bdev, struct drm_via_private, bdev);
+	struct drm_device *dev = dev_priv->dev;
+	unsigned int i;
+	int ret = 0;
+
+	if (ttm->state != tt_unpopulated)
+		return 0;
+
+#if __OS_HAS_AGP
+	if (drm_pci_device_is_agp(dev_priv->dev))
+		return ttm_agp_tt_populate(ttm);
+#endif
+
+#ifdef CONFIG_SWIOTLB
+	if (swiotlb_nr_tbl())
+		return ttm_dma_populate(sgdma, dev->dev);
+#endif
+
+	ret = ttm_pool_populate(ttm);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < ttm->num_pages; i++) {
+		sgdma->dma_address[i] = pci_map_page(dev->pdev, ttm->pages[i],
+							0, PAGE_SIZE,
+							PCI_DMA_BIDIRECTIONAL);
+		if (pci_dma_mapping_error(dev->pdev, sgdma->dma_address[i])) {
+			while (--i) {
+				pci_unmap_page(dev->pdev, sgdma->dma_address[i],
+						PAGE_SIZE, PCI_DMA_BIDIRECTIONAL);
+				sgdma->dma_address[i] = 0;
+			}
+			ttm_pool_unpopulate(ttm);
+			return -EFAULT;
+		}
+	}
+	return ret;
+}
+
+static void
+via_ttm_tt_unpopulate(struct ttm_tt *ttm)
+{
+	struct sgdma_tt *dma_tt = (struct sgdma_tt *) ttm;
+	struct ttm_dma_tt *sgdma = &dma_tt->sgdma;
+	struct ttm_bo_device *bdev = ttm->bdev;
+	struct drm_via_private *dev_priv =
+		container_of(bdev, struct drm_via_private, bdev);
+	struct drm_device *dev = dev_priv->dev;
+	unsigned int i;
+
+#if __OS_HAS_AGP
+	if (drm_pci_device_is_agp(dev_priv->dev)) {
+		ttm_agp_tt_unpopulate(ttm);
+		return;
+	}
+#endif
+
+#ifdef CONFIG_SWIOTLB
+	if (swiotlb_nr_tbl()) {
+		ttm_dma_unpopulate(sgdma, dev->dev);
+		return;
+	}
+#endif
+
+	for (i = 0; i < ttm->num_pages; i++) {
+		if (sgdma->dma_address[i]) {
+			pci_unmap_page(dev->pdev, sgdma->dma_address[i],
+					PAGE_SIZE, PCI_DMA_BIDIRECTIONAL);
+		}
+	}
+
+	ttm_pool_unpopulate(ttm);
+}
+
+static int
+via_invalidate_caches(struct ttm_bo_device *bdev, uint32_t flags)
+{
+	/*
+	 * FIXME: Invalidate texture caches here.
+	 */
+	return 0;
+}
+
+static int
+via_init_mem_type(struct ttm_bo_device *bdev, uint32_t type,
+		struct ttm_mem_type_manager *man)
+{
+#if __OS_HAS_AGP
+	struct drm_via_private *dev_priv =
+		container_of(bdev, struct drm_via_private, bdev);
+	struct drm_device *dev = dev_priv->dev;
+#endif
+
+	switch (type) {
+	case TTM_PL_SYSTEM:
+		/* System memory */
+		man->flags = TTM_MEMTYPE_FLAG_MAPPABLE;
+		man->available_caching = TTM_PL_MASK_CACHING;
+		man->default_caching = TTM_PL_FLAG_CACHED;
+		break;
+
+	case TTM_PL_TT:
+		man->func = &ttm_bo_manager_func;
+
+		/* By default we handle PCI/PCIe DMA. If AGP is avaliable
+		 * then we use that instead */
+		man->flags = TTM_MEMTYPE_FLAG_MAPPABLE | TTM_MEMTYPE_FLAG_CMA;
+		man->available_caching = TTM_PL_MASK_CACHING;
+		man->default_caching = TTM_PL_FLAG_CACHED;
+
+#if __OS_HAS_AGP
+		if (drm_pci_device_is_agp(dev)) {
+			if (drm_core_has_AGP(dev) && dev->agp) {
+				man->flags = TTM_MEMTYPE_FLAG_MAPPABLE;
+				man->available_caching = TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_WC;
+				man->default_caching = TTM_PL_FLAG_WC;
+			} else
+				DRM_ERROR("AGP is possible but not enabled\n");
+		}
+#endif
+		break;
+
+	case TTM_PL_VRAM:
+		/* "On-card" video ram */
+		man->func = &ttm_bo_manager_func;
+		man->flags = TTM_MEMTYPE_FLAG_FIXED | TTM_MEMTYPE_FLAG_MAPPABLE;
+		man->available_caching = TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_WC;
+		man->default_caching = TTM_PL_FLAG_WC;
+		/* The display base address does not always equal the start of
+		 * the memory region of the VRAM. In our case it is */
+		man->gpu_offset = 0;
+		break;
+
+	case TTM_PL_PRIV0:
+		/* MMIO region */
+		man->func = &ttm_bo_manager_func;
+		man->flags = TTM_MEMTYPE_FLAG_FIXED | TTM_MEMTYPE_FLAG_MAPPABLE;
+		man->available_caching = TTM_PL_FLAG_UNCACHED;
+		man->default_caching = TTM_PL_FLAG_UNCACHED;
+		break;
+
+	default:
+		DRM_ERROR("Unsupported memory type %u\n", (unsigned)type);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void
+via_evict_flags(struct ttm_buffer_object *bo, struct ttm_placement *placement)
+{
+	switch (bo->mem.mem_type) {
+	case TTM_PL_VRAM:
+		ttm_placement_from_domain(bo, placement, TTM_PL_FLAG_TT | TTM_PL_FLAG_SYSTEM, bo->bdev);
+		break;
+
+	case TTM_PL_TT:
+	default:
+		ttm_placement_from_domain(bo, placement, TTM_PL_FLAG_SYSTEM, bo->bdev);
+		break;
+	}
+}
+
+/*
+ * Allocate DMA capable memory for the blit descriptor chain, and an array that keeps
+ * track of the pages we allocate. We don't want to use kmalloc for the descriptor
+ * chain because it may be quite large for some blits, and pages don't need to be
+ * contingous.
+ */
+struct drm_via_sg_info *
+via_alloc_desc_pages(struct ttm_tt *ttm, struct drm_device *dev,
+			unsigned long dev_start, enum dma_data_direction direction)
+{
+	struct drm_via_sg_info *vsg = kzalloc(sizeof(*vsg), GFP_KERNEL);
+	struct drm_via_private *dev_priv = dev->dev_private;
+	int desc_size = dev_priv->desc_size, i;
+
+	vsg->ttm = ttm;
+	vsg->dev_start = dev_start;
+	vsg->direction = direction;
+	vsg->num_desc = ttm->num_pages; // + 1;
+	vsg->descriptors_per_page = PAGE_SIZE / desc_size;
+	vsg->num_desc_pages = (vsg->num_desc + vsg->descriptors_per_page - 1) /
+				vsg->descriptors_per_page;
+
+	vsg->desc_pages = kzalloc(vsg->num_desc_pages * sizeof(void *), GFP_KERNEL);
+	if (!vsg->desc_pages)
+		return ERR_PTR(-ENOMEM);
+
+	vsg->state = dr_via_desc_pages_alloc;
+
+	/* Alloc pages for descriptor chain */
+	for (i = 0; i < vsg->num_desc_pages; ++i) {
+		vsg->desc_pages[i] = (unsigned long *) __get_free_page(GFP_KERNEL);
+
+		if (!vsg->desc_pages[i])
+			return ERR_PTR(-ENOMEM);
+	}
+	return vsg;
+}
+
+/* Move between GART and VRAM */
+static int
+via_move_blit(struct ttm_buffer_object *bo, bool evict, bool no_wait_gpu,
+		struct ttm_mem_reg *new_mem, struct ttm_mem_reg *old_mem)
+{
+	struct drm_via_private *dev_priv =
+		container_of(bo->bdev, struct drm_via_private, bdev);
+	enum dma_data_direction direction = DMA_TO_DEVICE;
+	unsigned long old_start, new_start, dev_addr = 0;
+	struct drm_via_sg_info *vsg;
+	int ret = -ENXIO;
+	struct via_fence *fence;
+
+	/* Real CPU physical address */
+	old_start = (old_mem->start << PAGE_SHIFT) + old_mem->bus.base;
+	new_start = (new_mem->start << PAGE_SHIFT) + new_mem->bus.base;
+
+	if (old_mem->mem_type == TTM_PL_VRAM) {
+		direction = DMA_FROM_DEVICE;
+		dev_addr = old_start;
+	} else if (new_mem->mem_type == TTM_PL_VRAM) {
+		/* direction is DMA_TO_DEVICE */
+		dev_addr = new_start;
+	}
+
+	/* device addr must be 16 byte align */
+	if (dev_addr & 0x0F)
+		return ret;
+
+	vsg = via_alloc_desc_pages(bo->ttm, dev_priv->dev, dev_addr, direction);
+	if (unlikely(IS_ERR(vsg)))
+		return PTR_ERR(vsg);
+
+	fence = via_fence_create_and_emit(dev_priv->dma_fences, vsg, 0);
+	if (unlikely(IS_ERR(fence)))
+		return PTR_ERR(fence);
+	return ttm_bo_move_accel_cleanup(bo, (void *)fence, evict, no_wait_gpu, new_mem);
+}
+
+static int
+via_move_from_vram(struct ttm_buffer_object *bo, bool interruptible,
+			bool no_wait_gpu, struct ttm_mem_reg *new_mem)
+{
+	struct ttm_mem_reg *old_mem = &bo->mem;
+	struct ttm_mem_reg tmp_mem;
+	struct ttm_placement placement;
+	u32 placements;
+	int ret;
+
+	tmp_mem = *new_mem;
+	tmp_mem.mm_node = NULL;
+	placement.fpfn = 0;
+	placement.lpfn = 0;
+	placement.num_placement = 1;
+	placement.placement = &placements;
+	placement.num_busy_placement = 1;
+	placement.busy_placement = &placements;
+	placements = TTM_PL_MASK_CACHING | TTM_PL_FLAG_TT;
+	ret = ttm_bo_mem_space(bo, &placement, &tmp_mem,
+				interruptible, no_wait_gpu);
+	if (unlikely(ret))
+		return ret;
+
+	/* Allocate some DMA memory for the GART address space */
+	ret = ttm_tt_set_placement_caching(bo->ttm, tmp_mem.placement);
+	if (unlikely(ret))
+		goto out_cleanup;
+
+	ret = ttm_tt_bind(bo->ttm, &tmp_mem);
+	if (unlikely(ret))
+		goto out_cleanup;
+
+	/* Move from the VRAM to GART space */
+	ret = via_move_blit(bo, true, no_wait_gpu, &tmp_mem, old_mem);
+	if (unlikely(ret))
+		goto out_cleanup;
+
+	/* Expose the GART region to the system memory */
+	ret = ttm_bo_move_ttm(bo, true, no_wait_gpu, new_mem);
+out_cleanup:
+	ttm_bo_mem_put(bo, &tmp_mem);
+	return ret;
+}
+
+static int
+via_move_to_vram(struct ttm_buffer_object *bo, bool interruptible,
+			bool no_wait_gpu, struct ttm_mem_reg *new_mem)
+{
+	struct ttm_mem_reg *old_mem = &bo->mem;
+	struct ttm_mem_reg tmp_mem;
+	struct ttm_placement placement;
+	u32 placements;
+	int ret;
+
+	tmp_mem = *new_mem;
+	tmp_mem.mm_node = NULL;
+	placement.fpfn = 0;
+	placement.lpfn = 0;
+	placement.num_placement = 1;
+	placement.placement = &placements;
+	placement.num_busy_placement = 1;
+	placement.busy_placement = &placements;
+	placements = TTM_PL_MASK_CACHING | TTM_PL_FLAG_TT;
+	ret = ttm_bo_mem_space(bo, &placement, &tmp_mem,
+				interruptible, no_wait_gpu);
+	if (unlikely(ret))
+		return ret;
+
+	/* Expose the GART region to the system memory */
+	ret = ttm_bo_move_ttm(bo, true, no_wait_gpu, &tmp_mem);
+	if (unlikely(ret))
+		goto out_cleanup;
+
+	/* Move from the GART to VRAM */
+	ret = via_move_blit(bo, true, no_wait_gpu, new_mem, old_mem);
+out_cleanup:
+	ttm_bo_mem_put(bo, &tmp_mem);
+	return ret;
+}
+
+static int
+via_bo_move(struct ttm_buffer_object *bo, bool evict, bool interruptible,
+		bool no_wait_gpu, struct ttm_mem_reg *new_mem)
+{
+	struct ttm_mem_reg *old_mem = &bo->mem;
+	int ret = 0;
+
+	/* No real memory copy. Just use the new_mem
+	 * directly. */
+	if ((old_mem->mem_type == TTM_PL_SYSTEM &&
+	    (new_mem->mem_type == TTM_PL_TT || !bo->ttm)) ||
+	    (old_mem->mem_type == TTM_PL_TT &&
+	     new_mem->mem_type == TTM_PL_SYSTEM) ||
+	    (new_mem->mem_type == TTM_PL_PRIV0)) {
+		BUG_ON(old_mem->mm_node != NULL);
+		*old_mem = *new_mem;
+		new_mem->mm_node = NULL;
+		return ret;
+	}
+
+	/* Accelerated copy involving the VRAM. */
+	if (old_mem->mem_type == TTM_PL_VRAM &&
+	    new_mem->mem_type == TTM_PL_SYSTEM) {
+		ret = via_move_from_vram(bo, interruptible, no_wait_gpu, new_mem);
+	} else if (old_mem->mem_type == TTM_PL_SYSTEM &&
+		   new_mem->mem_type == TTM_PL_VRAM) {
+		ret = via_move_to_vram(bo, interruptible, no_wait_gpu, new_mem);
+	} else {
+		ret = via_move_blit(bo, evict, no_wait_gpu, new_mem, old_mem);
+	}
+
+	if (ret) {
+		ret = ttm_bo_move_memcpy(bo, evict, no_wait_gpu, new_mem);
+	}
+	return ret;
+}
+
+static int
+via_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_reg *mem)
+{
+	struct drm_via_private *dev_priv =
+		container_of(bdev, struct drm_via_private, bdev);
+	struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type];
+	struct drm_device *dev = dev_priv->dev;
+
+	mem->bus.base = 0;
+	mem->bus.addr = NULL;
+	mem->bus.offset = mem->start << PAGE_SHIFT;
+	mem->bus.size = mem->num_pages << PAGE_SHIFT;
+	mem->bus.is_iomem = true;
+	if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE))
+		return -EINVAL;
+
+	switch (mem->mem_type) {
+	case TTM_PL_SYSTEM:
+		/* system memory */
+		mem->bus.is_iomem = false;
+		mem->bus.offset = 0;
+		return 0;
+
+	case TTM_PL_TT:
+#if __OS_HAS_AGP
+		if (drm_pci_device_is_agp(dev)) {
+			mem->bus.is_iomem = !dev->agp->cant_use_aperture;
+			mem->bus.base = dev->agp->base;
+		}
+#endif
+		break;
+
+	case TTM_PL_PRIV0:
+		mem->bus.base = pci_resource_start(dev->pdev, 1);
+		break;
+
+	case TTM_PL_VRAM:
+		if (dev->pci_device == PCI_DEVICE_ID_VIA_VX900)
+			mem->bus.base = pci_resource_start(dev->pdev, 2);
+		else
+			mem->bus.base = pci_resource_start(dev->pdev, 0);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void via_ttm_io_mem_free(struct ttm_bo_device *bdev, struct ttm_mem_reg *mem)
+{
+}
+
+static int via_verify_access(struct ttm_buffer_object *bo, struct file *filp)
+{
+	return 0;
+}
+
+static struct ttm_bo_driver via_bo_driver = {
+	.ttm_tt_create		= via_ttm_tt_create,
+	.ttm_tt_populate	= via_ttm_tt_populate,
+	.ttm_tt_unpopulate	= via_ttm_tt_unpopulate,
+	.invalidate_caches	= via_invalidate_caches,
+	.init_mem_type		= via_init_mem_type,
+	.evict_flags		= via_evict_flags,
+	.move			= via_bo_move,
+	.verify_access		= via_verify_access,
+	.sync_obj_signaled	= via_fence_signaled,
+	.sync_obj_wait		= via_fence_wait,
+	.sync_obj_flush		= via_fence_flush,
+	.sync_obj_unref		= via_fence_unref,
+	.sync_obj_ref		= via_fence_ref,
+	.io_mem_reserve		= via_ttm_io_mem_reserve,
+	.io_mem_free		= via_ttm_io_mem_free,
+};
+
+int via_ttm_init(struct drm_via_private *dev_priv)
+{
+	int ret = ttm_global_init(&dev_priv->mem_global_ref,
+				&dev_priv->bo_global_ref,
+				&via_bo_driver,
+				&dev_priv->bdev, false);
+	if (!ret)
+		dev_priv->bdev.dev_mapping = dev_priv->dev->dev_mapping;
+	return ret;
+}