diff --git a/Documentation/devicetree/bindings/dma/sifive,fu540-c000-pdma.yaml b/Documentation/devicetree/bindings/dma/sifive,fu540-c000-pdma.yaml index 609e38901434a4..b6c49060bc6f3f 100644 --- a/Documentation/devicetree/bindings/dma/sifive,fu540-c000-pdma.yaml +++ b/Documentation/devicetree/bindings/dma/sifive,fu540-c000-pdma.yaml @@ -36,6 +36,7 @@ properties: - enum: - microchip,mpfs-pdma - sifive,fu540-c000-pdma + - sifive,fu740-c000-pdma - const: sifive,pdma0 description: Should be "sifive,-pdma" and "sifive,pdma". diff --git a/arch/riscv/boot/dts/sifive/fu740-c000.dtsi b/arch/riscv/boot/dts/sifive/fu740-c000.dtsi index 6150f3397bff92..30d0d6837c57ed 100644 --- a/arch/riscv/boot/dts/sifive/fu740-c000.dtsi +++ b/arch/riscv/boot/dts/sifive/fu740-c000.dtsi @@ -329,6 +329,15 @@ clocks = <&prci FU740_PRCI_CLK_PCLK>; status = "disabled"; }; + dma: dma-controller@3000000 { + compatible = "sifive,fu740-c000-pdma", "sifive,pdma0"; + reg = <0x0 0x3000000 0x0 0x100000>; + interrupt-parent = <&plic0>; + interrupts = <11>, <12>, <13>, <14>, <15>, <16>, <17>, <18>; + dma-channels = <4>; + clocks = <&prci FU740_PRCI_CLK_PCLK>; + #dma-cells = <1>; + }; pcie@e00000000 { compatible = "sifive,fu740-pcie"; #address-cells = <3>; diff --git a/drivers/dma/sf-pdma/sf-pdma.c b/drivers/dma/sf-pdma/sf-pdma.c index 7ad3c29be14679..70e4afcda52a15 100644 --- a/drivers/dma/sf-pdma/sf-pdma.c +++ b/drivers/dma/sf-pdma/sf-pdma.c @@ -298,33 +298,56 @@ static void sf_pdma_free_desc(struct virt_dma_desc *vdesc) static void sf_pdma_donebh_tasklet(struct tasklet_struct *t) { struct sf_pdma_chan *chan = from_tasklet(chan, t, done_tasklet); + struct sf_pdma_desc *desc; unsigned long flags; - spin_lock_irqsave(&chan->lock, flags); - if (chan->xfer_err) { - chan->retries = MAX_RETRY; - chan->status = DMA_COMPLETE; - chan->xfer_err = false; + spin_lock_irqsave(&chan->vchan.lock, flags); + desc = chan->desc; + if (!desc) { + /* + * The descriptor was already freed (e.g., by terminate_all + * or completion on another CPU). Nothing to do. + */ + spin_unlock_irqrestore(&chan->vchan.lock, flags); + return; } - spin_unlock_irqrestore(&chan->lock, flags); - spin_lock_irqsave(&chan->vchan.lock, flags); - list_del(&chan->desc->vdesc.node); - vchan_cookie_complete(&chan->desc->vdesc); + list_del(&desc->vdesc.node); + vchan_cookie_complete(&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); + + spin_lock_irqsave(&chan->lock, flags); + if (chan->xfer_err) { + chan->retries = MAX_RETRY; + chan->status = DMA_COMPLETE; + chan->xfer_err = false; + } + spin_unlock_irqrestore(&chan->lock, flags); } static void sf_pdma_errbh_tasklet(struct tasklet_struct *t) { struct sf_pdma_chan *chan = from_tasklet(chan, t, err_tasklet); - struct sf_pdma_desc *desc = chan->desc; + struct sf_pdma_desc *desc; unsigned long flags; + spin_lock_irqsave(&chan->vchan.lock, flags); + desc = chan->desc; + if (!desc) { + /* + * The descriptor was already freed (e.g., by terminate_all + * or completion on another CPU). Nothing to do. + */ + spin_unlock_irqrestore(&chan->vchan.lock, flags); + return; + } + spin_unlock_irqrestore(&chan->vchan.lock, flags); + spin_lock_irqsave(&chan->lock, flags); if (chan->retries <= 0) { /* fail to recover */ @@ -346,9 +369,25 @@ 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; u64 residue; + u32 control_reg; spin_lock(&chan->vchan.lock); - writel((readl(regs->ctrl)) & ~PDMA_DONE_STATUS_MASK, regs->ctrl); + control_reg = readl(regs->ctrl); + if (control_reg & PDMA_ERR_STATUS_MASK) { + spin_unlock(&chan->vchan.lock); + return IRQ_HANDLED; + } + + /* + * Check if DONE bit is still set. If not, the error ISR on another + * CPU has already cleared it, so abort to avoid double-processing. + */ + if (!(control_reg & PDMA_DONE_STATUS_MASK)) { + spin_unlock(&chan->vchan.lock); + return IRQ_HANDLED; + } + + writel((control_reg & ~PDMA_DONE_STATUS_MASK), regs->ctrl); residue = readq(regs->residue); if (!residue) { @@ -375,7 +414,7 @@ static irqreturn_t sf_pdma_err_isr(int irq, void *dev_id) struct pdma_regs *regs = &chan->regs; spin_lock(&chan->lock); - writel((readl(regs->ctrl)) & ~PDMA_ERR_STATUS_MASK, regs->ctrl); + writel((readl(regs->ctrl)) & ~(PDMA_DONE_STATUS_MASK | PDMA_ERR_STATUS_MASK), regs->ctrl); spin_unlock(&chan->lock); tasklet_schedule(&chan->err_tasklet); diff --git a/drivers/dma/sf-pdma/sf-pdma.h b/drivers/dma/sf-pdma/sf-pdma.h index 215e07183d7e26..d33551eb2ee816 100644 --- a/drivers/dma/sf-pdma/sf-pdma.h +++ b/drivers/dma/sf-pdma/sf-pdma.h @@ -24,7 +24,7 @@ #define PDMA_MAX_NR_CH 4 -#define PDMA_BASE_ADDR 0x3000000 +#define PDMA_BASE_OFFSET 0x80000 #define PDMA_CHAN_OFFSET 0x1000 /* Register Offset */ @@ -54,7 +54,7 @@ /* Error Recovery */ #define MAX_RETRY 1 -#define SF_PDMA_REG_BASE(ch) (pdma->membase + (PDMA_CHAN_OFFSET * (ch))) +#define SF_PDMA_REG_BASE(ch) (pdma->membase + PDMA_BASE_OFFSET + (PDMA_CHAN_OFFSET * (ch))) struct pdma_regs { /* read-write regs */