| /* |
| * 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/spinlock.h> |
| #include <linux/memory.h> |
| #include <linux/gfp.h> |
| #include <linux/dmapool.h> |
| #include <linux/dmaengine.h> |
| |
| #include "coh901318.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_transfer_direction dir) |
| { |
| int s = size; |
| dma_addr_t src; |
| dma_addr_t dst; |
| |
| |
| if (dir == DMA_MEM_TO_DEV) { |
| src = buf; |
| dst = dev_addr; |
| |
| } else if (dir == DMA_DEV_TO_MEM) { |
| |
| 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_MEM_TO_DEV) |
| src += block_size; |
| else if (dir == DMA_DEV_TO_MEM) |
| 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_transfer_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_MEM_TO_DEV) |
| dst = dev_addr; |
| else if (dir == DMA_DEV_TO_MEM) |
| 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_MEM_TO_DEV) |
| /* increment source address */ |
| src = sg_dma_address(sg); |
| else |
| /* increment destination address */ |
| dst = sg_dma_address(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_DEV_TO_MEM) |
| 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; |
| } |