diff options
author | Gary King <gking@nvidia.com> | 2010-05-21 18:57:48 -0700 |
---|---|---|
committer | Gary King <gking@nvidia.com> | 2010-05-21 19:35:12 -0700 |
commit | 6da18fdbed7a1574890f461d676836c2b5ca9f2e (patch) | |
tree | 8e35546ba7de8b81def48f8e16b4ddebd4072b8c | |
parent | d2b1af1975c3fe4069c1a09c7be8c2ad1d542230 (diff) |
[ARM/tegra] dma: add transfer count query, correct spelling of CONTINUOUS
add an API to return the completed transfer count of a pending, active
or finished DMA request
CONTINUOUS was misspelled CONTINOUS; fix this in the hsuart and dma drivers
Change-Id: I732396f60f876c92fe3b74ad1d2fc02d00868d24
-rw-r--r-- | arch/arm/mach-tegra/dma.c | 104 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/dma.h | 6 | ||||
-rw-r--r-- | drivers/serial/tegra_hsuart.c | 2 |
3 files changed, 79 insertions, 33 deletions
diff --git a/arch/arm/mach-tegra/dma.c b/arch/arm/mach-tegra/dma.c index 423db61998a2..43f8622ee12f 100644 --- a/arch/arm/mach-tegra/dma.c +++ b/arch/arm/mach-tegra/dma.c @@ -95,8 +95,9 @@ #define APB_SEQ_WRAP_SHIFT 16 #define APB_SEQ_WRAP_MASK (0x7<<APB_SEQ_WRAP_SHIFT) +#define TEGRA_SYSTEM_DMA_AVP_CH_NUM 3 #define TEGRA_SYSTEM_DMA_CH_MIN 0 -#define TEGRA_SYSTEM_DMA_CH_MAX 15 +#define TEGRA_SYSTEM_DMA_CH_MAX (15 - TEGRA_SYSTEM_DMA_AVP_CH_NUM) #define NV_DMA_MAX_TRASFER_SIZE 0x10000 @@ -193,18 +194,81 @@ int tegra_dma_cancel(struct tegra_dma_channel *ch) return 0; } +/* 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; + + /* 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->csr & CSR_WCOUNT_MASK) >> CSR_WCOUNT_SHIFT; + req_transfer_count += 1; + to_transfer += 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. + * + * 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_CONTINUOUS) + if (req->buffer_status == TEGRA_DMA_REQ_BUF_STATUS_HALF_FULL) + bytes_transferred += req_transfer_count; + + bytes_transferred *= 4; + + return bytes_transferred; +} + +unsigned int tegra_dma_transferred_req(struct tegra_dma_channel *ch, + struct tegra_dma_req *req) +{ + unsigned long irq_flags; + unsigned int bytes_transferred; + + 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); + return req->bytes_transferred; + } + + bytes_transferred = dma_active_count(ch, req); + + spin_unlock_irqrestore(&ch->lock, irq_flags); + + return bytes_transferred; +} +EXPORT_SYMBOL(tegra_dma_transferred_req); + 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 int csr; 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); @@ -217,6 +281,9 @@ int tegra_dma_dequeue_req(struct tegra_dma_channel *ch, return 0; } + if (!stop) + goto skip_status; + /* 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 @@ -234,31 +301,7 @@ int tegra_dma_dequeue_req(struct tegra_dma_channel *ch, csr |= CSR_ENB; 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->csr & CSR_WCOUNT_MASK) >> CSR_WCOUNT_SHIFT; - req_transfer_count += 1; - to_transfer += 1; - - req->bytes_transferred = req_transfer_count; - - if (status & STA_BUSY) - req->bytes_transferred -= to_transfer; - - /* In continous 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; + req->bytes_transferred = dma_active_count(ch, req); tegra_dma_stop(ch); if (!list_empty(&ch->list)) { @@ -268,6 +311,7 @@ int tegra_dma_dequeue_req(struct tegra_dma_channel *ch, typeof(*next_req), node); tegra_dma_update_hw(ch, next_req); } +skip_status: req->status = -TEGRA_DMA_REQ_ERROR_ABORTED; spin_unlock_irqrestore(&ch->lock, irq_flags); diff --git a/arch/arm/mach-tegra/include/mach/dma.h b/arch/arm/mach-tegra/include/mach/dma.h index 59f734038ea9..731b433812a9 100644 --- a/arch/arm/mach-tegra/include/mach/dma.h +++ b/arch/arm/mach-tegra/include/mach/dma.h @@ -58,7 +58,7 @@ struct tegra_dma_channel; enum tegra_dma_mode { TEGRA_DMA_SHARED = 1, - TEGRA_DMA_MODE_CONTINOUS = 2, + TEGRA_DMA_MODE_CONTINUOUS = 2, TEGRA_DMA_MODE_ONESHOT = 4, }; @@ -75,7 +75,6 @@ enum tegra_dma_req_buff_status { struct tegra_dma_req { struct list_head node; - unsigned int modid; int instance; /* Called when the req is complete and from the DMA ISR context. @@ -140,6 +139,9 @@ int tegra_dma_dequeue_req(struct tegra_dma_channel *ch, void tegra_dma_dequeue(struct tegra_dma_channel *ch); 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); + 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); diff --git a/drivers/serial/tegra_hsuart.c b/drivers/serial/tegra_hsuart.c index f3a03e44947b..94c6ddb1bb1b 100644 --- a/drivers/serial/tegra_hsuart.c +++ b/drivers/serial/tegra_hsuart.c @@ -642,7 +642,7 @@ static int tegra_uart_init_rx_dma(struct tegra_uart_port *t) dma_addr_t rx_dma_phys; void *rx_dma_virt; - t->rx_dma = tegra_dma_allocate_channel(TEGRA_DMA_MODE_CONTINOUS); + t->rx_dma = tegra_dma_allocate_channel(TEGRA_DMA_MODE_CONTINUOUS); if (IS_ERR_OR_NULL(t->rx_dma)) return -ENODEV; |