18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * io-unit.c: IO-UNIT specific routines for memory management. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 1997,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/init.h> 108c2ecf20Sopenharmony_ci#include <linux/slab.h> 118c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 128c2ecf20Sopenharmony_ci#include <linux/mm.h> 138c2ecf20Sopenharmony_ci#include <linux/bitops.h> 148c2ecf20Sopenharmony_ci#include <linux/dma-map-ops.h> 158c2ecf20Sopenharmony_ci#include <linux/of.h> 168c2ecf20Sopenharmony_ci#include <linux/of_device.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include <asm/io.h> 198c2ecf20Sopenharmony_ci#include <asm/io-unit.h> 208c2ecf20Sopenharmony_ci#include <asm/mxcc.h> 218c2ecf20Sopenharmony_ci#include <asm/cacheflush.h> 228c2ecf20Sopenharmony_ci#include <asm/tlbflush.h> 238c2ecf20Sopenharmony_ci#include <asm/dma.h> 248c2ecf20Sopenharmony_ci#include <asm/oplib.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include "mm_32.h" 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* #define IOUNIT_DEBUG */ 298c2ecf20Sopenharmony_ci#ifdef IOUNIT_DEBUG 308c2ecf20Sopenharmony_ci#define IOD(x) printk(x) 318c2ecf20Sopenharmony_ci#else 328c2ecf20Sopenharmony_ci#define IOD(x) do { } while (0) 338c2ecf20Sopenharmony_ci#endif 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#define IOPERM (IOUPTE_CACHE | IOUPTE_WRITE | IOUPTE_VALID) 368c2ecf20Sopenharmony_ci#define MKIOPTE(phys) __iopte((((phys)>>4) & IOUPTE_PAGE) | IOPERM) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic const struct dma_map_ops iounit_dma_ops; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic void __init iounit_iommu_init(struct platform_device *op) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci struct iounit_struct *iounit; 438c2ecf20Sopenharmony_ci iopte_t __iomem *xpt; 448c2ecf20Sopenharmony_ci iopte_t __iomem *xptend; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci iounit = kzalloc(sizeof(struct iounit_struct), GFP_ATOMIC); 478c2ecf20Sopenharmony_ci if (!iounit) { 488c2ecf20Sopenharmony_ci prom_printf("SUN4D: Cannot alloc iounit, halting.\n"); 498c2ecf20Sopenharmony_ci prom_halt(); 508c2ecf20Sopenharmony_ci } 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci iounit->limit[0] = IOUNIT_BMAP1_START; 538c2ecf20Sopenharmony_ci iounit->limit[1] = IOUNIT_BMAP2_START; 548c2ecf20Sopenharmony_ci iounit->limit[2] = IOUNIT_BMAPM_START; 558c2ecf20Sopenharmony_ci iounit->limit[3] = IOUNIT_BMAPM_END; 568c2ecf20Sopenharmony_ci iounit->rotor[1] = IOUNIT_BMAP2_START; 578c2ecf20Sopenharmony_ci iounit->rotor[2] = IOUNIT_BMAPM_START; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci xpt = of_ioremap(&op->resource[2], 0, PAGE_SIZE * 16, "XPT"); 608c2ecf20Sopenharmony_ci if (!xpt) { 618c2ecf20Sopenharmony_ci prom_printf("SUN4D: Cannot map External Page Table."); 628c2ecf20Sopenharmony_ci prom_halt(); 638c2ecf20Sopenharmony_ci } 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci op->dev.archdata.iommu = iounit; 668c2ecf20Sopenharmony_ci iounit->page_table = xpt; 678c2ecf20Sopenharmony_ci spin_lock_init(&iounit->lock); 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci xptend = iounit->page_table + (16 * PAGE_SIZE) / sizeof(iopte_t); 708c2ecf20Sopenharmony_ci for (; xpt < xptend; xpt++) 718c2ecf20Sopenharmony_ci sbus_writel(0, xpt); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci op->dev.dma_ops = &iounit_dma_ops; 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic int __init iounit_init(void) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci extern void sun4d_init_sbi_irq(void); 798c2ecf20Sopenharmony_ci struct device_node *dp; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci for_each_node_by_name(dp, "sbi") { 828c2ecf20Sopenharmony_ci struct platform_device *op = of_find_device_by_node(dp); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci iounit_iommu_init(op); 858c2ecf20Sopenharmony_ci of_propagate_archdata(op); 868c2ecf20Sopenharmony_ci } 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci sun4d_init_sbi_irq(); 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci return 0; 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cisubsys_initcall(iounit_init); 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci/* One has to hold iounit->lock to call this */ 968c2ecf20Sopenharmony_cistatic unsigned long iounit_get_area(struct iounit_struct *iounit, unsigned long vaddr, int size) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci int i, j, k, npages; 998c2ecf20Sopenharmony_ci unsigned long rotor, scan, limit; 1008c2ecf20Sopenharmony_ci iopte_t iopte; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci npages = ((vaddr & ~PAGE_MASK) + size + (PAGE_SIZE-1)) >> PAGE_SHIFT; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci /* A tiny bit of magic ingredience :) */ 1058c2ecf20Sopenharmony_ci switch (npages) { 1068c2ecf20Sopenharmony_ci case 1: i = 0x0231; break; 1078c2ecf20Sopenharmony_ci case 2: i = 0x0132; break; 1088c2ecf20Sopenharmony_ci default: i = 0x0213; break; 1098c2ecf20Sopenharmony_ci } 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci IOD(("iounit_get_area(%08lx,%d[%d])=", vaddr, size, npages)); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cinext: j = (i & 15); 1148c2ecf20Sopenharmony_ci rotor = iounit->rotor[j - 1]; 1158c2ecf20Sopenharmony_ci limit = iounit->limit[j]; 1168c2ecf20Sopenharmony_ci scan = rotor; 1178c2ecf20Sopenharmony_cinexti: scan = find_next_zero_bit(iounit->bmap, limit, scan); 1188c2ecf20Sopenharmony_ci if (scan + npages > limit) { 1198c2ecf20Sopenharmony_ci if (limit != rotor) { 1208c2ecf20Sopenharmony_ci limit = rotor; 1218c2ecf20Sopenharmony_ci scan = iounit->limit[j - 1]; 1228c2ecf20Sopenharmony_ci goto nexti; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci i >>= 4; 1258c2ecf20Sopenharmony_ci if (!(i & 15)) 1268c2ecf20Sopenharmony_ci panic("iounit_get_area: Couldn't find free iopte slots for (%08lx,%d)\n", vaddr, size); 1278c2ecf20Sopenharmony_ci goto next; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci for (k = 1, scan++; k < npages; k++) 1308c2ecf20Sopenharmony_ci if (test_bit(scan++, iounit->bmap)) 1318c2ecf20Sopenharmony_ci goto nexti; 1328c2ecf20Sopenharmony_ci iounit->rotor[j - 1] = (scan < limit) ? scan : iounit->limit[j - 1]; 1338c2ecf20Sopenharmony_ci scan -= npages; 1348c2ecf20Sopenharmony_ci iopte = MKIOPTE(__pa(vaddr & PAGE_MASK)); 1358c2ecf20Sopenharmony_ci vaddr = IOUNIT_DMA_BASE + (scan << PAGE_SHIFT) + (vaddr & ~PAGE_MASK); 1368c2ecf20Sopenharmony_ci for (k = 0; k < npages; k++, iopte = __iopte(iopte_val(iopte) + 0x100), scan++) { 1378c2ecf20Sopenharmony_ci set_bit(scan, iounit->bmap); 1388c2ecf20Sopenharmony_ci sbus_writel(iopte_val(iopte), &iounit->page_table[scan]); 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci IOD(("%08lx\n", vaddr)); 1418c2ecf20Sopenharmony_ci return vaddr; 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic dma_addr_t iounit_map_page(struct device *dev, struct page *page, 1458c2ecf20Sopenharmony_ci unsigned long offset, size_t len, enum dma_data_direction dir, 1468c2ecf20Sopenharmony_ci unsigned long attrs) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci void *vaddr = page_address(page) + offset; 1498c2ecf20Sopenharmony_ci struct iounit_struct *iounit = dev->archdata.iommu; 1508c2ecf20Sopenharmony_ci unsigned long ret, flags; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci /* XXX So what is maxphys for us and how do drivers know it? */ 1538c2ecf20Sopenharmony_ci if (!len || len > 256 * 1024) 1548c2ecf20Sopenharmony_ci return DMA_MAPPING_ERROR; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci spin_lock_irqsave(&iounit->lock, flags); 1578c2ecf20Sopenharmony_ci ret = iounit_get_area(iounit, (unsigned long)vaddr, len); 1588c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&iounit->lock, flags); 1598c2ecf20Sopenharmony_ci return ret; 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cistatic int iounit_map_sg(struct device *dev, struct scatterlist *sgl, int nents, 1638c2ecf20Sopenharmony_ci enum dma_data_direction dir, unsigned long attrs) 1648c2ecf20Sopenharmony_ci{ 1658c2ecf20Sopenharmony_ci struct iounit_struct *iounit = dev->archdata.iommu; 1668c2ecf20Sopenharmony_ci struct scatterlist *sg; 1678c2ecf20Sopenharmony_ci unsigned long flags; 1688c2ecf20Sopenharmony_ci int i; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci /* FIXME: Cache some resolved pages - often several sg entries are to the same page */ 1718c2ecf20Sopenharmony_ci spin_lock_irqsave(&iounit->lock, flags); 1728c2ecf20Sopenharmony_ci for_each_sg(sgl, sg, nents, i) { 1738c2ecf20Sopenharmony_ci sg->dma_address = iounit_get_area(iounit, (unsigned long) sg_virt(sg), sg->length); 1748c2ecf20Sopenharmony_ci sg->dma_length = sg->length; 1758c2ecf20Sopenharmony_ci } 1768c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&iounit->lock, flags); 1778c2ecf20Sopenharmony_ci return nents; 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cistatic void iounit_unmap_page(struct device *dev, dma_addr_t vaddr, size_t len, 1818c2ecf20Sopenharmony_ci enum dma_data_direction dir, unsigned long attrs) 1828c2ecf20Sopenharmony_ci{ 1838c2ecf20Sopenharmony_ci struct iounit_struct *iounit = dev->archdata.iommu; 1848c2ecf20Sopenharmony_ci unsigned long flags; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci spin_lock_irqsave(&iounit->lock, flags); 1878c2ecf20Sopenharmony_ci len = ((vaddr & ~PAGE_MASK) + len + (PAGE_SIZE-1)) >> PAGE_SHIFT; 1888c2ecf20Sopenharmony_ci vaddr = (vaddr - IOUNIT_DMA_BASE) >> PAGE_SHIFT; 1898c2ecf20Sopenharmony_ci IOD(("iounit_release %08lx-%08lx\n", (long)vaddr, (long)len+vaddr)); 1908c2ecf20Sopenharmony_ci for (len += vaddr; vaddr < len; vaddr++) 1918c2ecf20Sopenharmony_ci clear_bit(vaddr, iounit->bmap); 1928c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&iounit->lock, flags); 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic void iounit_unmap_sg(struct device *dev, struct scatterlist *sgl, 1968c2ecf20Sopenharmony_ci int nents, enum dma_data_direction dir, unsigned long attrs) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci struct iounit_struct *iounit = dev->archdata.iommu; 1998c2ecf20Sopenharmony_ci unsigned long flags, vaddr, len; 2008c2ecf20Sopenharmony_ci struct scatterlist *sg; 2018c2ecf20Sopenharmony_ci int i; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci spin_lock_irqsave(&iounit->lock, flags); 2048c2ecf20Sopenharmony_ci for_each_sg(sgl, sg, nents, i) { 2058c2ecf20Sopenharmony_ci len = ((sg->dma_address & ~PAGE_MASK) + sg->length + (PAGE_SIZE-1)) >> PAGE_SHIFT; 2068c2ecf20Sopenharmony_ci vaddr = (sg->dma_address - IOUNIT_DMA_BASE) >> PAGE_SHIFT; 2078c2ecf20Sopenharmony_ci IOD(("iounit_release %08lx-%08lx\n", (long)vaddr, (long)len+vaddr)); 2088c2ecf20Sopenharmony_ci for (len += vaddr; vaddr < len; vaddr++) 2098c2ecf20Sopenharmony_ci clear_bit(vaddr, iounit->bmap); 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&iounit->lock, flags); 2128c2ecf20Sopenharmony_ci} 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci#ifdef CONFIG_SBUS 2158c2ecf20Sopenharmony_cistatic void *iounit_alloc(struct device *dev, size_t len, 2168c2ecf20Sopenharmony_ci dma_addr_t *dma_handle, gfp_t gfp, unsigned long attrs) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci struct iounit_struct *iounit = dev->archdata.iommu; 2198c2ecf20Sopenharmony_ci unsigned long va, addr, page, end, ret; 2208c2ecf20Sopenharmony_ci pgprot_t dvma_prot; 2218c2ecf20Sopenharmony_ci iopte_t __iomem *iopte; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci /* XXX So what is maxphys for us and how do drivers know it? */ 2248c2ecf20Sopenharmony_ci if (!len || len > 256 * 1024) 2258c2ecf20Sopenharmony_ci return NULL; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci len = PAGE_ALIGN(len); 2288c2ecf20Sopenharmony_ci va = __get_free_pages(gfp | __GFP_ZERO, get_order(len)); 2298c2ecf20Sopenharmony_ci if (!va) 2308c2ecf20Sopenharmony_ci return NULL; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci addr = ret = sparc_dma_alloc_resource(dev, len); 2338c2ecf20Sopenharmony_ci if (!addr) 2348c2ecf20Sopenharmony_ci goto out_free_pages; 2358c2ecf20Sopenharmony_ci *dma_handle = addr; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci dvma_prot = __pgprot(SRMMU_CACHE | SRMMU_ET_PTE | SRMMU_PRIV); 2388c2ecf20Sopenharmony_ci end = PAGE_ALIGN((addr + len)); 2398c2ecf20Sopenharmony_ci while(addr < end) { 2408c2ecf20Sopenharmony_ci page = va; 2418c2ecf20Sopenharmony_ci { 2428c2ecf20Sopenharmony_ci pmd_t *pmdp; 2438c2ecf20Sopenharmony_ci pte_t *ptep; 2448c2ecf20Sopenharmony_ci long i; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci pmdp = pmd_off_k(addr); 2478c2ecf20Sopenharmony_ci ptep = pte_offset_map(pmdp, addr); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci set_pte(ptep, mk_pte(virt_to_page(page), dvma_prot)); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci i = ((addr - IOUNIT_DMA_BASE) >> PAGE_SHIFT); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci iopte = iounit->page_table + i; 2548c2ecf20Sopenharmony_ci sbus_writel(iopte_val(MKIOPTE(__pa(page))), iopte); 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci addr += PAGE_SIZE; 2578c2ecf20Sopenharmony_ci va += PAGE_SIZE; 2588c2ecf20Sopenharmony_ci } 2598c2ecf20Sopenharmony_ci flush_cache_all(); 2608c2ecf20Sopenharmony_ci flush_tlb_all(); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci return (void *)ret; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ciout_free_pages: 2658c2ecf20Sopenharmony_ci free_pages(va, get_order(len)); 2668c2ecf20Sopenharmony_ci return NULL; 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic void iounit_free(struct device *dev, size_t size, void *cpu_addr, 2708c2ecf20Sopenharmony_ci dma_addr_t dma_addr, unsigned long attrs) 2718c2ecf20Sopenharmony_ci{ 2728c2ecf20Sopenharmony_ci /* XXX Somebody please fill this in */ 2738c2ecf20Sopenharmony_ci} 2748c2ecf20Sopenharmony_ci#endif 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_cistatic const struct dma_map_ops iounit_dma_ops = { 2778c2ecf20Sopenharmony_ci#ifdef CONFIG_SBUS 2788c2ecf20Sopenharmony_ci .alloc = iounit_alloc, 2798c2ecf20Sopenharmony_ci .free = iounit_free, 2808c2ecf20Sopenharmony_ci#endif 2818c2ecf20Sopenharmony_ci .map_page = iounit_map_page, 2828c2ecf20Sopenharmony_ci .unmap_page = iounit_unmap_page, 2838c2ecf20Sopenharmony_ci .map_sg = iounit_map_sg, 2848c2ecf20Sopenharmony_ci .unmap_sg = iounit_unmap_sg, 2858c2ecf20Sopenharmony_ci}; 286