/* * driver/dma/coh901318_lli.c * * Copyright (C) 2007-2009 ST-Ericsson * License terms: GNU General Public License (GPL) version 2 * Support functions for handling lli for dma * Author: Per Friden <per.friden@stericsson.com> */ #include <linux/dma-mapping.h> #include <linux/spinlock.h> #include <linux/dmapool.h> #include <linux/memory.h> #include <linux/gfp.h> #include <mach/coh901318.h> #include "coh901318_lli.h" #if (defined(CONFIG_DEBUG_FS) && defined(CONFIG_U300_DEBUG)) #define DEBUGFS_POOL_COUNTER_RESET(pool) (pool->debugfs_pool_counter = 0) #define DEBUGFS_POOL_COUNTER_ADD(pool, add) (pool->debugfs_pool_counter += add) #else #define DEBUGFS_POOL_COUNTER_RESET(pool) #define DEBUGFS_POOL_COUNTER_ADD(pool, add) #endif static struct coh901318_lli * coh901318_lli_next(struct coh901318_lli *data) { if (data == NULL || data->link_addr == 0) return NULL; return (struct coh901318_lli *) data->virt_link_addr; } int coh901318_pool_create(struct coh901318_pool *pool, struct device *dev, size_t size, size_t align) { spin_lock_init(&pool->lock); pool->dev = dev; pool->dmapool = dma_pool_create("lli_pool", dev, size, align, 0); DEBUGFS_POOL_COUNTER_RESET(pool); return 0; } int coh901318_pool_destroy(struct coh901318_pool *pool) { dma_pool_destroy(pool->dmapool); return 0; } struct coh901318_lli * coh901318_lli_alloc(struct coh901318_pool *pool, unsigned int len) { int i; struct coh901318_lli *head; struct coh901318_lli *lli; struct coh901318_lli *lli_prev; dma_addr_t phy; if (len == 0) goto err; spin_lock(&pool->lock); head = dma_pool_alloc(pool->dmapool, GFP_NOWAIT, &phy); if (head == NULL) goto err; DEBUGFS_POOL_COUNTER_ADD(pool, 1); lli = head; lli->phy_this = phy; lli->link_addr = 0x00000000; lli->virt_link_addr = 0x00000000U; for (i = 1; i < len; i++) { lli_prev = lli; lli = dma_pool_alloc(pool->dmapool, GFP_NOWAIT, &phy); if (lli == NULL) goto err_clean_up; DEBUGFS_POOL_COUNTER_ADD(pool, 1); lli->phy_this = phy; lli->link_addr = 0x00000000; lli->virt_link_addr = 0x00000000U; lli_prev->link_addr = phy; lli_prev->virt_link_addr = lli; } spin_unlock(&pool->lock); return head; err: spin_unlock(&pool->lock); return NULL; err_clean_up: lli_prev->link_addr = 0x00000000U; spin_unlock(&pool->lock); coh901318_lli_free(pool, &head); return NULL; } void coh901318_lli_free(struct coh901318_pool *pool, struct coh901318_lli **lli) { struct coh901318_lli *l; struct coh901318_lli *next; if (lli == NULL) return; l = *lli; if (l == NULL) return; spin_lock(&pool->lock); while (l->link_addr) { next = l->virt_link_addr; dma_pool_free(pool->dmapool, l, l->phy_this); DEBUGFS_POOL_COUNTER_ADD(pool, -1); l = next; } dma_pool_free(pool->dmapool, l, l->phy_this); DEBUGFS_POOL_COUNTER_ADD(pool, -1); spin_unlock(&pool->lock); *lli = NULL; } int coh901318_lli_fill_memcpy(struct coh901318_pool *pool, struct coh901318_lli *lli, dma_addr_t source, unsigned int size, dma_addr_t destination, u32 ctrl_chained, u32 ctrl_eom) { int s = size; dma_addr_t src = source; dma_addr_t dst = destination; lli->src_addr = src; lli->dst_addr = dst; while (lli->link_addr) { lli->control = ctrl_chained | MAX_DMA_PACKET_SIZE; lli->src_addr = src; lli->dst_addr = dst; s -= MAX_DMA_PACKET_SIZE; lli = coh901318_lli_next(lli); src += MAX_DMA_PACKET_SIZE; dst += MAX_DMA_PACKET_SIZE; } lli->control = ctrl_eom | s; lli->src_addr = src; lli->dst_addr = dst; return 0; } int coh901318_lli_fill_single(struct coh901318_pool *pool, struct coh901318_lli *lli, dma_addr_t buf, unsigned int size, dma_addr_t dev_addr, u32 ctrl_chained, u32 ctrl_eom, enum dma_data_direction dir) { int s = size; dma_addr_t src; dma_addr_t dst; if (dir == DMA_TO_DEVICE) { src = buf; dst = dev_addr; } else if (dir == DMA_FROM_DEVICE) { src = dev_addr; dst = buf; } else { return -EINVAL; } while (lli->link_addr) { size_t block_size = MAX_DMA_PACKET_SIZE; lli->control = ctrl_chained | MAX_DMA_PACKET_SIZE; /* If we are on the next-to-final block and there will * be less than half a DMA packet left for the last * block, then we want to make this block a little * smaller to balance the sizes. This is meant to * avoid too small transfers if the buffer size is * (MAX_DMA_PACKET_SIZE*N + 1) */ if (s < (MAX_DMA_PACKET_SIZE + MAX_DMA_PACKET_SIZE/2)) block_size = MAX_DMA_PACKET_SIZE/2; s -= block_size; lli->src_addr = src; lli->dst_addr = dst; lli = coh901318_lli_next(lli); if (dir == DMA_TO_DEVICE) src += block_size; else if (dir == DMA_FROM_DEVICE) dst += block_size; } lli->control = ctrl_eom | s; lli->src_addr = src; lli->dst_addr = dst; return 0; } int coh901318_lli_fill_sg(struct coh901318_pool *pool, struct coh901318_lli *lli, struct scatterlist *sgl, unsigned int nents, dma_addr_t dev_addr, u32 ctrl_chained, u32 ctrl, u32 ctrl_last, enum dma_data_direction dir, u32 ctrl_irq_mask) { int i; struct scatterlist *sg; u32 ctrl_sg; dma_addr_t src = 0; dma_addr_t dst = 0; u32 bytes_to_transfer; u32 elem_size; if (lli == NULL) goto err; spin_lock(&pool->lock); if (dir == DMA_TO_DEVICE) dst = dev_addr; else if (dir == DMA_FROM_DEVICE) src = dev_addr; else goto err; for_each_sg(sgl, sg, nents, i) { if (sg_is_chain(sg)) { /* sg continues to the next sg-element don't * send ctrl_finish until the last * sg-element in the chain */ ctrl_sg = ctrl_chained; } else if (i == nents - 1) ctrl_sg = ctrl_last; else ctrl_sg = ctrl ? ctrl : ctrl_last; if (dir == DMA_TO_DEVICE) /* increment source address */ src = sg_phys(sg); else /* increment destination address */ dst = sg_phys(sg); bytes_to_transfer = sg_dma_len(sg); while (bytes_to_transfer) { u32 val; if (bytes_to_transfer > MAX_DMA_PACKET_SIZE) { elem_size = MAX_DMA_PACKET_SIZE; val = ctrl_chained; } else { elem_size = bytes_to_transfer; val = ctrl_sg; } lli->control = val | elem_size; lli->src_addr = src; lli->dst_addr = dst; if (dir == DMA_FROM_DEVICE) dst += elem_size; else src += elem_size; BUG_ON(lli->link_addr & 3); bytes_to_transfer -= elem_size; lli = coh901318_lli_next(lli); } } spin_unlock(&pool->lock); return 0; err: spin_unlock(&pool->lock); return -EINVAL; }