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