summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-tegra/dma.c184
-rw-r--r--arch/arm/mach-tegra/include/mach/dma.h9
2 files changed, 152 insertions, 41 deletions
diff --git a/arch/arm/mach-tegra/dma.c b/arch/arm/mach-tegra/dma.c
index feaa7fdb46bf..7eb0bb758b67 100644
--- a/arch/arm/mach-tegra/dma.c
+++ b/arch/arm/mach-tegra/dma.c
@@ -223,6 +223,45 @@ static unsigned int dma_active_count(struct tegra_dma_channel *ch,
return bytes_transferred;
}
+static unsigned int get_channel_status(struct tegra_dma_channel *ch,
+ struct tegra_dma_req *req, bool is_stop_dma)
+{
+ static DEFINE_SPINLOCK(global_dma_bit_access_lock);
+ void __iomem *addr = IO_ADDRESS(TEGRA_APB_DMA_BASE);
+ unsigned long g_irq_flags;
+ 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_irqsave(&global_dma_bit_access_lock, g_irq_flags);
+ writel(0, addr + APB_DMA_GEN);
+ status = readl(ch->addr + APB_DMA_CHAN_STA);
+ tegra_dma_stop(ch);
+ writel(GEN_ENABLE, addr + APB_DMA_GEN);
+ spin_unlock_irqrestore(&global_dma_bit_access_lock, g_irq_flags);
+
+ 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_STOPPED;
+ } else {
+ status = readl(ch->addr + APB_DMA_CHAN_STA);
+ if (status & STA_ISE_EOC) {
+ pr_err("Got Dma Int here clearing");
+ writel(status, ch->addr + APB_DMA_CHAN_STA);
+ }
+ }
+ return status;
+}
+
unsigned int tegra_dma_transferred_req(struct tegra_dma_channel *ch,
struct tegra_dma_req *req)
{
@@ -246,16 +285,82 @@ unsigned int tegra_dma_transferred_req(struct tegra_dma_channel *ch,
}
EXPORT_SYMBOL(tegra_dma_transferred_req);
+int tegra_dma_get_transfer_count(struct tegra_dma_channel *ch,
+ struct tegra_dma_req *req, bool is_stop_dma)
+{
+ unsigned int status;
+ unsigned long irq_flags;
+ int bytes_transferred = 0;
+
+ spin_lock_irqsave(&ch->lock, irq_flags);
+
+ if (list_entry(ch->list.next, struct tegra_dma_req, node) != req) {
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ pr_err("The dma request is not running\n");
+ return -1;
+ }
+
+ if (req->status != TEGRA_DMA_REQ_INFLIGHT) {
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ pr_err("The dma request is not running\n");
+ return -1;
+ }
+ status = get_channel_status(ch, req, is_stop_dma);
+ bytes_transferred = dma_active_count(ch, req, status);
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ return bytes_transferred;
+}
+EXPORT_SYMBOL(tegra_dma_get_transfer_count);
+
+int tegra_dma_start_dma(struct tegra_dma_channel *ch, struct tegra_dma_req *req)
+{
+ unsigned int csr;
+ unsigned int status;
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&ch->lock, irq_flags);
+
+ if (list_entry(ch->list.next, struct tegra_dma_req, node)!=req) {
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ pr_err("The dma request is not on top request\n");
+ return -1;
+ }
+
+ if (req->status != TEGRA_DMA_REQ_STOPPED) {
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ pr_err("Dma state is not in STOPPED\n");
+ return -1;
+ }
+
+ /* clear interrupt if already there */
+ status = readl(ch->addr + APB_DMA_CHAN_STA);
+ if (status & STA_ISE_EOC)
+ writel(status, ch->addr + APB_DMA_CHAN_STA);
+
+ csr = ch->csr;
+ csr |= CSR_IE_EOC;
+ writel(csr, ch->addr + APB_DMA_CHAN_CSR);
+ ch->csr = csr;
+
+ req->bytes_transferred = 0;
+ req->status = TEGRA_DMA_REQ_INFLIGHT;
+ req->buffer_status = TEGRA_DMA_REQ_BUF_STATUS_EMPTY;
+
+ csr |= CSR_ENB;
+ writel(csr, ch->addr + APB_DMA_CHAN_CSR);
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ return 0;
+}
+EXPORT_SYMBOL(tegra_dma_start_dma);
+
int tegra_dma_dequeue_req(struct tegra_dma_channel *ch,
struct tegra_dma_req *_req)
{
- static DEFINE_SPINLOCK(enable_lock);
struct tegra_dma_req *req = NULL;
int found = 0;
unsigned int status;
unsigned long irq_flags;
int stop = 0;
- void __iomem *addr = IO_ADDRESS(TEGRA_APB_DMA_BASE);
spin_lock_irqsave(&ch->lock, irq_flags);
@@ -277,22 +382,7 @@ int tegra_dma_dequeue_req(struct tegra_dma_channel *ch,
if (!stop)
goto skip_status;
- /* 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);
- status = readl(ch->addr + APB_DMA_CHAN_STA);
- tegra_dma_stop(ch);
- writel(GEN_ENABLE, addr + APB_DMA_GEN);
- spin_unlock(&enable_lock);
-
+ status = get_channel_status(ch, req, true);
req->bytes_transferred = dma_active_count(ch, req, status);
if (!list_empty(&ch->list)) {
@@ -665,13 +755,18 @@ static void handle_continuous_dma(struct tegra_dma_channel *ch)
}
/* Load the next request into the hardware, if available
* */
- if (!list_is_last(&req->node, &ch->list)) {
- next_req = list_entry(req->node.next,
- typeof(*next_req), node);
- tegra_dma_update_hw_partial(ch, next_req);
+ if (!req->is_repeat_req) {
+ if (!list_is_last(&req->node, &ch->list)) {
+ next_req = list_entry(req->node.next,
+ typeof(*next_req), node);
+ tegra_dma_update_hw_partial(ch, next_req);
+ }
+ req->buffer_status = TEGRA_DMA_REQ_BUF_STATUS_HALF_FULL;
+ req->status = TEGRA_DMA_REQ_SUCCESS;
+ } else {
+ req->buffer_status = TEGRA_DMA_REQ_BUF_STATUS_HALF_FULL;
+ req->status = TEGRA_DMA_REQ_INFLIGHT;
}
- req->buffer_status = TEGRA_DMA_REQ_BUF_STATUS_HALF_FULL;
- req->status = TEGRA_DMA_REQ_SUCCESS;
/* DMA lock is NOT held when callback is called */
spin_unlock_irqrestore(&ch->lock, irq_flags);
if (likely(req->threshold))
@@ -692,26 +787,33 @@ static void handle_continuous_dma(struct tegra_dma_channel *ch)
req->buffer_status = TEGRA_DMA_REQ_BUF_STATUS_FULL;
req->bytes_transferred = bytes_transferred;
req->status = TEGRA_DMA_REQ_SUCCESS;
- if (list_is_last(&req->node, &ch->list)) {
- tegra_dma_stop(ch);
- } else {
- /* It may be possible that req came after
- * half dma complete so it need to start
- * immediately */
- next_req = list_entry(req->node.next,
- typeof(*next_req), node);
- if (next_req->status !=
- TEGRA_DMA_REQ_INFLIGHT) {
+ if (!req->is_repeat_req) {
+ if (list_is_last(&req->node, &ch->list)) {
tegra_dma_stop(ch);
- tegra_dma_update_hw(ch, next_req);
+ } else {
+ /* It may be possible that req came after
+ * half dma complete so it need to start
+ * immediately */
+ next_req = list_entry(req->node.next,
+ typeof(*next_req), node);
+ if (next_req->status !=
+ TEGRA_DMA_REQ_INFLIGHT) {
+ tegra_dma_stop(ch);
+ tegra_dma_update_hw(ch, next_req);
+ }
}
- }
-
- list_del(&req->node);
+ list_del(&req->node);
- /* DMA lock is NOT held when callbak is called */
- spin_unlock_irqrestore(&ch->lock, irq_flags);
- req->complete(req);
+ /* DMA lock is NOT held when callbak is called */
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ req->complete(req);
+ } else {
+ req->buffer_status = TEGRA_DMA_REQ_BUF_STATUS_EMPTY;
+ req->status = TEGRA_DMA_REQ_INFLIGHT;
+ spin_unlock_irqrestore(&ch->lock, irq_flags);
+ if (likely(req->threshold))
+ req->threshold(req);
+ }
return;
} else {
diff --git a/arch/arm/mach-tegra/include/mach/dma.h b/arch/arm/mach-tegra/include/mach/dma.h
index a088b000ac48..a381b2ff893a 100644
--- a/arch/arm/mach-tegra/include/mach/dma.h
+++ b/arch/arm/mach-tegra/include/mach/dma.h
@@ -66,6 +66,7 @@ enum tegra_dma_req_error {
TEGRA_DMA_REQ_SUCCESS = 0,
TEGRA_DMA_REQ_ERROR_ABORTED,
TEGRA_DMA_REQ_INFLIGHT,
+ TEGRA_DMA_REQ_STOPPED,
};
enum tegra_dma_req_buff_status {
@@ -129,6 +130,9 @@ struct tegra_dma_req {
/* DMA completion tracking information */
int buffer_status;
+ /* Repeat same buffer provided on this request*/
+ bool is_repeat_req;
+
/* Client specific data */
void *dev;
};
@@ -143,6 +147,11 @@ void tegra_dma_flush(struct tegra_dma_channel *ch);
unsigned int tegra_dma_transferred_req(struct tegra_dma_channel *ch,
struct tegra_dma_req *req);
+int tegra_dma_get_transfer_count(struct tegra_dma_channel *ch,
+ struct tegra_dma_req *_req, bool is_stop_dma);
+int tegra_dma_start_dma(struct tegra_dma_channel *ch,
+ struct tegra_dma_req *_req);
+
bool tegra_dma_is_req_inflight(struct tegra_dma_channel *ch,
struct tegra_dma_req *req);
bool tegra_dma_is_empty(struct tegra_dma_channel *ch);