| /* |
| * bfin_dma_5xx.c - Blackfin DMA implementation |
| * |
| * Copyright 2004-2008 Analog Devices Inc. |
| * Licensed under the GPL-2 or later. |
| */ |
| |
| #include <linux/errno.h> |
| #include <linux/interrupt.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/param.h> |
| #include <linux/proc_fs.h> |
| #include <linux/sched.h> |
| #include <linux/seq_file.h> |
| #include <linux/spinlock.h> |
| |
| #include <asm/blackfin.h> |
| #include <asm/cacheflush.h> |
| #include <asm/dma.h> |
| #include <asm/uaccess.h> |
| |
| struct dma_channel dma_ch[MAX_DMA_CHANNELS]; |
| EXPORT_SYMBOL(dma_ch); |
| |
| static int __init blackfin_dma_init(void) |
| { |
| int i; |
| |
| printk(KERN_INFO "Blackfin DMA Controller\n"); |
| |
| for (i = 0; i < MAX_DMA_CHANNELS; i++) { |
| dma_ch[i].chan_status = DMA_CHANNEL_FREE; |
| dma_ch[i].regs = dma_io_base_addr[i]; |
| mutex_init(&(dma_ch[i].dmalock)); |
| } |
| /* Mark MEMDMA Channel 0 as requested since we're using it internally */ |
| request_dma(CH_MEM_STREAM0_DEST, "Blackfin dma_memcpy"); |
| request_dma(CH_MEM_STREAM0_SRC, "Blackfin dma_memcpy"); |
| |
| #if defined(CONFIG_DEB_DMA_URGENT) |
| bfin_write_EBIU_DDRQUE(bfin_read_EBIU_DDRQUE() |
| | DEB1_URGENT | DEB2_URGENT | DEB3_URGENT); |
| #endif |
| |
| return 0; |
| } |
| arch_initcall(blackfin_dma_init); |
| |
| #ifdef CONFIG_PROC_FS |
| static int proc_dma_show(struct seq_file *m, void *v) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_DMA_CHANNELS; ++i) |
| if (dma_ch[i].chan_status != DMA_CHANNEL_FREE) |
| seq_printf(m, "%2d: %s\n", i, dma_ch[i].device_id); |
| |
| return 0; |
| } |
| |
| static int proc_dma_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, proc_dma_show, NULL); |
| } |
| |
| static const struct file_operations proc_dma_operations = { |
| .open = proc_dma_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static int __init proc_dma_init(void) |
| { |
| return proc_create("dma", 0, NULL, &proc_dma_operations) != NULL; |
| } |
| late_initcall(proc_dma_init); |
| #endif |
| |
| /** |
| * request_dma - request a DMA channel |
| * |
| * Request the specific DMA channel from the system if it's available. |
| */ |
| int request_dma(unsigned int channel, const char *device_id) |
| { |
| pr_debug("request_dma() : BEGIN \n"); |
| |
| if (device_id == NULL) |
| printk(KERN_WARNING "request_dma(%u): no device_id given\n", channel); |
| |
| #if defined(CONFIG_BF561) && ANOMALY_05000182 |
| if (channel >= CH_IMEM_STREAM0_DEST && channel <= CH_IMEM_STREAM1_DEST) { |
| if (get_cclk() > 500000000) { |
| printk(KERN_WARNING |
| "Request IMDMA failed due to ANOMALY 05000182\n"); |
| return -EFAULT; |
| } |
| } |
| #endif |
| |
| mutex_lock(&(dma_ch[channel].dmalock)); |
| |
| if ((dma_ch[channel].chan_status == DMA_CHANNEL_REQUESTED) |
| || (dma_ch[channel].chan_status == DMA_CHANNEL_ENABLED)) { |
| mutex_unlock(&(dma_ch[channel].dmalock)); |
| pr_debug("DMA CHANNEL IN USE \n"); |
| return -EBUSY; |
| } else { |
| dma_ch[channel].chan_status = DMA_CHANNEL_REQUESTED; |
| pr_debug("DMA CHANNEL IS ALLOCATED \n"); |
| } |
| |
| mutex_unlock(&(dma_ch[channel].dmalock)); |
| |
| #ifdef CONFIG_BF54x |
| if (channel >= CH_UART2_RX && channel <= CH_UART3_TX) { |
| unsigned int per_map; |
| per_map = dma_ch[channel].regs->peripheral_map & 0xFFF; |
| if (strncmp(device_id, "BFIN_UART", 9) == 0) |
| dma_ch[channel].regs->peripheral_map = per_map | |
| ((channel - CH_UART2_RX + 0xC)<<12); |
| else |
| dma_ch[channel].regs->peripheral_map = per_map | |
| ((channel - CH_UART2_RX + 0x6)<<12); |
| } |
| #endif |
| |
| dma_ch[channel].device_id = device_id; |
| dma_ch[channel].irq = 0; |
| |
| /* This is to be enabled by putting a restriction - |
| * you have to request DMA, before doing any operations on |
| * descriptor/channel |
| */ |
| pr_debug("request_dma() : END \n"); |
| return 0; |
| } |
| EXPORT_SYMBOL(request_dma); |
| |
| int set_dma_callback(unsigned int channel, irq_handler_t callback, void *data) |
| { |
| BUG_ON(!(dma_ch[channel].chan_status != DMA_CHANNEL_FREE |
| && channel < MAX_DMA_CHANNELS)); |
| |
| if (callback != NULL) { |
| int ret; |
| unsigned int irq = channel2irq(channel); |
| |
| ret = request_irq(irq, callback, IRQF_DISABLED, |
| dma_ch[channel].device_id, data); |
| if (ret) |
| return ret; |
| |
| dma_ch[channel].irq = irq; |
| dma_ch[channel].data = data; |
| } |
| return 0; |
| } |
| EXPORT_SYMBOL(set_dma_callback); |
| |
| /** |
| * clear_dma_buffer - clear DMA fifos for specified channel |
| * |
| * Set the Buffer Clear bit in the Configuration register of specific DMA |
| * channel. This will stop the descriptor based DMA operation. |
| */ |
| static void clear_dma_buffer(unsigned int channel) |
| { |
| dma_ch[channel].regs->cfg |= RESTART; |
| SSYNC(); |
| dma_ch[channel].regs->cfg &= ~RESTART; |
| } |
| |
| void free_dma(unsigned int channel) |
| { |
| pr_debug("freedma() : BEGIN \n"); |
| BUG_ON(!(dma_ch[channel].chan_status != DMA_CHANNEL_FREE |
| && channel < MAX_DMA_CHANNELS)); |
| |
| /* Halt the DMA */ |
| disable_dma(channel); |
| clear_dma_buffer(channel); |
| |
| if (dma_ch[channel].irq) |
| free_irq(dma_ch[channel].irq, dma_ch[channel].data); |
| |
| /* Clear the DMA Variable in the Channel */ |
| mutex_lock(&(dma_ch[channel].dmalock)); |
| dma_ch[channel].chan_status = DMA_CHANNEL_FREE; |
| mutex_unlock(&(dma_ch[channel].dmalock)); |
| |
| pr_debug("freedma() : END \n"); |
| } |
| EXPORT_SYMBOL(free_dma); |
| |
| #ifdef CONFIG_PM |
| # ifndef MAX_DMA_SUSPEND_CHANNELS |
| # define MAX_DMA_SUSPEND_CHANNELS MAX_DMA_CHANNELS |
| # endif |
| int blackfin_dma_suspend(void) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_DMA_SUSPEND_CHANNELS; ++i) { |
| if (dma_ch[i].chan_status == DMA_CHANNEL_ENABLED) { |
| printk(KERN_ERR "DMA Channel %d failed to suspend\n", i); |
| return -EBUSY; |
| } |
| |
| dma_ch[i].saved_peripheral_map = dma_ch[i].regs->peripheral_map; |
| } |
| |
| return 0; |
| } |
| |
| void blackfin_dma_resume(void) |
| { |
| int i; |
| for (i = 0; i < MAX_DMA_SUSPEND_CHANNELS; ++i) |
| dma_ch[i].regs->peripheral_map = dma_ch[i].saved_peripheral_map; |
| } |
| #endif |
| |
| /** |
| * blackfin_dma_early_init - minimal DMA init |
| * |
| * Setup a few DMA registers so we can safely do DMA transfers early on in |
| * the kernel booting process. Really this just means using dma_memcpy(). |
| */ |
| void __init blackfin_dma_early_init(void) |
| { |
| bfin_write_MDMA_S0_CONFIG(0); |
| } |
| |
| /** |
| * __dma_memcpy - program the MDMA registers |
| * |
| * Actually program MDMA0 and wait for the transfer to finish. Disable IRQs |
| * while programming registers so that everything is fully configured. Wait |
| * for DMA to finish with IRQs enabled. If interrupted, the initial DMA_DONE |
| * check will make sure we don't clobber any existing transfer. |
| */ |
| static void __dma_memcpy(u32 daddr, s16 dmod, u32 saddr, s16 smod, size_t cnt, u32 conf) |
| { |
| static DEFINE_SPINLOCK(mdma_lock); |
| unsigned long flags; |
| |
| spin_lock_irqsave(&mdma_lock, flags); |
| |
| /* Force a sync in case a previous config reset on this channel |
| * occurred. This is needed so subsequent writes to DMA registers |
| * are not spuriously lost/corrupted. Do it under irq lock and |
| * without the anomaly version (because we are atomic already). |
| */ |
| __builtin_bfin_ssync(); |
| |
| if (bfin_read_MDMA_S0_CONFIG()) |
| while (!(bfin_read_MDMA_D0_IRQ_STATUS() & DMA_DONE)) |
| continue; |
| |
| if (conf & DMA2D) { |
| /* For larger bit sizes, we've already divided down cnt so it |
| * is no longer a multiple of 64k. So we have to break down |
| * the limit here so it is a multiple of the incoming size. |
| * There is no limitation here in terms of total size other |
| * than the hardware though as the bits lost in the shift are |
| * made up by MODIFY (== we can hit the whole address space). |
| * X: (2^(16 - 0)) * 1 == (2^(16 - 1)) * 2 == (2^(16 - 2)) * 4 |
| */ |
| u32 shift = abs(dmod) >> 1; |
| size_t ycnt = cnt >> (16 - shift); |
| cnt = 1 << (16 - shift); |
| bfin_write_MDMA_D0_Y_COUNT(ycnt); |
| bfin_write_MDMA_S0_Y_COUNT(ycnt); |
| bfin_write_MDMA_D0_Y_MODIFY(dmod); |
| bfin_write_MDMA_S0_Y_MODIFY(smod); |
| } |
| |
| bfin_write_MDMA_D0_START_ADDR(daddr); |
| bfin_write_MDMA_D0_X_COUNT(cnt); |
| bfin_write_MDMA_D0_X_MODIFY(dmod); |
| bfin_write_MDMA_D0_IRQ_STATUS(DMA_DONE | DMA_ERR); |
| |
| bfin_write_MDMA_S0_START_ADDR(saddr); |
| bfin_write_MDMA_S0_X_COUNT(cnt); |
| bfin_write_MDMA_S0_X_MODIFY(smod); |
| bfin_write_MDMA_S0_IRQ_STATUS(DMA_DONE | DMA_ERR); |
| |
| bfin_write_MDMA_S0_CONFIG(DMAEN | conf); |
| bfin_write_MDMA_D0_CONFIG(WNR | DI_EN | DMAEN | conf); |
| |
| spin_unlock_irqrestore(&mdma_lock, flags); |
| |
| SSYNC(); |
| |
| while (!(bfin_read_MDMA_D0_IRQ_STATUS() & DMA_DONE)) |
| if (bfin_read_MDMA_S0_CONFIG()) |
| continue; |
| else |
| return; |
| |
| bfin_write_MDMA_D0_IRQ_STATUS(DMA_DONE | DMA_ERR); |
| |
| bfin_write_MDMA_S0_CONFIG(0); |
| bfin_write_MDMA_D0_CONFIG(0); |
| } |
| |
| /** |
| * _dma_memcpy - translate C memcpy settings into MDMA settings |
| * |
| * Handle all the high level steps before we touch the MDMA registers. So |
| * handle direction, tweaking of sizes, and formatting of addresses. |
| */ |
| static void *_dma_memcpy(void *pdst, const void *psrc, size_t size) |
| { |
| u32 conf, shift; |
| s16 mod; |
| unsigned long dst = (unsigned long)pdst; |
| unsigned long src = (unsigned long)psrc; |
| |
| if (size == 0) |
| return NULL; |
| |
| if (dst % 4 == 0 && src % 4 == 0 && size % 4 == 0) { |
| conf = WDSIZE_32; |
| shift = 2; |
| } else if (dst % 2 == 0 && src % 2 == 0 && size % 2 == 0) { |
| conf = WDSIZE_16; |
| shift = 1; |
| } else { |
| conf = WDSIZE_8; |
| shift = 0; |
| } |
| |
| /* If the two memory regions have a chance of overlapping, make |
| * sure the memcpy still works as expected. Do this by having the |
| * copy run backwards instead. |
| */ |
| mod = 1 << shift; |
| if (src < dst) { |
| mod *= -1; |
| dst += size + mod; |
| src += size + mod; |
| } |
| size >>= shift; |
| |
| if (size > 0x10000) |
| conf |= DMA2D; |
| |
| __dma_memcpy(dst, mod, src, mod, size, conf); |
| |
| return pdst; |
| } |
| |
| /** |
| * dma_memcpy - DMA memcpy under mutex lock |
| * |
| * Do not check arguments before starting the DMA memcpy. Break the transfer |
| * up into two pieces. The first transfer is in multiples of 64k and the |
| * second transfer is the piece smaller than 64k. |
| */ |
| void *dma_memcpy(void *pdst, const void *psrc, size_t size) |
| { |
| unsigned long dst = (unsigned long)pdst; |
| unsigned long src = (unsigned long)psrc; |
| size_t bulk, rest; |
| |
| if (bfin_addr_dcachable(src)) |
| blackfin_dcache_flush_range(src, src + size); |
| |
| if (bfin_addr_dcachable(dst)) |
| blackfin_dcache_invalidate_range(dst, dst + size); |
| |
| bulk = size & ~0xffff; |
| rest = size - bulk; |
| if (bulk) |
| _dma_memcpy(pdst, psrc, bulk); |
| _dma_memcpy(pdst + bulk, psrc + bulk, rest); |
| return pdst; |
| } |
| EXPORT_SYMBOL(dma_memcpy); |
| |
| /** |
| * safe_dma_memcpy - DMA memcpy w/argument checking |
| * |
| * Verify arguments are safe before heading to dma_memcpy(). |
| */ |
| void *safe_dma_memcpy(void *dst, const void *src, size_t size) |
| { |
| if (!access_ok(VERIFY_WRITE, dst, size)) |
| return NULL; |
| if (!access_ok(VERIFY_READ, src, size)) |
| return NULL; |
| return dma_memcpy(dst, src, size); |
| } |
| EXPORT_SYMBOL(safe_dma_memcpy); |
| |
| static void _dma_out(unsigned long addr, unsigned long buf, unsigned short len, |
| u16 size, u16 dma_size) |
| { |
| blackfin_dcache_flush_range(buf, buf + len * size); |
| __dma_memcpy(addr, 0, buf, size, len, dma_size); |
| } |
| |
| static void _dma_in(unsigned long addr, unsigned long buf, unsigned short len, |
| u16 size, u16 dma_size) |
| { |
| blackfin_dcache_invalidate_range(buf, buf + len * size); |
| __dma_memcpy(buf, size, addr, 0, len, dma_size); |
| } |
| |
| #define MAKE_DMA_IO(io, bwl, isize, dmasize, cnst) \ |
| void dma_##io##s##bwl(unsigned long addr, cnst void *buf, unsigned short len) \ |
| { \ |
| _dma_##io(addr, (unsigned long)buf, len, isize, WDSIZE_##dmasize); \ |
| } \ |
| EXPORT_SYMBOL(dma_##io##s##bwl) |
| MAKE_DMA_IO(out, b, 1, 8, const); |
| MAKE_DMA_IO(in, b, 1, 8, ); |
| MAKE_DMA_IO(out, w, 2, 16, const); |
| MAKE_DMA_IO(in, w, 2, 16, ); |
| MAKE_DMA_IO(out, l, 4, 32, const); |
| MAKE_DMA_IO(in, l, 4, 32, ); |