1437 lines · 37.0 KB · C
RawDownload
// 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 voidswitchtec_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

Lines 1–500 of 1437.