Add support for HDMA NATIVE, as long the IP design has set the compatible register map parameter-HDMA_NATIVE, which allows compatibility for native HDMA register configuration. The HDMA Hyper-DMA IP is an enhancement of the eDMA embedded-DMA IP. And the native HDMA registers are different from eDMA, so this patch add support for HDMA NATIVE mode Signed-off-by: Cai Huoqing <cai.huoqing@xxxxxxxxx> --- drivers/dma/dw-edma/Makefile | 4 +- drivers/dma/dw-edma/dw-edma-core.c | 116 ++++++++--- drivers/dma/dw-edma/dw-edma-pcie.c | 5 +- drivers/dma/dw-edma/dw-edma-v0-core.c | 3 + drivers/dma/dw-edma/dw-hdma-v0-core.c | 236 +++++++++++++++++++++++ drivers/dma/dw-edma/dw-hdma-v0-core.h | 27 +++ drivers/dma/dw-edma/dw-hdma-v0-debugfs.c | 167 ++++++++++++++++ drivers/dma/dw-edma/dw-hdma-v0-debugfs.h | 25 +++ drivers/dma/dw-edma/dw-hdma-v0-regs.h | 94 +++++++++ include/linux/dma/edma.h | 3 +- 10 files changed, 646 insertions(+), 34 deletions(-) create mode 100644 drivers/dma/dw-edma/dw-hdma-v0-core.c create mode 100644 drivers/dma/dw-edma/dw-hdma-v0-core.h create mode 100644 drivers/dma/dw-edma/dw-hdma-v0-debugfs.c create mode 100644 drivers/dma/dw-edma/dw-hdma-v0-debugfs.h create mode 100644 drivers/dma/dw-edma/dw-hdma-v0-regs.h diff --git a/drivers/dma/dw-edma/Makefile b/drivers/dma/dw-edma/Makefile index 8d45c0d5689d..95c5ae760a64 100644 --- a/drivers/dma/dw-edma/Makefile +++ b/drivers/dma/dw-edma/Makefile @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DW_EDMA) += dw-edma.o -dw-edma-$(CONFIG_DEBUG_FS) := dw-edma-v0-debugfs.o +dw-edma-$(CONFIG_DEBUG_FS) := dw-edma-v0-debugfs.o \ + dw-hdma-v0-debugfs.o dw-edma-objs := dw-edma-core.o \ + dw-hdma-v0-core.o \ dw-edma-v0-core.o $(dw-edma-y) obj-$(CONFIG_DW_EDMA_PCIE) += dw-edma-pcie.o diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index 07f756479663..f0e44cb810d0 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -19,6 +19,7 @@ #include "dw-edma-core.h" #include "dw-edma-v0-core.h" +#include "dw-hdma-v0-core.h" #include "../dmaengine.h" #include "../virt-dma.h" @@ -176,6 +177,7 @@ static void dw_edma_start_transfer(struct dw_edma_chan *chan) struct dw_edma_chunk *child; struct dw_edma_desc *desc; struct virt_dma_desc *vd; + struct dw_edma *dw = chan->dw; vd = vchan_next_desc(&chan->vc); if (!vd) @@ -190,7 +192,11 @@ static void dw_edma_start_transfer(struct dw_edma_chan *chan) if (!child) return; - dw_edma_v0_core_start(child, !desc->xfer_sz); + if (dw->chip->mf == EDMA_MF_HDMA_NATIVE) + dw_hdma_v0_core_start(child, !desc->xfer_sz); + else + dw_edma_v0_core_start(child, !desc->xfer_sz); + desc->xfer_sz += child->ll_region.sz; dw_edma_free_burst(child); list_del(&child->list); @@ -248,8 +254,15 @@ static int dw_edma_device_resume(struct dma_chan *dchan) static int dw_edma_device_terminate_all(struct dma_chan *dchan) { struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + struct dw_edma *dw = chan->dw; + enum dma_status status; int err = 0; + if (dw->chip->mf == EDMA_MF_HDMA_NATIVE) + status = dw_hdma_v0_core_ch_status(chan); + else + status = dw_edma_v0_core_ch_status(chan); + if (!chan->configured) { /* Do nothing */ } else if (chan->status == EDMA_ST_PAUSE) { @@ -257,7 +270,7 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan) chan->configured = false; } else if (chan->status == EDMA_ST_IDLE) { chan->configured = false; - } else if (dw_edma_v0_core_ch_status(chan) == DMA_COMPLETE) { + } else if (status == DMA_COMPLETE) { /* * The channel is in a false BUSY state, probably didn't * receive or lost an interrupt @@ -557,11 +570,15 @@ dw_edma_device_prep_interleaved_dma(struct dma_chan *dchan, static void dw_edma_done_interrupt(struct dw_edma_chan *chan) { + struct dw_edma *dw = chan->dw; struct dw_edma_desc *desc; struct virt_dma_desc *vd; unsigned long flags; - dw_edma_v0_core_clear_done_int(chan); + if (dw->chip->mf == EDMA_MF_HDMA_NATIVE) + dw_hdma_v0_core_clear_done_int(chan); + else + dw_edma_v0_core_clear_done_int(chan); spin_lock_irqsave(&chan->vc.lock, flags); vd = vchan_next_desc(&chan->vc); @@ -601,9 +618,13 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan) static void dw_edma_abort_interrupt(struct dw_edma_chan *chan) { struct virt_dma_desc *vd; + struct dw_edma *dw = chan->dw; unsigned long flags; - dw_edma_v0_core_clear_abort_int(chan); + if (dw->chip->mf == EDMA_MF_HDMA_NATIVE) + dw_hdma_v0_core_clear_abort_int(chan); + else + dw_edma_v0_core_clear_abort_int(chan); spin_lock_irqsave(&chan->vc.lock, flags); vd = vchan_next_desc(&chan->vc); @@ -620,9 +641,8 @@ static irqreturn_t dw_edma_interrupt(int irq, void *data, bool write) { struct dw_edma_irq *dw_irq = data; struct dw_edma *dw = dw_irq->dw; - unsigned long total, pos, val; + unsigned long total, pos, val, mask; unsigned long off; - u32 mask; if (write) { total = dw->wr_ch_cnt; @@ -634,24 +654,36 @@ static irqreturn_t dw_edma_interrupt(int irq, void *data, bool write) mask = dw_irq->rd_mask; } - val = dw_edma_v0_core_status_done_int(dw, write ? - EDMA_DIR_WRITE : - EDMA_DIR_READ); - val &= mask; - for_each_set_bit(pos, &val, total) { - struct dw_edma_chan *chan = &dw->chan[pos + off]; + if (dw->chip->mf == EDMA_MF_HDMA_NATIVE) { + for_each_set_bit(pos, &mask, total) { + struct dw_edma_chan *chan = &dw->chan[pos + off]; - dw_edma_done_interrupt(chan); - } + val = dw_hdma_v0_core_status_int(chan); + if (dw_hdma_v0_core_check_done_int(val)) + dw_edma_done_interrupt(chan); + if (dw_hdma_v0_core_check_abort_int(val)) + dw_edma_abort_interrupt(chan); + } + } else { + val = dw_edma_v0_core_status_done_int(dw, write ? + EDMA_DIR_WRITE : + EDMA_DIR_READ); + val &= mask; + for_each_set_bit(pos, &val, total) { + struct dw_edma_chan *chan = &dw->chan[pos + off]; + + dw_edma_done_interrupt(chan); + } - val = dw_edma_v0_core_status_abort_int(dw, write ? - EDMA_DIR_WRITE : - EDMA_DIR_READ); - val &= mask; - for_each_set_bit(pos, &val, total) { - struct dw_edma_chan *chan = &dw->chan[pos + off]; + val = dw_edma_v0_core_status_abort_int(dw, write ? + EDMA_DIR_WRITE : + EDMA_DIR_READ); + val &= mask; + for_each_set_bit(pos, &val, total) { + struct dw_edma_chan *chan = &dw->chan[pos + off]; - dw_edma_abort_interrupt(chan); + dw_edma_abort_interrupt(chan); + } } return IRQ_HANDLED; @@ -794,7 +826,10 @@ static int dw_edma_channel_setup(struct dw_edma *dw, bool write, dt_region->sz = chip->dt_region_rd[j].sz; } - dw_edma_v0_core_device_config(chan); + if (dw->chip->mf == EDMA_MF_HDMA_NATIVE) + dw_hdma_v0_core_device_config(chan); + else + dw_edma_v0_core_device_config(chan); } /* Set DMA channel capabilities */ @@ -937,13 +972,23 @@ int dw_edma_probe(struct dw_edma_chip *chip) raw_spin_lock_init(&dw->lock); - dw->wr_ch_cnt = min_t(u16, chip->ll_wr_cnt, - dw_edma_v0_core_ch_count(dw, EDMA_DIR_WRITE)); - dw->wr_ch_cnt = min_t(u16, dw->wr_ch_cnt, EDMA_MAX_WR_CH); + if (chip->mf == EDMA_MF_HDMA_NATIVE) { + dw->wr_ch_cnt = min_t(u16, chip->ll_wr_cnt, + dw_hdma_v0_core_ch_count(dw, EDMA_DIR_WRITE)); + dw->wr_ch_cnt = min_t(u16, dw->wr_ch_cnt, EDMA_MAX_WR_CH); - dw->rd_ch_cnt = min_t(u16, chip->ll_rd_cnt, - dw_edma_v0_core_ch_count(dw, EDMA_DIR_READ)); - dw->rd_ch_cnt = min_t(u16, dw->rd_ch_cnt, EDMA_MAX_RD_CH); + dw->rd_ch_cnt = min_t(u16, chip->ll_rd_cnt, + dw_hdma_v0_core_ch_count(dw, EDMA_DIR_READ)); + dw->rd_ch_cnt = min_t(u16, dw->rd_ch_cnt, EDMA_MAX_RD_CH); + } else { + dw->wr_ch_cnt = min_t(u16, chip->ll_wr_cnt, + dw_edma_v0_core_ch_count(dw, EDMA_DIR_WRITE)); + dw->wr_ch_cnt = min_t(u16, dw->wr_ch_cnt, EDMA_MAX_WR_CH); + + dw->rd_ch_cnt = min_t(u16, chip->ll_rd_cnt, + dw_edma_v0_core_ch_count(dw, EDMA_DIR_READ)); + dw->rd_ch_cnt = min_t(u16, dw->rd_ch_cnt, EDMA_MAX_RD_CH); + } if (!dw->wr_ch_cnt && !dw->rd_ch_cnt) return -EINVAL; @@ -960,7 +1005,10 @@ int dw_edma_probe(struct dw_edma_chip *chip) snprintf(dw->name, sizeof(dw->name), "dw-edma-core:%d", chip->id); /* Disable eDMA, only to establish the ideal initial conditions */ - dw_edma_v0_core_off(dw); + if (chip->mf == EDMA_MF_HDMA_NATIVE) + dw_hdma_v0_core_off(dw); + else + dw_edma_v0_core_off(dw); /* Request IRQs */ err = dw_edma_irq_request(dw, &wr_alloc, &rd_alloc); @@ -981,7 +1029,10 @@ int dw_edma_probe(struct dw_edma_chip *chip) pm_runtime_enable(dev); /* Turn debugfs on */ - dw_edma_v0_core_debugfs_on(dw); + if (chip->mf == EDMA_MF_HDMA_NATIVE) + dw_hdma_v0_core_debugfs_on(dw); + else + dw_edma_v0_core_debugfs_on(dw); chip->dw = dw; @@ -1028,7 +1079,10 @@ int dw_edma_remove(struct dw_edma_chip *chip) } /* Turn debugfs off */ - dw_edma_v0_core_debugfs_off(dw); + if (chip->mf == EDMA_MF_HDMA_NATIVE) + dw_hdma_v0_core_debugfs_off(dw); + else + dw_edma_v0_core_debugfs_off(dw); return 0; } diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c index d6b5e2463884..7cd2e045325d 100644 --- a/drivers/dma/dw-edma/dw-edma-pcie.c +++ b/drivers/dma/dw-edma/dw-edma-pcie.c @@ -121,7 +121,8 @@ static void dw_edma_pcie_get_vsec_dma_data(struct pci_dev *pdev, map = FIELD_GET(DW_PCIE_VSEC_DMA_MAP, val); if (map != EDMA_MF_EDMA_LEGACY && map != EDMA_MF_EDMA_UNROLL && - map != EDMA_MF_HDMA_COMPAT) + map != EDMA_MF_HDMA_COMPAT && + map != EDMA_MF_HDMA_NATIVE) return; pdata->mf = map; @@ -277,6 +278,8 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, pci_dbg(pdev, "Version:\teDMA Unroll (0x%x)\n", chip->mf); else if (chip->mf == EDMA_MF_HDMA_COMPAT) pci_dbg(pdev, "Version:\tHDMA Compatible (0x%x)\n", chip->mf); + else if (chip->mf == EDMA_MF_HDMA_NATIVE) + pci_dbg(pdev, "Version:\tHDMA Native (0x%x)\n", chip->mf); else pci_dbg(pdev, "Version:\tUnknown (0x%x)\n", chip->mf); diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c index 77e6cfe52e0a..167a2c4144dd 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.c +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -12,6 +12,9 @@ #include "dw-edma-v0-core.h" #include "dw-edma-v0-regs.h" #include "dw-edma-v0-debugfs.h" +#include "dw-hdma-v0-core.h" +#include "dw-hdma-v0-regs.h" +#include "dw-hdma-v0-debugfs.h" enum dw_edma_control { DW_EDMA_V0_CB = BIT(0), diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c new file mode 100644 index 000000000000..cd4746d98839 --- /dev/null +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 Cai Huoqing + * Synopsys DesignWare HDMA v0 core + */ + +#include <linux/bitfield.h> + +#include "dw-edma-core.h" +#include "dw-edma-v0-core.h" +#include "dw-edma-v0-regs.h" +#include "dw-hdma-v0-core.h" +#include "dw-hdma-v0-regs.h" +#include "dw-hdma-v0-debugfs.h" + +enum dw_hdma_control { + DW_HDMA_V0_CB = BIT(0), + DW_HDMA_V0_TCB = BIT(1), + DW_HDMA_V0_LLP = BIT(2), + DW_HDMA_V0_LIE = BIT(3), + DW_HDMA_V0_RIE = BIT(4), + DW_HDMA_V0_CCS = BIT(8), + DW_HDMA_V0_LLE = BIT(9), +}; +static inline struct dw_hdma_v0_regs __iomem *__dw_regs(struct dw_edma *dw) +{ + return dw->chip->reg_base; +} + +static inline struct dw_hdma_v0_ch_regs __iomem * +__dw_ch_regs(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch) +{ + + if (dir == EDMA_DIR_WRITE) + return &(__dw_regs(dw)->ch[ch].wr); + else + return &(__dw_regs(dw)->ch[ch].rd); +} + +#define SET_CH_32(dw, dir, ch, name, value) \ + writel(value, &(__dw_ch_regs(dw, dir, ch)->name)) + +#define GET_CH_32(dw, dir, ch, name) \ + readl(&(__dw_ch_regs(dw, dir, ch)->name)) + +#define SET_BOTH_CH_32(dw, ch, name, value) \ + do { \ + writel(value, &(__dw_ch_regs(dw, EDMA_DIR_WRITE, ch)->name)); \ + writel(value, &(__dw_ch_regs(dw, EDMA_DIR_READ, ch)->name)); \ + } while (0) + +#define SET_LL_32(ll, value) \ + writel(value, ll) + +/* HDMA management callbacks */ +void dw_hdma_v0_core_off(struct dw_edma *dw) +{ + int id; + + for (id = 0; id < HDMA_V0_MAX_NR_CH; id++) { + SET_BOTH_CH_32(dw, id, int_setup, + HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK); + SET_BOTH_CH_32(dw, id, int_clear, + HDMA_V0_STOP_INT_MASK & HDMA_V0_ABORT_INT_MASK); + SET_BOTH_CH_32(dw, id, ch_en, 0); + } +} + +u16 dw_hdma_v0_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir) +{ + u32 num_ch = 0; + int id; + + for (id = 0; id < HDMA_V0_MAX_NR_CH; id++) { + if (GET_CH_32(dw, id, dir, ch_en) & BIT(0)) + num_ch++; + } + + if (num_ch > HDMA_V0_MAX_NR_CH) + num_ch = HDMA_V0_MAX_NR_CH; + + return (u16)num_ch; +} + +enum dma_status dw_hdma_v0_core_ch_status(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + u32 tmp; + + tmp = FIELD_GET(HDMA_V0_CH_STATUS_MASK, + GET_CH_32(dw, chan->id, chan->dir, ch_stat)); + + if (tmp == 1) + return DMA_IN_PROGRESS; + else if (tmp == 3) + return DMA_COMPLETE; + else + return DMA_ERROR; +} + +void dw_hdma_v0_core_clear_done_int(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + SET_CH_32(dw, chan->dir, chan->id, int_clear, HDMA_V0_STOP_INT_MASK); +} + +void dw_hdma_v0_core_clear_abort_int(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + SET_CH_32(dw, chan->dir, chan->id, int_clear, HDMA_V0_ABORT_INT_MASK); +} + +u32 dw_hdma_v0_core_status_int(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + return GET_CH_32(dw, chan->dir, chan->id, int_stat); +} + +u32 dw_hdma_v0_core_check_done_int(u32 val) +{ + return (FIELD_GET(HDMA_V0_STOP_INT_MASK, val) == BIT(0)); +} + +u32 dw_hdma_v0_core_check_abort_int(u32 val) +{ + return (FIELD_GET(HDMA_V0_ABORT_INT_MASK, val) == BIT(0)); +} + +static void dw_hdma_v0_core_write_chunk(struct dw_edma_chunk *chunk) +{ + struct dw_edma_burst *child; + struct dw_edma_chan *chan = chunk->chan; + struct dw_edma_v0_lli __iomem *lli; + struct dw_edma_v0_llp __iomem *llp; + u32 control = 0, i = 0; + int j; + + lli = chunk->ll_region.vaddr; + + if (chunk->cb) + control = DW_HDMA_V0_CB; + + j = chunk->bursts_alloc; + list_for_each_entry(child, &chunk->burst->list, list) { + j--; + if (!j) { + control |= DW_HDMA_V0_LIE; + if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL)) + control |= DW_HDMA_V0_RIE; + } + /* Channel control */ + SET_LL_32(&lli[i].control, control); + /* Transfer size */ + SET_LL_32(&lli[i].transfer_size, child->sz); + /* SAR */ + SET_LL_32(&lli[i].sar.lsb, lower_32_bits(child->sar)); + SET_LL_32(&lli[i].sar.msb, upper_32_bits(child->sar)); + /* DAR */ + SET_LL_32(&lli[i].dar.lsb, lower_32_bits(child->dar)); + SET_LL_32(&lli[i].dar.msb, upper_32_bits(child->dar)); + i++; + } + + llp = (void __iomem *)&lli[i]; + control = DW_HDMA_V0_LLP | DW_HDMA_V0_TCB; + if (!chunk->cb) + control |= DW_HDMA_V0_CB; + + /* Channel control */ + SET_LL_32(&llp->control, control); + /* Linked list */ + SET_LL_32(&llp->llp.lsb, lower_32_bits(chunk->ll_region.paddr)); + SET_LL_32(&llp->llp.msb, upper_32_bits(chunk->ll_region.paddr)); +} + +void dw_hdma_v0_core_start(struct dw_edma_chunk *chunk, bool first) +{ + struct dw_edma_chan *chan = chunk->chan; + struct dw_edma *dw = chan->dw; + u32 tmp; + + dw_hdma_v0_core_write_chunk(chunk); + + if (first) { + /* Enable engine */ + SET_CH_32(dw, chan->dir, chan->id, ch_en, BIT(0)); + /* Interrupt enable&unmask - done, abort */ + tmp = GET_CH_32(dw, chan->dir, chan->id, int_setup); + tmp |= HDMA_V0_STOP_INT_MASK; + tmp |= HDMA_V0_ABORT_INT_MASK; + tmp |= HDMA_V0_LOCAL_ABORT_INT_EN; + tmp |= HDMA_V0_LOCAL_STOP_INT_EN; + SET_CH_32(dw, chan->dir, chan->id, int_setup, tmp); + /* Channel control */ + SET_CH_32(dw, chan->dir, chan->id, control1, HDMA_V0_LINKLIST_EN); + /* Linked list */ + /* llp is not aligned on 64bit -> keep 32bit accesses */ + SET_CH_32(dw, chan->dir, chan->id, llp.lsb, + lower_32_bits(chunk->ll_region.paddr)); + SET_CH_32(dw, chan->dir, chan->id, llp.msb, + upper_32_bits(chunk->ll_region.paddr)); + } + /* Doorbell */ + SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START); +} + +int dw_hdma_v0_core_device_config(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + /* MSI done addr - low, high */ + SET_CH_32(dw, chan->dir, chan->id, msi_stop.lsb, chan->msi.address_lo); + SET_CH_32(dw, chan->dir, chan->id, msi_stop.msb, chan->msi.address_hi); + /* MSI abort addr - low, high */ + SET_CH_32(dw, chan->dir, chan->id, msi_abort.lsb, chan->msi.address_lo); + SET_CH_32(dw, chan->dir, chan->id, msi_abort.msb, chan->msi.address_hi); + SET_CH_32(dw, chan->dir, chan->id, msi_abort.msb, chan->msi.address_hi); + /* config MSI data */ + SET_CH_32(dw, chan->dir, chan->id, msi_msgdata, chan->msi.data); + + return 0; +} + +/* eDMA debugfs callbacks */ +void dw_hdma_v0_core_debugfs_on(struct dw_edma *dw) +{ + dw_hdma_v0_debugfs_on(dw); +} + +void dw_hdma_v0_core_debugfs_off(struct dw_edma *dw) +{ + dw_hdma_v0_debugfs_off(dw); +} diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.h b/drivers/dma/dw-edma/dw-hdma-v0-core.h new file mode 100644 index 000000000000..61d3a6d9d024 --- /dev/null +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2022 Cai Huoqing + * Synopsys DesignWare eDMA v0 core + */ + +#ifndef _DW_HDMA_V0_CORE_H +#define _DW_HDMA_V0_CORE_H + +#include <linux/dma/edma.h> + +/* HDMA management callbacks */ +void dw_hdma_v0_core_off(struct dw_edma *chan); +u16 dw_hdma_v0_core_ch_count(struct dw_edma *chan, enum dw_edma_dir dir); +enum dma_status dw_hdma_v0_core_ch_status(struct dw_edma_chan *chan); +void dw_hdma_v0_core_clear_done_int(struct dw_edma_chan *chan); +void dw_hdma_v0_core_clear_abort_int(struct dw_edma_chan *chan); +u32 dw_hdma_v0_core_status_int(struct dw_edma_chan *chan); +u32 dw_hdma_v0_core_check_done_int(u32 val); +u32 dw_hdma_v0_core_check_abort_int(u32 val); +void dw_hdma_v0_core_start(struct dw_edma_chunk *chunk, bool first); +int dw_hdma_v0_core_device_config(struct dw_edma_chan *chan); +/* HDMA debug fs callbacks */ +void dw_hdma_v0_core_debugfs_on(struct dw_edma *dw); +void dw_hdma_v0_core_debugfs_off(struct dw_edma *dw); + +#endif /* _DW_HDMA_V0_CORE_H */ diff --git a/drivers/dma/dw-edma/dw-hdma-v0-debugfs.c b/drivers/dma/dw-edma/dw-hdma-v0-debugfs.c new file mode 100644 index 000000000000..2f1ddac9513b --- /dev/null +++ b/drivers/dma/dw-edma/dw-hdma-v0-debugfs.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 Cai Huoqing + * Synopsys DesignWare HDMA v0 debugfs + */ + +#include <linux/debugfs.h> +#include <linux/bitfield.h> + +#include "dw-hdma-v0-debugfs.h" +#include "dw-hdma-v0-regs.h" +#include "dw-edma-core.h" + +#define REGS_ADDR(name) \ + ((void __force *)®s->name) + +#define REGISTER(name) \ + { #name, REGS_ADDR(name) } + +#define WRITE_STR "write" +#define READ_STR "read" +#define CHANNEL_STR "channel" +#define REGISTERS_STR "registers" + +static struct dw_edma *dw; +static struct dw_hdma_v0_regs __iomem *regs; +struct debugfs_entries { + const char *name; + dma_addr_t *reg; +}; + +static int dw_hdma_debugfs_u32_get(void *data, u64 *val) +{ + void __iomem *reg = (void __force __iomem *)data; + *val = readl(reg); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_hdma_debugfs_u32_get, NULL, "0x%08llx\n"); + +static void dw_hdma_debugfs_create_x32(const struct debugfs_entries entries[], + int nr_entries, struct dentry *dir) +{ + int i; + + for (i = 0; i < nr_entries; i++) { + if (!debugfs_create_file_unsafe(entries[i].name, 0444, dir, + entries[i].reg, &fops_x32)) + break; + } +} + +static void dw_hdma_debugfs_regs_ch(struct dw_hdma_v0_ch_regs __iomem *regs, + struct dentry *dir) +{ + int nr_entries; + const struct debugfs_entries debugfs_regs[] = { + REGISTER(ch_en), + REGISTER(doorbell), + REGISTER(llp.lsb), + REGISTER(llp.msb), + REGISTER(cycle_sync), + REGISTER(transfer_size), + REGISTER(sar.lsb), + REGISTER(sar.msb), + REGISTER(dar.lsb), + REGISTER(dar.msb), + REGISTER(control1), + REGISTER(ch_stat), + REGISTER(int_stat), + REGISTER(int_setup), + REGISTER(int_clear), + REGISTER(msi_stop.lsb), + REGISTER(msi_stop.msb), + REGISTER(msi_abort.lsb), + REGISTER(msi_abort.msb), + REGISTER(msi_msgdata), + }; + + nr_entries = ARRAY_SIZE(debugfs_regs); + dw_hdma_debugfs_create_x32(debugfs_regs, nr_entries, dir); +} + +static void dw_hdma_debugfs_regs_wr(struct dentry *dir) +{ + struct dentry *regs_dir, *ch_dir; + int i; + char name[16]; + + regs_dir = debugfs_create_dir(WRITE_STR, dir); + if (!regs_dir) + return; + + for (i = 0; i < dw->wr_ch_cnt; i++) { + snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i); + + ch_dir = debugfs_create_dir(name, regs_dir); + if (!ch_dir) + return; + + dw_hdma_debugfs_regs_ch(®s->ch[i].wr, ch_dir); + } +} + +static void dw_hdma_debugfs_regs_rd(struct dentry *dir) +{ + struct dentry *regs_dir, *ch_dir; + int i; + char name[16]; + + regs_dir = debugfs_create_dir(READ_STR, dir); + if (!regs_dir) + return; + + for (i = 0; i < dw->rd_ch_cnt; i++) { + snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i); + + ch_dir = debugfs_create_dir(name, regs_dir); + if (!ch_dir) + return; + + dw_hdma_debugfs_regs_ch(®s->ch[i].rd, ch_dir); + } +} + +static void dw_hdma_debugfs_regs(void) +{ + struct dentry *regs_dir; + + regs_dir = debugfs_create_dir(REGISTERS_STR, dw->debugfs); + if (!regs_dir) + return; + + dw_hdma_debugfs_regs_wr(regs_dir); + dw_hdma_debugfs_regs_rd(regs_dir); +} + +void dw_hdma_v0_debugfs_on(struct dw_edma *_dw) +{ + dw = _dw; + if (!dw) + return; + + regs = dw->chip->reg_base; + if (!regs) + return; + + dw->debugfs = debugfs_create_dir(dw->name, NULL); + if (!dw->debugfs) + return; + + debugfs_create_u32("mf", 0444, dw->debugfs, &dw->chip->mf); + debugfs_create_u16("wr_ch_cnt", 0444, dw->debugfs, &dw->wr_ch_cnt); + debugfs_create_u16("rd_ch_cnt", 0444, dw->debugfs, &dw->rd_ch_cnt); + + dw_hdma_debugfs_regs(); +} + +void dw_hdma_v0_debugfs_off(struct dw_edma *_dw) +{ + dw = _dw; + if (!dw) + return; + + debugfs_remove_recursive(dw->debugfs); + dw->debugfs = NULL; +} diff --git a/drivers/dma/dw-edma/dw-hdma-v0-debugfs.h b/drivers/dma/dw-edma/dw-hdma-v0-debugfs.h new file mode 100644 index 000000000000..53bc7e93f4a0 --- /dev/null +++ b/drivers/dma/dw-edma/dw-hdma-v0-debugfs.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2022 Cai Huoqing + * Synopsys DesignWare HDMA v0 debugfs + */ + +#ifndef _DW_HDMA_V0_DEBUG_FS_H +#define _DW_HDMA_V0_DEBUG_FS_H + +#include <linux/dma/edma.h> + +#ifdef CONFIG_DEBUG_FS +void dw_hdma_v0_debugfs_on(struct dw_edma *dw); +void dw_hdma_v0_debugfs_off(struct dw_edma *dw); +#else +static inline void dw_hdma_v0_debugfs_on(struct dw_edma *dw) +{ +} + +static inline void dw_hdma_v0_debugfs_off(struct dw_edma *dw) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +#endif /* _DW_HDMA_V0_DEBUG_FS_H */ diff --git a/drivers/dma/dw-edma/dw-hdma-v0-regs.h b/drivers/dma/dw-edma/dw-hdma-v0-regs.h new file mode 100644 index 000000000000..2a2a3620d809 --- /dev/null +++ b/drivers/dma/dw-edma/dw-hdma-v0-regs.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2022 Cai Huoqing + * Synopsys DesignWare HDMA v0 reg + */ + +#ifndef _DW_HDMA_V0_REGS_H +#define _DW_HDMA_V0_REGS_H + +#include <linux/dmaengine.h> + +#define HDMA_V0_MAX_NR_CH 8 +#define HDMA_V0_LOCAL_ABORT_INT_EN BIT(6) +#define HDMA_V0_REMOTE_ABORT_INT_EN BIT(5) +#define HDMA_V0_LOCAL_STOP_INT_EN BIT(4) +#define HDMA_V0_REMOTEL_STOP_INT_EN BIT(3) +#define HDMA_V0_ABORT_INT_MASK BIT(2) +#define HDMA_V0_STOP_INT_MASK BIT(0) +#define HDMA_V0_LINKLIST_EN BIT(0) +#define HDMA_V0_DOORBELL_START BIT(0) +#define HDMA_V0_CH_STATUS_MASK GENMASK(1, 0) + +struct dw_hdma_v0_ch_regs { + u32 ch_en; /* 0x0000 */ + u32 doorbell; /* 0x0004 */ + u32 prefetch; /* 0x0008 */ + u32 handshake; /* 0x000c */ + union { + u64 reg; /* 0x0010..0x0014 */ + struct { + u32 lsb; /* 0x0010 */ + u32 msb; /* 0x0014 */ + }; + } llp; + u32 cycle_sync; /* 0x0018 */ + u32 transfer_size; /* 0x001c */ + union { + u64 reg; /* 0x0020..0x0024 */ + struct { + u32 lsb; /* 0x0020 */ + u32 msb; /* 0x0024 */ + }; + } sar; + union { + u64 reg; /* 0x0028..0x002c */ + struct { + u32 lsb; /* 0x0028 */ + u32 msb; /* 0x002c */ + }; + } dar; + + u32 watermark_en; /* 0x0030 */ + u32 control1; /* 0x0034 */ + u32 func_num; /* 0x0038 */ + u32 qos; /* 0x003c */ + u32 reserved; /* 0x0040..0x007c */ + u32 ch_stat; /* 0x0080 */ + u32 int_stat; /* 0x0084 */ + u32 int_setup; /* 0x0088 */ + u32 int_clear; /* 0x008c */ + union { + u64 reg; /* 0x0090..0x0094 */ + struct { + u32 lsb; /* 0x0090 */ + u32 msb; /* 0x0094 */ + }; + } msi_stop; + union { + u64 reg; /* 0x0098..0x009c */ + struct { + u32 lsb; /* 0x0098 */ + u32 msb; /* 0x009c */ + }; + } msi_watermark; + union { + u64 reg; /* 0x00a0..0x00a4 */ + struct { + u32 lsb; /* 0x00a0 */ + u32 msb; /* 0x00a4 */ + }; + } msi_abort; + u32 msi_msgdata; /* 0x00a8 */ +} __packed; + +struct dw_hdma_v0_ch { + struct dw_hdma_v0_ch_regs wr; /* 0x0000 */ + struct dw_hdma_v0_ch_regs rd; /* 0x0100 */ +} __packed; + +struct dw_hdma_v0_regs { + struct dw_hdma_v0_ch ch[HDMA_V0_MAX_NR_CH]; /* 0x0000..0x0fa8 */ +} __packed; + +#endif /* _DW_HDMA_V0_REGS_H */ diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h index 7d8062e9c544..a379b599c7fa 100644 --- a/include/linux/dma/edma.h +++ b/include/linux/dma/edma.h @@ -30,7 +30,8 @@ struct dw_edma_core_ops { enum dw_edma_map_format { EDMA_MF_EDMA_LEGACY = 0x0, EDMA_MF_EDMA_UNROLL = 0x1, - EDMA_MF_HDMA_COMPAT = 0x5 + EDMA_MF_HDMA_COMPAT = 0x5, + EDMA_MF_HDMA_NATIVE = 0x7 }; /** -- 2.25.1