summaryrefslogtreecommitdiffstats
path: root/arch/xtensa/kernel/pci-dma.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/xtensa/kernel/pci-dma.c')
-rw-r--r--arch/xtensa/kernel/pci-dma.c212
1 files changed, 212 insertions, 0 deletions
diff --git a/arch/xtensa/kernel/pci-dma.c b/arch/xtensa/kernel/pci-dma.c
new file mode 100644
index 000000000..1fc138b6b
--- /dev/null
+++ b/arch/xtensa/kernel/pci-dma.c
@@ -0,0 +1,212 @@
+/*
+ * DMA coherent memory allocation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Copyright (C) 2002 - 2005 Tensilica Inc.
+ * Copyright (C) 2015 Cadence Design Systems Inc.
+ *
+ * Based on version for i386.
+ *
+ * Chris Zankel <chris@zankel.net>
+ * Joe Taylor <joe@tensilica.com, joetylr@yahoo.com>
+ */
+
+#include <linux/dma-contiguous.h>
+#include <linux/dma-noncoherent.h>
+#include <linux/dma-direct.h>
+#include <linux/gfp.h>
+#include <linux/highmem.h>
+#include <linux/mm.h>
+#include <linux/types.h>
+#include <asm/cacheflush.h>
+#include <asm/io.h>
+#include <asm/platform.h>
+
+static void do_cache_op(phys_addr_t paddr, size_t size,
+ void (*fn)(unsigned long, unsigned long))
+{
+ unsigned long off = paddr & (PAGE_SIZE - 1);
+ unsigned long pfn = PFN_DOWN(paddr);
+ struct page *page = pfn_to_page(pfn);
+
+ if (!PageHighMem(page))
+ fn((unsigned long)phys_to_virt(paddr), size);
+ else
+ while (size > 0) {
+ size_t sz = min_t(size_t, size, PAGE_SIZE - off);
+ void *vaddr = kmap_atomic(page);
+
+ fn((unsigned long)vaddr + off, sz);
+ kunmap_atomic(vaddr);
+ off = 0;
+ ++page;
+ size -= sz;
+ }
+}
+
+void arch_sync_dma_for_cpu(struct device *dev, phys_addr_t paddr,
+ size_t size, enum dma_data_direction dir)
+{
+ switch (dir) {
+ case DMA_BIDIRECTIONAL:
+ case DMA_FROM_DEVICE:
+ do_cache_op(paddr, size, __invalidate_dcache_range);
+ break;
+
+ case DMA_NONE:
+ BUG();
+ break;
+
+ default:
+ break;
+ }
+}
+
+void arch_sync_dma_for_device(struct device *dev, phys_addr_t paddr,
+ size_t size, enum dma_data_direction dir)
+{
+ switch (dir) {
+ case DMA_BIDIRECTIONAL:
+ case DMA_TO_DEVICE:
+ if (XCHAL_DCACHE_IS_WRITEBACK)
+ do_cache_op(paddr, size, __flush_dcache_range);
+ break;
+
+ case DMA_NONE:
+ BUG();
+ break;
+
+ default:
+ break;
+ }
+}
+
+#ifdef CONFIG_MMU
+bool platform_vaddr_cached(const void *p)
+{
+ unsigned long addr = (unsigned long)p;
+
+ return addr >= XCHAL_KSEG_CACHED_VADDR &&
+ addr - XCHAL_KSEG_CACHED_VADDR < XCHAL_KSEG_SIZE;
+}
+
+bool platform_vaddr_uncached(const void *p)
+{
+ unsigned long addr = (unsigned long)p;
+
+ return addr >= XCHAL_KSEG_BYPASS_VADDR &&
+ addr - XCHAL_KSEG_BYPASS_VADDR < XCHAL_KSEG_SIZE;
+}
+
+void *platform_vaddr_to_uncached(void *p)
+{
+ return p + XCHAL_KSEG_BYPASS_VADDR - XCHAL_KSEG_CACHED_VADDR;
+}
+
+void *platform_vaddr_to_cached(void *p)
+{
+ return p + XCHAL_KSEG_CACHED_VADDR - XCHAL_KSEG_BYPASS_VADDR;
+}
+#else
+bool __attribute__((weak)) platform_vaddr_cached(const void *p)
+{
+ WARN_ONCE(1, "Default %s implementation is used\n", __func__);
+ return true;
+}
+
+bool __attribute__((weak)) platform_vaddr_uncached(const void *p)
+{
+ WARN_ONCE(1, "Default %s implementation is used\n", __func__);
+ return false;
+}
+
+void __attribute__((weak)) *platform_vaddr_to_uncached(void *p)
+{
+ WARN_ONCE(1, "Default %s implementation is used\n", __func__);
+ return p;
+}
+
+void __attribute__((weak)) *platform_vaddr_to_cached(void *p)
+{
+ WARN_ONCE(1, "Default %s implementation is used\n", __func__);
+ return p;
+}
+#endif
+
+/*
+ * Note: We assume that the full memory space is always mapped to 'kseg'
+ * Otherwise we have to use page attributes (not implemented).
+ */
+
+void *arch_dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,
+ gfp_t flag, unsigned long attrs)
+{
+ unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+ struct page *page = NULL;
+
+ /* ignore region speicifiers */
+
+ flag &= ~(__GFP_DMA | __GFP_HIGHMEM);
+
+ if (dev == NULL || (dev->coherent_dma_mask < 0xffffffff))
+ flag |= GFP_DMA;
+
+ if (gfpflags_allow_blocking(flag))
+ page = dma_alloc_from_contiguous(dev, count, get_order(size),
+ flag & __GFP_NOWARN);
+
+ if (!page)
+ page = alloc_pages(flag, get_order(size));
+
+ if (!page)
+ return NULL;
+
+ *handle = phys_to_dma(dev, page_to_phys(page));
+
+ if (attrs & DMA_ATTR_NO_KERNEL_MAPPING) {
+ return page;
+ }
+
+#ifdef CONFIG_MMU
+ if (PageHighMem(page)) {
+ void *p;
+
+ p = dma_common_contiguous_remap(page, size, VM_MAP,
+ pgprot_noncached(PAGE_KERNEL),
+ __builtin_return_address(0));
+ if (!p) {
+ if (!dma_release_from_contiguous(dev, page, count))
+ __free_pages(page, get_order(size));
+ }
+ return p;
+ }
+#endif
+ BUG_ON(!platform_vaddr_cached(page_address(page)));
+ __invalidate_dcache_range((unsigned long)page_address(page), size);
+ return platform_vaddr_to_uncached(page_address(page));
+}
+
+void arch_dma_free(struct device *dev, size_t size, void *vaddr,
+ dma_addr_t dma_handle, unsigned long attrs)
+{
+ unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+ struct page *page;
+
+ if (attrs & DMA_ATTR_NO_KERNEL_MAPPING) {
+ page = vaddr;
+ } else if (platform_vaddr_uncached(vaddr)) {
+ page = virt_to_page(platform_vaddr_to_cached(vaddr));
+ } else {
+#ifdef CONFIG_MMU
+ dma_common_free_remap(vaddr, size, VM_MAP);
+#endif
+ page = pfn_to_page(PHYS_PFN(dma_to_phys(dev, dma_handle)));
+ }
+
+ if (!dma_release_from_contiguous(dev, page, count))
+ __free_pages(page, get_order(size));
+}