Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0
config DW_EDMA
tristate "Synopsys DesignWare eDMA controller driver"
depends on PCI && PCI_MSI
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS
help
Support the Synopsys DesignWare eDMA controller, normally
implemented on endpoints SoCs.
if DW_EDMA
config DW_EDMA_PCIE
tristate "Synopsys DesignWare eDMA PCIe driver"
depends on PCI && PCI_MSI
help
Provides a glue-logic between the Synopsys DesignWare
eDMA controller and an endpoint PCIe device. This also serves
as a reference design to whom desires to use this IP.
endif # DW_EDMA
+9
View File
@@ -0,0 +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-hdma-v0-debugfs.o
dw-edma-objs := dw-edma-core.o \
dw-edma-v0-core.o \
dw-hdma-v0-core.o $(dw-edma-y)
obj-$(CONFIG_DW_EDMA_PCIE) += dw-edma-pcie.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,227 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
* Synopsys DesignWare eDMA core driver
*
* Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
*/
#ifndef _DW_EDMA_CORE_H
#define _DW_EDMA_CORE_H
#include <linux/msi.h>
#include <linux/dma/edma.h>
#include "../virt-dma.h"
#define EDMA_LL_SZ 24
enum dw_edma_dir {
EDMA_DIR_WRITE = 0,
EDMA_DIR_READ
};
enum dw_edma_request {
EDMA_REQ_NONE = 0,
EDMA_REQ_STOP,
EDMA_REQ_PAUSE
};
enum dw_edma_status {
EDMA_ST_IDLE = 0,
EDMA_ST_PAUSE,
EDMA_ST_BUSY
};
enum dw_edma_xfer_type {
EDMA_XFER_SCATTER_GATHER = 0,
EDMA_XFER_CYCLIC,
EDMA_XFER_INTERLEAVED
};
struct dw_edma_chan;
struct dw_edma_chunk;
struct dw_edma_burst {
struct list_head list;
u64 sar;
u64 dar;
u32 sz;
};
struct dw_edma_chunk {
struct list_head list;
struct dw_edma_chan *chan;
struct dw_edma_burst *burst;
u32 bursts_alloc;
u8 cb;
struct dw_edma_region ll_region; /* Linked list */
};
struct dw_edma_desc {
struct virt_dma_desc vd;
struct dw_edma_chan *chan;
struct dw_edma_chunk *chunk;
u32 chunks_alloc;
u32 alloc_sz;
u32 xfer_sz;
};
struct dw_edma_chan {
struct virt_dma_chan vc;
struct dw_edma *dw;
int id;
enum dw_edma_dir dir;
u32 ll_max;
struct msi_msg msi;
enum dw_edma_request request;
enum dw_edma_status status;
u8 configured;
struct dma_slave_config config;
bool non_ll;
};
struct dw_edma_irq {
struct msi_msg msi;
u32 wr_mask;
u32 rd_mask;
struct dw_edma *dw;
};
struct dw_edma {
char name[32];
struct dma_device dma;
u16 wr_ch_cnt;
u16 rd_ch_cnt;
struct dw_edma_irq *irq;
int nr_irqs;
struct dw_edma_chan *chan;
raw_spinlock_t lock; /* Protect v0 shared registers */
struct dw_edma_chip *chip;
const struct dw_edma_core_ops *core;
};
typedef void (*dw_edma_handler_t)(struct dw_edma_chan *);
struct dw_edma_core_ops {
void (*off)(struct dw_edma *dw);
u16 (*ch_count)(struct dw_edma *dw, enum dw_edma_dir dir);
enum dma_status (*ch_status)(struct dw_edma_chan *chan);
irqreturn_t (*handle_int)(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
dw_edma_handler_t done, dw_edma_handler_t abort);
void (*start)(struct dw_edma_chunk *chunk, bool first);
void (*ch_config)(struct dw_edma_chan *chan);
void (*debugfs_on)(struct dw_edma *dw);
void (*ack_emulated_irq)(struct dw_edma *dw);
resource_size_t (*db_offset)(struct dw_edma *dw);
};
struct dw_edma_sg {
struct scatterlist *sgl;
unsigned int len;
};
struct dw_edma_cyclic {
dma_addr_t paddr;
size_t len;
size_t cnt;
};
struct dw_edma_transfer {
struct dma_chan *dchan;
union dw_edma_xfer {
struct dw_edma_sg sg;
struct dw_edma_cyclic cyclic;
struct dma_interleaved_template *il;
} xfer;
enum dma_transfer_direction direction;
unsigned long flags;
enum dw_edma_xfer_type type;
};
static inline
struct dw_edma_chan *vc2dw_edma_chan(struct virt_dma_chan *vc)
{
return container_of(vc, struct dw_edma_chan, vc);
}
static inline
struct dw_edma_chan *dchan2dw_edma_chan(struct dma_chan *dchan)
{
return vc2dw_edma_chan(to_virt_chan(dchan));
}
static inline
void dw_edma_core_off(struct dw_edma *dw)
{
dw->core->off(dw);
}
static inline
u16 dw_edma_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir)
{
return dw->core->ch_count(dw, dir);
}
static inline
enum dma_status dw_edma_core_ch_status(struct dw_edma_chan *chan)
{
return chan->dw->core->ch_status(chan);
}
static inline irqreturn_t
dw_edma_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
dw_edma_handler_t done, dw_edma_handler_t abort)
{
return dw_irq->dw->core->handle_int(dw_irq, dir, done, abort);
}
static inline
void dw_edma_core_start(struct dw_edma *dw, struct dw_edma_chunk *chunk, bool first)
{
dw->core->start(chunk, first);
}
static inline
void dw_edma_core_ch_config(struct dw_edma_chan *chan)
{
chan->dw->core->ch_config(chan);
}
static inline
void dw_edma_core_debugfs_on(struct dw_edma *dw)
{
dw->core->debugfs_on(dw);
}
static inline int dw_edma_core_ack_emulated_irq(struct dw_edma *dw)
{
if (!dw->core->ack_emulated_irq)
return -EOPNOTSUPP;
dw->core->ack_emulated_irq(dw);
return 0;
}
static inline resource_size_t
dw_edma_core_db_offset(struct dw_edma *dw)
{
return dw->core->db_offset(dw);
}
#endif /* _DW_EDMA_CORE_H */
@@ -0,0 +1,584 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
* Synopsys DesignWare eDMA PCIe driver
*
* Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/dma/edma.h>
#include <linux/pci-epf.h>
#include <linux/msi.h>
#include <linux/bitfield.h>
#include <linux/sizes.h>
#include "dw-edma-core.h"
/* Synopsys */
#define DW_PCIE_SYNOPSYS_VSEC_DMA_ID 0x6
#define DW_PCIE_SYNOPSYS_VSEC_DMA_BAR GENMASK(10, 8)
#define DW_PCIE_SYNOPSYS_VSEC_DMA_MAP GENMASK(2, 0)
#define DW_PCIE_SYNOPSYS_VSEC_DMA_WR_CH GENMASK(9, 0)
#define DW_PCIE_SYNOPSYS_VSEC_DMA_RD_CH GENMASK(25, 16)
/* AMD MDB (Xilinx) specific defines */
#define PCI_DEVICE_ID_XILINX_B054 0xb054
#define PCI_DEVICE_ID_XILINX_B00F 0xb00f
#define DW_PCIE_XILINX_MDB_VSEC_DMA_ID 0x6
#define DW_PCIE_XILINX_MDB_VSEC_ID 0x20
#define DW_PCIE_XILINX_MDB_VSEC_DMA_BAR GENMASK(10, 8)
#define DW_PCIE_XILINX_MDB_VSEC_DMA_MAP GENMASK(2, 0)
#define DW_PCIE_XILINX_MDB_VSEC_DMA_WR_CH GENMASK(9, 0)
#define DW_PCIE_XILINX_MDB_VSEC_DMA_RD_CH GENMASK(25, 16)
#define DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_HIGH 0xc
#define DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_LOW 0x8
#define DW_PCIE_XILINX_MDB_INVALID_ADDR (~0ULL)
#define DW_PCIE_XILINX_MDB_LL_OFF_GAP 0x200000
#define DW_PCIE_XILINX_MDB_LL_SIZE 0x800
#define DW_PCIE_XILINX_MDB_DT_OFF_GAP 0x100000
#define DW_PCIE_XILINX_MDB_DT_SIZE 0x800
#define DW_BLOCK(a, b, c) \
{ \
.bar = a, \
.off = b, \
.sz = c, \
},
struct dw_edma_block {
enum pci_barno bar;
off_t off;
size_t sz;
};
struct dw_edma_pcie_data {
/* eDMA registers location */
struct dw_edma_block rg;
/* eDMA memory linked list location */
struct dw_edma_block ll_wr[EDMA_MAX_WR_CH];
struct dw_edma_block ll_rd[EDMA_MAX_RD_CH];
/* eDMA memory data location */
struct dw_edma_block dt_wr[EDMA_MAX_WR_CH];
struct dw_edma_block dt_rd[EDMA_MAX_RD_CH];
/* Other */
enum dw_edma_map_format mf;
u8 irqs;
u16 wr_ch_cnt;
u16 rd_ch_cnt;
u64 devmem_phys_off;
};
static const struct dw_edma_pcie_data snps_edda_data = {
/* eDMA registers location */
.rg.bar = BAR_0,
.rg.off = 0x00001000, /* 4 Kbytes */
.rg.sz = 0x00002000, /* 8 Kbytes */
/* eDMA memory linked list location */
.ll_wr = {
/* Channel 0 - BAR 2, offset 0 Mbytes, size 2 Kbytes */
DW_BLOCK(BAR_2, 0x00000000, 0x00000800)
/* Channel 1 - BAR 2, offset 2 Mbytes, size 2 Kbytes */
DW_BLOCK(BAR_2, 0x00200000, 0x00000800)
},
.ll_rd = {
/* Channel 0 - BAR 2, offset 4 Mbytes, size 2 Kbytes */
DW_BLOCK(BAR_2, 0x00400000, 0x00000800)
/* Channel 1 - BAR 2, offset 6 Mbytes, size 2 Kbytes */
DW_BLOCK(BAR_2, 0x00600000, 0x00000800)
},
/* eDMA memory data location */
.dt_wr = {
/* Channel 0 - BAR 2, offset 8 Mbytes, size 2 Kbytes */
DW_BLOCK(BAR_2, 0x00800000, 0x00000800)
/* Channel 1 - BAR 2, offset 9 Mbytes, size 2 Kbytes */
DW_BLOCK(BAR_2, 0x00900000, 0x00000800)
},
.dt_rd = {
/* Channel 0 - BAR 2, offset 10 Mbytes, size 2 Kbytes */
DW_BLOCK(BAR_2, 0x00a00000, 0x00000800)
/* Channel 1 - BAR 2, offset 11 Mbytes, size 2 Kbytes */
DW_BLOCK(BAR_2, 0x00b00000, 0x00000800)
},
/* Other */
.mf = EDMA_MF_EDMA_UNROLL,
.irqs = 1,
.wr_ch_cnt = 2,
.rd_ch_cnt = 2,
};
static const struct dw_edma_pcie_data xilinx_mdb_data = {
/* MDB registers location */
.rg.bar = BAR_0,
.rg.off = SZ_4K, /* 4 Kbytes */
.rg.sz = SZ_8K, /* 8 Kbytes */
/* Other */
.mf = EDMA_MF_HDMA_NATIVE,
.irqs = 1,
.wr_ch_cnt = 8,
.rd_ch_cnt = 8,
};
static const struct dw_edma_pcie_data xilinx_cpm6_dma_data = {
/* MDB registers location */
.rg.bar = BAR_0,
.rg.off = SZ_4K, /* 4 Kbytes */
.rg.sz = SZ_8K, /* 8 Kbytes */
/* Other */
.mf = EDMA_MF_HDMA_NATIVE,
.irqs = 1,
.wr_ch_cnt = 8,
.rd_ch_cnt = 8,
};
static void dw_edma_set_chan_region_offset(struct dw_edma_pcie_data *pdata,
enum pci_barno bar, off_t start_off,
off_t ll_off_gap, size_t ll_size,
off_t dt_off_gap, size_t dt_size)
{
u16 wr_ch = pdata->wr_ch_cnt;
u16 rd_ch = pdata->rd_ch_cnt;
off_t off;
u16 i;
off = start_off;
/* Write channel LL region */
for (i = 0; i < wr_ch; i++) {
pdata->ll_wr[i].bar = bar;
pdata->ll_wr[i].off = off;
pdata->ll_wr[i].sz = ll_size;
off += ll_off_gap;
}
/* Read channel LL region */
for (i = 0; i < rd_ch; i++) {
pdata->ll_rd[i].bar = bar;
pdata->ll_rd[i].off = off;
pdata->ll_rd[i].sz = ll_size;
off += ll_off_gap;
}
/* Write channel data region */
for (i = 0; i < wr_ch; i++) {
pdata->dt_wr[i].bar = bar;
pdata->dt_wr[i].off = off;
pdata->dt_wr[i].sz = dt_size;
off += dt_off_gap;
}
/* Read channel data region */
for (i = 0; i < rd_ch; i++) {
pdata->dt_rd[i].bar = bar;
pdata->dt_rd[i].off = off;
pdata->dt_rd[i].sz = dt_size;
off += dt_off_gap;
}
}
static int dw_edma_pcie_irq_vector(struct device *dev, unsigned int nr)
{
return pci_irq_vector(to_pci_dev(dev), nr);
}
static u64 dw_edma_pcie_address(struct device *dev, phys_addr_t cpu_addr)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct pci_bus_region region;
struct resource res = {
.flags = IORESOURCE_MEM,
.start = cpu_addr,
.end = cpu_addr,
};
pcibios_resource_to_bus(pdev->bus, &region, &res);
return region.start;
}
static const struct dw_edma_plat_ops dw_edma_pcie_plat_ops = {
.irq_vector = dw_edma_pcie_irq_vector,
.pci_address = dw_edma_pcie_address,
};
static void dw_edma_pcie_get_synopsys_dma_data(struct pci_dev *pdev,
struct dw_edma_pcie_data *pdata)
{
u32 val, map;
u16 vsec;
u64 off;
vsec = pci_find_vsec_capability(pdev, PCI_VENDOR_ID_SYNOPSYS,
DW_PCIE_SYNOPSYS_VSEC_DMA_ID);
if (!vsec)
return;
pci_read_config_dword(pdev, vsec + PCI_VNDR_HEADER, &val);
if (PCI_VNDR_HEADER_REV(val) != 0x00 ||
PCI_VNDR_HEADER_LEN(val) != 0x18)
return;
pci_dbg(pdev, "Detected Synopsys PCIe Vendor-Specific Extended Capability DMA\n");
pci_read_config_dword(pdev, vsec + 0x8, &val);
map = FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_MAP, val);
if (map != EDMA_MF_EDMA_LEGACY &&
map != EDMA_MF_EDMA_UNROLL &&
map != EDMA_MF_HDMA_COMPAT &&
map != EDMA_MF_HDMA_NATIVE)
return;
pdata->mf = map;
pdata->rg.bar = FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_BAR, val);
pci_read_config_dword(pdev, vsec + 0xc, &val);
pdata->wr_ch_cnt = min_t(u16, pdata->wr_ch_cnt,
FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_WR_CH, val));
pdata->rd_ch_cnt = min_t(u16, pdata->rd_ch_cnt,
FIELD_GET(DW_PCIE_SYNOPSYS_VSEC_DMA_RD_CH, val));
pci_read_config_dword(pdev, vsec + 0x14, &val);
off = val;
pci_read_config_dword(pdev, vsec + 0x10, &val);
off <<= 32;
off |= val;
pdata->rg.off = off;
}
static void dw_edma_pcie_get_xilinx_dma_data(struct pci_dev *pdev,
struct dw_edma_pcie_data *pdata)
{
u32 val, map;
u16 vsec;
u64 off;
pdata->devmem_phys_off = DW_PCIE_XILINX_MDB_INVALID_ADDR;
vsec = pci_find_vsec_capability(pdev, PCI_VENDOR_ID_XILINX,
DW_PCIE_XILINX_MDB_VSEC_DMA_ID);
if (!vsec)
return;
pci_read_config_dword(pdev, vsec + PCI_VNDR_HEADER, &val);
if (PCI_VNDR_HEADER_REV(val) != 0x00 ||
PCI_VNDR_HEADER_LEN(val) != 0x18)
return;
pci_dbg(pdev, "Detected Xilinx PCIe Vendor-Specific Extended Capability DMA\n");
pci_read_config_dword(pdev, vsec + 0x8, &val);
map = FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_MAP, val);
if (map != EDMA_MF_HDMA_NATIVE)
return;
pdata->mf = map;
pdata->rg.bar = FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_BAR, val);
pci_read_config_dword(pdev, vsec + 0xc, &val);
pdata->wr_ch_cnt = min(pdata->wr_ch_cnt,
FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_WR_CH, val));
pdata->rd_ch_cnt = min(pdata->rd_ch_cnt,
FIELD_GET(DW_PCIE_XILINX_MDB_VSEC_DMA_RD_CH, val));
pci_read_config_dword(pdev, vsec + 0x14, &val);
off = val;
pci_read_config_dword(pdev, vsec + 0x10, &val);
off <<= 32;
off |= val;
pdata->rg.off = off;
vsec = pci_find_vsec_capability(pdev, PCI_VENDOR_ID_XILINX,
DW_PCIE_XILINX_MDB_VSEC_ID);
if (!vsec)
return;
pci_read_config_dword(pdev,
vsec + DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_HIGH,
&val);
off = val;
pci_read_config_dword(pdev,
vsec + DW_PCIE_XILINX_MDB_DEVMEM_OFF_REG_LOW,
&val);
off <<= 32;
off |= val;
pdata->devmem_phys_off = off;
}
static u64 dw_edma_get_phys_addr(struct pci_dev *pdev,
struct dw_edma_pcie_data *pdata,
enum pci_barno bar)
{
if (pdev->vendor == PCI_VENDOR_ID_XILINX)
return pdata->devmem_phys_off;
return pci_bus_address(pdev, bar);
}
static int dw_edma_pcie_probe(struct pci_dev *pdev,
const struct pci_device_id *pid)
{
struct dw_edma_pcie_data *pdata = (void *)pid->driver_data;
struct device *dev = &pdev->dev;
struct dw_edma_chip *chip;
int err, nr_irqs;
int i, mask;
bool non_ll = false;
if (!pdata)
return -ENODEV;
struct dw_edma_pcie_data *vsec_data __free(kfree) =
kmalloc_obj(*vsec_data);
if (!vsec_data)
return -ENOMEM;
/* Enable PCI device */
err = pcim_enable_device(pdev);
if (err) {
pci_err(pdev, "enabling device failed\n");
return err;
}
memcpy(vsec_data, pdata, sizeof(struct dw_edma_pcie_data));
/*
* Tries to find if exists a PCIe Vendor-Specific Extended Capability
* for the DMA, if one exists, then reconfigures it.
*/
dw_edma_pcie_get_synopsys_dma_data(pdev, vsec_data);
if (pdev->vendor == PCI_VENDOR_ID_XILINX) {
dw_edma_pcie_get_xilinx_dma_data(pdev, vsec_data);
/*
* There is no valid address found for the LL memory
* space on the device side. In the absence of LL base
* address use the non-LL mode or simple mode supported by
* the HDMA IP.
*/
if (vsec_data->devmem_phys_off == DW_PCIE_XILINX_MDB_INVALID_ADDR)
non_ll = true;
/*
* Configure the channel LL and data blocks if number of
* channels enabled in VSEC capability are more than the
* channels configured in xilinx_mdb_data.
*/
if (!non_ll)
dw_edma_set_chan_region_offset(vsec_data, BAR_2, 0,
DW_PCIE_XILINX_MDB_LL_OFF_GAP,
DW_PCIE_XILINX_MDB_LL_SIZE,
DW_PCIE_XILINX_MDB_DT_OFF_GAP,
DW_PCIE_XILINX_MDB_DT_SIZE);
}
/* Mapping PCI BAR regions */
mask = BIT(vsec_data->rg.bar);
for (i = 0; i < vsec_data->wr_ch_cnt; i++) {
mask |= BIT(vsec_data->ll_wr[i].bar);
mask |= BIT(vsec_data->dt_wr[i].bar);
}
for (i = 0; i < vsec_data->rd_ch_cnt; i++) {
mask |= BIT(vsec_data->ll_rd[i].bar);
mask |= BIT(vsec_data->dt_rd[i].bar);
}
err = pcim_iomap_regions(pdev, mask, pci_name(pdev));
if (err) {
pci_err(pdev, "eDMA BAR I/O remapping failed\n");
return err;
}
pci_set_master(pdev);
/* DMA configuration */
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (err) {
pci_err(pdev, "DMA mask 64 set failed\n");
return err;
}
/* Data structure allocation */
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
/* IRQs allocation */
nr_irqs = pci_alloc_irq_vectors(pdev, 1, vsec_data->irqs,
PCI_IRQ_MSI | PCI_IRQ_MSIX);
if (nr_irqs < 1) {
pci_err(pdev, "fail to alloc IRQ vector (number of IRQs=%u)\n",
nr_irqs);
return -EPERM;
}
/* Data structure initialization */
chip->dev = dev;
chip->mf = vsec_data->mf;
chip->nr_irqs = nr_irqs;
chip->ops = &dw_edma_pcie_plat_ops;
chip->cfg_non_ll = non_ll;
chip->ll_wr_cnt = vsec_data->wr_ch_cnt;
chip->ll_rd_cnt = vsec_data->rd_ch_cnt;
chip->reg_base = pcim_iomap_table(pdev)[vsec_data->rg.bar];
if (!chip->reg_base)
return -ENOMEM;
for (i = 0; i < chip->ll_wr_cnt && !non_ll; i++) {
struct dw_edma_region *ll_region = &chip->ll_region_wr[i];
struct dw_edma_region *dt_region = &chip->dt_region_wr[i];
struct dw_edma_block *ll_block = &vsec_data->ll_wr[i];
struct dw_edma_block *dt_block = &vsec_data->dt_wr[i];
ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar];
if (!ll_region->vaddr.io)
return -ENOMEM;
ll_region->vaddr.io += ll_block->off;
ll_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data,
ll_block->bar);
ll_region->paddr += ll_block->off;
ll_region->sz = ll_block->sz;
dt_region->vaddr.io = pcim_iomap_table(pdev)[dt_block->bar];
if (!dt_region->vaddr.io)
return -ENOMEM;
dt_region->vaddr.io += dt_block->off;
dt_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data,
dt_block->bar);
dt_region->paddr += dt_block->off;
dt_region->sz = dt_block->sz;
}
for (i = 0; i < chip->ll_rd_cnt && !non_ll; i++) {
struct dw_edma_region *ll_region = &chip->ll_region_rd[i];
struct dw_edma_region *dt_region = &chip->dt_region_rd[i];
struct dw_edma_block *ll_block = &vsec_data->ll_rd[i];
struct dw_edma_block *dt_block = &vsec_data->dt_rd[i];
ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar];
if (!ll_region->vaddr.io)
return -ENOMEM;
ll_region->vaddr.io += ll_block->off;
ll_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data,
ll_block->bar);
ll_region->paddr += ll_block->off;
ll_region->sz = ll_block->sz;
dt_region->vaddr.io = pcim_iomap_table(pdev)[dt_block->bar];
if (!dt_region->vaddr.io)
return -ENOMEM;
dt_region->vaddr.io += dt_block->off;
dt_region->paddr = dw_edma_get_phys_addr(pdev, vsec_data,
dt_block->bar);
dt_region->paddr += dt_block->off;
dt_region->sz = dt_block->sz;
}
/* Debug info */
if (chip->mf == EDMA_MF_EDMA_LEGACY)
pci_dbg(pdev, "Version:\teDMA Port Logic (0x%x)\n", chip->mf);
else if (chip->mf == EDMA_MF_EDMA_UNROLL)
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);
pci_dbg(pdev, "Registers:\tBAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p)\n",
vsec_data->rg.bar, vsec_data->rg.off, vsec_data->rg.sz,
chip->reg_base);
for (i = 0; i < chip->ll_wr_cnt; i++) {
pci_dbg(pdev, "L. List:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
i, vsec_data->ll_wr[i].bar,
vsec_data->ll_wr[i].off, chip->ll_region_wr[i].sz,
chip->ll_region_wr[i].vaddr.io, &chip->ll_region_wr[i].paddr);
pci_dbg(pdev, "Data:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
i, vsec_data->dt_wr[i].bar,
vsec_data->dt_wr[i].off, chip->dt_region_wr[i].sz,
chip->dt_region_wr[i].vaddr.io, &chip->dt_region_wr[i].paddr);
}
for (i = 0; i < chip->ll_rd_cnt; i++) {
pci_dbg(pdev, "L. List:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
i, vsec_data->ll_rd[i].bar,
vsec_data->ll_rd[i].off, chip->ll_region_rd[i].sz,
chip->ll_region_rd[i].vaddr.io, &chip->ll_region_rd[i].paddr);
pci_dbg(pdev, "Data:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n",
i, vsec_data->dt_rd[i].bar,
vsec_data->dt_rd[i].off, chip->dt_region_rd[i].sz,
chip->dt_region_rd[i].vaddr.io, &chip->dt_region_rd[i].paddr);
}
pci_dbg(pdev, "Nr. IRQs:\t%u\n", chip->nr_irqs);
/* Validating if PCI interrupts were enabled */
if (!pci_dev_msi_enabled(pdev)) {
pci_err(pdev, "enable interrupt failed\n");
return -EPERM;
}
/* Starting eDMA driver */
err = dw_edma_probe(chip);
if (err) {
pci_err(pdev, "eDMA probe failed\n");
return err;
}
/* Saving data structure reference */
pci_set_drvdata(pdev, chip);
return 0;
}
static void dw_edma_pcie_remove(struct pci_dev *pdev)
{
struct dw_edma_chip *chip = pci_get_drvdata(pdev);
int err;
/* Stopping eDMA driver */
err = dw_edma_remove(chip);
if (err)
pci_warn(pdev, "can't remove device properly: %d\n", err);
/* Freeing IRQs */
pci_free_irq_vectors(pdev);
}
static const struct pci_device_id dw_edma_pcie_id_table[] = {
{ PCI_DEVICE_DATA(SYNOPSYS, EDDA, &snps_edda_data) },
{ PCI_VDEVICE(XILINX, PCI_DEVICE_ID_XILINX_B054),
(kernel_ulong_t)&xilinx_mdb_data },
{ PCI_VDEVICE(XILINX, PCI_DEVICE_ID_XILINX_B00F),
.driver_data = (kernel_ulong_t)&xilinx_cpm6_dma_data },
{ }
};
MODULE_DEVICE_TABLE(pci, dw_edma_pcie_id_table);
static struct pci_driver dw_edma_pcie_driver = {
.name = "dw-edma-pcie",
.id_table = dw_edma_pcie_id_table,
.probe = dw_edma_pcie_probe,
.remove = dw_edma_pcie_remove,
};
module_pci_driver(dw_edma_pcie_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Synopsys DesignWare eDMA PCIe driver");
MODULE_AUTHOR("Gustavo Pimentel <gustavo.pimentel@synopsys.com>");
@@ -0,0 +1,552 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
* Synopsys DesignWare eDMA v0 core
*
* Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
*/
#include <linux/bitfield.h>
#include <linux/irqreturn.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include "dw-edma-core.h"
#include "dw-edma-v0-core.h"
#include "dw-edma-v0-regs.h"
#include "dw-edma-v0-debugfs.h"
enum dw_edma_control {
DW_EDMA_V0_CB = BIT(0),
DW_EDMA_V0_TCB = BIT(1),
DW_EDMA_V0_LLP = BIT(2),
DW_EDMA_V0_LIE = BIT(3),
DW_EDMA_V0_RIE = BIT(4),
DW_EDMA_V0_CCS = BIT(8),
DW_EDMA_V0_LLE = BIT(9),
};
static inline struct dw_edma_v0_regs __iomem *__dw_regs(struct dw_edma *dw)
{
return dw->chip->reg_base;
}
#define SET_32(dw, name, value) \
writel(value, &(__dw_regs(dw)->name))
#define GET_32(dw, name) \
readl(&(__dw_regs(dw)->name))
#define SET_RW_32(dw, dir, name, value) \
do { \
if ((dir) == EDMA_DIR_WRITE) \
SET_32(dw, wr_##name, value); \
else \
SET_32(dw, rd_##name, value); \
} while (0)
#define GET_RW_32(dw, dir, name) \
((dir) == EDMA_DIR_WRITE \
? GET_32(dw, wr_##name) \
: GET_32(dw, rd_##name))
#define SET_BOTH_32(dw, name, value) \
do { \
SET_32(dw, wr_##name, value); \
SET_32(dw, rd_##name, value); \
} while (0)
#define SET_64(dw, name, value) \
writeq(value, &(__dw_regs(dw)->name))
#define GET_64(dw, name) \
readq(&(__dw_regs(dw)->name))
#define SET_RW_64(dw, dir, name, value) \
do { \
if ((dir) == EDMA_DIR_WRITE) \
SET_64(dw, wr_##name, value); \
else \
SET_64(dw, rd_##name, value); \
} while (0)
#define GET_RW_64(dw, dir, name) \
((dir) == EDMA_DIR_WRITE \
? GET_64(dw, wr_##name) \
: GET_64(dw, rd_##name))
#define SET_BOTH_64(dw, name, value) \
do { \
SET_64(dw, wr_##name, value); \
SET_64(dw, rd_##name, value); \
} while (0)
#define SET_COMPAT(dw, name, value) \
writel(value, &(__dw_regs(dw)->type.unroll.name))
#define SET_RW_COMPAT(dw, dir, name, value) \
do { \
if ((dir) == EDMA_DIR_WRITE) \
SET_COMPAT(dw, wr_##name, value); \
else \
SET_COMPAT(dw, rd_##name, value); \
} while (0)
static inline struct dw_edma_v0_ch_regs __iomem *
__dw_ch_regs(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch)
{
if (dw->chip->mf == EDMA_MF_EDMA_LEGACY)
return &(__dw_regs(dw)->type.legacy.ch);
if (dir == EDMA_DIR_WRITE)
return &__dw_regs(dw)->type.unroll.ch[ch].wr;
return &__dw_regs(dw)->type.unroll.ch[ch].rd;
}
static inline void writel_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch,
u32 value, void __iomem *addr)
{
if (dw->chip->mf == EDMA_MF_EDMA_LEGACY) {
u32 viewport_sel;
unsigned long flags;
raw_spin_lock_irqsave(&dw->lock, flags);
viewport_sel = FIELD_PREP(EDMA_V0_VIEWPORT_MASK, ch);
if (dir == EDMA_DIR_READ)
viewport_sel |= BIT(31);
writel(viewport_sel,
&(__dw_regs(dw)->type.legacy.viewport_sel));
writel(value, addr);
raw_spin_unlock_irqrestore(&dw->lock, flags);
} else {
writel(value, addr);
}
}
static inline u32 readl_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch,
const void __iomem *addr)
{
u32 value;
if (dw->chip->mf == EDMA_MF_EDMA_LEGACY) {
u32 viewport_sel;
unsigned long flags;
raw_spin_lock_irqsave(&dw->lock, flags);
viewport_sel = FIELD_PREP(EDMA_V0_VIEWPORT_MASK, ch);
if (dir == EDMA_DIR_READ)
viewport_sel |= BIT(31);
writel(viewport_sel,
&(__dw_regs(dw)->type.legacy.viewport_sel));
value = readl(addr);
raw_spin_unlock_irqrestore(&dw->lock, flags);
} else {
value = readl(addr);
}
return value;
}
#define SET_CH_32(dw, dir, ch, name, value) \
writel_ch(dw, dir, ch, value, &(__dw_ch_regs(dw, dir, ch)->name))
#define GET_CH_32(dw, dir, ch, name) \
readl_ch(dw, dir, ch, &(__dw_ch_regs(dw, dir, ch)->name))
/* eDMA management callbacks */
static void dw_edma_v0_core_off(struct dw_edma *dw)
{
SET_BOTH_32(dw, int_mask,
EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK);
SET_BOTH_32(dw, int_clear,
EDMA_V0_DONE_INT_MASK | EDMA_V0_ABORT_INT_MASK);
SET_BOTH_32(dw, engine_en, 0);
}
static u16 dw_edma_v0_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir)
{
u32 num_ch;
if (dir == EDMA_DIR_WRITE)
num_ch = FIELD_GET(EDMA_V0_WRITE_CH_COUNT_MASK,
GET_32(dw, ctrl));
else
num_ch = FIELD_GET(EDMA_V0_READ_CH_COUNT_MASK,
GET_32(dw, ctrl));
if (num_ch > EDMA_V0_MAX_NR_CH)
num_ch = EDMA_V0_MAX_NR_CH;
return (u16)num_ch;
}
static enum dma_status dw_edma_v0_core_ch_status(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
u32 tmp;
tmp = FIELD_GET(EDMA_V0_CH_STATUS_MASK,
GET_CH_32(dw, chan->dir, chan->id, ch_control1));
if (tmp == 1)
return DMA_IN_PROGRESS;
else if (tmp == 3)
return DMA_COMPLETE;
else
return DMA_ERROR;
}
static void dw_edma_v0_core_clear_done_int(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
SET_RW_32(dw, chan->dir, int_clear,
FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)));
}
static void dw_edma_v0_core_clear_abort_int(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
SET_RW_32(dw, chan->dir, int_clear,
FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)));
}
static u32 dw_edma_v0_core_status_done_int(struct dw_edma *dw, enum dw_edma_dir dir)
{
return FIELD_GET(EDMA_V0_DONE_INT_MASK,
GET_RW_32(dw, dir, int_status));
}
static u32 dw_edma_v0_core_status_abort_int(struct dw_edma *dw, enum dw_edma_dir dir)
{
return FIELD_GET(EDMA_V0_ABORT_INT_MASK,
GET_RW_32(dw, dir, int_status));
}
static irqreturn_t
dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
dw_edma_handler_t done, dw_edma_handler_t abort)
{
struct dw_edma *dw = dw_irq->dw;
unsigned long total, pos, val;
irqreturn_t ret = IRQ_NONE;
struct dw_edma_chan *chan;
unsigned long off;
u32 mask;
if (dir == EDMA_DIR_WRITE) {
total = dw->wr_ch_cnt;
off = 0;
mask = dw_irq->wr_mask;
} else {
total = dw->rd_ch_cnt;
off = dw->wr_ch_cnt;
mask = dw_irq->rd_mask;
}
val = dw_edma_v0_core_status_done_int(dw, dir);
val &= mask;
for_each_set_bit(pos, &val, total) {
chan = &dw->chan[pos + off];
dw_edma_v0_core_clear_done_int(chan);
done(chan);
ret = IRQ_HANDLED;
}
val = dw_edma_v0_core_status_abort_int(dw, dir);
val &= mask;
for_each_set_bit(pos, &val, total) {
chan = &dw->chan[pos + off];
dw_edma_v0_core_clear_abort_int(chan);
abort(chan);
ret = IRQ_HANDLED;
}
return ret;
}
static void dw_edma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
u32 control, u32 size, u64 sar, u64 dar)
{
ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli);
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_edma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
lli->control = control;
lli->transfer_size = size;
lli->sar.reg = sar;
lli->dar.reg = dar;
} else {
struct dw_edma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs;
writel(control, &lli->control);
writel(size, &lli->transfer_size);
writeq(sar, &lli->sar.reg);
writeq(dar, &lli->dar.reg);
}
}
static void dw_edma_v0_write_ll_link(struct dw_edma_chunk *chunk,
int i, u32 control, u64 pointer)
{
ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli);
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_edma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
llp->control = control;
llp->llp.reg = pointer;
} else {
struct dw_edma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs;
writel(control, &llp->control);
writeq(pointer, &llp->llp.reg);
}
}
static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
{
struct dw_edma_burst *child;
struct dw_edma_chan *chan = chunk->chan;
u32 control = 0, i = 0;
int j;
if (chunk->cb)
control = DW_EDMA_V0_CB;
j = chunk->bursts_alloc;
list_for_each_entry(child, &chunk->burst->list, list) {
j--;
if (!j) {
control |= DW_EDMA_V0_LIE;
if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
control |= DW_EDMA_V0_RIE;
}
dw_edma_v0_write_ll_data(chunk, i++, control, child->sz,
child->sar, child->dar);
}
control = DW_EDMA_V0_LLP | DW_EDMA_V0_TCB;
if (!chunk->cb)
control |= DW_EDMA_V0_CB;
dw_edma_v0_write_ll_link(chunk, i, control, chunk->ll_region.paddr);
}
static void dw_edma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
{
/*
* In case of remote eDMA engine setup, the DW PCIe RP/EP internal
* configuration registers and application memory are normally accessed
* over different buses. Ensure LL-data reaches the memory before the
* doorbell register is toggled by issuing the dummy-read from the remote
* LL memory in a hope that the MRd TLP will return only after the
* last MWr TLP is completed
*/
if (!(chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
readl(chunk->ll_region.vaddr.io);
}
static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
{
struct dw_edma_chan *chan = chunk->chan;
struct dw_edma *dw = chan->dw;
unsigned long flags;
u32 tmp;
dw_edma_v0_core_write_chunk(chunk);
if (first) {
/* Enable engine */
SET_RW_32(dw, chan->dir, engine_en, BIT(0));
if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) {
switch (chan->id) {
case 0:
SET_RW_COMPAT(dw, chan->dir, ch0_pwr_en,
BIT(0));
break;
case 1:
SET_RW_COMPAT(dw, chan->dir, ch1_pwr_en,
BIT(0));
break;
case 2:
SET_RW_COMPAT(dw, chan->dir, ch2_pwr_en,
BIT(0));
break;
case 3:
SET_RW_COMPAT(dw, chan->dir, ch3_pwr_en,
BIT(0));
break;
case 4:
SET_RW_COMPAT(dw, chan->dir, ch4_pwr_en,
BIT(0));
break;
case 5:
SET_RW_COMPAT(dw, chan->dir, ch5_pwr_en,
BIT(0));
break;
case 6:
SET_RW_COMPAT(dw, chan->dir, ch6_pwr_en,
BIT(0));
break;
case 7:
SET_RW_COMPAT(dw, chan->dir, ch7_pwr_en,
BIT(0));
break;
}
}
/* Interrupt unmask - done, abort */
raw_spin_lock_irqsave(&dw->lock, flags);
tmp = GET_RW_32(dw, chan->dir, int_mask);
tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id));
tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id));
SET_RW_32(dw, chan->dir, int_mask, tmp);
/* Linked list error */
tmp = GET_RW_32(dw, chan->dir, linked_list_err_en);
tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id));
SET_RW_32(dw, chan->dir, linked_list_err_en, tmp);
raw_spin_unlock_irqrestore(&dw->lock, flags);
/* Channel control */
SET_CH_32(dw, chan->dir, chan->id, ch_control1,
(DW_EDMA_V0_CCS | DW_EDMA_V0_LLE));
/* 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));
}
dw_edma_v0_sync_ll_data(chunk);
/* Doorbell */
SET_RW_32(dw, chan->dir, doorbell,
FIELD_PREP(EDMA_V0_DOORBELL_CH_MASK, chan->id));
}
static void dw_edma_v0_core_ch_config(struct dw_edma_chan *chan)
{
struct dw_edma *dw = chan->dw;
u32 tmp = 0;
/* MSI done addr - low, high */
SET_RW_32(dw, chan->dir, done_imwr.lsb, chan->msi.address_lo);
SET_RW_32(dw, chan->dir, done_imwr.msb, chan->msi.address_hi);
/* MSI abort addr - low, high */
SET_RW_32(dw, chan->dir, abort_imwr.lsb, chan->msi.address_lo);
SET_RW_32(dw, chan->dir, abort_imwr.msb, chan->msi.address_hi);
/* MSI data - low, high */
switch (chan->id) {
case 0:
case 1:
tmp = GET_RW_32(dw, chan->dir, ch01_imwr_data);
break;
case 2:
case 3:
tmp = GET_RW_32(dw, chan->dir, ch23_imwr_data);
break;
case 4:
case 5:
tmp = GET_RW_32(dw, chan->dir, ch45_imwr_data);
break;
case 6:
case 7:
tmp = GET_RW_32(dw, chan->dir, ch67_imwr_data);
break;
}
if (chan->id & BIT(0)) {
/* Channel odd {1, 3, 5, 7} */
tmp &= EDMA_V0_CH_EVEN_MSI_DATA_MASK;
tmp |= FIELD_PREP(EDMA_V0_CH_ODD_MSI_DATA_MASK,
chan->msi.data);
} else {
/* Channel even {0, 2, 4, 6} */
tmp &= EDMA_V0_CH_ODD_MSI_DATA_MASK;
tmp |= FIELD_PREP(EDMA_V0_CH_EVEN_MSI_DATA_MASK,
chan->msi.data);
}
switch (chan->id) {
case 0:
case 1:
SET_RW_32(dw, chan->dir, ch01_imwr_data, tmp);
break;
case 2:
case 3:
SET_RW_32(dw, chan->dir, ch23_imwr_data, tmp);
break;
case 4:
case 5:
SET_RW_32(dw, chan->dir, ch45_imwr_data, tmp);
break;
case 6:
case 7:
SET_RW_32(dw, chan->dir, ch67_imwr_data, tmp);
break;
}
}
/* eDMA debugfs callbacks */
static void dw_edma_v0_core_debugfs_on(struct dw_edma *dw)
{
dw_edma_v0_debugfs_on(dw);
}
static void dw_edma_v0_core_ack_emulated_irq(struct dw_edma *dw)
{
/*
* Interrupt emulation may assert the IRQ without setting
* DONE/ABORT status bits. A zero write to INT_CLEAR deasserts the
* emulated IRQ, while being a no-op for real interrupts.
*/
SET_BOTH_32(dw, int_clear, 0);
}
static resource_size_t dw_edma_v0_core_db_offset(struct dw_edma *dw)
{
/*
* rd_int_status is chosen arbitrarily, but wr_int_status would be
* equally suitable.
*/
return offsetof(struct dw_edma_v0_regs, rd_int_status);
}
static const struct dw_edma_core_ops dw_edma_v0_core = {
.off = dw_edma_v0_core_off,
.ch_count = dw_edma_v0_core_ch_count,
.ch_status = dw_edma_v0_core_ch_status,
.handle_int = dw_edma_v0_core_handle_int,
.start = dw_edma_v0_core_start,
.ch_config = dw_edma_v0_core_ch_config,
.debugfs_on = dw_edma_v0_core_debugfs_on,
.ack_emulated_irq = dw_edma_v0_core_ack_emulated_irq,
.db_offset = dw_edma_v0_core_db_offset,
};
void dw_edma_v0_core_register(struct dw_edma *dw)
{
dw->core = &dw_edma_v0_core;
}
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
* Synopsys DesignWare eDMA v0 core
*
* Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
*/
#ifndef _DW_EDMA_V0_CORE_H
#define _DW_EDMA_V0_CORE_H
#include <linux/dma/edma.h>
/* eDMA core register */
void dw_edma_v0_core_register(struct dw_edma *dw);
#endif /* _DW_EDMA_V0_CORE_H */
@@ -0,0 +1,292 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
* Synopsys DesignWare eDMA v0 core
*
* Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
*/
#include <linux/debugfs.h>
#include <linux/bitfield.h>
#include "dw-edma-v0-debugfs.h"
#include "dw-edma-v0-regs.h"
#include "dw-edma-core.h"
#define REGS_ADDR(dw, name) \
({ \
struct dw_edma_v0_regs __iomem *__regs = (dw)->chip->reg_base; \
\
(void __iomem *)&__regs->name; \
})
#define REGS_CH_ADDR(dw, name, _dir, _ch) \
({ \
struct dw_edma_v0_ch_regs __iomem *__ch_regs; \
\
if ((dw)->chip->mf == EDMA_MF_EDMA_LEGACY) \
__ch_regs = REGS_ADDR(dw, type.legacy.ch); \
else if (_dir == EDMA_DIR_READ) \
__ch_regs = REGS_ADDR(dw, type.unroll.ch[_ch].rd); \
else \
__ch_regs = REGS_ADDR(dw, type.unroll.ch[_ch].wr); \
\
(void __iomem *)&__ch_regs->name; \
})
#define REGISTER(dw, name) \
{ dw, #name, REGS_ADDR(dw, name) }
#define CTX_REGISTER(dw, name, dir, ch) \
{ dw, #name, REGS_CH_ADDR(dw, name, dir, ch), dir, ch }
#define WR_REGISTER(dw, name) \
{ dw, #name, REGS_ADDR(dw, wr_##name) }
#define RD_REGISTER(dw, name) \
{ dw, #name, REGS_ADDR(dw, rd_##name) }
#define WR_REGISTER_LEGACY(dw, name) \
{ dw, #name, REGS_ADDR(dw, type.legacy.wr_##name) }
#define RD_REGISTER_LEGACY(name) \
{ dw, #name, REGS_ADDR(dw, type.legacy.rd_##name) }
#define WR_REGISTER_UNROLL(dw, name) \
{ dw, #name, REGS_ADDR(dw, type.unroll.wr_##name) }
#define RD_REGISTER_UNROLL(dw, name) \
{ dw, #name, REGS_ADDR(dw, type.unroll.rd_##name) }
#define WRITE_STR "write"
#define READ_STR "read"
#define CHANNEL_STR "channel"
#define REGISTERS_STR "registers"
struct dw_edma_debugfs_entry {
struct dw_edma *dw;
const char *name;
void __iomem *reg;
enum dw_edma_dir dir;
u16 ch;
};
static int dw_edma_debugfs_u32_get(void *data, u64 *val)
{
struct dw_edma_debugfs_entry *entry = data;
struct dw_edma *dw = entry->dw;
void __iomem *reg = entry->reg;
if (dw->chip->mf == EDMA_MF_EDMA_LEGACY &&
reg >= REGS_ADDR(dw, type.legacy.ch)) {
unsigned long flags;
u32 viewport_sel;
viewport_sel = entry->dir == EDMA_DIR_READ ? BIT(31) : 0;
viewport_sel |= FIELD_PREP(EDMA_V0_VIEWPORT_MASK, entry->ch);
raw_spin_lock_irqsave(&dw->lock, flags);
writel(viewport_sel, REGS_ADDR(dw, type.legacy.viewport_sel));
*val = readl(reg);
raw_spin_unlock_irqrestore(&dw->lock, flags);
} else {
*val = readl(reg);
}
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_edma_debugfs_u32_get, NULL, "0x%08llx\n");
static void dw_edma_debugfs_create_x32(struct dw_edma *dw,
const struct dw_edma_debugfs_entry ini[],
int nr_entries, struct dentry *dent)
{
struct dw_edma_debugfs_entry *entries;
int i;
entries = devm_kcalloc(dw->chip->dev, nr_entries, sizeof(*entries),
GFP_KERNEL);
if (!entries)
return;
for (i = 0; i < nr_entries; i++) {
entries[i] = ini[i];
debugfs_create_file_unsafe(entries[i].name, 0444, dent,
&entries[i], &fops_x32);
}
}
static void dw_edma_debugfs_regs_ch(struct dw_edma *dw, enum dw_edma_dir dir,
u16 ch, struct dentry *dent)
{
struct dw_edma_debugfs_entry debugfs_regs[] = {
CTX_REGISTER(dw, ch_control1, dir, ch),
CTX_REGISTER(dw, ch_control2, dir, ch),
CTX_REGISTER(dw, transfer_size, dir, ch),
CTX_REGISTER(dw, sar.lsb, dir, ch),
CTX_REGISTER(dw, sar.msb, dir, ch),
CTX_REGISTER(dw, dar.lsb, dir, ch),
CTX_REGISTER(dw, dar.msb, dir, ch),
CTX_REGISTER(dw, llp.lsb, dir, ch),
CTX_REGISTER(dw, llp.msb, dir, ch),
};
int nr_entries;
nr_entries = ARRAY_SIZE(debugfs_regs);
dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, dent);
}
static noinline_for_stack void
dw_edma_debugfs_regs_wr(struct dw_edma *dw, struct dentry *dent)
{
const struct dw_edma_debugfs_entry debugfs_regs[] = {
/* eDMA global registers */
WR_REGISTER(dw, engine_en),
WR_REGISTER(dw, doorbell),
WR_REGISTER(dw, ch_arb_weight.lsb),
WR_REGISTER(dw, ch_arb_weight.msb),
/* eDMA interrupts registers */
WR_REGISTER(dw, int_status),
WR_REGISTER(dw, int_mask),
WR_REGISTER(dw, int_clear),
WR_REGISTER(dw, err_status),
WR_REGISTER(dw, done_imwr.lsb),
WR_REGISTER(dw, done_imwr.msb),
WR_REGISTER(dw, abort_imwr.lsb),
WR_REGISTER(dw, abort_imwr.msb),
WR_REGISTER(dw, ch01_imwr_data),
WR_REGISTER(dw, ch23_imwr_data),
WR_REGISTER(dw, ch45_imwr_data),
WR_REGISTER(dw, ch67_imwr_data),
WR_REGISTER(dw, linked_list_err_en),
};
const struct dw_edma_debugfs_entry debugfs_unroll_regs[] = {
/* eDMA channel context grouping */
WR_REGISTER_UNROLL(dw, engine_chgroup),
WR_REGISTER_UNROLL(dw, engine_hshake_cnt.lsb),
WR_REGISTER_UNROLL(dw, engine_hshake_cnt.msb),
WR_REGISTER_UNROLL(dw, ch0_pwr_en),
WR_REGISTER_UNROLL(dw, ch1_pwr_en),
WR_REGISTER_UNROLL(dw, ch2_pwr_en),
WR_REGISTER_UNROLL(dw, ch3_pwr_en),
WR_REGISTER_UNROLL(dw, ch4_pwr_en),
WR_REGISTER_UNROLL(dw, ch5_pwr_en),
WR_REGISTER_UNROLL(dw, ch6_pwr_en),
WR_REGISTER_UNROLL(dw, ch7_pwr_en),
};
struct dentry *regs_dent, *ch_dent;
int nr_entries, i;
char name[32];
regs_dent = debugfs_create_dir(WRITE_STR, dent);
nr_entries = ARRAY_SIZE(debugfs_regs);
dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, regs_dent);
if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) {
nr_entries = ARRAY_SIZE(debugfs_unroll_regs);
dw_edma_debugfs_create_x32(dw, debugfs_unroll_regs, nr_entries,
regs_dent);
}
for (i = 0; i < dw->wr_ch_cnt; i++) {
snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i);
ch_dent = debugfs_create_dir(name, regs_dent);
dw_edma_debugfs_regs_ch(dw, EDMA_DIR_WRITE, i, ch_dent);
}
}
static noinline_for_stack void dw_edma_debugfs_regs_rd(struct dw_edma *dw,
struct dentry *dent)
{
const struct dw_edma_debugfs_entry debugfs_regs[] = {
/* eDMA global registers */
RD_REGISTER(dw, engine_en),
RD_REGISTER(dw, doorbell),
RD_REGISTER(dw, ch_arb_weight.lsb),
RD_REGISTER(dw, ch_arb_weight.msb),
/* eDMA interrupts registers */
RD_REGISTER(dw, int_status),
RD_REGISTER(dw, int_mask),
RD_REGISTER(dw, int_clear),
RD_REGISTER(dw, err_status.lsb),
RD_REGISTER(dw, err_status.msb),
RD_REGISTER(dw, linked_list_err_en),
RD_REGISTER(dw, done_imwr.lsb),
RD_REGISTER(dw, done_imwr.msb),
RD_REGISTER(dw, abort_imwr.lsb),
RD_REGISTER(dw, abort_imwr.msb),
RD_REGISTER(dw, ch01_imwr_data),
RD_REGISTER(dw, ch23_imwr_data),
RD_REGISTER(dw, ch45_imwr_data),
RD_REGISTER(dw, ch67_imwr_data),
};
const struct dw_edma_debugfs_entry debugfs_unroll_regs[] = {
/* eDMA channel context grouping */
RD_REGISTER_UNROLL(dw, engine_chgroup),
RD_REGISTER_UNROLL(dw, engine_hshake_cnt.lsb),
RD_REGISTER_UNROLL(dw, engine_hshake_cnt.msb),
RD_REGISTER_UNROLL(dw, ch0_pwr_en),
RD_REGISTER_UNROLL(dw, ch1_pwr_en),
RD_REGISTER_UNROLL(dw, ch2_pwr_en),
RD_REGISTER_UNROLL(dw, ch3_pwr_en),
RD_REGISTER_UNROLL(dw, ch4_pwr_en),
RD_REGISTER_UNROLL(dw, ch5_pwr_en),
RD_REGISTER_UNROLL(dw, ch6_pwr_en),
RD_REGISTER_UNROLL(dw, ch7_pwr_en),
};
struct dentry *regs_dent, *ch_dent;
int nr_entries, i;
char name[32];
regs_dent = debugfs_create_dir(READ_STR, dent);
nr_entries = ARRAY_SIZE(debugfs_regs);
dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, regs_dent);
if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) {
nr_entries = ARRAY_SIZE(debugfs_unroll_regs);
dw_edma_debugfs_create_x32(dw, debugfs_unroll_regs, nr_entries,
regs_dent);
}
for (i = 0; i < dw->rd_ch_cnt; i++) {
snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i);
ch_dent = debugfs_create_dir(name, regs_dent);
dw_edma_debugfs_regs_ch(dw, EDMA_DIR_READ, i, ch_dent);
}
}
static void dw_edma_debugfs_regs(struct dw_edma *dw)
{
const struct dw_edma_debugfs_entry debugfs_regs[] = {
REGISTER(dw, ctrl_data_arb_prior),
REGISTER(dw, ctrl),
};
struct dentry *regs_dent;
int nr_entries;
regs_dent = debugfs_create_dir(REGISTERS_STR, dw->dma.dbg_dev_root);
nr_entries = ARRAY_SIZE(debugfs_regs);
dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, regs_dent);
dw_edma_debugfs_regs_wr(dw, regs_dent);
dw_edma_debugfs_regs_rd(dw, regs_dent);
}
void dw_edma_v0_debugfs_on(struct dw_edma *dw)
{
if (!debugfs_initialized())
return;
debugfs_create_u32("mf", 0444, dw->dma.dbg_dev_root, &dw->chip->mf);
debugfs_create_u16("wr_ch_cnt", 0444, dw->dma.dbg_dev_root, &dw->wr_ch_cnt);
debugfs_create_u16("rd_ch_cnt", 0444, dw->dma.dbg_dev_root, &dw->rd_ch_cnt);
dw_edma_debugfs_regs(dw);
}
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
* Synopsys DesignWare eDMA v0 core
*
* Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
*/
#ifndef _DW_EDMA_V0_DEBUG_FS_H
#define _DW_EDMA_V0_DEBUG_FS_H
#include <linux/dma/edma.h>
#ifdef CONFIG_DEBUG_FS
void dw_edma_v0_debugfs_on(struct dw_edma *dw);
#else
static inline void dw_edma_v0_debugfs_on(struct dw_edma *dw)
{
}
#endif /* CONFIG_DEBUG_FS */
#endif /* _DW_EDMA_V0_DEBUG_FS_H */
@@ -0,0 +1,233 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
* Synopsys DesignWare eDMA v0 core
*
* Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
*/
#ifndef _DW_EDMA_V0_REGS_H
#define _DW_EDMA_V0_REGS_H
#include <linux/dmaengine.h>
#define EDMA_V0_MAX_NR_CH 8
#define EDMA_V0_VIEWPORT_MASK GENMASK(2, 0)
#define EDMA_V0_DONE_INT_MASK GENMASK(7, 0)
#define EDMA_V0_ABORT_INT_MASK GENMASK(23, 16)
#define EDMA_V0_WRITE_CH_COUNT_MASK GENMASK(3, 0)
#define EDMA_V0_READ_CH_COUNT_MASK GENMASK(19, 16)
#define EDMA_V0_CH_STATUS_MASK GENMASK(6, 5)
#define EDMA_V0_DOORBELL_CH_MASK GENMASK(2, 0)
#define EDMA_V0_LINKED_LIST_ERR_MASK GENMASK(7, 0)
#define EDMA_V0_CH_ODD_MSI_DATA_MASK GENMASK(31, 16)
#define EDMA_V0_CH_EVEN_MSI_DATA_MASK GENMASK(15, 0)
struct dw_edma_v0_ch_regs {
u32 ch_control1; /* 0x0000 */
u32 ch_control2; /* 0x0004 */
u32 transfer_size; /* 0x0008 */
union {
u64 reg; /* 0x000c..0x0010 */
struct {
u32 lsb; /* 0x000c */
u32 msb; /* 0x0010 */
};
} sar;
union {
u64 reg; /* 0x0014..0x0018 */
struct {
u32 lsb; /* 0x0014 */
u32 msb; /* 0x0018 */
};
} dar;
union {
u64 reg; /* 0x001c..0x0020 */
struct {
u32 lsb; /* 0x001c */
u32 msb; /* 0x0020 */
};
} llp;
} __packed;
struct dw_edma_v0_ch {
struct dw_edma_v0_ch_regs wr; /* 0x0200 */
u32 padding_1[55]; /* 0x0224..0x02fc */
struct dw_edma_v0_ch_regs rd; /* 0x0300 */
u32 padding_2[55]; /* 0x0324..0x03fc */
} __packed;
struct dw_edma_v0_unroll {
u32 padding_1; /* 0x00f8 */
u32 wr_engine_chgroup; /* 0x0100 */
u32 rd_engine_chgroup; /* 0x0104 */
union {
u64 reg; /* 0x0108..0x010c */
struct {
u32 lsb; /* 0x0108 */
u32 msb; /* 0x010c */
};
} wr_engine_hshake_cnt;
u32 padding_2[2]; /* 0x0110..0x0114 */
union {
u64 reg; /* 0x0120..0x0124 */
struct {
u32 lsb; /* 0x0120 */
u32 msb; /* 0x0124 */
};
} rd_engine_hshake_cnt;
u32 padding_3[2]; /* 0x0120..0x0124 */
u32 wr_ch0_pwr_en; /* 0x0128 */
u32 wr_ch1_pwr_en; /* 0x012c */
u32 wr_ch2_pwr_en; /* 0x0130 */
u32 wr_ch3_pwr_en; /* 0x0134 */
u32 wr_ch4_pwr_en; /* 0x0138 */
u32 wr_ch5_pwr_en; /* 0x013c */
u32 wr_ch6_pwr_en; /* 0x0140 */
u32 wr_ch7_pwr_en; /* 0x0144 */
u32 padding_4[8]; /* 0x0148..0x0164 */
u32 rd_ch0_pwr_en; /* 0x0168 */
u32 rd_ch1_pwr_en; /* 0x016c */
u32 rd_ch2_pwr_en; /* 0x0170 */
u32 rd_ch3_pwr_en; /* 0x0174 */
u32 rd_ch4_pwr_en; /* 0x0178 */
u32 rd_ch5_pwr_en; /* 0x018c */
u32 rd_ch6_pwr_en; /* 0x0180 */
u32 rd_ch7_pwr_en; /* 0x0184 */
u32 padding_5[30]; /* 0x0188..0x01fc */
struct dw_edma_v0_ch ch[EDMA_V0_MAX_NR_CH]; /* 0x0200..0x1120 */
} __packed;
struct dw_edma_v0_legacy {
u32 viewport_sel; /* 0x00f8 */
struct dw_edma_v0_ch_regs ch; /* 0x0100..0x0120 */
} __packed;
struct dw_edma_v0_regs {
/* eDMA global registers */
u32 ctrl_data_arb_prior; /* 0x0000 */
u32 padding_1; /* 0x0004 */
u32 ctrl; /* 0x0008 */
u32 wr_engine_en; /* 0x000c */
u32 wr_doorbell; /* 0x0010 */
u32 padding_2; /* 0x0014 */
union {
u64 reg; /* 0x0018..0x001c */
struct {
u32 lsb; /* 0x0018 */
u32 msb; /* 0x001c */
};
} wr_ch_arb_weight;
u32 padding_3[3]; /* 0x0020..0x0028 */
u32 rd_engine_en; /* 0x002c */
u32 rd_doorbell; /* 0x0030 */
u32 padding_4; /* 0x0034 */
union {
u64 reg; /* 0x0038..0x003c */
struct {
u32 lsb; /* 0x0038 */
u32 msb; /* 0x003c */
};
} rd_ch_arb_weight;
u32 padding_5[3]; /* 0x0040..0x0048 */
/* eDMA interrupts registers */
u32 wr_int_status; /* 0x004c */
u32 padding_6; /* 0x0050 */
u32 wr_int_mask; /* 0x0054 */
u32 wr_int_clear; /* 0x0058 */
u32 wr_err_status; /* 0x005c */
union {
u64 reg; /* 0x0060..0x0064 */
struct {
u32 lsb; /* 0x0060 */
u32 msb; /* 0x0064 */
};
} wr_done_imwr;
union {
u64 reg; /* 0x0068..0x006c */
struct {
u32 lsb; /* 0x0068 */
u32 msb; /* 0x006c */
};
} wr_abort_imwr;
u32 wr_ch01_imwr_data; /* 0x0070 */
u32 wr_ch23_imwr_data; /* 0x0074 */
u32 wr_ch45_imwr_data; /* 0x0078 */
u32 wr_ch67_imwr_data; /* 0x007c */
u32 padding_7[4]; /* 0x0080..0x008c */
u32 wr_linked_list_err_en; /* 0x0090 */
u32 padding_8[3]; /* 0x0094..0x009c */
u32 rd_int_status; /* 0x00a0 */
u32 padding_9; /* 0x00a4 */
u32 rd_int_mask; /* 0x00a8 */
u32 rd_int_clear; /* 0x00ac */
u32 padding_10; /* 0x00b0 */
union {
u64 reg; /* 0x00b4..0x00b8 */
struct {
u32 lsb; /* 0x00b4 */
u32 msb; /* 0x00b8 */
};
} rd_err_status;
u32 padding_11[2]; /* 0x00bc..0x00c0 */
u32 rd_linked_list_err_en; /* 0x00c4 */
u32 padding_12; /* 0x00c8 */
union {
u64 reg; /* 0x00cc..0x00d0 */
struct {
u32 lsb; /* 0x00cc */
u32 msb; /* 0x00d0 */
};
} rd_done_imwr;
union {
u64 reg; /* 0x00d4..0x00d8 */
struct {
u32 lsb; /* 0x00d4 */
u32 msb; /* 0x00d8 */
};
} rd_abort_imwr;
u32 rd_ch01_imwr_data; /* 0x00dc */
u32 rd_ch23_imwr_data; /* 0x00e0 */
u32 rd_ch45_imwr_data; /* 0x00e4 */
u32 rd_ch67_imwr_data; /* 0x00e8 */
u32 padding_13[4]; /* 0x00ec..0x00f8 */
/* eDMA channel context grouping */
union dw_edma_v0_type {
struct dw_edma_v0_legacy legacy; /* 0x00f8..0x0120 */
struct dw_edma_v0_unroll unroll; /* 0x00f8..0x1120 */
} type;
} __packed;
struct dw_edma_v0_lli {
u32 control;
u32 transfer_size;
union {
u64 reg;
struct {
u32 lsb;
u32 msb;
};
} sar;
union {
u64 reg;
struct {
u32 lsb;
u32 msb;
};
} dar;
} __packed;
struct dw_edma_v0_llp {
u32 control;
u32 reserved;
union {
u64 reg;
struct {
u32 lsb;
u32 msb;
};
} llp;
} __packed;
#endif /* _DW_EDMA_V0_REGS_H */
@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 Cai Huoqing
* Synopsys DesignWare HDMA v0 core
*/
#include <linux/bitfield.h>
#include <linux/irqreturn.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include "dw-edma-core.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_LWIE = BIT(3),
DW_HDMA_V0_RWIE = 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)
/* HDMA management callbacks */
static 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);
}
}
static u16 dw_hdma_v0_core_ch_count(struct dw_edma *dw, enum dw_edma_dir dir)
{
/*
* The HDMA IP have no way to know the number of hardware channels
* available, we set it to maximum channels and let the platform
* set the right number of channels.
*/
return HDMA_V0_MAX_NR_CH;
}
static 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;
}
static 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);
}
static 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);
}
static 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);
}
static irqreturn_t
dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir,
dw_edma_handler_t done, dw_edma_handler_t abort)
{
struct dw_edma *dw = dw_irq->dw;
unsigned long total, pos, val;
irqreturn_t ret = IRQ_NONE;
struct dw_edma_chan *chan;
unsigned long off, mask;
if (dir == EDMA_DIR_WRITE) {
total = dw->wr_ch_cnt;
off = 0;
mask = dw_irq->wr_mask;
} else {
total = dw->rd_ch_cnt;
off = dw->wr_ch_cnt;
mask = dw_irq->rd_mask;
}
for_each_set_bit(pos, &mask, total) {
chan = &dw->chan[pos + off];
val = dw_hdma_v0_core_status_int(chan);
if (FIELD_GET(HDMA_V0_STOP_INT_MASK, val)) {
dw_hdma_v0_core_clear_done_int(chan);
done(chan);
ret = IRQ_HANDLED;
}
if (FIELD_GET(HDMA_V0_ABORT_INT_MASK, val)) {
dw_hdma_v0_core_clear_abort_int(chan);
abort(chan);
ret = IRQ_HANDLED;
}
}
return ret;
}
static void dw_hdma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i,
u32 control, u32 size, u64 sar, u64 dar)
{
ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli);
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_hdma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs;
lli->control = control;
lli->transfer_size = size;
lli->sar.reg = sar;
lli->dar.reg = dar;
} else {
struct dw_hdma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs;
writel(control, &lli->control);
writel(size, &lli->transfer_size);
writeq(sar, &lli->sar.reg);
writeq(dar, &lli->dar.reg);
}
}
static void dw_hdma_v0_write_ll_link(struct dw_edma_chunk *chunk,
int i, u32 control, u64 pointer)
{
ptrdiff_t ofs = i * sizeof(struct dw_hdma_v0_lli);
if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) {
struct dw_hdma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs;
llp->control = control;
llp->llp.reg = pointer;
} else {
struct dw_hdma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs;
writel(control, &llp->control);
writeq(pointer, &llp->llp.reg);
}
}
static void dw_hdma_v0_core_write_chunk(struct dw_edma_chunk *chunk)
{
struct dw_edma_burst *child;
u32 control = 0, i = 0;
if (chunk->cb)
control = DW_HDMA_V0_CB;
list_for_each_entry(child, &chunk->burst->list, list)
dw_hdma_v0_write_ll_data(chunk, i++, control, child->sz,
child->sar, child->dar);
control = DW_HDMA_V0_LLP | DW_HDMA_V0_TCB;
if (!chunk->cb)
control |= DW_HDMA_V0_CB;
dw_hdma_v0_write_ll_link(chunk, i, control, chunk->ll_region.paddr);
}
static void dw_hdma_v0_sync_ll_data(struct dw_edma_chunk *chunk)
{
/*
* In case of remote HDMA engine setup, the DW PCIe RP/EP internal
* configuration registers and application memory are normally accessed
* over different buses. Ensure LL-data reaches the memory before the
* doorbell register is toggled by issuing the dummy-read from the remote
* LL memory in a hope that the MRd TLP will return only after the
* last MWr TLP is completed
*/
if (!(chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL))
readl(chunk->ll_region.vaddr.io);
}
static void dw_hdma_v0_core_ll_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 unmask - stop, abort */
tmp = GET_CH_32(dw, chan->dir, chan->id, int_setup);
tmp &= ~(HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK);
/* Interrupt enable - stop, abort */
tmp |= HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_LOCAL_ABORT_INT_EN;
if (!(dw->chip->flags & DW_EDMA_CHIP_LOCAL))
tmp |= HDMA_V0_REMOTE_STOP_INT_EN | HDMA_V0_REMOTE_ABORT_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));
/* Set consumer cycle */
SET_CH_32(dw, chan->dir, chan->id, cycle_sync,
HDMA_V0_CONSUMER_CYCLE_STAT | HDMA_V0_CONSUMER_CYCLE_BIT);
}
dw_hdma_v0_sync_ll_data(chunk);
/* Doorbell */
SET_CH_32(dw, chan->dir, chan->id, doorbell, HDMA_V0_DOORBELL_START);
}
static void dw_hdma_v0_core_non_ll_start(struct dw_edma_chunk *chunk)
{
struct dw_edma_chan *chan = chunk->chan;
struct dw_edma *dw = chan->dw;
struct dw_edma_burst *child;
u32 val;
child = list_first_entry_or_null(&chunk->burst->list,
struct dw_edma_burst, list);
if (!child)
return;
SET_CH_32(dw, chan->dir, chan->id, ch_en, HDMA_V0_CH_EN);
/* Source address */
SET_CH_32(dw, chan->dir, chan->id, sar.lsb,
lower_32_bits(child->sar));
SET_CH_32(dw, chan->dir, chan->id, sar.msb,
upper_32_bits(child->sar));
/* Destination address */
SET_CH_32(dw, chan->dir, chan->id, dar.lsb,
lower_32_bits(child->dar));
SET_CH_32(dw, chan->dir, chan->id, dar.msb,
upper_32_bits(child->dar));
/* Transfer size */
SET_CH_32(dw, chan->dir, chan->id, transfer_size, child->sz);
/* Interrupt setup */
val = GET_CH_32(dw, chan->dir, chan->id, int_setup) |
HDMA_V0_STOP_INT_MASK |
HDMA_V0_ABORT_INT_MASK |
HDMA_V0_LOCAL_STOP_INT_EN |
HDMA_V0_LOCAL_ABORT_INT_EN;
if (!(dw->chip->flags & DW_EDMA_CHIP_LOCAL)) {
val |= HDMA_V0_REMOTE_STOP_INT_EN |
HDMA_V0_REMOTE_ABORT_INT_EN;
}
SET_CH_32(dw, chan->dir, chan->id, int_setup, val);
/* Channel control setup */
val = GET_CH_32(dw, chan->dir, chan->id, control1);
val &= ~HDMA_V0_LINKLIST_EN;
SET_CH_32(dw, chan->dir, chan->id, control1, val);
SET_CH_32(dw, chan->dir, chan->id, doorbell,
HDMA_V0_DOORBELL_START);
}
static void dw_hdma_v0_core_start(struct dw_edma_chunk *chunk, bool first)
{
struct dw_edma_chan *chan = chunk->chan;
if (chan->non_ll)
dw_hdma_v0_core_non_ll_start(chunk);
else
dw_hdma_v0_core_ll_start(chunk, first);
}
static void dw_hdma_v0_core_ch_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);
/* config MSI data */
SET_CH_32(dw, chan->dir, chan->id, msi_msgdata, chan->msi.data);
}
/* HDMA debugfs callbacks */
static void dw_hdma_v0_core_debugfs_on(struct dw_edma *dw)
{
dw_hdma_v0_debugfs_on(dw);
}
static resource_size_t dw_hdma_v0_core_db_offset(struct dw_edma *dw)
{
/* Implement once the correct offset is known. */
return ~0;
}
static const struct dw_edma_core_ops dw_hdma_v0_core = {
.off = dw_hdma_v0_core_off,
.ch_count = dw_hdma_v0_core_ch_count,
.ch_status = dw_hdma_v0_core_ch_status,
.handle_int = dw_hdma_v0_core_handle_int,
.start = dw_hdma_v0_core_start,
.ch_config = dw_hdma_v0_core_ch_config,
.debugfs_on = dw_hdma_v0_core_debugfs_on,
.db_offset = dw_hdma_v0_core_db_offset,
};
void dw_hdma_v0_core_register(struct dw_edma *dw)
{
dw->core = &dw_hdma_v0_core;
}
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2023 Cai Huoqing
* Synopsys DesignWare HDMA v0 core
*
* Author: Cai Huoqing <cai.huoqing@linux.dev>
*/
#ifndef _DW_HDMA_V0_CORE_H
#define _DW_HDMA_V0_CORE_H
#include <linux/dma/edma.h>
/* HDMA core register */
void dw_hdma_v0_core_register(struct dw_edma *dw);
#endif /* _DW_HDMA_V0_CORE_H */
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 Cai Huoqing
* Synopsys DesignWare HDMA v0 debugfs
*
* Author: Cai Huoqing <cai.huoqing@linux.dev>
*/
#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(dw, name) \
({ \
struct dw_hdma_v0_regs __iomem *__regs = (dw)->chip->reg_base; \
\
(void __iomem *)&__regs->name; \
})
#define REGS_CH_ADDR(dw, name, _dir, _ch) \
({ \
struct dw_hdma_v0_ch_regs __iomem *__ch_regs; \
\
if (_dir == EDMA_DIR_READ) \
__ch_regs = REGS_ADDR(dw, ch[_ch].rd); \
else \
__ch_regs = REGS_ADDR(dw, ch[_ch].wr); \
\
(void __iomem *)&__ch_regs->name; \
})
#define CTX_REGISTER(dw, name, dir, ch) \
{#name, REGS_CH_ADDR(dw, name, dir, ch)}
#define WRITE_STR "write"
#define READ_STR "read"
#define CHANNEL_STR "channel"
#define REGISTERS_STR "registers"
struct dw_hdma_debugfs_entry {
const char *name;
void __iomem *reg;
};
static int dw_hdma_debugfs_u32_get(void *data, u64 *val)
{
struct dw_hdma_debugfs_entry *entry = data;
void __iomem *reg = entry->reg;
*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(struct dw_edma *dw,
const struct dw_hdma_debugfs_entry ini[],
int nr_entries, struct dentry *dent)
{
struct dw_hdma_debugfs_entry *entries;
int i;
entries = devm_kcalloc(dw->chip->dev, nr_entries, sizeof(*entries),
GFP_KERNEL);
if (!entries)
return;
for (i = 0; i < nr_entries; i++) {
entries[i] = ini[i];
debugfs_create_file_unsafe(entries[i].name, 0444, dent,
&entries[i], &fops_x32);
}
}
static void dw_hdma_debugfs_regs_ch(struct dw_edma *dw, enum dw_edma_dir dir,
u16 ch, struct dentry *dent)
{
const struct dw_hdma_debugfs_entry debugfs_regs[] = {
CTX_REGISTER(dw, ch_en, dir, ch),
CTX_REGISTER(dw, doorbell, dir, ch),
CTX_REGISTER(dw, prefetch, dir, ch),
CTX_REGISTER(dw, handshake, dir, ch),
CTX_REGISTER(dw, llp.lsb, dir, ch),
CTX_REGISTER(dw, llp.msb, dir, ch),
CTX_REGISTER(dw, cycle_sync, dir, ch),
CTX_REGISTER(dw, transfer_size, dir, ch),
CTX_REGISTER(dw, sar.lsb, dir, ch),
CTX_REGISTER(dw, sar.msb, dir, ch),
CTX_REGISTER(dw, dar.lsb, dir, ch),
CTX_REGISTER(dw, dar.msb, dir, ch),
CTX_REGISTER(dw, watermark_en, dir, ch),
CTX_REGISTER(dw, control1, dir, ch),
CTX_REGISTER(dw, func_num, dir, ch),
CTX_REGISTER(dw, qos, dir, ch),
CTX_REGISTER(dw, ch_stat, dir, ch),
CTX_REGISTER(dw, int_stat, dir, ch),
CTX_REGISTER(dw, int_setup, dir, ch),
CTX_REGISTER(dw, int_clear, dir, ch),
CTX_REGISTER(dw, msi_stop.lsb, dir, ch),
CTX_REGISTER(dw, msi_stop.msb, dir, ch),
CTX_REGISTER(dw, msi_watermark.lsb, dir, ch),
CTX_REGISTER(dw, msi_watermark.msb, dir, ch),
CTX_REGISTER(dw, msi_abort.lsb, dir, ch),
CTX_REGISTER(dw, msi_abort.msb, dir, ch),
CTX_REGISTER(dw, msi_msgdata, dir, ch),
};
int nr_entries = ARRAY_SIZE(debugfs_regs);
dw_hdma_debugfs_create_x32(dw, debugfs_regs, nr_entries, dent);
}
static void dw_hdma_debugfs_regs_wr(struct dw_edma *dw, struct dentry *dent)
{
struct dentry *regs_dent, *ch_dent;
char name[32];
int i;
regs_dent = debugfs_create_dir(WRITE_STR, dent);
for (i = 0; i < dw->wr_ch_cnt; i++) {
snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i);
ch_dent = debugfs_create_dir(name, regs_dent);
dw_hdma_debugfs_regs_ch(dw, EDMA_DIR_WRITE, i, ch_dent);
}
}
static void dw_hdma_debugfs_regs_rd(struct dw_edma *dw, struct dentry *dent)
{
struct dentry *regs_dent, *ch_dent;
char name[32];
int i;
regs_dent = debugfs_create_dir(READ_STR, dent);
for (i = 0; i < dw->rd_ch_cnt; i++) {
snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i);
ch_dent = debugfs_create_dir(name, regs_dent);
dw_hdma_debugfs_regs_ch(dw, EDMA_DIR_READ, i, ch_dent);
}
}
static void dw_hdma_debugfs_regs(struct dw_edma *dw)
{
struct dentry *regs_dent;
regs_dent = debugfs_create_dir(REGISTERS_STR, dw->dma.dbg_dev_root);
dw_hdma_debugfs_regs_wr(dw, regs_dent);
dw_hdma_debugfs_regs_rd(dw, regs_dent);
}
void dw_hdma_v0_debugfs_on(struct dw_edma *dw)
{
if (!debugfs_initialized())
return;
debugfs_create_u32("mf", 0444, dw->dma.dbg_dev_root, &dw->chip->mf);
debugfs_create_u16("wr_ch_cnt", 0444, dw->dma.dbg_dev_root, &dw->wr_ch_cnt);
debugfs_create_u16("rd_ch_cnt", 0444, dw->dma.dbg_dev_root, &dw->rd_ch_cnt);
dw_hdma_debugfs_regs(dw);
}
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2023 Cai Huoqing
* Synopsys DesignWare HDMA v0 debugfs
*
* Author: Cai Huoqing <cai.huoqing@linux.dev>
*/
#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);
#else
static inline void dw_hdma_v0_debugfs_on(struct dw_edma *dw)
{
}
#endif /* CONFIG_DEBUG_FS */
#endif /* _DW_HDMA_V0_DEBUG_FS_H */
@@ -0,0 +1,130 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2023 Cai Huoqing
* Synopsys DesignWare HDMA v0 reg
*
* Author: Cai Huoqing <cai.huoqing@linux.dev>
*/
#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_CH_EN BIT(0)
#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_REMOTE_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_CONSUMER_CYCLE_STAT BIT(1)
#define HDMA_V0_CONSUMER_CYCLE_BIT 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 padding_1[16]; /* 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 */
u32 padding_2[21]; /* 0x00ac..0x00fc */
} __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;
struct dw_hdma_v0_lli {
u32 control;
u32 transfer_size;
union {
u64 reg;
struct {
u32 lsb;
u32 msb;
};
} sar;
union {
u64 reg;
struct {
u32 lsb;
u32 msb;
};
} dar;
} __packed;
struct dw_hdma_v0_llp {
u32 control;
u32 reserved;
union {
u64 reg;
struct {
u32 lsb;
u32 msb;
};
} llp;
} __packed;
#endif /* _DW_HDMA_V0_REGS_H */