 deeb8d1948
			
		
	
	
	deeb8d1948
	
	
	
		
			
			No need to be public. Checked with: $ touch arch/arm/mach-tegra/*[ch] && make C=1 Signed-off-by: Hiroshi Doyu <hdoyu@nvidia.com> Signed-off-by: Stephen Warren <swarren@nvidia.com>
		
			
				
	
	
		
			206 lines
		
	
	
	
		
			5.1 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
	
		
			5.1 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2010 NVIDIA Corporation.
 | |
|  * Copyright (C) 2010 Google, Inc.
 | |
|  *
 | |
|  * This software is licensed under the terms of the GNU General Public
 | |
|  * License version 2, as published by the Free Software Foundation, and
 | |
|  * may be copied, distributed, and modified under those terms.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/io.h>
 | |
| #include <linux/of.h>
 | |
| #include <linux/dmaengine.h>
 | |
| #include <linux/dma-mapping.h>
 | |
| #include <linux/spinlock.h>
 | |
| #include <linux/completion.h>
 | |
| #include <linux/sched.h>
 | |
| #include <linux/mutex.h>
 | |
| 
 | |
| #include "apbio.h"
 | |
| #include "iomap.h"
 | |
| 
 | |
| #if defined(CONFIG_TEGRA20_APB_DMA)
 | |
| static DEFINE_MUTEX(tegra_apb_dma_lock);
 | |
| static u32 *tegra_apb_bb;
 | |
| static dma_addr_t tegra_apb_bb_phys;
 | |
| static DECLARE_COMPLETION(tegra_apb_wait);
 | |
| 
 | |
| static u32 tegra_apb_readl_direct(unsigned long offset);
 | |
| static void tegra_apb_writel_direct(u32 value, unsigned long offset);
 | |
| 
 | |
| static struct dma_chan *tegra_apb_dma_chan;
 | |
| static struct dma_slave_config dma_sconfig;
 | |
| 
 | |
| static bool tegra_apb_dma_init(void)
 | |
| {
 | |
| 	dma_cap_mask_t mask;
 | |
| 
 | |
| 	mutex_lock(&tegra_apb_dma_lock);
 | |
| 
 | |
| 	/* Check to see if we raced to setup */
 | |
| 	if (tegra_apb_dma_chan)
 | |
| 		goto skip_init;
 | |
| 
 | |
| 	dma_cap_zero(mask);
 | |
| 	dma_cap_set(DMA_SLAVE, mask);
 | |
| 	tegra_apb_dma_chan = dma_request_channel(mask, NULL, NULL);
 | |
| 	if (!tegra_apb_dma_chan) {
 | |
| 		/*
 | |
| 		 * This is common until the device is probed, so don't
 | |
| 		 * shout about it.
 | |
| 		 */
 | |
| 		pr_debug("%s: can not allocate dma channel\n", __func__);
 | |
| 		goto err_dma_alloc;
 | |
| 	}
 | |
| 
 | |
| 	tegra_apb_bb = dma_alloc_coherent(NULL, sizeof(u32),
 | |
| 		&tegra_apb_bb_phys, GFP_KERNEL);
 | |
| 	if (!tegra_apb_bb) {
 | |
| 		pr_err("%s: can not allocate bounce buffer\n", __func__);
 | |
| 		goto err_buff_alloc;
 | |
| 	}
 | |
| 
 | |
| 	dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 | |
| 	dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 | |
| 	dma_sconfig.src_maxburst = 1;
 | |
| 	dma_sconfig.dst_maxburst = 1;
 | |
| 
 | |
| skip_init:
 | |
| 	mutex_unlock(&tegra_apb_dma_lock);
 | |
| 	return true;
 | |
| 
 | |
| err_buff_alloc:
 | |
| 	dma_release_channel(tegra_apb_dma_chan);
 | |
| 	tegra_apb_dma_chan = NULL;
 | |
| 
 | |
| err_dma_alloc:
 | |
| 	mutex_unlock(&tegra_apb_dma_lock);
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| static void apb_dma_complete(void *args)
 | |
| {
 | |
| 	complete(&tegra_apb_wait);
 | |
| }
 | |
| 
 | |
| static int do_dma_transfer(unsigned long apb_add,
 | |
| 		enum dma_transfer_direction dir)
 | |
| {
 | |
| 	struct dma_async_tx_descriptor *dma_desc;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (dir == DMA_DEV_TO_MEM)
 | |
| 		dma_sconfig.src_addr = apb_add;
 | |
| 	else
 | |
| 		dma_sconfig.dst_addr = apb_add;
 | |
| 
 | |
| 	ret = dmaengine_slave_config(tegra_apb_dma_chan, &dma_sconfig);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	dma_desc = dmaengine_prep_slave_single(tegra_apb_dma_chan,
 | |
| 			tegra_apb_bb_phys, sizeof(u32), dir,
 | |
| 			DMA_PREP_INTERRUPT |  DMA_CTRL_ACK);
 | |
| 	if (!dma_desc)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	dma_desc->callback = apb_dma_complete;
 | |
| 	dma_desc->callback_param = NULL;
 | |
| 
 | |
| 	INIT_COMPLETION(tegra_apb_wait);
 | |
| 
 | |
| 	dmaengine_submit(dma_desc);
 | |
| 	dma_async_issue_pending(tegra_apb_dma_chan);
 | |
| 	ret = wait_for_completion_timeout(&tegra_apb_wait,
 | |
| 		msecs_to_jiffies(50));
 | |
| 
 | |
| 	if (WARN(ret == 0, "apb read dma timed out")) {
 | |
| 		dmaengine_terminate_all(tegra_apb_dma_chan);
 | |
| 		return -EFAULT;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static u32 tegra_apb_readl_using_dma(unsigned long offset)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	if (!tegra_apb_dma_chan && !tegra_apb_dma_init())
 | |
| 		return tegra_apb_readl_direct(offset);
 | |
| 
 | |
| 	mutex_lock(&tegra_apb_dma_lock);
 | |
| 	ret = do_dma_transfer(offset, DMA_DEV_TO_MEM);
 | |
| 	if (ret < 0) {
 | |
| 		pr_err("error in reading offset 0x%08lx using dma\n", offset);
 | |
| 		*(u32 *)tegra_apb_bb = 0;
 | |
| 	}
 | |
| 	mutex_unlock(&tegra_apb_dma_lock);
 | |
| 	return *((u32 *)tegra_apb_bb);
 | |
| }
 | |
| 
 | |
| static void tegra_apb_writel_using_dma(u32 value, unsigned long offset)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	if (!tegra_apb_dma_chan && !tegra_apb_dma_init()) {
 | |
| 		tegra_apb_writel_direct(value, offset);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	mutex_lock(&tegra_apb_dma_lock);
 | |
| 	*((u32 *)tegra_apb_bb) = value;
 | |
| 	ret = do_dma_transfer(offset, DMA_MEM_TO_DEV);
 | |
| 	if (ret < 0)
 | |
| 		pr_err("error in writing offset 0x%08lx using dma\n", offset);
 | |
| 	mutex_unlock(&tegra_apb_dma_lock);
 | |
| }
 | |
| #else
 | |
| #define tegra_apb_readl_using_dma tegra_apb_readl_direct
 | |
| #define tegra_apb_writel_using_dma tegra_apb_writel_direct
 | |
| #endif
 | |
| 
 | |
| typedef u32 (*apbio_read_fptr)(unsigned long offset);
 | |
| typedef void (*apbio_write_fptr)(u32 value, unsigned long offset);
 | |
| 
 | |
| static apbio_read_fptr apbio_read;
 | |
| static apbio_write_fptr apbio_write;
 | |
| 
 | |
| static u32 tegra_apb_readl_direct(unsigned long offset)
 | |
| {
 | |
| 	return readl(IO_ADDRESS(offset));
 | |
| }
 | |
| 
 | |
| static void tegra_apb_writel_direct(u32 value, unsigned long offset)
 | |
| {
 | |
| 	writel(value, IO_ADDRESS(offset));
 | |
| }
 | |
| 
 | |
| void tegra_apb_io_init(void)
 | |
| {
 | |
| 	/* Need to use dma only when it is Tegra20 based platform */
 | |
| 	if (of_machine_is_compatible("nvidia,tegra20") ||
 | |
| 			!of_have_populated_dt()) {
 | |
| 		apbio_read = tegra_apb_readl_using_dma;
 | |
| 		apbio_write = tegra_apb_writel_using_dma;
 | |
| 	} else {
 | |
| 		apbio_read = tegra_apb_readl_direct;
 | |
| 		apbio_write = tegra_apb_writel_direct;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| u32 tegra_apb_readl(unsigned long offset)
 | |
| {
 | |
| 	return apbio_read(offset);
 | |
| }
 | |
| 
 | |
| void tegra_apb_writel(u32 value, unsigned long offset)
 | |
| {
 | |
| 	apbio_write(value, offset);
 | |
| }
 |