| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Mips Jazz DMA controller support | |
| * Copyright (C) 1995, 1996 by Andreas Busse | |
| * | |
| * NOTE: Some of the argument checking could be removed when | |
| * things have settled down. Also, instead of returning 0xffffffff | |
| * on failure of vdma_alloc() one could leave page #0 unused | |
| * and return the more usual NULL pointer as logical address. | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/init.h> | |
| #include <linux/export.h> | |
| #include <linux/errno.h> | |
| #include <linux/mm.h> | |
| #include <linux/bootmem.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/gfp.h> | |
| #include <asm/mipsregs.h> | |
| #include <asm/jazz.h> | |
| #include <asm/io.h> | |
| #include <linux/uaccess.h> | |
| #include <asm/dma.h> | |
| #include <asm/jazzdma.h> | |
| #include <asm/pgtable.h> | |
| /* | |
| * Set this to one to enable additional vdma debug code. | |
| */ | |
| #define CONF_DEBUG_VDMA 0 | |
| static VDMA_PGTBL_ENTRY *pgtbl; | |
| static DEFINE_SPINLOCK(vdma_lock); | |
| /* | |
| * Debug stuff | |
| */ | |
| #define vdma_debug ((CONF_DEBUG_VDMA) ? debuglvl : 0) | |
| static int debuglvl = 3; | |
| /* | |
| * Initialize the pagetable with a one-to-one mapping of | |
| * the first 16 Mbytes of main memory and declare all | |
| * entries to be unused. Using this method will at least | |
| * allow some early device driver operations to work. | |
| */ | |
| static inline void vdma_pgtbl_init(void) | |
| { | |
| unsigned long paddr = 0; | |
| int i; | |
| for (i = 0; i < VDMA_PGTBL_ENTRIES; i++) { | |
| pgtbl[i].frame = paddr; | |
| pgtbl[i].owner = VDMA_PAGE_EMPTY; | |
| paddr += VDMA_PAGESIZE; | |
| } | |
| } | |
| /* | |
| * Initialize the Jazz R4030 dma controller | |
| */ | |
| static int __init vdma_init(void) | |
| { | |
| /* | |
| * Allocate 32k of memory for DMA page tables. This needs to be page | |
| * aligned and should be uncached to avoid cache flushing after every | |
| * update. | |
| */ | |
| pgtbl = (VDMA_PGTBL_ENTRY *)__get_free_pages(GFP_KERNEL | GFP_DMA, | |
| get_order(VDMA_PGTBL_SIZE)); | |
| BUG_ON(!pgtbl); | |
| dma_cache_wback_inv((unsigned long)pgtbl, VDMA_PGTBL_SIZE); | |
| pgtbl = (VDMA_PGTBL_ENTRY *)KSEG1ADDR(pgtbl); | |
| /* | |
| * Clear the R4030 translation table | |
| */ | |
| vdma_pgtbl_init(); | |
| r4030_write_reg32(JAZZ_R4030_TRSTBL_BASE, CPHYSADDR(pgtbl)); | |
| r4030_write_reg32(JAZZ_R4030_TRSTBL_LIM, VDMA_PGTBL_SIZE); | |
| r4030_write_reg32(JAZZ_R4030_TRSTBL_INV, 0); | |
| printk(KERN_INFO "VDMA: R4030 DMA pagetables initialized.\n"); | |
| return 0; | |
| } | |
| /* | |
| * Allocate DMA pagetables using a simple first-fit algorithm | |
| */ | |
| unsigned long vdma_alloc(unsigned long paddr, unsigned long size) | |
| { | |
| int first, last, pages, frame, i; | |
| unsigned long laddr, flags; | |
| /* check arguments */ | |
| if (paddr > 0x1fffffff) { | |
| if (vdma_debug) | |
| printk("vdma_alloc: Invalid physical address: %08lx\n", | |
| paddr); | |
| return VDMA_ERROR; /* invalid physical address */ | |
| } | |
| if (size > 0x400000 || size == 0) { | |
| if (vdma_debug) | |
| printk("vdma_alloc: Invalid size: %08lx\n", size); | |
| return VDMA_ERROR; /* invalid physical address */ | |
| } | |
| spin_lock_irqsave(&vdma_lock, flags); | |
| /* | |
| * Find free chunk | |
| */ | |
| pages = VDMA_PAGE(paddr + size) - VDMA_PAGE(paddr) + 1; | |
| first = 0; | |
| while (1) { | |
| while (pgtbl[first].owner != VDMA_PAGE_EMPTY && | |
| first < VDMA_PGTBL_ENTRIES) first++; | |
| if (first + pages > VDMA_PGTBL_ENTRIES) { /* nothing free */ | |
| spin_unlock_irqrestore(&vdma_lock, flags); | |
| return VDMA_ERROR; | |
| } | |
| last = first + 1; | |
| while (pgtbl[last].owner == VDMA_PAGE_EMPTY | |
| && last - first < pages) | |
| last++; | |
| if (last - first == pages) | |
| break; /* found */ | |
| first = last + 1; | |
| } | |
| /* | |
| * Mark pages as allocated | |
| */ | |
| laddr = (first << 12) + (paddr & (VDMA_PAGESIZE - 1)); | |
| frame = paddr & ~(VDMA_PAGESIZE - 1); | |
| for (i = first; i < last; i++) { | |
| pgtbl[i].frame = frame; | |
| pgtbl[i].owner = laddr; | |
| frame += VDMA_PAGESIZE; | |
| } | |
| /* | |
| * Update translation table and return logical start address | |
| */ | |
| r4030_write_reg32(JAZZ_R4030_TRSTBL_INV, 0); | |
| if (vdma_debug > 1) | |
| printk("vdma_alloc: Allocated %d pages starting from %08lx\n", | |
| pages, laddr); | |
| if (vdma_debug > 2) { | |
| printk("LADDR: "); | |
| for (i = first; i < last; i++) | |
| printk("%08x ", i << 12); | |
| printk("\nPADDR: "); | |
| for (i = first; i < last; i++) | |
| printk("%08x ", pgtbl[i].frame); | |
| printk("\nOWNER: "); | |
| for (i = first; i < last; i++) | |
| printk("%08x ", pgtbl[i].owner); | |
| printk("\n"); | |
| } | |
| spin_unlock_irqrestore(&vdma_lock, flags); | |
| return laddr; | |
| } | |
| EXPORT_SYMBOL(vdma_alloc); | |
| /* | |
| * Free previously allocated dma translation pages | |
| * Note that this does NOT change the translation table, | |
| * it just marks the free'd pages as unused! | |
| */ | |
| int vdma_free(unsigned long laddr) | |
| { | |
| int i; | |
| i = laddr >> 12; | |
| if (pgtbl[i].owner != laddr) { | |
| printk | |
| ("vdma_free: trying to free other's dma pages, laddr=%8lx\n", | |
| laddr); | |
| return -1; | |
| } | |
| while (i < VDMA_PGTBL_ENTRIES && pgtbl[i].owner == laddr) { | |
| pgtbl[i].owner = VDMA_PAGE_EMPTY; | |
| i++; | |
| } | |
| if (vdma_debug > 1) | |
| printk("vdma_free: freed %ld pages starting from %08lx\n", | |
| i - (laddr >> 12), laddr); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(vdma_free); | |
| /* | |
| * Map certain page(s) to another physical address. | |
| * Caller must have allocated the page(s) before. | |
| */ | |
| int vdma_remap(unsigned long laddr, unsigned long paddr, unsigned long size) | |
| { | |
| int first, pages; | |
| if (laddr > 0xffffff) { | |
| if (vdma_debug) | |
| printk | |
| ("vdma_map: Invalid logical address: %08lx\n", | |
| laddr); | |
| return -EINVAL; /* invalid logical address */ | |
| } | |
| if (paddr > 0x1fffffff) { | |
| if (vdma_debug) | |
| printk | |
| ("vdma_map: Invalid physical address: %08lx\n", | |
| paddr); | |
| return -EINVAL; /* invalid physical address */ | |
| } | |
| pages = (((paddr & (VDMA_PAGESIZE - 1)) + size) >> 12) + 1; | |
| first = laddr >> 12; | |
| if (vdma_debug) | |
| printk("vdma_remap: first=%x, pages=%x\n", first, pages); | |
| if (first + pages > VDMA_PGTBL_ENTRIES) { | |
| if (vdma_debug) | |
| printk("vdma_alloc: Invalid size: %08lx\n", size); | |
| return -EINVAL; | |
| } | |
| paddr &= ~(VDMA_PAGESIZE - 1); | |
| while (pages > 0 && first < VDMA_PGTBL_ENTRIES) { | |
| if (pgtbl[first].owner != laddr) { | |
| if (vdma_debug) | |
| printk("Trying to remap other's pages.\n"); | |
| return -EPERM; /* not owner */ | |
| } | |
| pgtbl[first].frame = paddr; | |
| paddr += VDMA_PAGESIZE; | |
| first++; | |
| pages--; | |
| } | |
| /* | |
| * Update translation table | |
| */ | |
| r4030_write_reg32(JAZZ_R4030_TRSTBL_INV, 0); | |
| if (vdma_debug > 2) { | |
| int i; | |
| pages = (((paddr & (VDMA_PAGESIZE - 1)) + size) >> 12) + 1; | |
| first = laddr >> 12; | |
| printk("LADDR: "); | |
| for (i = first; i < first + pages; i++) | |
| printk("%08x ", i << 12); | |
| printk("\nPADDR: "); | |
| for (i = first; i < first + pages; i++) | |
| printk("%08x ", pgtbl[i].frame); | |
| printk("\nOWNER: "); | |
| for (i = first; i < first + pages; i++) | |
| printk("%08x ", pgtbl[i].owner); | |
| printk("\n"); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Translate a physical address to a logical address. | |
| * This will return the logical address of the first | |
| * match. | |
| */ | |
| unsigned long vdma_phys2log(unsigned long paddr) | |
| { | |
| int i; | |
| int frame; | |
| frame = paddr & ~(VDMA_PAGESIZE - 1); | |
| for (i = 0; i < VDMA_PGTBL_ENTRIES; i++) { | |
| if (pgtbl[i].frame == frame) | |
| break; | |
| } | |
| if (i == VDMA_PGTBL_ENTRIES) | |
| return ~0UL; | |
| return (i << 12) + (paddr & (VDMA_PAGESIZE - 1)); | |
| } | |
| EXPORT_SYMBOL(vdma_phys2log); | |
| /* | |
| * Translate a logical DMA address to a physical address | |
| */ | |
| unsigned long vdma_log2phys(unsigned long laddr) | |
| { | |
| return pgtbl[laddr >> 12].frame + (laddr & (VDMA_PAGESIZE - 1)); | |
| } | |
| EXPORT_SYMBOL(vdma_log2phys); | |
| /* | |
| * Print DMA statistics | |
| */ | |
| void vdma_stats(void) | |
| { | |
| int i; | |
| printk("vdma_stats: CONFIG: %08x\n", | |
| r4030_read_reg32(JAZZ_R4030_CONFIG)); | |
| printk("R4030 translation table base: %08x\n", | |
| r4030_read_reg32(JAZZ_R4030_TRSTBL_BASE)); | |
| printk("R4030 translation table limit: %08x\n", | |
| r4030_read_reg32(JAZZ_R4030_TRSTBL_LIM)); | |
| printk("vdma_stats: INV_ADDR: %08x\n", | |
| r4030_read_reg32(JAZZ_R4030_INV_ADDR)); | |
| printk("vdma_stats: R_FAIL_ADDR: %08x\n", | |
| r4030_read_reg32(JAZZ_R4030_R_FAIL_ADDR)); | |
| printk("vdma_stats: M_FAIL_ADDR: %08x\n", | |
| r4030_read_reg32(JAZZ_R4030_M_FAIL_ADDR)); | |
| printk("vdma_stats: IRQ_SOURCE: %08x\n", | |
| r4030_read_reg32(JAZZ_R4030_IRQ_SOURCE)); | |
| printk("vdma_stats: I386_ERROR: %08x\n", | |
| r4030_read_reg32(JAZZ_R4030_I386_ERROR)); | |
| printk("vdma_chnl_modes: "); | |
| for (i = 0; i < 8; i++) | |
| printk("%04x ", | |
| (unsigned) r4030_read_reg32(JAZZ_R4030_CHNL_MODE + | |
| (i << 5))); | |
| printk("\n"); | |
| printk("vdma_chnl_enables: "); | |
| for (i = 0; i < 8; i++) | |
| printk("%04x ", | |
| (unsigned) r4030_read_reg32(JAZZ_R4030_CHNL_ENABLE + | |
| (i << 5))); | |
| printk("\n"); | |
| } | |
| /* | |
| * DMA transfer functions | |
| */ | |
| /* | |
| * Enable a DMA channel. Also clear any error conditions. | |
| */ | |
| void vdma_enable(int channel) | |
| { | |
| int status; | |
| if (vdma_debug) | |
| printk("vdma_enable: channel %d\n", channel); | |
| /* | |
| * Check error conditions first | |
| */ | |
| status = r4030_read_reg32(JAZZ_R4030_CHNL_ENABLE + (channel << 5)); | |
| if (status & 0x400) | |
| printk("VDMA: Channel %d: Address error!\n", channel); | |
| if (status & 0x200) | |
| printk("VDMA: Channel %d: Memory error!\n", channel); | |
| /* | |
| * Clear all interrupt flags | |
| */ | |
| r4030_write_reg32(JAZZ_R4030_CHNL_ENABLE + (channel << 5), | |
| r4030_read_reg32(JAZZ_R4030_CHNL_ENABLE + | |
| (channel << 5)) | R4030_TC_INTR | |
| | R4030_MEM_INTR | R4030_ADDR_INTR); | |
| /* | |
| * Enable the desired channel | |
| */ | |
| r4030_write_reg32(JAZZ_R4030_CHNL_ENABLE + (channel << 5), | |
| r4030_read_reg32(JAZZ_R4030_CHNL_ENABLE + | |
| (channel << 5)) | | |
| R4030_CHNL_ENABLE); | |
| } | |
| EXPORT_SYMBOL(vdma_enable); | |
| /* | |
| * Disable a DMA channel | |
| */ | |
| void vdma_disable(int channel) | |
| { | |
| if (vdma_debug) { | |
| int status = | |
| r4030_read_reg32(JAZZ_R4030_CHNL_ENABLE + | |
| (channel << 5)); | |
| printk("vdma_disable: channel %d\n", channel); | |
| printk("VDMA: channel %d status: %04x (%s) mode: " | |
| "%02x addr: %06x count: %06x\n", | |
| channel, status, | |
| ((status & 0x600) ? "ERROR" : "OK"), | |
| (unsigned) r4030_read_reg32(JAZZ_R4030_CHNL_MODE + | |
| (channel << 5)), | |
| (unsigned) r4030_read_reg32(JAZZ_R4030_CHNL_ADDR + | |
| (channel << 5)), | |
| (unsigned) r4030_read_reg32(JAZZ_R4030_CHNL_COUNT + | |
| (channel << 5))); | |
| } | |
| r4030_write_reg32(JAZZ_R4030_CHNL_ENABLE + (channel << 5), | |
| r4030_read_reg32(JAZZ_R4030_CHNL_ENABLE + | |
| (channel << 5)) & | |
| ~R4030_CHNL_ENABLE); | |
| /* | |
| * After disabling a DMA channel a remote bus register should be | |
| * read to ensure that the current DMA acknowledge cycle is completed. | |
| */ | |
| *((volatile unsigned int *) JAZZ_DUMMY_DEVICE); | |
| } | |
| EXPORT_SYMBOL(vdma_disable); | |
| /* | |
| * Set DMA mode. This function accepts the mode values used | |
| * to set a PC-style DMA controller. For the SCSI and FDC | |
| * channels, we also set the default modes each time we're | |
| * called. | |
| * NOTE: The FAST and BURST dma modes are supported by the | |
| * R4030 Rev. 2 and PICA chipsets only. I leave them disabled | |
| * for now. | |
| */ | |
| void vdma_set_mode(int channel, int mode) | |
| { | |
| if (vdma_debug) | |
| printk("vdma_set_mode: channel %d, mode 0x%x\n", channel, | |
| mode); | |
| switch (channel) { | |
| case JAZZ_SCSI_DMA: /* scsi */ | |
| r4030_write_reg32(JAZZ_R4030_CHNL_MODE + (channel << 5), | |
| /* R4030_MODE_FAST | */ | |
| /* R4030_MODE_BURST | */ | |
| R4030_MODE_INTR_EN | | |
| R4030_MODE_WIDTH_16 | | |
| R4030_MODE_ATIME_80); | |
| break; | |
| case JAZZ_FLOPPY_DMA: /* floppy */ | |
| r4030_write_reg32(JAZZ_R4030_CHNL_MODE + (channel << 5), | |
| /* R4030_MODE_FAST | */ | |
| /* R4030_MODE_BURST | */ | |
| R4030_MODE_INTR_EN | | |
| R4030_MODE_WIDTH_8 | | |
| R4030_MODE_ATIME_120); | |
| break; | |
| case JAZZ_AUDIOL_DMA: | |
| case JAZZ_AUDIOR_DMA: | |
| printk("VDMA: Audio DMA not supported yet.\n"); | |
| break; | |
| default: | |
| printk | |
| ("VDMA: vdma_set_mode() called with unsupported channel %d!\n", | |
| channel); | |
| } | |
| switch (mode) { | |
| case DMA_MODE_READ: | |
| r4030_write_reg32(JAZZ_R4030_CHNL_ENABLE + (channel << 5), | |
| r4030_read_reg32(JAZZ_R4030_CHNL_ENABLE + | |
| (channel << 5)) & | |
| ~R4030_CHNL_WRITE); | |
| break; | |
| case DMA_MODE_WRITE: | |
| r4030_write_reg32(JAZZ_R4030_CHNL_ENABLE + (channel << 5), | |
| r4030_read_reg32(JAZZ_R4030_CHNL_ENABLE + | |
| (channel << 5)) | | |
| R4030_CHNL_WRITE); | |
| break; | |
| default: | |
| printk | |
| ("VDMA: vdma_set_mode() called with unknown dma mode 0x%x\n", | |
| mode); | |
| } | |
| } | |
| EXPORT_SYMBOL(vdma_set_mode); | |
| /* | |
| * Set Transfer Address | |
| */ | |
| void vdma_set_addr(int channel, long addr) | |
| { | |
| if (vdma_debug) | |
| printk("vdma_set_addr: channel %d, addr %lx\n", channel, | |
| addr); | |
| r4030_write_reg32(JAZZ_R4030_CHNL_ADDR + (channel << 5), addr); | |
| } | |
| EXPORT_SYMBOL(vdma_set_addr); | |
| /* | |
| * Set Transfer Count | |
| */ | |
| void vdma_set_count(int channel, int count) | |
| { | |
| if (vdma_debug) | |
| printk("vdma_set_count: channel %d, count %08x\n", channel, | |
| (unsigned) count); | |
| r4030_write_reg32(JAZZ_R4030_CHNL_COUNT + (channel << 5), count); | |
| } | |
| EXPORT_SYMBOL(vdma_set_count); | |
| /* | |
| * Get Residual | |
| */ | |
| int vdma_get_residue(int channel) | |
| { | |
| int residual; | |
| residual = r4030_read_reg32(JAZZ_R4030_CHNL_COUNT + (channel << 5)); | |
| if (vdma_debug) | |
| printk("vdma_get_residual: channel %d: residual=%d\n", | |
| channel, residual); | |
| return residual; | |
| } | |
| /* | |
| * Get DMA channel enable register | |
| */ | |
| int vdma_get_enable(int channel) | |
| { | |
| int enable; | |
| enable = r4030_read_reg32(JAZZ_R4030_CHNL_ENABLE + (channel << 5)); | |
| if (vdma_debug) | |
| printk("vdma_get_enable: channel %d: enable=%d\n", channel, | |
| enable); | |
| return enable; | |
| } | |
| arch_initcall(vdma_init); |