2012-11-05 09:52:06

by Alban Bedel

[permalink] [raw]
Subject: dmaengine: PL08x: Allow using PL08x with sound drivers

This patch serie extend the PL08x driver to allow using it with
sound drivers. Sound drivers require the cyclic transfer mode and
that tx_status give reliable results.

* Patch 1 is a trivial fix
* Patch 2 implement the cyclic mode needed by sound drivers
* Patch 3 fix the tx_status implementation to get reliable results


2012-11-05 09:52:11

by Alban Bedel

[permalink] [raw]
Subject: [PATCH 3/3] dmaengine: PL08x: Make the transfer status reliable

To reliably get the current DMA position it is not possible to
just read the PL08x registers. As several reads are needed the values
are not consistent. To overcome this keep track of the position as
each LLI finish, this give a coarse but reliable value.

Signed-off-by: Alban Bedel <[email protected]>
---
drivers/dma/amba-pl08x.c | 79 +++++++++++++--------------------------------
1 files changed, 23 insertions(+), 56 deletions(-)

diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index b097788..57a4d80 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -175,6 +175,10 @@ struct pl08x_sg {
* @done: this marks completed descriptors, which should not have their
* mux released.
* @cyclic: indicate cyclic transfers
+ * @llis_pos: current position in the linked list
+ * @llis_count: number of transfer in the linked list
+ * @bytes_transferred: number of bytes tranfered up to now
+ * @bytes_requested: number of bytes in the whole transfer
*/
struct pl08x_txd {
struct virt_dma_desc vd;
@@ -191,6 +195,10 @@ struct pl08x_txd {
bool done;

bool cyclic;
+ unsigned llis_pos;
+ unsigned llis_count;
+ unsigned bytes_transferred;
+ unsigned bytes_requested;
};

/**
@@ -470,56 +478,6 @@ static inline u32 get_bytes_in_cctl(u32 cctl)
return bytes;
}

-/* The channel should be paused when calling this */
-static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan)
-{
- struct pl08x_phy_chan *ch;
- struct pl08x_txd *txd;
- size_t bytes = 0;
-
- ch = plchan->phychan;
- txd = plchan->at;
-
- /*
- * Follow the LLIs to get the number of remaining
- * bytes in the currently active transaction.
- */
- if (ch && txd) {
- u32 clli = readl(ch->base + PL080_CH_LLI) & ~PL080_LLI_LM_AHB2;
-
- /* First get the remaining bytes in the active transfer */
- bytes = get_bytes_in_cctl(readl(ch->base + PL080_CH_CONTROL));
-
- if (clli) {
- struct pl08x_lli *llis_va = txd->llis_va;
- dma_addr_t llis_bus = txd->llis_bus;
- int index;
-
- BUG_ON(clli < llis_bus || clli >= llis_bus +
- sizeof(struct pl08x_lli) * MAX_NUM_TSFR_LLIS);
-
- /*
- * Locate the next LLI - as this is an array,
- * it's simple maths to find.
- */
- index = (clli - llis_bus) / sizeof(struct pl08x_lli);
-
- for (; index < MAX_NUM_TSFR_LLIS; index++) {
- bytes += get_bytes_in_cctl(llis_va[index].cctl);
-
- /*
- * A LLI pointer going backward terminates
- * the LLI list
- */
- if (llis_va[index].lli <= clli)
- break;
- }
- }
- }
-
- return bytes;
-}
-
/*
* Allocate a physical channel for a virtual channel
*
@@ -777,7 +735,8 @@ static void pl08x_fill_lli_for_desc(struct pl08x_lli_build_data *bd,

BUG_ON(num_llis >= MAX_NUM_TSFR_LLIS);

- llis_va[num_llis].cctl = cctl;
+ /* Always enable TC IRQ to keep track of the position in the LLIs */
+ llis_va[num_llis].cctl = cctl | PL080_CONTROL_TC_IRQ_EN;
llis_va[num_llis].src = bd->srcbus.addr;
llis_va[num_llis].dst = bd->dstbus.addr;
llis_va[num_llis].lli = llis_bus + (num_llis + 1) *
@@ -1011,8 +970,10 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
__func__, (u32) MAX_NUM_TSFR_LLIS);
return 0;
}
+ txd->bytes_requested += total_bytes;
}

+ txd->llis_count = num_llis;
llis_va = txd->llis_va;
if (txd->cyclic) {
/* Link back to the first LLI. */
@@ -1020,7 +981,6 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
} else {
/* The final LLI terminates the LLI. */
llis_va[num_llis - 1].lli = 0;
- llis_va[num_llis - 1].cctl |= PL080_CONTROL_TC_IRQ_EN;
}

#ifdef VERBOSE_DEBUG
@@ -1177,8 +1137,9 @@ static enum dma_status pl08x_dma_tx_status(struct dma_chan *chan,

list_for_each_entry(dsg, &txd->dsg_list, node)
bytes += dsg->len;
- } else {
- bytes = pl08x_getbytes_chan(plchan);
+ } else if (plchan->at) {
+ bytes = plchan->at->bytes_requested -
+ plchan->at->bytes_transferred;
}
}
spin_unlock_irqrestore(&plchan->vc.lock, flags);
@@ -1594,7 +1555,6 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_cyclic(
return NULL;

txd->cyclic = true;
- txd->cctl |= PL080_CONTROL_TC_IRQ_EN;
for (tmp = 0 ; tmp < buf_len ; tmp += period_len) {
ret = pl08x_tx_add_sg(txd, direction, slave_addr,
buf_addr + tmp, period_len);
@@ -1745,9 +1705,16 @@ static irqreturn_t pl08x_irq(int irq, void *dev)
spin_lock(&plchan->vc.lock);
tx = plchan->at;
if (tx) {
+ tx->bytes_transferred += get_bytes_in_cctl(
+ tx->llis_va[tx->llis_pos].cctl);
+ tx->llis_pos += 1;
if (tx->cyclic) {
+ tx->llis_pos %=
+ tx->llis_count;
+ tx->bytes_transferred %=
+ tx->bytes_requested;
vchan_cyclic_callback(&tx->vd);
- } else {
+ } else if (tx->llis_pos >= tx->llis_count) {
plchan->at = NULL;
/*
* This descriptor is done, release
--
1.7.0.4

2012-11-05 09:52:16

by Alban Bedel

[permalink] [raw]
Subject: [PATCH 1/3] dmaengine: PL08x: Fix reading the byte count in cctl

Signed-off-by: Alban Bedel <[email protected]>
---
drivers/dma/amba-pl08x.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index d1cc579..fc8bedf 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -453,7 +453,8 @@ static inline u32 get_bytes_in_cctl(u32 cctl)
/* The source width defines the number of bytes */
u32 bytes = cctl & PL080_CONTROL_TRANSFER_SIZE_MASK;

- switch (cctl >> PL080_CONTROL_SWIDTH_SHIFT) {
+ switch ((cctl & PL080_CONTROL_SWIDTH_MASK) >>
+ PL080_CONTROL_SWIDTH_SHIFT) {
case PL080_WIDTH_8BIT:
break;
case PL080_WIDTH_16BIT:
--
1.7.0.4

2012-11-05 09:52:36

by Alban Bedel

[permalink] [raw]
Subject: [PATCH 2/3] dmaengine: PL08x: Add cyclic transfer support

Signed-off-by: Alban Bedel <[email protected]>
---
drivers/dma/amba-pl08x.c | 184 ++++++++++++++++++++++++++++++++++------------
1 files changed, 138 insertions(+), 46 deletions(-)

diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c
index fc8bedf..b097788 100644
--- a/drivers/dma/amba-pl08x.c
+++ b/drivers/dma/amba-pl08x.c
@@ -174,6 +174,7 @@ struct pl08x_sg {
* @ccfg: config reg values for current txd
* @done: this marks completed descriptors, which should not have their
* mux released.
+ * @cyclic: indicate cyclic transfers
*/
struct pl08x_txd {
struct virt_dma_desc vd;
@@ -188,6 +189,8 @@ struct pl08x_txd {
*/
u32 ccfg;
bool done;
+
+ bool cyclic;
};

/**
@@ -505,9 +508,10 @@ static u32 pl08x_getbytes_chan(struct pl08x_dma_chan *plchan)
bytes += get_bytes_in_cctl(llis_va[index].cctl);

/*
- * A LLI pointer of 0 terminates the LLI list
+ * A LLI pointer going backward terminates
+ * the LLI list
*/
- if (!llis_va[index].lli)
+ if (llis_va[index].lli <= clli)
break;
}
}
@@ -1010,10 +1014,14 @@ static int pl08x_fill_llis_for_desc(struct pl08x_driver_data *pl08x,
}

llis_va = txd->llis_va;
- /* The final LLI terminates the LLI. */
- llis_va[num_llis - 1].lli = 0;
- /* The final LLI element shall also fire an interrupt. */
- llis_va[num_llis - 1].cctl |= PL080_CONTROL_TC_IRQ_EN;
+ if (txd->cyclic) {
+ /* Link back to the first LLI. */
+ llis_va[num_llis - 1].lli = txd->llis_bus | bd.lli_bus;
+ } else {
+ /* The final LLI terminates the LLI. */
+ llis_va[num_llis - 1].lli = 0;
+ llis_va[num_llis - 1].cctl |= PL080_CONTROL_TC_IRQ_EN;
+ }

#ifdef VERBOSE_DEBUG
{
@@ -1411,25 +1419,19 @@ static struct dma_async_tx_descriptor *pl08x_prep_dma_memcpy(
return vchan_tx_prep(&plchan->vc, &txd->vd, flags);
}

-static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
- struct dma_chan *chan, struct scatterlist *sgl,
- unsigned int sg_len, enum dma_transfer_direction direction,
- unsigned long flags, void *context)
+static struct pl08x_txd *pl08x_init_txd(
+ struct dma_chan *chan,
+ enum dma_transfer_direction direction,
+ dma_addr_t *slave_addr)
{
struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
struct pl08x_driver_data *pl08x = plchan->host;
struct pl08x_txd *txd;
- struct pl08x_sg *dsg;
- struct scatterlist *sg;
enum dma_slave_buswidth addr_width;
- dma_addr_t slave_addr;
int ret, tmp;
u8 src_buses, dst_buses;
u32 maxburst, cctl;

- dev_dbg(&pl08x->adev->dev, "%s prepare transaction of %d bytes from %s\n",
- __func__, sg_dma_len(sgl), plchan->name);
-
txd = pl08x_get_txd(plchan);
if (!txd) {
dev_err(&pl08x->adev->dev, "%s no txd\n", __func__);
@@ -1443,14 +1445,14 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
*/
if (direction == DMA_MEM_TO_DEV) {
cctl = PL080_CONTROL_SRC_INCR;
- slave_addr = plchan->cfg.dst_addr;
+ *slave_addr = plchan->cfg.dst_addr;
addr_width = plchan->cfg.dst_addr_width;
maxburst = plchan->cfg.dst_maxburst;
src_buses = pl08x->mem_buses;
dst_buses = plchan->cd->periph_buses;
} else if (direction == DMA_DEV_TO_MEM) {
cctl = PL080_CONTROL_DST_INCR;
- slave_addr = plchan->cfg.src_addr;
+ *slave_addr = plchan->cfg.src_addr;
addr_width = plchan->cfg.src_addr_width;
maxburst = plchan->cfg.src_maxburst;
src_buses = plchan->cd->periph_buses;
@@ -1499,24 +1501,107 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
else
txd->ccfg |= plchan->signal << PL080_CONFIG_SRC_SEL_SHIFT;

+ return txd;
+}
+
+static int pl08x_tx_add_sg(struct pl08x_txd *txd,
+ enum dma_transfer_direction direction,
+ dma_addr_t slave_addr,
+ dma_addr_t buf_addr,
+ unsigned int len)
+{
+ struct pl08x_sg *dsg;
+
+ dsg = kzalloc(sizeof(struct pl08x_sg), GFP_NOWAIT);
+ if (!dsg)
+ return -ENOMEM;
+
+ list_add_tail(&dsg->node, &txd->dsg_list);
+
+ dsg->len = len;
+ if (direction == DMA_MEM_TO_DEV) {
+ dsg->src_addr = buf_addr;
+ dsg->dst_addr = slave_addr;
+ } else {
+ dsg->src_addr = slave_addr;
+ dsg->dst_addr = buf_addr;
+ }
+
+ return 0;
+}
+
+static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
+ struct dma_chan *chan, struct scatterlist *sgl,
+ unsigned int sg_len, enum dma_transfer_direction direction,
+ unsigned long flags, void *context)
+{
+ struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
+ struct pl08x_driver_data *pl08x = plchan->host;
+ struct pl08x_txd *txd;
+ struct scatterlist *sg;
+ int ret, tmp;
+ dma_addr_t slave_addr;
+
+ dev_dbg(&pl08x->adev->dev, "%s prepare transaction of %d bytes from %s\n",
+ __func__, sg_dma_len(sgl), plchan->name);
+
+ txd = pl08x_init_txd(chan, direction, &slave_addr);
+ if (!txd)
+ return NULL;
+
for_each_sg(sgl, sg, sg_len, tmp) {
- dsg = kzalloc(sizeof(struct pl08x_sg), GFP_NOWAIT);
- if (!dsg) {
+ ret = pl08x_tx_add_sg(txd, direction, slave_addr,
+ sg_dma_address(sg),
+ sg_dma_len(sg));
+ if (ret) {
pl08x_release_mux(plchan);
pl08x_free_txd(pl08x, txd);
dev_err(&pl08x->adev->dev, "%s no mem for pl080 sg\n",
__func__);
return NULL;
}
- list_add_tail(&dsg->node, &txd->dsg_list);
+ }

- dsg->len = sg_dma_len(sg);
- if (direction == DMA_MEM_TO_DEV) {
- dsg->src_addr = sg_dma_address(sg);
- dsg->dst_addr = slave_addr;
- } else {
- dsg->src_addr = slave_addr;
- dsg->dst_addr = sg_dma_address(sg);
+ ret = pl08x_fill_llis_for_desc(plchan->host, txd);
+ if (!ret) {
+ pl08x_release_mux(plchan);
+ pl08x_free_txd(pl08x, txd);
+ return NULL;
+ }
+
+ return vchan_tx_prep(&plchan->vc, &txd->vd, flags);
+}
+
+static struct dma_async_tx_descriptor *pl08x_prep_dma_cyclic(
+ struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
+ size_t period_len, enum dma_transfer_direction direction,
+ void *context)
+{
+ struct pl08x_dma_chan *plchan = to_pl08x_chan(chan);
+ struct pl08x_driver_data *pl08x = plchan->host;
+ struct pl08x_txd *txd;
+ int ret, tmp;
+ dma_addr_t slave_addr;
+
+ dev_dbg(&pl08x->adev->dev,
+ "%s prepare cyclic transaction of %d/%d bytes %s %s\n",
+ __func__, period_len, buf_len,
+ direction == DMA_MEM_TO_DEV ? "to" : "from",
+ plchan->name);
+
+ txd = pl08x_init_txd(chan, direction, &slave_addr);
+ if (!txd)
+ return NULL;
+
+ txd->cyclic = true;
+ txd->cctl |= PL080_CONTROL_TC_IRQ_EN;
+ for (tmp = 0 ; tmp < buf_len ; tmp += period_len) {
+ ret = pl08x_tx_add_sg(txd, direction, slave_addr,
+ buf_addr + tmp, period_len);
+ if (ret) {
+ pl08x_release_mux(plchan);
+ pl08x_free_txd(pl08x, txd);
+ return NULL;
}
}

@@ -1527,7 +1612,7 @@ static struct dma_async_tx_descriptor *pl08x_prep_slave_sg(
return NULL;
}

- return vchan_tx_prep(&plchan->vc, &txd->vd, flags);
+ return vchan_tx_prep(&plchan->vc, &txd->vd, 0);
}

static int pl08x_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
@@ -1660,23 +1745,28 @@ static irqreturn_t pl08x_irq(int irq, void *dev)
spin_lock(&plchan->vc.lock);
tx = plchan->at;
if (tx) {
- plchan->at = NULL;
- /*
- * This descriptor is done, release its mux
- * reservation.
- */
- pl08x_release_mux(plchan);
- tx->done = true;
- vchan_cookie_complete(&tx->vd);
-
- /*
- * And start the next descriptor (if any),
- * otherwise free this channel.
- */
- if (vchan_next_desc(&plchan->vc))
- pl08x_start_next_txd(plchan);
- else
- pl08x_phy_free(plchan);
+ if (tx->cyclic) {
+ vchan_cyclic_callback(&tx->vd);
+ } else {
+ plchan->at = NULL;
+ /*
+ * This descriptor is done, release
+ * its mux reservation.
+ */
+ pl08x_release_mux(plchan);
+ tx->done = true;
+ vchan_cookie_complete(&tx->vd);
+
+ /*
+ * And start the next descriptor
+ * (if any), otherwise free this
+ * channel.
+ */
+ if (vchan_next_desc(&plchan->vc))
+ pl08x_start_next_txd(plchan);
+ else
+ pl08x_phy_free(plchan);
+ }
}
spin_unlock(&plchan->vc.lock);

@@ -1880,6 +1970,7 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)

/* Initialize slave engine */
dma_cap_set(DMA_SLAVE, pl08x->slave.cap_mask);
+ dma_cap_set(DMA_CYCLIC, pl08x->slave.cap_mask);
pl08x->slave.dev = &adev->dev;
pl08x->slave.device_alloc_chan_resources = pl08x_alloc_chan_resources;
pl08x->slave.device_free_chan_resources = pl08x_free_chan_resources;
@@ -1887,6 +1978,7 @@ static int pl08x_probe(struct amba_device *adev, const struct amba_id *id)
pl08x->slave.device_tx_status = pl08x_dma_tx_status;
pl08x->slave.device_issue_pending = pl08x_issue_pending;
pl08x->slave.device_prep_slave_sg = pl08x_prep_slave_sg;
+ pl08x->slave.device_prep_dma_cyclic = pl08x_prep_dma_cyclic;
pl08x->slave.device_control = pl08x_control;

/* Get the platform data */
--
1.7.0.4