[RFC PATCH 5/5] PCI: rcar-ep: Add support for DMAC

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



R-Car PCIe controller has an internal DMAC to support data transfer
between Internal Bus -> PCI Express and vice versa.

This patch fills in the required flags and ops for the PCIe EP to
support DMAC transfer.

Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>
---
 drivers/pci/controller/pcie-rcar-ep.c | 227 ++++++++++++++++++++++++++
 drivers/pci/controller/pcie-rcar.h    |  23 +++
 2 files changed, 250 insertions(+)

diff --git a/drivers/pci/controller/pcie-rcar-ep.c b/drivers/pci/controller/pcie-rcar-ep.c
index f9682df1da61..c49b25069328 100644
--- a/drivers/pci/controller/pcie-rcar-ep.c
+++ b/drivers/pci/controller/pcie-rcar-ep.c
@@ -18,6 +18,21 @@
 
 #define RCAR_EPC_MAX_FUNCTIONS		1
 
+#define RCAR_PCIE_MAX_DMAC_BYTE_COUNT		0x7FFFFFFU
+#define RCAR_PCIE_DMAC_BYTE_COUNT_MULTIPLE	8
+#define RCAR_PCIE_DMAC_TIMEOUT			(msecs_to_jiffies(3 * 1000))
+#define RCAR_PCIE_DMAC_DEFAULT_CHANNEL		0
+
+enum rcar_pcie_ep_dmac_xfr_status {
+	RCAR_PCIE_DMA_XFR_SUCCESS,
+	RCAR_PCIE_DMA_XFR_ERROR,
+};
+
+struct rcar_pcie_ep_dmac_info {
+	enum rcar_pcie_ep_dmac_xfr_status status;
+	size_t bytes;
+};
+
 /* Structure representing the PCIe interface */
 struct rcar_pcie_endpoint {
 	struct rcar_pcie	pcie;
@@ -28,8 +43,114 @@ struct rcar_pcie_endpoint {
 	unsigned long		*ib_window_map;
 	u32			num_ib_windows;
 	u32			num_ob_windows;
+	struct completion	irq_raised;
+	struct mutex		dma_operation;
+	spinlock_t		lock;
+	struct rcar_pcie_ep_dmac_info xfr;
 };
 
+static inline bool rcar_pcie_ep_is_dmac_active(struct rcar_pcie_endpoint *ep)
+{
+	if (rcar_pci_read_reg(&ep->pcie, PCIEDMAOR) & PCIEDMAOR_DMAACT)
+		return true;
+
+	return false;
+}
+
+static void
+rcar_pcie_ep_setup_dmac_request(struct rcar_pcie_endpoint *ep,
+				dma_addr_t dma_dst, dma_addr_t dma_src,
+				size_t len, enum pci_epf_xfr_direction dir, u8 ch)
+{
+	struct rcar_pcie *pcie = &ep->pcie;
+	u32 val;
+
+	ep->xfr.status = RCAR_PCIE_DMA_XFR_ERROR;
+	ep->xfr.bytes = RCAR_PCIE_MAX_DMAC_BYTE_COUNT;
+
+	/* swap values if xfr is from pcie to internal */
+	if (dir == PCIE_TO_INTERNAL)
+		swap(dma_dst, dma_src);
+
+	/* Configure the PCI Express lower */
+	rcar_pci_write_reg(pcie, lower_32_bits(dma_dst), PCIEDMPALR(ch));
+
+	/* Configure the PCI Express upper */
+	rcar_pci_write_reg(pcie, upper_32_bits(dma_dst), PCIEDMPAUR(ch));
+
+	/* Configure the internal bus address */
+	rcar_pci_write_reg(pcie, lower_32_bits(dma_src), PCIEDMIAR(ch));
+
+	/* Configure the byte count values */
+	rcar_pci_write_reg(pcie, len, PCIEDMBCNTR(ch));
+
+	/* Enable interrupts */
+	val = rcar_pci_read_reg(pcie, PCIEDMCHSR(ch));
+
+	/* set enable flags */
+	val |= PCIEDMCHSR_IE;
+	val |= PCIEDMCHSR_IBEE;
+	val |= PCIEDMCHSR_PEEE;
+	val |= PCIEDMCHSR_CHTCE;
+
+	/* Clear error flags */
+	val &= ~PCIEDMCHSR_TE;
+	val &= ~PCIEDMCHSR_PEE;
+	val &= ~PCIEDMCHSR_IBE;
+	val &= ~PCIEDMCHSR_CHTC;
+
+	rcar_pci_write_reg(pcie, val, PCIEDMCHSR(ch));
+
+	wmb(); /* flush the settings */
+}
+
+static void rcar_pcie_ep_execute_dmac_request(struct rcar_pcie_endpoint *ep,
+					      enum pci_epf_xfr_direction dir, u8 ch)
+{
+	struct rcar_pcie *pcie = &ep->pcie;
+	u32 val;
+
+	/* Enable DMA */
+	val = rcar_pci_read_reg(pcie, PCIEDMAOR);
+	val |= PCIEDMAOR_DMAE;
+	rcar_pci_write_reg(pcie, val, PCIEDMAOR);
+
+	/* Configure the DMA direction */
+	val = rcar_pci_read_reg(pcie, PCIEDMCHCR(ch));
+	if (dir == INTERNAL_TO_PCIE)
+		val |= PCIEDMCHCR_DIR;
+	else
+		val &= ~PCIEDMCHCR_DIR;
+
+	val |= PCIEDMCHCR_CHE;
+	rcar_pci_write_reg(pcie, val, PCIEDMCHCR(ch));
+
+	wmb(); /* flush the settings */
+}
+
+static enum rcar_pcie_ep_dmac_xfr_status
+rcar_pcie_ep_get_dmac_status(struct rcar_pcie_endpoint *ep,
+			     size_t *count, u8 ch)
+{
+	*count = ep->xfr.bytes;
+	return ep->xfr.status;
+}
+
+static void rcar_pcie_ep_stop_dmac_request(struct rcar_pcie_endpoint *ep, u8 ch)
+{
+	struct rcar_pcie *pcie = &ep->pcie;
+	u32 val;
+
+	val = rcar_pci_read_reg(pcie, PCIEDMCHCR(ch));
+	val &= ~PCIEDMCHCR_CHE;
+	rcar_pci_write_reg(pcie, val, PCIEDMCHCR(ch));
+
+	/* Disable interrupt */
+	val = rcar_pci_read_reg(pcie, PCIEDMAOR);
+	val &= ~PCIEDMAOR_DMAE;
+	rcar_pci_write_reg(pcie, val, PCIEDMAOR);
+}
+
 static void rcar_pcie_ep_hw_init(struct rcar_pcie *pcie)
 {
 	u32 val;
@@ -419,6 +540,44 @@ static int rcar_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, u8 vfn,
 	}
 }
 
+static int rcar_pcie_ep_data_transfer(struct pci_epc *epc, struct pci_epf *epf,
+				      dma_addr_t dma_dst, dma_addr_t dma_src,
+				      size_t len, enum pci_epf_xfr_direction dir)
+{
+	struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc);
+	u8 ch = RCAR_PCIE_DMAC_DEFAULT_CHANNEL;
+	enum rcar_pcie_ep_dmac_xfr_status stat;
+	int ret = -EINVAL;
+	long wait_status;
+	size_t count;
+
+	if (len > RCAR_PCIE_MAX_DMAC_BYTE_COUNT ||
+	    (len % RCAR_PCIE_DMAC_BYTE_COUNT_MULTIPLE) != 0)
+		return -EINVAL;
+
+	if (mutex_is_locked(&ep->dma_operation) || rcar_pcie_ep_is_dmac_active(ep))
+		return -EBUSY;
+
+	mutex_lock(&ep->dma_operation);
+
+	rcar_pcie_ep_setup_dmac_request(ep, dma_dst, dma_src, len, dir, ch);
+
+	rcar_pcie_ep_execute_dmac_request(ep, dir, ch);
+
+	wait_status = wait_for_completion_interruptible_timeout(&ep->irq_raised,
+								RCAR_PCIE_DMAC_TIMEOUT);
+	if (wait_status <= 0) {
+		rcar_pcie_ep_stop_dmac_request(ep, ch);
+	} else {
+		stat = rcar_pcie_ep_get_dmac_status(ep, &count, ch);
+		if (stat == RCAR_PCIE_DMA_XFR_SUCCESS && !count)
+			ret = 0;
+	}
+
+	mutex_unlock(&ep->dma_operation);
+	return ret;
+}
+
 static int rcar_pcie_ep_start(struct pci_epc *epc)
 {
 	struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc);
@@ -429,6 +588,55 @@ static int rcar_pcie_ep_start(struct pci_epc *epc)
 	return 0;
 }
 
+static irqreturn_t rcar_pcie_ep_dmac_irq_handler(int irq, void *arg)
+{
+	u8 ch = RCAR_PCIE_DMAC_DEFAULT_CHANNEL;
+	struct rcar_pcie_endpoint *ep = arg;
+	struct rcar_pcie *pcie = &ep->pcie;
+	unsigned long flags;
+	u32 chsr_val;
+	u32 chcr_val;
+	u32 bytes;
+
+	spin_lock_irqsave(&ep->lock, flags);
+
+	chsr_val = rcar_pci_read_reg(pcie, PCIEDMCHSR(ch));
+
+	chcr_val = rcar_pci_read_reg(pcie, PCIEDMCHCR(ch));
+
+	if (mutex_is_locked(&ep->dma_operation)) {
+		if ((chsr_val &  PCIEDMCHSR_PEE) ||
+		    (chsr_val & PCIEDMCHSR_IBE) ||
+		    (chsr_val & PCIEDMCHSR_CHTC))
+			ep->xfr.status = RCAR_PCIE_DMA_XFR_ERROR;
+		else if (chsr_val & PCIEDMCHSR_TE)
+			ep->xfr.status = RCAR_PCIE_DMA_XFR_SUCCESS;
+
+		/* get byte count */
+		bytes = rcar_pci_read_reg(pcie, PCIEDMBCNTR(ch));
+		ep->xfr.bytes = bytes;
+
+		if ((chsr_val & PCIEDMCHSR_PEE) || (chsr_val & PCIEDMCHSR_IBE) ||
+		    (chsr_val & PCIEDMCHSR_TE) || (chsr_val & PCIEDMCHSR_CHTC)) {
+			complete(&ep->irq_raised);
+		}
+	} else {
+		spin_unlock_irqrestore(&ep->lock, flags);
+		return IRQ_NONE;
+	}
+
+	if (chcr_val & PCIEDMCHCR_CHE)
+		chcr_val &= ~PCIEDMCHCR_CHE;
+	rcar_pci_write_reg(pcie, chcr_val, PCIEDMCHCR(ch));
+
+	/* Clear DMA interrupt source */
+	rcar_pci_write_reg(pcie, chsr_val, PCIEDMCHSR(ch));
+
+	spin_unlock_irqrestore(&ep->lock, flags);
+
+	return IRQ_HANDLED;
+}
+
 static void rcar_pcie_ep_stop(struct pci_epc *epc)
 {
 	struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc);
@@ -446,6 +654,8 @@ static const struct pci_epc_features rcar_pcie_epc_features = {
 	.bar_fixed_size[0] = 128,
 	.bar_fixed_size[2] = 256,
 	.bar_fixed_size[4] = 256,
+	.internal_dmac = true,
+	.internal_dmac_mask = DMA_BIT_MASK(32),
 };
 
 static const struct pci_epc_features*
@@ -466,6 +676,7 @@ static const struct pci_epc_ops rcar_pcie_epc_ops = {
 	.start		= rcar_pcie_ep_start,
 	.stop		= rcar_pcie_ep_stop,
 	.get_features	= rcar_pcie_ep_get_features,
+	.dmac_transfer	= rcar_pcie_ep_data_transfer,
 };
 
 static const struct of_device_id rcar_pcie_ep_of_match[] = {
@@ -480,6 +691,7 @@ static int rcar_pcie_ep_probe(struct platform_device *pdev)
 	struct rcar_pcie_endpoint *ep;
 	struct rcar_pcie *pcie;
 	struct pci_epc *epc;
+	int dmac_irq;
 	int err;
 
 	ep = devm_kzalloc(dev, sizeof(*ep), GFP_KERNEL);
@@ -502,6 +714,14 @@ static int rcar_pcie_ep_probe(struct platform_device *pdev)
 		goto err_pm_put;
 	}
 
+	dmac_irq = platform_get_irq(pdev, 1);
+	if (dmac_irq < 0)
+		goto err_pm_put;
+
+	init_completion(&ep->irq_raised);
+	mutex_init(&ep->dma_operation);
+	spin_lock_init(&ep->lock);
+
 	ep->num_ib_windows = MAX_NR_INBOUND_MAPS;
 	ep->ib_window_map =
 			devm_kcalloc(dev, BITS_TO_LONGS(ep->num_ib_windows),
@@ -533,6 +753,13 @@ static int rcar_pcie_ep_probe(struct platform_device *pdev)
 
 	rcar_pcie_ep_hw_init(pcie);
 
+	err = devm_request_irq(dev, dmac_irq, rcar_pcie_ep_dmac_irq_handler,
+			       0, "pcie-rcar-ep-dmac", pcie);
+	if (err) {
+		dev_err(dev, "failed to request dmac irq\n");
+		goto err_pm_put;
+	}
+
 	err = pci_epc_multi_mem_init(epc, ep->ob_window, ep->num_ob_windows);
 	if (err < 0) {
 		dev_err(dev, "failed to initialize the epc memory space\n");
diff --git a/drivers/pci/controller/pcie-rcar.h b/drivers/pci/controller/pcie-rcar.h
index 9bb125db85c6..874f8a384e6d 100644
--- a/drivers/pci/controller/pcie-rcar.h
+++ b/drivers/pci/controller/pcie-rcar.h
@@ -54,6 +54,29 @@
 #define  PAR_ENABLE		BIT(31)
 #define  IO_SPACE		BIT(8)
 
+/* PCIe DMAC control reg & mask */
+#define PCIEDMAOR		0x04000
+#define  PCIEDMAOR_DMAE		BIT(31)
+#define  PCIEDMAOR_DMAACT	BIT(16)
+#define PCIEDMPALR(x)		(0x04100 + ((x) * 0x40))
+#define PCIEDMPAUR(x)		(0x04104 + ((x) * 0x40))
+#define PCIEDMIAR(x)		(0x04108 + ((x) * 0x40))
+#define PCIEDMBCNTR(x)		(0x04110 + ((x) * 0x40))
+#define PCIEDMCCAR(x)		(0x04120 + ((x) * 0x40))
+#define PCIEDMCHCR(x)		(0x04128 + ((x) * 0x40))
+#define  PCIEDMCHCR_CHE		BIT(31)
+#define  PCIEDMCHCR_DIR		BIT(30)
+#define PCIEDMCHSR(x)		(0x0412c + ((x) * 0x40))
+#define  PCIEDMCHSR_CHTCE	BIT(28)
+#define  PCIEDMCHSR_PEEE	BIT(27)
+#define  PCIEDMCHSR_IBEE	BIT(25)
+#define  PCIEDMCHSR_CHTC	BIT(12)
+#define  PCIEDMCHSR_PEE		BIT(11)
+#define  PCIEDMCHSR_IBE		BIT(9)
+#define  PCIEDMCHSR_IE		BIT(3)
+#define  PCIEDMCHSR_TE		BIT(0)
+#define PCIEDMCHC2R(x)		(0x04130 + ((x) * 0x40))
+
 /* Configuration */
 #define PCICONF(x)		(0x010000 + ((x) * 0x4))
 #define  INTDIS			BIT(10)
-- 
2.25.1




[Index of Archives]     [DMA Engine]     [Linux Coverity]     [Linux USB]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Greybus]

  Powered by Linux