/* * 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);