| 
									
										
										
										
											2009-11-19 19:49:17 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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 <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; | 
					
						
							| 
									
										
										
										
											2010-03-02 20:12:56 +01:00
										 |  |  | 	lli->link_addr = 0x00000000; | 
					
						
							|  |  |  | 	lli->virt_link_addr = 0x00000000U; | 
					
						
							| 
									
										
										
										
											2009-11-19 19:49:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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; | 
					
						
							| 
									
										
										
										
											2010-03-02 20:12:56 +01:00
										 |  |  | 		lli->link_addr = 0x00000000; | 
					
						
							|  |  |  | 		lli->virt_link_addr = 0x00000000U; | 
					
						
							| 
									
										
										
										
											2009-11-19 19:49:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-03-02 14:17:44 -07:00
										 |  |  | 	return 0; | 
					
						
							| 
									
										
										
										
											2009-11-19 19:49:17 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-03-02 14:17:44 -07:00
										 |  |  | 	return 0; | 
					
						
							| 
									
										
										
										
											2009-11-19 19:49:17 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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 */ | 
					
						
							| 
									
										
										
										
											2010-03-02 20:12:56 +01:00
										 |  |  | 			src = sg_phys(sg); | 
					
						
							| 
									
										
										
										
											2009-11-19 19:49:17 +01:00
										 |  |  | 		else | 
					
						
							|  |  |  | 			/* increment destination address */ | 
					
						
							| 
									
										
										
										
											2010-03-02 20:12:56 +01:00
										 |  |  | 			dst =  sg_phys(sg); | 
					
						
							| 
									
										
										
										
											2009-11-19 19:49:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		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); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-03-02 14:17:44 -07:00
										 |  |  | 	return 0; | 
					
						
							| 
									
										
										
										
											2009-11-19 19:49:17 +01:00
										 |  |  |  err: | 
					
						
							|  |  |  | 	spin_unlock(&pool->lock); | 
					
						
							|  |  |  | 	return -EINVAL; | 
					
						
							|  |  |  | } |