All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH V1] ARM: tegra: Pause DMA when reading transfer count
@ 2012-01-10  6:05 ` Laxman Dewangan
  0 siblings, 0 replies; 8+ messages in thread
From: Laxman Dewangan @ 2012-01-10  6:05 UTC (permalink / raw)
  To: ccross-z5hGa2qSFaRBDgjK7y7TUQ, olof-nZhT3qVonbNeoWH0uzbU5w,
	swarren-DDmLM1+adcrQT0dZR+AlfA, linux-lFZ/pmaqli7XmaaqVzeoHQ
  Cc: linux-tegra-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	ldewangan-DDmLM1+adcrQT0dZR+AlfA

From: Laxman Dewangan <ldewangan-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>

In order to read an accurate channel transfer count
from the APB DMA engine, the DMA controller must be
paused first.

Signed-off-by: Laxman Dewangan <ldewangan-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>

---
 arch/arm/mach-tegra/dma.c |  116 ++++++++++++++++++++++++++++----------------
 1 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/arch/arm/mach-tegra/dma.c b/arch/arm/mach-tegra/dma.c
index c0cf967..c3529cf 100644
--- a/arch/arm/mach-tegra/dma.c
+++ b/arch/arm/mach-tegra/dma.c
@@ -133,6 +133,7 @@ struct tegra_dma_channel {
 
 static bool tegra_dma_initialized;
 static DEFINE_MUTEX(tegra_dma_lock);
+static DEFINE_SPINLOCK(enable_lock);
 
 static DECLARE_BITMAP(channel_usage, NV_DMA_MAX_CHANNELS);
 static struct tegra_dma_channel dma_channels[NV_DMA_MAX_CHANNELS];
@@ -198,18 +199,82 @@ static int tegra_dma_cancel(struct tegra_dma_channel *ch)
 	return 0;
 }
 
+static unsigned int get_channel_status(struct tegra_dma_channel *ch,
+			struct tegra_dma_req *req, bool is_stop_dma)
+{
+	void __iomem *addr = IO_ADDRESS(TEGRA_APB_DMA_BASE);
+	unsigned int status;
+
+	if (is_stop_dma) {
+		/*
+		 * STOP the DMA and get the transfer count.
+		 * Getting the transfer count is tricky.
+		 *  - Globally disable DMA on all channels
+		 *  - Read the channel's status register to know the number
+		 *    of pending bytes to be transfered.
+		 *  - Stop the dma channel
+		 *  - Globally re-enable DMA to resume other transfers
+		 */
+		spin_lock(&enable_lock);
+		writel(0, addr + APB_DMA_GEN);
+		udelay(20);
+		status = readl(ch->addr + APB_DMA_CHAN_STA);
+		tegra_dma_stop(ch);
+		writel(GEN_ENABLE, addr + APB_DMA_GEN);
+		spin_unlock(&enable_lock);
+		if (status & STA_ISE_EOC) {
+			pr_err("Got Dma Int here clearing");
+			writel(status, ch->addr + APB_DMA_CHAN_STA);
+		}
+		req->status = TEGRA_DMA_REQ_ERROR_ABORTED;
+	} else {
+		status = readl(ch->addr + APB_DMA_CHAN_STA);
+	}
+	return status;
+}
+
+/* should be called with the channel lock held */
+static unsigned int dma_active_count(struct tegra_dma_channel *ch,
+	struct tegra_dma_req *req, unsigned int status)
+{
+	unsigned int to_transfer;
+	unsigned int req_transfer_count;
+	unsigned int bytes_transferred;
+
+	to_transfer = ((status & STA_COUNT_MASK) >> STA_COUNT_SHIFT) + 1;
+	req_transfer_count = ch->req_transfer_count + 1;
+	bytes_transferred = req_transfer_count;
+	if (status & STA_BUSY)
+		bytes_transferred -= to_transfer;
+	/*
+	 * In continuous transfer mode, DMA only tracks the count of the
+	 * half DMA buffer. So, if the DMA already finished half the DMA
+	 * then add the half buffer to the completed count.
+	 */
+	if (ch->mode & TEGRA_DMA_MODE_CONTINOUS) {
+		if (req->buffer_status == TEGRA_DMA_REQ_BUF_STATUS_HALF_FULL)
+			bytes_transferred += req_transfer_count;
+		if (status & STA_ISE_EOC)
+			bytes_transferred += req_transfer_count;
+	}
+	bytes_transferred *= 4;
+	return bytes_transferred;
+}
+
 int tegra_dma_dequeue_req(struct tegra_dma_channel *ch,
 	struct tegra_dma_req *_req)
 {
-	unsigned int csr;
 	unsigned int status;
 	struct tegra_dma_req *req = NULL;
 	int found = 0;
 	unsigned long irq_flags;
-	int to_transfer;
-	int req_transfer_count;
+	int stop = 0;
 
 	spin_lock_irqsave(&ch->lock, irq_flags);
+
+	if (list_entry(ch->list.next, struct tegra_dma_req, node) == _req)
+		stop = 1;
+
 	list_for_each_entry(req, &ch->list, node) {
 		if (req == _req) {
 			list_del(&req->node);
@@ -222,47 +287,12 @@ int tegra_dma_dequeue_req(struct tegra_dma_channel *ch,
 		return 0;
 	}
 
-	/* STOP the DMA and get the transfer count.
-	 * Getting the transfer count is tricky.
-	 *  - Change the source selector to invalid to stop the DMA from
-	 *    FIFO to memory.
-	 *  - Read the status register to know the number of pending
-	 *    bytes to be transferred.
-	 *  - Finally stop or program the DMA to the next buffer in the
-	 *    list.
-	 */
-	csr = readl(ch->addr + APB_DMA_CHAN_CSR);
-	csr &= ~CSR_REQ_SEL_MASK;
-	csr |= CSR_REQ_SEL_INVALID;
-	writel(csr, ch->addr + APB_DMA_CHAN_CSR);
-
-	/* Get the transfer count */
-	status = readl(ch->addr + APB_DMA_CHAN_STA);
-	to_transfer = (status & STA_COUNT_MASK) >> STA_COUNT_SHIFT;
-	req_transfer_count = ch->req_transfer_count;
-	req_transfer_count += 1;
-	to_transfer += 1;
+	if (!stop)
+		goto skip_stop_dma;
 
-	req->bytes_transferred = req_transfer_count;
+	status = get_channel_status(ch, req, true);
+	req->bytes_transferred = dma_active_count(ch, req, status);
 
-	if (status & STA_BUSY)
-		req->bytes_transferred -= to_transfer;
-
-	/* In continuous transfer mode, DMA only tracks the count of the
-	 * half DMA buffer. So, if the DMA already finished half the DMA
-	 * then add the half buffer to the completed count.
-	 *
-	 *	FIXME: There can be a race here. What if the req to
-	 *	dequue happens at the same time as the DMA just moved to
-	 *	the new buffer and SW didn't yet received the interrupt?
-	 */
-	if (ch->mode & TEGRA_DMA_MODE_CONTINOUS)
-		if (req->buffer_status == TEGRA_DMA_REQ_BUF_STATUS_HALF_FULL)
-			req->bytes_transferred += req_transfer_count;
-
-	req->bytes_transferred *= 4;
-
-	tegra_dma_stop(ch);
 	if (!list_empty(&ch->list)) {
 		/* if the list is not empty, queue the next request */
 		struct tegra_dma_req *next_req;
@@ -270,6 +300,8 @@ int tegra_dma_dequeue_req(struct tegra_dma_channel *ch,
 			typeof(*next_req), node);
 		tegra_dma_update_hw(ch, next_req);
 	}
+
+skip_stop_dma:
 	req->status = -TEGRA_DMA_REQ_ERROR_ABORTED;
 
 	spin_unlock_irqrestore(&ch->lock, irq_flags);
-- 
1.7.1.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH V1] ARM: tegra: Pause DMA when reading transfer count
@ 2012-01-10  6:05 ` Laxman Dewangan
  0 siblings, 0 replies; 8+ messages in thread
From: Laxman Dewangan @ 2012-01-10  6:05 UTC (permalink / raw)
  To: ccross, olof, swarren, linux
  Cc: linux-tegra, linux-arm-kernel, linux-kernel, ldewangan

From: Laxman Dewangan <ldewangan@nvidia.com>

In order to read an accurate channel transfer count
from the APB DMA engine, the DMA controller must be
paused first.

Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>

---
 arch/arm/mach-tegra/dma.c |  116 ++++++++++++++++++++++++++++----------------
 1 files changed, 74 insertions(+), 42 deletions(-)

diff --git a/arch/arm/mach-tegra/dma.c b/arch/arm/mach-tegra/dma.c
index c0cf967..c3529cf 100644
--- a/arch/arm/mach-tegra/dma.c
+++ b/arch/arm/mach-tegra/dma.c
@@ -133,6 +133,7 @@ struct tegra_dma_channel {
 
 static bool tegra_dma_initialized;
 static DEFINE_MUTEX(tegra_dma_lock);
+static DEFINE_SPINLOCK(enable_lock);
 
 static DECLARE_BITMAP(channel_usage, NV_DMA_MAX_CHANNELS);
 static struct tegra_dma_channel dma_channels[NV_DMA_MAX_CHANNELS];
@@ -198,18 +199,82 @@ static int tegra_dma_cancel(struct tegra_dma_channel *ch)
 	return 0;
 }
 
+static unsigned int get_channel_status(struct tegra_dma_channel *ch,
+			struct tegra_dma_req *req, bool is_stop_dma)
+{
+	void __iomem *addr = IO_ADDRESS(TEGRA_APB_DMA_BASE);
+	unsigned int status;
+
+	if (is_stop_dma) {
+		/*
+		 * STOP the DMA and get the transfer count.
+		 * Getting the transfer count is tricky.
+		 *  - Globally disable DMA on all channels
+		 *  - Read the channel's status register to know the number
+		 *    of pending bytes to be transfered.
+		 *  - Stop the dma channel
+		 *  - Globally re-enable DMA to resume other transfers
+		 */
+		spin_lock(&enable_lock);
+		writel(0, addr + APB_DMA_GEN);
+		udelay(20);
+		status = readl(ch->addr + APB_DMA_CHAN_STA);
+		tegra_dma_stop(ch);
+		writel(GEN_ENABLE, addr + APB_DMA_GEN);
+		spin_unlock(&enable_lock);
+		if (status & STA_ISE_EOC) {
+			pr_err("Got Dma Int here clearing");
+			writel(status, ch->addr + APB_DMA_CHAN_STA);
+		}
+		req->status = TEGRA_DMA_REQ_ERROR_ABORTED;
+	} else {
+		status = readl(ch->addr + APB_DMA_CHAN_STA);
+	}
+	return status;
+}
+
+/* should be called with the channel lock held */
+static unsigned int dma_active_count(struct tegra_dma_channel *ch,
+	struct tegra_dma_req *req, unsigned int status)
+{
+	unsigned int to_transfer;
+	unsigned int req_transfer_count;
+	unsigned int bytes_transferred;
+
+	to_transfer = ((status & STA_COUNT_MASK) >> STA_COUNT_SHIFT) + 1;
+	req_transfer_count = ch->req_transfer_count + 1;
+	bytes_transferred = req_transfer_count;
+	if (status & STA_BUSY)
+		bytes_transferred -= to_transfer;
+	/*
+	 * In continuous transfer mode, DMA only tracks the count of the
+	 * half DMA buffer. So, if the DMA already finished half the DMA
+	 * then add the half buffer to the completed count.
+	 */
+	if (ch->mode & TEGRA_DMA_MODE_CONTINOUS) {
+		if (req->buffer_status == TEGRA_DMA_REQ_BUF_STATUS_HALF_FULL)
+			bytes_transferred += req_transfer_count;
+		if (status & STA_ISE_EOC)
+			bytes_transferred += req_transfer_count;
+	}
+	bytes_transferred *= 4;
+	return bytes_transferred;
+}
+
 int tegra_dma_dequeue_req(struct tegra_dma_channel *ch,
 	struct tegra_dma_req *_req)
 {
-	unsigned int csr;
 	unsigned int status;
 	struct tegra_dma_req *req = NULL;
 	int found = 0;
 	unsigned long irq_flags;
-	int to_transfer;
-	int req_transfer_count;
+	int stop = 0;
 
 	spin_lock_irqsave(&ch->lock, irq_flags);
+
+	if (list_entry(ch->list.next, struct tegra_dma_req, node) == _req)
+		stop = 1;
+
 	list_for_each_entry(req, &ch->list, node) {
 		if (req == _req) {
 			list_del(&req->node);
@@ -222,47 +287,12 @@ int tegra_dma_dequeue_req(struct tegra_dma_channel *ch,
 		return 0;
 	}
 
-	/* STOP the DMA and get the transfer count.
-	 * Getting the transfer count is tricky.
-	 *  - Change the source selector to invalid to stop the DMA from
-	 *    FIFO to memory.
-	 *  - Read the status register to know the number of pending
-	 *    bytes to be transferred.
-	 *  - Finally stop or program the DMA to the next buffer in the
-	 *    list.
-	 */
-	csr = readl(ch->addr + APB_DMA_CHAN_CSR);
-	csr &= ~CSR_REQ_SEL_MASK;
-	csr |= CSR_REQ_SEL_INVALID;
-	writel(csr, ch->addr + APB_DMA_CHAN_CSR);
-
-	/* Get the transfer count */
-	status = readl(ch->addr + APB_DMA_CHAN_STA);
-	to_transfer = (status & STA_COUNT_MASK) >> STA_COUNT_SHIFT;
-	req_transfer_count = ch->req_transfer_count;
-	req_transfer_count += 1;
-	to_transfer += 1;
+	if (!stop)
+		goto skip_stop_dma;
 
-	req->bytes_transferred = req_transfer_count;
+	status = get_channel_status(ch, req, true);
+	req->bytes_transferred = dma_active_count(ch, req, status);
 
-	if (status & STA_BUSY)
-		req->bytes_transferred -= to_transfer;
-
-	/* In continuous transfer mode, DMA only tracks the count of the
-	 * half DMA buffer. So, if the DMA already finished half the DMA
-	 * then add the half buffer to the completed count.
-	 *
-	 *	FIXME: There can be a race here. What if the req to
-	 *	dequue happens at the same time as the DMA just moved to
-	 *	the new buffer and SW didn't yet received the interrupt?
-	 */
-	if (ch->mode & TEGRA_DMA_MODE_CONTINOUS)
-		if (req->buffer_status == TEGRA_DMA_REQ_BUF_STATUS_HALF_FULL)
-			req->bytes_transferred += req_transfer_count;
-
-	req->bytes_transferred *= 4;
-
-	tegra_dma_stop(ch);
 	if (!list_empty(&ch->list)) {
 		/* if the list is not empty, queue the next request */
 		struct tegra_dma_req *next_req;
@@ -270,6 +300,8 @@ int tegra_dma_dequeue_req(struct tegra_dma_channel *ch,
 			typeof(*next_req), node);
 		tegra_dma_update_hw(ch, next_req);
 	}
+
+skip_stop_dma:
 	req->status = -TEGRA_DMA_REQ_ERROR_ABORTED;
 
 	spin_unlock_irqrestore(&ch->lock, irq_flags);
-- 
1.7.1.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* RE: [PATCH V1] ARM: tegra: Pause DMA when reading transfer count
  2012-01-10  6:05 ` Laxman Dewangan
  (?)
@ 2012-01-11 20:48     ` Stephen Warren
  -1 siblings, 0 replies; 8+ messages in thread
From: Stephen Warren @ 2012-01-11 20:48 UTC (permalink / raw)
  To: Laxman Dewangan, ccross-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org,
	olof-nZhT3qVonbNeoWH0uzbU5w@public.gmane.org,
	linux-lFZ/pmaqli7XmaaqVzeoHQ@public.gmane.org
  Cc: linux-tegra-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Laxman Dewangan

Laxman Dewangan wrote at Monday, January 09, 2012 11:05 PM:
> In order to read an accurate channel transfer count
> from the APB DMA engine, the DMA controller must be
> paused first.
> 
> Signed-off-by: Laxman Dewangan <ldewangan-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>

Acked-by: Stephen Warren <swarren-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
Tested-by: Stephen Warren <swarren-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>

(Audio playback on Harmony booted using device tree works fine in my
brief testing)

-- 
nvpublic

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH V1] ARM: tegra: Pause DMA when reading transfer count
@ 2012-01-11 20:48     ` Stephen Warren
  0 siblings, 0 replies; 8+ messages in thread
From: Stephen Warren @ 2012-01-11 20:48 UTC (permalink / raw)
  To: linux-arm-kernel

Laxman Dewangan wrote at Monday, January 09, 2012 11:05 PM:
> In order to read an accurate channel transfer count
> from the APB DMA engine, the DMA controller must be
> paused first.
> 
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>

Acked-by: Stephen Warren <swarren@nvidia.com>
Tested-by: Stephen Warren <swarren@nvidia.com>

(Audio playback on Harmony booted using device tree works fine in my
brief testing)

-- 
nvpublic

^ permalink raw reply	[flat|nested] 8+ messages in thread

* RE: [PATCH V1] ARM: tegra: Pause DMA when reading transfer count
@ 2012-01-11 20:48     ` Stephen Warren
  0 siblings, 0 replies; 8+ messages in thread
From: Stephen Warren @ 2012-01-11 20:48 UTC (permalink / raw)
  To: Laxman Dewangan, ccross@android.com, olof@lixom.net,
	linux@arm.linux.org.uk
  Cc: linux-tegra@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, Laxman Dewangan

Laxman Dewangan wrote at Monday, January 09, 2012 11:05 PM:
> In order to read an accurate channel transfer count
> from the APB DMA engine, the DMA controller must be
> paused first.
> 
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>

Acked-by: Stephen Warren <swarren@nvidia.com>
Tested-by: Stephen Warren <swarren@nvidia.com>

(Audio playback on Harmony booted using device tree works fine in my
brief testing)

-- 
nvpublic



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH V1] ARM: tegra: Pause DMA when reading transfer count
  2012-01-11 20:48     ` Stephen Warren
  (?)
@ 2012-01-19  3:48         ` Olof Johansson
  -1 siblings, 0 replies; 8+ messages in thread
From: Olof Johansson @ 2012-01-19  3:48 UTC (permalink / raw)
  To: Stephen Warren
  Cc: Laxman Dewangan, ccross-z5hGa2qSFaRBDgjK7y7TUQ@public.gmane.org,
	linux-lFZ/pmaqli7XmaaqVzeoHQ@public.gmane.org,
	linux-tegra-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Laxman Dewangan

On Wed, Jan 11, 2012 at 12:48:26PM -0800, Stephen Warren wrote:
> Laxman Dewangan wrote at Monday, January 09, 2012 11:05 PM:
> > In order to read an accurate channel transfer count
> > from the APB DMA engine, the DMA controller must be
> > paused first.
> > 
> > Signed-off-by: Laxman Dewangan <ldewangan-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
> 
> Acked-by: Stephen Warren <swarren-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
> Tested-by: Stephen Warren <swarren-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
> 
> (Audio playback on Harmony booted using device tree works fine in my
> brief testing)

Thanks, applied.


-Olof

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH V1] ARM: tegra: Pause DMA when reading transfer count
@ 2012-01-19  3:48         ` Olof Johansson
  0 siblings, 0 replies; 8+ messages in thread
From: Olof Johansson @ 2012-01-19  3:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jan 11, 2012 at 12:48:26PM -0800, Stephen Warren wrote:
> Laxman Dewangan wrote at Monday, January 09, 2012 11:05 PM:
> > In order to read an accurate channel transfer count
> > from the APB DMA engine, the DMA controller must be
> > paused first.
> > 
> > Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> 
> Acked-by: Stephen Warren <swarren@nvidia.com>
> Tested-by: Stephen Warren <swarren@nvidia.com>
> 
> (Audio playback on Harmony booted using device tree works fine in my
> brief testing)

Thanks, applied.


-Olof

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH V1] ARM: tegra: Pause DMA when reading transfer count
@ 2012-01-19  3:48         ` Olof Johansson
  0 siblings, 0 replies; 8+ messages in thread
From: Olof Johansson @ 2012-01-19  3:48 UTC (permalink / raw)
  To: Stephen Warren
  Cc: Laxman Dewangan, ccross@android.com, linux@arm.linux.org.uk,
	linux-tegra@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, Laxman Dewangan

On Wed, Jan 11, 2012 at 12:48:26PM -0800, Stephen Warren wrote:
> Laxman Dewangan wrote at Monday, January 09, 2012 11:05 PM:
> > In order to read an accurate channel transfer count
> > from the APB DMA engine, the DMA controller must be
> > paused first.
> > 
> > Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> 
> Acked-by: Stephen Warren <swarren@nvidia.com>
> Tested-by: Stephen Warren <swarren@nvidia.com>
> 
> (Audio playback on Harmony booted using device tree works fine in my
> brief testing)

Thanks, applied.


-Olof

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2012-01-19  3:48 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-01-10  6:05 [PATCH V1] ARM: tegra: Pause DMA when reading transfer count Laxman Dewangan
2012-01-10  6:05 ` Laxman Dewangan
     [not found] ` <1326175511-28642-1-git-send-email-ldewangan-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
2012-01-11 20:48   ` Stephen Warren
2012-01-11 20:48     ` Stephen Warren
2012-01-11 20:48     ` Stephen Warren
     [not found]     ` <74CDBE0F657A3D45AFBB94109FB122FF177EE3A77E-C7FfzLzN0UxDw2glCA4ptUEOCMrvLtNR@public.gmane.org>
2012-01-19  3:48       ` Olof Johansson
2012-01-19  3:48         ` Olof Johansson
2012-01-19  3:48         ` Olof Johansson

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.