mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-12 22:21:29 -04:00
FIELD_GET(SE_THRESH_MASK, thresh) extracts bits [31:23] from thresh and
right-shifts them, which is the inverse of the intended operation. Since
thresh is derived from se_buf_len / 2 (at most 255), bits [31:23] are
always zero, so the SE threshold is never actually programmed into the
register.
Use FIELD_PREP() instead to correctly left-shift thresh into bits [31:23]
of the valid_en_se register, consistent with the FIELD_PREP usage for
the perf tuner config just above.
Fixes: 30eba9df76 ("dmaengine: switchtec-dma: Implement hardware initialization and cleanup")
Signed-off-by: David Carlier <devnexen@gmail.com>
Review-by: Logan Gunthorpe <logang@deltatee.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Link: https://patch.msgid.link/20260317083252.13224-1-devnexen@gmail.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
1438 lines
37 KiB
C
1438 lines
37 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Microchip Switchtec(tm) DMA Controller Driver
|
|
* Copyright (c) 2025, Kelvin Cao <kelvin.cao@microchip.com>
|
|
* Copyright (c) 2025, Microchip Corporation
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/circ_buf.h>
|
|
#include <linux/dmaengine.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/iopoll.h>
|
|
|
|
#include "dmaengine.h"
|
|
|
|
MODULE_DESCRIPTION("Switchtec PCIe Switch DMA Engine");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Kelvin Cao");
|
|
|
|
#define SWITCHTEC_DMAC_CHAN_CTRL_OFFSET 0x1000
|
|
#define SWITCHTEC_DMAC_CHAN_CFG_STS_OFFSET 0x160000
|
|
|
|
#define SWITCHTEC_DMA_CHAN_HW_REGS_SIZE 0x1000
|
|
#define SWITCHTEC_DMA_CHAN_FW_REGS_SIZE 0x80
|
|
|
|
#define SWITCHTEC_REG_CAP 0x80
|
|
#define SWITCHTEC_REG_CHAN_CNT 0x84
|
|
#define SWITCHTEC_REG_TAG_LIMIT 0x90
|
|
#define SWITCHTEC_REG_CHAN_STS_VEC 0x94
|
|
#define SWITCHTEC_REG_SE_BUF_CNT 0x98
|
|
#define SWITCHTEC_REG_SE_BUF_BASE 0x9a
|
|
|
|
#define SWITCHTEC_DESC_MAX_SIZE 0x100000
|
|
|
|
#define SWITCHTEC_CHAN_CTRL_PAUSE BIT(0)
|
|
#define SWITCHTEC_CHAN_CTRL_HALT BIT(1)
|
|
#define SWITCHTEC_CHAN_CTRL_RESET BIT(2)
|
|
#define SWITCHTEC_CHAN_CTRL_ERR_PAUSE BIT(3)
|
|
|
|
#define SWITCHTEC_CHAN_STS_PAUSED BIT(9)
|
|
#define SWITCHTEC_CHAN_STS_HALTED BIT(10)
|
|
#define SWITCHTEC_CHAN_STS_PAUSED_MASK GENMASK(29, 13)
|
|
|
|
#define SWITCHTEC_INVALID_HFID 0xffff
|
|
|
|
#define SWITCHTEC_DMA_SQ_SIZE SZ_32K
|
|
#define SWITCHTEC_DMA_CQ_SIZE SZ_32K
|
|
|
|
#define SWITCHTEC_DMA_RING_SIZE SZ_32K
|
|
|
|
static const char * const channel_status_str[] = {
|
|
[13] = "received a VDM with length error status",
|
|
[14] = "received a VDM or Cpl with Unsupported Request error status",
|
|
[15] = "received a VDM or Cpl with Completion Abort error status",
|
|
[16] = "received a VDM with ECRC error status",
|
|
[17] = "received a VDM with EP error status",
|
|
[18] = "received a VDM with Reserved Cpl error status",
|
|
[19] = "received only part of split SE CplD",
|
|
[20] = "the ISP_DMAC detected a Completion Time Out",
|
|
[21] = "received a Cpl with Unsupported Request status",
|
|
[22] = "received a Cpl with Completion Abort status",
|
|
[23] = "received a Cpl with a reserved status",
|
|
[24] = "received a TLP with ECRC error status in its metadata",
|
|
[25] = "received a TLP with the EP bit set in the header",
|
|
[26] = "the ISP_DMAC tried to process a SE with an invalid Connection ID",
|
|
[27] = "the ISP_DMAC tried to process a SE with an invalid Remote Host interrupt",
|
|
[28] = "a reserved opcode was detected in an SE",
|
|
[29] = "received a SE Cpl with error status",
|
|
};
|
|
|
|
struct chan_hw_regs {
|
|
u16 cq_head;
|
|
u16 rsvd1;
|
|
u16 sq_tail;
|
|
u16 rsvd2;
|
|
u8 ctrl;
|
|
u8 rsvd3[3];
|
|
u16 status;
|
|
u16 rsvd4;
|
|
};
|
|
|
|
#define PERF_BURST_SCALE_MASK GENMASK_U32(3, 2)
|
|
#define PERF_MRRS_MASK GENMASK_U32(6, 4)
|
|
#define PERF_INTERVAL_MASK GENMASK_U32(10, 8)
|
|
#define PERF_BURST_SIZE_MASK GENMASK_U32(14, 12)
|
|
#define PERF_ARB_WEIGHT_MASK GENMASK_U32(31, 24)
|
|
|
|
#define SE_BUF_BASE_MASK GENMASK_U32(10, 2)
|
|
#define SE_BUF_LEN_MASK GENMASK_U32(20, 12)
|
|
#define SE_THRESH_MASK GENMASK_U32(31, 23)
|
|
|
|
#define SWITCHTEC_CHAN_ENABLE BIT(1)
|
|
|
|
struct chan_fw_regs {
|
|
u32 valid_en_se;
|
|
u32 cq_base_lo;
|
|
u32 cq_base_hi;
|
|
u16 cq_size;
|
|
u16 rsvd1;
|
|
u32 sq_base_lo;
|
|
u32 sq_base_hi;
|
|
u16 sq_size;
|
|
u16 rsvd2;
|
|
u32 int_vec;
|
|
u32 perf_cfg;
|
|
u32 rsvd3;
|
|
u32 perf_latency_selector;
|
|
u32 perf_fetched_se_cnt_lo;
|
|
u32 perf_fetched_se_cnt_hi;
|
|
u32 perf_byte_cnt_lo;
|
|
u32 perf_byte_cnt_hi;
|
|
u32 rsvd4;
|
|
u16 perf_se_pending;
|
|
u16 perf_se_buf_empty;
|
|
u32 perf_chan_idle;
|
|
u32 perf_lat_max;
|
|
u32 perf_lat_min;
|
|
u32 perf_lat_last;
|
|
u16 sq_current;
|
|
u16 sq_phase;
|
|
u16 cq_current;
|
|
u16 cq_phase;
|
|
};
|
|
|
|
struct switchtec_dma_chan {
|
|
struct switchtec_dma_dev *swdma_dev;
|
|
struct dma_chan dma_chan;
|
|
struct chan_hw_regs __iomem *mmio_chan_hw;
|
|
struct chan_fw_regs __iomem *mmio_chan_fw;
|
|
|
|
/* Serialize hardware control register access */
|
|
spinlock_t hw_ctrl_lock;
|
|
|
|
struct tasklet_struct desc_task;
|
|
|
|
/* Serialize descriptor preparation */
|
|
spinlock_t submit_lock;
|
|
bool ring_active;
|
|
int cid;
|
|
|
|
/* Serialize completion processing */
|
|
spinlock_t complete_lock;
|
|
bool comp_ring_active;
|
|
|
|
/* channel index and irq */
|
|
int index;
|
|
int irq;
|
|
|
|
/*
|
|
* In driver context, head is advanced by producer while
|
|
* tail is advanced by consumer.
|
|
*/
|
|
|
|
/* the head and tail for both desc_ring and hw_sq */
|
|
int head;
|
|
int tail;
|
|
int phase_tag;
|
|
struct switchtec_dma_hw_se_desc *hw_sq;
|
|
dma_addr_t dma_addr_sq;
|
|
|
|
/* the tail for hw_cq */
|
|
int cq_tail;
|
|
struct switchtec_dma_hw_ce *hw_cq;
|
|
dma_addr_t dma_addr_cq;
|
|
|
|
struct list_head list;
|
|
|
|
struct switchtec_dma_desc *desc_ring[SWITCHTEC_DMA_RING_SIZE];
|
|
};
|
|
|
|
struct switchtec_dma_dev {
|
|
struct dma_device dma_dev;
|
|
struct pci_dev __rcu *pdev;
|
|
void __iomem *bar;
|
|
|
|
struct switchtec_dma_chan **swdma_chans;
|
|
int chan_cnt;
|
|
int chan_status_irq;
|
|
};
|
|
|
|
enum chan_op {
|
|
ENABLE_CHAN,
|
|
DISABLE_CHAN,
|
|
};
|
|
|
|
enum switchtec_dma_opcode {
|
|
SWITCHTEC_DMA_OPC_MEMCPY = 0,
|
|
SWITCHTEC_DMA_OPC_RDIMM = 0x1,
|
|
SWITCHTEC_DMA_OPC_WRIMM = 0x2,
|
|
SWITCHTEC_DMA_OPC_RHI = 0x6,
|
|
SWITCHTEC_DMA_OPC_NOP = 0x7,
|
|
};
|
|
|
|
struct switchtec_dma_hw_se_desc {
|
|
u8 opc;
|
|
u8 ctrl;
|
|
__le16 tlp_setting;
|
|
__le16 rsvd1;
|
|
__le16 cid;
|
|
__le32 byte_cnt;
|
|
__le32 addr_lo; /* SADDR_LO/WIADDR_LO */
|
|
__le32 addr_hi; /* SADDR_HI/WIADDR_HI */
|
|
__le32 daddr_lo;
|
|
__le32 daddr_hi;
|
|
__le16 dfid;
|
|
__le16 sfid;
|
|
};
|
|
|
|
#define SWITCHTEC_SE_DFM BIT(5)
|
|
#define SWITCHTEC_SE_LIOF BIT(6)
|
|
#define SWITCHTEC_SE_BRR BIT(7)
|
|
#define SWITCHTEC_SE_CID_MASK GENMASK(15, 0)
|
|
|
|
#define SWITCHTEC_CE_SC_LEN_ERR BIT(0)
|
|
#define SWITCHTEC_CE_SC_UR BIT(1)
|
|
#define SWITCHTEC_CE_SC_CA BIT(2)
|
|
#define SWITCHTEC_CE_SC_RSVD_CPL BIT(3)
|
|
#define SWITCHTEC_CE_SC_ECRC_ERR BIT(4)
|
|
#define SWITCHTEC_CE_SC_EP_SET BIT(5)
|
|
#define SWITCHTEC_CE_SC_D_RD_CTO BIT(8)
|
|
#define SWITCHTEC_CE_SC_D_RIMM_UR BIT(9)
|
|
#define SWITCHTEC_CE_SC_D_RIMM_CA BIT(10)
|
|
#define SWITCHTEC_CE_SC_D_RIMM_RSVD_CPL BIT(11)
|
|
#define SWITCHTEC_CE_SC_D_ECRC BIT(12)
|
|
#define SWITCHTEC_CE_SC_D_EP_SET BIT(13)
|
|
#define SWITCHTEC_CE_SC_D_BAD_CONNID BIT(14)
|
|
#define SWITCHTEC_CE_SC_D_BAD_RHI_ADDR BIT(15)
|
|
#define SWITCHTEC_CE_SC_D_INVD_CMD BIT(16)
|
|
#define SWITCHTEC_CE_SC_MASK GENMASK(16, 0)
|
|
|
|
struct switchtec_dma_hw_ce {
|
|
__le32 rdimm_cpl_dw0;
|
|
__le32 rdimm_cpl_dw1;
|
|
__le32 rsvd1;
|
|
__le32 cpl_byte_cnt;
|
|
__le16 sq_head;
|
|
__le16 rsvd2;
|
|
__le32 rsvd3;
|
|
__le32 sts_code;
|
|
__le16 cid;
|
|
__le16 phase_tag;
|
|
};
|
|
|
|
struct switchtec_dma_desc {
|
|
struct dma_async_tx_descriptor txd;
|
|
struct switchtec_dma_hw_se_desc *hw;
|
|
u32 orig_size;
|
|
bool completed;
|
|
};
|
|
|
|
static int wait_for_chan_status(struct chan_hw_regs __iomem *chan_hw, u32 mask,
|
|
bool set)
|
|
{
|
|
u32 status;
|
|
|
|
return readl_poll_timeout_atomic(&chan_hw->status, status,
|
|
(set && (status & mask)) ||
|
|
(!set && !(status & mask)),
|
|
10, 100 * USEC_PER_MSEC);
|
|
}
|
|
|
|
static int halt_channel(struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw;
|
|
struct pci_dev *pdev;
|
|
int ret;
|
|
|
|
rcu_read_lock();
|
|
pdev = rcu_dereference(swdma_chan->swdma_dev->pdev);
|
|
if (!pdev) {
|
|
ret = -ENODEV;
|
|
goto unlock_and_exit;
|
|
}
|
|
|
|
spin_lock(&swdma_chan->hw_ctrl_lock);
|
|
writeb(SWITCHTEC_CHAN_CTRL_HALT, &chan_hw->ctrl);
|
|
ret = wait_for_chan_status(chan_hw, SWITCHTEC_CHAN_STS_HALTED, true);
|
|
spin_unlock(&swdma_chan->hw_ctrl_lock);
|
|
|
|
unlock_and_exit:
|
|
rcu_read_unlock();
|
|
return ret;
|
|
}
|
|
|
|
static int unhalt_channel(struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw;
|
|
struct pci_dev *pdev;
|
|
u8 ctrl;
|
|
int ret;
|
|
|
|
rcu_read_lock();
|
|
pdev = rcu_dereference(swdma_chan->swdma_dev->pdev);
|
|
if (!pdev) {
|
|
ret = -ENODEV;
|
|
goto unlock_and_exit;
|
|
}
|
|
|
|
spin_lock(&swdma_chan->hw_ctrl_lock);
|
|
ctrl = readb(&chan_hw->ctrl);
|
|
ctrl &= ~SWITCHTEC_CHAN_CTRL_HALT;
|
|
writeb(ctrl, &chan_hw->ctrl);
|
|
ret = wait_for_chan_status(chan_hw, SWITCHTEC_CHAN_STS_HALTED, false);
|
|
spin_unlock(&swdma_chan->hw_ctrl_lock);
|
|
|
|
unlock_and_exit:
|
|
rcu_read_unlock();
|
|
return ret;
|
|
}
|
|
|
|
static void flush_pci_write(struct chan_hw_regs __iomem *chan_hw)
|
|
{
|
|
readl(&chan_hw->cq_head);
|
|
}
|
|
|
|
static int reset_channel(struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw;
|
|
struct pci_dev *pdev;
|
|
|
|
rcu_read_lock();
|
|
pdev = rcu_dereference(swdma_chan->swdma_dev->pdev);
|
|
if (!pdev) {
|
|
rcu_read_unlock();
|
|
return -ENODEV;
|
|
}
|
|
|
|
spin_lock(&swdma_chan->hw_ctrl_lock);
|
|
writel(SWITCHTEC_CHAN_CTRL_RESET | SWITCHTEC_CHAN_CTRL_ERR_PAUSE,
|
|
&chan_hw->ctrl);
|
|
flush_pci_write(chan_hw);
|
|
|
|
udelay(1000);
|
|
|
|
writel(SWITCHTEC_CHAN_CTRL_ERR_PAUSE, &chan_hw->ctrl);
|
|
spin_unlock(&swdma_chan->hw_ctrl_lock);
|
|
flush_pci_write(chan_hw);
|
|
|
|
rcu_read_unlock();
|
|
return 0;
|
|
}
|
|
|
|
static int pause_reset_channel(struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw;
|
|
struct pci_dev *pdev;
|
|
|
|
rcu_read_lock();
|
|
pdev = rcu_dereference(swdma_chan->swdma_dev->pdev);
|
|
if (!pdev) {
|
|
rcu_read_unlock();
|
|
return -ENODEV;
|
|
}
|
|
|
|
spin_lock(&swdma_chan->hw_ctrl_lock);
|
|
writeb(SWITCHTEC_CHAN_CTRL_PAUSE, &chan_hw->ctrl);
|
|
spin_unlock(&swdma_chan->hw_ctrl_lock);
|
|
|
|
flush_pci_write(chan_hw);
|
|
|
|
rcu_read_unlock();
|
|
|
|
/* wait 60ms to ensure no pending CEs */
|
|
mdelay(60);
|
|
|
|
return reset_channel(swdma_chan);
|
|
}
|
|
|
|
static int channel_op(struct switchtec_dma_chan *swdma_chan, int op)
|
|
{
|
|
struct chan_fw_regs __iomem *chan_fw = swdma_chan->mmio_chan_fw;
|
|
struct pci_dev *pdev;
|
|
u32 valid_en_se;
|
|
|
|
rcu_read_lock();
|
|
pdev = rcu_dereference(swdma_chan->swdma_dev->pdev);
|
|
if (!pdev) {
|
|
rcu_read_unlock();
|
|
return -ENODEV;
|
|
}
|
|
|
|
valid_en_se = readl(&chan_fw->valid_en_se);
|
|
if (op == ENABLE_CHAN)
|
|
valid_en_se |= SWITCHTEC_CHAN_ENABLE;
|
|
else
|
|
valid_en_se &= ~SWITCHTEC_CHAN_ENABLE;
|
|
|
|
writel(valid_en_se, &chan_fw->valid_en_se);
|
|
|
|
rcu_read_unlock();
|
|
return 0;
|
|
}
|
|
|
|
static int enable_channel(struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
return channel_op(swdma_chan, ENABLE_CHAN);
|
|
}
|
|
|
|
static int disable_channel(struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
return channel_op(swdma_chan, DISABLE_CHAN);
|
|
}
|
|
|
|
static void
|
|
switchtec_dma_cleanup_completed(struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
struct device *chan_dev = &swdma_chan->dma_chan.dev->device;
|
|
struct switchtec_dma_desc *desc;
|
|
struct switchtec_dma_hw_ce *ce;
|
|
struct dmaengine_result res;
|
|
int tail, cid, se_idx, i;
|
|
__le16 phase_tag;
|
|
u32 sts_code;
|
|
__le32 *p;
|
|
|
|
do {
|
|
spin_lock_bh(&swdma_chan->complete_lock);
|
|
if (!swdma_chan->comp_ring_active) {
|
|
spin_unlock_bh(&swdma_chan->complete_lock);
|
|
break;
|
|
}
|
|
|
|
ce = &swdma_chan->hw_cq[swdma_chan->cq_tail];
|
|
/*
|
|
* phase_tag is updated by hardware, ensure the value is
|
|
* not from the cache
|
|
*/
|
|
phase_tag = smp_load_acquire(&ce->phase_tag);
|
|
if (le16_to_cpu(phase_tag) == swdma_chan->phase_tag) {
|
|
spin_unlock_bh(&swdma_chan->complete_lock);
|
|
break;
|
|
}
|
|
|
|
cid = le16_to_cpu(ce->cid);
|
|
se_idx = cid & (SWITCHTEC_DMA_SQ_SIZE - 1);
|
|
desc = swdma_chan->desc_ring[se_idx];
|
|
|
|
tail = swdma_chan->tail;
|
|
|
|
res.residue = desc->orig_size - le32_to_cpu(ce->cpl_byte_cnt);
|
|
|
|
sts_code = le32_to_cpu(ce->sts_code);
|
|
|
|
if (!(sts_code & SWITCHTEC_CE_SC_MASK)) {
|
|
res.result = DMA_TRANS_NOERROR;
|
|
} else {
|
|
if (sts_code & SWITCHTEC_CE_SC_D_RD_CTO)
|
|
res.result = DMA_TRANS_READ_FAILED;
|
|
else
|
|
res.result = DMA_TRANS_WRITE_FAILED;
|
|
|
|
dev_err(chan_dev, "CID 0x%04x failed, SC 0x%08x\n", cid,
|
|
(u32)(sts_code & SWITCHTEC_CE_SC_MASK));
|
|
|
|
p = (__le32 *)ce;
|
|
for (i = 0; i < sizeof(*ce) / 4; i++) {
|
|
dev_err(chan_dev, "CE DW%d: 0x%08x\n", i,
|
|
le32_to_cpu(*p));
|
|
p++;
|
|
}
|
|
}
|
|
|
|
desc->completed = true;
|
|
|
|
swdma_chan->cq_tail++;
|
|
swdma_chan->cq_tail &= SWITCHTEC_DMA_CQ_SIZE - 1;
|
|
|
|
rcu_read_lock();
|
|
if (!rcu_dereference(swdma_chan->swdma_dev->pdev)) {
|
|
rcu_read_unlock();
|
|
spin_unlock_bh(&swdma_chan->complete_lock);
|
|
return;
|
|
}
|
|
writew(swdma_chan->cq_tail, &swdma_chan->mmio_chan_hw->cq_head);
|
|
rcu_read_unlock();
|
|
|
|
if (swdma_chan->cq_tail == 0)
|
|
swdma_chan->phase_tag = !swdma_chan->phase_tag;
|
|
|
|
/* Out of order CE */
|
|
if (se_idx != tail) {
|
|
spin_unlock_bh(&swdma_chan->complete_lock);
|
|
continue;
|
|
}
|
|
|
|
do {
|
|
dma_cookie_complete(&desc->txd);
|
|
dma_descriptor_unmap(&desc->txd);
|
|
dmaengine_desc_get_callback_invoke(&desc->txd, &res);
|
|
desc->txd.callback = NULL;
|
|
desc->txd.callback_result = NULL;
|
|
desc->completed = false;
|
|
|
|
tail++;
|
|
tail &= SWITCHTEC_DMA_SQ_SIZE - 1;
|
|
|
|
/*
|
|
* Ensure the desc updates are visible before updating
|
|
* the tail index
|
|
*/
|
|
smp_store_release(&swdma_chan->tail, tail);
|
|
desc = swdma_chan->desc_ring[swdma_chan->tail];
|
|
if (!desc->completed)
|
|
break;
|
|
} while (CIRC_CNT(READ_ONCE(swdma_chan->head), swdma_chan->tail,
|
|
SWITCHTEC_DMA_SQ_SIZE));
|
|
|
|
spin_unlock_bh(&swdma_chan->complete_lock);
|
|
} while (1);
|
|
}
|
|
|
|
static void
|
|
switchtec_dma_abort_desc(struct switchtec_dma_chan *swdma_chan, int force)
|
|
{
|
|
struct switchtec_dma_desc *desc;
|
|
struct dmaengine_result res;
|
|
|
|
if (!force)
|
|
switchtec_dma_cleanup_completed(swdma_chan);
|
|
|
|
spin_lock_bh(&swdma_chan->complete_lock);
|
|
|
|
while (CIRC_CNT(swdma_chan->head, swdma_chan->tail,
|
|
SWITCHTEC_DMA_SQ_SIZE) >= 1) {
|
|
desc = swdma_chan->desc_ring[swdma_chan->tail];
|
|
|
|
res.residue = desc->orig_size;
|
|
res.result = DMA_TRANS_ABORTED;
|
|
|
|
dma_cookie_complete(&desc->txd);
|
|
dma_descriptor_unmap(&desc->txd);
|
|
if (!force)
|
|
dmaengine_desc_get_callback_invoke(&desc->txd, &res);
|
|
desc->txd.callback = NULL;
|
|
desc->txd.callback_result = NULL;
|
|
|
|
swdma_chan->tail++;
|
|
swdma_chan->tail &= SWITCHTEC_DMA_SQ_SIZE - 1;
|
|
}
|
|
|
|
spin_unlock_bh(&swdma_chan->complete_lock);
|
|
}
|
|
|
|
static void switchtec_dma_chan_stop(struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
int rc;
|
|
|
|
rc = halt_channel(swdma_chan);
|
|
if (rc)
|
|
return;
|
|
|
|
rcu_read_lock();
|
|
if (!rcu_dereference(swdma_chan->swdma_dev->pdev)) {
|
|
rcu_read_unlock();
|
|
return;
|
|
}
|
|
|
|
writel(0, &swdma_chan->mmio_chan_fw->sq_base_lo);
|
|
writel(0, &swdma_chan->mmio_chan_fw->sq_base_hi);
|
|
writel(0, &swdma_chan->mmio_chan_fw->cq_base_lo);
|
|
writel(0, &swdma_chan->mmio_chan_fw->cq_base_hi);
|
|
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static int switchtec_dma_terminate_all(struct dma_chan *chan)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan =
|
|
container_of(chan, struct switchtec_dma_chan, dma_chan);
|
|
|
|
spin_lock_bh(&swdma_chan->complete_lock);
|
|
swdma_chan->comp_ring_active = false;
|
|
spin_unlock_bh(&swdma_chan->complete_lock);
|
|
|
|
return pause_reset_channel(swdma_chan);
|
|
}
|
|
|
|
static void switchtec_dma_synchronize(struct dma_chan *chan)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan =
|
|
container_of(chan, struct switchtec_dma_chan, dma_chan);
|
|
|
|
int rc;
|
|
|
|
switchtec_dma_abort_desc(swdma_chan, 1);
|
|
|
|
rc = enable_channel(swdma_chan);
|
|
if (rc)
|
|
return;
|
|
|
|
rc = reset_channel(swdma_chan);
|
|
if (rc)
|
|
return;
|
|
|
|
rc = unhalt_channel(swdma_chan);
|
|
if (rc)
|
|
return;
|
|
|
|
spin_lock_bh(&swdma_chan->submit_lock);
|
|
swdma_chan->head = 0;
|
|
spin_unlock_bh(&swdma_chan->submit_lock);
|
|
|
|
spin_lock_bh(&swdma_chan->complete_lock);
|
|
swdma_chan->comp_ring_active = true;
|
|
swdma_chan->phase_tag = 0;
|
|
swdma_chan->tail = 0;
|
|
swdma_chan->cq_tail = 0;
|
|
swdma_chan->cid = 0;
|
|
dma_cookie_init(chan);
|
|
spin_unlock_bh(&swdma_chan->complete_lock);
|
|
}
|
|
|
|
static struct dma_async_tx_descriptor *
|
|
switchtec_dma_prep_desc(struct dma_chan *c, u16 dst_fid, dma_addr_t dma_dst,
|
|
u16 src_fid, dma_addr_t dma_src, u64 data,
|
|
size_t len, unsigned long flags)
|
|
__acquires(swdma_chan->submit_lock)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan =
|
|
container_of(c, struct switchtec_dma_chan, dma_chan);
|
|
struct switchtec_dma_desc *desc;
|
|
int head, tail;
|
|
|
|
spin_lock_bh(&swdma_chan->submit_lock);
|
|
|
|
if (!swdma_chan->ring_active)
|
|
goto err_unlock;
|
|
|
|
tail = READ_ONCE(swdma_chan->tail);
|
|
head = swdma_chan->head;
|
|
|
|
if (!CIRC_SPACE(head, tail, SWITCHTEC_DMA_RING_SIZE))
|
|
goto err_unlock;
|
|
|
|
desc = swdma_chan->desc_ring[head];
|
|
|
|
if (src_fid != SWITCHTEC_INVALID_HFID &&
|
|
dst_fid != SWITCHTEC_INVALID_HFID)
|
|
desc->hw->ctrl |= SWITCHTEC_SE_DFM;
|
|
|
|
if (flags & DMA_PREP_INTERRUPT)
|
|
desc->hw->ctrl |= SWITCHTEC_SE_LIOF;
|
|
|
|
if (flags & DMA_PREP_FENCE)
|
|
desc->hw->ctrl |= SWITCHTEC_SE_BRR;
|
|
|
|
desc->txd.flags = flags;
|
|
|
|
desc->completed = false;
|
|
desc->hw->opc = SWITCHTEC_DMA_OPC_MEMCPY;
|
|
desc->hw->addr_lo = cpu_to_le32(lower_32_bits(dma_src));
|
|
desc->hw->addr_hi = cpu_to_le32(upper_32_bits(dma_src));
|
|
desc->hw->daddr_lo = cpu_to_le32(lower_32_bits(dma_dst));
|
|
desc->hw->daddr_hi = cpu_to_le32(upper_32_bits(dma_dst));
|
|
desc->hw->byte_cnt = cpu_to_le32(len);
|
|
desc->hw->tlp_setting = 0;
|
|
desc->hw->dfid = cpu_to_le16(dst_fid);
|
|
desc->hw->sfid = cpu_to_le16(src_fid);
|
|
swdma_chan->cid &= SWITCHTEC_SE_CID_MASK;
|
|
desc->hw->cid = cpu_to_le16(swdma_chan->cid++);
|
|
desc->orig_size = len;
|
|
|
|
/* return with the lock held, it will be released in tx_submit */
|
|
|
|
return &desc->txd;
|
|
|
|
err_unlock:
|
|
/*
|
|
* Keep sparse happy by restoring an even lock count on
|
|
* this lock.
|
|
*/
|
|
__acquire(swdma_chan->submit_lock);
|
|
|
|
spin_unlock_bh(&swdma_chan->submit_lock);
|
|
return NULL;
|
|
}
|
|
|
|
static struct dma_async_tx_descriptor *
|
|
switchtec_dma_prep_memcpy(struct dma_chan *c, dma_addr_t dma_dst,
|
|
dma_addr_t dma_src, size_t len, unsigned long flags)
|
|
__acquires(swdma_chan->submit_lock)
|
|
{
|
|
if (len > SWITCHTEC_DESC_MAX_SIZE) {
|
|
/*
|
|
* Keep sparse happy by restoring an even lock count on
|
|
* this lock.
|
|
*/
|
|
__acquire(swdma_chan->submit_lock);
|
|
return NULL;
|
|
}
|
|
|
|
return switchtec_dma_prep_desc(c, SWITCHTEC_INVALID_HFID, dma_dst,
|
|
SWITCHTEC_INVALID_HFID, dma_src, 0, len,
|
|
flags);
|
|
}
|
|
|
|
static dma_cookie_t
|
|
switchtec_dma_tx_submit(struct dma_async_tx_descriptor *desc)
|
|
__releases(swdma_chan->submit_lock)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan =
|
|
container_of(desc->chan, struct switchtec_dma_chan, dma_chan);
|
|
dma_cookie_t cookie;
|
|
int head;
|
|
|
|
head = swdma_chan->head + 1;
|
|
head &= SWITCHTEC_DMA_RING_SIZE - 1;
|
|
|
|
/*
|
|
* Ensure the desc updates are visible before updating the head index
|
|
*/
|
|
smp_store_release(&swdma_chan->head, head);
|
|
|
|
cookie = dma_cookie_assign(desc);
|
|
|
|
spin_unlock_bh(&swdma_chan->submit_lock);
|
|
|
|
return cookie;
|
|
}
|
|
|
|
static enum dma_status switchtec_dma_tx_status(struct dma_chan *chan,
|
|
dma_cookie_t cookie, struct dma_tx_state *txstate)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan =
|
|
container_of(chan, struct switchtec_dma_chan, dma_chan);
|
|
enum dma_status ret;
|
|
|
|
ret = dma_cookie_status(chan, cookie, txstate);
|
|
if (ret == DMA_COMPLETE)
|
|
return ret;
|
|
|
|
/*
|
|
* For jobs where the interrupts are disabled, this is the only place
|
|
* to process the completions returned by the hardware. Callers that
|
|
* disable interrupts must call tx_status() to determine when a job
|
|
* is done, so it is safe to process completions here. If a job has
|
|
* interrupts enabled, then the completions will normally be processed
|
|
* in the tasklet that is triggered by the interrupt and tx_status()
|
|
* does not need to be called.
|
|
*/
|
|
switchtec_dma_cleanup_completed(swdma_chan);
|
|
|
|
return dma_cookie_status(chan, cookie, txstate);
|
|
}
|
|
|
|
static void switchtec_dma_issue_pending(struct dma_chan *chan)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan =
|
|
container_of(chan, struct switchtec_dma_chan, dma_chan);
|
|
struct switchtec_dma_dev *swdma_dev = swdma_chan->swdma_dev;
|
|
|
|
/*
|
|
* The sq_tail register is actually for the head of the
|
|
* submisssion queue. Chip has the opposite define of head/tail
|
|
* to the Linux kernel.
|
|
*/
|
|
|
|
rcu_read_lock();
|
|
if (!rcu_dereference(swdma_dev->pdev)) {
|
|
rcu_read_unlock();
|
|
return;
|
|
}
|
|
|
|
spin_lock_bh(&swdma_chan->submit_lock);
|
|
writew(swdma_chan->head, &swdma_chan->mmio_chan_hw->sq_tail);
|
|
spin_unlock_bh(&swdma_chan->submit_lock);
|
|
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static int switchtec_dma_pause(struct dma_chan *chan)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan =
|
|
container_of(chan, struct switchtec_dma_chan, dma_chan);
|
|
struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw;
|
|
struct pci_dev *pdev;
|
|
int ret;
|
|
|
|
rcu_read_lock();
|
|
pdev = rcu_dereference(swdma_chan->swdma_dev->pdev);
|
|
if (!pdev) {
|
|
ret = -ENODEV;
|
|
goto unlock_and_exit;
|
|
}
|
|
|
|
spin_lock(&swdma_chan->hw_ctrl_lock);
|
|
writeb(SWITCHTEC_CHAN_CTRL_PAUSE, &chan_hw->ctrl);
|
|
ret = wait_for_chan_status(chan_hw, SWITCHTEC_CHAN_STS_PAUSED, true);
|
|
spin_unlock(&swdma_chan->hw_ctrl_lock);
|
|
|
|
unlock_and_exit:
|
|
rcu_read_unlock();
|
|
return ret;
|
|
}
|
|
|
|
static int switchtec_dma_resume(struct dma_chan *chan)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan =
|
|
container_of(chan, struct switchtec_dma_chan, dma_chan);
|
|
struct chan_hw_regs __iomem *chan_hw = swdma_chan->mmio_chan_hw;
|
|
struct pci_dev *pdev;
|
|
int ret;
|
|
|
|
rcu_read_lock();
|
|
pdev = rcu_dereference(swdma_chan->swdma_dev->pdev);
|
|
if (!pdev) {
|
|
ret = -ENODEV;
|
|
goto unlock_and_exit;
|
|
}
|
|
|
|
spin_lock(&swdma_chan->hw_ctrl_lock);
|
|
writeb(0, &chan_hw->ctrl);
|
|
ret = wait_for_chan_status(chan_hw, SWITCHTEC_CHAN_STS_PAUSED, false);
|
|
spin_unlock(&swdma_chan->hw_ctrl_lock);
|
|
|
|
unlock_and_exit:
|
|
rcu_read_unlock();
|
|
return ret;
|
|
}
|
|
|
|
static void switchtec_dma_desc_task(unsigned long data)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan = (void *)data;
|
|
|
|
switchtec_dma_cleanup_completed(swdma_chan);
|
|
}
|
|
|
|
static irqreturn_t switchtec_dma_isr(int irq, void *chan)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan = chan;
|
|
|
|
if (swdma_chan->comp_ring_active)
|
|
tasklet_schedule(&swdma_chan->desc_task);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t switchtec_dma_chan_status_isr(int irq, void *dma)
|
|
{
|
|
struct switchtec_dma_dev *swdma_dev = dma;
|
|
struct dma_device *dma_dev = &swdma_dev->dma_dev;
|
|
struct switchtec_dma_chan *swdma_chan;
|
|
struct chan_hw_regs __iomem *chan_hw;
|
|
struct device *chan_dev;
|
|
struct dma_chan *chan;
|
|
u32 chan_status;
|
|
int bit;
|
|
|
|
list_for_each_entry(chan, &dma_dev->channels, device_node) {
|
|
swdma_chan = container_of(chan, struct switchtec_dma_chan,
|
|
dma_chan);
|
|
chan_dev = &swdma_chan->dma_chan.dev->device;
|
|
chan_hw = swdma_chan->mmio_chan_hw;
|
|
|
|
rcu_read_lock();
|
|
if (!rcu_dereference(swdma_dev->pdev)) {
|
|
rcu_read_unlock();
|
|
goto out;
|
|
}
|
|
|
|
chan_status = readl(&chan_hw->status);
|
|
chan_status &= SWITCHTEC_CHAN_STS_PAUSED_MASK;
|
|
rcu_read_unlock();
|
|
|
|
bit = ffs(chan_status);
|
|
if (!bit)
|
|
dev_dbg(chan_dev, "No pause bit set.\n");
|
|
else
|
|
dev_err(chan_dev, "Paused, %s\n",
|
|
channel_status_str[bit - 1]);
|
|
}
|
|
|
|
out:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void switchtec_dma_free_desc(struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
struct switchtec_dma_dev *swdma_dev = swdma_chan->swdma_dev;
|
|
size_t size;
|
|
int i;
|
|
|
|
size = SWITCHTEC_DMA_SQ_SIZE * sizeof(*swdma_chan->hw_sq);
|
|
if (swdma_chan->hw_sq)
|
|
dma_free_coherent(swdma_dev->dma_dev.dev, size,
|
|
swdma_chan->hw_sq, swdma_chan->dma_addr_sq);
|
|
|
|
size = SWITCHTEC_DMA_CQ_SIZE * sizeof(*swdma_chan->hw_cq);
|
|
if (swdma_chan->hw_cq)
|
|
dma_free_coherent(swdma_dev->dma_dev.dev, size,
|
|
swdma_chan->hw_cq, swdma_chan->dma_addr_cq);
|
|
|
|
for (i = 0; i < SWITCHTEC_DMA_RING_SIZE; i++)
|
|
kfree(swdma_chan->desc_ring[i]);
|
|
}
|
|
|
|
static int switchtec_dma_alloc_desc(struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
struct switchtec_dma_dev *swdma_dev = swdma_chan->swdma_dev;
|
|
struct chan_fw_regs __iomem *chan_fw = swdma_chan->mmio_chan_fw;
|
|
struct switchtec_dma_desc *desc;
|
|
struct pci_dev *pdev;
|
|
size_t size;
|
|
int rc, i;
|
|
|
|
swdma_chan->head = 0;
|
|
swdma_chan->tail = 0;
|
|
swdma_chan->cq_tail = 0;
|
|
|
|
size = SWITCHTEC_DMA_SQ_SIZE * sizeof(*swdma_chan->hw_sq);
|
|
swdma_chan->hw_sq = dma_alloc_coherent(swdma_dev->dma_dev.dev, size,
|
|
&swdma_chan->dma_addr_sq,
|
|
GFP_NOWAIT);
|
|
if (!swdma_chan->hw_sq) {
|
|
rc = -ENOMEM;
|
|
goto free_and_exit;
|
|
}
|
|
|
|
size = SWITCHTEC_DMA_CQ_SIZE * sizeof(*swdma_chan->hw_cq);
|
|
swdma_chan->hw_cq = dma_alloc_coherent(swdma_dev->dma_dev.dev, size,
|
|
&swdma_chan->dma_addr_cq,
|
|
GFP_NOWAIT);
|
|
if (!swdma_chan->hw_cq) {
|
|
rc = -ENOMEM;
|
|
goto free_and_exit;
|
|
}
|
|
|
|
/* reset host phase tag */
|
|
swdma_chan->phase_tag = 0;
|
|
|
|
for (i = 0; i < SWITCHTEC_DMA_RING_SIZE; i++) {
|
|
desc = kzalloc_obj(*desc, GFP_NOWAIT);
|
|
if (!desc) {
|
|
rc = -ENOMEM;
|
|
goto free_and_exit;
|
|
}
|
|
|
|
dma_async_tx_descriptor_init(&desc->txd, &swdma_chan->dma_chan);
|
|
desc->txd.tx_submit = switchtec_dma_tx_submit;
|
|
desc->hw = &swdma_chan->hw_sq[i];
|
|
desc->completed = true;
|
|
|
|
swdma_chan->desc_ring[i] = desc;
|
|
}
|
|
|
|
rcu_read_lock();
|
|
pdev = rcu_dereference(swdma_dev->pdev);
|
|
if (!pdev) {
|
|
rcu_read_unlock();
|
|
rc = -ENODEV;
|
|
goto free_and_exit;
|
|
}
|
|
|
|
/* set sq/cq */
|
|
writel(lower_32_bits(swdma_chan->dma_addr_sq), &chan_fw->sq_base_lo);
|
|
writel(upper_32_bits(swdma_chan->dma_addr_sq), &chan_fw->sq_base_hi);
|
|
writel(lower_32_bits(swdma_chan->dma_addr_cq), &chan_fw->cq_base_lo);
|
|
writel(upper_32_bits(swdma_chan->dma_addr_cq), &chan_fw->cq_base_hi);
|
|
|
|
writew(SWITCHTEC_DMA_SQ_SIZE, &swdma_chan->mmio_chan_fw->sq_size);
|
|
writew(SWITCHTEC_DMA_CQ_SIZE, &swdma_chan->mmio_chan_fw->cq_size);
|
|
|
|
rcu_read_unlock();
|
|
return 0;
|
|
|
|
free_and_exit:
|
|
switchtec_dma_free_desc(swdma_chan);
|
|
return rc;
|
|
}
|
|
|
|
static int switchtec_dma_alloc_chan_resources(struct dma_chan *chan)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan =
|
|
container_of(chan, struct switchtec_dma_chan, dma_chan);
|
|
struct switchtec_dma_dev *swdma_dev = swdma_chan->swdma_dev;
|
|
u32 perf_cfg;
|
|
int rc;
|
|
|
|
rc = switchtec_dma_alloc_desc(swdma_chan);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = enable_channel(swdma_chan);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = reset_channel(swdma_chan);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = unhalt_channel(swdma_chan);
|
|
if (rc)
|
|
return rc;
|
|
|
|
swdma_chan->ring_active = true;
|
|
swdma_chan->comp_ring_active = true;
|
|
swdma_chan->cid = 0;
|
|
|
|
dma_cookie_init(chan);
|
|
|
|
rcu_read_lock();
|
|
if (!rcu_dereference(swdma_dev->pdev)) {
|
|
rcu_read_unlock();
|
|
return -ENODEV;
|
|
}
|
|
|
|
perf_cfg = readl(&swdma_chan->mmio_chan_fw->perf_cfg);
|
|
rcu_read_unlock();
|
|
|
|
dev_dbg(&chan->dev->device, "Burst Size: 0x%x\n",
|
|
FIELD_GET(PERF_BURST_SIZE_MASK, perf_cfg));
|
|
|
|
dev_dbg(&chan->dev->device, "Burst Scale: 0x%x\n",
|
|
FIELD_GET(PERF_BURST_SCALE_MASK, perf_cfg));
|
|
|
|
dev_dbg(&chan->dev->device, "Interval: 0x%x\n",
|
|
FIELD_GET(PERF_INTERVAL_MASK, perf_cfg));
|
|
|
|
dev_dbg(&chan->dev->device, "Arb Weight: 0x%x\n",
|
|
FIELD_GET(PERF_ARB_WEIGHT_MASK, perf_cfg));
|
|
|
|
dev_dbg(&chan->dev->device, "MRRS: 0x%x\n",
|
|
FIELD_GET(PERF_MRRS_MASK, perf_cfg));
|
|
|
|
return SWITCHTEC_DMA_SQ_SIZE;
|
|
}
|
|
|
|
static void switchtec_dma_free_chan_resources(struct dma_chan *chan)
|
|
{
|
|
struct switchtec_dma_chan *swdma_chan =
|
|
container_of(chan, struct switchtec_dma_chan, dma_chan);
|
|
|
|
spin_lock_bh(&swdma_chan->submit_lock);
|
|
swdma_chan->ring_active = false;
|
|
spin_unlock_bh(&swdma_chan->submit_lock);
|
|
|
|
spin_lock_bh(&swdma_chan->complete_lock);
|
|
swdma_chan->comp_ring_active = false;
|
|
spin_unlock_bh(&swdma_chan->complete_lock);
|
|
|
|
switchtec_dma_chan_stop(swdma_chan);
|
|
switchtec_dma_abort_desc(swdma_chan, 0);
|
|
switchtec_dma_free_desc(swdma_chan);
|
|
|
|
disable_channel(swdma_chan);
|
|
}
|
|
|
|
static int switchtec_dma_chan_init(struct switchtec_dma_dev *swdma_dev,
|
|
struct pci_dev *pdev, int i)
|
|
{
|
|
struct dma_device *dma = &swdma_dev->dma_dev;
|
|
struct switchtec_dma_chan *swdma_chan;
|
|
u32 valid_en_se, thresh;
|
|
int se_buf_len, irq, rc;
|
|
struct dma_chan *chan;
|
|
|
|
swdma_chan = kzalloc_obj(*swdma_chan, GFP_KERNEL);
|
|
if (!swdma_chan)
|
|
return -ENOMEM;
|
|
|
|
swdma_chan->phase_tag = 0;
|
|
swdma_chan->index = i;
|
|
swdma_chan->swdma_dev = swdma_dev;
|
|
|
|
spin_lock_init(&swdma_chan->hw_ctrl_lock);
|
|
spin_lock_init(&swdma_chan->submit_lock);
|
|
spin_lock_init(&swdma_chan->complete_lock);
|
|
tasklet_init(&swdma_chan->desc_task, switchtec_dma_desc_task,
|
|
(unsigned long)swdma_chan);
|
|
|
|
swdma_chan->mmio_chan_fw =
|
|
swdma_dev->bar + SWITCHTEC_DMAC_CHAN_CFG_STS_OFFSET +
|
|
i * SWITCHTEC_DMA_CHAN_FW_REGS_SIZE;
|
|
swdma_chan->mmio_chan_hw =
|
|
swdma_dev->bar + SWITCHTEC_DMAC_CHAN_CTRL_OFFSET +
|
|
i * SWITCHTEC_DMA_CHAN_HW_REGS_SIZE;
|
|
|
|
swdma_dev->swdma_chans[i] = swdma_chan;
|
|
|
|
rc = pause_reset_channel(swdma_chan);
|
|
if (rc)
|
|
goto free_and_exit;
|
|
|
|
/* init perf tuner */
|
|
writel(FIELD_PREP(PERF_BURST_SCALE_MASK, 1) |
|
|
FIELD_PREP(PERF_MRRS_MASK, 3) |
|
|
FIELD_PREP(PERF_BURST_SIZE_MASK, 6) |
|
|
FIELD_PREP(PERF_ARB_WEIGHT_MASK, 1),
|
|
&swdma_chan->mmio_chan_fw->perf_cfg);
|
|
|
|
valid_en_se = readl(&swdma_chan->mmio_chan_fw->valid_en_se);
|
|
|
|
dev_dbg(&pdev->dev, "Channel %d: SE buffer base %d\n", i,
|
|
FIELD_GET(SE_BUF_BASE_MASK, valid_en_se));
|
|
|
|
se_buf_len = FIELD_GET(SE_BUF_LEN_MASK, valid_en_se);
|
|
dev_dbg(&pdev->dev, "Channel %d: SE buffer count %d\n", i, se_buf_len);
|
|
|
|
thresh = se_buf_len / 2;
|
|
valid_en_se |= FIELD_PREP(SE_THRESH_MASK, thresh);
|
|
writel(valid_en_se, &swdma_chan->mmio_chan_fw->valid_en_se);
|
|
|
|
/* request irqs */
|
|
irq = readl(&swdma_chan->mmio_chan_fw->int_vec);
|
|
dev_dbg(&pdev->dev, "Channel %d: CE irq vector %d\n", i, irq);
|
|
|
|
rc = pci_request_irq(pdev, irq, switchtec_dma_isr, NULL, swdma_chan,
|
|
KBUILD_MODNAME);
|
|
if (rc)
|
|
goto free_and_exit;
|
|
|
|
swdma_chan->irq = irq;
|
|
|
|
chan = &swdma_chan->dma_chan;
|
|
chan->device = dma;
|
|
dma_cookie_init(chan);
|
|
|
|
list_add_tail(&chan->device_node, &dma->channels);
|
|
|
|
return 0;
|
|
|
|
free_and_exit:
|
|
kfree(swdma_chan);
|
|
return rc;
|
|
}
|
|
|
|
static int switchtec_dma_chan_free(struct pci_dev *pdev,
|
|
struct switchtec_dma_chan *swdma_chan)
|
|
{
|
|
spin_lock_bh(&swdma_chan->submit_lock);
|
|
swdma_chan->ring_active = false;
|
|
spin_unlock_bh(&swdma_chan->submit_lock);
|
|
|
|
spin_lock_bh(&swdma_chan->complete_lock);
|
|
swdma_chan->comp_ring_active = false;
|
|
spin_unlock_bh(&swdma_chan->complete_lock);
|
|
|
|
pci_free_irq(pdev, swdma_chan->irq, swdma_chan);
|
|
tasklet_kill(&swdma_chan->desc_task);
|
|
|
|
switchtec_dma_chan_stop(swdma_chan);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int switchtec_dma_chans_release(struct pci_dev *pdev,
|
|
struct switchtec_dma_dev *swdma_dev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < swdma_dev->chan_cnt; i++)
|
|
switchtec_dma_chan_free(pdev, swdma_dev->swdma_chans[i]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int switchtec_dma_chans_enumerate(struct switchtec_dma_dev *swdma_dev,
|
|
struct pci_dev *pdev, int chan_cnt)
|
|
{
|
|
struct dma_device *dma = &swdma_dev->dma_dev;
|
|
int base, cnt, rc, i;
|
|
|
|
swdma_dev->swdma_chans = kcalloc(chan_cnt, sizeof(*swdma_dev->swdma_chans),
|
|
GFP_KERNEL);
|
|
|
|
if (!swdma_dev->swdma_chans)
|
|
return -ENOMEM;
|
|
|
|
base = readw(swdma_dev->bar + SWITCHTEC_REG_SE_BUF_BASE);
|
|
cnt = readw(swdma_dev->bar + SWITCHTEC_REG_SE_BUF_CNT);
|
|
|
|
dev_dbg(&pdev->dev, "EP SE buffer base %d\n", base);
|
|
dev_dbg(&pdev->dev, "EP SE buffer count %d\n", cnt);
|
|
|
|
INIT_LIST_HEAD(&dma->channels);
|
|
|
|
for (i = 0; i < chan_cnt; i++) {
|
|
rc = switchtec_dma_chan_init(swdma_dev, pdev, i);
|
|
if (rc) {
|
|
dev_err(&pdev->dev, "Channel %d: init channel failed\n",
|
|
i);
|
|
chan_cnt = i;
|
|
goto err_exit;
|
|
}
|
|
}
|
|
|
|
return chan_cnt;
|
|
|
|
err_exit:
|
|
for (i = 0; i < chan_cnt; i++)
|
|
switchtec_dma_chan_free(pdev, swdma_dev->swdma_chans[i]);
|
|
|
|
kfree(swdma_dev->swdma_chans);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void switchtec_dma_release(struct dma_device *dma_dev)
|
|
{
|
|
struct switchtec_dma_dev *swdma_dev =
|
|
container_of(dma_dev, struct switchtec_dma_dev, dma_dev);
|
|
int i;
|
|
|
|
for (i = 0; i < swdma_dev->chan_cnt; i++)
|
|
kfree(swdma_dev->swdma_chans[i]);
|
|
|
|
kfree(swdma_dev->swdma_chans);
|
|
|
|
put_device(dma_dev->dev);
|
|
kfree(swdma_dev);
|
|
}
|
|
|
|
static int switchtec_dma_create(struct pci_dev *pdev)
|
|
{
|
|
struct switchtec_dma_dev *swdma_dev;
|
|
int chan_cnt, nr_vecs, irq, rc;
|
|
struct dma_device *dma;
|
|
struct dma_chan *chan;
|
|
|
|
/*
|
|
* Create the switchtec dma device
|
|
*/
|
|
swdma_dev = kzalloc_obj(*swdma_dev, GFP_KERNEL);
|
|
if (!swdma_dev)
|
|
return -ENOMEM;
|
|
|
|
swdma_dev->bar = ioremap(pci_resource_start(pdev, 0),
|
|
pci_resource_len(pdev, 0));
|
|
|
|
RCU_INIT_POINTER(swdma_dev->pdev, pdev);
|
|
|
|
nr_vecs = pci_msix_vec_count(pdev);
|
|
rc = pci_alloc_irq_vectors(pdev, nr_vecs, nr_vecs, PCI_IRQ_MSIX);
|
|
if (rc < 0)
|
|
goto err_exit;
|
|
|
|
irq = readw(swdma_dev->bar + SWITCHTEC_REG_CHAN_STS_VEC);
|
|
pci_dbg(pdev, "Channel pause irq vector %d\n", irq);
|
|
|
|
rc = pci_request_irq(pdev, irq, NULL, switchtec_dma_chan_status_isr,
|
|
swdma_dev, KBUILD_MODNAME);
|
|
if (rc)
|
|
goto err_exit;
|
|
|
|
swdma_dev->chan_status_irq = irq;
|
|
|
|
chan_cnt = readl(swdma_dev->bar + SWITCHTEC_REG_CHAN_CNT);
|
|
if (!chan_cnt) {
|
|
pci_err(pdev, "No channel configured.\n");
|
|
rc = -ENXIO;
|
|
goto err_exit;
|
|
}
|
|
|
|
chan_cnt = switchtec_dma_chans_enumerate(swdma_dev, pdev, chan_cnt);
|
|
if (chan_cnt < 0) {
|
|
pci_err(pdev, "Failed to enumerate dma channels: %d\n",
|
|
chan_cnt);
|
|
rc = -ENXIO;
|
|
goto err_exit;
|
|
}
|
|
|
|
swdma_dev->chan_cnt = chan_cnt;
|
|
|
|
dma = &swdma_dev->dma_dev;
|
|
dma->copy_align = DMAENGINE_ALIGN_8_BYTES;
|
|
dma_cap_set(DMA_MEMCPY, dma->cap_mask);
|
|
dma_cap_set(DMA_PRIVATE, dma->cap_mask);
|
|
dma->dev = get_device(&pdev->dev);
|
|
|
|
dma->device_alloc_chan_resources = switchtec_dma_alloc_chan_resources;
|
|
dma->device_free_chan_resources = switchtec_dma_free_chan_resources;
|
|
dma->device_prep_dma_memcpy = switchtec_dma_prep_memcpy;
|
|
dma->device_tx_status = switchtec_dma_tx_status;
|
|
dma->device_issue_pending = switchtec_dma_issue_pending;
|
|
dma->device_pause = switchtec_dma_pause;
|
|
dma->device_resume = switchtec_dma_resume;
|
|
dma->device_terminate_all = switchtec_dma_terminate_all;
|
|
dma->device_synchronize = switchtec_dma_synchronize;
|
|
dma->device_release = switchtec_dma_release;
|
|
|
|
rc = dma_async_device_register(dma);
|
|
if (rc) {
|
|
pci_err(pdev, "Failed to register dma device: %d\n", rc);
|
|
goto err_chans_release_exit;
|
|
}
|
|
|
|
pci_dbg(pdev, "Channel count: %d\n", chan_cnt);
|
|
|
|
list_for_each_entry(chan, &dma->channels, device_node)
|
|
pci_dbg(pdev, "%s\n", dma_chan_name(chan));
|
|
|
|
pci_set_drvdata(pdev, swdma_dev);
|
|
|
|
return 0;
|
|
|
|
err_chans_release_exit:
|
|
switchtec_dma_chans_release(pdev, swdma_dev);
|
|
|
|
err_exit:
|
|
if (swdma_dev->chan_status_irq)
|
|
free_irq(swdma_dev->chan_status_irq, swdma_dev);
|
|
|
|
iounmap(swdma_dev->bar);
|
|
kfree(swdma_dev);
|
|
return rc;
|
|
}
|
|
|
|
static int switchtec_dma_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *id)
|
|
{
|
|
int rc;
|
|
|
|
rc = pci_enable_device(pdev);
|
|
if (rc)
|
|
return rc;
|
|
|
|
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
|
|
|
|
rc = pci_request_mem_regions(pdev, KBUILD_MODNAME);
|
|
if (rc)
|
|
goto err_disable;
|
|
|
|
pci_set_master(pdev);
|
|
|
|
rc = switchtec_dma_create(pdev);
|
|
if (rc)
|
|
goto err_free;
|
|
|
|
return 0;
|
|
|
|
err_free:
|
|
pci_free_irq_vectors(pdev);
|
|
pci_release_mem_regions(pdev);
|
|
|
|
err_disable:
|
|
pci_disable_device(pdev);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void switchtec_dma_remove(struct pci_dev *pdev)
|
|
{
|
|
struct switchtec_dma_dev *swdma_dev = pci_get_drvdata(pdev);
|
|
|
|
switchtec_dma_chans_release(pdev, swdma_dev);
|
|
|
|
rcu_assign_pointer(swdma_dev->pdev, NULL);
|
|
synchronize_rcu();
|
|
|
|
pci_free_irq(pdev, swdma_dev->chan_status_irq, swdma_dev);
|
|
|
|
pci_free_irq_vectors(pdev);
|
|
|
|
dma_async_device_unregister(&swdma_dev->dma_dev);
|
|
|
|
iounmap(swdma_dev->bar);
|
|
pci_release_mem_regions(pdev);
|
|
pci_disable_device(pdev);
|
|
}
|
|
|
|
/*
|
|
* Also use the class code to identify the devices, as some of the
|
|
* device IDs are also used for other devices with other classes by
|
|
* Microsemi.
|
|
*/
|
|
#define SW_ID(vendor_id, device_id) \
|
|
{ \
|
|
.vendor = vendor_id, \
|
|
.device = device_id, \
|
|
.subvendor = PCI_ANY_ID, \
|
|
.subdevice = PCI_ANY_ID, \
|
|
.class = PCI_CLASS_SYSTEM_OTHER << 8, \
|
|
.class_mask = 0xffffffff, \
|
|
}
|
|
|
|
static const struct pci_device_id switchtec_dma_pci_tbl[] = {
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4000), /* PFX 100XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4084), /* PFX 84XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4068), /* PFX 68XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4052), /* PFX 52XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4036), /* PFX 36XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4028), /* PFX 28XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4100), /* PSX 100XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4184), /* PSX 84XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4168), /* PSX 68XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4152), /* PSX 52XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4136), /* PSX 36XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4128), /* PSX 28XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4352), /* PFXA 52XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4336), /* PFXA 36XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4328), /* PFXA 28XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4452), /* PSXA 52XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4436), /* PSXA 36XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x4428), /* PSXA 28XG4 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5000), /* PFX 100XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5084), /* PFX 84XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5068), /* PFX 68XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5052), /* PFX 52XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5036), /* PFX 36XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5028), /* PFX 28XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5100), /* PSX 100XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5184), /* PSX 84XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5168), /* PSX 68XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5152), /* PSX 52XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5136), /* PSX 36XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5128), /* PSX 28XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5300), /* PFXA 100XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5384), /* PFXA 84XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5368), /* PFXA 68XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5352), /* PFXA 52XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5336), /* PFXA 36XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5328), /* PFXA 28XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5400), /* PSXA 100XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5484), /* PSXA 84XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5468), /* PSXA 68XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5452), /* PSXA 52XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5436), /* PSXA 36XG5 */
|
|
SW_ID(PCI_VENDOR_ID_MICROSEMI, 0x5428), /* PSXA 28XG5 */
|
|
SW_ID(PCI_VENDOR_ID_EFAR, 0x1001), /* PCI1001 16XG4 */
|
|
SW_ID(PCI_VENDOR_ID_EFAR, 0x1002), /* PCI1002 16XG4 */
|
|
SW_ID(PCI_VENDOR_ID_EFAR, 0x1003), /* PCI1003 16XG4 */
|
|
SW_ID(PCI_VENDOR_ID_EFAR, 0x1004), /* PCI1004 16XG4 */
|
|
SW_ID(PCI_VENDOR_ID_EFAR, 0x1005), /* PCI1005 16XG4 */
|
|
SW_ID(PCI_VENDOR_ID_EFAR, 0x1006), /* PCI1006 16XG4 */
|
|
{0}
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, switchtec_dma_pci_tbl);
|
|
|
|
static struct pci_driver switchtec_dma_pci_driver = {
|
|
.name = KBUILD_MODNAME,
|
|
.id_table = switchtec_dma_pci_tbl,
|
|
.probe = switchtec_dma_probe,
|
|
.remove = switchtec_dma_remove,
|
|
};
|
|
module_pci_driver(switchtec_dma_pci_driver);
|