Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 296079
b: refs/heads/master
c: cb3732d
h: refs/heads/master
i:
  296077: 8796d3c
  296075: 56b2600
  296071: d06f9c5
  296063: 2d02ad0
v: v3
  • Loading branch information
Laxman Dewangan authored and Olof Johansson committed Feb 7, 2012
1 parent 6d81316 commit faabb8b
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 43 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 941b8db1df8bfc29a88fc8e3e203289d84a3f64d
refs/heads/master: cb3732d0dc9df198c889a26210b6b27bc51a1c4a
116 changes: 74 additions & 42 deletions trunk/arch/arm/mach-tegra/dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,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];
Expand Down Expand Up @@ -200,18 +201,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);
Expand All @@ -224,54 +289,21 @@ 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;
next_req = list_entry(ch->list.next,
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);
Expand Down

0 comments on commit faabb8b

Please sign in to comment.