Use this new function to make code more comprehensible, since we are reinitialzing the completion, not initializing. [akpm@linux-foundation.org: linux-next resyncs] Signed-off-by: Wolfram Sang <wsa@the-dreams.de> Acked-by: Linus Walleij <linus.walleij@linaro.org> (personally at LCE13) Cc: Ingo Molnar <mingo@kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
		
			
				
	
	
		
			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;
 | 
						|
 | 
						|
	reinit_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);
 | 
						|
}
 |