summaryrefslogtreecommitdiff
path: root/drivers/dma/sf-pdma/sf-pdma.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/dma/sf-pdma/sf-pdma.c')
-rw-r--r--drivers/dma/sf-pdma/sf-pdma.c174
1 files changed, 112 insertions, 62 deletions
diff --git a/drivers/dma/sf-pdma/sf-pdma.c b/drivers/dma/sf-pdma/sf-pdma.c
index 5c118c7e02bd..7ad3c29be146 100644
--- a/drivers/dma/sf-pdma/sf-pdma.c
+++ b/drivers/dma/sf-pdma/sf-pdma.c
@@ -20,9 +20,13 @@
#include <linux/mod_devicetable.h>
#include <linux/dma-mapping.h>
#include <linux/of.h>
+#include <linux/of_dma.h>
+#include <linux/slab.h>
#include "sf-pdma.h"
+#define PDMA_QUIRK_NO_STRICT_ORDERING BIT(0)
+
#ifndef readq
static inline unsigned long long readq(void __iomem *addr)
{
@@ -51,16 +55,6 @@ static inline struct sf_pdma_desc *to_sf_pdma_desc(struct virt_dma_desc *vd)
static struct sf_pdma_desc *sf_pdma_alloc_desc(struct sf_pdma_chan *chan)
{
struct sf_pdma_desc *desc;
- unsigned long flags;
-
- spin_lock_irqsave(&chan->lock, flags);
-
- if (chan->desc && !chan->desc->in_use) {
- spin_unlock_irqrestore(&chan->lock, flags);
- return chan->desc;
- }
-
- spin_unlock_irqrestore(&chan->lock, flags);
desc = kzalloc(sizeof(*desc), GFP_NOWAIT);
if (!desc)
@@ -74,7 +68,7 @@ static struct sf_pdma_desc *sf_pdma_alloc_desc(struct sf_pdma_chan *chan)
static void sf_pdma_fill_desc(struct sf_pdma_desc *desc,
u64 dst, u64 src, u64 size)
{
- desc->xfer_type = PDMA_FULL_SPEED;
+ desc->xfer_type = desc->chan->pdma->transfer_type;
desc->xfer_size = size;
desc->dst_addr = dst;
desc->src_addr = src;
@@ -93,6 +87,7 @@ sf_pdma_prep_dma_memcpy(struct dma_chan *dchan, dma_addr_t dest, dma_addr_t src,
{
struct sf_pdma_chan *chan = to_sf_pdma_chan(dchan);
struct sf_pdma_desc *desc;
+ unsigned long iflags;
if (chan && (!len || !dest || !src)) {
dev_err(chan->pdma->dma_dev.dev,
@@ -104,14 +99,12 @@ sf_pdma_prep_dma_memcpy(struct dma_chan *dchan, dma_addr_t dest, dma_addr_t src,
if (!desc)
return NULL;
- desc->in_use = true;
desc->dirn = DMA_MEM_TO_MEM;
desc->async_tx = vchan_tx_prep(&chan->vchan, &desc->vdesc, flags);
- spin_lock_irqsave(&chan->vchan.lock, flags);
- chan->desc = desc;
+ spin_lock_irqsave(&chan->vchan.lock, iflags);
sf_pdma_fill_desc(desc, dest, src, len);
- spin_unlock_irqrestore(&chan->vchan.lock, flags);
+ spin_unlock_irqrestore(&chan->vchan.lock, iflags);
return desc->async_tx;
}
@@ -168,11 +161,17 @@ static size_t sf_pdma_desc_residue(struct sf_pdma_chan *chan,
unsigned long flags;
u64 residue = 0;
struct sf_pdma_desc *desc;
- struct dma_async_tx_descriptor *tx;
+ struct dma_async_tx_descriptor *tx = NULL;
spin_lock_irqsave(&chan->vchan.lock, flags);
- tx = &chan->desc->vdesc.tx;
+ list_for_each_entry(vd, &chan->vchan.desc_submitted, node)
+ if (vd->tx.cookie == cookie)
+ tx = &vd->tx;
+
+ if (!tx)
+ goto out;
+
if (cookie == tx->chan->completed_cookie)
goto out;
@@ -239,6 +238,19 @@ static void sf_pdma_enable_request(struct sf_pdma_chan *chan)
writel(v, regs->ctrl);
}
+static struct sf_pdma_desc *sf_pdma_get_first_pending_desc(struct sf_pdma_chan *chan)
+{
+ struct virt_dma_chan *vchan = &chan->vchan;
+ struct virt_dma_desc *vdesc;
+
+ if (list_empty(&vchan->desc_issued))
+ return NULL;
+
+ vdesc = list_first_entry(&vchan->desc_issued, struct virt_dma_desc, node);
+
+ return container_of(vdesc, struct sf_pdma_desc, vdesc);
+}
+
static void sf_pdma_xfer_desc(struct sf_pdma_chan *chan)
{
struct sf_pdma_desc *desc = chan->desc;
@@ -266,8 +278,11 @@ static void sf_pdma_issue_pending(struct dma_chan *dchan)
spin_lock_irqsave(&chan->vchan.lock, flags);
- if (vchan_issue_pending(&chan->vchan) && chan->desc)
+ if (!chan->desc && vchan_issue_pending(&chan->vchan)) {
+ /* vchan_issue_pending has made a check that desc in not NULL */
+ chan->desc = sf_pdma_get_first_pending_desc(chan);
sf_pdma_xfer_desc(chan);
+ }
spin_unlock_irqrestore(&chan->vchan.lock, flags);
}
@@ -277,13 +292,12 @@ static void sf_pdma_free_desc(struct virt_dma_desc *vdesc)
struct sf_pdma_desc *desc;
desc = to_sf_pdma_desc(vdesc);
- desc->in_use = false;
+ kfree(desc);
}
-static void sf_pdma_donebh_tasklet(unsigned long arg)
+static void sf_pdma_donebh_tasklet(struct tasklet_struct *t)
{
- struct sf_pdma_chan *chan = (struct sf_pdma_chan *)arg;
- struct sf_pdma_desc *desc = chan->desc;
+ struct sf_pdma_chan *chan = from_tasklet(chan, t, done_tasklet);
unsigned long flags;
spin_lock_irqsave(&chan->lock, flags);
@@ -294,12 +308,20 @@ static void sf_pdma_donebh_tasklet(unsigned long arg)
}
spin_unlock_irqrestore(&chan->lock, flags);
- dmaengine_desc_get_callback_invoke(desc->async_tx, NULL);
+ spin_lock_irqsave(&chan->vchan.lock, flags);
+ list_del(&chan->desc->vdesc.node);
+ vchan_cookie_complete(&chan->desc->vdesc);
+
+ chan->desc = sf_pdma_get_first_pending_desc(chan);
+ if (chan->desc)
+ sf_pdma_xfer_desc(chan);
+
+ spin_unlock_irqrestore(&chan->vchan.lock, flags);
}
-static void sf_pdma_errbh_tasklet(unsigned long arg)
+static void sf_pdma_errbh_tasklet(struct tasklet_struct *t)
{
- struct sf_pdma_chan *chan = (struct sf_pdma_chan *)arg;
+ struct sf_pdma_chan *chan = from_tasklet(chan, t, err_tasklet);
struct sf_pdma_desc *desc = chan->desc;
unsigned long flags;
@@ -323,18 +345,16 @@ static irqreturn_t sf_pdma_done_isr(int irq, void *dev_id)
{
struct sf_pdma_chan *chan = dev_id;
struct pdma_regs *regs = &chan->regs;
- unsigned long flags;
u64 residue;
- spin_lock_irqsave(&chan->vchan.lock, flags);
+ spin_lock(&chan->vchan.lock);
writel((readl(regs->ctrl)) & ~PDMA_DONE_STATUS_MASK, regs->ctrl);
residue = readq(regs->residue);
if (!residue) {
- list_del(&chan->desc->vdesc.node);
- vchan_cookie_complete(&chan->desc->vdesc);
+ tasklet_hi_schedule(&chan->done_tasklet);
} else {
- /* submit next trascatioin if possible */
+ /* submit next transaction if possible */
struct sf_pdma_desc *desc = chan->desc;
desc->src_addr += desc->xfer_size - residue;
@@ -344,9 +364,7 @@ static irqreturn_t sf_pdma_done_isr(int irq, void *dev_id)
sf_pdma_xfer_desc(chan);
}
- spin_unlock_irqrestore(&chan->vchan.lock, flags);
-
- tasklet_hi_schedule(&chan->done_tasklet);
+ spin_unlock(&chan->vchan.lock);
return IRQ_HANDLED;
}
@@ -355,11 +373,10 @@ static irqreturn_t sf_pdma_err_isr(int irq, void *dev_id)
{
struct sf_pdma_chan *chan = dev_id;
struct pdma_regs *regs = &chan->regs;
- unsigned long flags;
- spin_lock_irqsave(&chan->lock, flags);
+ spin_lock(&chan->lock);
writel((readl(regs->ctrl)) & ~PDMA_ERR_STATUS_MASK, regs->ctrl);
- spin_unlock_irqrestore(&chan->lock, flags);
+ spin_unlock(&chan->lock);
tasklet_schedule(&chan->err_tasklet);
@@ -390,10 +407,8 @@ static int sf_pdma_irq_init(struct platform_device *pdev, struct sf_pdma *pdma)
chan = &pdma->chans[i];
irq = platform_get_irq(pdev, i * 2);
- if (irq < 0) {
- dev_err(&pdev->dev, "ch(%d) Can't get done irq.\n", i);
+ if (irq < 0)
return -EINVAL;
- }
r = devm_request_irq(&pdev->dev, irq, sf_pdma_done_isr, 0,
dev_name(&pdev->dev), (void *)chan);
@@ -405,10 +420,8 @@ static int sf_pdma_irq_init(struct platform_device *pdev, struct sf_pdma *pdma)
chan->txirq = irq;
irq = platform_get_irq(pdev, (i * 2) + 1);
- if (irq < 0) {
- dev_err(&pdev->dev, "ch(%d) Can't get err irq.\n", i);
+ if (irq < 0)
return -EINVAL;
- }
r = devm_request_irq(&pdev->dev, irq, sf_pdma_err_isr, 0,
dev_name(&pdev->dev), (void *)chan);
@@ -475,36 +488,48 @@ static void sf_pdma_setup_chans(struct sf_pdma *pdma)
writel(PDMA_CLEAR_CTRL, chan->regs.ctrl);
- tasklet_init(&chan->done_tasklet,
- sf_pdma_donebh_tasklet, (unsigned long)chan);
- tasklet_init(&chan->err_tasklet,
- sf_pdma_errbh_tasklet, (unsigned long)chan);
+ tasklet_setup(&chan->done_tasklet, sf_pdma_donebh_tasklet);
+ tasklet_setup(&chan->err_tasklet, sf_pdma_errbh_tasklet);
}
}
static int sf_pdma_probe(struct platform_device *pdev)
{
+ const struct sf_pdma_driver_platdata *ddata;
struct sf_pdma *pdma;
- struct sf_pdma_chan *chan;
- struct resource *res;
- int len, chans;
- int ret;
+ int ret, n_chans;
const enum dma_slave_buswidth widths =
DMA_SLAVE_BUSWIDTH_1_BYTE | DMA_SLAVE_BUSWIDTH_2_BYTES |
DMA_SLAVE_BUSWIDTH_4_BYTES | DMA_SLAVE_BUSWIDTH_8_BYTES |
DMA_SLAVE_BUSWIDTH_16_BYTES | DMA_SLAVE_BUSWIDTH_32_BYTES |
DMA_SLAVE_BUSWIDTH_64_BYTES;
- chans = PDMA_NR_CH;
- len = sizeof(*pdma) + sizeof(*chan) * chans;
- pdma = devm_kzalloc(&pdev->dev, len, GFP_KERNEL);
+ ret = of_property_read_u32(pdev->dev.of_node, "dma-channels", &n_chans);
+ if (ret) {
+ /* backwards-compatibility for no dma-channels property */
+ dev_dbg(&pdev->dev, "set number of channels to default value: 4\n");
+ n_chans = PDMA_MAX_NR_CH;
+ } else if (n_chans > PDMA_MAX_NR_CH) {
+ dev_err(&pdev->dev, "the number of channels exceeds the maximum\n");
+ return -EINVAL;
+ }
+
+ pdma = devm_kzalloc(&pdev->dev, struct_size(pdma, chans, n_chans),
+ GFP_KERNEL);
if (!pdma)
return -ENOMEM;
- pdma->n_chans = chans;
+ pdma->n_chans = n_chans;
+
+ pdma->transfer_type = PDMA_FULL_SPEED | PDMA_STRICT_ORDERING;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- pdma->membase = devm_ioremap_resource(&pdev->dev, res);
+ ddata = device_get_match_data(&pdev->dev);
+ if (ddata) {
+ if (ddata->quirks & PDMA_QUIRK_NO_STRICT_ORDERING)
+ pdma->transfer_type &= ~PDMA_STRICT_ORDERING;
+ }
+
+ pdma->membase = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(pdma->membase))
return PTR_ERR(pdma->membase);
@@ -550,16 +575,29 @@ static int sf_pdma_probe(struct platform_device *pdev)
return ret;
}
+ ret = of_dma_controller_register(pdev->dev.of_node,
+ of_dma_xlate_by_chan_id, pdma);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Can't register SiFive Platform OF_DMA. (%d)\n", ret);
+ goto err_unregister;
+ }
+
return 0;
+
+err_unregister:
+ dma_async_device_unregister(&pdma->dma_dev);
+
+ return ret;
}
-static int sf_pdma_remove(struct platform_device *pdev)
+static void sf_pdma_remove(struct platform_device *pdev)
{
struct sf_pdma *pdma = platform_get_drvdata(pdev);
struct sf_pdma_chan *ch;
int i;
- for (i = 0; i < PDMA_NR_CH; i++) {
+ for (i = 0; i < pdma->n_chans; i++) {
ch = &pdma->chans[i];
devm_free_irq(&pdev->dev, ch->txirq, ch);
@@ -570,13 +608,25 @@ static int sf_pdma_remove(struct platform_device *pdev)
tasklet_kill(&ch->err_tasklet);
}
- dma_async_device_unregister(&pdma->dma_dev);
+ if (pdev->dev.of_node)
+ of_dma_controller_free(pdev->dev.of_node);
- return 0;
+ dma_async_device_unregister(&pdma->dma_dev);
}
+static const struct sf_pdma_driver_platdata mpfs_pdma = {
+ .quirks = PDMA_QUIRK_NO_STRICT_ORDERING,
+};
+
static const struct of_device_id sf_pdma_dt_ids[] = {
- { .compatible = "sifive,fu540-c000-pdma" },
+ {
+ .compatible = "sifive,fu540-c000-pdma",
+ }, {
+ .compatible = "sifive,pdma0",
+ }, {
+ .compatible = "microchip,mpfs-pdma",
+ .data = &mpfs_pdma,
+ },
{},
};
MODULE_DEVICE_TABLE(of, sf_pdma_dt_ids);
@@ -586,7 +636,7 @@ static struct platform_driver sf_pdma_driver = {
.remove = sf_pdma_remove,
.driver = {
.name = "sf-pdma",
- .of_match_table = of_match_ptr(sf_pdma_dt_ids),
+ .of_match_table = sf_pdma_dt_ids,
},
};