| /* |
| * arch/arm/kernel/dma-sa1100.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, struct pt_regs *regs) |
| { |
| 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, SA_INTERRUPT, |
| 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); |
| |