| /* | |
| * arch/arm/mach-sa1100/dma.c | |
| * | |
| * Support functions for the SA11x0 internal DMA channels. | |
| * | |
| * Copyright (C) 2000, 2001 by Nicolas Pitre | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/init.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/errno.h> | |
| #include <asm/system.h> | |
| #include <asm/irq.h> | |
| #include <asm/hardware.h> | |
| #include <asm/dma.h> | |
| #undef DEBUG | |
| #ifdef DEBUG | |
| #define DPRINTK( s, arg... ) printk( "dma<%p>: " s, regs , ##arg ) | |
| #else | |
| #define DPRINTK( x... ) | |
| #endif | |
| typedef struct { | |
| const char *device_id; /* device name */ | |
| u_long device; /* this channel device, 0 if unused*/ | |
| dma_callback_t callback; /* to call when DMA completes */ | |
| void *data; /* ... with private data ptr */ | |
| } sa1100_dma_t; | |
| static sa1100_dma_t dma_chan[SA1100_DMA_CHANNELS]; | |
| static spinlock_t dma_list_lock; | |
| static irqreturn_t dma_irq_handler(int irq, void *dev_id) | |
| { | |
| dma_regs_t *dma_regs = dev_id; | |
| sa1100_dma_t *dma = dma_chan + (((u_int)dma_regs >> 5) & 7); | |
| int status = dma_regs->RdDCSR; | |
| if (status & (DCSR_ERROR)) { | |
| printk(KERN_CRIT "DMA on \"%s\" caused an error\n", dma->device_id); | |
| dma_regs->ClrDCSR = DCSR_ERROR; | |
| } | |
| dma_regs->ClrDCSR = status & (DCSR_DONEA | DCSR_DONEB); | |
| if (dma->callback) { | |
| if (status & DCSR_DONEA) | |
| dma->callback(dma->data); | |
| if (status & DCSR_DONEB) | |
| dma->callback(dma->data); | |
| } | |
| return IRQ_HANDLED; | |
| } | |
| /** | |
| * sa1100_request_dma - allocate one of the SA11x0's DMA chanels | |
| * @device: The SA11x0 peripheral targeted by this request | |
| * @device_id: An ascii name for the claiming device | |
| * @callback: Function to be called when the DMA completes | |
| * @data: A cookie passed back to the callback function | |
| * @dma_regs: Pointer to the location of the allocated channel's identifier | |
| * | |
| * This function will search for a free DMA channel and returns the | |
| * address of the hardware registers for that channel as the channel | |
| * identifier. This identifier is written to the location pointed by | |
| * @dma_regs. The list of possible values for @device are listed into | |
| * linux/include/asm-arm/arch-sa1100/dma.h as a dma_device_t enum. | |
| * | |
| * Note that reading from a port and writing to the same port are | |
| * actually considered as two different streams requiring separate | |
| * DMA registrations. | |
| * | |
| * The @callback function is called from interrupt context when one | |
| * of the two possible DMA buffers in flight has terminated. That | |
| * function has to be small and efficient while posponing more complex | |
| * processing to a lower priority execution context. | |
| * | |
| * If no channels are available, or if the desired @device is already in | |
| * use by another DMA channel, then an error code is returned. This | |
| * function must be called before any other DMA calls. | |
| **/ | |
| int sa1100_request_dma (dma_device_t device, const char *device_id, | |
| dma_callback_t callback, void *data, | |
| dma_regs_t **dma_regs) | |
| { | |
| sa1100_dma_t *dma = NULL; | |
| dma_regs_t *regs; | |
| int i, err; | |
| *dma_regs = NULL; | |
| err = 0; | |
| spin_lock(&dma_list_lock); | |
| for (i = 0; i < SA1100_DMA_CHANNELS; i++) { | |
| if (dma_chan[i].device == device) { | |
| err = -EBUSY; | |
| break; | |
| } else if (!dma_chan[i].device && !dma) { | |
| dma = &dma_chan[i]; | |
| } | |
| } | |
| if (!err) { | |
| if (dma) | |
| dma->device = device; | |
| else | |
| err = -ENOSR; | |
| } | |
| spin_unlock(&dma_list_lock); | |
| if (err) | |
| return err; | |
| i = dma - dma_chan; | |
| regs = (dma_regs_t *)&DDAR(i); | |
| err = request_irq(IRQ_DMA0 + i, dma_irq_handler, IRQF_DISABLED, | |
| device_id, regs); | |
| if (err) { | |
| printk(KERN_ERR | |
| "%s: unable to request IRQ %d for %s\n", | |
| __FUNCTION__, IRQ_DMA0 + i, device_id); | |
| dma->device = 0; | |
| return err; | |
| } | |
| *dma_regs = regs; | |
| dma->device_id = device_id; | |
| dma->callback = callback; | |
| dma->data = data; | |
| regs->ClrDCSR = | |
| (DCSR_DONEA | DCSR_DONEB | DCSR_STRTA | DCSR_STRTB | | |
| DCSR_IE | DCSR_ERROR | DCSR_RUN); | |
| regs->DDAR = device; | |
| return 0; | |
| } | |
| /** | |
| * sa1100_free_dma - free a SA11x0 DMA channel | |
| * @regs: identifier for the channel to free | |
| * | |
| * This clears all activities on a given DMA channel and releases it | |
| * for future requests. The @regs identifier is provided by a | |
| * successful call to sa1100_request_dma(). | |
| **/ | |
| void sa1100_free_dma(dma_regs_t *regs) | |
| { | |
| int i; | |
| for (i = 0; i < SA1100_DMA_CHANNELS; i++) | |
| if (regs == (dma_regs_t *)&DDAR(i)) | |
| break; | |
| if (i >= SA1100_DMA_CHANNELS) { | |
| printk(KERN_ERR "%s: bad DMA identifier\n", __FUNCTION__); | |
| return; | |
| } | |
| if (!dma_chan[i].device) { | |
| printk(KERN_ERR "%s: Trying to free free DMA\n", __FUNCTION__); | |
| return; | |
| } | |
| regs->ClrDCSR = | |
| (DCSR_DONEA | DCSR_DONEB | DCSR_STRTA | DCSR_STRTB | | |
| DCSR_IE | DCSR_ERROR | DCSR_RUN); | |
| free_irq(IRQ_DMA0 + i, regs); | |
| dma_chan[i].device = 0; | |
| } | |
| /** | |
| * sa1100_start_dma - submit a data buffer for DMA | |
| * @regs: identifier for the channel to use | |
| * @dma_ptr: buffer physical (or bus) start address | |
| * @size: buffer size | |
| * | |
| * This function hands the given data buffer to the hardware for DMA | |
| * access. If another buffer is already in flight then this buffer | |
| * will be queued so the DMA engine will switch to it automatically | |
| * when the previous one is done. The DMA engine is actually toggling | |
| * between two buffers so at most 2 successful calls can be made before | |
| * one of them terminates and the callback function is called. | |
| * | |
| * The @regs identifier is provided by a successful call to | |
| * sa1100_request_dma(). | |
| * | |
| * The @size must not be larger than %MAX_DMA_SIZE. If a given buffer | |
| * is larger than that then it's the caller's responsibility to split | |
| * it into smaller chunks and submit them separately. If this is the | |
| * case then a @size of %CUT_DMA_SIZE is recommended to avoid ending | |
| * up with too small chunks. The callback function can be used to chain | |
| * submissions of buffer chunks. | |
| * | |
| * Error return values: | |
| * %-EOVERFLOW: Given buffer size is too big. | |
| * %-EBUSY: Both DMA buffers are already in use. | |
| * %-EAGAIN: Both buffers were busy but one of them just completed | |
| * but the interrupt handler has to execute first. | |
| * | |
| * This function returs 0 on success. | |
| **/ | |
| int sa1100_start_dma(dma_regs_t *regs, dma_addr_t dma_ptr, u_int size) | |
| { | |
| unsigned long flags; | |
| u_long status; | |
| int ret; | |
| if (dma_ptr & 3) | |
| printk(KERN_WARNING "DMA: unaligned start address (0x%08lx)\n", | |
| (unsigned long)dma_ptr); | |
| if (size > MAX_DMA_SIZE) | |
| return -EOVERFLOW; | |
| local_irq_save(flags); | |
| status = regs->RdDCSR; | |
| /* If both DMA buffers are started, there's nothing else we can do. */ | |
| if ((status & (DCSR_STRTA | DCSR_STRTB)) == (DCSR_STRTA | DCSR_STRTB)) { | |
| DPRINTK("start: st %#x busy\n", status); | |
| ret = -EBUSY; | |
| goto out; | |
| } | |
| if (((status & DCSR_BIU) && (status & DCSR_STRTB)) || | |
| (!(status & DCSR_BIU) && !(status & DCSR_STRTA))) { | |
| if (status & DCSR_DONEA) { | |
| /* give a chance for the interrupt to be processed */ | |
| ret = -EAGAIN; | |
| goto out; | |
| } | |
| regs->DBSA = dma_ptr; | |
| regs->DBTA = size; | |
| regs->SetDCSR = DCSR_STRTA | DCSR_IE | DCSR_RUN; | |
| DPRINTK("start a=%#x s=%d on A\n", dma_ptr, size); | |
| } else { | |
| if (status & DCSR_DONEB) { | |
| /* give a chance for the interrupt to be processed */ | |
| ret = -EAGAIN; | |
| goto out; | |
| } | |
| regs->DBSB = dma_ptr; | |
| regs->DBTB = size; | |
| regs->SetDCSR = DCSR_STRTB | DCSR_IE | DCSR_RUN; | |
| DPRINTK("start a=%#x s=%d on B\n", dma_ptr, size); | |
| } | |
| ret = 0; | |
| out: | |
| local_irq_restore(flags); | |
| return ret; | |
| } | |
| /** | |
| * sa1100_get_dma_pos - return current DMA position | |
| * @regs: identifier for the channel to use | |
| * | |
| * This function returns the current physical (or bus) address for the | |
| * given DMA channel. If the channel is running i.e. not in a stopped | |
| * state then the caller must disable interrupts prior calling this | |
| * function and process the returned value before re-enabling them to | |
| * prevent races with the completion interrupt handler and the callback | |
| * function. The validation of the returned value is the caller's | |
| * responsibility as well -- the hardware seems to return out of range | |
| * values when the DMA engine completes a buffer. | |
| * | |
| * The @regs identifier is provided by a successful call to | |
| * sa1100_request_dma(). | |
| **/ | |
| dma_addr_t sa1100_get_dma_pos(dma_regs_t *regs) | |
| { | |
| int status; | |
| /* | |
| * We must determine whether buffer A or B is active. | |
| * Two possibilities: either we are in the middle of | |
| * a buffer, or the DMA controller just switched to the | |
| * next toggle but the interrupt hasn't been serviced yet. | |
| * The former case is straight forward. In the later case, | |
| * we'll do like if DMA is just at the end of the previous | |
| * toggle since all registers haven't been reset yet. | |
| * This goes around the edge case and since we're always | |
| * a little behind anyways it shouldn't make a big difference. | |
| * If DMA has been stopped prior calling this then the | |
| * position is exact. | |
| */ | |
| status = regs->RdDCSR; | |
| if ((!(status & DCSR_BIU) && (status & DCSR_STRTA)) || | |
| ( (status & DCSR_BIU) && !(status & DCSR_STRTB))) | |
| return regs->DBSA; | |
| else | |
| return regs->DBSB; | |
| } | |
| /** | |
| * sa1100_reset_dma - reset a DMA channel | |
| * @regs: identifier for the channel to use | |
| * | |
| * This function resets and reconfigure the given DMA channel. This is | |
| * particularly useful after a sleep/wakeup event. | |
| * | |
| * The @regs identifier is provided by a successful call to | |
| * sa1100_request_dma(). | |
| **/ | |
| void sa1100_reset_dma(dma_regs_t *regs) | |
| { | |
| int i; | |
| for (i = 0; i < SA1100_DMA_CHANNELS; i++) | |
| if (regs == (dma_regs_t *)&DDAR(i)) | |
| break; | |
| if (i >= SA1100_DMA_CHANNELS) { | |
| printk(KERN_ERR "%s: bad DMA identifier\n", __FUNCTION__); | |
| return; | |
| } | |
| regs->ClrDCSR = | |
| (DCSR_DONEA | DCSR_DONEB | DCSR_STRTA | DCSR_STRTB | | |
| DCSR_IE | DCSR_ERROR | DCSR_RUN); | |
| regs->DDAR = dma_chan[i].device; | |
| } | |
| EXPORT_SYMBOL(sa1100_request_dma); | |
| EXPORT_SYMBOL(sa1100_free_dma); | |
| EXPORT_SYMBOL(sa1100_start_dma); | |
| EXPORT_SYMBOL(sa1100_get_dma_pos); | |
| EXPORT_SYMBOL(sa1100_reset_dma); | |